Skip to content

Commit 19aba65

Browse files
committed
Enhance data table functionality and configuration
- Updated VSCode settings to disable HTTP/2 for improved development experience. - Refactored data table filter stories to improve type imports and added new mock data for testing. - Revised pagination and sorting state management in DataTableWithBazzaFilters for better synchronization with URL parameters. - Enhanced DataTable component to accept a className prop for improved styling flexibility. - Updated data table filter logic to handle faceted options using Maps for better performance and clarity. - Added debug logging in useDataTableFilters for enhanced visibility during development.
1 parent 5d300f0 commit 19aba65

5 files changed

Lines changed: 210 additions & 75 deletions

File tree

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,6 @@
2222
"source.fixAll.biome": "explicit",
2323
"source.organizeImports.biome": "explicit"
2424
},
25-
"tailwindCSS.classAttributes": ["class", "className", "ngClass", "class:list", "wrapperClassName"]
25+
"tailwindCSS.classAttributes": ["class", "className", "ngClass", "class:list", "wrapperClassName"],
26+
"cursor.general.disableHttp2": true
2627
}

apps/docs/src/remix-hook-form/data-table-bazza-filters.stories.tsx

Lines changed: 159 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,19 @@ import { createColumnConfigHelper } from '@lambdacurry/forms/ui/data-table-filte
77
import { DataTable } from '@lambdacurry/forms/ui/data-table/data-table';
88
import { DataTableColumnHeader } from '@lambdacurry/forms/ui/data-table/data-table-column-header';
99
// Import the filters schema and types from the new location
10-
import { type Filter, type FiltersState, filtersArraySchema } from '@lambdacurry/forms/ui/utils/filters'; // Assuming path alias
10+
import type { Filter, FiltersState } from '@lambdacurry/forms/ui/utils/filters'; // Assuming path alias
11+
import { filtersArraySchema } from '@lambdacurry/forms/ui/utils/filters'; // Assuming path alias
1112
// --- Re-add useDataTableFilters import ---
1213
import { useDataTableFilters } from '@lambdacurry/forms/ui/utils/use-data-table-filters';
1314
import { useFilterSync } from '@lambdacurry/forms/ui/utils/use-filter-sync'; // Ensure this is the correct path for filter sync
1415
// Add icon imports
1516
import { CalendarIcon, CheckCircledIcon, PersonIcon, StarIcon, TextIcon } from '@radix-ui/react-icons';
16-
import type { Meta, StoryObj } from '@storybook/react';
17+
import type { Meta, StoryObj } from '@storybook/react'; // FIX: Add Meta, StoryObj
1718
import type { ColumnDef, PaginationState, SortingState } from '@tanstack/react-table'; // Added PaginationState, SortingState
1819
import { getCoreRowModel, getPaginationRowModel, getSortedRowModel, useReactTable } from '@tanstack/react-table';
1920
import { useEffect, useMemo, useState } from 'react'; // Added useState, useEffect
2021
import { type LoaderFunctionArgs, useLoaderData, useLocation, useNavigate } from 'react-router'; // Added LoaderFunctionArgs, useLoaderData, useNavigate, useLocation
21-
import { withReactRouterStubDecorator } from '../lib/storybook/react-router-stub';
22+
import { withReactRouterStubDecorator } from '../lib/storybook/react-router-stub'; // FIX: Add withReactRouterStubDecorator
2223

