
All checks were successful
Build and Deploy Arctic Species Portal / build-and-deploy (push) Successful in 1m11s
241 lines
10 KiB
TypeScript
241 lines
10 KiB
TypeScript
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
import { AlertCircle, Edit, Loader2, Plus, Save, Trash2 } from "lucide-react";
|
|
import { SpeciesDetails, TimelineEvent } from "@/lib/api";
|
|
import { formatDate } from "@/lib/utils";
|
|
import { useTimelineEventCrud } from "@/hooks/useTimelineEventCrud";
|
|
import { useQuery } from "@tanstack/react-query";
|
|
import { getTimelineEvents } from "@/lib/api";
|
|
|
|
type TimelineTabProps = {
|
|
species: SpeciesDetails;
|
|
};
|
|
|
|
export function TimelineTab({ species }: TimelineTabProps) {
|
|
const { data: timelineEvents, isLoading: timelineLoading } = useQuery({
|
|
queryKey: ["timelineEvents", species.id],
|
|
queryFn: () => getTimelineEvents(species.id),
|
|
});
|
|
|
|
const {
|
|
isEditingTimelineEvent,
|
|
isAddingTimelineEvent,
|
|
timelineEventForm,
|
|
operationStatus,
|
|
handleTimelineEventFormChange,
|
|
handleEditTimelineEvent,
|
|
handleAddTimelineEvent,
|
|
handleSaveTimelineEvent,
|
|
handleDeleteTimelineEvent,
|
|
handleCancelTimelineEvent
|
|
} = useTimelineEventCrud(species.id);
|
|
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Conservation Timeline</CardTitle>
|
|
<CardDescription>Historical conservation and trade events</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{/* Operation status messages */}
|
|
{operationStatus.error && (
|
|
<div className="rounded-md bg-red-50 p-4 flex items-start mb-4">
|
|
<AlertCircle className="h-5 w-5 text-red-500 mr-2 mt-0.5" />
|
|
<div>
|
|
<h4 className="text-sm font-medium text-red-800">Error</h4>
|
|
<p className="text-sm text-red-700 mt-1">{operationStatus.error}</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{operationStatus.success && (
|
|
<div className="rounded-md bg-green-50 p-4 mb-4">
|
|
<p className="text-sm text-green-700">Operation completed successfully.</p>
|
|
</div>
|
|
)}
|
|
|
|
{/* Timeline event form */}
|
|
{(isAddingTimelineEvent || isEditingTimelineEvent) && (
|
|
<div className="rounded-md border p-4 mb-6">
|
|
<h3 className="text-lg font-semibold mb-4">
|
|
{isAddingTimelineEvent ? "Add New Timeline Event" : "Edit Timeline Event"}
|
|
</h3>
|
|
<div className="grid gap-4 md:grid-cols-2">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="event-date">Event Date</Label>
|
|
<input
|
|
id="event-date"
|
|
type="date"
|
|
value={timelineEventForm.event_date}
|
|
onChange={(e) => {
|
|
handleTimelineEventFormChange("event_date", e.target.value);
|
|
// Also update the year based on the date
|
|
const year = new Date(e.target.value).getFullYear();
|
|
handleTimelineEventFormChange("year", year);
|
|
}}
|
|
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="event-type">Event Type</Label>
|
|
<Select
|
|
value={timelineEventForm.event_type}
|
|
onValueChange={(value) => handleTimelineEventFormChange("event_type", value)}
|
|
>
|
|
<SelectTrigger id="event-type">
|
|
<SelectValue placeholder="Select Event Type" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="cites_listing">CITES Listing</SelectItem>
|
|
<SelectItem value="iucn_assessment">IUCN Assessment</SelectItem>
|
|
<SelectItem value="conservation_action">Conservation Action</SelectItem>
|
|
<SelectItem value="population_trend">Population Trend</SelectItem>
|
|
<SelectItem value="legislation">Legislation</SelectItem>
|
|
<SelectItem value="other">Other</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
<div className="space-y-2 md:col-span-2">
|
|
<Label htmlFor="title">Title</Label>
|
|
<input
|
|
id="title"
|
|
type="text"
|
|
value={timelineEventForm.title}
|
|
onChange={(e) => handleTimelineEventFormChange("title", e.target.value)}
|
|
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background"
|
|
placeholder="Event title"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2 md:col-span-2">
|
|
<Label htmlFor="description">Description</Label>
|
|
<textarea
|
|
id="description"
|
|
value={timelineEventForm.description}
|
|
onChange={(e) => handleTimelineEventFormChange("description", e.target.value)}
|
|
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background min-h-[100px]"
|
|
placeholder="Optional description of this event"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="status">Status (Optional)</Label>
|
|
<input
|
|
id="status"
|
|
type="text"
|
|
value={timelineEventForm.status}
|
|
onChange={(e) => handleTimelineEventFormChange("status", e.target.value)}
|
|
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background"
|
|
placeholder="e.g., Completed, In Progress, etc."
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-6 flex justify-end space-x-2">
|
|
<Button
|
|
variant="outline"
|
|
onClick={handleCancelTimelineEvent}
|
|
>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
onClick={handleSaveTimelineEvent}
|
|
disabled={operationStatus.loading || !timelineEventForm.title}
|
|
>
|
|
{operationStatus.loading ? (
|
|
<>
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
Saving...
|
|
</>
|
|
) : (
|
|
<>
|
|
<Save className="mr-2 h-4 w-4" />
|
|
Save
|
|
</>
|
|
)}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Timeline events list */}
|
|
{timelineLoading ? (
|
|
<div className="flex items-center justify-center py-8">
|
|
<Loader2 className="h-8 w-8 animate-spin text-primary" />
|
|
</div>
|
|
) : (
|
|
<div>
|
|
<div className="flex justify-between items-center mb-4">
|
|
<h3 className="text-lg font-semibold">
|
|
Timeline Events ({timelineEvents?.length || 0})
|
|
</h3>
|
|
<Button
|
|
onClick={handleAddTimelineEvent}
|
|
disabled={isAddingTimelineEvent || isEditingTimelineEvent}
|
|
size="sm"
|
|
>
|
|
<Plus className="mr-2 h-4 w-4" />
|
|
Add Event
|
|
</Button>
|
|
</div>
|
|
|
|
{timelineEvents && timelineEvents.length > 0 ? (
|
|
<div className="relative ml-4 space-y-6 border-l border-muted pl-6 pt-2">
|
|
{timelineEvents.map((event: TimelineEvent) => (
|
|
<div key={event.id} className="relative">
|
|
<div className="absolute -left-10 top-1 h-4 w-4 rounded-full bg-primary"></div>
|
|
<div className="mb-1 flex items-center justify-between">
|
|
<div className="flex items-center text-sm">
|
|
<span className="font-medium">{formatDate(event.event_date)}</span>
|
|
{event.status && (
|
|
<Badge
|
|
className="ml-2"
|
|
variant={event.event_type === 'cites_listing' ? 'default' : 'secondary'}
|
|
>
|
|
{event.status}
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
<div className="flex space-x-1">
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
onClick={() => handleEditTimelineEvent(event)}
|
|
disabled={isAddingTimelineEvent || isEditingTimelineEvent}
|
|
title="Edit event"
|
|
className="h-8 w-8"
|
|
>
|
|
<Edit className="h-4 w-4" />
|
|
</Button>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
onClick={() => handleDeleteTimelineEvent(event)}
|
|
disabled={isAddingTimelineEvent || isEditingTimelineEvent || operationStatus.loading}
|
|
title="Delete event"
|
|
className="h-8 w-8 text-red-500 hover:text-red-700 hover:bg-red-50"
|
|
>
|
|
<Trash2 className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
<h4 className="text-base font-medium">{event.title}</h4>
|
|
{event.description && <p className="text-sm text-muted-foreground">{event.description}</p>}
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<p className="text-muted-foreground">No timeline events available for this species.</p>
|
|
)}
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|