List Pages
Standard Layout
PageTitle → Card (toolbar) → Table → Pagination
URL-Synced Filters
const [searchParams, setSearchParams] = useSearchParams();
const filters = useMemo(() => ({
search: searchParams.get('search') ?? '',
page: Number(searchParams.get('page') ?? 1),
pageItemsCount: Number(searchParams.get('pageItemsCount') ?? 10),
status: searchParams.get('status') ?? undefined,
}), [searchParams]);
const updateFilters = (patch: Partial<Filters>) => {
const next = new URLSearchParams(searchParams);
Object.entries(patch).forEach(([k, v]) => {
if (v) next.set(k, String(v)); else next.delete(k);
});
if (!patch.page) next.set('page', '1'); // reset page on filter change
setSearchParams(next);
};
Shareable/bookmarkable admin URLs.
Debounced Search
const [searchInput, setSearchInput] = useState(filters.search);
const debouncedSearch = useDebounce(searchInput, 500);
useEffect(() => { updateFilters({ search: debouncedSearch }); }, [debouncedSearch]);
Table + Delete
const [deleteId, setDeleteId] = useState<string | null>(null);
<AlertDialog open={!!deleteId} => setDeleteId(null)}>
<AlertDialogAction => deleteMutation.mutate(deleteId!)}>Delete</AlertDialogAction>
</AlertDialog>
Bulk Select
Checkbox column + shift-click range select (products page pattern).
Images in Table
<img src={`${API_URL}${row.imageUrl}`} />
Deep-Link Modal
// ?orderId= opens OrderDetailsModal — useOrderDetailsDeepLink hook
Drag-and-Drop Sort
@dnd-kit on list pages — arrayMove → optimistic UI → sort API → revert on error.
Loading/Empty
Spinner while isLoading. Empty state message in Table body.