Reports.vue 10 KB

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