From 2971fb298ee8a15756a2b36d14e4db5a934c892f Mon Sep 17 00:00:00 2001 From: Citali Date: Wed, 4 Feb 2026 12:39:25 +0100 Subject: [PATCH] Change artwork table to be persistent in its filters and sorting --- bun.lock | 15 +- package.json | 1 + src/components/artworks/ArtworksTable.tsx | 209 ++++++++++-------- src/components/artworks/MultiSelectFilter.tsx | 7 +- src/stores/artworksTableStore.ts | 74 +++++++ 5 files changed, 210 insertions(+), 96 deletions(-) create mode 100644 src/stores/artworksTableStore.ts diff --git a/bun.lock b/bun.lock index b694c4d..c37bc11 100644 --- a/bun.lock +++ b/bun.lock @@ -5,8 +5,8 @@ "": { "name": "admin.gaertan.art", "dependencies": { - "@aws-sdk/client-s3": "^3.974.0", - "@aws-sdk/s3-request-presigner": "^3.974.0", + "@aws-sdk/client-s3": "^3.980.0", + "@aws-sdk/s3-request-presigner": "^3.980.0", "@dnd-kit/core": "^6.3.1", "@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/sortable": "^10.0.0", @@ -39,7 +39,7 @@ "@radix-ui/react-tooltip": "^1.2.8", "@tanstack/react-table": "^8.21.3", "archiver": "^7.0.1", - "better-auth": "^1.4.17", + "better-auth": "^1.4.18", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", @@ -50,9 +50,9 @@ "lucide-react": "^0.561.0", "next": "16.1.6", "next-themes": "^0.4.6", - "node-vibrant": "^4.0.3", - "nodemailer": "^7.0.12", - "pg": "^8.17.2", + "node-vibrant": "^4.0.4", + "nodemailer": "^7.0.13", + "pg": "^8.18.0", "platejs": "^52.0.17", "react": "19.2.4", "react-day-picker": "^9.13.0", @@ -65,6 +65,7 @@ "tailwind-scrollbar-hide": "^4.0.0", "uuid": "^13.0.0", "zod": "^4.3.6", + "zustand": "^5.0.8", }, "devDependencies": { "@biomejs/biome": "2.2.0", @@ -73,7 +74,7 @@ "@types/culori": "^4.0.1", "@types/date-fns": "^2.6.3", "@types/node": "^20.19.30", - "@types/nodemailer": "^7.0.5", + "@types/nodemailer": "^7.0.9", "@types/pg": "^8.16.0", "@types/react": "19.2.10", "@types/react-dom": "19.2.3", diff --git a/package.json b/package.json index a450f66..fa253aa 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "tailwind-merge": "^3.4.0", "tailwind-scrollbar-hide": "^4.0.0", "uuid": "^13.0.0", + "zustand": "^5.0.8", "zod": "^4.3.6" }, "devDependencies": { diff --git a/src/components/artworks/ArtworksTable.tsx b/src/components/artworks/ArtworksTable.tsx index 467af5a..f90b854 100644 --- a/src/components/artworks/ArtworksTable.tsx +++ b/src/components/artworks/ArtworksTable.tsx @@ -5,8 +5,7 @@ import { type ColumnDef, flexRender, getCoreRowModel, - type SortingState, - useReactTable, + useReactTable } from "@tanstack/react-table"; import { ArrowUpDown, @@ -48,6 +47,7 @@ import { HoverCardTrigger, } from "@/components/ui/hover-card"; import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; import { Select, SelectContent, @@ -64,6 +64,7 @@ import { TableRow, } from "@/components/ui/table"; import type { ArtworkTableRow } from "@/schemas/artworks/tableSchema"; +import { useArtworksTableStore } from "@/stores/artworksTableStore"; import { MultiSelectFilter } from "./MultiSelectFilter"; // Client-side table for filtering, sorting, and managing artworks. @@ -141,6 +142,7 @@ function Chips(props: { items: { id: string; name: string }[]; max?: number }) { } function TriSelectInline(props: { + id?: string; value: TriState; onChange: (v: TriState) => void; }) { @@ -149,7 +151,7 @@ function TriSelectInline(props: { value={props.value} onValueChange={(v) => props.onChange(v as TriState)} > - + @@ -163,29 +165,23 @@ function TriSelectInline(props: { type Filters = { name: string; - slug: string; published: TriState; needsWork: TriState; categoryIds: string[]; }; export function ArtworksTable() { - const [sorting, setSorting] = useState([ - { id: "updatedAt", desc: true }, - ]); - const [pageIndex, setPageIndex] = useState(0); - const [pageSize, setPageSize] = useState(25); - - const [filters, setFilters] = useState({ - name: "", - slug: "", - published: "any", - needsWork: "any", - categoryIds: [], - }); + const sorting = useArtworksTableStore((s) => s.sorting); + const setSorting = useArtworksTableStore((s) => s.setSorting); + const pageIndex = useArtworksTableStore((s) => s.pageIndex); + const setPageIndex = useArtworksTableStore((s) => s.setPageIndex); + const pageSize = useArtworksTableStore((s) => s.pageSize); + const setPageSize = useArtworksTableStore((s) => s.setPageSize); + const filters = useArtworksTableStore((s) => s.filters) as Filters; + const setFilters = useArtworksTableStore((s) => s.setFilters); + const resetTable = useArtworksTableStore((s) => s.reset); const debouncedName = useDebouncedValue(filters.name, 300); - const debouncedSlug = useDebouncedValue(filters.slug, 300); const [rows, setRows] = useState([]); const [total, setTotal] = useState(0); @@ -401,9 +397,8 @@ export function ArtworksTable() { manualSorting: true, pageCount, onSortingChange: (updater) => { - setSorting((prev) => - typeof updater === "function" ? updater(prev) : updater, - ); + const next = typeof updater === "function" ? updater(sorting) : updater; + setSorting(next); setPageIndex(0); }, getCoreRowModel: getCoreRowModel(), @@ -416,7 +411,6 @@ export function ArtworksTable() { sorting, filters: { name: debouncedName || undefined, - slug: debouncedSlug || undefined, published: filters.published, needsWork: filters.needsWork, categoryIds: filters.categoryIds.length @@ -433,16 +427,120 @@ export function ArtworksTable() { pageSize, sorting, debouncedName, - debouncedSlug, filters.published, filters.needsWork, filters.categoryIds, ]); const headerGroup = table.getHeaderGroups()[0]; + const hasChanges = + filters.name || + filters.published !== "any" || + filters.needsWork !== "any" || + filters.categoryIds.length > 0 || + pageIndex !== 0 || + pageSize !== 25 || + sorting.length !== 1 || + sorting[0]?.id !== "updatedAt" || + sorting[0]?.desc !== true; return (
+
+
+
+ + { + setFilters((f) => ({ ...f, name: e.target.value })); + setPageIndex(0); + }} + /> +
+ +
+ +
+ { + setFilters((f) => ({ ...f, categoryIds: next })); + setPageIndex(0); + }} + /> +
+
+ +
+ +
+ { + setFilters((f) => ({ ...f, published: v })); + setPageIndex(0); + }} + /> +
+
+ +
+ +
+ { + setFilters((f) => ({ ...f, needsWork: v })); + setPageIndex(0); + }} + /> +
+
+ +
+ +
+
+
+
@@ -464,70 +562,6 @@ export function ArtworksTable() { ))} - - {/* filter row */} - - {headerGroup.headers.map((header) => { - const colId = header.column.id; - - return ( - - {colId === "name" ? ( - { - setFilters((f) => ({ ...f, name: e.target.value })); - setPageIndex(0); - }} - /> - ) : colId === "slug" ? ( - { - setFilters((f) => ({ ...f, slug: e.target.value })); - setPageIndex(0); - }} - /> - ) : colId === "published" ? ( - { - setFilters((f) => ({ ...f, published: v })); - setPageIndex(0); - }} - /> - ) : colId === "needsWork" ? ( - { - setFilters((f) => ({ ...f, needsWork: v })); - setPageIndex(0); - }} - /> - ) : colId === "categories" ? ( - { - setFilters((f) => ({ ...f, categoryIds: next })); - setPageIndex(0); - }} - /> - ) : ( -
- )} - - ); - })} - @@ -671,7 +705,6 @@ export function ArtworksTable() { sorting, filters: { name: debouncedName || undefined, - slug: debouncedSlug || undefined, published: filters.published, needsWork: filters.needsWork, categoryIds: filters.categoryIds.length diff --git a/src/components/artworks/MultiSelectFilter.tsx b/src/components/artworks/MultiSelectFilter.tsx index ba00011..12db43b 100644 --- a/src/components/artworks/MultiSelectFilter.tsx +++ b/src/components/artworks/MultiSelectFilter.tsx @@ -11,6 +11,7 @@ type Option = { id: string; name: string }; // Simple multi-select filter control for artwork filters. export function MultiSelectFilter(props: { + id?: string; placeholder: string; options: Option[]; value: string[]; // selected ids @@ -21,7 +22,11 @@ export function MultiSelectFilter(props: { return ( -