mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-21 16:11:13 -05:00
267 lines
9.4 KiB
TypeScript
267 lines
9.4 KiB
TypeScript
import { Calendar, Tag, ArrowRight, MapPin, Building2, Clock } from 'lucide-react';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Separator } from '@/components/ui/separator';
|
|
import { FlexibleDateDisplay } from '@/components/ui/flexible-date-display';
|
|
import type { TimelineSubmissionData } from '@/types/timeline';
|
|
import { useEffect, useState } from 'react';
|
|
import { supabase } from '@/lib/supabaseClient';
|
|
|
|
interface RichTimelineEventDisplayProps {
|
|
data: TimelineSubmissionData;
|
|
actionType: 'create' | 'edit' | 'delete';
|
|
}
|
|
|
|
export function RichTimelineEventDisplay({ data, actionType }: RichTimelineEventDisplayProps) {
|
|
const [entityName, setEntityName] = useState<string | null>(null);
|
|
const [parkContext, setParkContext] = useState<string | null>(null);
|
|
const [fromEntity, setFromEntity] = useState<string | null>(null);
|
|
const [toEntity, setToEntity] = useState<string | null>(null);
|
|
const [fromLocation, setFromLocation] = useState<any>(null);
|
|
const [toLocation, setToLocation] = useState<any>(null);
|
|
|
|
useEffect(() => {
|
|
if (!data) return;
|
|
|
|
const fetchRelatedData = async () => {
|
|
// Fetch the main entity this timeline event is for
|
|
if (data.entity_id && data.entity_type) {
|
|
if (data.entity_type === 'park') {
|
|
const { data: park } = await supabase
|
|
.from('parks')
|
|
.select('name')
|
|
.eq('id', data.entity_id)
|
|
.single();
|
|
setEntityName(park?.name || null);
|
|
} else if (data.entity_type === 'ride') {
|
|
const { data: ride } = await supabase
|
|
.from('rides')
|
|
.select('name, park:parks(name)')
|
|
.eq('id', data.entity_id)
|
|
.single();
|
|
setEntityName(ride?.name || null);
|
|
setParkContext((ride?.park as any)?.name || null);
|
|
}
|
|
}
|
|
|
|
// Fetch from/to entities for relational changes
|
|
if (data.from_entity_id) {
|
|
const { data: entity } = await supabase
|
|
.from('companies')
|
|
.select('name')
|
|
.eq('id', data.from_entity_id)
|
|
.single();
|
|
setFromEntity(entity?.name || null);
|
|
}
|
|
|
|
if (data.to_entity_id) {
|
|
const { data: entity } = await supabase
|
|
.from('companies')
|
|
.select('name')
|
|
.eq('id', data.to_entity_id)
|
|
.single();
|
|
setToEntity(entity?.name || null);
|
|
}
|
|
|
|
// Fetch from/to locations for location changes
|
|
if (data.from_location_id) {
|
|
const { data: loc } = await supabase
|
|
.from('locations')
|
|
.select('*')
|
|
.eq('id', data.from_location_id)
|
|
.single();
|
|
setFromLocation(loc);
|
|
}
|
|
|
|
if (data.to_location_id) {
|
|
const { data: loc } = await supabase
|
|
.from('locations')
|
|
.select('*')
|
|
.eq('id', data.to_location_id)
|
|
.single();
|
|
setToLocation(loc);
|
|
}
|
|
};
|
|
|
|
fetchRelatedData();
|
|
}, [data.entity_id, data.entity_type, data.from_entity_id, data.to_entity_id, data.from_location_id, data.to_location_id]);
|
|
|
|
const formatEventType = (type: string) => {
|
|
return type.replace(/_/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase());
|
|
};
|
|
|
|
const getEventTypeColor = (type: string) => {
|
|
switch (type) {
|
|
case 'opening': return 'bg-green-600';
|
|
case 'closure': return 'bg-red-600';
|
|
case 'reopening': return 'bg-blue-600';
|
|
case 'renovation': return 'bg-purple-600';
|
|
case 'expansion': return 'bg-indigo-600';
|
|
case 'acquisition': return 'bg-amber-600';
|
|
case 'name_change': return 'bg-cyan-600';
|
|
case 'operator_change':
|
|
case 'owner_change': return 'bg-orange-600';
|
|
case 'location_change': return 'bg-pink-600';
|
|
case 'status_change': return 'bg-yellow-600';
|
|
case 'milestone': return 'bg-emerald-600';
|
|
default: return 'bg-gray-600';
|
|
}
|
|
};
|
|
|
|
const getPrecisionIcon = (precision: string) => {
|
|
switch (precision) {
|
|
case 'day': return '📅';
|
|
case 'month': return '📆';
|
|
case 'year': return '🗓️';
|
|
default: return '📅';
|
|
}
|
|
};
|
|
|
|
const formatLocation = (loc: any) => {
|
|
if (!loc) return null;
|
|
const parts = [loc.city, loc.state_province, loc.country].filter(Boolean);
|
|
return parts.join(', ');
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
{/* Header Section */}
|
|
<div className="flex items-start gap-3">
|
|
<div className="p-2 rounded-lg bg-primary/10 text-primary">
|
|
<Calendar className="h-5 w-5" />
|
|
</div>
|
|
<div className="flex-1 min-w-0">
|
|
<h3 className="text-xl font-bold text-foreground">{data.title}</h3>
|
|
<div className="flex items-center gap-2 mt-1 flex-wrap">
|
|
<Badge className={`${getEventTypeColor(data.event_type)} text-white text-xs`}>
|
|
{formatEventType(data.event_type)}
|
|
</Badge>
|
|
{actionType === 'create' && (
|
|
<Badge className="bg-green-600 text-white text-xs">New Event</Badge>
|
|
)}
|
|
{actionType === 'edit' && (
|
|
<Badge className="bg-amber-600 text-white text-xs">Edit Event</Badge>
|
|
)}
|
|
{actionType === 'delete' && (
|
|
<Badge variant="destructive" className="text-xs">Delete Event</Badge>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<Separator />
|
|
|
|
{/* Entity Context Section */}
|
|
<div className="grid gap-3">
|
|
<div className="flex items-center gap-2 text-sm">
|
|
<Tag className="h-4 w-4 text-muted-foreground" />
|
|
<span className="font-medium">Event For:</span>
|
|
<span className="text-foreground">
|
|
{entityName || 'Loading...'}
|
|
<Badge variant="outline" className="ml-2 text-xs">
|
|
{data.entity_type}
|
|
</Badge>
|
|
</span>
|
|
</div>
|
|
|
|
{parkContext && (
|
|
<div className="flex items-center gap-2 text-sm">
|
|
<Building2 className="h-4 w-4 text-muted-foreground" />
|
|
<span className="font-medium">Park:</span>
|
|
<span className="text-foreground">{parkContext}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<Separator />
|
|
|
|
{/* Event Date Section */}
|
|
<div className="space-y-2">
|
|
<div className="flex items-center gap-2 text-sm">
|
|
<Clock className="h-4 w-4 text-muted-foreground" />
|
|
<span className="font-medium">Event Date:</span>
|
|
</div>
|
|
<div className="flex items-center gap-3 pl-6">
|
|
<span className="text-2xl">{getPrecisionIcon(data.event_date_precision)}</span>
|
|
<div>
|
|
<div className="text-lg font-semibold">
|
|
<FlexibleDateDisplay
|
|
date={data.event_date}
|
|
precision={data.event_date_precision}
|
|
/>
|
|
</div>
|
|
<div className="text-xs text-muted-foreground">
|
|
Precision: {data.event_date_precision}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Change Details Section */}
|
|
{(data.from_value || data.to_value || fromEntity || toEntity) && (
|
|
<>
|
|
<Separator />
|
|
<div className="space-y-2">
|
|
<div className="text-sm font-medium">Change Details:</div>
|
|
<div className="flex items-center gap-3 pl-6">
|
|
<div className="flex-1 p-3 rounded-lg bg-muted/50">
|
|
<div className="text-xs text-muted-foreground mb-1">From</div>
|
|
<div className="font-medium">
|
|
{fromEntity || data.from_value || '—'}
|
|
</div>
|
|
</div>
|
|
<ArrowRight className="h-5 w-5 text-muted-foreground flex-shrink-0" />
|
|
<div className="flex-1 p-3 rounded-lg bg-muted/50">
|
|
<div className="text-xs text-muted-foreground mb-1">To</div>
|
|
<div className="font-medium">
|
|
{toEntity || data.to_value || '—'}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{/* Location Change Section */}
|
|
{(fromLocation || toLocation) && (
|
|
<>
|
|
<Separator />
|
|
<div className="space-y-2">
|
|
<div className="flex items-center gap-2 text-sm font-medium">
|
|
<MapPin className="h-4 w-4" />
|
|
Location Change:
|
|
</div>
|
|
<div className="flex items-center gap-3 pl-6">
|
|
<div className="flex-1 p-3 rounded-lg bg-muted/50">
|
|
<div className="text-xs text-muted-foreground mb-1">From</div>
|
|
<div className="font-medium">
|
|
{formatLocation(fromLocation) || '—'}
|
|
</div>
|
|
</div>
|
|
<ArrowRight className="h-5 w-5 text-muted-foreground flex-shrink-0" />
|
|
<div className="flex-1 p-3 rounded-lg bg-muted/50">
|
|
<div className="text-xs text-muted-foreground mb-1">To</div>
|
|
<div className="font-medium">
|
|
{formatLocation(toLocation) || '—'}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{/* Description Section */}
|
|
{data.description && (
|
|
<>
|
|
<Separator />
|
|
<div className="space-y-2">
|
|
<div className="text-sm font-medium">Description:</div>
|
|
<p className="text-sm text-muted-foreground pl-6 leading-relaxed">
|
|
{data.description}
|
|
</p>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|