import { createFileRoute, Link } from '@tanstack/react-router' import { useState } from 'react' import { Stack, SimpleGrid, Card, Text, Group, Loader, Center, ActionIcon, Tooltip, Drawer, Textarea, Button, Select, TextInput, } from '@pikku/mantine/core' import { Sailboat, Play, Ban, Check, Plus } from 'lucide-react' import { useQueryClient } from '@tanstack/react-query' import { notifications } from '@mantine/notifications' import { usePikkuQuery, usePikkuMutation } from '@perauset/functions-sdk/pikku/api.gen' import { usePermission } from '@/lib/permissions' import { PageHeader } from '@perauset/components' import { StatusBadge } from '@perauset/components' import { m } from '@/i18n/messages' import { useLocale } from '@/i18n/config' import { asI18n } from '@pikku/react' export const Route = createFileRoute('/_authenticated/boats/')({ component: BoatsPage, }) type BoatTrip = { tripId: string boatId: string | null routeId: string status: string scheduledDepartureAt: string | Date scheduledArrivalAt: string | Date | null notes: string | null } function formatDateTime(dateStr: string | Date): string { try { return new Date(dateStr).toLocaleString('en-US', { month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: '2-digit', }) } catch { return String(dateStr) } } function BoatActions({ onCreateTrip }: { onCreateTrip?: () => void }) { const canManage = usePermission('boats.manage') return ( {m.boats_request_seat()} {canManage && onCreateTrip && ( } color="teal" size="sm"> {m.boats_create_trip()} )} ) } function TripActions({ trip }: { trip: BoatTrip }) { const queryClient = useQueryClient() const invalidate = () => queryClient.invalidateQueries({ queryKey: ['listBoatTrips'] }) const openMutation = usePikkuMutation('openBoatTrip', { onSuccess: () => { invalidate() notifications.show({ title: 'Trip opened', message: 'Boat trip is now open for seat requests.', color: 'green' }) }, onError: (err) => notifications.show({ title: 'Error', message: err.message || 'Failed to open trip.', color: 'red' }), }) const cancelMutation = usePikkuMutation('cancelBoatTrip', { onSuccess: () => { invalidate() notifications.show({ title: 'Trip cancelled', message: 'Boat trip has been cancelled.', color: 'orange' }) }, onError: (err) => notifications.show({ title: 'Error', message: err.message || 'Failed to cancel trip.', color: 'red' }), }) const completeMutation = usePikkuMutation('completeBoatTrip', { onSuccess: () => { invalidate() notifications.show({ title: 'Trip completed', message: 'Boat trip has been marked as completed.', color: 'green' }) }, onError: (err) => notifications.show({ title: 'Error', message: err.message || 'Failed to complete trip.', color: 'red' }), }) const canOpen = trip.status === 'planned' const canComplete = trip.status === 'open' || trip.status === 'in_progress' const canCancel = trip.status === 'planned' || trip.status === 'open' if (!canOpen && !canComplete && !canCancel) return null return ( e.preventDefault()}> {canOpen && ( openMutation.mutate({ tripId: trip.tripId })} loading={openMutation.isPending} > )} {canComplete && ( completeMutation.mutate({ tripId: trip.tripId })} loading={completeMutation.isPending} > )} {canCancel && ( cancelMutation.mutate({ tripId: trip.tripId })} loading={cancelMutation.isPending} > )} ) } function CreateTripDrawer({ opened, onClose, routeOptions, boatOptions, }: { opened: boolean onClose: () => void routeOptions: Array<{ value: string; label: string }> boatOptions: Array<{ value: string; label: string }> }) { const queryClient = useQueryClient() const [routeId, setRouteId] = useState(null) const [boatId, setBoatId] = useState(null) const [scheduledDepartureAt, setScheduledDepartureAt] = useState('') const [scheduledArrivalAt, setScheduledArrivalAt] = useState('') const [notes, setNotes] = useState('') const resetForm = () => { setRouteId(null) setBoatId(null) setScheduledDepartureAt('') setScheduledArrivalAt('') setNotes('') } const mutation = usePikkuMutation('createBoatTrip', { onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['listBoatTrips'] }) notifications.show({ title: 'Trip created', message: 'Boat trip has been created successfully.', color: 'green' }) resetForm() onClose() }, onError: (err) => notifications.show({ title: 'Error', message: err.message || 'Failed to create boat trip.', color: 'red' }), }) const handleSubmit = (e: React.FormEvent) => { e.preventDefault() if (!routeId || !scheduledDepartureAt) { notifications.show({ title: 'Missing fields', message: 'Route and departure time are required.', color: 'red' }) return } mutation.mutate({ routeId, boatId: boatId || undefined, scheduledDepartureAt: new Date(scheduledDepartureAt).toISOString(), scheduledArrivalAt: scheduledArrivalAt ? new Date(scheduledArrivalAt).toISOString() : undefined, notes: notes || undefined, }) } return ( setScheduledDepartureAt(e.currentTarget.value)} required /> setScheduledArrivalAt(e.currentTarget.value)} min={scheduledDepartureAt || undefined} /> setNotes(e.currentTarget.value)} minRows={3} /> {m.common_cancel()} {m.boats_create_trip()} ) } function BoatsPage() { useLocale() const canManage = usePermission('boats.manage') const [createDrawerOpen, setCreateDrawerOpen] = useState(false) const { data, isLoading } = usePikkuQuery('listBoatTrips', { limit: 50, offset: 0 }) const { data: routesData } = usePikkuQuery('listBoatRoutes', { limit: 100, offset: 0 }) const { data: boatsData } = usePikkuQuery('listBoats', { limit: 100, offset: 0 }) const trips = data?.items ?? [] const routeMap = new Map((routesData?.items ?? []).map((r) => [r.routeId, r])) const boatMap = new Map((boatsData?.items ?? []).map((b) => [b.boatId, b])) if (isLoading) { return ( ) } return ( setCreateDrawerOpen(true)} />} /> {trips.length === 0 ? ( {m.boats_no_trips()} ) : ( {trips.map((trip) => { const route = routeMap.get(trip.routeId) const boat = trip.boatId ? boatMap.get(trip.boatId) : null const routeLabel = route ? `${route.name} (${route.origin} - ${route.destination})` : 'Route' const boatLabel = boat ? boat.name : null return ( {asI18n(routeLabel)} {canManage && } {m.boats_departure()} {asI18n(formatDateTime(trip.scheduledDepartureAt))} {trip.scheduledArrivalAt && ( {m.boats_arrival()} {asI18n(formatDateTime(trip.scheduledArrivalAt))} )} {boatLabel && ( {asI18n(`${m.boats_boat()}: ${boatLabel}`)} )} {trip.boatId && !boatLabel && ( {asI18n(`${m.boats_boat()}: ${m.boats_assigned()}`)} )} {trip.notes && ( {asI18n(trip.notes)} )} ) })} )} setCreateDrawerOpen(false)} routeOptions={(routesData?.items ?? []).map((r) => ({ value: r.routeId, label: `${r.name} (${r.origin} - ${r.destination})`, }))} boatOptions={(boatsData?.items ?? []).map((b) => ({ value: b.boatId, label: `${b.name} (${b.passengerCapacity} seats)`, }))} /> ) }