223 lines
7.5 KiB
TypeScript
223 lines
7.5 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { useRouter } from "next/navigation";
|
|
|
|
export type OverviewParams = {
|
|
filter: string;
|
|
q: string;
|
|
kat: string;
|
|
typ: string;
|
|
sort: string;
|
|
ansicht: string;
|
|
gruppe: string;
|
|
bestand: string;
|
|
householdName: string | null;
|
|
};
|
|
|
|
const FILTERS = [
|
|
{ key: "", label: "Alle" },
|
|
{ key: "aktiv", label: "Aktiv" },
|
|
{ key: "bald", label: "Bald ablaufend" },
|
|
{ key: "abgelaufen", label: "Abgelaufen" },
|
|
{ key: "eingeloest", label: "Eingelöst" },
|
|
] as const;
|
|
|
|
function toUrl(p: OverviewParams) {
|
|
const q = new URLSearchParams();
|
|
if (p.filter) q.set("filter", p.filter);
|
|
if (p.q) q.set("q", p.q);
|
|
if (p.kat) q.set("kat", p.kat);
|
|
if (p.typ) q.set("typ", p.typ);
|
|
if (p.sort !== "neu") q.set("sort", p.sort);
|
|
if (p.ansicht !== "liste") q.set("ansicht", p.ansicht);
|
|
if (p.gruppe !== "keine") q.set("gruppe", p.gruppe);
|
|
if (p.bestand && p.bestand !== "household") q.set("bestand", p.bestand);
|
|
const s = q.toString();
|
|
return s ? `/?${s}` : "/";
|
|
}
|
|
|
|
const selectClass =
|
|
"h-10 w-full rounded-field border border-line bg-white px-2.5 text-[13px] font-medium";
|
|
|
|
export default function OverviewToolbar({
|
|
current,
|
|
kategorien,
|
|
}: {
|
|
current: OverviewParams;
|
|
kategorien: string[];
|
|
}) {
|
|
const router = useRouter();
|
|
const [q, setQ] = useState(current.q);
|
|
|
|
useEffect(() => {
|
|
setQ(current.q);
|
|
}, [current.q]);
|
|
|
|
function update(overrides: Partial<OverviewParams>) {
|
|
router.replace(toUrl({ ...current, ...overrides }), { scroll: false });
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (q === current.q) return;
|
|
const t = setTimeout(() => update({ q }), 400);
|
|
return () => clearTimeout(t);
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [q]);
|
|
|
|
const bestandDefault = current.householdName !== null ? "household" : "private";
|
|
const hasActive =
|
|
current.filter !== "" ||
|
|
current.q !== "" ||
|
|
current.kat !== "" ||
|
|
current.typ !== "" ||
|
|
current.sort !== "neu" ||
|
|
current.gruppe !== "keine" ||
|
|
(current.bestand !== "" && current.bestand !== bestandDefault);
|
|
|
|
return (
|
|
<div className="mb-4 grid gap-4 rounded-card border border-line bg-white p-4">
|
|
{current.householdName !== null && (
|
|
<div className="flex flex-wrap gap-2" role="group" aria-label="Bestand">
|
|
{(
|
|
[
|
|
{ key: "household", label: current.householdName },
|
|
{ key: "private", label: "Privat" },
|
|
] as const
|
|
).map((b) => (
|
|
<button
|
|
key={b.key}
|
|
type="button"
|
|
onClick={() => update({ bestand: b.key })}
|
|
aria-current={current.bestand === b.key ? "page" : undefined}
|
|
className={`rounded-full border px-4 py-2 text-[13px] font-semibold transition-colors ${
|
|
current.bestand === b.key
|
|
? "border-brand-700 bg-brand-600 text-white"
|
|
: "border-line bg-white text-ink hover:border-brand-600 hover:text-brand-700"
|
|
}`}
|
|
>
|
|
{b.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
)}
|
|
<div>
|
|
<label htmlFor="q" className="sr-only">
|
|
Suchen
|
|
</label>
|
|
<input
|
|
id="q"
|
|
value={q}
|
|
onChange={(e) => setQ(e.target.value)}
|
|
placeholder="Name oder Anbieter suchen …"
|
|
autoComplete="off"
|
|
className="h-11 w-full rounded-field border border-line bg-white px-3.5 text-sm text-ink"
|
|
/>
|
|
</div>
|
|
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
|
|
<div>
|
|
<label htmlFor="kat" className="block text-[13px] font-semibold">
|
|
Kategorie
|
|
</label>
|
|
<select id="kat" value={current.kat} onChange={(e) => update({ kat: e.target.value })} className={`${selectClass} mt-1.5`}>
|
|
<option value="">Alle</option>
|
|
{kategorien.map((k) => (
|
|
<option key={k} value={k}>
|
|
{k}
|
|
</option>
|
|
))}
|
|
<option value="ohne">Ohne Kategorie</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label htmlFor="typ" className="block text-[13px] font-semibold">
|
|
Gutscheinart
|
|
</label>
|
|
<select id="typ" value={current.typ} onChange={(e) => update({ typ: e.target.value })} className={`${selectClass} mt-1.5`}>
|
|
<option value="">Alle</option>
|
|
<option value="euro">Geldwert</option>
|
|
<option value="prozent">Rabatt</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label htmlFor="sort" className="block text-[13px] font-semibold">
|
|
Sortierung
|
|
</label>
|
|
<select id="sort" value={current.sort} onChange={(e) => update({ sort: e.target.value })} className={`${selectClass} mt-1.5`}>
|
|
<option value="neu">Neueste zuerst</option>
|
|
<option value="ablauf">Ablaufdatum</option>
|
|
<option value="name">Name</option>
|
|
<option value="anbieter">Anbieter</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label htmlFor="gruppe" className="block text-[13px] font-semibold">
|
|
Gruppierung
|
|
</label>
|
|
<select id="gruppe" value={current.gruppe} onChange={(e) => update({ gruppe: e.target.value })} className={`${selectClass} mt-1.5`}>
|
|
<option value="keine">Keine</option>
|
|
<option value="anbieter">Anbieter</option>
|
|
<option value="kategorie">Kategorie</option>
|
|
<option value="ablauf">Ablauf</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-wrap items-center justify-between gap-2">
|
|
<nav className="flex flex-wrap gap-2" aria-label="Filter">
|
|
{FILTERS.map((f) => (
|
|
<button
|
|
key={f.key}
|
|
type="button"
|
|
onClick={() => update({ filter: f.key })}
|
|
aria-current={current.filter === f.key ? "page" : undefined}
|
|
className={`rounded-full border px-3.5 py-1.5 text-[13px] font-medium transition-colors ${
|
|
current.filter === f.key
|
|
? "border-brand-700 bg-brand-600 text-white"
|
|
: "border-line bg-white text-ink hover:border-brand-600 hover:text-brand-700"
|
|
}`}
|
|
>
|
|
{f.label}
|
|
</button>
|
|
))}
|
|
</nav>
|
|
<div className="flex gap-2" role="group" aria-label="Ansicht">
|
|
{(
|
|
[
|
|
{ key: "liste", label: "Liste" },
|
|
{ key: "kachel", label: "Kacheln" },
|
|
] as const
|
|
).map((a) => (
|
|
<button
|
|
key={a.key}
|
|
type="button"
|
|
onClick={() => update({ ansicht: a.key })}
|
|
aria-current={current.ansicht === a.key ? "page" : undefined}
|
|
className={`rounded-full border px-3.5 py-1.5 text-[13px] font-medium transition-colors ${
|
|
current.ansicht === a.key
|
|
? "border-brand-700 bg-brand-600 text-white"
|
|
: "border-line bg-white text-ink hover:border-brand-600 hover:text-brand-700"
|
|
}`}
|
|
>
|
|
{a.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
{hasActive && (
|
|
<div>
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
setQ("");
|
|
router.replace("/", { scroll: false });
|
|
}}
|
|
className="text-sm font-medium text-muted underline hover:text-ink"
|
|
>
|
|
Zurücksetzen
|
|
</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|