Report.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  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. </template>
  190. </modal>
  191. <view-report v-if="modals.viewReport" />
  192. </div>
  193. </template>
  194. <script>
  195. import { mapState, mapGetters, mapActions } from "vuex";
  196. import Toast from "toasters";
  197. import ViewReport from "@/components/modals/ViewReport.vue";
  198. import SongItem from "@/components/SongItem.vue";
  199. import ReportInfoItem from "@/components/ReportInfoItem.vue";
  200. import Modal from "../Modal.vue";
  201. export default {
  202. components: { Modal, ViewReport, SongItem, ReportInfoItem },
  203. data() {
  204. return {
  205. icons: {
  206. duration: "timer",
  207. video: "tv",
  208. thumbnail: "image",
  209. artists: "record_voice_over",
  210. title: "title",
  211. custom: "lightbulb"
  212. },
  213. existingReports: [],
  214. customIssues: [],
  215. predefinedCategories: [
  216. {
  217. category: "video",
  218. issues: [
  219. {
  220. enabled: false,
  221. title: "Doesn't exist",
  222. description: "",
  223. showDescription: false
  224. },
  225. {
  226. enabled: false,
  227. title: "It's private",
  228. description: "",
  229. showDescription: false
  230. },
  231. {
  232. enabled: false,
  233. title: "It's not available in my country",
  234. description: "",
  235. showDescription: false
  236. },
  237. {
  238. enabled: false,
  239. title: "Unofficial",
  240. description: "",
  241. showDescription: false
  242. }
  243. ]
  244. },
  245. {
  246. category: "title",
  247. issues: [
  248. {
  249. enabled: false,
  250. title: "Incorrect",
  251. description: "",
  252. showDescription: false
  253. },
  254. {
  255. enabled: false,
  256. title: "Inappropriate",
  257. description: "",
  258. showDescription: false
  259. }
  260. ]
  261. },
  262. {
  263. category: "duration",
  264. issues: [
  265. {
  266. enabled: false,
  267. title: "Skips too soon",
  268. description: "",
  269. showDescription: false
  270. },
  271. {
  272. enabled: false,
  273. title: "Skips too late",
  274. description: "",
  275. showDescription: false
  276. },
  277. {
  278. enabled: false,
  279. title: "Starts too soon",
  280. description: "",
  281. showDescription: false
  282. },
  283. {
  284. enabled: false,
  285. title: "Starts too late",
  286. description: "",
  287. showDescription: false
  288. }
  289. ]
  290. },
  291. {
  292. category: "artists",
  293. issues: [
  294. {
  295. enabled: false,
  296. title: "Incorrect",
  297. description: "",
  298. showDescription: false
  299. },
  300. {
  301. enabled: false,
  302. title: "Inappropriate",
  303. description: "",
  304. showDescription: false
  305. }
  306. ]
  307. },
  308. {
  309. category: "thumbnail",
  310. issues: [
  311. {
  312. enabled: false,
  313. title: "Incorrect",
  314. description: "",
  315. showDescription: false
  316. },
  317. {
  318. enabled: false,
  319. title: "Inappropriate",
  320. description: "",
  321. showDescription: false
  322. },
  323. {
  324. enabled: false,
  325. title: "Doesn't exist",
  326. description: "",
  327. showDescription: false
  328. }
  329. ]
  330. }
  331. ]
  332. };
  333. },
  334. computed: {
  335. ...mapState({
  336. song: state => state.modals.report.song
  337. }),
  338. ...mapState("modalVisibility", {
  339. modals: state => state.modals
  340. }),
  341. ...mapGetters({
  342. socket: "websockets/getSocket"
  343. })
  344. },
  345. mounted() {
  346. this.socket.dispatch("reports.myReportsForSong", this.song._id, res => {
  347. if (res.status === "success") {
  348. this.existingReports = res.data.reports;
  349. this.existingReports.forEach(report =>
  350. this.socket.dispatch(
  351. "apis.joinRoom",
  352. `view-report.${report._id}`
  353. )
  354. );
  355. }
  356. });
  357. this.socket.on(
  358. "event:admin.report.resolved",
  359. res => {
  360. this.existingReports = this.existingReports.filter(
  361. report => report._id !== res.data.reportId
  362. );
  363. },
  364. { modal: "report" }
  365. );
  366. },
  367. methods: {
  368. view(reportId) {
  369. this.viewReport(reportId);
  370. this.openModal("viewReport");
  371. },
  372. create() {
  373. const issues = [];
  374. // any predefined issues that are enabled
  375. this.predefinedCategories.forEach(category =>
  376. category.issues.forEach(issue => {
  377. if (issue.enabled)
  378. issues.push({
  379. category: category.category,
  380. title: issue.title,
  381. description: issue.description
  382. });
  383. })
  384. );
  385. // any custom issues
  386. this.customIssues.forEach(issue =>
  387. issues.push({ category: "custom", title: issue })
  388. );
  389. if (issues.length === 0)
  390. return new Toast("Reports must have at least one issue");
  391. return this.socket.dispatch(
  392. "reports.create",
  393. {
  394. issues,
  395. youtubeId: this.song.youtubeId
  396. },
  397. res => {
  398. new Toast(res.message);
  399. if (res.status === "success") this.closeModal("report");
  400. }
  401. );
  402. },
  403. ...mapActions("modalVisibility", ["openModal", "closeModal"]),
  404. ...mapActions("modals/viewReport", ["viewReport"])
  405. }
  406. };
  407. </script>
  408. <style lang="scss">
  409. .report-modal {
  410. .modal-card {
  411. width: 1050px;
  412. }
  413. .song-item {
  414. .thumbnail {
  415. min-width: 130px;
  416. width: 130px;
  417. height: 130px;
  418. }
  419. }
  420. }
  421. </style>
  422. <style lang="scss" scoped>
  423. .night-mode {
  424. @media screen and (max-width: 900px) {
  425. #right-part {
  426. background-color: var(--dark-grey-3) !important;
  427. }
  428. }
  429. .columns {
  430. background-color: var(--dark-grey-3) !important;
  431. border-radius: 5px;
  432. }
  433. }
  434. .report-modal-inner-container {
  435. display: flex;
  436. @media screen and (max-width: 900px) {
  437. flex-wrap: wrap-reverse;
  438. #left-part {
  439. width: 100%;
  440. }
  441. #right-part {
  442. border-left: 0 !important;
  443. margin-left: 0 !important;
  444. width: 100%;
  445. min-width: 0 !important;
  446. margin-bottom: 20px;
  447. padding: 20px;
  448. background-color: var(--light-grey);
  449. }
  450. }
  451. #right-part {
  452. border-left: 1px solid var(--light-grey-3);
  453. padding-left: 20px;
  454. margin-left: 20px;
  455. min-width: 325px;
  456. .report-items {
  457. max-height: 485px;
  458. overflow: auto;
  459. .report-item:not(:first-of-type) {
  460. margin-top: 10px;
  461. }
  462. }
  463. }
  464. }
  465. .label {
  466. text-transform: capitalize;
  467. }
  468. .columns {
  469. margin-left: unset;
  470. margin-right: unset;
  471. margin-top: 20px;
  472. .control {
  473. display: flex;
  474. flex-direction: column;
  475. span.align-horizontally {
  476. width: 100%;
  477. display: flex;
  478. align-items: center;
  479. justify-content: space-between;
  480. span {
  481. display: flex;
  482. }
  483. }
  484. i {
  485. cursor: pointer;
  486. }
  487. input[type="text"] {
  488. height: initial;
  489. margin: 10px 0;
  490. }
  491. }
  492. }
  493. #custom-issues {
  494. height: 100%;
  495. #custom-issues-title {
  496. display: flex;
  497. align-items: center;
  498. justify-content: space-between;
  499. margin-bottom: 15px;
  500. button {
  501. padding: 3px 5px;
  502. height: initial;
  503. }
  504. label {
  505. margin: 0;
  506. }
  507. }
  508. #no-issues-listed {
  509. display: flex;
  510. height: calc(100% - 32px - 15px);
  511. align-items: center;
  512. justify-content: center;
  513. }
  514. .custom-issue {
  515. flex-direction: row;
  516. input {
  517. height: 36px;
  518. margin: 0;
  519. }
  520. }
  521. }
  522. </style>