From f9e8c476bc8ee1431eb27d1b030f9dbb0fcba686 Mon Sep 17 00:00:00 2001 From: tanyar09 Date: Tue, 25 Nov 2025 13:08:40 -0500 Subject: [PATCH] feat: Enhance reported photos management with cleanup functionality and report comment This commit introduces a new cleanup feature for reported photos, allowing admins to delete records based on their review status. The API has been updated with a new endpoint for cleanup operations, and the frontend now includes a button to trigger this action. Additionally, a report comment field has been added to the reported photo response model, improving the detail available for each reported photo. The user interface has been updated to display report comments and provide a confirmation dialog for the cleanup action. Documentation has been updated to reflect these changes. --- frontend/src/api/reportedPhotos.ts | 15 +++ frontend/src/components/Layout.tsx | 1 - frontend/src/pages/ReportedPhotos.tsx | 152 ++++++++++++++++++++------ src/web/api/reported_photos.py | 97 ++++++++++++++-- 4 files changed, 220 insertions(+), 45 deletions(-) diff --git a/frontend/src/api/reportedPhotos.ts b/frontend/src/api/reportedPhotos.ts index d4252e2..07ae8ef 100644 --- a/frontend/src/api/reportedPhotos.ts +++ b/frontend/src/api/reportedPhotos.ts @@ -11,6 +11,7 @@ export interface ReportedPhotoResponse { reviewed_at: string | null reviewed_by: number | null review_notes: string | null + report_comment: string | null photo_path: string | null photo_filename: string | null } @@ -36,6 +37,12 @@ export interface ReviewResponse { errors: string[] } +export interface ReportedCleanupResponse { + deleted_records: number + errors: string[] + warnings?: string[] +} + export const reportedPhotosApi = { listReportedPhotos: async (statusFilter?: string): Promise => { const { data } = await apiClient.get( @@ -54,5 +61,13 @@ export const reportedPhotosApi = { ) return data }, + + cleanupReportedPhotos: async (): Promise => { + const { data } = await apiClient.post( + '/api/v1/reported-photos/cleanup', + {}, + ) + return data + }, } diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index 014b2aa..ecd9388 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -27,7 +27,6 @@ export default function Layout() { { path: '/auto-match', label: 'Auto-Match', icon: '🤖', adminOnly: false }, { path: '/modify', label: 'Modify', icon: 'âœī¸', adminOnly: false }, { path: '/tags', label: 'Tag', icon: 'đŸˇī¸', adminOnly: false }, - { path: '/manage-photos', label: 'Manage Photos', icon: '📷', adminOnly: false }, { path: '/faces-maintenance', label: 'Faces Maintenance', icon: '🔧', adminOnly: false }, { path: '/approve-identified', label: 'Approve identified', icon: '✅', adminOnly: true }, { path: '/manage-users', label: 'Manage users', icon: 'đŸ‘Ĩ', adminOnly: true }, diff --git a/frontend/src/pages/ReportedPhotos.tsx b/frontend/src/pages/ReportedPhotos.tsx index a02f1b0..3b33bf4 100644 --- a/frontend/src/pages/ReportedPhotos.tsx +++ b/frontend/src/pages/ReportedPhotos.tsx @@ -13,6 +13,7 @@ export default function ReportedPhotos() { const [decisions, setDecisions] = useState>({}) const [reviewNotes, setReviewNotes] = useState>({}) const [submitting, setSubmitting] = useState(false) + const [clearing, setClearing] = useState(false) const [statusFilter, setStatusFilter] = useState('pending') const loadReportedPhotos = useCallback(async () => { @@ -55,10 +56,14 @@ export default function ReportedPhotos() { } const handleDecisionChange = (id: number, decision: 'keep' | 'remove') => { - setDecisions((prev) => ({ - ...prev, - [id]: decision, - })) + setDecisions((prev) => { + const currentDecision = prev[id] ?? null + const nextDecision = currentDecision === decision ? null : decision + return { + ...prev, + [id]: nextDecision, + } + }) } const handleReviewNotesChange = (id: number, notes: string) => { @@ -124,18 +129,21 @@ export default function ReportedPhotos() { decisions: decisionsList, }) - const message = [ + const messageParts = [ `✅ Kept: ${response.kept}`, `❌ Removed: ${response.removed}`, - response.errors.length > 0 ? `âš ī¸ Errors: ${response.errors.length}` : '', ] - .filter(Boolean) - .join('\n') + + if (import.meta.env.DEV && response.errors.length > 0) { + messageParts.push(`âš ī¸ Errors: ${response.errors.length}`) + } + + const message = messageParts.join('\n') alert(message) if (response.errors.length > 0) { - console.error('Errors:', response.errors) + console.error('Reported photo review errors:', response.errors) } // Reload the list to show updated status @@ -153,6 +161,51 @@ export default function ReportedPhotos() { } } + const handleClearDatabase = async () => { + const confirmMessage = [ + 'Delete all kept and removed reported photo records from the auth database?', + '', + 'Only photos with Pending status will remain.', + 'This action cannot be undone.', + ].join('\n') + + if (!confirm(confirmMessage)) { + return + } + + setClearing(true) + try { + const response = await reportedPhotosApi.cleanupReportedPhotos() + const summary = [ + `✅ Deleted ${response.deleted_records} record(s)`, + response.warnings && response.warnings.length > 0 + ? `â„šī¸ ${response.warnings.join('; ')}` + : '', + response.errors.length > 0 ? `âš ī¸ ${response.errors.join('; ')}` : '', + ] + .filter(Boolean) + .join('\n') + + alert(summary || 'Cleanup complete.') + + if (response.errors.length > 0) { + console.error('Cleanup errors:', response.errors) + } + if (response.warnings && response.warnings.length > 0) { + console.info('Cleanup warnings:', response.warnings) + } + + await loadReportedPhotos() + } catch (err: any) { + const errorMessage = + err.response?.data?.detail || err.message || 'Failed to cleanup reported photos' + alert(`Error: ${errorMessage}`) + console.error('Error clearing reported photos:', err) + } finally { + setClearing(false) + } + } + return (
@@ -179,32 +232,47 @@ export default function ReportedPhotos() { {!loading && !error && ( <> -
-
-
- Total reported photos: {reportedPhotos.length} +
+
+
+
+ Total reported photos:{' '} + {reportedPhotos.length} +
+
- + {submitting ? 'Submitting...' : 'Submit Decisions'} + +
+
+ + + Clear kept/removed records +
-
{reportedPhotos.length === 0 ? ( @@ -225,6 +293,9 @@ export default function ReportedPhotos() { Reported At + + Report Comment + Status @@ -299,6 +370,15 @@ export default function ReportedPhotos() { {formatDate(reported.reported_at)}
+ + {reported.report_comment ? ( +
+ {reported.report_comment} +
+ ) : ( + - + )} +