"use client"; import { useCallback, useEffect, useRef, useState } from "react"; import ReactMarkdown from "react-markdown"; import { useParams, useRouter } from "next/navigation"; import { useAuth } from "@/components/AuthProvider"; import { c2api } from "@/lib/c2api"; import type { TripEvent, TripRecord, PlaceResult } from "@/lib/types"; // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- function uid(): string { try { return crypto.randomUUID(); } catch { return Math.random().toString(36).slice(2) + Date.now().toString(36); } } function toMin(t: string): number { const [h, m] = t.split(":").map(Number); return h * 60 + (m ?? 0); } function fmtDate(iso: string) { return new Date(`${iso}T12:00:00`).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", }); } function fmtDayTab(iso: string) { const d = new Date(`${iso}T12:00:00`); return { short: d.toLocaleDateString("en-US", { weekday: "short" }), date: d.toLocaleDateString("en-US", { month: "short", day: "numeric" }), }; } function fmtDayHeading(iso: string) { return new Date(`${iso}T12:00:00`).toLocaleDateString("en-US", { weekday: "long", month: "long", day: "numeric", }); } function fmtTime(t: string | null | undefined): string { if (!t) return ""; const [h, m] = t.split(":").map(Number); const d = new Date(); d.setHours(h, m, 0, 0); return d.toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" }); } function dateRange(start: string, end: string): string[] { const dates: string[] = []; const cur = new Date(`${start}T12:00:00`); const endDate = new Date(`${end}T12:00:00`); while (cur <= endDate) { dates.push(cur.toISOString().slice(0, 10)); cur.setDate(cur.getDate() + 1); } return dates; } // --------------------------------------------------------------------------- // Tag helpers // --------------------------------------------------------------------------- const TAG_PALETTE = [ "bg-violet-900/60 text-violet-300 border-violet-700/50", "bg-sky-900/60 text-sky-300 border-sky-700/50", "bg-emerald-900/60 text-emerald-300 border-emerald-700/50", "bg-amber-900/60 text-amber-300 border-amber-700/50", "bg-rose-900/60 text-rose-300 border-rose-700/50", "bg-fuchsia-900/60 text-fuchsia-300 border-fuchsia-700/50", "bg-cyan-900/60 text-cyan-300 border-cyan-700/50", "bg-orange-900/60 text-orange-300 border-orange-700/50", ]; function tagColor(tag: string, availableTags: string[]): string { const idx = availableTags.indexOf(tag); return TAG_PALETTE[(idx >= 0 ? idx : 0) % TAG_PALETTE.length]; } function TagPill({ tag, availableTags }: { tag: string; availableTags: string[] }) { return ( {tag} ); } function detectConflicts(events: TripEvent[]): Set { const timed = events.filter((e) => e.start_time); const conflicts = new Set(); for (let i = 0; i < timed.length; i++) { for (let j = i + 1; j < timed.length; j++) { const aS = toMin(timed[i].start_time!); const aE = timed[i].end_time ? toMin(timed[i].end_time!) : aS + 60; const bS = toMin(timed[j].start_time!); const bE = timed[j].end_time ? toMin(timed[j].end_time!) : bS + 60; if (aS < bE && bS < aE) { conflicts.add(timed[i].event_id); conflicts.add(timed[j].event_id); } } } return conflicts; } // --------------------------------------------------------------------------- // Places search dropdown (reusable within modal) // --------------------------------------------------------------------------- function PlaceSearch({ near, onSelect, }: { near: string; onSelect: (p: PlaceResult) => void; }) { const [query, setQuery] = useState(""); const [results, setResults] = useState([]); const [loading, setLoading] = useState(false); async function search() { if (!query.trim()) return; setLoading(true); try { const r = await c2api.searchPlaces(query, near); setResults(r); } finally { setLoading(false); } } return (
setQuery(e.target.value)} onKeyDown={(e) => e.key === "Enter" && (e.preventDefault(), search())} placeholder="Search a place…" className="flex-1 bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500" />
{results.length > 0 && (
{results.map((p) => ( ))}
)}
); } // --------------------------------------------------------------------------- // Add Event modal // --------------------------------------------------------------------------- function AddEventModal({ trip, onClose, onAdd, prefill, editEventId, }: { trip: TripRecord; onClose: () => void; onAdd: (body: object) => Promise; prefill?: Partial; editEventId?: string; }) { const isEdit = !!editEventId; const availableTags = trip.available_tags ?? []; const [title, setTitle] = useState(prefill?.title ?? ""); const [date, setDate] = useState(prefill?.date ?? trip.start_date); const [startTime, setStartTime] = useState(prefill?.start_time ?? ""); const [endTime, setEndTime] = useState(prefill?.end_time ?? ""); const [location, setLocation] = useState(prefill?.location ?? ""); const [mapsLink, setMapsLink] = useState(prefill?.maps_link ?? ""); const [placeId, setPlaceId] = useState(prefill?.place_id ?? ""); const [notes, setNotes] = useState(prefill?.notes ?? ""); const [tags, setTags] = useState(prefill?.tags ?? []); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); function handlePlaceSelect(p: PlaceResult) { if (!title) setTitle(p.name); setLocation(p.address); setMapsLink(p.maps_link); setPlaceId(p.place_id); } async function handleSubmit(e: React.FormEvent) { e.preventDefault(); setSaving(true); setError(null); try { await onAdd({ title, date, start_time: startTime || null, end_time: endTime || null, location: location || null, maps_link: mapsLink || null, place_id: placeId || null, notes: notes || null, tags, }); onClose(); } catch { setError(isEdit ? "Failed to save changes." : "Failed to add event. Check the date is within the trip range."); } finally { setSaving(false); } } return (

{isEdit ? "Edit Event" : "Add Event"}

setTitle(e.target.value)} placeholder="Dinner at the bar" className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500" />
setDate(e.target.value)} className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500" />
setStartTime(e.target.value)} className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500" />
setEndTime(e.target.value)} min={startTime} className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500" />
setLocation(e.target.value)} placeholder={`Inherits: ${trip.location}`} className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500" />
{mapsLink && (

Maps link attached —{" "} preview

)}