mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 12:31:26 -05:00
Migrate date precision to exact
Batch update all date precision handling to use expanded DatePrecision, replace hardcoded day defaults, and adjust related validation, UI, and helpers. Includes wrapper migration across Phase 1-3 functions, updates to logs, displays, and formatting utilities to align frontend with new precision values ('exact', 'month', 'year', 'decade', 'century', 'approximate').
This commit is contained in:
@@ -57,7 +57,7 @@ export function ManufacturerForm({ onSubmit, onCancel, initialData }: Manufactur
|
|||||||
website_url: initialData?.website_url || '',
|
website_url: initialData?.website_url || '',
|
||||||
founded_year: initialData?.founded_year ? String(initialData.founded_year) : '',
|
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: 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 || '',
|
headquarters_location: initialData?.headquarters_location || '',
|
||||||
source_url: initialData?.source_url || '',
|
source_url: initialData?.source_url || '',
|
||||||
submission_notes: initialData?.submission_notes || '',
|
submission_notes: initialData?.submission_notes || '',
|
||||||
|
|||||||
@@ -38,9 +38,9 @@ const parkSchema = z.object({
|
|||||||
park_type: z.string().min(1, 'Park type is required'),
|
park_type: z.string().min(1, 'Park type is required'),
|
||||||
status: z.string().min(1, 'Status is required'),
|
status: z.string().min(1, 'Status is required'),
|
||||||
opening_date: z.string().optional().transform(val => val || undefined),
|
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: 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({
|
location: z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
street_address: z.string().optional(),
|
street_address: z.string().optional(),
|
||||||
|
|||||||
@@ -227,9 +227,9 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }:
|
|||||||
ride_sub_type: initialData?.ride_sub_type || '',
|
ride_sub_type: initialData?.ride_sub_type || '',
|
||||||
status: initialData?.status || 'operating' as const, // Store DB value directly
|
status: initialData?.status || 'operating' as const, // Store DB value directly
|
||||||
opening_date: initialData?.opening_date || undefined,
|
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: 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
|
// Convert metric values to user's preferred unit for display
|
||||||
height_requirement: initialData?.height_requirement
|
height_requirement: initialData?.height_requirement
|
||||||
? convertValueFromMetric(initialData.height_requirement, getDisplayUnit('cm', measurementSystem), 'cm')
|
? convertValueFromMetric(initialData.height_requirement, getDisplayUnit('cm', measurementSystem), 'cm')
|
||||||
|
|||||||
@@ -102,11 +102,11 @@ export function TimeZoneIndependentDateRangePicker({
|
|||||||
if (!fromDate && !toDate) return null;
|
if (!fromDate && !toDate) return null;
|
||||||
|
|
||||||
if (fromDate && toDate) {
|
if (fromDate && toDate) {
|
||||||
return `${formatDateDisplay(fromDate, 'day')} - ${formatDateDisplay(toDate, 'day')}`;
|
return `${formatDateDisplay(fromDate, 'exact')} - ${formatDateDisplay(toDate, 'exact')}`;
|
||||||
} else if (fromDate) {
|
} else if (fromDate) {
|
||||||
return `From ${formatDateDisplay(fromDate, 'day')}`;
|
return `From ${formatDateDisplay(fromDate, 'exact')}`;
|
||||||
} else if (toDate) {
|
} else if (toDate) {
|
||||||
return `Until ${formatDateDisplay(toDate, 'day')}`;
|
return `Until ${formatDateDisplay(toDate, 'exact')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ import { ArrowRight } from 'lucide-react';
|
|||||||
import { ArrayFieldDiff } from './ArrayFieldDiff';
|
import { ArrayFieldDiff } from './ArrayFieldDiff';
|
||||||
import { SpecialFieldDisplay } from './SpecialFieldDisplay';
|
import { SpecialFieldDisplay } from './SpecialFieldDisplay';
|
||||||
|
|
||||||
|
import type { DatePrecision } from '@/components/ui/flexible-date-input';
|
||||||
|
|
||||||
// Helper to format compact values (truncate long strings)
|
// 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);
|
const formatted = formatFieldValue(value, precision);
|
||||||
if (formatted.length > maxLength) {
|
if (formatted.length > maxLength) {
|
||||||
return formatted.substring(0, maxLength) + '...';
|
return formatted.substring(0, maxLength) + '...';
|
||||||
|
|||||||
@@ -211,7 +211,13 @@ function DateFieldDisplay({ change, compact }: { change: FieldChange; compact: b
|
|||||||
{formatFieldName(change.field)}
|
{formatFieldName(change.field)}
|
||||||
{precision && (
|
{precision && (
|
||||||
<Badge variant="outline" className="text-xs ml-2">
|
<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>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ const timelineEventSchema = z.object({
|
|||||||
event_date: z.date({
|
event_date: z.date({
|
||||||
message: 'Event date is required',
|
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'),
|
title: z.string().min(1, 'Title is required').max(200, 'Title is too long'),
|
||||||
description: z.string().max(1000, 'Description is too long').optional(),
|
description: z.string().max(1000, 'Description is too long').optional(),
|
||||||
|
|
||||||
@@ -133,7 +133,7 @@ export function TimelineEventEditorDialog({
|
|||||||
} : {
|
} : {
|
||||||
event_type: 'milestone',
|
event_type: 'milestone',
|
||||||
event_date: new Date(),
|
event_date: new Date(),
|
||||||
event_date_precision: 'day',
|
event_date_precision: 'exact',
|
||||||
title: '',
|
title: '',
|
||||||
description: '',
|
description: '',
|
||||||
},
|
},
|
||||||
@@ -319,9 +319,12 @@ export function TimelineEventEditorDialog({
|
|||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="day">Exact Day</SelectItem>
|
<SelectItem value="exact">Exact Day</SelectItem>
|
||||||
<SelectItem value="month">Month Only</SelectItem>
|
<SelectItem value="month">Month Only</SelectItem>
|
||||||
<SelectItem value="year">Year Only</SelectItem>
|
<SelectItem value="year">Year Only</SelectItem>
|
||||||
|
<SelectItem value="decade">Decade</SelectItem>
|
||||||
|
<SelectItem value="century">Century</SelectItem>
|
||||||
|
<SelectItem value="approximate">Approximate</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ interface FlexibleDateDisplayProps {
|
|||||||
|
|
||||||
export function FlexibleDateDisplay({
|
export function FlexibleDateDisplay({
|
||||||
date,
|
date,
|
||||||
precision = 'day',
|
precision = 'exact',
|
||||||
fallback = 'Unknown',
|
fallback = 'Unknown',
|
||||||
className
|
className
|
||||||
}: FlexibleDateDisplayProps) {
|
}: FlexibleDateDisplayProps) {
|
||||||
@@ -36,7 +36,16 @@ export function FlexibleDateDisplay({
|
|||||||
case 'month':
|
case 'month':
|
||||||
formatted = format(dateObj, 'MMMM yyyy');
|
formatted = format(dateObj, 'MMMM yyyy');
|
||||||
break;
|
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:
|
default:
|
||||||
formatted = format(dateObj, 'PPP');
|
formatted = format(dateObj, 'PPP');
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { toDateOnly, toDateWithPrecision } from "@/lib/dateUtils";
|
import { toDateOnly, toDateWithPrecision } from "@/lib/dateUtils";
|
||||||
|
|
||||||
export type DatePrecision = 'day' | 'month' | 'year';
|
export type DatePrecision = 'exact' | 'month' | 'year' | 'decade' | 'century' | 'approximate';
|
||||||
|
|
||||||
interface FlexibleDateInputProps {
|
interface FlexibleDateInputProps {
|
||||||
value?: Date;
|
value?: Date;
|
||||||
@@ -34,7 +34,7 @@ interface FlexibleDateInputProps {
|
|||||||
|
|
||||||
export function FlexibleDateInput({
|
export function FlexibleDateInput({
|
||||||
value,
|
value,
|
||||||
precision = 'day',
|
precision = 'exact',
|
||||||
onChange,
|
onChange,
|
||||||
placeholder = "Select date",
|
placeholder = "Select date",
|
||||||
disabled = false,
|
disabled = false,
|
||||||
@@ -71,13 +71,16 @@ export function FlexibleDateInput({
|
|||||||
let newDate: Date;
|
let newDate: Date;
|
||||||
switch (newPrecision) {
|
switch (newPrecision) {
|
||||||
case 'year':
|
case 'year':
|
||||||
|
case 'decade':
|
||||||
|
case 'century':
|
||||||
|
case 'approximate':
|
||||||
newDate = new Date(year, 0, 1); // January 1st (local timezone)
|
newDate = new Date(year, 0, 1); // January 1st (local timezone)
|
||||||
setYearValue(year.toString());
|
setYearValue(year.toString());
|
||||||
break;
|
break;
|
||||||
case 'month':
|
case 'month':
|
||||||
newDate = new Date(year, month, 1); // 1st of month (local timezone)
|
newDate = new Date(year, month, 1); // 1st of month (local timezone)
|
||||||
break;
|
break;
|
||||||
case 'day':
|
case 'exact':
|
||||||
default:
|
default:
|
||||||
newDate = value; // Keep existing date
|
newDate = value; // Keep existing date
|
||||||
break;
|
break;
|
||||||
@@ -104,10 +107,13 @@ export function FlexibleDateInput({
|
|||||||
const getPlaceholderText = () => {
|
const getPlaceholderText = () => {
|
||||||
switch (localPrecision) {
|
switch (localPrecision) {
|
||||||
case 'year':
|
case 'year':
|
||||||
|
case 'decade':
|
||||||
|
case 'century':
|
||||||
|
case 'approximate':
|
||||||
return 'Enter year (e.g., 2005)';
|
return 'Enter year (e.g., 2005)';
|
||||||
case 'month':
|
case 'month':
|
||||||
return 'Select month and year';
|
return 'Select month and year';
|
||||||
case 'day':
|
case 'exact':
|
||||||
default:
|
default:
|
||||||
return placeholder;
|
return placeholder;
|
||||||
}
|
}
|
||||||
@@ -119,10 +125,10 @@ export function FlexibleDateInput({
|
|||||||
|
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
{localPrecision === 'day' && (
|
{(localPrecision === 'exact') && (
|
||||||
<DatePicker
|
<DatePicker
|
||||||
date={value}
|
date={value}
|
||||||
onSelect={(date) => onChange(date, 'day')}
|
onSelect={(date) => onChange(date, 'exact')}
|
||||||
placeholder={getPlaceholderText()}
|
placeholder={getPlaceholderText()}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
disableFuture={disableFuture}
|
disableFuture={disableFuture}
|
||||||
@@ -143,7 +149,7 @@ export function FlexibleDateInput({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{localPrecision === 'year' && (
|
{(localPrecision === 'year' || localPrecision === 'decade' || localPrecision === 'century' || localPrecision === 'approximate') && (
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
value={yearValue}
|
value={yearValue}
|
||||||
@@ -166,9 +172,12 @@ export function FlexibleDateInput({
|
|||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="day">Use Full Date</SelectItem>
|
<SelectItem value="exact">Exact Day</SelectItem>
|
||||||
<SelectItem value="month">Use Month/Year</SelectItem>
|
<SelectItem value="month">Month & Year</SelectItem>
|
||||||
<SelectItem value="year">Use Year Only</SelectItem>
|
<SelectItem value="year">Year Only</SelectItem>
|
||||||
|
<SelectItem value="decade">Decade</SelectItem>
|
||||||
|
<SelectItem value="century">Century</SelectItem>
|
||||||
|
<SelectItem value="approximate">Approximate</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export function getCurrentDateLocal(): string {
|
|||||||
*/
|
*/
|
||||||
export function formatDateDisplay(
|
export function formatDateDisplay(
|
||||||
dateString: string | null | undefined,
|
dateString: string | null | undefined,
|
||||||
precision: 'day' | 'month' | 'year' = 'day'
|
precision: 'exact' | 'month' | 'year' | 'decade' | 'century' | 'approximate' = 'exact'
|
||||||
): string {
|
): string {
|
||||||
if (!dateString) return '';
|
if (!dateString) return '';
|
||||||
|
|
||||||
@@ -83,7 +83,13 @@ export function formatDateDisplay(
|
|||||||
return date.getFullYear().toString();
|
return date.getFullYear().toString();
|
||||||
case 'month':
|
case 'month':
|
||||||
return date.toLocaleDateString('en-US', { year: 'numeric', month: 'long' });
|
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:
|
default:
|
||||||
return date.toLocaleDateString('en-US', {
|
return date.toLocaleDateString('en-US', {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
@@ -182,7 +188,7 @@ export function parseDateForDisplay(date: string | Date): Date {
|
|||||||
*/
|
*/
|
||||||
export function toDateWithPrecision(
|
export function toDateWithPrecision(
|
||||||
date: Date,
|
date: Date,
|
||||||
precision: 'day' | 'month' | 'year'
|
precision: 'exact' | 'month' | 'year' | 'decade' | 'century' | 'approximate'
|
||||||
): string {
|
): string {
|
||||||
const year = date.getFullYear();
|
const year = date.getFullYear();
|
||||||
const month = date.getMonth() + 1;
|
const month = date.getMonth() + 1;
|
||||||
@@ -193,7 +199,13 @@ export function toDateWithPrecision(
|
|||||||
return `${year}-01-01`;
|
return `${year}-01-01`;
|
||||||
case 'month':
|
case 'month':
|
||||||
return `${year}-${String(month).padStart(2, '0')}-01`;
|
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:
|
default:
|
||||||
return `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
return `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,9 +51,9 @@ export const parkValidationSchema = z.object({
|
|||||||
const date = new Date(val);
|
const date = new Date(val);
|
||||||
return date <= new Date();
|
return date <= new Date();
|
||||||
}, 'Opening date cannot be in the future'),
|
}, '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: 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_id: z.string().uuid().optional().nullable(),
|
||||||
location: z.object({
|
location: z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
@@ -139,9 +139,9 @@ export const rideValidationSchema = z.object({
|
|||||||
.optional()
|
.optional()
|
||||||
.nullable(),
|
.nullable(),
|
||||||
opening_date: z.string().nullish().transform(val => val ?? undefined),
|
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: 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(
|
height_requirement: z.preprocess(
|
||||||
(val) => val === '' || val === null || val === undefined ? undefined : Number(val),
|
(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()
|
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),
|
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']),
|
person_type: z.enum(['company', 'individual', 'firm', 'organization']),
|
||||||
founded_date: z.string().nullish().transform(val => val ?? undefined),
|
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(
|
founded_year: z.preprocess(
|
||||||
(val) => val === '' || val === null || val === undefined ? undefined : Number(val),
|
(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()
|
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);
|
fiveYearsFromNow.setFullYear(fiveYearsFromNow.getFullYear() + 5);
|
||||||
return date <= fiveYearsFromNow;
|
return date <= fiveYearsFromNow;
|
||||||
}, 'Event date cannot be more than 5 years in the future'),
|
}, '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_type: z.string().min(1, 'Entity type is required'),
|
||||||
entity_id: z.string().uuid('Invalid entity ID'),
|
entity_id: z.string().uuid('Invalid entity ID'),
|
||||||
is_public: z.boolean().optional(),
|
is_public: z.boolean().optional(),
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ export interface FieldChange {
|
|||||||
changeType: 'added' | 'removed' | 'modified';
|
changeType: 'added' | 'removed' | 'modified';
|
||||||
metadata?: {
|
metadata?: {
|
||||||
isCreatingNewLocation?: boolean;
|
isCreatingNewLocation?: boolean;
|
||||||
precision?: 'day' | 'month' | 'year';
|
precision?: 'exact' | 'month' | 'year' | 'decade' | 'century' | 'approximate';
|
||||||
oldPrecision?: 'day' | 'month' | 'year';
|
oldPrecision?: 'exact' | 'month' | 'year' | 'decade' | 'century' | 'approximate';
|
||||||
newPrecision?: 'day' | 'month' | 'year';
|
newPrecision?: 'exact' | 'month' | 'year' | 'decade' | 'century' | 'approximate';
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -803,7 +803,7 @@ function formatEntityType(entityType: string): string {
|
|||||||
/**
|
/**
|
||||||
* Format field value for display
|
* 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 (value === null || value === undefined) return 'None';
|
||||||
if (typeof value === 'boolean') return value ? 'Yes' : 'No';
|
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();
|
return date.getFullYear().toString();
|
||||||
} else if (precision === 'month') {
|
} else if (precision === 'month') {
|
||||||
return date.toLocaleDateString('en-US', { year: 'numeric', month: 'long' });
|
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' });
|
return date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
|
||||||
} catch {
|
} catch {
|
||||||
return String(value);
|
return String(value);
|
||||||
|
|||||||
@@ -659,9 +659,9 @@ export default function ParkDetail() {
|
|||||||
park_type: park?.park_type,
|
park_type: park?.park_type,
|
||||||
status: park?.status,
|
status: park?.status,
|
||||||
opening_date: park?.opening_date ?? undefined,
|
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: 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_id: park?.location?.id,
|
||||||
location: park?.location ? {
|
location: park?.location ? {
|
||||||
name: park.location.name || '',
|
name: park.location.name || '',
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export type TimelineEventType =
|
|||||||
|
|
||||||
export type EntityType = 'park' | 'ride' | 'company' | 'ride_model';
|
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
|
* 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