'use client' import { useEffect, useMemo, useState } from 'react' import { trpc } from '@/lib/trpc/client' import { toast } from 'sonner' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Textarea } from '@/components/ui/textarea' import { Skeleton } from '@/components/ui/skeleton' import { Sheet, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, } from '@/components/ui/sheet' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { Loader2, Plane } from 'lucide-react' import type { FlightDetailStatus } from '@prisma/client' interface Props { programId: string } type AttendeeRow = { id: string needsVisa: boolean user: { id: string; name: string | null; email: string; country: string | null } confirmation: { project: { id: string title: string country: string | null competitionCategory: string | null } } flightDetail: { id: string arrivalAt: Date | null arrivalFlightNumber: string | null arrivalAirport: string | null departureAt: Date | null departureFlightNumber: string | null departureAirport: string | null status: FlightDetailStatus adminNotes: string | null } | null } type StatusFilter = 'all' | 'PENDING' | 'CONFIRMED' | 'unfilled' function formatDateTime(d: Date | null): string { if (!d) return '—' return new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'short', }).format(new Date(d)) } function isoLocalForInput(d: Date | null): string { if (!d) return '' // Format as 'YYYY-MM-DDTHH:mm' for datetime-local input const local = new Date(d.getTime() - new Date().getTimezoneOffset() * 60000) return local.toISOString().slice(0, 16) } function FlightEditorSheet({ attendee, programId, open, onClose, }: { attendee: AttendeeRow | null programId: string open: boolean onClose: () => void }) { const utils = trpc.useUtils() const [arrivalAt, setArrivalAt] = useState('') const [arrivalFlightNumber, setArrivalFlightNumber] = useState('') const [arrivalAirport, setArrivalAirport] = useState('') const [departureAt, setDepartureAt] = useState('') const [departureFlightNumber, setDepartureFlightNumber] = useState('') const [departureAirport, setDepartureAirport] = useState('') const [adminNotes, setAdminNotes] = useState('') useEffect(() => { if (!attendee) return const fd = attendee.flightDetail setArrivalAt(isoLocalForInput(fd?.arrivalAt ?? null)) setArrivalFlightNumber(fd?.arrivalFlightNumber ?? '') setArrivalAirport(fd?.arrivalAirport ?? '') setDepartureAt(isoLocalForInput(fd?.departureAt ?? null)) setDepartureFlightNumber(fd?.departureFlightNumber ?? '') setDepartureAirport(fd?.departureAirport ?? '') setAdminNotes(fd?.adminNotes ?? '') }, [attendee]) const upsertMutation = trpc.logistics.upsertFlightDetail.useMutation({ onSuccess: () => { toast.success('Flight details saved') utils.logistics.listFlightDetails.invalidate({ programId }) onClose() }, onError: (err) => toast.error(err.message), }) if (!attendee) return null const handleSave = () => { upsertMutation.mutate({ attendingMemberId: attendee.id, arrivalAt: arrivalAt ? new Date(arrivalAt) : null, arrivalFlightNumber: arrivalFlightNumber.trim() || null, arrivalAirport: arrivalAirport.trim().toUpperCase() || null, departureAt: departureAt ? new Date(departureAt) : null, departureFlightNumber: departureFlightNumber.trim() || null, departureAirport: departureAirport.trim().toUpperCase() || null, adminNotes: adminNotes.trim() || null, }) } return ( !o && onClose()}> {attendee.user.name ?? attendee.user.email} {attendee.confirmation.project.title}
Arrival
setArrivalAt(e.target.value)} />
setArrivalFlightNumber(e.target.value)} placeholder="AF7400" />
setArrivalAirport(e.target.value)} placeholder="NCE" />
Departure
setDepartureAt(e.target.value)} />
setDepartureFlightNumber(e.target.value)} placeholder="AF7405" />
setDepartureAirport(e.target.value)} placeholder="NCE" />