feat(admin): add OrdersFilterButton (status filter via native dialog)
This commit is contained in:
@@ -0,0 +1,114 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter button for the Orders list. Opens a native <dialog> as a
|
||||||
|
* right-edge sheet (matching the MoreSheet pattern) with status options.
|
||||||
|
* Picking a status pushes a new URL with the `status` search param —
|
||||||
|
* the server component re-renders with the filter applied.
|
||||||
|
*
|
||||||
|
* The status values here are the v2 StatusPill vocabulary
|
||||||
|
* (placed/ready/picked-up/cancelled) which is what the orders page
|
||||||
|
* filters on. Empty string = clear filter (all orders).
|
||||||
|
*/
|
||||||
|
export function OrdersFilterButton() {
|
||||||
|
const router = useRouter();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const dialogRef = useRef<HTMLDialogElement>(null);
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const dialog = dialogRef.current;
|
||||||
|
if (!dialog) return;
|
||||||
|
if (open && !dialog.open) dialog.showModal();
|
||||||
|
if (!open && dialog.open) dialog.close();
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
// Sync the dialog's open state when the user closes via Escape
|
||||||
|
// (native <dialog> behavior — fires a "close" event, not a React event).
|
||||||
|
useEffect(() => {
|
||||||
|
const dialog = dialogRef.current;
|
||||||
|
if (!dialog) return;
|
||||||
|
const onClose = () => setOpen(false);
|
||||||
|
dialog.addEventListener("close", onClose);
|
||||||
|
return () => dialog.removeEventListener("close", onClose);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const currentStatus = searchParams.get("status") ?? "";
|
||||||
|
|
||||||
|
function apply(status: string) {
|
||||||
|
const params = new URLSearchParams(searchParams.toString());
|
||||||
|
if (status) params.set("status", status);
|
||||||
|
else params.delete("status");
|
||||||
|
router.push(`/admin/v2/orders?${params.toString()}`);
|
||||||
|
setOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setOpen(true)}
|
||||||
|
aria-label="Filter orders"
|
||||||
|
className="rounded-xl flex items-center gap-2"
|
||||||
|
style={{
|
||||||
|
minHeight: "56px",
|
||||||
|
padding: "0 16px",
|
||||||
|
backgroundColor: "var(--color-surface-2)",
|
||||||
|
color: "var(--color-text)",
|
||||||
|
fontWeight: 600,
|
||||||
|
letterSpacing: "0.02em",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2}>
|
||||||
|
<path d="M3 6h18M6 12h12M10 18h4" strokeLinecap="round" />
|
||||||
|
</svg>
|
||||||
|
Filter
|
||||||
|
</button>
|
||||||
|
<dialog
|
||||||
|
ref={dialogRef}
|
||||||
|
onClick={(e) => { if (e.target === dialogRef.current) setOpen(false); }}
|
||||||
|
className="w-full max-w-[640px] p-0 m-0 ml-auto h-full max-h-screen backdrop:bg-black/40"
|
||||||
|
style={{ backgroundColor: "var(--color-bg)", color: "var(--color-text)" }}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col h-full overflow-y-auto">
|
||||||
|
<div
|
||||||
|
className="sticky top-0 px-6 py-4 border-b flex items-center justify-between"
|
||||||
|
style={{ backgroundColor: "var(--color-bg)", borderColor: "var(--color-surface-3)" }}
|
||||||
|
>
|
||||||
|
<h2 className="text-h1 font-display" style={{ fontWeight: 600 }}>Filter</h2>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setOpen(false)}
|
||||||
|
aria-label="Close"
|
||||||
|
className="p-2"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="px-6 py-6 space-y-3">
|
||||||
|
{["", "placed", "ready", "picked-up", "cancelled"].map((s) => (
|
||||||
|
<button
|
||||||
|
key={s || "all"}
|
||||||
|
type="button"
|
||||||
|
onClick={() => apply(s)}
|
||||||
|
className="w-full text-left rounded-xl px-4 py-4"
|
||||||
|
style={{
|
||||||
|
minHeight: "56px",
|
||||||
|
backgroundColor: currentStatus === s ? "var(--color-accent-soft)" : "var(--color-surface-2)",
|
||||||
|
fontWeight: 600,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{s ? s.charAt(0).toUpperCase() + s.slice(1) : "All orders"}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OrdersFilterButton;
|
||||||
Reference in New Issue
Block a user