"use client"; import { useEffect, useState } from "react"; import { useRouter } from "next/navigation"; import { Loader2, X, MapPin, Sprout } from "lucide-react"; type SiteOption = { id: string; name: string; taken: boolean }; // Book an invoice line that has no matching plant item to a SITE instead. // // One container per site: sites another line has already taken are listed but disabled, // so the outstanding containers spread across sites rather than piling onto the first. // Clearing frees the site again. Booking to a site accounts for the line without // creating income, because no machine exists to carry it. export function SiteBookingSelect({ lineId, value, }: { lineId: string; value: string; }) { const router = useRouter(); const [options, setOptions] = useState(null); const [current, setCurrent] = useState(value); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); useEffect(() => { setCurrent(value); }, [value]); useEffect(() => { let alive = true; fetch(`/api/invoice-lines/${lineId}/site-options`, { cache: "no-store" }) .then((r) => r.json()) .then((d) => { if (alive && d.ok) setOptions(d.options); }) .catch(() => { if (alive) setOptions([]); }); return () => { alive = false; }; }, [lineId]); async function book(payload: Record, optimistic: string) { setBusy(true); setError(null); const previous = current; setCurrent(optimistic); try { const res = await fetch(`/api/invoice-lines/${lineId}/book`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); const data = await res.json().catch(() => ({ ok: false, error: "Unexpected response" })); if (!data.ok) { setCurrent(previous); setError(data.error || "Could not book it"); } else { router.refresh(); } } catch { setCurrent(previous); setError("Could not reach the server"); } finally { setBusy(false); } } if (options === null) return null; const currentName = options.find((o) => o.id === current)?.name; return (
{busy && } {current && !busy && ( )}
{currentName && (

Booked to {currentName} — no income posted (no machine)

)} {error &&

{error}

}
); } export function SiteBookingLegend() { return (

A site booking accounts for a line that matches no plant item. It posts no income — there is no machine to carry it — and each site takes one container.

); }