"use client"; import { useEffect, useMemo, useState } from "react"; type OverviewRow = { skuId: number; skuCode: string; productName: string; category: string | null; size: string | null; color: string | null; price: number; currentStock: number; }; type MovementPayload = { skuId: number; delta: number; reason: "PRODUCTION" | "SALE" | "ADJUSTMENT" | "RETURN"; reference?: string; }; export default function Home() { const [rows, setRows] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [q, setQ] = useState(""); const [category, setCategory] = useState("ALL"); const [modalOpen, setModalOpen] = useState(false); const [selectedSku, setSelectedSku] = useState(null); const [delta, setDelta] = useState(0); const [reason, setReason] = useState("ADJUSTMENT"); const [reference, setReference] = useState(""); async function load() { setLoading(true); setError(null); try { const res = await fetch("/api/inventory/overview", { credentials: "include" }); if (res.status === 401 || res.status === 302) { // If you're not logged in, Spring may redirect. In the browser, just go to /login. setError("Not logged in. Open /login in the browser."); setRows([]); return; } if (!res.ok) throw new Error(`Failed to load: ${res.status}`); const data = (await res.json()) as OverviewRow[]; setRows(data); } catch (e: any) { setError(e?.message ?? "Unknown error"); } finally { setLoading(false); } } async function logout() { await fetch("/logout", { method: "POST", credentials: "include" }); window.location.href = "/login"; } useEffect(() => { load(); }, []); const categories = useMemo(() => { const set = new Set(); rows.forEach((r) => r.category && set.add(r.category)); return ["ALL", ...Array.from(set).sort()]; }, [rows]); const filtered = useMemo(() => { const qq = q.trim().toLowerCase(); return rows.filter((r) => { const matchesQ = !qq || r.productName.toLowerCase().includes(qq) || r.skuCode.toLowerCase().includes(qq); const matchesCat = category === "ALL" || r.category === category; return matchesQ && matchesCat; }); }, [rows, q, category]); function openMovement(row: OverviewRow) { setSelectedSku(row); setDelta(0); setReason("ADJUSTMENT"); setReference(""); setModalOpen(true); } async function submitMovement() { if (!selectedSku) return; if (!Number.isFinite(delta) || delta === 0) { alert("Delta must be a non-zero number (e.g. 10 or -1)."); return; } const payload: MovementPayload = { skuId: selectedSku.skuId, delta, reason, reference: reference.trim() || undefined, }; const res = await fetch("/api/inventory/movements", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify(payload), }); if (!res.ok) { const txt = await res.text(); alert(`Failed: ${res.status}\n${txt}`); return; } setModalOpen(false); setSelectedSku(null); await load(); } return (

Workspace Inventory

Data from /api/inventory/overview

Login
setQ(e.target.value)} />
{loading &&

Loading…

} {error && (

{error}

)}
{filtered.map((r) => ( ))} {!loading && filtered.length === 0 && ( )}
Product Category SKU Size Color Price Stock
{r.productName} {r.category ?? "-"} {r.skuCode} {r.size ?? "-"} {r.color ?? "-"} {Number(r.price).toFixed(2)} {r.currentStock}
No rows. (Did you create SKUs + movements in the backend?)
{modalOpen && selectedSku && (

Add movement

{selectedSku.productName} — {selectedSku.skuCode}

setDelta(Number(e.target.value))} placeholder="e.g. 10 or -1" />
setReference(e.target.value)} placeholder='e.g. "Order #1001"' />
)}
); }