import { Badge } from '@/components/ui/badge'; import { formatFieldName, formatFieldValue } from '@/lib/submissionChangeDetection'; import type { FieldChange, ImageChange } from '@/lib/submissionChangeDetection'; import { ArrowRight } from 'lucide-react'; import { ArrayFieldDiff } from './ArrayFieldDiff'; import { SpecialFieldDisplay } from './SpecialFieldDisplay'; import type { DatePrecision } from '@/components/ui/flexible-date-input'; // Helper to format compact values (truncate long strings) function formatCompactValue(value: unknown, precision?: DatePrecision, maxLength = 30): string { const formatted = formatFieldValue(value, precision); if (formatted.length > maxLength) { return formatted.substring(0, maxLength) + '...'; } return formatted; } interface FieldDiffProps { change: FieldChange; compact?: boolean; } export function FieldDiff({ change, compact = false }: FieldDiffProps) { const { field, oldValue, newValue, changeType, metadata } = change; // Extract precision for date fields const precision = metadata?.precision; const oldPrecision = metadata?.oldPrecision; const newPrecision = metadata?.newPrecision; // Check if this is an array field that needs special handling if (Array.isArray(oldValue) && Array.isArray(newValue)) { return ( ); } // 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'; case 'removed': return 'text-red-600 dark:text-red-400'; case 'modified': return 'text-amber-600 dark:text-amber-400'; default: return ''; } }; if (compact) { const fieldName = formatFieldName(field); if (changeType === 'added') { return ( {fieldName}: + {formatCompactValue(newValue, precision)} ); } if (changeType === 'removed') { return ( {fieldName}: {formatCompactValue(oldValue, precision)} ); } if (changeType === 'modified') { return ( {fieldName}: {formatCompactValue(oldValue, oldPrecision || precision)} → {formatCompactValue(newValue, newPrecision || precision)} ); } return ( {fieldName} ); } return (
{formatFieldName(field)}
{changeType === 'added' && (
+ {formatFieldValue(newValue, precision)}
)} {changeType === 'removed' && (
{formatFieldValue(oldValue, precision)}
)} {changeType === 'modified' && (
{formatFieldValue(oldValue, oldPrecision || precision)} {formatFieldValue(newValue, newPrecision || precision)}
)}
); } interface ImageDiffProps { change: ImageChange; compact?: boolean; } export function ImageDiff({ change, compact = false }: ImageDiffProps) { const { type, oldUrl, newUrl } = change; if (compact) { const imageLabel = type === 'banner' ? 'Banner' : 'Card'; const isAddition = !oldUrl && newUrl; const isRemoval = oldUrl && !newUrl; const isReplacement = oldUrl && newUrl; let action = ''; let colorClass = 'text-blue-600 dark:text-blue-400'; if (isAddition) { action = ' (Added)'; colorClass = 'text-green-600 dark:text-green-400'; } else if (isRemoval) { action = ' (Removed)'; colorClass = 'text-red-600 dark:text-red-400'; } else if (isReplacement) { action = ' (Changed)'; colorClass = 'text-amber-600 dark:text-amber-400'; } return ( {imageLabel} Image{action} ); } // Determine scenario const isAddition = !oldUrl && newUrl; const isRemoval = oldUrl && !newUrl; const isReplacement = oldUrl && newUrl; return (
{type === 'banner' ? 'Banner' : 'Card'} Image {isAddition && (New)} {isRemoval && (Removed)} {isReplacement && (Changed)}
{oldUrl && (
Before
Previous
)} {oldUrl && newUrl && ( )} {newUrl && (
{isAddition ? 'New Image' : 'After'}
New
)}
); } interface LocationData { location_id?: string; city?: string; state_province?: string; country?: string; postal_code?: string; latitude?: number | string; longitude?: number | string; } interface LocationDiffProps { oldLocation: LocationData | string | null | undefined; newLocation: LocationData | string | null | undefined; compact?: boolean; } export function LocationDiff({ oldLocation, newLocation, compact = false }: LocationDiffProps) { // Type guards for LocationData const isLocationData = (loc: unknown): loc is LocationData => { return typeof loc === 'object' && loc !== null && !Array.isArray(loc); }; // Check if we're creating a new location entity const isCreatingNewLocation = isLocationData(oldLocation) && oldLocation.location_id && !oldLocation.city && isLocationData(newLocation) && newLocation.city; const formatLocation = (loc: LocationData | string | null | undefined) => { if (!loc) return 'None'; if (typeof loc === 'string') return loc; // Handle location_id reference if (loc.location_id && !loc.city) { return `Location ID: ${loc.location_id.substring(0, 8)}...`; } if (typeof loc === 'object') { const parts: string[] = []; if (loc.city) parts.push(String(loc.city)); if (loc.state_province) parts.push(String(loc.state_province)); if (loc.country && loc.country !== loc.state_province) parts.push(String(loc.country)); if (loc.postal_code) parts.push(String(loc.postal_code)); let locationStr = parts.join(', ') || 'Unknown'; // Add coordinates if available if (loc.latitude && loc.longitude) { const lat = Number(loc.latitude).toFixed(6); const lng = Number(loc.longitude).toFixed(6); locationStr += ` (${lat}°, ${lng}°)`; } return locationStr; } return String(loc); }; // Check if only coordinates changed const onlyCoordinatesChanged = isLocationData(oldLocation) && isLocationData(newLocation) && oldLocation.city === newLocation.city && oldLocation.state_province === newLocation.state_province && oldLocation.country === newLocation.country && oldLocation.postal_code === newLocation.postal_code && (Number(oldLocation.latitude) !== Number(newLocation.latitude) || Number(oldLocation.longitude) !== Number(newLocation.longitude)); if (compact) { const oldLoc = formatLocation(oldLocation); const newLoc = formatLocation(newLocation); if (!oldLocation && newLocation) { return ( Location: + {newLoc} ); } if (oldLocation && !newLocation) { return ( Location: {oldLoc} ); } return ( Location{isCreatingNewLocation && ' (New Entity)'} {onlyCoordinatesChanged && ' (GPS Refined)'} : {oldLoc} → {newLoc} ); } return (
Location {isCreatingNewLocation && ( New location entity )} {onlyCoordinatesChanged && ( GPS coordinates refined )}
{oldLocation && ( {formatLocation(oldLocation)} )} {oldLocation && newLocation && ( )} {newLocation && ( {formatLocation(newLocation)} )}
); }