mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-21 17:51:14 -05:00
Fix founded year/date handling
This commit is contained in:
@@ -6,8 +6,8 @@ import { ArrayFieldDiff } from './ArrayFieldDiff';
|
|||||||
import { SpecialFieldDisplay } from './SpecialFieldDisplay';
|
import { SpecialFieldDisplay } from './SpecialFieldDisplay';
|
||||||
|
|
||||||
// Helper to format compact values (truncate long strings)
|
// Helper to format compact values (truncate long strings)
|
||||||
function formatCompactValue(value: any, maxLength = 30): string {
|
function formatCompactValue(value: any, precision?: 'day' | 'month' | 'year', maxLength = 30): string {
|
||||||
const formatted = formatFieldValue(value);
|
const formatted = formatFieldValue(value, precision);
|
||||||
if (formatted.length > maxLength) {
|
if (formatted.length > maxLength) {
|
||||||
return formatted.substring(0, maxLength) + '...';
|
return formatted.substring(0, maxLength) + '...';
|
||||||
}
|
}
|
||||||
@@ -20,7 +20,12 @@ interface FieldDiffProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function FieldDiff({ change, compact = false }: 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
|
// Check if this is an array field that needs special handling
|
||||||
if (Array.isArray(oldValue) && Array.isArray(newValue)) {
|
if (Array.isArray(oldValue) && Array.isArray(newValue)) {
|
||||||
@@ -55,7 +60,7 @@ export function FieldDiff({ change, compact = false }: FieldDiffProps) {
|
|||||||
if (changeType === 'added') {
|
if (changeType === 'added') {
|
||||||
return (
|
return (
|
||||||
<Badge variant="outline" className={getChangeColor()}>
|
<Badge variant="outline" className={getChangeColor()}>
|
||||||
{fieldName}: + {formatCompactValue(newValue)}
|
{fieldName}: + {formatCompactValue(newValue, precision)}
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -63,7 +68,7 @@ export function FieldDiff({ change, compact = false }: FieldDiffProps) {
|
|||||||
if (changeType === 'removed') {
|
if (changeType === 'removed') {
|
||||||
return (
|
return (
|
||||||
<Badge variant="outline" className={getChangeColor()}>
|
<Badge variant="outline" className={getChangeColor()}>
|
||||||
{fieldName}: <span className="line-through">{formatCompactValue(oldValue)}</span>
|
{fieldName}: <span className="line-through">{formatCompactValue(oldValue, precision)}</span>
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -71,7 +76,7 @@ export function FieldDiff({ change, compact = false }: FieldDiffProps) {
|
|||||||
if (changeType === 'modified') {
|
if (changeType === 'modified') {
|
||||||
return (
|
return (
|
||||||
<Badge variant="outline" className={getChangeColor()}>
|
<Badge variant="outline" className={getChangeColor()}>
|
||||||
{fieldName}: {formatCompactValue(oldValue)} → {formatCompactValue(newValue)}
|
{fieldName}: {formatCompactValue(oldValue, oldPrecision || precision)} → {formatCompactValue(newValue, newPrecision || precision)}
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -89,24 +94,24 @@ export function FieldDiff({ change, compact = false }: FieldDiffProps) {
|
|||||||
|
|
||||||
{changeType === 'added' && (
|
{changeType === 'added' && (
|
||||||
<div className="text-sm text-green-600 dark:text-green-400">
|
<div className="text-sm text-green-600 dark:text-green-400">
|
||||||
+ {formatFieldValue(newValue)}
|
+ {formatFieldValue(newValue, precision)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{changeType === 'removed' && (
|
{changeType === 'removed' && (
|
||||||
<div className="text-sm text-red-600 dark:text-red-400 line-through">
|
<div className="text-sm text-red-600 dark:text-red-400 line-through">
|
||||||
{formatFieldValue(oldValue)}
|
{formatFieldValue(oldValue, precision)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{changeType === 'modified' && (
|
{changeType === 'modified' && (
|
||||||
<div className="flex items-center gap-2 text-sm">
|
<div className="flex items-center gap-2 text-sm">
|
||||||
<span className="text-red-600 dark:text-red-400 line-through">
|
<span className="text-red-600 dark:text-red-400 line-through">
|
||||||
{formatFieldValue(oldValue)}
|
{formatFieldValue(oldValue, oldPrecision || precision)}
|
||||||
</span>
|
</span>
|
||||||
<ArrowRight className="h-3 w-3 text-muted-foreground" />
|
<ArrowRight className="h-3 w-3 text-muted-foreground" />
|
||||||
<span className="text-green-600 dark:text-green-400">
|
<span className="text-green-600 dark:text-green-400">
|
||||||
{formatFieldValue(newValue)}
|
{formatFieldValue(newValue, newPrecision || precision)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -186,6 +186,11 @@ function StatusFieldDisplay({ change, compact }: { change: FieldChange; compact:
|
|||||||
}
|
}
|
||||||
|
|
||||||
function DateFieldDisplay({ change, compact }: { change: FieldChange; compact: boolean }) {
|
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) {
|
if (compact) {
|
||||||
return (
|
return (
|
||||||
<Badge variant="outline" className="text-teal-600 dark:text-teal-400">
|
<Badge variant="outline" className="text-teal-600 dark:text-teal-400">
|
||||||
@@ -204,29 +209,34 @@ function DateFieldDisplay({ change, compact }: { change: FieldChange; compact: b
|
|||||||
<div className="text-sm font-medium flex items-center gap-2">
|
<div className="text-sm font-medium flex items-center gap-2">
|
||||||
<Calendar className="h-4 w-4" />
|
<Calendar className="h-4 w-4" />
|
||||||
{formatFieldName(change.field)}
|
{formatFieldName(change.field)}
|
||||||
|
{precision && (
|
||||||
|
<Badge variant="outline" className="text-xs ml-2">
|
||||||
|
{precision === 'year' ? 'Year Only' : precision === 'month' ? 'Month & Year' : 'Full Date'}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{change.changeType === 'modified' && (
|
{change.changeType === 'modified' && (
|
||||||
<div className="flex items-center gap-3 text-sm">
|
<div className="flex items-center gap-3 text-sm">
|
||||||
<span className="text-red-600 dark:text-red-400 line-through">
|
<span className="text-red-600 dark:text-red-400 line-through">
|
||||||
{formatFieldValue(change.oldValue)}
|
{formatFieldValue(change.oldValue, oldPrecision || precision)}
|
||||||
</span>
|
</span>
|
||||||
<ArrowRight className="h-3 w-3 text-muted-foreground" />
|
<ArrowRight className="h-3 w-3 text-muted-foreground" />
|
||||||
<span className="text-green-600 dark:text-green-400">
|
<span className="text-green-600 dark:text-green-400">
|
||||||
{formatFieldValue(change.newValue)}
|
{formatFieldValue(change.newValue, newPrecision || precision)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{change.changeType === 'added' && (
|
{change.changeType === 'added' && (
|
||||||
<div className="text-sm text-green-600 dark:text-green-400">
|
<div className="text-sm text-green-600 dark:text-green-400">
|
||||||
+ {formatFieldValue(change.newValue)}
|
+ {formatFieldValue(change.newValue, precision)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{change.changeType === 'removed' && (
|
{change.changeType === 'removed' && (
|
||||||
<div className="text-sm text-red-600 dark:text-red-400 line-through">
|
<div className="text-sm text-red-600 dark:text-red-400 line-through">
|
||||||
{formatFieldValue(change.oldValue)}
|
{formatFieldValue(change.oldValue, precision)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -101,18 +101,10 @@ export const companyValidationSchema = z.object({
|
|||||||
company_type: z.enum(['manufacturer', 'designer', 'operator', 'property_owner']),
|
company_type: z.enum(['manufacturer', 'designer', 'operator', 'property_owner']),
|
||||||
description: z.string().max(2000, 'Description must be less than 2000 characters').optional(),
|
description: z.string().max(2000, 'Description must be less than 2000 characters').optional(),
|
||||||
person_type: z.enum(['company', 'individual', 'firm', 'organization']),
|
person_type: z.enum(['company', 'individual', 'firm', 'organization']),
|
||||||
founded_year: z.string()
|
// Support both founded_date (preferred) and founded_year (legacy)
|
||||||
.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}`
|
|
||||||
}),
|
|
||||||
founded_date: z.string().optional(),
|
founded_date: z.string().optional(),
|
||||||
founded_date_precision: z.enum(['day', 'month', 'year']).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(),
|
headquarters_location: z.string().max(200, 'Location must be less than 200 characters').optional(),
|
||||||
website_url: z.string().optional().refine((val) => {
|
website_url: z.string().optional().refine((val) => {
|
||||||
if (!val || val === '') return true;
|
if (!val || val === '') return true;
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ export interface FieldChange {
|
|||||||
changeType: 'added' | 'removed' | 'modified';
|
changeType: 'added' | 'removed' | 'modified';
|
||||||
metadata?: {
|
metadata?: {
|
||||||
isCreatingNewLocation?: boolean;
|
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
|
// Check for changes
|
||||||
if (!isEqual(oldValue, newValue)) {
|
if (!isEqual(oldValue, newValue)) {
|
||||||
if (oldEmpty && !newEmpty) {
|
const fieldChange: FieldChange = {
|
||||||
fieldChanges.push({
|
field: key,
|
||||||
field: key,
|
oldValue,
|
||||||
oldValue,
|
newValue,
|
||||||
newValue,
|
changeType: oldEmpty && !newEmpty ? 'added' :
|
||||||
changeType: 'added',
|
newEmpty && !oldEmpty ? 'removed' :
|
||||||
});
|
'modified',
|
||||||
} else if (newEmpty && !oldEmpty) {
|
};
|
||||||
fieldChanges.push({
|
|
||||||
field: key,
|
// Add precision metadata for date fields
|
||||||
oldValue,
|
if (key.endsWith('_date') && !key.endsWith('_precision')) {
|
||||||
newValue,
|
const precisionKey = `${key}_precision`;
|
||||||
changeType: 'removed',
|
const newPrecision = itemData[precisionKey];
|
||||||
});
|
const oldPrecision = originalData[precisionKey];
|
||||||
} else {
|
|
||||||
fieldChanges.push({
|
if (newPrecision || oldPrecision) {
|
||||||
field: key,
|
fieldChange.metadata = {
|
||||||
oldValue,
|
...fieldChange.metadata,
|
||||||
newValue,
|
precision: newPrecision || oldPrecision,
|
||||||
changeType: 'modified',
|
oldPrecision,
|
||||||
});
|
newPrecision,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fieldChanges.push(fieldChange);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -490,14 +497,23 @@ function formatEntityType(entityType: string): string {
|
|||||||
/**
|
/**
|
||||||
* Format field value for display
|
* 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 (value === null || value === undefined) return 'None';
|
||||||
if (typeof value === 'boolean') return value ? 'Yes' : 'No';
|
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))) {
|
if (value instanceof Date || (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}/.test(value))) {
|
||||||
try {
|
try {
|
||||||
const date = new Date(value);
|
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' });
|
return date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
|
||||||
} catch {
|
} catch {
|
||||||
return String(value);
|
return String(value);
|
||||||
@@ -525,6 +541,15 @@ export function formatFieldValue(value: any): string {
|
|||||||
return entries.map(([k, v]) => `${k}: ${v}`).join(', ');
|
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
|
// Handle URLs
|
||||||
if (typeof value === 'string' && value.startsWith('http')) {
|
if (typeof value === 'string' && value.startsWith('http')) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user