From fa613dd1c3382565cd18de03b96818e1491e12d5 Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Thu, 30 Apr 2026 20:00:23 +0800 Subject: [PATCH] Create saved filters component --- components/saved-filters.tsx | 129 +++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 components/saved-filters.tsx diff --git a/components/saved-filters.tsx b/components/saved-filters.tsx new file mode 100644 index 0000000..d7d5356 --- /dev/null +++ b/components/saved-filters.tsx @@ -0,0 +1,129 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { Bookmark, Plus, Trash2, Check } from "lucide-react"; +import { cn } from "@/lib/utils"; + +interface SavedFilter { + id: string; + name: string; + filters: Record; +} + +interface SavedFiltersProps { + currentFilters: Record; + onLoad: (filters: Record) => void; + className?: string; +} + +export function SavedFilters({ currentFilters, onLoad, className }: SavedFiltersProps) { + const [saved, setSaved] = useState([]); + const [showSave, setShowSave] = useState(false); + const [filterName, setFilterName] = useState(""); + const [loading, setLoading] = useState(true); + + const fetchSaved = async () => { + try { + const res = await fetch("/api/saved-filters"); + if (res.ok) setSaved(await res.json()); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchSaved(); + }, []); + + const saveCurrent = async () => { + if (!filterName.trim()) return; + + const res = await fetch("/api/saved-filters", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ name: filterName.trim(), filters: currentFilters }), + }); + + if (res.ok) { + setFilterName(""); + setShowSave(false); + fetchSaved(); + } + }; + + const deleteFilter = async (id: string) => { + await fetch(`/api/saved-filters/${id}`, { method: "DELETE" }); + setSaved((prev) => prev.filter((f) => f.id !== id)); + }; + + const hasActiveFilters = Object.values(currentFilters).some( + (v) => v !== undefined && v !== "" && v !== "any" + ); + + return ( +
+
+ + + Saved Filters + + {hasActiveFilters && ( + + )} +
+ + {showSave && ( +
+ setFilterName(e.target.value)} + placeholder="Filter name..." + className="flex-1 rounded-md border border-zinc-700 bg-zinc-800 px-2 py-1 text-sm" + onKeyDown={(e) => e.key === "Enter" && saveCurrent()} + /> + +
+ )} + + {!loading && saved.length > 0 && ( +
+ {saved.map((filter) => ( +
+ + +
+ ))} +
+ )} + + {!loading && saved.length === 0 && ( +

No saved filters yet

+ )} +
+ ); +}