feat(web): full-width sortable/filterable objects table with URL state (#44)

Replace the narrow ObjectList with a full-width ObjectsTable whose state
(sort/order/q/visibility/limit/offset) lives entirely in the URL via
useSearchParams. Sortable headers toggle sort+dir with aria-sort, a
debounced quick-filter and visibility chips mirror the search-panel
pattern, and a pagination footer offers prev/next + page-size select.
Rows deep-link to /objects/:id preserving the query string.

useObjectsPage now takes an ObjectListParams object (sort/order/
visibility/q) with keepPreviousData. ObjectsPage renders the table as
the full-width landing view, surfacing the nested <Outlet/> detail as a
simple right-side panel only when a :id child route is active (Phase 3
makes this responsive). object-list.tsx and its test are removed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-06 23:34:13 +02:00
parent 98c00d3732
commit 49f694d1fb
10 changed files with 553 additions and 169 deletions
+16 -7
View File
@@ -1,16 +1,25 @@
import { Outlet } from "react-router-dom";
import { Outlet, useMatch } from "react-router-dom";
import { ObjectList } from "./object-list";
import { ObjectsTable } from "./objects-table";
export function ObjectsPage() {
// Interim layout (Phase 3 makes this a responsive pane/drawer): the table is the
// full-width landing view; when a `:id`/`:id/edit` child route is active we render
// the nested <Outlet/> as a simple right-side panel.
const detailMatch = useMatch("/objects/:id");
const editMatch = useMatch("/objects/:id/edit");
const detail = detailMatch ?? editMatch;
return (
<div className="grid h-full grid-cols-[20rem_1fr]">
<div className="overflow-hidden border-r">
<ObjectList />
</div>
<div className={`grid h-full ${detail ? "grid-cols-[1fr_28rem]" : "grid-cols-1"}`}>
<div className="overflow-hidden">
<Outlet />
<ObjectsTable />
</div>
{detail && (
<div className="overflow-auto border-l">
<Outlet />
</div>
)}
</div>
);
}