Report.vue 11 KB

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