diff --git a/src/components/history/EntityHistoryTimeline.tsx b/src/components/history/EntityHistoryTimeline.tsx new file mode 100644 index 00000000..0b4db2fe --- /dev/null +++ b/src/components/history/EntityHistoryTimeline.tsx @@ -0,0 +1,120 @@ +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Calendar, MapPin, Building2, Tag, Milestone, ArrowRight } from "lucide-react"; +import { format } from "date-fns"; + +export type HistoryEventType = 'name_change' | 'status_change' | 'ownership_change' | 'relocation' | 'milestone'; + +export interface HistoryEvent { + date: string; + title: string; + description?: string; + type: HistoryEventType; + from?: string; + to?: string; +} + +interface EntityHistoryTimelineProps { + events: HistoryEvent[]; + entityName: string; +} + +const eventTypeConfig: Record = { + name_change: { icon: Tag, color: 'text-blue-500', label: 'Name Change' }, + status_change: { icon: Calendar, color: 'text-amber-500', label: 'Status Change' }, + ownership_change: { icon: Building2, color: 'text-purple-500', label: 'Ownership Change' }, + relocation: { icon: MapPin, color: 'text-green-500', label: 'Relocation' }, + milestone: { icon: Milestone, color: 'text-pink-500', label: 'Milestone' }, +}; + +export function EntityHistoryTimeline({ events, entityName }: EntityHistoryTimelineProps) { + if (events.length === 0) { + return ( + + + No Historical Events + + No historical events recorded for {entityName} + + + + ); + } + + // Sort events by date (most recent first) + const sortedEvents = [...events].sort((a, b) => { + const dateA = new Date(a.date); + const dateB = new Date(b.date); + return dateB.getTime() - dateA.getTime(); + }); + + return ( +
+
+ {/* Timeline line */} +
+ + {sortedEvents.map((event, index) => { + const config = eventTypeConfig[event.type]; + const Icon = config.icon; + + return ( +
+ {/* Timeline dot */} +
+
+ +
+
+ + {/* Event content */} + + +
+
+ {event.title} + + + {formatEventDate(event.date)} + + {config.label} + +
+
+
+ {(event.description || (event.from && event.to)) && ( + + {event.from && event.to && ( +
+ {event.from} + + {event.to} +
+ )} + {event.description && ( +

{event.description}

+ )} +
+ )} +
+
+ ); + })} +
+
+ ); +} + +function formatEventDate(dateString: string): string { + try { + // Handle year-only dates + if (/^\d{4}$/.test(dateString)) { + return dateString; + } + + // Handle full dates + const date = new Date(dateString); + return format(date, 'MMMM d, yyyy'); + } catch { + return dateString; + } +} diff --git a/src/components/history/FormerNamesSection.tsx b/src/components/history/FormerNamesSection.tsx new file mode 100644 index 00000000..883eb12f --- /dev/null +++ b/src/components/history/FormerNamesSection.tsx @@ -0,0 +1,100 @@ +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Tag, Calendar } from "lucide-react"; + +interface FormerName { + former_name?: string; + name?: string; + from_year?: number; + to_year?: number; + reason?: string; + date_changed?: string; +} + +interface FormerNamesSectionProps { + currentName: string; + formerNames: FormerName[]; + entityType: 'ride' | 'park' | 'company'; +} + +export function FormerNamesSection({ currentName, formerNames, entityType }: FormerNamesSectionProps) { + if (!formerNames || formerNames.length === 0) { + return ( + + + + + Former Names + + + This {entityType} has not had any previous names + + + + ); + } + + // Sort by date (most recent first) + const sortedNames = [...formerNames].sort((a, b) => { + const yearA = a.to_year || (a.date_changed ? new Date(a.date_changed).getFullYear() : 0); + const yearB = b.to_year || (b.date_changed ? new Date(b.date_changed).getFullYear() : 0); + return yearB - yearA; + }); + + return ( + + + + + Former Names + + + Historical names for this {entityType} + + + +
+ {/* Current name */} +
+
+
+
+
+

{currentName}

+

Current Name

+
+
+ + {/* Former names */} + {sortedNames.map((name, index) => { + const displayName = name.former_name || name.name; + const yearRange = name.from_year && name.to_year + ? `${name.from_year} - ${name.to_year}` + : name.date_changed + ? `Until ${new Date(name.date_changed).getFullYear()}` + : null; + + return ( +
+
+
+
+
+

{displayName}

+ {yearRange && ( +

+ + {yearRange} +

+ )} + {name.reason && ( +

{name.reason}

+ )} +
+
+ ); + })} +
+ + + ); +} diff --git a/src/pages/DesignerDetail.tsx b/src/pages/DesignerDetail.tsx index f4b64bfc..4ca2cda7 100644 --- a/src/pages/DesignerDetail.tsx +++ b/src/pages/DesignerDetail.tsx @@ -16,6 +16,9 @@ import { useUserRole } from '@/hooks/useUserRole'; import { toast } from '@/hooks/use-toast'; import { submitCompanyUpdate } from '@/lib/companyHelpers'; import { VersionIndicator } from '@/components/versioning/VersionIndicator'; +import { EntityVersionHistory } from '@/components/versioning/EntityVersionHistory'; +import { EntityHistoryTimeline, HistoryEvent } from '@/components/history/EntityHistoryTimeline'; +import { FormerNamesSection } from '@/components/history/FormerNamesSection'; export default function DesignerDetail() { const { slug } = useParams<{ slug: string }>(); @@ -226,10 +229,11 @@ export default function DesignerDetail() { {/* Tabs */} - + Overview Rides Photos + History @@ -270,6 +274,37 @@ export default function DesignerDetail() { designerName={designer.name} /> + + + + + Company History + Version History + + + + + + + + + + + diff --git a/src/pages/ManufacturerDetail.tsx b/src/pages/ManufacturerDetail.tsx index 1f636ccd..35b0dd12 100644 --- a/src/pages/ManufacturerDetail.tsx +++ b/src/pages/ManufacturerDetail.tsx @@ -16,6 +16,9 @@ import { useUserRole } from '@/hooks/useUserRole'; import { toast } from '@/hooks/use-toast'; import { submitCompanyUpdate } from '@/lib/companyHelpers'; import { VersionIndicator } from '@/components/versioning/VersionIndicator'; +import { EntityVersionHistory } from '@/components/versioning/EntityVersionHistory'; +import { EntityHistoryTimeline, HistoryEvent } from '@/components/history/EntityHistoryTimeline'; +import { FormerNamesSection } from '@/components/history/FormerNamesSection'; export default function ManufacturerDetail() { const { slug } = useParams<{ slug: string }>(); @@ -228,11 +231,12 @@ export default function ManufacturerDetail() { {/* Tabs */} - + Overview Rides Models Photos + History @@ -292,6 +296,37 @@ export default function ManufacturerDetail() { manufacturerName={manufacturer.name} /> + + + + + Company History + Version History + + + + + + + + + + + diff --git a/src/pages/OperatorDetail.tsx b/src/pages/OperatorDetail.tsx index 5561d9b5..c9983ae8 100644 --- a/src/pages/OperatorDetail.tsx +++ b/src/pages/OperatorDetail.tsx @@ -17,6 +17,9 @@ import { useUserRole } from '@/hooks/useUserRole'; import { toast } from '@/hooks/use-toast'; import { submitCompanyUpdate } from '@/lib/companyHelpers'; import { VersionIndicator } from '@/components/versioning/VersionIndicator'; +import { EntityVersionHistory } from '@/components/versioning/EntityVersionHistory'; +import { EntityHistoryTimeline, HistoryEvent } from '@/components/history/EntityHistoryTimeline'; +import { FormerNamesSection } from '@/components/history/FormerNamesSection'; export default function OperatorDetail() { const { slug } = useParams<{ slug: string }>(); @@ -255,10 +258,11 @@ export default function OperatorDetail() { {/* Tabs */} - + Overview Parks Photos + History @@ -315,6 +319,37 @@ export default function OperatorDetail() { operatorName={operator.name} /> + + + + + Company History + Version History + + + + + + + + + + + diff --git a/src/pages/ParkDetail.tsx b/src/pages/ParkDetail.tsx index 64115094..24c06755 100644 --- a/src/pages/ParkDetail.tsx +++ b/src/pages/ParkDetail.tsx @@ -21,6 +21,9 @@ import { toast } from '@/hooks/use-toast'; import { useUserRole } from '@/hooks/useUserRole'; import { Edit } from 'lucide-react'; import { VersionIndicator } from '@/components/versioning/VersionIndicator'; +import { EntityVersionHistory } from '@/components/versioning/EntityVersionHistory'; +import { EntityHistoryTimeline, HistoryEvent } from '@/components/history/EntityHistoryTimeline'; +import { FormerNamesSection } from '@/components/history/FormerNamesSection'; export default function ParkDetail() { const { @@ -356,11 +359,12 @@ export default function ParkDetail() { {/* Main Content */} - + Overview Rides ({rides.length}) Reviews Photos + History @@ -587,6 +591,43 @@ export default function ParkDetail() { entityName={park.name} /> + + + + + Park History + Version History + + + + + + + + + + + {/* Add Ride Modal */} diff --git a/src/pages/PropertyOwnerDetail.tsx b/src/pages/PropertyOwnerDetail.tsx index d643908c..3a8e860e 100644 --- a/src/pages/PropertyOwnerDetail.tsx +++ b/src/pages/PropertyOwnerDetail.tsx @@ -17,6 +17,9 @@ import { useUserRole } from '@/hooks/useUserRole'; import { toast } from '@/hooks/use-toast'; import { submitCompanyUpdate } from '@/lib/companyHelpers'; import { VersionIndicator } from '@/components/versioning/VersionIndicator'; +import { EntityVersionHistory } from '@/components/versioning/EntityVersionHistory'; +import { EntityHistoryTimeline, HistoryEvent } from '@/components/history/EntityHistoryTimeline'; +import { FormerNamesSection } from '@/components/history/FormerNamesSection'; export default function PropertyOwnerDetail() { const { slug } = useParams<{ slug: string }>(); @@ -255,10 +258,11 @@ export default function PropertyOwnerDetail() { {/* Tabs */} - + Overview Parks Photos + History @@ -315,6 +319,37 @@ export default function PropertyOwnerDetail() { propertyOwnerName={owner.name} /> + + + + + Company History + Version History + + + + + + + + + + + diff --git a/src/pages/RideDetail.tsx b/src/pages/RideDetail.tsx index 1d4182cb..16d67a82 100644 --- a/src/pages/RideDetail.tsx +++ b/src/pages/RideDetail.tsx @@ -44,6 +44,9 @@ import { useAuth } from '@/hooks/useAuth'; import { useUserRole } from '@/hooks/useUserRole'; import { toast } from '@/hooks/use-toast'; import { VersionIndicator } from '@/components/versioning/VersionIndicator'; +import { EntityVersionHistory } from '@/components/versioning/EntityVersionHistory'; +import { EntityHistoryTimeline, HistoryEvent } from '@/components/history/EntityHistoryTimeline'; +import { FormerNamesSection } from '@/components/history/FormerNamesSection'; export default function RideDetail() { const { parkSlug, rideSlug } = useParams<{ parkSlug: string; rideSlug: string }>(); @@ -379,11 +382,12 @@ export default function RideDetail() { {/* Main Content */} - + Overview Specifications Reviews Photos + History @@ -653,6 +657,51 @@ export default function RideDetail() { parentId={(ride as any).currentParkId} /> + + + + + Ride History + Version History + + + + {ride.name_history && ride.name_history.length > 0 && ( + + )} + + + + + + + + + {/* Edit Ride Modal */}