diff --git a/src/components/moderation/ModerationQueue.tsx b/src/components/moderation/ModerationQueue.tsx index 558f6dcb..8b9f1cc6 100644 --- a/src/components/moderation/ModerationQueue.tsx +++ b/src/components/moderation/ModerationQueue.tsx @@ -1518,20 +1518,39 @@ export const ModerationQueue = forwardRef((props, ref) => { item.submission_type === 'operator' || item.submission_type === 'property_owner' || item.submission_type === 'park' || - item.submission_type === 'ride') ? ( + item.submission_type === 'ride' || + item.submission_type === 'ride_model' || + item.submission_type === 'photo_delete' || + item.submission_type === 'photo_edit') ? ( ) : ( -
-
- Type: {item.submission_type} +
+
+ + + Unknown Submission Type +
-
-                          {JSON.stringify(item.content, null, 2)}
-                        
+
+ Type: {item.submission_type} +
+ {item.content?.action && ( +
+ Action: {item.content.action} +
+ )} +
+ + View raw data (for developers) + +
+                            {JSON.stringify(item.content, null, 2)}
+                          
+
)}
diff --git a/src/components/moderation/PhotoComparison.tsx b/src/components/moderation/PhotoComparison.tsx index 746c8acc..cbccfa31 100644 --- a/src/components/moderation/PhotoComparison.tsx +++ b/src/components/moderation/PhotoComparison.tsx @@ -118,6 +118,9 @@ interface PhotoDeletionPreviewProps { url: string; title?: string; caption?: string; + entity_type?: string; + entity_name?: string; + deletion_reason?: string; }; compact?: boolean; } @@ -147,12 +150,20 @@ export function PhotoDeletionPreview({ photo, compact = false }: PhotoDeletionPr loading="lazy" /> - {(photo.title || photo.caption) && ( -
- {photo.title &&
{photo.title}
} - {photo.caption &&
{photo.caption}
} -
- )} +
+ {photo.title &&
{photo.title}
} + {photo.caption &&
{photo.caption}
} + {photo.entity_type && photo.entity_name && ( +
+ From: {photo.entity_type} - {photo.entity_name} +
+ )} + {photo.deletion_reason && ( +
+ Reason: {photo.deletion_reason} +
+ )} +
); diff --git a/src/components/moderation/SubmissionChangesDisplay.tsx b/src/components/moderation/SubmissionChangesDisplay.tsx index 5a411c73..a221d2ff 100644 --- a/src/components/moderation/SubmissionChangesDisplay.tsx +++ b/src/components/moderation/SubmissionChangesDisplay.tsx @@ -54,11 +54,14 @@ export function SubmissionChangesDisplay({ switch (item.item_type) { case 'park': return ; case 'ride': return ; + case 'ride_model': return ; case 'manufacturer': case 'operator': case 'property_owner': case 'designer': return ; - case 'photo': return ; + case 'photo': + case 'photo_edit': + case 'photo_delete': return ; default: return ; } }; diff --git a/src/lib/submissionChangeDetection.ts b/src/lib/submissionChangeDetection.ts index e3e1e3dd..350a4d4a 100644 --- a/src/lib/submissionChangeDetection.ts +++ b/src/lib/submissionChangeDetection.ts @@ -30,6 +30,9 @@ export interface PhotoChange { newCaption?: string; oldTitle?: string; newTitle?: string; + entity_type?: string; + entity_name?: string; + deletion_reason?: string; }; } @@ -51,8 +54,8 @@ async function detectPhotoChanges(submissionId: string): Promise const changes: PhotoChange[] = []; try { - // Fetch photo submission with items - use array query to avoid 406 errors - const { data: photoSubmissions, error } = await supabase + // First check for photo submission items (photo additions) + const { data: photoSubmissions, error: photoError } = await supabase .from('photo_submissions') .select(` *, @@ -60,23 +63,61 @@ async function detectPhotoChanges(submissionId: string): Promise `) .eq('submission_id', submissionId); - if (error) { - console.error('Error fetching photo submissions:', error); - return changes; + if (photoError) { + console.error('Error fetching photo submissions:', photoError); + } else { + const photoSubmission = photoSubmissions?.[0]; + if (photoSubmission?.items && photoSubmission.items.length > 0) { + changes.push({ + type: 'added', + photos: photoSubmission.items.map((item: any) => ({ + url: item.cloudflare_image_url, + title: item.title, + caption: item.caption + })) + }); + } } - const photoSubmission = photoSubmissions?.[0]; - if (photoSubmission?.items && photoSubmission.items.length > 0) { - // For now, treat all photos as additions - // TODO: Implement edit/delete detection by comparing with existing entity photos - changes.push({ - type: 'added', - photos: photoSubmission.items.map((item: any) => ({ - url: item.cloudflare_image_url, - title: item.title, - caption: item.caption - })) - }); + // Check for photo edits and deletions in submission_items + const { data: submissionItems, error: itemsError } = await supabase + .from('submission_items') + .select('*') + .eq('submission_id', submissionId) + .in('item_type', ['photo_edit', 'photo_delete']); + + if (itemsError) { + console.error('Error fetching submission items for photos:', itemsError); + } else if (submissionItems && submissionItems.length > 0) { + for (const item of submissionItems) { + const itemData = item.item_data as Record; + const originalData = item.original_data as Record | null; + + if (item.item_type === 'photo_delete' && itemData) { + changes.push({ + type: 'deleted', + photo: { + url: itemData.photo_url || itemData.cloudflare_image_url || '', + title: itemData.title, + caption: itemData.caption, + entity_type: itemData.entity_type, + entity_name: itemData.entity_name, + deletion_reason: itemData.deletion_reason + } + }); + } else if (item.item_type === 'photo_edit' && itemData && originalData) { + changes.push({ + type: 'edited', + photo: { + url: itemData.photo_url || itemData.cloudflare_image_url || '', + title: itemData.title, + caption: itemData.caption, + oldTitle: originalData.title, + oldCaption: originalData.caption + } + }); + } + } } } catch (err) { console.error('Error detecting photo changes:', err); @@ -420,6 +461,14 @@ export function formatFieldValue(value: any): string { } } + // Handle enum-like strings (snake_case or kebab-case) - capitalize and replace separators + if (typeof value === 'string' && (value.includes('_') || value.includes('-'))) { + return value + .split(/[_-]/) + .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .join(' '); + } + if (typeof value === 'number') return value.toLocaleString(); return String(value); }