Refactor: Add History Tab

This commit is contained in:
gpt-engineer-app[bot]
2025-10-06 15:55:04 +00:00
parent 9380c8fd68
commit cb1ef6c177
8 changed files with 456 additions and 6 deletions

View File

@@ -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<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' },
};
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) => {
const config = eventTypeConfig[event.type];
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 {
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;
}
}

View File

@@ -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 (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Tag className="h-5 w-5" />
Former Names
</CardTitle>
<CardDescription>
This {entityType} has not had any previous names
</CardDescription>
</CardHeader>
</Card>
);
}
// 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 (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Tag className="h-5 w-5" />
Former Names
</CardTitle>
<CardDescription>
Historical names for this {entityType}
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
{/* Current name */}
<div className="flex items-start gap-3 p-3 rounded-lg bg-primary/5 border border-primary/20">
<div className="flex-shrink-0 mt-1">
<div className="h-3 w-3 rounded-full bg-primary" />
</div>
<div className="flex-1">
<h4 className="font-semibold text-foreground">{currentName}</h4>
<p className="text-sm text-muted-foreground">Current Name</p>
</div>
</div>
{/* 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 (
<div key={index} className="flex items-start gap-3 p-3 rounded-lg border">
<div className="flex-shrink-0 mt-1">
<div className="h-3 w-3 rounded-full bg-muted-foreground/30" />
</div>
<div className="flex-1 space-y-1">
<h4 className="font-medium text-foreground">{displayName}</h4>
{yearRange && (
<p className="text-sm text-muted-foreground flex items-center gap-1">
<Calendar className="h-3 w-3" />
{yearRange}
</p>
)}
{name.reason && (
<p className="text-sm text-muted-foreground italic">{name.reason}</p>
)}
</div>
</div>
);
})}
</div>
</CardContent>
</Card>
);
}