mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-28 14:06:58 -05:00
Compare commits
2 Commits
9ee84b31ff
...
dce8747651
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dce8747651 | ||
|
|
d0c613031e |
@@ -57,7 +57,7 @@ export function ManufacturerForm({ onSubmit, onCancel, initialData }: Manufactur
|
||||
website_url: initialData?.website_url || '',
|
||||
founded_year: initialData?.founded_year ? String(initialData.founded_year) : '',
|
||||
founded_date: initialData?.founded_date || (initialData?.founded_year ? `${initialData.founded_year}-01-01` : undefined),
|
||||
founded_date_precision: initialData?.founded_date_precision || (initialData?.founded_year ? ('year' as const) : ('day' as const)),
|
||||
founded_date_precision: initialData?.founded_date_precision || (initialData?.founded_year ? ('year' as const) : ('exact' as const)),
|
||||
headquarters_location: initialData?.headquarters_location || '',
|
||||
source_url: initialData?.source_url || '',
|
||||
submission_notes: initialData?.submission_notes || '',
|
||||
|
||||
@@ -38,9 +38,9 @@ const parkSchema = z.object({
|
||||
park_type: z.string().min(1, 'Park type is required'),
|
||||
status: z.string().min(1, 'Status is required'),
|
||||
opening_date: z.string().optional().transform(val => val || undefined),
|
||||
opening_date_precision: z.enum(['day', 'month', 'year']).optional(),
|
||||
opening_date_precision: z.enum(['exact', 'month', 'year', 'decade', 'century', 'approximate']).optional(),
|
||||
closing_date: z.string().optional().transform(val => val || undefined),
|
||||
closing_date_precision: z.enum(['day', 'month', 'year']).optional(),
|
||||
closing_date_precision: z.enum(['exact', 'month', 'year', 'decade', 'century', 'approximate']).optional(),
|
||||
location: z.object({
|
||||
name: z.string(),
|
||||
street_address: z.string().optional(),
|
||||
@@ -405,7 +405,7 @@ export function ParkForm({ onSubmit, onCancel, initialData, isEditing = false }:
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<FlexibleDateInput
|
||||
value={watch('opening_date') ? parseDateOnly(watch('opening_date')!) : undefined}
|
||||
precision={(watch('opening_date_precision') as DatePrecision) || 'day'}
|
||||
precision={(watch('opening_date_precision') as DatePrecision) || 'exact'}
|
||||
onChange={(date, precision) => {
|
||||
setValue('opening_date', date ? toDateWithPrecision(date, precision) : undefined);
|
||||
setValue('opening_date_precision', precision);
|
||||
@@ -418,7 +418,7 @@ export function ParkForm({ onSubmit, onCancel, initialData, isEditing = false }:
|
||||
|
||||
<FlexibleDateInput
|
||||
value={watch('closing_date') ? parseDateOnly(watch('closing_date')!) : undefined}
|
||||
precision={(watch('closing_date_precision') as DatePrecision) || 'day'}
|
||||
precision={(watch('closing_date_precision') as DatePrecision) || 'exact'}
|
||||
onChange={(date, precision) => {
|
||||
setValue('closing_date', date ? toDateWithPrecision(date, precision) : undefined);
|
||||
setValue('closing_date_precision', precision);
|
||||
|
||||
@@ -227,9 +227,9 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }:
|
||||
ride_sub_type: initialData?.ride_sub_type || '',
|
||||
status: initialData?.status || 'operating' as const, // Store DB value directly
|
||||
opening_date: initialData?.opening_date || undefined,
|
||||
opening_date_precision: initialData?.opening_date_precision || 'day',
|
||||
opening_date_precision: initialData?.opening_date_precision || 'exact',
|
||||
closing_date: initialData?.closing_date || undefined,
|
||||
closing_date_precision: initialData?.closing_date_precision || 'day',
|
||||
closing_date_precision: initialData?.closing_date_precision || 'exact',
|
||||
// Convert metric values to user's preferred unit for display
|
||||
height_requirement: initialData?.height_requirement
|
||||
? convertValueFromMetric(initialData.height_requirement, getDisplayUnit('cm', measurementSystem), 'cm')
|
||||
@@ -711,7 +711,7 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }:
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<FlexibleDateInput
|
||||
value={watch('opening_date') ? parseDateOnly(watch('opening_date')!) : undefined}
|
||||
precision={(watch('opening_date_precision') as DatePrecision) || 'day'}
|
||||
precision={(watch('opening_date_precision') as DatePrecision) || 'exact'}
|
||||
onChange={(date, precision) => {
|
||||
setValue('opening_date', date ? toDateWithPrecision(date, precision) : undefined);
|
||||
setValue('opening_date_precision', precision);
|
||||
@@ -724,7 +724,7 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }:
|
||||
|
||||
<FlexibleDateInput
|
||||
value={watch('closing_date') ? parseDateOnly(watch('closing_date')!) : undefined}
|
||||
precision={(watch('closing_date_precision') as DatePrecision) || 'day'}
|
||||
precision={(watch('closing_date_precision') as DatePrecision) || 'exact'}
|
||||
onChange={(date, precision) => {
|
||||
setValue('closing_date', date ? toDateWithPrecision(date, precision) : undefined);
|
||||
setValue('closing_date_precision', precision);
|
||||
|
||||
@@ -102,11 +102,11 @@ export function TimeZoneIndependentDateRangePicker({
|
||||
if (!fromDate && !toDate) return null;
|
||||
|
||||
if (fromDate && toDate) {
|
||||
return `${formatDateDisplay(fromDate, 'day')} - ${formatDateDisplay(toDate, 'day')}`;
|
||||
return `${formatDateDisplay(fromDate, 'exact')} - ${formatDateDisplay(toDate, 'exact')}`;
|
||||
} else if (fromDate) {
|
||||
return `From ${formatDateDisplay(fromDate, 'day')}`;
|
||||
return `From ${formatDateDisplay(fromDate, 'exact')}`;
|
||||
} else if (toDate) {
|
||||
return `Until ${formatDateDisplay(toDate, 'day')}`;
|
||||
return `Until ${formatDateDisplay(toDate, 'exact')}`;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -5,8 +5,10 @@ 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?: 'day' | 'month' | 'year', maxLength = 30): string {
|
||||
function formatCompactValue(value: unknown, precision?: DatePrecision, maxLength = 30): string {
|
||||
const formatted = formatFieldValue(value, precision);
|
||||
if (formatted.length > maxLength) {
|
||||
return formatted.substring(0, maxLength) + '...';
|
||||
|
||||
@@ -211,7 +211,13 @@ function DateFieldDisplay({ change, compact }: { change: FieldChange; compact: b
|
||||
{formatFieldName(change.field)}
|
||||
{precision && (
|
||||
<Badge variant="outline" className="text-xs ml-2">
|
||||
{precision === 'year' ? 'Year Only' : precision === 'month' ? 'Month & Year' : 'Full Date'}
|
||||
{precision === 'exact' ? 'Exact Day' :
|
||||
precision === 'month' ? 'Month & Year' :
|
||||
precision === 'year' ? 'Year Only' :
|
||||
precision === 'decade' ? 'Decade' :
|
||||
precision === 'century' ? 'Century' :
|
||||
precision === 'approximate' ? 'Approximate' :
|
||||
'Full Date'}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -67,7 +67,7 @@ export function RichCompanyDisplay({ data, actionType, showAllFields = true }: R
|
||||
{data.founded_date ? (
|
||||
<FlexibleDateDisplay
|
||||
date={data.founded_date}
|
||||
precision={(data.founded_date_precision as DatePrecision) || 'day'}
|
||||
precision={(data.founded_date_precision as DatePrecision) || 'exact'}
|
||||
className="font-medium"
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -165,7 +165,7 @@ export function RichParkDisplay({ data, actionType, showAllFields = true }: Rich
|
||||
<span className="text-muted-foreground">Opened:</span>{' '}
|
||||
<FlexibleDateDisplay
|
||||
date={data.opening_date}
|
||||
precision={(data.opening_date_precision as DatePrecision) || 'day'}
|
||||
precision={(data.opening_date_precision as DatePrecision) || 'exact'}
|
||||
className="font-medium"
|
||||
/>
|
||||
</div>
|
||||
@@ -175,7 +175,7 @@ export function RichParkDisplay({ data, actionType, showAllFields = true }: Rich
|
||||
<span className="text-muted-foreground">Closed:</span>{' '}
|
||||
<FlexibleDateDisplay
|
||||
date={data.closing_date}
|
||||
precision={(data.closing_date_precision as DatePrecision) || 'day'}
|
||||
precision={(data.closing_date_precision as DatePrecision) || 'exact'}
|
||||
className="font-medium"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -606,7 +606,7 @@ export function RichRideDisplay({ data, actionType, showAllFields = true }: Rich
|
||||
<span className="text-muted-foreground">Opened:</span>{' '}
|
||||
<FlexibleDateDisplay
|
||||
date={data.opening_date}
|
||||
precision={(data.opening_date_precision as DatePrecision) || 'day'}
|
||||
precision={(data.opening_date_precision as DatePrecision) || 'exact'}
|
||||
className="font-medium"
|
||||
/>
|
||||
</div>
|
||||
@@ -616,7 +616,7 @@ export function RichRideDisplay({ data, actionType, showAllFields = true }: Rich
|
||||
<span className="text-muted-foreground">Closed:</span>{' '}
|
||||
<FlexibleDateDisplay
|
||||
date={data.closing_date}
|
||||
precision={(data.closing_date_precision as DatePrecision) || 'day'}
|
||||
precision={(data.closing_date_precision as DatePrecision) || 'exact'}
|
||||
className="font-medium"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -17,7 +17,7 @@ interface TimelineEventCardProps {
|
||||
|
||||
// ⚠️ IMPORTANT: Use parseDateForDisplay to prevent timezone shifts
|
||||
// YYYY-MM-DD strings must be interpreted as local dates, not UTC
|
||||
const formatEventDate = (date: string, precision: string = 'day') => {
|
||||
const formatEventDate = (date: string, precision: string = 'exact') => {
|
||||
const dateObj = parseDateForDisplay(date);
|
||||
|
||||
switch (precision) {
|
||||
|
||||
@@ -72,7 +72,7 @@ const timelineEventSchema = z.object({
|
||||
event_date: z.date({
|
||||
message: 'Event date is required',
|
||||
}),
|
||||
event_date_precision: z.enum(['day', 'month', 'year']).default('day'),
|
||||
event_date_precision: z.enum(['exact', 'month', 'year', 'decade', 'century', 'approximate']).default('exact'),
|
||||
title: z.string().min(1, 'Title is required').max(200, 'Title is too long'),
|
||||
description: z.string().max(1000, 'Description is too long').optional(),
|
||||
|
||||
@@ -133,7 +133,7 @@ export function TimelineEventEditorDialog({
|
||||
} : {
|
||||
event_type: 'milestone',
|
||||
event_date: new Date(),
|
||||
event_date_precision: 'day',
|
||||
event_date_precision: 'exact',
|
||||
title: '',
|
||||
description: '',
|
||||
},
|
||||
@@ -319,9 +319,12 @@ export function TimelineEventEditorDialog({
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="day">Exact Day</SelectItem>
|
||||
<SelectItem value="exact">Exact Day</SelectItem>
|
||||
<SelectItem value="month">Month Only</SelectItem>
|
||||
<SelectItem value="year">Year Only</SelectItem>
|
||||
<SelectItem value="decade">Decade</SelectItem>
|
||||
<SelectItem value="century">Century</SelectItem>
|
||||
<SelectItem value="approximate">Approximate</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
|
||||
@@ -11,7 +11,7 @@ interface FlexibleDateDisplayProps {
|
||||
|
||||
export function FlexibleDateDisplay({
|
||||
date,
|
||||
precision = 'day',
|
||||
precision = 'exact',
|
||||
fallback = 'Unknown',
|
||||
className
|
||||
}: FlexibleDateDisplayProps) {
|
||||
@@ -36,7 +36,16 @@ export function FlexibleDateDisplay({
|
||||
case 'month':
|
||||
formatted = format(dateObj, 'MMMM yyyy');
|
||||
break;
|
||||
case 'day':
|
||||
case 'decade':
|
||||
formatted = `${Math.floor(dateObj.getFullYear() / 10) * 10}s`;
|
||||
break;
|
||||
case 'century':
|
||||
formatted = `${Math.ceil(dateObj.getFullYear() / 100)}th century`;
|
||||
break;
|
||||
case 'approximate':
|
||||
formatted = `circa ${format(dateObj, 'yyyy')}`;
|
||||
break;
|
||||
case 'exact':
|
||||
default:
|
||||
formatted = format(dateObj, 'PPP');
|
||||
break;
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
} from "@/components/ui/select";
|
||||
import { toDateOnly, toDateWithPrecision } from "@/lib/dateUtils";
|
||||
|
||||
export type DatePrecision = 'day' | 'month' | 'year';
|
||||
export type DatePrecision = 'exact' | 'month' | 'year' | 'decade' | 'century' | 'approximate';
|
||||
|
||||
interface FlexibleDateInputProps {
|
||||
value?: Date;
|
||||
@@ -34,7 +34,7 @@ interface FlexibleDateInputProps {
|
||||
|
||||
export function FlexibleDateInput({
|
||||
value,
|
||||
precision = 'day',
|
||||
precision = 'exact',
|
||||
onChange,
|
||||
placeholder = "Select date",
|
||||
disabled = false,
|
||||
@@ -71,13 +71,16 @@ export function FlexibleDateInput({
|
||||
let newDate: Date;
|
||||
switch (newPrecision) {
|
||||
case 'year':
|
||||
case 'decade':
|
||||
case 'century':
|
||||
case 'approximate':
|
||||
newDate = new Date(year, 0, 1); // January 1st (local timezone)
|
||||
setYearValue(year.toString());
|
||||
break;
|
||||
case 'month':
|
||||
newDate = new Date(year, month, 1); // 1st of month (local timezone)
|
||||
break;
|
||||
case 'day':
|
||||
case 'exact':
|
||||
default:
|
||||
newDate = value; // Keep existing date
|
||||
break;
|
||||
@@ -104,10 +107,13 @@ export function FlexibleDateInput({
|
||||
const getPlaceholderText = () => {
|
||||
switch (localPrecision) {
|
||||
case 'year':
|
||||
case 'decade':
|
||||
case 'century':
|
||||
case 'approximate':
|
||||
return 'Enter year (e.g., 2005)';
|
||||
case 'month':
|
||||
return 'Select month and year';
|
||||
case 'day':
|
||||
case 'exact':
|
||||
default:
|
||||
return placeholder;
|
||||
}
|
||||
@@ -119,10 +125,10 @@ export function FlexibleDateInput({
|
||||
|
||||
<div className="flex gap-2">
|
||||
<div className="flex-1">
|
||||
{localPrecision === 'day' && (
|
||||
{(localPrecision === 'exact') && (
|
||||
<DatePicker
|
||||
date={value}
|
||||
onSelect={(date) => onChange(date, 'day')}
|
||||
onSelect={(date) => onChange(date, 'exact')}
|
||||
placeholder={getPlaceholderText()}
|
||||
disabled={disabled}
|
||||
disableFuture={disableFuture}
|
||||
@@ -143,7 +149,7 @@ export function FlexibleDateInput({
|
||||
/>
|
||||
)}
|
||||
|
||||
{localPrecision === 'year' && (
|
||||
{(localPrecision === 'year' || localPrecision === 'decade' || localPrecision === 'century' || localPrecision === 'approximate') && (
|
||||
<Input
|
||||
type="number"
|
||||
value={yearValue}
|
||||
@@ -166,9 +172,12 @@ export function FlexibleDateInput({
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="day">Use Full Date</SelectItem>
|
||||
<SelectItem value="month">Use Month/Year</SelectItem>
|
||||
<SelectItem value="year">Use Year Only</SelectItem>
|
||||
<SelectItem value="exact">Exact Day</SelectItem>
|
||||
<SelectItem value="month">Month & Year</SelectItem>
|
||||
<SelectItem value="year">Year Only</SelectItem>
|
||||
<SelectItem value="decade">Decade</SelectItem>
|
||||
<SelectItem value="century">Century</SelectItem>
|
||||
<SelectItem value="approximate">Approximate</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
@@ -72,7 +72,7 @@ export function getCurrentDateLocal(): string {
|
||||
*/
|
||||
export function formatDateDisplay(
|
||||
dateString: string | null | undefined,
|
||||
precision: 'day' | 'month' | 'year' = 'day'
|
||||
precision: 'exact' | 'month' | 'year' | 'decade' | 'century' | 'approximate' = 'exact'
|
||||
): string {
|
||||
if (!dateString) return '';
|
||||
|
||||
@@ -83,7 +83,13 @@ export function formatDateDisplay(
|
||||
return date.getFullYear().toString();
|
||||
case 'month':
|
||||
return date.toLocaleDateString('en-US', { year: 'numeric', month: 'long' });
|
||||
case 'day':
|
||||
case 'decade':
|
||||
return `${Math.floor(date.getFullYear() / 10) * 10}s`;
|
||||
case 'century':
|
||||
return `${Math.ceil(date.getFullYear() / 100)}th century`;
|
||||
case 'approximate':
|
||||
return `circa ${date.getFullYear()}`;
|
||||
case 'exact':
|
||||
default:
|
||||
return date.toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
@@ -182,7 +188,7 @@ export function parseDateForDisplay(date: string | Date): Date {
|
||||
*/
|
||||
export function toDateWithPrecision(
|
||||
date: Date,
|
||||
precision: 'day' | 'month' | 'year'
|
||||
precision: 'exact' | 'month' | 'year' | 'decade' | 'century' | 'approximate'
|
||||
): string {
|
||||
const year = date.getFullYear();
|
||||
const month = date.getMonth() + 1;
|
||||
@@ -193,7 +199,13 @@ export function toDateWithPrecision(
|
||||
return `${year}-01-01`;
|
||||
case 'month':
|
||||
return `${year}-${String(month).padStart(2, '0')}-01`;
|
||||
case 'day':
|
||||
case 'decade':
|
||||
return `${Math.floor(year / 10) * 10}-01-01`;
|
||||
case 'century':
|
||||
return `${Math.floor((year - 1) / 100) * 100 + 1}-01-01`;
|
||||
case 'approximate':
|
||||
return `${year}-01-01`;
|
||||
case 'exact':
|
||||
default:
|
||||
return `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
@@ -3708,7 +3708,7 @@ export async function submitTimelineEventUpdate(
|
||||
entity_id: originalEvent.entity_id,
|
||||
event_type: changedFields.event_type !== undefined ? changedFields.event_type : originalEvent.event_type,
|
||||
event_date: changedFields.event_date !== undefined ? (typeof changedFields.event_date === 'string' ? changedFields.event_date : changedFields.event_date.toISOString().split('T')[0]) : originalEvent.event_date,
|
||||
event_date_precision: (changedFields.event_date_precision !== undefined ? changedFields.event_date_precision : originalEvent.event_date_precision) || 'day',
|
||||
event_date_precision: (changedFields.event_date_precision !== undefined ? changedFields.event_date_precision : originalEvent.event_date_precision) || 'exact',
|
||||
title: changedFields.title !== undefined ? changedFields.title : originalEvent.title,
|
||||
description: changedFields.description !== undefined ? changedFields.description : originalEvent.description,
|
||||
from_value: changedFields.from_value !== undefined ? changedFields.from_value : originalEvent.from_value,
|
||||
|
||||
@@ -51,9 +51,9 @@ export const parkValidationSchema = z.object({
|
||||
const date = new Date(val);
|
||||
return date <= new Date();
|
||||
}, 'Opening date cannot be in the future'),
|
||||
opening_date_precision: z.enum(['day', 'month', 'year']).nullable().optional(),
|
||||
opening_date_precision: z.enum(['exact', 'month', 'year', 'decade', 'century', 'approximate']).nullable().optional(),
|
||||
closing_date: z.string().nullish().transform(val => val ?? undefined),
|
||||
closing_date_precision: z.enum(['day', 'month', 'year']).nullable().optional(),
|
||||
closing_date_precision: z.enum(['exact', 'month', 'year', 'decade', 'century', 'approximate']).nullable().optional(),
|
||||
location_id: z.string().uuid().optional().nullable(),
|
||||
location: z.object({
|
||||
name: z.string(),
|
||||
@@ -139,9 +139,9 @@ export const rideValidationSchema = z.object({
|
||||
.optional()
|
||||
.nullable(),
|
||||
opening_date: z.string().nullish().transform(val => val ?? undefined),
|
||||
opening_date_precision: z.enum(['day', 'month', 'year']).nullable().optional(),
|
||||
opening_date_precision: z.enum(['exact', 'month', 'year', 'decade', 'century', 'approximate']).nullable().optional(),
|
||||
closing_date: z.string().nullish().transform(val => val ?? undefined),
|
||||
closing_date_precision: z.enum(['day', 'month', 'year']).nullable().optional(),
|
||||
closing_date_precision: z.enum(['exact', 'month', 'year', 'decade', 'century', 'approximate']).nullable().optional(),
|
||||
height_requirement: z.preprocess(
|
||||
(val) => val === '' || val === null || val === undefined ? undefined : Number(val),
|
||||
z.number().int().min(0, 'Height requirement must be positive').max(300, 'Height requirement must be less than 300cm').optional()
|
||||
@@ -322,7 +322,7 @@ export const companyValidationSchema = z.object({
|
||||
description: z.string().trim().max(2000, 'Description must be less than 2000 characters').nullish().transform(val => val ?? undefined),
|
||||
person_type: z.enum(['company', 'individual', 'firm', 'organization']),
|
||||
founded_date: z.string().nullish().transform(val => val ?? undefined),
|
||||
founded_date_precision: z.enum(['day', 'month', 'year']).nullable().optional(),
|
||||
founded_date_precision: z.enum(['exact', 'month', 'year', 'decade', 'century', 'approximate']).nullable().optional(),
|
||||
founded_year: z.preprocess(
|
||||
(val) => val === '' || val === null || val === undefined ? undefined : Number(val),
|
||||
z.number().int().min(1800, 'Founded year must be after 1800').max(currentYear, `Founded year cannot be after ${currentYear}`).optional()
|
||||
@@ -401,7 +401,7 @@ export const milestoneValidationSchema = z.object({
|
||||
fiveYearsFromNow.setFullYear(fiveYearsFromNow.getFullYear() + 5);
|
||||
return date <= fiveYearsFromNow;
|
||||
}, 'Event date cannot be more than 5 years in the future'),
|
||||
event_date_precision: z.enum(['day', 'month', 'year']).optional().default('day'),
|
||||
event_date_precision: z.enum(['exact', 'month', 'year', 'decade', 'century', 'approximate']).optional().default('exact'),
|
||||
entity_type: z.string().min(1, 'Entity type is required'),
|
||||
entity_id: z.string().uuid('Invalid entity ID'),
|
||||
is_public: z.boolean().optional(),
|
||||
|
||||
@@ -21,9 +21,9 @@ export interface FieldChange {
|
||||
changeType: 'added' | 'removed' | 'modified';
|
||||
metadata?: {
|
||||
isCreatingNewLocation?: boolean;
|
||||
precision?: 'day' | 'month' | 'year';
|
||||
oldPrecision?: 'day' | 'month' | 'year';
|
||||
newPrecision?: 'day' | 'month' | 'year';
|
||||
precision?: 'exact' | 'month' | 'year' | 'decade' | 'century' | 'approximate';
|
||||
oldPrecision?: 'exact' | 'month' | 'year' | 'decade' | 'century' | 'approximate';
|
||||
newPrecision?: 'exact' | 'month' | 'year' | 'decade' | 'century' | 'approximate';
|
||||
};
|
||||
}
|
||||
|
||||
@@ -803,7 +803,7 @@ function formatEntityType(entityType: string): string {
|
||||
/**
|
||||
* Format field value for display
|
||||
*/
|
||||
export function formatFieldValue(value: any, precision?: 'day' | 'month' | 'year'): string {
|
||||
export function formatFieldValue(value: any, precision?: 'exact' | 'month' | 'year' | 'decade' | 'century' | 'approximate'): string {
|
||||
if (value === null || value === undefined) return 'None';
|
||||
if (typeof value === 'boolean') return value ? 'Yes' : 'No';
|
||||
|
||||
@@ -817,9 +817,15 @@ export function formatFieldValue(value: any, precision?: 'day' | 'month' | 'year
|
||||
return date.getFullYear().toString();
|
||||
} else if (precision === 'month') {
|
||||
return date.toLocaleDateString('en-US', { year: 'numeric', month: 'long' });
|
||||
} else if (precision === 'decade') {
|
||||
return `${Math.floor(date.getFullYear() / 10) * 10}s`;
|
||||
} else if (precision === 'century') {
|
||||
return `${Math.ceil(date.getFullYear() / 100)}th century`;
|
||||
} else if (precision === 'approximate') {
|
||||
return `circa ${date.getFullYear()}`;
|
||||
}
|
||||
|
||||
// Default: full date
|
||||
// Default: full date (exact)
|
||||
return date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
|
||||
} catch {
|
||||
return String(value);
|
||||
|
||||
@@ -257,14 +257,14 @@ export function generateRandomCompany(type: 'manufacturer' | 'operator' | 'desig
|
||||
// Add full founded date with precision
|
||||
if (shouldPopulateField(density, counter, 'medium')) {
|
||||
companyData.founded_date = `${foundedYear}-01-01`;
|
||||
companyData.founded_date_precision = randomItem(['year', 'month', 'day']);
|
||||
companyData.founded_date_precision = randomItem(['year', 'month', 'exact']);
|
||||
}
|
||||
|
||||
// Add defunct date for some companies
|
||||
if (shouldPopulateField(density, counter, 'low') && Math.random() > 0.85) {
|
||||
const defunctYear = randomInt(foundedYear + 10, 2024);
|
||||
companyData.defunct_date = `${defunctYear}-12-31`;
|
||||
companyData.defunct_date_precision = randomItem(['year', 'month', 'day']);
|
||||
companyData.defunct_date_precision = randomItem(['year', 'month', 'exact']);
|
||||
}
|
||||
|
||||
// Add source URL
|
||||
|
||||
@@ -61,8 +61,8 @@ export function randomDate(startYear: number, endYear: number): string {
|
||||
return `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
export function randomDatePrecision(): 'day' | 'month' | 'year' {
|
||||
return randomItem(['day', 'month', 'year']);
|
||||
export function randomDatePrecision(): 'exact' | 'month' | 'year' {
|
||||
return randomItem(['exact', 'month', 'year']);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -659,9 +659,9 @@ export default function ParkDetail() {
|
||||
park_type: park?.park_type,
|
||||
status: park?.status,
|
||||
opening_date: park?.opening_date ?? undefined,
|
||||
opening_date_precision: (park?.opening_date_precision as 'day' | 'month' | 'year') ?? undefined,
|
||||
opening_date_precision: (park?.opening_date_precision as 'exact' | 'month' | 'year' | 'decade' | 'century' | 'approximate') ?? undefined,
|
||||
closing_date: park?.closing_date ?? undefined,
|
||||
closing_date_precision: (park?.closing_date_precision as 'day' | 'month' | 'year') ?? undefined,
|
||||
closing_date_precision: (park?.closing_date_precision as 'exact' | 'month' | 'year' | 'decade' | 'century' | 'approximate') ?? undefined,
|
||||
location_id: park?.location?.id,
|
||||
location: park?.location ? {
|
||||
name: park.location.name || '',
|
||||
|
||||
@@ -32,7 +32,7 @@ export interface TimelineEventDatabaseRecord {
|
||||
entity_type: 'park' | 'ride' | 'company' | 'ride_model';
|
||||
event_type: string;
|
||||
event_date: string;
|
||||
event_date_precision: 'day' | 'month' | 'year';
|
||||
event_date_precision: 'exact' | 'month' | 'year' | 'decade' | 'century' | 'approximate';
|
||||
title: string;
|
||||
description?: string | null;
|
||||
from_value?: string | null;
|
||||
|
||||
@@ -151,7 +151,7 @@ export interface TimelineEventItemData {
|
||||
entity_type: 'park' | 'ride' | 'company' | 'ride_model';
|
||||
event_type: string;
|
||||
event_date: string; // ISO date
|
||||
event_date_precision: 'day' | 'month' | 'year';
|
||||
event_date_precision: 'exact' | 'month' | 'year' | 'decade' | 'century' | 'approximate';
|
||||
title: string;
|
||||
description?: string | null;
|
||||
from_value?: string | null;
|
||||
|
||||
@@ -22,7 +22,7 @@ export type TimelineEventType =
|
||||
|
||||
export type EntityType = 'park' | 'ride' | 'company' | 'ride_model';
|
||||
|
||||
export type DatePrecision = 'day' | 'month' | 'year';
|
||||
export type DatePrecision = 'exact' | 'month' | 'year' | 'decade' | 'century' | 'approximate';
|
||||
|
||||
/**
|
||||
* Timeline event stored in database after approval
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
-- Phase 3: Migrate 'day' precision to 'exact' across all tables
|
||||
-- This fixes the check constraint violations and aligns with the new precision system
|
||||
|
||||
-- Parks
|
||||
UPDATE parks
|
||||
SET opening_date_precision = 'exact'
|
||||
WHERE opening_date_precision = 'day';
|
||||
|
||||
UPDATE parks
|
||||
SET closing_date_precision = 'exact'
|
||||
WHERE closing_date_precision = 'day';
|
||||
|
||||
-- Park Submissions
|
||||
UPDATE park_submissions
|
||||
SET opening_date_precision = 'exact'
|
||||
WHERE opening_date_precision = 'day';
|
||||
|
||||
UPDATE park_submissions
|
||||
SET closing_date_precision = 'exact'
|
||||
WHERE closing_date_precision = 'day';
|
||||
|
||||
-- Park Versions
|
||||
UPDATE park_versions
|
||||
SET opening_date_precision = 'exact'
|
||||
WHERE opening_date_precision = 'day';
|
||||
|
||||
UPDATE park_versions
|
||||
SET closing_date_precision = 'exact'
|
||||
WHERE closing_date_precision = 'day';
|
||||
|
||||
-- Rides
|
||||
UPDATE rides
|
||||
SET opening_date_precision = 'exact'
|
||||
WHERE opening_date_precision = 'day';
|
||||
|
||||
UPDATE rides
|
||||
SET closing_date_precision = 'exact'
|
||||
WHERE closing_date_precision = 'day';
|
||||
|
||||
-- Ride Submissions
|
||||
UPDATE ride_submissions
|
||||
SET opening_date_precision = 'exact'
|
||||
WHERE opening_date_precision = 'day';
|
||||
|
||||
UPDATE ride_submissions
|
||||
SET closing_date_precision = 'exact'
|
||||
WHERE closing_date_precision = 'day';
|
||||
|
||||
-- Ride Versions
|
||||
UPDATE ride_versions
|
||||
SET opening_date_precision = 'exact'
|
||||
WHERE opening_date_precision = 'day';
|
||||
|
||||
UPDATE ride_versions
|
||||
SET closing_date_precision = 'exact'
|
||||
WHERE closing_date_precision = 'day';
|
||||
|
||||
-- Companies
|
||||
UPDATE companies
|
||||
SET founded_date_precision = 'exact'
|
||||
WHERE founded_date_precision = 'day';
|
||||
|
||||
-- Company Submissions
|
||||
UPDATE company_submissions
|
||||
SET founded_date_precision = 'exact'
|
||||
WHERE founded_date_precision = 'day';
|
||||
|
||||
-- Company Versions
|
||||
UPDATE company_versions
|
||||
SET founded_date_precision = 'exact'
|
||||
WHERE founded_date_precision = 'day';
|
||||
|
||||
-- Entity Timeline Events
|
||||
UPDATE entity_timeline_events
|
||||
SET event_date_precision = 'exact'
|
||||
WHERE event_date_precision = 'day';
|
||||
|
||||
-- Timeline Event Submissions
|
||||
UPDATE timeline_event_submissions
|
||||
SET event_date_precision = 'exact'
|
||||
WHERE event_date_precision = 'day';
|
||||
|
||||
-- Historical Parks
|
||||
UPDATE historical_parks
|
||||
SET opening_date_precision = 'exact'
|
||||
WHERE opening_date_precision = 'day';
|
||||
|
||||
UPDATE historical_parks
|
||||
SET closing_date_precision = 'exact'
|
||||
WHERE closing_date_precision = 'day';
|
||||
|
||||
-- Historical Rides
|
||||
UPDATE historical_rides
|
||||
SET opening_date_precision = 'exact'
|
||||
WHERE opening_date_precision = 'day';
|
||||
|
||||
UPDATE historical_rides
|
||||
SET closing_date_precision = 'exact'
|
||||
WHERE closing_date_precision = 'day';
|
||||
Reference in New Issue
Block a user