mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-22 13:31:22 -05:00
138 lines
4.8 KiB
TypeScript
138 lines
4.8 KiB
TypeScript
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<HistoryEventType, { icon: typeof Tag; color: string; label: string }> = {
|
|
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' },
|
|
};
|
|
|
|
// Fallback config for unknown event types
|
|
const defaultEventConfig = { icon: Tag, color: 'text-gray-500', label: 'Event' };
|
|
|
|
export function EntityHistoryTimeline({ events, entityName }: EntityHistoryTimelineProps) {
|
|
if (events.length === 0) {
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>No Historical Events</CardTitle>
|
|
<CardDescription>
|
|
No historical events recorded for {entityName}
|
|
</CardDescription>
|
|
</CardHeader>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
// 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 (
|
|
<div className="space-y-4">
|
|
<div className="relative space-y-4">
|
|
{/* Timeline line */}
|
|
<div className="absolute left-6 top-0 bottom-0 w-0.5 bg-border" />
|
|
|
|
{sortedEvents.map((event, index) => {
|
|
// Safety check: verify event.type exists in eventTypeConfig, use fallback if not
|
|
const config = event.type && eventTypeConfig[event.type]
|
|
? eventTypeConfig[event.type]
|
|
: defaultEventConfig;
|
|
const Icon = config.icon;
|
|
|
|
return (
|
|
<div key={index} className="relative flex gap-4">
|
|
{/* Timeline dot */}
|
|
<div className="relative flex-shrink-0">
|
|
<div className={`flex h-12 w-12 items-center justify-center rounded-full border-2 border-background bg-card ${config.color}`}>
|
|
<Icon className="h-5 w-5" />
|
|
</div>
|
|
</div>
|
|
|
|
{/* Event content */}
|
|
<Card className="flex-1">
|
|
<CardHeader className="pb-3">
|
|
<div className="flex items-start justify-between gap-4">
|
|
<div className="space-y-1">
|
|
<CardTitle className="text-lg">{event.title}</CardTitle>
|
|
<CardDescription className="flex items-center gap-2">
|
|
<Calendar className="h-3 w-3" />
|
|
{formatEventDate(event.date)}
|
|
<span className="text-xs">•</span>
|
|
<span className={config.color}>{config.label}</span>
|
|
</CardDescription>
|
|
</div>
|
|
</div>
|
|
</CardHeader>
|
|
{(event.description || (event.from && event.to)) && (
|
|
<CardContent className="pt-0">
|
|
{event.from && event.to && (
|
|
<div className="flex items-center gap-2 text-sm text-muted-foreground mb-2">
|
|
<span className="font-medium">{event.from}</span>
|
|
<ArrowRight className="h-4 w-4" />
|
|
<span className="font-medium">{event.to}</span>
|
|
</div>
|
|
)}
|
|
{event.description && (
|
|
<p className="text-sm text-muted-foreground">{event.description}</p>
|
|
)}
|
|
</CardContent>
|
|
)}
|
|
</Card>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function formatEventDate(dateString: string): string {
|
|
// Safety check: validate dateString exists and is a string
|
|
if (!dateString || typeof dateString !== 'string') {
|
|
return 'Unknown date';
|
|
}
|
|
|
|
try {
|
|
// Handle year-only dates
|
|
if (/^\d{4}$/.test(dateString)) {
|
|
return dateString;
|
|
}
|
|
|
|
// Validate date string before creating Date object
|
|
const date = new Date(dateString);
|
|
|
|
// Check if date is valid
|
|
if (isNaN(date.getTime())) {
|
|
return dateString;
|
|
}
|
|
|
|
return format(date, 'MMMM d, yyyy');
|
|
} catch {
|
|
return dateString;
|
|
}
|
|
}
|