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
+22 -3
View File
@@ -43,12 +43,31 @@ export function useMe() {
});
}
export function useObjectsPage(limit: number, offset: number) {
export type ObjectListParams = {
limit: number;
offset: number;
sort?: string;
order?: "asc" | "desc";
visibility?: string;
q?: string;
};
export function useObjectsPage(params: ObjectListParams) {
return useQuery({
queryKey: ["objects", { limit, offset }],
queryKey: ["objects", params],
placeholderData: keepPreviousData,
queryFn: async () => {
const { data, error } = await api.GET("/api/admin/objects", {
params: { query: { limit, offset } },
params: {
query: {
limit: params.limit,
offset: params.offset,
sort: params.sort,
order: params.order,
visibility: params.visibility,
q: params.q,
},
},
});
if (error || !data) throw new Error("failed to load objects");