import { useRef, useEffect, useState, useCallback } from "preact/hooks"; import type { Trip, Day } from "../../lib/types"; import type { TripTimeState } from "../../lib/use-trip-time"; import { slugify } from "../../lib/categories"; function DayCard({ day, time, dayIdx }: { day: Day; time: TripTimeState; dayIdx: number; }) { const currentRef = useRef(null); const isToday = dayIdx === time.todayIdx; const isPast = time.tripStarted && dayIdx < time.todayIdx; useEffect(() => { if (isToday && currentRef.current) { currentRef.current.scrollIntoView({ behavior: "smooth", block: "center" }); } }, [isToday, time.currentStopIdx]); const dateStr = new Date(day.date + "T12:00:00").toLocaleDateString("en-US", { weekday: "long", month: "long", day: "numeric", }); return (
{isToday && } {day.label}
{dateStr}
{day.summary}
{day.stops.map((stop, i) => { const isCurrent = isToday && i === time.currentStopIdx; const isNext = isToday && i === time.nextStopIdx; const isDone = isToday && time.currentStopIdx >= 0 && i < time.currentStopIdx; const isPastDay = isPast; return (
{isCurrent && NOW} {isNext && NEXT} {stop.arrival && {stop.arrival}}
{stop.name} {stop.purpose && {stop.purpose}}
); })}
{day.drives.length > 0 && (
{day.drives.map((d, i) => (
{d.from} {d.to} {d.distance && {d.distance}} {d.duration && ยท {d.duration}}
))}
)}
); } function PackCard({ items, initialState, tripId }: { items: string[]; initialState: Record; tripId: string; }) { const [checked, setChecked] = useState>(() => { const state: Record = {}; items.forEach(item => { const key = slugify(item); state[key] = initialState[key] ?? false; }); return state; }); const saveTimeout = useRef | null>(null); const persist = useCallback((newState: Record) => { if (saveTimeout.current) clearTimeout(saveTimeout.current); saveTimeout.current = setTimeout(() => { fetch(`/travel/api/pack-state?tripId=${tripId}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(newState), }).catch(() => {}); }, 500); }, [tripId]); const toggle = useCallback((item: string) => { const key = slugify(item); setChecked(prev => { const next = { ...prev, [key]: !prev[key] }; persist(next); return next; }); }, [persist]); const total = items.length; const done = Object.values(checked).filter(Boolean).length; const pct = total > 0 ? Math.round((done / total) * 100) : 0; if (items.length === 0) return null; return (
Packing List
{done}/{total} packed
{items.map(item => { const key = slugify(item); const isChecked = checked[key] ?? false; return (
toggle(item)} >
{isChecked ? "\u2713" : ""}
{item}
); })}
); } export function ItineraryTab({ trip, time, packItems, packState }: { trip: Trip; time: TripTimeState; packItems: string[]; packState: Record; }) { return (
{trip.days.map((day, i) => ( ))}
); }