/* =========================================================================== VESTIRÉ STUDIO — admin: dashboard · requests · messages -> window =========================================================================== */ const { useState, useEffect, useRef, useMemo } = React; function adminMetrics(requests) { const active = requests.filter((r) => r.status !== "Cancelled"); const revenue = requests.filter((r) => r.status === "Completed").reduce((a, b) => a + b.total, 0); const pipeline = active.filter((r) => r.status !== "Completed").reduce((a, b) => a + b.total, 0); return { revenue, pipeline, total: requests.length, pending: requests.filter((r) => r.status === "Pending").length, processing: requests.filter((r) => r.status === "Processing").length, completed: requests.filter((r) => r.status === "Completed").length, cancelled: requests.filter((r) => r.status === "Cancelled").length }; } function AdminPageHead({ kicker, title, children }) { return (
{kicker}

{title}

{children}
); } /* ---- DASHBOARD ----------------------------------------------------------- */ function ADashboard({ ctx }) { const m = adminMetrics(ctx.requests); const recent = [...ctx.requests].sort((a, b) => b.createdAt.localeCompare(a.createdAt)).slice(0, 6); const unread = ctx.messages.filter((x) => !x.isRead); const lowStock = ctx.gowns.filter((g) => g.stock <= 1); const months = ["Feb", "Mar", "Apr", "May", "Jun"]; const byMonth = { Feb: 8, Mar: 22, Apr: 31, May: 27, Jun: 42 }; const maxM = Math.max(...Object.values(byMonth)); return (
ctx.nav("messages")}>Inbox{unread.length ? " · " + unread.length : ""} ctx.nav("gownEdit", { id: null })}>Add gown
{/* recent requests */}

Recent requests

ctx.nav("requests")}>Manage all
{recent.map((r) => ( ctx.nav("requests", { focus: r.id })}> ))}
RequestCustomerItemsStatusTotal
{r.code}{fmtDate(r.createdAt)}
{r.customer.name} {r.items.length} · {r.type} {VS.peso(r.total)}
{/* side column */}

Revenue trend

{months.map((mo) => (
{mo}
))}
Jun to date{VS.peso(42000)}

Unread messages

{unread.length}
{unread.slice(0, 3).map((x) => (
ctx.nav("messages", { focus: x.id })}> {initials(x.name)}
{x.subject}{x.body}
))} {unread.length === 0 && You're all caught up.} ctx.nav("messages")}>Open inbox
); } /* ---- REQUESTS (admin) ---------------------------------------------------- */ const NEXT_ACTION = { Pending: [["Approve → Processing", "Processing", "accent", "check"], ["Decline", "Cancelled", "danger", "x"]], Processing: [["Mark completed", "Completed", "accent", "check"], ["Cancel", "Cancelled", "danger", "x"]], Completed: [["Reopen", "Processing", "soft", "arrowL"]], Cancelled: [["Restore → Pending", "Pending", "soft", "arrowL"]] }; function ARequests({ ctx }) { const focus = (ctx.route.params && ctx.route.params.focus) || null; const [status, setStatus] = useState("All"); const [q, setQ] = useState(""); const [open, setOpen] = useState(focus); const STAT = ["All", "Pending", "Processing", "Completed", "Cancelled"]; const list = ctx.requests .filter((r) => (status === "All" || r.status === status) && (q === "" || r.code.toLowerCase().includes(q.toLowerCase()) || r.customer.name.toLowerCase().includes(q.toLowerCase()))) .sort((a, b) => b.createdAt.localeCompare(a.createdAt)); return (
setQ(e.target.value)} />
{STAT.map((s) => { const n = s === "All" ? ctx.requests.length : ctx.requests.filter((r) => r.status === s).length; return ; })}
{list.map((r) => { const isOpen = open === r.id; return (
setOpen(isOpen ? null : r.id)}>
{initials(r.customer.name)}
{r.code}{r.type}
{r.customer.name} · {fmtDate(r.createdAt)} · {r.items.length} piece{r.items.length !== 1 ? "s" : ""}
{VS.peso(r.total)}
{isOpen && (
Line items
{r.items.map((it, i) => (
{it.name}{it.lineType} · size {it.size} · qty {it.qty} · {VS.peso(it.unitPrice)} each
{VS.peso(it.subtotal)}
))}
Total{VS.peso(r.total)}
{r.type === "rental" &&
Pickup date{fmtDate(r.rentalStart)}
Rental days{r.rentalDays}
Payment{r.payment}
}
Customer {r.customer.name} {r.customer.email} {r.customer.phone} {r.customer.address}
Update status
{(NEXT_ACTION[r.status] || []).map(([lbl, to, variant, icon]) => ( ctx.setRequestStatus(r.id, to)}>{lbl} ))}
)}
); })} {list.length === 0 &&

No requests match.

}
); } /* ---- MESSAGES (admin inbox) --------------------------------------------- */ function AMessages({ ctx }) { const focus = (ctx.route.params && ctx.route.params.focus) || null; const sorted = [...ctx.messages].sort((a, b) => b.createdAt.localeCompare(a.createdAt)); const [sel, setSel] = useState(focus || (sorted[0] && sorted[0].id)); const [filter, setFilter] = useState("All"); const list = sorted.filter((x) => filter === "All" || (filter === "Unread" ? !x.isRead : x.isRead)); const current = ctx.messages.find((x) => x.id === sel); const unread = ctx.messages.filter((x) => !x.isRead).length; function openMsg(id) { setSel(id); ctx.markRead(id); } return (
{["All", "Unread", "Read"].map((f) => )}
{list.map((x) => (
openMsg(x.id)} className="row gap12" style={{ padding: "18px 20px", cursor: "pointer", borderTop: "1px solid var(--line-2)", background: sel === x.id ? "var(--accent-soft)" : "transparent" }}> {initials(x.name)} {!x.isRead && }
{x.name}{String(x.createdAt).slice(5, 10)}
{x.subject} {x.body}
))} {list.length === 0 &&
Nothing here.
}
{current ? (
{initials(current.name)}

{current.name}

{current.email}
{fmtDate(String(current.createdAt).slice(0, 10))} · {String(current.createdAt).slice(11)}
{current.subject}

{current.body}