2324
// --- Use MockIssue Schema and Data ---
2425
interface MockIssue {
@@ -93,6 +94,79 @@ const mockDatabase: MockIssue[] = [
9394
priority: 'medium',
9495
createdDate: new Date('2024-03-01'),
9596
},
97+
{
98+
id: 'TASK-7',
99+
title: 'Design new landing page',
100+
status: 'todo',
101+
assignee: 'Alice',
102+
priority: 'high',
103+
createdDate: new Date('2024-03-05'),
104+
},
105+
{
106+
id: 'TASK-8',
107+
title: 'Write API integration tests',
108+
status: 'in progress',
109+
assignee: 'Bob',
110+
priority: 'medium',
111+
createdDate: new Date('2024-03-10'),
112+
},
113+
{
114+
id: 'TASK-9',
115+
title: 'Deploy to staging environment',
116+
status: 'todo',
117+
assignee: 'Charlie',
118+
priority: 'high',
119+
createdDate: new Date('2024-03-15'),
120+
},
121+
{
122+
id: 'TASK-10',
123+
title: 'User feedback session',
124+
status: 'done',
125+
assignee: 'Alice',
126+
priority: 'low',
127+
createdDate: new Date('2024-03-20'),
128+
},
129+
{
130+
id: 'TASK-11',
131+
title: 'Fix critical bug in payment module',
132+
status: 'in progress',
133+
assignee: 'Bob',
134+
priority: 'high',
135+
createdDate: new Date('2024-03-22'),
136+
},
137+
{
138+
id: 'TASK-12',
139+
title: 'Update third-party libraries',
140+
status: 'backlog',
141+
assignee: 'Charlie',
142+
priority: 'low',
143+
createdDate: new Date('2024-03-25'),
144+
},
145+
{
146+
id: 'TASK-13',
147+
title: 'Onboard new developer',
148+
status: 'done',
149+
assignee: 'Alice',
150+
priority: 'medium',
151+
createdDate: new Date('2024-04-01'),
152+
},
153+
{
154+
id: 'TASK-14',
155+
title: 'Research new caching strategy',
156+
status: 'todo',
157+
assignee: 'Bob',
158+
priority: 'medium',
159+
createdDate: new Date('2024-04-05'),
160+
},
161+
{
162+
id: 'TASK-15',
163+
title: 'Accessibility audit',
164+
status: 'in progress',
165+
assignee: 'Charlie',
166+
priority: 'high',
167+
createdDate: new Date('2024-04-10'),
168+
},
169+
// --- END ADDED DATA ---
96170
];
97171

98172
// --- Helper Functions (copied from deleted API route) ---
@@ -281,26 +355,42 @@ function DataTableWithBazzaFilters() {
281355
// Extract data and meta from loader, provide defaults
282356
const data = useMemo(() => loaderData?.data ?? [], [loaderData?.data]);
283357
const pageCount = useMemo(() => loaderData?.meta.pageCount ?? 0, [loaderData?.meta.pageCount]);
284-
const facetedCounts = useMemo(() => loaderData?.facetedCounts ?? {}, [loaderData?.facetedCounts]);
358+
359+
// NEW: Convert to Map of Maps
360+
const facetedCounts = useMemo(() => {
361+
const rawCounts = loaderData?.facetedCounts ?? {};
362+
const mapOfMaps = new Map<string, Map<string, number>>();
363+
for (const columnId in rawCounts) {
364+
const innerObject = rawCounts[columnId as keyof typeof rawCounts];
365+
const innerMap = new Map<string, number>();
366+
if (innerObject) {
367+
// Check if innerObject is not undefined
368+
for (const optionValue in innerObject) {
369+
innerMap.set(optionValue, innerObject[optionValue as keyof typeof innerObject]);
370+
}
371+
}
372+
mapOfMaps.set(columnId, innerMap);
373+
}
374+
return mapOfMaps;
375+
}, [loaderData?.facetedCounts]);
285376

286377
// Use filter sync hook (this manages filters in the URL)
287378
const [filters, setFilters] = useFilterSync();
288379

289-
// Initialize state from URL params (via loader meta) or defaults
290-
const initialPage = loaderData?.meta.page ?? dataTableRouterParsers.page.defaultValue;
291-
let initialPageSize = loaderData?.meta.pageSize ?? dataTableRouterParsers.pageSize.defaultValue;
380+
// --- REVISED PAGINATION STATE MANAGEMENT ---
381+
// Define defaults for pagination
382+
const defaultPageIndex = dataTableRouterParsers.page.defaultValue;
383+
const defaultPageSize = dataTableRouterParsers.pageSize.defaultValue;
292384

293-
// --- FIX: Ensure a valid default pageSize ---
294-
if (!initialPageSize || initialPageSize <= 0) {
295-
console.log(`[Loader] - Invalid or missing pageSize (${initialPageSize}), defaulting to 10.`);
296-
initialPageSize = 10; // Set a sensible default
297-
}
298-
// --- END FIX ---
385+
// Get current pagination values from URL (via loaderData) or use defaults
386+
const currentPageIndexFromUrl = loaderData?.meta.page ?? defaultPageIndex;
387+
const currentPageSizeFromUrl = loaderData?.meta.pageSize ?? defaultPageSize;
299388

300389
// Manage local pagination and sorting state
390+
// Initialize from URL-derived values
301391
const [pagination, setPagination] = useState<PaginationState>({
302-
pageIndex: initialPage,
303-
pageSize: initialPageSize,
392+
pageIndex: currentPageIndexFromUrl,
393+
pageSize: currentPageSizeFromUrl,
304394
});
305395

306396
// Initialize sorting state from URL params if they exist
@@ -311,29 +401,60 @@ function DataTableWithBazzaFilters() {
311401
return sortField ? [{ id: sortField, desc: sortDesc }] : [];
312402
});
313403

