mega-plant/components/SiteContainerEditor.tsx

154 lines
5.3 KiB
TypeScript
Raw Normal View History

"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { Loader2, Package, X, Check } from "lucide-react";
// Assign the container this site hires.
//
// `code` is what the invoice prints for it (e.g. "MPLANT05"); `assetId` is the plant
// item it books to. With both set, a container line arriving on an invoice for this
// site is allocated automatically on import instead of landing unbooked.
export function SiteContainerEditor({
siteId,
siteName,
code,
assetId,
assets,
}: {
siteId: string;
siteName: string;
code: string;
assetId: string;
assets: { id: string; label: string }[];
}) {
const router = useRouter();
const [open, setOpen] = useState(false);
const [c, setC] = useState(code);
const [a, setA] = useState(assetId);
const [busy, setBusy] = useState(false);
const [error, setError] = useState<string | null>(null);
const [saved, setSaved] = useState(false);
async function save(clear = false) {
setBusy(true);
setError(null);
try {
const res = await fetch(`/api/sites/${siteId}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(clear ? { container_code: "", container_asset: "" } : { container_code: c, container_asset: a }),
});
const data = await res.json().catch(() => ({ ok: false, error: "Unexpected response" }));
if (!data.ok) {
setError(data.error || "Could not save");
} else {
setOpen(false);
setSaved(true);
setTimeout(() => setSaved(false), 2500);
router.refresh();
}
} catch {
setError("Could not reach the server");
} finally {
setBusy(false);
}
}
if (!open) {
return (
<button
type="button"
onClick={() => setOpen(true)}
className="inline-flex items-center gap-2 min-h-[44px] px-3 rounded-lg text-sm font-medium text-[#003D5B] dark:text-white hover:bg-gray-100 dark:hover:bg-white/5 transition"
>
{saved ? (
<Check className="w-4 h-4 text-[#00798C]" />
) : (
<Package className={`w-4 h-4 ${assetId ? "text-[#00798C]" : "text-gray-400"}`} />
)}
{assetId ? (
<span>
{code || "container"} → {assets.find((x) => x.id === assetId)?.label ?? "assigned"}
</span>
) : (
<span className="text-gray-500 dark:text-gray-400">No container assigned</span>
)}
</button>
);
}
return (
<div className="space-y-3">
<div className="flex flex-col sm:flex-row gap-3">
<div className="flex-1">
<label htmlFor={`cc-${siteId}`} className="block text-xs text-gray-500 dark:text-gray-400 mb-1">
Invoice code
</label>
<input
id={`cc-${siteId}`}
value={c}
onChange={(e) => setC(e.target.value)}
placeholder="MPLANT05"
className="min-h-[44px] w-full px-3 rounded-lg border border-gray-200 dark:border-white/10 bg-white dark:bg-white/5 text-sm font-mono focus:outline-none focus:border-[#30638E]"
/>
</div>
<div className="flex-1">
<label htmlFor={`ca-${siteId}`} className="block text-xs text-gray-500 dark:text-gray-400 mb-1">
Books to
</label>
<select
id={`ca-${siteId}`}
value={a}
onChange={(e) => setA(e.target.value)}
className="min-h-[44px] w-full px-3 rounded-lg border border-gray-200 dark:border-white/10 bg-white dark:bg-white/5 text-sm focus:outline-none focus:border-[#30638E]"
>
<option value="">— choose a plant item —</option>
{assets.map((x) => (
<option key={x.id} value={x.id}>
{x.label}
</option>
))}
</select>
</div>
</div>
{error && <p className="text-sm text-[#D1495B]">{error}</p>}
<div className="flex flex-wrap items-center gap-2">
<button
type="button"
onClick={() => save(false)}
disabled={busy || !c || !a}
className="inline-flex items-center gap-2 min-h-[44px] px-4 rounded-lg text-sm font-semibold text-white bg-[#00798C] hover:opacity-90 transition disabled:opacity-40"
>
{busy && <Loader2 className="w-4 h-4 animate-spin" />}
Save container
</button>
<button
type="button"
onClick={() => setOpen(false)}
disabled={busy}
className="inline-flex items-center gap-2 min-h-[44px] px-3 rounded-lg text-sm text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-white/5 transition"
>
<X className="w-4 h-4" />
Cancel
</button>
{assetId && (
<button
type="button"
onClick={() => save(true)}
disabled={busy}
className="inline-flex items-center min-h-[44px] px-3 text-sm text-[#D1495B] hover:underline disabled:opacity-40"
>
Clear
</button>
)}
</div>
<p className="text-xs text-gray-500 dark:text-gray-400">
Container lines on an invoice for <span className="font-medium">{siteName}</span> matching this code
will be booked to it automatically.
</p>
</div>
);
}