From 177e86a77ad978ac4d5670a9ef6c0a4de05c3dcf Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Fri, 3 Oct 2025 15:59:05 +0000 Subject: [PATCH] Implement remaining phases --- src/components/moderation/FieldComparison.tsx | 7 + src/components/moderation/ItemReviewCard.tsx | 14 +- .../moderation/SpecialFieldDisplay.tsx | 327 ++++++++++++++++++ .../moderation/SubmissionReviewManager.tsx | 1 + 4 files changed, 346 insertions(+), 3 deletions(-) create mode 100644 src/components/moderation/SpecialFieldDisplay.tsx diff --git a/src/components/moderation/FieldComparison.tsx b/src/components/moderation/FieldComparison.tsx index 96876565..c35b13b3 100644 --- a/src/components/moderation/FieldComparison.tsx +++ b/src/components/moderation/FieldComparison.tsx @@ -3,6 +3,7 @@ import { formatFieldName, formatFieldValue } from '@/lib/submissionChangeDetecti import type { FieldChange, ImageChange } from '@/lib/submissionChangeDetection'; import { ArrowRight } from 'lucide-react'; import { ArrayFieldDiff } from './ArrayFieldDiff'; +import { SpecialFieldDisplay } from './SpecialFieldDisplay'; interface FieldDiffProps { change: FieldChange; @@ -24,6 +25,12 @@ export function FieldDiff({ change, compact = false }: FieldDiffProps) { ); } + // Check if this is a special field type that needs custom rendering + const specialDisplay = SpecialFieldDisplay({ change, compact }); + if (specialDisplay) { + return specialDisplay; + } + const getChangeColor = () => { switch (changeType) { case 'added': return 'text-green-600 dark:text-green-400'; diff --git a/src/components/moderation/ItemReviewCard.tsx b/src/components/moderation/ItemReviewCard.tsx index 16c69f49..a67f1729 100644 --- a/src/components/moderation/ItemReviewCard.tsx +++ b/src/components/moderation/ItemReviewCard.tsx @@ -11,9 +11,10 @@ interface ItemReviewCardProps { item: SubmissionItemWithDeps; onEdit: () => void; onStatusChange: (status: 'approved' | 'rejected') => void; + submissionId: string; } -export function ItemReviewCard({ item, onEdit, onStatusChange }: ItemReviewCardProps) { +export function ItemReviewCard({ item, onEdit, onStatusChange, submissionId }: ItemReviewCardProps) { const isMobile = useIsMobile(); const getItemIcon = () => { @@ -40,8 +41,15 @@ export function ItemReviewCard({ item, onEdit, onStatusChange }: ItemReviewCardP }; const renderItemPreview = () => { - // Use standardized change detection display - return ; + // Use detailed view for review manager with photo detection + return ( + + ); }; return ( diff --git a/src/components/moderation/SpecialFieldDisplay.tsx b/src/components/moderation/SpecialFieldDisplay.tsx new file mode 100644 index 00000000..296d0f24 --- /dev/null +++ b/src/components/moderation/SpecialFieldDisplay.tsx @@ -0,0 +1,327 @@ +import { useState } from 'react'; +import { Badge } from '@/components/ui/badge'; +import { MeasurementDisplay } from '@/components/ui/measurement-display'; +import { SpeedDisplay } from '@/components/ui/speed-display'; +import { MapPin, ArrowRight, Calendar, ExternalLink } from 'lucide-react'; +import type { FieldChange } from '@/lib/submissionChangeDetection'; +import { formatFieldValue } from '@/lib/submissionChangeDetection'; + +interface SpecialFieldDisplayProps { + change: FieldChange; + compact?: boolean; +} + +export function SpecialFieldDisplay({ change, compact = false }: SpecialFieldDisplayProps) { + const fieldName = change.field.toLowerCase(); + + // Detect field type + if (fieldName.includes('speed') || fieldName === 'max_speed_kmh') { + return ; + } + + if (fieldName.includes('height') || fieldName.includes('length') || + fieldName === 'max_height_meters' || fieldName === 'length_meters' || + fieldName === 'drop_height_meters') { + return ; + } + + if (fieldName === 'status') { + return ; + } + + if (fieldName.includes('date') && !fieldName.includes('updated') && !fieldName.includes('created')) { + return ; + } + + if (fieldName.includes('_id') && fieldName !== 'id' && fieldName !== 'user_id') { + return ; + } + + if (fieldName === 'latitude' || fieldName === 'longitude') { + return ; + } + + // Fallback to null, will be handled by regular FieldDiff + return null; +} + +function SpeedFieldDisplay({ change, compact }: { change: FieldChange; compact: boolean }) { + if (compact) { + return ( + + Speed + + ); + } + + const formatFieldName = (name: string) => + name.replace(/_/g, ' ').replace(/([A-Z])/g, ' $1').trim() + .replace(/^./, str => str.toUpperCase()); + + return ( +
+
{formatFieldName(change.field)}
+ + {change.changeType === 'modified' && ( +
+
+ +
+ +
+ +
+
+ )} + + {change.changeType === 'added' && ( +
+ + +
+ )} + + {change.changeType === 'removed' && ( +
+ +
+ )} +
+ ); +} + +function MeasurementFieldDisplay({ change, compact }: { change: FieldChange; compact: boolean }) { + if (compact) { + return ( + + Measurement + + ); + } + + const formatFieldName = (name: string) => + name.replace(/_/g, ' ').replace(/([A-Z])/g, ' $1').trim() + .replace(/^./, str => str.toUpperCase()); + + return ( +
+
{formatFieldName(change.field)}
+ + {change.changeType === 'modified' && ( +
+
+ +
+ +
+ +
+
+ )} + + {change.changeType === 'added' && ( +
+ + +
+ )} + + {change.changeType === 'removed' && ( +
+ +
+ )} +
+ ); +} + +function StatusFieldDisplay({ change, compact }: { change: FieldChange; compact: boolean }) { + const getStatusColor = (status: string) => { + const statusLower = String(status).toLowerCase(); + if (statusLower === 'operating' || statusLower === 'active') return 'bg-green-500/10 text-green-600 dark:text-green-400 border-green-500/20'; + if (statusLower === 'closed' || statusLower === 'inactive') return 'bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/20'; + if (statusLower === 'under_construction' || statusLower === 'pending') return 'bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/20'; + return 'bg-muted/30 text-muted-foreground'; + }; + + if (compact) { + return ( + + Status + + ); + } + + const formatFieldName = (name: string) => + name.replace(/_/g, ' ').replace(/([A-Z])/g, ' $1').trim() + .replace(/^./, str => str.toUpperCase()); + + return ( +
+
{formatFieldName(change.field)}
+ + {change.changeType === 'modified' && ( +
+ + {formatFieldValue(change.oldValue)} + + + + {formatFieldValue(change.newValue)} + +
+ )} + + {change.changeType === 'added' && ( + + {formatFieldValue(change.newValue)} + + )} + + {change.changeType === 'removed' && ( + + {formatFieldValue(change.oldValue)} + + )} +
+ ); +} + +function DateFieldDisplay({ change, compact }: { change: FieldChange; compact: boolean }) { + if (compact) { + return ( + + + Date + + ); + } + + const formatFieldName = (name: string) => + name.replace(/_/g, ' ').replace(/([A-Z])/g, ' $1').trim() + .replace(/^./, str => str.toUpperCase()); + + return ( +
+
+ + {formatFieldName(change.field)} +
+ + {change.changeType === 'modified' && ( +
+ + {formatFieldValue(change.oldValue)} + + + + {formatFieldValue(change.newValue)} + +
+ )} + + {change.changeType === 'added' && ( +
+ + {formatFieldValue(change.newValue)} +
+ )} + + {change.changeType === 'removed' && ( +
+ {formatFieldValue(change.oldValue)} +
+ )} +
+ ); +} + +function RelationshipFieldDisplay({ change, compact }: { change: FieldChange; compact: boolean }) { + // This would ideally fetch entity names, but for now we show IDs with better formatting + const formatFieldName = (name: string) => + name.replace(/_id$/, '').replace(/_/g, ' ').trim() + .replace(/^./, str => str.toUpperCase()); + + if (compact) { + return ( + + {formatFieldName(change.field)} + + ); + } + + return ( +
+
{formatFieldName(change.field)}
+ + {change.changeType === 'modified' && ( +
+ + {String(change.oldValue).slice(0, 8)}... + + + + {String(change.newValue).slice(0, 8)}... + +
+ )} + + {change.changeType === 'added' && ( +
+ + {String(change.newValue).slice(0, 8)}... +
+ )} + + {change.changeType === 'removed' && ( +
+ {String(change.oldValue).slice(0, 8)}... +
+ )} +
+ ); +} + +function CoordinateFieldDisplay({ change, compact }: { change: FieldChange; compact: boolean }) { + if (compact) { + return ( + + + Coordinates + + ); + } + + const formatFieldName = (name: string) => + name.replace(/_/g, ' ').replace(/([A-Z])/g, ' $1').trim() + .replace(/^./, str => str.toUpperCase()); + + return ( +
+
+ + {formatFieldName(change.field)} +
+ + {change.changeType === 'modified' && ( +
+ + {Number(change.oldValue).toFixed(6)}° + + + + {Number(change.newValue).toFixed(6)}° + +
+ )} + + {change.changeType === 'added' && ( +
+ + {Number(change.newValue).toFixed(6)}° +
+ )} + + {change.changeType === 'removed' && ( +
+ {Number(change.oldValue).toFixed(6)}° +
+ )} +
+ ); +} diff --git a/src/components/moderation/SubmissionReviewManager.tsx b/src/components/moderation/SubmissionReviewManager.tsx index 111d223c..fb49be7c 100644 --- a/src/components/moderation/SubmissionReviewManager.tsx +++ b/src/components/moderation/SubmissionReviewManager.tsx @@ -420,6 +420,7 @@ export function SubmissionReviewManager({ // Status changes handled via approve/reject actions await loadSubmissionItems(); }} + submissionId={submissionId} /> ))}