Browse Source

feat(Report modal): view a user's unresolved reports for the chosen song

Signed-off-by: Jonathan <theflametrooper@gmail.com>
Jonathan 3 years ago
parent
commit
2f9dc5d4ba

+ 74 - 0
backend/logic/actions/reports.js

@@ -231,6 +231,80 @@ export default {
 		);
 	}),
 
+	/**
+	 * Gets all a users reports for a specific songId
+	 *
+	 * @param {object} session - the session object automatically added by the websocket
+	 * @param {string} songId - the id of the song
+	 * @param {Function} cb - gets called with the result
+	 */
+	myReportsForSong: isLoginRequired(async function myReportsForSong(session, songId, 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({ "song._id": songId, createdBy: session.userId, 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, user) => {
+									if (!user)
+										reports.push({
+											...report._doc,
+											createdBy: { _id: report.createdBy }
+										});
+									else
+										reports.push({
+											...report._doc,
+											createdBy: {
+												avatar: user.avatar,
+												name: user.name,
+												username: user.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);
+					this.log(
+						"ERROR",
+						"MY_REPORTS_FOR_SONG",
+						`Indexing reports of user ${session.userId} for song "${songId}" failed. "${err}"`
+					);
+					return cb({ status: "error", message: err });
+				}
+
+				this.log(
+					"SUCCESS",
+					"MY_REPORTS_FOR_SONG",
+					`Indexing reports of user ${session.userId} for song "${songId}" successful.`
+				);
+
+				return cb({ status: "success", data: { reports } });
+			}
+		);
+	}),
+
 	/**
 	 * Resolves a report as a whole
 	 *

+ 1 - 1
backend/logic/actions/songs.js

@@ -350,7 +350,7 @@ export default {
 	 * @param {string} songId - the song id
 	 * @param {Function} cb
 	 */
-	getSongFromSongId: isAdminRequired(function getSong(session, songId, cb) {
+	getSongFromSongId: isAdminRequired(function getSongFromSongId(session, songId, cb) {
 		async.waterfall(
 			[
 				next => {

+ 237 - 141
frontend/src/components/modals/Report.vue

@@ -1,151 +1,225 @@
 <template>
-	<modal title="Report">
-		<template #body>
-			<div class="edit-report-wrapper">
-				<song-item
-					:song="song"
-					:disabled-actions="['report']"
-					header="Selected Song.."
-				/>
-
-				<div class="columns is-multiline">
-					<div
-						v-for="category in predefinedCategories"
-						class="column is-half"
-						:key="category.category"
-					>
-						<label class="label">{{ category.category }}</label>
-
-						<p
-							v-for="issue in category.issues"
-							class="control checkbox-control"
-							:key="issue.title"
-						>
-							<span class="align-horizontally">
-								<span>
-									<label class="switch">
-										<input
-											type="checkbox"
-											:id="issue.title"
-											v-model="issue.enabled"
-										/>
-										<span class="slider round"></span>
-									</label>
-
-									<label :for="issue.title">
-										<span></span>
-										<p>{{ issue.title }}</p>
-									</label>
-								</span>
-
-								<i
-									class="material-icons"
-									content="Provide More info"
-									v-tippy
-									@click="
-										issue.showDescription = !issue.showDescription
-									"
-								>
-									info
-								</i>
-							</span>
-
-							<input
-								type="text"
-								class="input"
-								v-model="issue.description"
-								v-if="issue.showDescription"
-								placeholder="Provide more information..."
-								@keyup="issue.enabled = true"
-							/>
-						</p>
-					</div>
-					<!-- allow for multiple custom issues with plus/add button and then a input textbox -->
-					<!-- do away with textbox -->
-
-					<div class="column is-half">
-						<div id="custom-issues">
-							<div id="custom-issues-title">
-								<label class="label">Issues not listed</label>
-
-								<button
-									class="button tab-actionable-button "
-									content="Add an issue that isn't listed"
-									v-tippy
-									@click="customIssues.push('')"
+	<div>
+		<modal class="report-modal" title="Report">
+			<template #body>
+				<div class="report-modal-inner-container">
+					<div id="left-part">
+						<song-item
+							:song="song"
+							:duration="false"
+							:disabled-actions="['report']"
+							header="Selected Song.."
+						/>
+
+						<div class="columns is-multiline">
+							<div
+								v-for="category in predefinedCategories"
+								class="column is-half"
+								:key="category.category"
+							>
+								<label class="label">{{
+									category.category
+								}}</label>
+
+								<p
+									v-for="issue in category.issues"
+									class="control checkbox-control"
+									:key="issue.title"
 								>
-									<i class="material-icons icon-with-button"
-										>add</i
-									>
-									<span>
-										Add Custom Issue
+									<span class="align-horizontally">
+										<span>
+											<label class="switch">
+												<input
+													type="checkbox"
+													:id="issue.title"
+													v-model="issue.enabled"
+												/>
+												<span
+													class="slider round"
+												></span>
+											</label>
+
+											<label :for="issue.title">
+												<span></span>
+												<p>{{ issue.title }}</p>
+											</label>
+										</span>
+
+										<i
+											class="material-icons"
+											content="Provide More info"
+											v-tippy
+											@click="
+												issue.showDescription = !issue.showDescription
+											"
+										>
+											info
+										</i>
 									</span>
-								</button>
-							</div>
 
-							<div
-								class="custom-issue control is-grouped input-with-button"
-								v-for="(issue, index) in customIssues"
-								:key="index"
-							>
-								<p class="control is-expanded">
 									<input
 										type="text"
 										class="input"
-										v-model="customIssues[index]"
-										placeholder="Provide information..."
+										v-model="issue.description"
+										v-if="issue.showDescription"
+										placeholder="Provide more information..."
+										@keyup="issue.enabled = true"
 									/>
 								</p>
-								<p class="control">
-									<button
-										class="button is-danger"
-										content="Remove custom issue"
-										v-tippy
-										@click="customIssues.splice(index, 1)"
+							</div>
+							<!-- allow for multiple custom issues with plus/add button and then a input textbox -->
+							<!-- do away with textbox -->
+
+							<div class="column is-half">
+								<div id="custom-issues">
+									<div id="custom-issues-title">
+										<label class="label"
+											>Issues not listed</label
+										>
+
+										<button
+											class="button tab-actionable-button "
+											content="Add an issue that isn't listed"
+											v-tippy
+											@click="customIssues.push('')"
+										>
+											<i
+												class="material-icons icon-with-button"
+												>add</i
+											>
+											<span>
+												Add Custom Issue
+											</span>
+										</button>
+									</div>
+
+									<div
+										class="custom-issue control is-grouped input-with-button"
+										v-for="(issue, index) in customIssues"
+										:key="index"
 									>
-										<i class="material-icons">
-											delete
-										</i>
-									</button>
-								</p>
+										<p class="control is-expanded">
+											<input
+												type="text"
+												class="input"
+												v-model="customIssues[index]"
+												placeholder="Provide information..."
+											/>
+										</p>
+										<p class="control">
+											<button
+												class="button is-danger"
+												content="Remove custom issue"
+												v-tippy
+												@click="
+													customIssues.splice(
+														index,
+														1
+													)
+												"
+											>
+												<i class="material-icons">
+													delete
+												</i>
+											</button>
+										</p>
+									</div>
+
+									<p
+										id="no-issues-listed"
+										v-if="customIssues.length <= 0"
+									>
+										<em>
+											Add any issues that aren't listed
+											above.
+										</em>
+									</p>
+								</div>
 							</div>
+						</div>
+					</div>
+					<div id="right-part" v-if="existingReports.length > 0">
+						<h4 class="section-title">Previous Reports</h4>
+
+						<p class="section-description">
+							You have made
+							{{
+								existingReports.length > 1
+									? "multiple reports"
+									: "a report"
+							}}
+							about this song already.
+						</p>
 
-							<p
-								id="no-issues-listed"
-								v-if="customIssues.length <= 0"
+						<hr class="section-horizontal-rule" />
+
+						<div class="report-items">
+							<div
+								class="report-item"
+								v-for="report in existingReports"
+								:key="report._id"
 							>
-								<em>
-									Add any issues that aren't listed above.
-								</em>
-							</p>
+								<report-info-item
+									:created-at="report.createdAt"
+									:created-by="report.createdBy"
+								>
+									<template #actions>
+										<i
+											class="material-icons"
+											content="View Report"
+											v-tippy
+											@click="view(report._id)"
+										>
+											open_in_full
+										</i>
+									</template>
+								</report-info-item>
+							</div>
 						</div>
 					</div>
 				</div>
-			</div>
-		</template>
-		<template #footer>
-			<a class="button is-success" @click="create()" href="#">
-				<i class="material-icons save-changes">done</i>
-				<span>&nbsp;Create</span>
-			</a>
-			<a class="button is-danger" href="#" @click="closeModal('report')">
-				<span>&nbsp;Cancel</span>
-			</a>
-		</template>
-	</modal>
+			</template>
+			<template #footer>
+				<a class="button is-success" @click="create()" href="#">
+					<i class="material-icons save-changes">done</i>
+					<span>&nbsp;Create</span>
+				</a>
+				<a
+					class="button is-danger"
+					href="#"
+					@click="closeModal('report')"
+				>
+					<span>&nbsp;Cancel</span>
+				</a>
+			</template>
+		</modal>
+		<view-report v-if="modals.viewReport" :report-id="viewingReportId" />
+	</div>
 </template>
 
 <script>
 import { mapState, mapGetters, mapActions } from "vuex";
-
 import Toast from "toasters";
+
+import ViewReport from "@/components/modals/ViewReport.vue";
 import SongItem from "@/components/SongItem.vue";
+import ReportInfoItem from "@/components/ReportInfoItem.vue";
 import Modal from "../Modal.vue";
 
 export default {
-	components: { Modal, SongItem },
+	components: { Modal, ViewReport, SongItem, ReportInfoItem },
 	data() {
 		return {
+			viewingReportId: "",
+			icons: {
+				duration: "timer",
+				video: "tv",
+				thumbnail: "image",
+				artists: "record_voice_over",
+				title: "title",
+				custom: "lightbulb"
+			},
+			existingReports: [],
 			customIssues: [],
 			predefinedCategories: [
 				{
@@ -267,17 +341,27 @@ export default {
 		};
 	},
 	computed: {
-		charactersRemaining() {
-			return 400 - this.report.description.length;
-		},
 		...mapState({
 			song: state => state.modals.report.song
 		}),
+		...mapState("modalVisibility", {
+			modals: state => state.modals
+		}),
 		...mapGetters({
 			socket: "websockets/getSocket"
 		})
 	},
+	mounted() {
+		this.socket.dispatch("reports.myReportsForSong", this.song._id, res => {
+			if (res.status === "success")
+				this.existingReports = res.data.reports;
+		});
+	},
 	methods: {
+		view(reportId) {
+			this.viewingReportId = reportId;
+			this.openModal("viewReport");
+		},
 		create() {
 			const issues = [];
 
@@ -314,37 +398,49 @@ export default {
 			);
 		},
 		...mapActions("modals/report", ["reportSong"]),
-		...mapActions("modalVisibility", ["closeModal"])
+		...mapActions("modalVisibility", ["openModal", "closeModal"])
 	}
 };
 </script>
 
 <style lang="scss">
-.edit-report-wrapper .song-item {
-	.song- {
-		width: calc(100% - 150px);
+.report-modal {
+	.modal-card {
+		width: 1050px;
 	}
-	.thumbnail {
-		min-width: 130px;
-		width: 130px;
-		height: 130px;
+
+	.song-item {
+		.thumbnail {
+			min-width: 130px;
+			width: 130px;
+			height: 130px;
+		}
 	}
 }
 </style>
 
 <style lang="scss" scoped>
-.radio-controls .control {
+.report-modal-inner-container {
 	display: flex;
-	align-items: center;
-}
 
-.textarea-counter {
-	text-align: right;
-}
+	#right-part {
+		border-left: 1px solid var(--light-grey-3);
+		padding-left: 20px;
+		margin-left: 20px;
+		min-width: 325px;
 
-@media screen and (min-width: 769px) {
-	.radio-controls .control-label {
-		padding-top: 0 !important;
+		@media screen and (max-width: 900px) {
+			display: none;
+		}
+
+		.report-items {
+			max-height: 485px;
+			overflow: auto;
+
+			.report-item:not(:first-of-type) {
+				margin-top: 10px;
+			}
+		}
 	}
 }
 

+ 11 - 14
frontend/src/components/modals/ViewReport.vue

@@ -2,7 +2,7 @@
 	<modal class="view-report-modal" title="View Report">
 		<template #body v-if="report && report._id">
 			<div class="report-item">
-				<div id="song-and-report-item">
+				<div id="song-and-report-items">
 					<report-info-item
 						:created-at="report.createdAt"
 						:created-by="report.createdBy"
@@ -146,7 +146,6 @@ export default {
 					"songs.getSongFromSongId",
 					this.report.song._id,
 					res => {
-						console.log("res", res);
 						if (res.status === "success") this.song = res.data.song;
 						else {
 							new Toast(
@@ -154,8 +153,6 @@ export default {
 							);
 							this.closeModal("viewReport");
 						}
-
-						console.log(this.song);
 					}
 				);
 			} else {
@@ -227,21 +224,21 @@ export default {
 	}
 }
 
+@media screen and (min-width: 650px) {
+	.report-info-item {
+		margin-right: 10px !important;
+	}
+}
+
 .report-item {
-	#song-and-report-item {
+	#song-and-report-items {
 		display: flex;
+		flex-wrap: wrap;
 		margin-bottom: 20px;
 
 		.universal-item {
-			width: 50%;
-		}
-
-		.song-item {
-			margin-left: 5px;
-		}
-
-		.report-info-item {
-			margin-right: 5px;
+			width: fit-content;
+			margin: 5px 0;
 		}
 	}
 

+ 3 - 3
frontend/src/pages/Admin/tabs/Reports.vue

@@ -51,7 +51,7 @@
 							<a
 								class="button is-primary"
 								href="#"
-								@click="view(report)"
+								@click="view(report._id)"
 								content="Expand"
 								v-tippy
 							>
@@ -163,8 +163,8 @@ export default {
 
 			return categories;
 		},
-		view(report) {
-			this.viewingReportId = report._id;
+		view(reportId) {
+			this.viewingReportId = reportId;
 			this.openModal("viewReport");
 		},
 		resolve(reportId) {