314-
// Effect to navigate when pagination or sorting changes locally
315-
// This triggers the loader to refetch data
404+
// Effect to synchronize pagination and sorting state FROM URL/loaderData if it changes
316405
useEffect(() => {
317-
const params = new URLSearchParams(location.search); // Start with current params
318-
params.set('page', String(pagination.pageIndex));
319-
params.set('pageSize', String(pagination.pageSize));
406+
const newPageIndex = loaderData?.meta.page ?? defaultPageIndex;
407+
const newPageSize = loaderData?.meta.pageSize ?? defaultPageSize;
320408

321-
if (sorting.length > 0) {
322-
params.set('sortField', sorting[0].id);
323-
params.set('sortDesc', String(sorting[0].desc));
324-
} else {
325-
params.delete('sortField');
326-
params.delete('sortDesc');
409+
if (pagination.pageIndex !== newPageIndex || pagination.pageSize !== newPageSize) {
410+
setPagination({ pageIndex: newPageIndex, pageSize: newPageSize });
411+
}
412+
413+
const params = new URLSearchParams(location.search);
414+
const sortFieldFromUrl = params.get('sortField');
415+
const sortDescFromUrl = params.get('sortDesc') === 'true';
416+
417+
const currentSorting = sorting.length > 0 ? sorting[0] : null;
418+
const urlHasSorting = !!sortFieldFromUrl;
419+
420+
if (urlHasSorting) {
421+
// Ensure sortFieldFromUrl is not null before using it with !
422+
if (
423+
sortFieldFromUrl &&
424+
(!currentSorting || currentSorting.id !== sortFieldFromUrl || currentSorting.desc !== sortDescFromUrl)
425+
) {
426+
setSorting([{ id: sortFieldFromUrl, desc: sortDescFromUrl }]);
427+
}
428+
} else if (currentSorting) {
429+
setSorting([]);
327430
}
431+
}, [loaderData, location.search, pagination, sorting, defaultPageIndex, defaultPageSize]);
432+
433+
// Handlers for pagination and sorting changes that navigate
434+
const handlePaginationChange = (updater: ((prevState: PaginationState) => PaginationState) | PaginationState) => {
435+
const newState = typeof updater === 'function' ? updater(pagination) : updater;
436+
const params = new URLSearchParams(location.search); // Preserve existing params like filters
437+
params.set('page', String(newState.pageIndex));
438+
params.set('pageSize', String(newState.pageSize));
439+
// Sorting is not changed by pagination, so it's already in location.search or not
440+
navigate(`${location.pathname}?${params.toString()}`, { replace: true });
441+
};
328442

329-
// Preserve filters from useFilterSync (which should already be in the URL)
330-
// No need to explicitly set 'filters' param here if useFilterSync handles it.
443+
const handleSortingChange = (updater: ((prevState: SortingState) => SortingState) | SortingState) => {
444+
const newState = typeof updater === 'function' ? updater(sorting) : updater;
445+
const params = new URLSearchParams(location.search); // Preserve existing params
331446

332-
// Only navigate if the search params actually changed
333-
if (params.toString() !== location.search.substring(1)) {
334-
navigate(`${location.pathname}?${params.toString()}`, { replace: true });
447+
if (newState.length > 0) {
448+
params.set('sortField', newState[0].id);
449+
params.set('sortDesc', String(newState[0].desc));
450+
} else {
451+
params.delete('sortField');
452+
params.delete('sortDesc');
335453
}
336-
}, [pagination, sorting, navigate, location.search, location.pathname]);
454+
// Optionally reset page to 0 on sort change
455+
// params.set('page', '0');
456+
navigate(`${location.pathname}?${params.toString()}`, { replace: true });
457+
};
337458

