Browse Source

feat(Reports): improved design of ViewReport modal and Reports tab on admin page

Signed-off-by: Jonathan <theflametrooper@gmail.com>
Jonathan 4 years ago
parent
commit
23b56bb83f

+ 11 - 9
backend/logic/actions/apis.js

@@ -119,20 +119,21 @@ export default {
 	 * Joins a room
 	 *
 	 * @param {object} session - user session
-	 * @param {string} page - the room to join
+	 * @param {string} room - the room to join
 	 * @param {Function} cb - callback
 	 */
-	joinRoom(session, page, cb) {
+	joinRoom(session, room, cb) {
 		if (
-			page === "home" ||
-			page === "news" ||
-			page.startsWith("profile.") ||
-			page.startsWith("manage-station.") ||
-			page.startsWith("edit-song.")
+			room === "home" ||
+			room === "news" ||
+			room.startsWith("profile.") ||
+			room.startsWith("manage-station.") ||
+			room.startsWith("edit-song.") ||
+			room.startsWith("view-report.")
 		) {
 			WSModule.runJob("SOCKET_JOIN_ROOM", {
 				socketId: session.socketId,
-				room: page
+				room
 			})
 				.then(() => {})
 				.catch(err => {
@@ -155,7 +156,8 @@ export default {
 			room === "home" ||
 			room.startsWith("profile.") ||
 			room.startsWith("manage-station.") ||
-			room.startsWith("edit-song.")
+			room.startsWith("edit-song.") ||
+			room.startsWith("view-report.")
 		) {
 			WSModule.runJob("SOCKET_LEAVE_ROOM", {
 				socketId: session.socketId,

+ 70 - 13
backend/logic/actions/reports.js

@@ -15,12 +15,12 @@ CacheModule.runJob("SUB", {
 	channel: "report.issue.toggle",
 	cb: data => {
 		WSModule.runJob("EMIT_TO_ROOM", {
-			room: "admin.reports",
+			room: `edit-song.${data.songId}`,
 			args: ["event:admin.report.issue.toggled", { data: { issueId: data.issueId, reportId: data.reportId } }]
 		});
 
 		WSModule.runJob("EMIT_TO_ROOM", {
-			room: `edit-song.${data.songId}`,
+			room: `view-report.${data.reportId}`,
 			args: ["event:admin.report.issue.toggled", { data: { issueId: data.issueId, reportId: data.reportId } }]
 		});
 	}
@@ -38,6 +38,13 @@ CacheModule.runJob("SUB", {
 			room: `edit-song.${songId}`,
 			args: ["event:admin.report.resolved", { data: { reportId } }]
 		});
+
+		console.log(`view-report.${reportId}`);
+
+		WSModule.runJob("EMIT_TO_ROOM", {
+			room: `view-report.${reportId}`,
+			args: ["event:admin.report.resolved", { data: { reportId } }]
+		});
 	}
 });
 
@@ -74,16 +81,45 @@ CacheModule.runJob("SUB", {
 
 export default {
 	/**
-	 * Gets all reports
+	 * Gets all reports that haven't been yet resolved
 	 *
 	 * @param {object} session - the session object automatically added by the websocket
 	 * @param {Function} cb - gets called with the result
 	 */
 	index: isAdminRequired(async function index(session, cb) {
 		const reportModel = await DBModule.runJob("GET_MODEL", { modelName: "report" }, this);
+		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
 
 		async.waterfall(
-			[next => reportModel.find({ resolved: false }).sort({ released: "desc" }).exec(next)],
+			[
+				next => reportModel.find({ resolved: false }).sort({ createdAt: "desc" }).exec(next),
+				(_reports, next) => {
+					const reports = [];
+
+					async.each(
+						_reports,
+						(report, cb) => {
+							userModel
+								.findById(report.createdBy)
+								.select({ avatar: -1, name: -1, username: -1 })
+								.exec((err, { avatar, name, username }) => {
+									reports.push({
+										...report._doc,
+										createdBy: {
+											avatar,
+											name,
+											username,
+											_id: report.createdBy
+										}
+									});
+
+									return cb(err);
+								});
+						},
+						err => next(err, reports)
+					);
+				}
+			],
 			async (err, reports) => {
 				if (err) {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
@@ -106,17 +142,38 @@ export default {
 	 */
 	findOne: isAdminRequired(async function findOne(session, reportId, cb) {
 		const reportModel = await DBModule.runJob("GET_MODEL", { modelName: "report" }, this);
+		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
 
-		async.waterfall([next => reportModel.findOne({ _id: reportId }).exec(next)], async (err, report) => {
-			if (err) {
-				err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
-				this.log("ERROR", "REPORTS_FIND_ONE", `Finding report "${reportId}" failed. "${err}"`);
-				return cb({ status: "error", message: err });
-			}
+		async.waterfall(
+			[
+				next => reportModel.findOne({ _id: reportId }).exec(next),
+				(report, next) =>
+					userModel
+						.findById(report.createdBy)
+						.select({ avatar: -1, name: -1, username: -1 })
+						.exec((err, { avatar, name, username }) =>
+							next(err, {
+								...report._doc,
+								createdBy: {
+									avatar,
+									name,
+									username,
+									_id: report.createdBy
+								}
+							})
+						)
+			],
+			async (err, report) => {
+				if (err) {
+					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+					this.log("ERROR", "REPORTS_FIND_ONE", `Finding report "${reportId}" failed. "${err}"`);
+					return cb({ status: "error", message: err });
+				}
 
-			this.log("SUCCESS", "REPORTS_FIND_ONE", `Finding report "${reportId}" successful.`);
-			return cb({ status: "success", data: { report } });
-		});
+				this.log("SUCCESS", "REPORTS_FIND_ONE", `Finding report "${reportId}" successful.`);
+				return cb({ status: "success", data: { report } });
+			}
+		);
 	}),
 
 	/**

+ 7 - 15
frontend/src/components/modals/EditSong/Tabs/Reports.vue

@@ -44,17 +44,6 @@
 								</p>
 							</div>
 						</div>
-
-						<!-- <div class="report-item-actions universal-item-actions">
-							<i
-								class="material-icons resolve-icon"
-								content="Resolve all"
-								v-tippy
-								@click="resolve(99)"
-							>
-								done_all
-							</i>
-						</div> -->
 					</div>
 					<div class="report-sub-items">
 						<div
@@ -299,7 +288,7 @@ export default {
 			this.resolveReport(res.data.reportId)
 		);
 
-		this.socket.on("event:admin.report.issue.toggled", res =>
+		this.socket.on("event:admin.report.issue.toggled", res => {
 			this.reports.forEach((report, index) => {
 				if (report._id === res.data.reportId) {
 					const issue = this.reports[index].issues.find(
@@ -308,8 +297,8 @@ export default {
 
 					issue.resolved = !issue.resolved;
 				}
-			})
-		);
+			});
+		});
 	},
 	methods: {
 		showTab(tab) {
@@ -382,9 +371,12 @@ export default {
 		background-color: var(--white);
 		border: 0.5px solid var(--primary-color);
 		border-radius: 5px;
-		margin-bottom: 16px;
 		padding: 8px;
 
+		&:not(:first-of-type) {
+			margin-bottom: 16px;
+		}
+
 		.report-item-header {
 			display: flex;
 			align-items: center;

+ 1 - 5
frontend/src/components/modals/EditSong/index.vue

@@ -943,11 +943,7 @@ export default {
 		clearInterval(this.interval);
 		clearInterval(this.activityWatchVideoDataInterval);
 
-		this.socket.dispatch(
-			"apis.leaveRoom",
-			`edit-song.${this.song._id}`,
-			() => {}
-		);
+		this.socket.dispatch("apis.leaveRoom", `edit-song.${this.song._id}`);
 
 		const shortcutNames = [
 			"editSong.pauseResume",

+ 305 - 80
frontend/src/components/modals/ViewReport.vue

@@ -1,110 +1,214 @@
 <template>
 	<modal title="View Report">
 		<template #body v-if="report && report._id">
-			<router-link
-				v-if="$route.query.returnToSong"
-				class="button is-dark back-to-song"
-				:to="{
-					path: '/admin/songs',
-					query: { id: report.youtubeId }
-				}"
-			>
-				<i class="material-icons">keyboard_return</i> &nbsp; Edit Song
-			</router-link>
-
-			<article class="message">
-				<div class="message-body">
-					<strong>Song ID:</strong>
-					{{ report.song.youtubeId }} / {{ report.song._id }}
-					<br />
-					<strong>Author:</strong>
-					<user-id-to-username
-						:user-id="report.createdBy"
-						:alt="report.createdBy"
-					/>
-					<br />
-					<strong>Time of report:</strong>
-					<span
-						:content="report.createdAt"
-						v-tippy="{ theme: 'info' }"
+			<div class="report-item">
+				<div class="report-item-header">
+					<div class="report-item-info">
+						<div class="report-item-icon">
+							<profile-picture
+								:avatar="report.createdBy.avatar"
+								:name="
+									report.createdBy.name
+										? report.createdBy.name
+										: report.createdBy.username
+								"
+							/>
+						</div>
+
+						<div class="report-item-summary">
+							<p class="report-item-summary-title">
+								Reported by
+								<router-link
+									:to="{
+										path: `/u/${report.createdBy.username}`
+									}"
+									:title="report.createdBy._id"
+								>
+									{{ report.createdBy.username }}
+								</router-link>
+							</p>
+							<p class="report-item-summary-description">
+								{{
+									formatDistance(
+										new Date(report.createdAt),
+										new Date(),
+										{
+											addSuffix: true
+										}
+									)
+								}}
+								/ YouTube:
+								<a
+									:href="
+										'https://www.youtube.com/watch?v=' +
+											`${report.song.youtubeId}`
+									"
+									target="_blank"
+								>
+									{{ report.song.youtubeId }}</a
+								>
+								/ Song ID: {{ report.song._id }}
+							</p>
+						</div>
+					</div>
+				</div>
+				<div class="report-sub-items">
+					<div
+						class="report-sub-item report-sub-item-unresolved"
+						:class="[
+							'report',
+							issue.resolved
+								? 'report-sub-item-resolved'
+								: 'report-sub-item-unresolved'
+						]"
+						v-for="(issue, issueIndex) in report.issues"
+						:key="issueIndex"
 					>
-						{{
-							formatDistance(
-								new Date(report.createdAt),
-								new Date(),
-								{
-									addSuffix: true
-								}
-							)
-						}}
-					</span>
-					<br />
-					<span>
-						<strong>Issues:</strong>
-						<ul id="issues">
-							<li
-								v-for="(issue, index) in report.issues"
-								:key="index"
+						<i
+							class="material-icons duration-icon report-sub-item-left-icon"
+							:content="issue.category"
+							v-tippy
+						>
+							{{ icons[issue.category] }}
+						</i>
+						<p class="report-sub-item-info">
+							<span class="report-sub-item-title">
+								{{ issue.title }}
+							</span>
+							<span
+								class="report-sub-item-description"
+								v-if="issue.description"
 							>
-								<strong> {{ issue.category }}:</strong>
-								{{ issue.info }}
-							</li>
-						</ul>
-					</span>
+								{{ issue.description }}
+							</span>
+						</p>
+
+						<div
+							class="report-sub-item-actions universal-item-actions"
+						>
+							<i
+								class="material-icons resolve-icon"
+								content="Resolve"
+								v-tippy
+								v-if="!issue.resolved"
+								@click="toggleIssue(report._id, issue._id)"
+							>
+								done
+							</i>
+							<i
+								class="material-icons unresolve-icon"
+								content="Unresolve"
+								v-tippy
+								v-else
+								@click="toggleIssue(report._id, issue._id)"
+							>
+								remove
+							</i>
+						</div>
+					</div>
 				</div>
-			</article>
+			</div>
 		</template>
 		<template #footer v-if="report && report._id">
-			<a class="button is-primary" href="#" @click="resolve(report._id)">
-				<span>Resolve</span>
-			</a>
 			<a
 				class="button is-primary"
 				:href="`/admin/songs?songId=${report.song._id}`"
 				target="_blank"
 			>
-				<span>Go to song</span>
+				<i
+					class="material-icons icon-with-button"
+					content="Open Song in New Tab"
+					v-tippy
+				>
+					open_in_new
+				</i>
+				Open Song
+			</a>
+			<a class="button is-success" href="#" @click="resolve(report._id)">
+				<i
+					class="material-icons icon-with-button"
+					content="Resolve"
+					v-tippy
+				>
+					done_all
+				</i>
+				Resolve
 			</a>
 		</template>
 	</modal>
 </template>
 
 <script>
-import { mapActions, mapGetters, mapState } from "vuex";
+import { mapActions, mapGetters } from "vuex";
 import { formatDistance } from "date-fns";
 import Toast from "toasters";
 
-import UserIdToUsername from "../UserIdToUsername.vue";
+import ProfilePicture from "@/components/ProfilePicture.vue";
 import Modal from "../Modal.vue";
 
 export default {
-	components: { Modal, UserIdToUsername },
+	components: { Modal, ProfilePicture },
 	props: {
 		reportId: { type: String, default: "" },
 		sector: { type: String, default: "admin" }
 	},
+	data() {
+		return {
+			icons: {
+				duration: "timer",
+				video: "tv",
+				thumbnail: "image",
+				artists: "record_voice_over",
+				title: "title",
+				custom: "lightbulb"
+			},
+			report: {}
+		};
+	},
 	computed: {
-		...mapState("modals/viewReport", {
-			report: state => state.report
-		}),
 		...mapGetters({
 			socket: "websockets/getSocket"
 		})
 	},
 	mounted() {
-		if (this.$route.query.returnToSong) {
-			this.closeModal("editSong");
-		}
-
-		this.socket.dispatch(`reports.findOne`, this.reportId, res => {
+		this.socket.dispatch("reports.findOne", this.reportId, res => {
 			if (res.status === "success") {
 				const { report } = res.data;
-				this.viewReport(report);
+
+				this.socket.dispatch(
+					"apis.joinRoom",
+					`view-report.${report._id}`
+				);
+
+				this.report = report;
 			} else {
 				new Toast("Report with that ID not found");
 				this.closeModal("viewReport");
 			}
 		});
+
+		this.socket.on(
+			"event:admin.report.resolved",
+			() => this.closeModal("viewReport"),
+			{ modal: "viewReport" }
+		);
+
+		this.socket.on(
+			"event:admin.report.issue.toggled",
+			res => {
+				if (this.report._id === res.data.reportId) {
+					const issue = this.report.issues.find(
+						issue => issue._id.toString() === res.data.issueId
+					);
+
+					issue.resolved = !issue.resolved;
+				}
+			},
+			{ modal: "viewReport" }
+		);
+	},
+	beforeUnmount() {
+		this.socket.dispatch("apis.leaveRoom", `view-report.${this.reportId}`);
 	},
 	methods: {
 		formatDistance,
@@ -115,31 +219,152 @@ export default {
 				})
 				.catch(err => new Toast(err.message));
 		},
-		...mapActions("modals/viewReport", ["viewReport", "resolveReport"]),
+		toggleIssue(reportId, issueId) {
+			this.socket.dispatch(
+				"reports.toggleIssue",
+				reportId,
+				issueId,
+				res => {
+					if (res.status !== "success") new Toast(res.message);
+				}
+			);
+		},
+		...mapActions("admin/reports", ["indexReports", "resolveReport"]),
 		...mapActions("modalVisibility", ["closeModal"])
 	}
 };
 </script>
 
 <style lang="scss" scoped>
-.night-mode {
-	.message,
-	.table {
-		color: var(--light-grey-2);
-		background-color: var(--dark-grey-3);
+.report-item {
+	background-color: var(--white);
+	border: 0.5px solid var(--primary-color);
+	border-radius: 5px;
+	padding: 8px;
+
+	&:not(:first-of-type) {
+		margin-bottom: 16px;
 	}
 
-	.table tr:hover {
-		filter: brightness(95%);
+	.report-item-header {
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		margin-bottom: 8px;
+		background-color: var(--light-grey);
+		// padding: 5px;
+		padding: 10px;
+		border-radius: 5px;
+
+		.report-item-info {
+			display: flex;
+			align-items: center;
+
+			.report-item-icon {
+				display: flex;
+				align-items: center;
+
+				.profile-picture,
+				i {
+					margin-right: 10px;
+					width: 45px;
+					height: 45px;
+				}
+
+				i {
+					font-size: 30px;
+					display: flex;
+					align-items: center;
+					justify-content: center;
+				}
+			}
+
+			.report-item-summary {
+				.report-item-summary-title {
+					// font-size: 14px;
+					font-size: 16px;
+					text-transform: capitalize;
+				}
+
+				.report-item-summary-description {
+					text-transform: capitalize;
+					// font-size: 12px;
+					font-size: 14px;
+				}
+			}
+		}
+
+		.report-item-actions {
+			height: 24px;
+			margin-right: 4px;
+		}
 	}
-}
 
-.back-to-song {
-	display: flex;
-	margin-bottom: 20px;
-}
+	.report-sub-items {
+		.report-sub-item {
+			border: 0.5px solid var(--black);
+			margin-top: -1px;
+			line-height: 24px;
+			display: flex;
+			// padding: 4px;
+			padding: 8px;
+			display: flex;
 
-#issues {
-	list-style: inside;
+			&:first-child {
+				border-radius: 3px 3px 0 0;
+			}
+
+			&:last-child {
+				border-radius: 0 0 3px 3px;
+			}
+
+			&.report-sub-item-resolved {
+				.report-sub-item-description,
+				.report-sub-item-title {
+					text-decoration: line-through;
+				}
+			}
+
+			.report-sub-item-left-icon {
+				margin-right: 8px;
+				margin-top: auto;
+				margin-bottom: auto;
+			}
+
+			.report-sub-item-info {
+				flex: 1;
+				display: flex;
+				flex-direction: column;
+
+				.report-sub-item-title {
+					// font-size: 14px;
+					font-size: 16px;
+				}
+
+				.report-sub-item-description {
+					// font-size: 12px;
+					font-size: 14px;
+					line-height: 16px;
+				}
+			}
+
+			.report-sub-item-actions {
+				height: 24px;
+				margin-left: 8px;
+				margin-top: auto;
+				margin-bottom: auto;
+			}
+		}
+	}
+
+	.resolve-icon {
+		color: var(--green);
+		cursor: pointer;
+	}
+
+	.unresolve-icon {
+		color: var(--red);
+		cursor: pointer;
+	}
 }
 </style>

+ 0 - 2
frontend/src/mixins/ScrollAndFetchHandler.vue

@@ -22,8 +22,6 @@ export default {
 	},
 	unmounted() {
 		clearInterval(this.interval);
-	},
-	onUnmounted() {
 		window.removeEventListener("scroll", this.handleScroll);
 	},
 	methods: {

+ 152 - 47
frontend/src/pages/Admin/tabs/Reports.vue

@@ -6,9 +6,8 @@
 				<thead>
 					<tr>
 						<td>Song ID</td>
-						<td>Author</td>
-						<td>Time of report</td>
-						<td>Issues</td>
+						<td>Summary</td>
+						<td>Categories Included</td>
 						<td>Options</td>
 					</tr>
 				</thead>
@@ -16,54 +15,99 @@
 					<tr v-for="report in reports" :key="report._id">
 						<td>
 							<span>
-								{{ report.song.youtubeId }}
+								<a
+									:href="
+										'https://www.youtube.com/watch?v=' +
+											`${report.song.youtubeId}`
+									"
+									target="_blank"
+								>
+									{{ report.song.youtubeId }}</a
+								>
 								<br />
 								{{ report.song._id }}
 							</span>
 						</td>
 						<td>
-							<user-id-to-username
-								:user-id="report.createdBy"
-								:link="true"
-							/>
-						</td>
-						<td>
-							<span
-								:content="report.createdAt"
-								v-tippy="{ theme: 'info' }"
-								>{{
-									formatDistance(
-										new Date(report.createdAt),
-										new Date(),
-										{ addSuffix: true }
-									)
-								}}</span
-							>
+							<div class="report-item-header">
+								<div class="report-item-info">
+									<div class="report-item-icon">
+										<profile-picture
+											:avatar="report.createdBy.avatar"
+											:name="
+												report.createdBy.name
+													? report.createdBy.name
+													: report.createdBy.username
+											"
+										/>
+									</div>
+
+									<div class="report-item-summary">
+										<p class="report-item-summary-title">
+											Reported by
+											<router-link
+												:to="{
+													path: `/u/${report.createdBy.username}`
+												}"
+												:title="report.createdBy._id"
+											>
+												{{ report.createdBy.username }}
+											</router-link>
+										</p>
+										<p
+											class="report-item-summary-description"
+										>
+											{{
+												formatDistance(
+													new Date(report.createdAt),
+													new Date(),
+													{
+														addSuffix: true
+													}
+												)
+											}}
+										</p>
+									</div>
+								</div>
+							</div>
 						</td>
-						<td>
+						<td id="categories-column">
 							<ul>
 								<li
-									v-for="(issue, index) in report.issues"
-									:key="index"
+									v-for="category in getCategories(
+										report.issues
+									)"
+									:key="category"
 								>
-									<strong> {{ issue.category }}:</strong>
-									{{ issue.info }}
+									{{ category }}
 								</li>
 							</ul>
 						</td>
-						<td>
+						<td id="options-column">
 							<a
-								class="button is-warning"
+								class="button is-primary"
 								href="#"
 								@click="view(report)"
-								>View</a
+								content="Expand"
+								v-tippy
 							>
+								<i class="material-icons icon-with-button">
+									open_in_full
+								</i>
+								Expand
+							</a>
 							<a
-								class="button is-primary"
+								class="button is-success "
 								href="#"
 								@click="resolve(report._id)"
-								>Resolve</a
+								content="Resolve"
+								v-tippy
 							>
+								<i class="material-icons icon-with-button">
+									done_all
+								</i>
+								Resolve
+							</a>
 						</td>
 					</tr>
 				</tbody>
@@ -84,7 +128,7 @@ import { formatDistance } from "date-fns";
 import { defineAsyncComponent } from "vue";
 
 import Toast from "toasters";
-import UserIdToUsername from "@/components/UserIdToUsername.vue";
+import ProfilePicture from "@/components/ProfilePicture.vue";
 import ws from "@/ws";
 
 export default {
@@ -92,7 +136,7 @@ export default {
 		ViewReport: defineAsyncComponent(() =>
 			import("@/components/modals/ViewReport.vue")
 		),
-		UserIdToUsername
+		ProfilePicture
 	},
 	data() {
 		return {
@@ -117,32 +161,41 @@ export default {
 		});
 
 		this.socket.on("event:admin.report.resolved", res => {
-			console.log("report resolved event", res);
 			this.reports = this.reports.filter(report => {
 				return report._id !== res.data.reportId;
 			});
 		});
 
 		this.socket.on("event:admin.report.created", res =>
-			this.reports.push(res.data.report)
+			this.reports.unshift(res.data.report)
 		);
 
-		if (this.$route.query.id) {
-			this.socket.dispatch(
-				"reports.findOne",
-				this.$route.query.id,
-				res => {
-					if (res.status === "success") this.view(res.data.report);
-					else new Toast("Report with that ID not found");
-				}
-			);
-		}
+		// if (this.$route.query.id) {
+		// 	this.socket.dispatch(
+		// 		"reports.findOne",
+		// 		this.$route.query.id,
+		// 		res => {
+		// 			if (res.status === "success") this.view(res.data.report);
+		// 			else new Toast("Report with that ID not found");
+		// 		}
+		// 	);
+		// }
 	},
 	methods: {
 		formatDistance,
 		init() {
 			this.socket.dispatch("apis.joinAdminRoom", "reports", () => {});
 		},
+		getCategories(issues) {
+			const categories = [];
+
+			issues.forEach(issue => {
+				if (categories.indexOf(issue.category) === -1)
+					categories.push(issue.category);
+			});
+
+			return categories;
+		},
 		view(report) {
 			this.viewingReportId = report._id;
 			this.openModal("viewReport");
@@ -188,8 +241,14 @@ export default {
 	}
 }
 
-.tag:not(:last-child) {
-	margin-right: 5px;
+#options-column {
+	a:not(:last-of-type) {
+		margin-right: 5px;
+	}
+}
+
+#categories-column {
+	text-transform: capitalize;
 }
 
 td {
@@ -201,4 +260,50 @@ td {
 li {
 	list-style: inside;
 }
+
+.report-item-header {
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	margin-bottom: 8px;
+	background-color: var(--light-grey);
+	padding: 5px;
+	border-radius: 5px;
+
+	.report-item-info {
+		display: flex;
+		align-items: center;
+
+		.report-item-icon {
+			display: flex;
+			align-items: center;
+
+			.profile-picture,
+			i {
+				margin-right: 10px;
+				width: 45px;
+				height: 45px;
+			}
+
+			i {
+				font-size: 30px;
+				display: flex;
+				align-items: center;
+				justify-content: center;
+			}
+		}
+
+		.report-item-summary {
+			.report-item-summary-title {
+				font-size: 14px;
+				text-transform: capitalize;
+			}
+
+			.report-item-summary-description {
+				text-transform: capitalize;
+				font-size: 12px;
+			}
+		}
+	}
+}
 </style>

+ 0 - 2
frontend/src/store/index.js

@@ -14,7 +14,6 @@ import editPlaylistModal from "./modules/modals/editPlaylist";
 import manageStationModal from "./modules/modals/manageStation";
 import editUserModal from "./modules/modals/editUser";
 import viewPunishmentModal from "./modules/modals/viewPunishment";
-import viewReportModal from "./modules/modals/viewReport";
 import reportModal from "./modules/modals/report";
 
 export default createStore({
@@ -33,7 +32,6 @@ export default createStore({
 				manageStation: manageStationModal,
 				editUser: editUserModal,
 				viewPunishment: viewPunishmentModal,
-				viewReport: viewReportModal,
 				report: reportModal
 			}
 		}

+ 6 - 1
frontend/src/store/modules/admin.js

@@ -141,7 +141,9 @@ const modules = {
 	},
 	reports: {
 		namespaced: true,
-		state: {},
+		state: {
+			reports: []
+		},
 		getters: {},
 		actions: {
 			/* eslint-disable-next-line no-unused-vars */
@@ -152,6 +154,9 @@ const modules = {
 						.then(res => resolve(res))
 						.catch(err => reject(new Error(err.message)));
 				});
+			},
+			indexReports({ commit }, reports) {
+				commit("indexReports", reports);
 			}
 		},
 		mutations: {}

+ 0 - 33
frontend/src/store/modules/modals/viewReport.js

@@ -1,33 +0,0 @@
-/* eslint no-param-reassign: 0 */
-/* eslint-disable import/no-cycle */
-
-import admin from "@/api/admin/index";
-
-export default {
-	namespaced: true,
-	state: {
-		report: {}
-	},
-	getters: {},
-	actions: {
-		viewReport: ({ commit }, report) => commit("viewReport", report),
-		/* eslint-disable-next-line no-unused-vars */
-		resolveReport: ({ commit }, reportId) => {
-			return new Promise((resolve, reject) => {
-				return admin.reports
-					.resolve(reportId)
-					.then(res => {
-						return resolve(res);
-					})
-					.catch(err => {
-						return reject(new Error(err.message));
-					});
-			});
-		}
-	},
-	mutations: {
-		viewReport(state, report) {
-			state.report = report;
-		}
-	}
-};

+ 1 - 0
frontend/src/ws.js

@@ -112,6 +112,7 @@ export default {
 				if (!(event.type in this.listeners)) return true; // event type doesn't exist
 
 				const stack = this.listeners[event.type].slice();
+
 				stack.forEach(element => element.cb.call(this, event));
 
 				return !event.defaultPrevented;