diff --git a/src/components/moderation/FieldComparison.tsx b/src/components/moderation/FieldComparison.tsx index 017cea2d..dd41e4a3 100644 --- a/src/components/moderation/FieldComparison.tsx +++ b/src/components/moderation/FieldComparison.tsx @@ -6,8 +6,8 @@ import { ArrayFieldDiff } from './ArrayFieldDiff'; import { SpecialFieldDisplay } from './SpecialFieldDisplay'; // Helper to format compact values (truncate long strings) -function formatCompactValue(value: any, maxLength = 30): string { - const formatted = formatFieldValue(value); +function formatCompactValue(value: any, precision?: 'day' | 'month' | 'year', maxLength = 30): string { + const formatted = formatFieldValue(value, precision); if (formatted.length > maxLength) { return formatted.substring(0, maxLength) + '...'; } @@ -20,7 +20,12 @@ interface FieldDiffProps { } export function FieldDiff({ change, compact = false }: FieldDiffProps) { - const { field, oldValue, newValue, changeType } = change; + 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)) { @@ -55,7 +60,7 @@ export function FieldDiff({ change, compact = false }: FieldDiffProps) { if (changeType === 'added') { return ( - {fieldName}: + {formatCompactValue(newValue)} + {fieldName}: + {formatCompactValue(newValue, precision)} ); } @@ -63,7 +68,7 @@ export function FieldDiff({ change, compact = false }: FieldDiffProps) { if (changeType === 'removed') { return ( - {fieldName}: {formatCompactValue(oldValue)} + {fieldName}: {formatCompactValue(oldValue, precision)} ); } @@ -71,7 +76,7 @@ export function FieldDiff({ change, compact = false }: FieldDiffProps) { if (changeType === 'modified') { return ( - {fieldName}: {formatCompactValue(oldValue)} → {formatCompactValue(newValue)} + {fieldName}: {formatCompactValue(oldValue, oldPrecision || precision)} → {formatCompactValue(newValue, newPrecision || precision)} ); } @@ -89,24 +94,24 @@ export function FieldDiff({ change, compact = false }: FieldDiffProps) { {changeType === 'added' && (
- + {formatFieldValue(newValue)} + + {formatFieldValue(newValue, precision)}
)} {changeType === 'removed' && (
- {formatFieldValue(oldValue)} + {formatFieldValue(oldValue, precision)}
)} {changeType === 'modified' && (
- {formatFieldValue(oldValue)} + {formatFieldValue(oldValue, oldPrecision || precision)} - {formatFieldValue(newValue)} + {formatFieldValue(newValue, newPrecision || precision)}
)} diff --git a/src/components/moderation/SpecialFieldDisplay.tsx b/src/components/moderation/SpecialFieldDisplay.tsx index 296d0f24..0fd1a200 100644 --- a/src/components/moderation/SpecialFieldDisplay.tsx +++ b/src/components/moderation/SpecialFieldDisplay.tsx @@ -186,6 +186,11 @@ function StatusFieldDisplay({ change, compact }: { change: FieldChange; compact: } function DateFieldDisplay({ change, compact }: { change: FieldChange; compact: boolean }) { + // Extract precision from metadata + const precision = change.metadata?.precision; + const oldPrecision = change.metadata?.oldPrecision; + const newPrecision = change.metadata?.newPrecision; + if (compact) { return ( @@ -204,29 +209,34 @@ function DateFieldDisplay({ change, compact }: { change: FieldChange; compact: b
{formatFieldName(change.field)} + {precision && ( + + {precision === 'year' ? 'Year Only' : precision === 'month' ? 'Month & Year' : 'Full Date'} + + )}
{change.changeType === 'modified' && (
- {formatFieldValue(change.oldValue)} + {formatFieldValue(change.oldValue, oldPrecision || precision)} - {formatFieldValue(change.newValue)} + {formatFieldValue(change.newValue, newPrecision || precision)}
)} {change.changeType === 'added' && (
- + {formatFieldValue(change.newValue)} + + {formatFieldValue(change.newValue, precision)}
)} {change.changeType === 'removed' && (
- {formatFieldValue(change.oldValue)} + {formatFieldValue(change.oldValue, precision)}
)} diff --git a/src/lib/entityValidationSchemas.ts b/src/lib/entityValidationSchemas.ts index a9b4fd8d..57186cde 100644 --- a/src/lib/entityValidationSchemas.ts +++ b/src/lib/entityValidationSchemas.ts @@ -101,18 +101,10 @@ export const companyValidationSchema = z.object({ company_type: z.enum(['manufacturer', 'designer', 'operator', 'property_owner']), description: z.string().max(2000, 'Description must be less than 2000 characters').optional(), person_type: z.enum(['company', 'individual', 'firm', 'organization']), - founded_year: z.string() - .optional() - .transform(val => { - if (!val || val.trim() === '') return undefined; - const num = Number(val); - return isNaN(num) ? undefined : num; - }) - .refine(val => val === undefined || (typeof val === 'number' && val >= 1800 && val <= currentYear), { - message: `Founded year must be between 1800 and ${currentYear}` - }), + // Support both founded_date (preferred) and founded_year (legacy) founded_date: z.string().optional(), founded_date_precision: z.enum(['day', 'month', 'year']).optional(), + founded_year: z.number().int().min(1800).max(currentYear).optional().nullable(), headquarters_location: z.string().max(200, 'Location must be less than 200 characters').optional(), website_url: z.string().optional().refine((val) => { if (!val || val === '') return true; diff --git a/src/lib/submissionChangeDetection.ts b/src/lib/submissionChangeDetection.ts index e51b93ee..50addf49 100644 --- a/src/lib/submissionChangeDetection.ts +++ b/src/lib/submissionChangeDetection.ts @@ -8,6 +8,9 @@ export interface FieldChange { changeType: 'added' | 'removed' | 'modified'; metadata?: { isCreatingNewLocation?: boolean; + precision?: 'day' | 'month' | 'year'; + oldPrecision?: 'day' | 'month' | 'year'; + newPrecision?: 'day' | 'month' | 'year'; }; } @@ -234,28 +237,32 @@ export async function detectChanges( // Check for changes if (!isEqual(oldValue, newValue)) { - if (oldEmpty && !newEmpty) { - fieldChanges.push({ - field: key, - oldValue, - newValue, - changeType: 'added', - }); - } else if (newEmpty && !oldEmpty) { - fieldChanges.push({ - field: key, - oldValue, - newValue, - changeType: 'removed', - }); - } else { - fieldChanges.push({ - field: key, - oldValue, - newValue, - changeType: 'modified', - }); + const fieldChange: FieldChange = { + field: key, + oldValue, + newValue, + changeType: oldEmpty && !newEmpty ? 'added' : + newEmpty && !oldEmpty ? 'removed' : + 'modified', + }; + + // Add precision metadata for date fields + if (key.endsWith('_date') && !key.endsWith('_precision')) { + const precisionKey = `${key}_precision`; + const newPrecision = itemData[precisionKey]; + const oldPrecision = originalData[precisionKey]; + + if (newPrecision || oldPrecision) { + fieldChange.metadata = { + ...fieldChange.metadata, + precision: newPrecision || oldPrecision, + oldPrecision, + newPrecision, + }; + } } + + fieldChanges.push(fieldChange); } }); @@ -490,14 +497,23 @@ function formatEntityType(entityType: string): string { /** * Format field value for display */ -export function formatFieldValue(value: any): string { +export function formatFieldValue(value: any, precision?: 'day' | 'month' | 'year'): string { if (value === null || value === undefined) return 'None'; if (typeof value === 'boolean') return value ? 'Yes' : 'No'; - // Handle dates + // Handle dates with precision support if (value instanceof Date || (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}/.test(value))) { try { const date = new Date(value); + + // Apply precision if provided + if (precision === 'year') { + return date.getFullYear().toString(); + } else if (precision === 'month') { + return date.toLocaleDateString('en-US', { year: 'numeric', month: 'long' }); + } + + // Default: full date return date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); } catch { return String(value); @@ -525,6 +541,15 @@ export function formatFieldValue(value: any): string { return entries.map(([k, v]) => `${k}: ${v}`).join(', '); } + // Handle year-like numbers (prevent comma formatting for founded_year) + if (typeof value === 'number') { + const currentYear = new Date().getFullYear(); + if (value >= 1800 && value <= currentYear + 10) { + return value.toString(); // Don't add commas for year values + } + return value.toLocaleString(); // Add commas for other numbers + } + // Handle URLs if (typeof value === 'string' && value.startsWith('http')) { try {