mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-23 12:31:13 -05:00
120 lines
3.9 KiB
TypeScript
120 lines
3.9 KiB
TypeScript
import { Edit, Trash, Clock, CheckCircle } from 'lucide-react';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Card, CardContent } from '@/components/ui/card';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { format } from 'date-fns';
|
|
import { parseDateForDisplay } from '@/lib/dateUtils';
|
|
import type { TimelineEvent } from '@/types/timeline';
|
|
|
|
interface TimelineEventCardProps {
|
|
event: TimelineEvent;
|
|
onEdit?: (event: TimelineEvent) => void;
|
|
onDelete?: (eventId: string) => void;
|
|
canEdit: boolean;
|
|
canDelete: boolean;
|
|
isPending?: boolean;
|
|
}
|
|
|
|
// ⚠️ IMPORTANT: Use parseDateForDisplay to prevent timezone shifts
|
|
// YYYY-MM-DD strings must be interpreted as local dates, not UTC
|
|
const formatEventDate = (date: string, precision: string = 'day') => {
|
|
const dateObj = parseDateForDisplay(date);
|
|
|
|
switch (precision) {
|
|
case 'year':
|
|
return format(dateObj, 'yyyy');
|
|
case 'month':
|
|
return format(dateObj, 'MMMM yyyy');
|
|
case 'day':
|
|
default:
|
|
return format(dateObj, 'MMMM d, yyyy');
|
|
}
|
|
};
|
|
|
|
const getEventTypeLabel = (type: string): string => {
|
|
return type.split('_').map(word =>
|
|
word.charAt(0).toUpperCase() + word.slice(1)
|
|
).join(' ');
|
|
};
|
|
|
|
export function TimelineEventCard({
|
|
event,
|
|
onEdit,
|
|
onDelete,
|
|
canEdit,
|
|
canDelete,
|
|
isPending = false
|
|
}: TimelineEventCardProps) {
|
|
return (
|
|
<Card className={isPending ? 'border-yellow-500/50 bg-yellow-500/5' : ''}>
|
|
<CardContent className="p-4">
|
|
<div className="flex justify-between items-start gap-4">
|
|
<div className="flex-1 space-y-2">
|
|
<div className="flex items-center gap-2 flex-wrap">
|
|
<Badge variant={isPending ? 'secondary' : 'default'}>
|
|
{getEventTypeLabel(event.event_type)}
|
|
</Badge>
|
|
{isPending && (
|
|
<Badge variant="outline" className="gap-1">
|
|
<Clock className="w-3 h-3" />
|
|
Pending Approval
|
|
</Badge>
|
|
)}
|
|
{!isPending && (
|
|
<Badge variant="outline" className="gap-1">
|
|
<CheckCircle className="w-3 h-3" />
|
|
Approved
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
|
|
<div>
|
|
<h4 className="font-semibold text-lg">{event.title}</h4>
|
|
<p className="text-sm text-muted-foreground">
|
|
{formatEventDate(event.event_date, event.event_date_precision)}
|
|
</p>
|
|
</div>
|
|
|
|
{event.description && (
|
|
<p className="text-sm">{event.description}</p>
|
|
)}
|
|
|
|
{(event.from_value || event.to_value) && (
|
|
<div className="text-sm text-muted-foreground">
|
|
{event.from_value && <span>From: {event.from_value}</span>}
|
|
{event.from_value && event.to_value && <span className="mx-2">→</span>}
|
|
{event.to_value && <span>To: {event.to_value}</span>}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{(canEdit || canDelete) && (
|
|
<div className="flex gap-2">
|
|
{canEdit && (
|
|
<Button
|
|
size="icon"
|
|
variant="ghost"
|
|
onClick={() => onEdit?.(event)}
|
|
title="Edit event"
|
|
>
|
|
<Edit className="w-4 h-4" />
|
|
</Button>
|
|
)}
|
|
{canDelete && (
|
|
<Button
|
|
size="icon"
|
|
variant="ghost"
|
|
onClick={() => onDelete?.(event.id)}
|
|
title="Delete event"
|
|
className="text-destructive hover:text-destructive"
|
|
>
|
|
<Trash className="w-4 h-4" />
|
|
</Button>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
} |