Reports.vue 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. <template>
  2. <div class="reports-tab tabs-container">
  3. <div class="tab-selection">
  4. <button
  5. class="button is-default"
  6. ref="sort-by-report-tab"
  7. :class="{ selected: tab === 'sort-by-report' }"
  8. @click="showTab('sort-by-report')"
  9. >
  10. Sort by Report
  11. </button>
  12. <button
  13. class="button is-default"
  14. ref="sort-by-category-tab"
  15. :class="{ selected: tab === 'sort-by-category' }"
  16. @click="showTab('sort-by-category')"
  17. >
  18. Sort by Category
  19. </button>
  20. </div>
  21. <div class="tab" v-if="tab === 'sort-by-category'">
  22. <div class="report-items" v-if="reports.length > 0">
  23. <div
  24. class="report-item"
  25. v-for="(issues, category) in sortedByCategory"
  26. :key="category"
  27. >
  28. <div class="report-item-header universal-item">
  29. <i
  30. class="material-icons"
  31. :content="category"
  32. v-tippy="{ theme: 'info' }"
  33. >
  34. {{ icons[category] }}
  35. </i>
  36. <p>{{ category }} Issues</p>
  37. </div>
  38. <div class="report-sub-items">
  39. <div
  40. class="report-sub-item report-sub-item-unresolved"
  41. :class="[
  42. 'report',
  43. issue.resolved
  44. ? 'report-sub-item-resolved'
  45. : 'report-sub-item-unresolved'
  46. ]"
  47. v-for="(issue, issueIndex) in issues"
  48. :key="issueIndex"
  49. >
  50. <i
  51. class="
  52. material-icons
  53. duration-icon
  54. report-sub-item-left-icon
  55. "
  56. :content="issue.category"
  57. v-tippy
  58. >
  59. {{ icons[category] }}
  60. </i>
  61. <p class="report-sub-item-info">
  62. <span class="report-sub-item-title">
  63. {{ issue.title }}
  64. </span>
  65. <span
  66. class="report-sub-item-description"
  67. v-if="issue.description"
  68. >
  69. {{ issue.description }}
  70. </span>
  71. </p>
  72. <div
  73. class="
  74. report-sub-item-actions
  75. universal-item-actions
  76. "
  77. >
  78. <i
  79. class="material-icons resolve-icon"
  80. content="Resolve"
  81. v-tippy
  82. v-if="!issue.resolved"
  83. @click="
  84. toggleIssue(issue.reportId, issue._id)
  85. "
  86. >
  87. done
  88. </i>
  89. <i
  90. class="material-icons unresolve-icon"
  91. content="Unresolve"
  92. v-tippy
  93. v-else
  94. @click="
  95. toggleIssue(issue.reportId, issue._id)
  96. "
  97. >
  98. remove
  99. </i>
  100. </div>
  101. </div>
  102. </div>
  103. </div>
  104. </div>
  105. <p class="no-reports" v-else>There are no reports for this song.</p>
  106. </div>
  107. <div class="tab" v-if="tab === 'sort-by-report'">
  108. <div class="report-items" v-if="reports.length > 0">
  109. <div
  110. class="report-item"
  111. v-for="report in reports"
  112. :key="report._id"
  113. >
  114. <report-info-item
  115. :created-at="report.createdAt"
  116. :created-by="report.createdBy"
  117. >
  118. <template #actions>
  119. <i
  120. class="material-icons resolve-icon"
  121. content="Resolve all"
  122. v-tippy
  123. @click="resolve(report._id)"
  124. >
  125. done_all
  126. </i>
  127. </template>
  128. </report-info-item>
  129. <div class="report-sub-items">
  130. <div
  131. class="report-sub-item report-sub-item-unresolved"
  132. :class="[
  133. 'report',
  134. issue.resolved
  135. ? 'report-sub-item-resolved'
  136. : 'report-sub-item-unresolved'
  137. ]"
  138. v-for="(issue, issueIndex) in report.issues"
  139. :key="issueIndex"
  140. >
  141. <i
  142. class="
  143. material-icons
  144. duration-icon
  145. report-sub-item-left-icon
  146. "
  147. :content="issue.category"
  148. v-tippy
  149. >
  150. {{ icons[issue.category] }}
  151. </i>
  152. <p class="report-sub-item-info">
  153. <span class="report-sub-item-title">
  154. {{ issue.title }}
  155. </span>
  156. <span
  157. class="report-sub-item-description"
  158. v-if="issue.description"
  159. >
  160. {{ issue.description }}
  161. </span>
  162. </p>
  163. <div
  164. class="
  165. report-sub-item-actions
  166. universal-item-actions
  167. "
  168. >
  169. <i
  170. class="material-icons resolve-icon"
  171. content="Resolve"
  172. v-tippy
  173. v-if="!issue.resolved"
  174. @click="toggleIssue(report._id, issue._id)"
  175. >
  176. done
  177. </i>
  178. <i
  179. class="material-icons unresolve-icon"
  180. content="Unresolve"
  181. v-tippy
  182. v-else
  183. @click="toggleIssue(report._id, issue._id)"
  184. >
  185. remove
  186. </i>
  187. </div>
  188. </div>
  189. </div>
  190. </div>
  191. </div>
  192. <p class="no-reports" v-else>There are no reports for this song.</p>
  193. </div>
  194. </div>
  195. </template>
  196. <script>
  197. import ReportInfoItem from "@/components/ReportInfoItem.vue";
  198. import { mapState, mapGetters, mapActions } from "vuex";
  199. import Toast from "toasters";
  200. export default {
  201. components: { ReportInfoItem },
  202. data() {
  203. return {
  204. tab: "sort-by-report",
  205. icons: {
  206. duration: "timer",
  207. video: "tv",
  208. thumbnail: "image",
  209. artists: "record_voice_over",
  210. title: "title",
  211. custom: "lightbulb"
  212. }
  213. };
  214. },
  215. computed: {
  216. ...mapState("modals/editSong", {
  217. reports: state => state.reports
  218. }),
  219. ...mapGetters({
  220. socket: "websockets/getSocket"
  221. }),
  222. sortedByCategory() {
  223. const categories = {};
  224. this.reports.forEach(report =>
  225. report.issues.forEach(issue => {
  226. if (categories[issue.category])
  227. categories[issue.category].push({
  228. ...issue,
  229. reportId: report._id
  230. });
  231. else
  232. categories[issue.category] = [
  233. { ...issue, reportId: report._id }
  234. ];
  235. })
  236. );
  237. return categories;
  238. }
  239. },
  240. mounted() {
  241. this.socket.on(
  242. "event:admin.report.created",
  243. res => this.reports.unshift(res.data.report),
  244. { modal: "editSong" }
  245. );
  246. this.socket.on(
  247. "event:admin.report.resolved",
  248. res => this.resolveReport(res.data.reportId),
  249. { modal: "editSong" }
  250. );
  251. this.socket.on(
  252. "event:admin.report.issue.toggled",
  253. res => {
  254. this.reports.forEach((report, index) => {
  255. if (report._id === res.data.reportId) {
  256. const issue = this.reports[index].issues.find(
  257. issue => issue._id.toString() === res.data.issueId
  258. );
  259. issue.resolved = res.data.resolved;
  260. }
  261. });
  262. },
  263. { modal: "editSong" }
  264. );
  265. },
  266. methods: {
  267. showTab(tab) {
  268. this.$refs[`${tab}-tab`].scrollIntoView({ block: "nearest" });
  269. this.tab = tab;
  270. },
  271. resolve(reportId) {
  272. this.socket.dispatch(
  273. "reports.resolve",
  274. reportId,
  275. res => new Toast(res.message)
  276. );
  277. },
  278. toggleIssue(reportId, issueId) {
  279. this.socket.dispatch(
  280. "reports.toggleIssue",
  281. reportId,
  282. issueId,
  283. res => {
  284. if (res.status !== "success") new Toast(res.message);
  285. }
  286. );
  287. },
  288. ...mapActions("modals/editSong", ["resolveReport"]),
  289. ...mapActions("modalVisibility", ["closeModal"])
  290. }
  291. };
  292. </script>
  293. <style lang="scss" scoped>
  294. .night-mode {
  295. .report-items .report-item {
  296. background-color: var(--dark-grey-3) !important;
  297. }
  298. .report-items .report-item .report-item-header {
  299. background-color: var(--dark-grey-2) !important;
  300. }
  301. .label,
  302. p,
  303. strong {
  304. color: var(--light-grey-2);
  305. }
  306. .tabs-container .tab-selection .button {
  307. background: var(--dark-grey) !important;
  308. color: var(--white) !important;
  309. }
  310. }
  311. .reports-tab.tabs-container {
  312. .tab-selection {
  313. display: flex;
  314. overflow-x: auto;
  315. .button {
  316. border-radius: 0;
  317. border: 0;
  318. text-transform: uppercase;
  319. font-size: 14px;
  320. color: var(--dark-grey-3);
  321. background-color: var(--light-grey-2);
  322. flex-grow: 1;
  323. height: 32px;
  324. &:not(:first-of-type) {
  325. margin-left: 5px;
  326. }
  327. }
  328. .selected {
  329. background-color: var(--primary-color) !important;
  330. color: var(--white) !important;
  331. font-weight: 600;
  332. }
  333. }
  334. .tab {
  335. padding: 15px 0;
  336. border-radius: 0;
  337. }
  338. }
  339. .no-reports {
  340. text-align: center;
  341. }
  342. .report-items {
  343. .report-item {
  344. background-color: var(--white);
  345. border: 0.5px solid var(--primary-color);
  346. border-radius: 5px;
  347. padding: 8px;
  348. &:not(:first-of-type) {
  349. margin-bottom: 16px;
  350. }
  351. .report-item-header {
  352. justify-content: center;
  353. text-transform: capitalize;
  354. i {
  355. margin-right: 5px;
  356. }
  357. }
  358. .report-sub-items {
  359. .report-sub-item {
  360. border: 0.5px solid var(--black);
  361. margin-top: -1px;
  362. line-height: 24px;
  363. display: flex;
  364. padding: 4px;
  365. display: flex;
  366. &:first-child {
  367. border-radius: 3px 3px 0 0;
  368. }
  369. &:last-child {
  370. border-radius: 0 0 3px 3px;
  371. }
  372. &.report-sub-item-resolved {
  373. .report-sub-item-description,
  374. .report-sub-item-title {
  375. text-decoration: line-through;
  376. }
  377. }
  378. .report-sub-item-left-icon {
  379. margin-right: 8px;
  380. margin-top: auto;
  381. margin-bottom: auto;
  382. }
  383. .report-sub-item-info {
  384. flex: 1;
  385. display: flex;
  386. flex-direction: column;
  387. .report-sub-item-title {
  388. font-size: 14px;
  389. }
  390. .report-sub-item-description {
  391. font-size: 12px;
  392. line-height: 16px;
  393. }
  394. }
  395. .report-sub-item-actions {
  396. height: 24px;
  397. margin-left: 8px;
  398. margin-top: auto;
  399. margin-bottom: auto;
  400. }
  401. }
  402. }
  403. .resolve-icon {
  404. color: var(--green);
  405. cursor: pointer;
  406. }
  407. .unresolve-icon {
  408. color: var(--red);
  409. cursor: pointer;
  410. }
  411. }
  412. }
  413. </style>