338459
// Use Bazza UI hook (strategy: 'server' means it expects externally filtered/faceted data)
339460
const {
@@ -349,21 +470,18 @@ function DataTableWithBazzaFilters() {
349470
onFiltersChange: setFilters, // Pass setter from useFilterSync
350471
});
351472

352-
// --- DEBUG LOG ---
353-
console.log('DataTable Component - bazzaProcessedColumns:', bazzaProcessedColumns);
354-
355473
// Setup TanStack Table instance
356474
const table = useReactTable({
357475
data,
358476
columns: columns, // <-- FIX: Use original columns for cell rendering
359477
state: {
360-
pagination, // Controlled pagination state
361-
sorting, // Controlled sorting state
478+
pagination, // Controlled by local state, which is synced from URL
479+
sorting, // Controlled by local state, which is synced from URL
362480
// columnFilters are implicitly handled by the loader via the 'filters' state
363481
},
364482
pageCount: pageCount, // Total pages from loader meta
365-
onPaginationChange: setPagination, // Update local pagination state
366-
onSortingChange: setSorting, // Update local sorting state
483+
onPaginationChange: handlePaginationChange, // Use new handler
484+
onSortingChange: handleSortingChange, // Use new handler
367485
manualPagination: true, // Pagination is handled by the loader
368486
manualFiltering: true, // Filtering is handled by the loader (triggered by filters state)
369487
manualSorting: true, // Sorting is handled by the loader
@@ -385,13 +503,8 @@ function DataTableWithBazzaFilters() {
385503

386504
{/* Render Bazza UI Filters - Pass Bazza's processed columns */}
387505
<DataTableFilter columns={bazzaProcessedColumns} filters={filters} actions={actions} strategy={strategy} />
388-
389-
{/* Render TanStack Table */}
390-
<div className="mt-4">
391-
{/* Pass table instance (which now uses original columns for rendering) */}
392-
<DataTable table={table} columns={columns.length} pagination />
393-
</div>
394-
{/* Remove isLoading check, loader handles loading state via router */}
506+
{/* Pass table instance (which now uses original columns for rendering) */}
507+
<DataTable className="mt-4" table={table} columns={columns.length} pagination />
395508
</div>
396509
);
397510
}

packages/components/src/ui/data-table-filter/components/data-table-filter.tsx

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Column, DataTableFilterActions, FilterOperators, FilterStrategy, FiltersState } from '../core/types';
1+
import type { Column, DataTableFilterActions, FilterStrategy, FiltersState } from '../core/types';
22
import type { Locale } from '../lib/i18n';
33
import { ActiveFilters, ActiveFiltersMobileContainer } from './active-filters';
44
import { FilterActions } from './filter-actions';
@@ -12,14 +12,6 @@ interface DataTableFilterProps<TData> {
1212
locale?: Locale;
1313
}
1414

15-
// Define default operators based on column type
16-
const defaultOperatorMap: Record<string, FilterOperators> = {
17-
option: 'is any of' as unknown as FilterOperators, // Use double assertion
18-
text: 'contains' as unknown as FilterOperators, // Use double assertion
19-
date: 'is' as unknown as FilterOperators, // Use double assertion
20-
number: 'eq' as unknown as FilterOperators, // Use double assertion
21-
};
22-
2315
export function DataTableFilter<TData>({
2416
columns,
2517
filters,

packages/components/src/ui/data-table/data-table.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { type Table as TableType, flexRender } from '@tanstack/react-table';
2+
import { cn } from '../../ui/utils';
23
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../table';
34
import { DataTablePagination } from './data-table-pagination';
45

@@ -8,6 +9,7 @@ interface DataTableProps<TData> {
89
pagination?: boolean;
910
onPaginationChange?: (pageIndex: number, pageSize: number) => void;
1011
pageCount?: number;
12+
className?: string;
1113
}
1214

1315
export function DataTable<TData>({
@@ -16,9 +18,10 @@ export function DataTable<TData>({
1618
pagination,
1719
onPaginationChange,
1820
pageCount = 1,
21+
className,
1922
}: DataTableProps<TData>) {
2023
return (
21-
<div className="space-y-4">
24+
<div className={cn('space-y-4', className)}>
2225
<div className="rounded-md border">
2326
<Table>
2427
<TableHeader>

0 commit comments

Comments
 (0)