Report.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  1. <template>
  2. <div>
  3. <modal class="report-modal" title="Report">
  4. <template #body>
  5. <div class="report-modal-inner-container">
  6. <div id="left-part">
  7. <song-item
  8. :song="song"
  9. :duration="false"
  10. :disabled-actions="['report']"
  11. header="Selected Song.."
  12. />
  13. <div class="columns is-multiline">
  14. <div
  15. v-for="category in predefinedCategories"
  16. class="column is-half"
  17. :key="category.category"
  18. >
  19. <label class="label">{{
  20. category.category
  21. }}</label>
  22. <p
  23. v-for="issue in category.issues"
  24. class="control checkbox-control"
  25. :key="issue.title"
  26. >
  27. <span class="align-horizontally">
  28. <span>
  29. <label class="switch">
  30. <input
  31. type="checkbox"
  32. :id="issue.title"
  33. v-model="issue.enabled"
  34. />
  35. <span
  36. class="slider round"
  37. ></span>
  38. </label>
  39. <label :for="issue.title">
  40. <span></span>
  41. <p>{{ issue.title }}</p>
  42. </label>
  43. </span>
  44. <i
  45. class="material-icons"
  46. content="Provide More info"
  47. v-tippy
  48. @click="
  49. issue.showDescription =
  50. !issue.showDescription
  51. "
  52. >
  53. info
  54. </i>
  55. </span>
  56. <input
  57. type="text"
  58. class="input"
  59. v-model="issue.description"
  60. v-if="issue.showDescription"
  61. placeholder="Provide more information..."
  62. @keyup="issue.enabled = true"
  63. />
  64. </p>
  65. </div>
  66. <!-- allow for multiple custom issues with plus/add button and then a input textbox -->
  67. <!-- do away with textbox -->
  68. <div class="column is-half">
  69. <div id="custom-issues">
  70. <div id="custom-issues-title">
  71. <label class="label"
  72. >Issues not listed</label
  73. >
  74. <button
  75. class="button tab-actionable-button"
  76. content="Add an issue that isn't listed"
  77. v-tippy
  78. @click="customIssues.push('')"
  79. >
  80. <i
  81. class="
  82. material-icons
  83. icon-with-button
  84. "
  85. >add</i
  86. >
  87. <span> Add Custom Issue </span>
  88. </button>
  89. </div>
  90. <div
  91. class="
  92. custom-issue
  93. control
  94. is-grouped
  95. input-with-button
  96. "
  97. v-for="(issue, index) in customIssues"
  98. :key="index"
  99. >
  100. <p class="control is-expanded">
  101. <input
  102. type="text"
  103. class="input"
  104. v-model="customIssues[index]"
  105. placeholder="Provide information..."
  106. />
  107. </p>
  108. <p class="control">
  109. <button
  110. class="button is-danger"
  111. content="Remove custom issue"
  112. v-tippy
  113. @click="
  114. customIssues.splice(
  115. index,
  116. 1
  117. )
  118. "
  119. >
  120. <i class="material-icons">
  121. delete
  122. </i>
  123. </button>
  124. </p>
  125. </div>
  126. <p
  127. id="no-issues-listed"
  128. v-if="customIssues.length <= 0"
  129. >
  130. <em>
  131. Add any issues that aren't listed
  132. above.
  133. </em>
  134. </p>
  135. </div>
  136. </div>
  137. </div>
  138. </div>
  139. <div id="right-part" v-if="existingReports.length > 0">
  140. <h4 class="section-title">Previous Reports</h4>
  141. <p class="section-description">
  142. You have made
  143. {{
  144. existingReports.length > 1
  145. ? "multiple reports"
  146. : "a report"
  147. }}
  148. about this song already
  149. </p>
  150. <hr class="section-horizontal-rule" />
  151. <div class="report-items">
  152. <div
  153. class="report-item"
  154. v-for="report in existingReports"
  155. :key="report._id"
  156. >
  157. <report-info-item
  158. :created-at="report.createdAt"
  159. :created-by="report.createdBy"
  160. >
  161. <template #actions>
  162. <i
  163. class="material-icons"
  164. content="View Report"
  165. v-tippy
  166. @click="view(report._id)"
  167. >
  168. open_in_full
  169. </i>
  170. </template>
  171. </report-info-item>
  172. </div>
  173. </div>
  174. </div>
  175. </div>
  176. </template>
  177. <template #footer>
  178. <a class="button is-success" @click="create()" href="#">
  179. <i class="material-icons save-changes">done</i>
  180. <span>&nbsp;Create</span>
  181. </a>
  182. <a
  183. class="button is-danger"
  184. href="#"
  185. @click="closeModal('report')"
  186. >
  187. <span>&nbsp;Cancel</span>
  188. </a>
  189. <div class="right">
  190. <a
  191. v-if="previousSong !== null && song !== previousSong"
  192. class="button is-primary"
  193. @click="reportSong(previousSong)"
  194. >
  195. <span>&nbsp;Report Previous Song</span>
  196. </a>
  197. </div>
  198. </template>
  199. </modal>
  200. <view-report v-if="modals.viewReport" />
  201. </div>
  202. </template>
  203. <script>
  204. import { mapState, mapGetters, mapActions } from "vuex";
  205. import Toast from "toasters";
  206. import ws from "@/ws";
  207. import ViewReport from "@/components/modals/ViewReport.vue";
  208. import SongItem from "@/components/SongItem.vue";
  209. import ReportInfoItem from "@/components/ReportInfoItem.vue";
  210. import Modal from "../Modal.vue";
  211. export default {
  212. components: { Modal, ViewReport, SongItem, ReportInfoItem },
  213. data() {
  214. return {
  215. icons: {
  216. duration: "timer",
  217. video: "tv",
  218. thumbnail: "image",
  219. artists: "record_voice_over",
  220. title: "title",
  221. custom: "lightbulb"
  222. },
  223. existingReports: [],
  224. customIssues: [],
  225. predefinedCategories: [
  226. {
  227. category: "video",
  228. issues: [
  229. {
  230. enabled: false,
  231. title: "Doesn't exist",
  232. description: "",
  233. showDescription: false
  234. },
  235. {
  236. enabled: false,
  237. title: "It's private",
  238. description: "",
  239. showDescription: false
  240. },
  241. {
  242. enabled: false,
  243. title: "It's not available in my country",
  244. description: "",
  245. showDescription: false
  246. },
  247. {
  248. enabled: false,
  249. title: "Unofficial",
  250. description: "",
  251. showDescription: false
  252. }
  253. ]
  254. },
  255. {
  256. category: "title",
  257. issues: [
  258. {
  259. enabled: false,
  260. title: "Incorrect",
  261. description: "",
  262. showDescription: false
  263. },
  264. {
  265. enabled: false,
  266. title: "Inappropriate",
  267. description: "",
  268. showDescription: false
  269. }
  270. ]
  271. },
  272. {
  273. category: "duration",
  274. issues: [
  275. {
  276. enabled: false,
  277. title: "Skips too soon",
  278. description: "",
  279. showDescription: false
  280. },
  281. {
  282. enabled: false,
  283. title: "Skips too late",
  284. description: "",
  285. showDescription: false
  286. },
  287. {
  288. enabled: false,
  289. title: "Starts too soon",
  290. description: "",
  291. showDescription: false
  292. },
  293. {
  294. enabled: false,
  295. title: "Starts too late",
  296. description: "",
  297. showDescription: false
  298. }
  299. ]
  300. },
  301. {
  302. category: "artists",
  303. issues: [
  304. {
  305. enabled: false,
  306. title: "Incorrect",
  307. description: "",
  308. showDescription: false
  309. },
  310. {
  311. enabled: false,
  312. title: "Inappropriate",
  313. description: "",
  314. showDescription: false
  315. }
  316. ]
  317. },
  318. {
  319. category: "thumbnail",
  320. issues: [
  321. {
  322. enabled: false,
  323. title: "Incorrect",
  324. description: "",
  325. showDescription: false
  326. },
  327. {
  328. enabled: false,
  329. title: "Inappropriate",
  330. description: "",
  331. showDescription: false
  332. },
  333. {
  334. enabled: false,
  335. title: "Doesn't exist",
  336. description: "",
  337. showDescription: false
  338. }
  339. ]
  340. }
  341. ]
  342. };
  343. },
  344. computed: {
  345. ...mapState({
  346. song: state => state.modals.report.song,
  347. previousSong: state => state.station.previousSong
  348. }),
  349. ...mapState("modalVisibility", {
  350. modals: state => state.modals
  351. }),
  352. ...mapGetters({
  353. socket: "websockets/getSocket"
  354. })
  355. },
  356. mounted() {
  357. ws.onConnect(this.init);
  358. this.socket.on(
  359. "event:admin.report.resolved",
  360. res => {
  361. this.existingReports = this.existingReports.filter(
  362. report => report._id !== res.data.reportId
  363. );
  364. },
  365. { modal: "report" }
  366. );
  367. },
  368. methods: {
  369. init() {
  370. this.socket.dispatch(
  371. "reports.myReportsForSong",
  372. this.song._id,
  373. res => {
  374. if (res.status === "success") {
  375. this.existingReports = res.data.reports;
  376. this.existingReports.forEach(report =>
  377. this.socket.dispatch(
  378. "apis.joinRoom",
  379. `view-report.${report._id}`
  380. )
  381. );
  382. }
  383. }
  384. );
  385. },
  386. view(reportId) {
  387. this.viewReport(reportId);
  388. this.openModal("viewReport");
  389. },
  390. create() {
  391. const issues = [];
  392. // any predefined issues that are enabled
  393. this.predefinedCategories.forEach(category =>
  394. category.issues.forEach(issue => {
  395. if (issue.enabled)
  396. issues.push({
  397. category: category.category,
  398. title: issue.title,
  399. description: issue.description
  400. });
  401. })
  402. );
  403. // any custom issues
  404. this.customIssues.forEach(issue =>
  405. issues.push({ category: "custom", title: issue })
  406. );
  407. if (issues.length === 0)
  408. return new Toast("Reports must have at least one issue");
  409. return this.socket.dispatch(
  410. "reports.create",
  411. {
  412. issues,
  413. youtubeId: this.song.youtubeId
  414. },
  415. res => {
  416. new Toast(res.message);
  417. if (res.status === "success") this.closeModal("report");
  418. }
  419. );
  420. },
  421. ...mapActions("modalVisibility", ["openModal", "closeModal"]),
  422. ...mapActions("modals/viewReport", ["viewReport"]),
  423. ...mapActions("modals/report", ["reportSong"])
  424. }
  425. };
  426. </script>
  427. <style lang="scss">
  428. .report-modal {
  429. .modal-card {
  430. width: 1050px;
  431. }
  432. .song-item {
  433. .thumbnail {
  434. min-width: 130px;
  435. width: 130px;
  436. height: 130px;
  437. }
  438. }
  439. }
  440. </style>
  441. <style lang="scss" scoped>
  442. .night-mode {
  443. @media screen and (max-width: 900px) {
  444. #right-part {
  445. background-color: var(--dark-grey-3) !important;
  446. }
  447. }
  448. .columns {
  449. background-color: var(--dark-grey-3) !important;
  450. border-radius: 5px;
  451. }
  452. }
  453. .report-modal-inner-container {
  454. display: flex;
  455. @media screen and (max-width: 900px) {
  456. flex-wrap: wrap-reverse;
  457. #left-part {
  458. width: 100%;
  459. }
  460. #right-part {
  461. border-left: 0 !important;
  462. margin-left: 0 !important;
  463. width: 100%;
  464. min-width: 0 !important;
  465. margin-bottom: 20px;
  466. padding: 20px;
  467. background-color: var(--light-grey);
  468. }
  469. }
  470. #right-part {
  471. border-left: 1px solid var(--light-grey-3);
  472. padding-left: 20px;
  473. margin-left: 20px;
  474. min-width: 325px;
  475. .report-items {
  476. max-height: 485px;
  477. overflow: auto;
  478. .report-item:not(:first-of-type) {
  479. margin-top: 10px;
  480. }
  481. }
  482. }
  483. }
  484. .label {
  485. text-transform: capitalize;
  486. }
  487. .columns {
  488. margin-left: unset;
  489. margin-right: unset;
  490. margin-top: 20px;
  491. .control {
  492. display: flex;
  493. flex-direction: column;
  494. span.align-horizontally {
  495. width: 100%;
  496. display: flex;
  497. align-items: center;
  498. justify-content: space-between;
  499. span {
  500. display: flex;
  501. }
  502. }
  503. i {
  504. cursor: pointer;
  505. }
  506. input[type="text"] {
  507. height: initial;
  508. margin: 10px 0;
  509. }
  510. }
  511. }
  512. #custom-issues {
  513. height: 100%;
  514. #custom-issues-title {
  515. display: flex;
  516. align-items: center;
  517. justify-content: space-between;
  518. margin-bottom: 15px;
  519. button {
  520. padding: 3px 5px;
  521. height: initial;
  522. }
  523. label {
  524. margin: 0;
  525. }
  526. }
  527. #no-issues-listed {
  528. display: flex;
  529. height: calc(100% - 32px - 15px);
  530. align-items: center;
  531. justify-content: center;
  532. }
  533. .custom-issue {
  534. flex-direction: row;
  535. input {
  536. height: 36px;
  537. margin: 0;
  538. }
  539. }
  540. }
  541. </style>