generated from nhcarrigan/template
feat: add comment report management to admin interface
Updated the admin-reports component to handle both profile and comment reports: - Added report type toggle to switch between profile and comment reports - Duplicated report display logic for comment reports - Comment reports show the comment content with truncation - Added separate review modals for profile and comment reports - Comment reports display comment author instead of reported user - Maintains all existing functionality for profile reports Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,17 +9,39 @@ import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
import { ReportService } from '../../services/report.service';
|
||||
import { CommentReportService } from '../../services/comment-report.service';
|
||||
import { AuthService } from '../../services/auth.service';
|
||||
import { ToastService } from '../../services/toast.service';
|
||||
import { ProfileReportWithUsers, ReportStatus, ReportReason } from '@library/shared-types';
|
||||
import { ProfileReportWithUsers, CommentReportWithDetails, ReportStatus, ReportReason } from '@library/shared-types';
|
||||
|
||||
@Component({
|
||||
selector: 'app-admin-reports',
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule],
|
||||
template: `
|
||||
<div class="admin-reports-wrapper">
|
||||
<div class="admin-reports-container">
|
||||
<h1>Profile Reports</h1>
|
||||
<div class="header-section">
|
||||
<h1>Reports</h1>
|
||||
<div class="report-type-toggle">
|
||||
<button
|
||||
type="button"
|
||||
class="toggle-btn"
|
||||
[class.active]="reportType() === 'profile'"
|
||||
(click)="setReportType('profile')"
|
||||
>
|
||||
Profile Reports
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="toggle-btn"
|
||||
[class.active]="reportType() === 'comment'"
|
||||
(click)="setReportType('comment')"
|
||||
>
|
||||
Comment Reports
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (loading()) {
|
||||
<div class="loading" role="status" aria-live="polite">
|
||||
@@ -40,7 +62,7 @@ import { ProfileReportWithUsers, ReportStatus, ReportReason } from '@library/sha
|
||||
[attr.aria-selected]="activeFilter() === 'all'"
|
||||
(click)="setFilter('all')"
|
||||
>
|
||||
All ({{ allReports().length }})
|
||||
All ({{ reportType() === 'profile' ? allProfileReports().length : allCommentReports().length }})
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -86,7 +108,8 @@ import { ProfileReportWithUsers, ReportStatus, ReportReason } from '@library/sha
|
||||
|
||||
<!-- Reports List -->
|
||||
<div class="reports-list" role="region" aria-label="Reports list">
|
||||
@for (report of filteredReports(); track report.id) {
|
||||
@if (reportType() === 'profile') {
|
||||
@for (report of filteredProfileReports(); track report.id) {
|
||||
<div class="report-card">
|
||||
<!-- Report Header -->
|
||||
<div class="report-header">
|
||||
@@ -182,7 +205,7 @@ import { ProfileReportWithUsers, ReportStatus, ReportReason } from '@library/sha
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-review"
|
||||
(click)="openReviewModal(report)"
|
||||
(click)="openProfileReviewModal(report)"
|
||||
[attr.aria-label]="'Review report for ' + report.reportedUser.username"
|
||||
>
|
||||
Review
|
||||
@@ -191,21 +214,133 @@ import { ProfileReportWithUsers, ReportStatus, ReportReason } from '@library/sha
|
||||
</div>
|
||||
} @empty {
|
||||
<div class="empty-state">
|
||||
<p>No reports found.</p>
|
||||
<p>No profile reports found.</p>
|
||||
</div>
|
||||
}
|
||||
} @else {
|
||||
@for (report of filteredCommentReports(); track report.id) {
|
||||
<div class="report-card">
|
||||
<!-- Report Header -->
|
||||
<div class="report-header">
|
||||
<div class="user-info">
|
||||
<div class="user-section">
|
||||
<h3>Comment Author</h3>
|
||||
<div class="user-details">
|
||||
@if (report.reportedComment.user.avatar) {
|
||||
<img
|
||||
[src]="report.reportedComment.user.avatar"
|
||||
[alt]="report.reportedComment.user.username + ' avatar'"
|
||||
class="avatar"
|
||||
/>
|
||||
} @else {
|
||||
<div class="avatar-placeholder" [attr.aria-label]="report.reportedComment.user.username + ' avatar'">
|
||||
{{ report.reportedComment.user.username.charAt(0).toUpperCase() }}
|
||||
</div>
|
||||
}
|
||||
<div class="user-names">
|
||||
<span class="username">{{ report.reportedComment.user.username }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Review Modal -->
|
||||
@if (reviewingReport()) {
|
||||
<div class="user-section">
|
||||
<h3>Reporter</h3>
|
||||
<div class="user-details">
|
||||
@if (report.reporter.avatar) {
|
||||
<img
|
||||
[src]="report.reporter.avatar"
|
||||
[alt]="report.reporter.username + ' avatar'"
|
||||
class="avatar"
|
||||
/>
|
||||
} @else {
|
||||
<div class="avatar-placeholder" [attr.aria-label]="report.reporter.username + ' avatar'">
|
||||
{{ report.reporter.username.charAt(0).toUpperCase() }}
|
||||
</div>
|
||||
}
|
||||
<div class="user-names">
|
||||
<span class="username">{{ report.reporter.username }}</span>
|
||||
@if (report.reporter.displayName) {
|
||||
<span class="display-name">({{ report.reporter.displayName }})</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="report-meta">
|
||||
<span
|
||||
class="status-badge"
|
||||
[class.pending]="report.status === 'PENDING'"
|
||||
[class.reviewed]="report.status === 'REVIEWED'"
|
||||
[class.dismissed]="report.status === 'DISMISSED'"
|
||||
[class.action-taken]="report.status === 'ACTION_TAKEN'"
|
||||
[attr.aria-label]="'Status: ' + formatStatus(report.status)"
|
||||
>
|
||||
{{ formatStatus(report.status) }}
|
||||
</span>
|
||||
<span class="report-date">
|
||||
{{ formatDate(report.createdAt) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Report Content -->
|
||||
<div class="report-content">
|
||||
<div class="comment-content-preview">
|
||||
<strong>Comment:</strong>
|
||||
<p class="comment-text">{{ truncateComment(report.reportedComment.content) }}</p>
|
||||
</div>
|
||||
<div class="reason">
|
||||
<strong>Reason:</strong> {{ formatReason(report.reason) }}
|
||||
</div>
|
||||
<div class="details">
|
||||
<strong>Details:</strong>
|
||||
<p>{{ report.details }}</p>
|
||||
</div>
|
||||
@if (report.reviewNotes) {
|
||||
<div class="review-notes">
|
||||
<strong>Review Notes:</strong>
|
||||
<p>{{ report.reviewNotes }}</p>
|
||||
@if (report.reviewer) {
|
||||
<small class="reviewer">
|
||||
Reviewed by {{ report.reviewer.username }}
|
||||
</small>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Report Actions -->
|
||||
<div class="report-actions">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-review"
|
||||
(click)="openCommentReviewModal(report)"
|
||||
[attr.aria-label]="'Review report for comment by ' + report.reportedComment.user.username"
|
||||
>
|
||||
Review
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
} @empty {
|
||||
<div class="empty-state">
|
||||
<p>No comment reports found.</p>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Profile Review Modal -->
|
||||
@if (reviewingProfileReport()) {
|
||||
<div
|
||||
class="modal-overlay"
|
||||
(click)="closeReviewModal()"
|
||||
(keydown.escape)="closeReviewModal()"
|
||||
(click)="closeProfileReviewModal()"
|
||||
(keydown.escape)="closeProfileReviewModal()"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="modal-title"
|
||||
aria-labelledby="profile-modal-title"
|
||||
>
|
||||
<div
|
||||
class="modal-content"
|
||||
@@ -215,11 +350,11 @@ import { ProfileReportWithUsers, ReportStatus, ReportReason } from '@library/sha
|
||||
tabindex="-1"
|
||||
>
|
||||
<div class="modal-header">
|
||||
<h2 id="modal-title">Review Report</h2>
|
||||
<h2 id="profile-modal-title">Review Profile Report</h2>
|
||||
<button
|
||||
type="button"
|
||||
class="modal-close"
|
||||
(click)="closeReviewModal()"
|
||||
(click)="closeProfileReviewModal()"
|
||||
aria-label="Close modal"
|
||||
>
|
||||
×
|
||||
@@ -231,29 +366,29 @@ import { ProfileReportWithUsers, ReportStatus, ReportReason } from '@library/sha
|
||||
<div class="review-summary">
|
||||
<div class="summary-item">
|
||||
<strong>Reported User:</strong>
|
||||
<span>{{ reviewingReport()!.reportedUser.username }}</span>
|
||||
<span>{{ reviewingProfileReport()!.reportedUser.username }}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<strong>Reporter:</strong>
|
||||
<span>{{ reviewingReport()!.reporter.username }}</span>
|
||||
<span>{{ reviewingProfileReport()!.reporter.username }}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<strong>Reason:</strong>
|
||||
<span>{{ formatReason(reviewingReport()!.reason) }}</span>
|
||||
<span>{{ formatReason(reviewingProfileReport()!.reason) }}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<strong>Reported On:</strong>
|
||||
<span>{{ formatDate(reviewingReport()!.createdAt) }}</span>
|
||||
<span>{{ formatDate(reviewingProfileReport()!.createdAt) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="details-section">
|
||||
<strong>Details:</strong>
|
||||
<p class="details-text">{{ reviewingReport()!.details }}</p>
|
||||
<p class="details-text">{{ reviewingProfileReport()!.details }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Review Form -->
|
||||
<form (ngSubmit)="submitReview()" class="review-form">
|
||||
<form (ngSubmit)="submitProfileReview()" class="review-form">
|
||||
<div class="form-group">
|
||||
<label for="status">Update Status</label>
|
||||
<select
|
||||
@@ -287,7 +422,121 @@ import { ProfileReportWithUsers, ReportStatus, ReportReason } from '@library/sha
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
(click)="closeReviewModal()"
|
||||
(click)="closeProfileReviewModal()"
|
||||
[disabled]="submitting()"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary"
|
||||
[disabled]="submitting()"
|
||||
>
|
||||
{{ submitting() ? 'Submitting...' : 'Submit Review' }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Comment Review Modal -->
|
||||
@if (reviewingCommentReport()) {
|
||||
<div
|
||||
class="modal-overlay"
|
||||
(click)="closeCommentReviewModal()"
|
||||
(keydown.escape)="closeCommentReviewModal()"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="comment-modal-title"
|
||||
>
|
||||
<div
|
||||
class="modal-content"
|
||||
(click)="$event.stopPropagation()"
|
||||
(keydown)="$event.stopPropagation()"
|
||||
role="document"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div class="modal-header">
|
||||
<h2 id="comment-modal-title">Review Comment Report</h2>
|
||||
<button
|
||||
type="button"
|
||||
class="modal-close"
|
||||
(click)="closeCommentReviewModal()"
|
||||
aria-label="Close modal"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<!-- Report Summary -->
|
||||
<div class="review-summary">
|
||||
<div class="summary-item">
|
||||
<strong>Comment Author:</strong>
|
||||
<span>{{ reviewingCommentReport()!.reportedComment.user.username }}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<strong>Reporter:</strong>
|
||||
<span>{{ reviewingCommentReport()!.reporter.username }}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<strong>Reason:</strong>
|
||||
<span>{{ formatReason(reviewingCommentReport()!.reason) }}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<strong>Reported On:</strong>
|
||||
<span>{{ formatDate(reviewingCommentReport()!.createdAt) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="comment-full-content">
|
||||
<strong>Full Comment:</strong>
|
||||
<p class="comment-text">{{ reviewingCommentReport()!.reportedComment.content }}</p>
|
||||
</div>
|
||||
|
||||
<div class="details-section">
|
||||
<strong>Report Details:</strong>
|
||||
<p class="details-text">{{ reviewingCommentReport()!.details }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Review Form -->
|
||||
<form (ngSubmit)="submitCommentReview()" class="review-form">
|
||||
<div class="form-group">
|
||||
<label for="comment-status">Update Status</label>
|
||||
<select
|
||||
id="comment-status"
|
||||
name="status"
|
||||
[(ngModel)]="reviewForm.status"
|
||||
class="form-control"
|
||||
required
|
||||
aria-required="true"
|
||||
>
|
||||
<option value="PENDING">Pending</option>
|
||||
<option value="REVIEWED">Reviewed</option>
|
||||
<option value="DISMISSED">Dismissed</option>
|
||||
<option value="ACTION_TAKEN">Action Taken</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="comment-reviewNotes">Review Notes (Optional)</label>
|
||||
<textarea
|
||||
id="comment-reviewNotes"
|
||||
name="reviewNotes"
|
||||
[(ngModel)]="reviewForm.reviewNotes"
|
||||
class="form-control"
|
||||
rows="4"
|
||||
placeholder="Add any notes about your review..."
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="modal-actions">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
(click)="closeCommentReviewModal()"
|
||||
[disabled]="submitting()"
|
||||
>
|
||||
Cancel
|
||||
@@ -308,18 +557,66 @@ import { ProfileReportWithUsers, ReportStatus, ReportReason } from '@library/sha
|
||||
</div>
|
||||
`,
|
||||
styles: [`
|
||||
.admin-reports-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.admin-reports-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.header-section {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 2rem;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--witch-purple);
|
||||
margin-bottom: 2rem;
|
||||
margin: 0;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.report-type-toggle {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
background: var(--witch-lavender);
|
||||
padding: 0.25rem;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.toggle-btn {
|
||||
padding: 0.75rem 1.5rem;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
color: var(--witch-mauve);
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.toggle-btn:hover {
|
||||
color: var(--witch-purple);
|
||||
}
|
||||
|
||||
.toggle-btn.active {
|
||||
background: var(--witch-purple);
|
||||
color: var(--witch-moon);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.toggle-btn:focus {
|
||||
outline: 2px solid var(--witch-purple);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.loading, .error, .empty-state {
|
||||
text-align: center;
|
||||
padding: 3rem 2rem;
|
||||
@@ -535,6 +832,41 @@ import { ProfileReportWithUsers, ReportStatus, ReportReason } from '@library/sha
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.comment-content-preview {
|
||||
padding: 1rem;
|
||||
background: var(--witch-lavender);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.comment-content-preview strong {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--witch-purple);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.comment-text {
|
||||
color: var(--witch-mauve);
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.comment-full-content {
|
||||
margin-bottom: 1.5rem;
|
||||
padding: 1rem;
|
||||
background: var(--witch-lavender);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.comment-full-content strong {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--witch-purple);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Report Actions */
|
||||
.report-actions {
|
||||
display: flex;
|
||||
@@ -578,17 +910,21 @@ import { ProfileReportWithUsers, ReportStatus, ReportReason } from '@library/sha
|
||||
|
||||
/* Modal */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
position: fixed !important;
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
bottom: 0 !important;
|
||||
width: 100vw !important;
|
||||
height: 100vh !important;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
z-index: 9999 !important;
|
||||
padding: 1rem;
|
||||
margin: 0 !important;
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
@@ -813,6 +1149,7 @@ import { ProfileReportWithUsers, ReportStatus, ReportReason } from '@library/sha
|
||||
})
|
||||
export class AdminReportsComponent implements OnInit {
|
||||
private reportService = inject(ReportService);
|
||||
private commentReportService = inject(CommentReportService);
|
||||
private authService = inject(AuthService);
|
||||
private toastService = inject(ToastService);
|
||||
private router = inject(Router);
|
||||
@@ -820,12 +1157,15 @@ export class AdminReportsComponent implements OnInit {
|
||||
// Make ReportStatus accessible in template
|
||||
protected readonly ReportStatus = ReportStatus;
|
||||
|
||||
allReports = signal<ProfileReportWithUsers[]>([]);
|
||||
reportType = signal<'profile' | 'comment'>('profile');
|
||||
allProfileReports = signal<ProfileReportWithUsers[]>([]);
|
||||
allCommentReports = signal<CommentReportWithDetails[]>([]);
|
||||
loading = signal(true);
|
||||
error = signal<string | null>(null);
|
||||
activeFilter = signal<'all' | ReportStatus>('all');
|
||||
|
||||
reviewingReport = signal<ProfileReportWithUsers | null>(null);
|
||||
reviewingProfileReport = signal<ProfileReportWithUsers | null>(null);
|
||||
reviewingCommentReport = signal<CommentReportWithDetails | null>(null);
|
||||
submitting = signal(false);
|
||||
|
||||
reviewForm = {
|
||||
@@ -833,29 +1173,49 @@ export class AdminReportsComponent implements OnInit {
|
||||
reviewNotes: ''
|
||||
};
|
||||
|
||||
filteredReports = computed(() => {
|
||||
filteredProfileReports = computed(() => {
|
||||
const filter = this.activeFilter();
|
||||
if (filter === 'all') {
|
||||
return this.allReports();
|
||||
return this.allProfileReports();
|
||||
}
|
||||
return this.allReports().filter(report => report.status === filter);
|
||||
return this.allProfileReports().filter(report => report.status === filter);
|
||||
});
|
||||
|
||||
pendingCount = computed(() =>
|
||||
this.allReports().filter(r => r.status === ReportStatus.PENDING).length
|
||||
);
|
||||
filteredCommentReports = computed(() => {
|
||||
const filter = this.activeFilter();
|
||||
if (filter === 'all') {
|
||||
return this.allCommentReports();
|
||||
}
|
||||
return this.allCommentReports().filter(report => report.status === filter);
|
||||
});
|
||||
|
||||
reviewedCount = computed(() =>
|
||||
this.allReports().filter(r => r.status === ReportStatus.REVIEWED).length
|
||||
);
|
||||
pendingCount = computed(() => {
|
||||
if (this.reportType() === 'profile') {
|
||||
return this.allProfileReports().filter(r => r.status === ReportStatus.PENDING).length;
|
||||
}
|
||||
return this.allCommentReports().filter(r => r.status === ReportStatus.PENDING).length;
|
||||
});
|
||||
|
||||
dismissedCount = computed(() =>
|
||||
this.allReports().filter(r => r.status === ReportStatus.DISMISSED).length
|
||||
);
|
||||
reviewedCount = computed(() => {
|
||||
if (this.reportType() === 'profile') {
|
||||
return this.allProfileReports().filter(r => r.status === ReportStatus.REVIEWED).length;
|
||||
}
|
||||
return this.allCommentReports().filter(r => r.status === ReportStatus.REVIEWED).length;
|
||||
});
|
||||
|
||||
actionTakenCount = computed(() =>
|
||||
this.allReports().filter(r => r.status === ReportStatus.ACTION_TAKEN).length
|
||||
);
|
||||
dismissedCount = computed(() => {
|
||||
if (this.reportType() === 'profile') {
|
||||
return this.allProfileReports().filter(r => r.status === ReportStatus.DISMISSED).length;
|
||||
}
|
||||
return this.allCommentReports().filter(r => r.status === ReportStatus.DISMISSED).length;
|
||||
});
|
||||
|
||||
actionTakenCount = computed(() => {
|
||||
if (this.reportType() === 'profile') {
|
||||
return this.allProfileReports().filter(r => r.status === ReportStatus.ACTION_TAKEN).length;
|
||||
}
|
||||
return this.allCommentReports().filter(r => r.status === ReportStatus.ACTION_TAKEN).length;
|
||||
});
|
||||
|
||||
ngOnInit(): void {
|
||||
if (!this.authService.isAdmin()) {
|
||||
@@ -870,34 +1230,54 @@ export class AdminReportsComponent implements OnInit {
|
||||
this.loading.set(true);
|
||||
this.error.set(null);
|
||||
|
||||
if (this.reportType() === 'profile') {
|
||||
this.reportService.getAllReports().subscribe({
|
||||
next: (reports) => {
|
||||
this.allReports.set(reports);
|
||||
this.allProfileReports.set(reports);
|
||||
this.loading.set(false);
|
||||
},
|
||||
error: (err) => {
|
||||
this.error.set(err.message ?? 'Failed to load reports');
|
||||
this.error.set(err.message ?? 'Failed to load profile reports');
|
||||
this.loading.set(false);
|
||||
this.toastService.error('Failed to load reports');
|
||||
this.toastService.error('Failed to load profile reports');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.commentReportService.getAllReports().subscribe({
|
||||
next: (reports) => {
|
||||
this.allCommentReports.set(reports);
|
||||
this.loading.set(false);
|
||||
},
|
||||
error: (err) => {
|
||||
this.error.set(err.message ?? 'Failed to load comment reports');
|
||||
this.loading.set(false);
|
||||
this.toastService.error('Failed to load comment reports');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setReportType(type: 'profile' | 'comment'): void {
|
||||
this.reportType.set(type);
|
||||
this.activeFilter.set('all');
|
||||
this.loadReports();
|
||||
}
|
||||
|
||||
setFilter(filter: 'all' | ReportStatus): void {
|
||||
this.activeFilter.set(filter);
|
||||
}
|
||||
|
||||
openReviewModal(report: ProfileReportWithUsers): void {
|
||||
this.reviewingReport.set(report);
|
||||
openProfileReviewModal(report: ProfileReportWithUsers): void {
|
||||
this.reviewingProfileReport.set(report);
|
||||
this.reviewForm = {
|
||||
status: report.status,
|
||||
reviewNotes: report.reviewNotes || ''
|
||||
};
|
||||
}
|
||||
|
||||
closeReviewModal(): void {
|
||||
closeProfileReviewModal(): void {
|
||||
if (!this.submitting()) {
|
||||
this.reviewingReport.set(null);
|
||||
this.reviewingProfileReport.set(null);
|
||||
this.reviewForm = {
|
||||
status: ReportStatus.PENDING,
|
||||
reviewNotes: ''
|
||||
@@ -905,8 +1285,8 @@ export class AdminReportsComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
submitReview(): void {
|
||||
const report = this.reviewingReport();
|
||||
submitProfileReview(): void {
|
||||
const report = this.reviewingProfileReport();
|
||||
if (!report) {
|
||||
return;
|
||||
}
|
||||
@@ -919,20 +1299,73 @@ export class AdminReportsComponent implements OnInit {
|
||||
this.reviewForm.reviewNotes || undefined
|
||||
).subscribe({
|
||||
next: (updatedReport) => {
|
||||
this.allReports.update(reports =>
|
||||
this.allProfileReports.update(reports =>
|
||||
reports.map(r => r.id === updatedReport.id ? updatedReport : r)
|
||||
);
|
||||
this.toastService.success('Report updated successfully');
|
||||
this.toastService.success('Profile report updated successfully');
|
||||
this.submitting.set(false);
|
||||
this.closeReviewModal();
|
||||
this.closeProfileReviewModal();
|
||||
},
|
||||
error: (err) => {
|
||||
this.toastService.error(err.message ?? 'Failed to update report');
|
||||
this.toastService.error(err.message ?? 'Failed to update profile report');
|
||||
this.submitting.set(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
openCommentReviewModal(report: CommentReportWithDetails): void {
|
||||
this.reviewingCommentReport.set(report);
|
||||
this.reviewForm = {
|
||||
status: report.status,
|
||||
reviewNotes: report.reviewNotes || ''
|
||||
};
|
||||
}
|
||||
|
||||
closeCommentReviewModal(): void {
|
||||
if (!this.submitting()) {
|
||||
this.reviewingCommentReport.set(null);
|
||||
this.reviewForm = {
|
||||
status: ReportStatus.PENDING,
|
||||
reviewNotes: ''
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
submitCommentReview(): void {
|
||||
const report = this.reviewingCommentReport();
|
||||
if (!report) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.submitting.set(true);
|
||||
|
||||
this.commentReportService.updateReport(report.id, {
|
||||
status: this.reviewForm.status,
|
||||
reviewNotes: this.reviewForm.reviewNotes || undefined
|
||||
}).subscribe({
|
||||
next: (updatedReport) => {
|
||||
this.allCommentReports.update(reports =>
|
||||
reports.map(r => r.id === updatedReport.id ? updatedReport : r)
|
||||
);
|
||||
this.toastService.success('Comment report updated successfully');
|
||||
this.submitting.set(false);
|
||||
this.closeCommentReviewModal();
|
||||
},
|
||||
error: (err) => {
|
||||
this.toastService.error(err.message ?? 'Failed to update comment report');
|
||||
this.submitting.set(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
truncateComment(content: string, maxLength = 200): string {
|
||||
const stripped = content.replace(/<[^>]*>/g, '');
|
||||
if (stripped.length <= maxLength) {
|
||||
return stripped;
|
||||
}
|
||||
return stripped.substring(0, maxLength) + '...';
|
||||
}
|
||||
|
||||
formatStatus(status: ReportStatus): string {
|
||||
const statusMap: Record<ReportStatus, string> = {
|
||||
[ReportStatus.PENDING]: 'Pending',
|
||||
|
||||
Reference in New Issue
Block a user