@jmruthers/pace-core 0.6.6 → 0.6.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{scripts/audit/audit-dependencies.cjs → audit-tool/00-dependencies.cjs} +12 -13
- package/audit-tool/audits/01-pace-core-compliance.cjs +556 -0
- package/audit-tool/audits/02-project-structure.cjs +255 -0
- package/audit-tool/audits/03-architecture.cjs +196 -0
- package/audit-tool/audits/04-code-quality.cjs +149 -0
- package/audit-tool/audits/05-styling.cjs +224 -0
- package/audit-tool/audits/06-security-rbac.cjs +544 -0
- package/audit-tool/audits/07-api-tech-stack.cjs +301 -0
- package/audit-tool/audits/08-testing-documentation.cjs +202 -0
- package/audit-tool/audits/09-operations.cjs +208 -0
- package/audit-tool/index.cjs +291 -0
- package/audit-tool/utils/code-utils.cjs +218 -0
- package/audit-tool/utils/file-utils.cjs +230 -0
- package/audit-tool/utils/report-utils.cjs +241 -0
- package/cursor-rules/00-standards-overview.mdc +156 -0
- package/cursor-rules/{00-pace-core-compliance.mdc → 01-pace-core-compliance.mdc} +187 -34
- package/cursor-rules/02-project-structure.mdc +37 -5
- package/cursor-rules/{03-solid-principles.mdc → 03-architecture.mdc} +125 -11
- package/cursor-rules/04-code-quality.mdc +419 -0
- package/cursor-rules/{08-markup-quality.mdc → 05-styling.mdc} +55 -10
- package/cursor-rules/{09-rbac-compliance.mdc → 06-security-rbac.mdc} +62 -6
- package/cursor-rules/07-api-tech-stack.mdc +377 -0
- package/cursor-rules/08-testing-documentation.mdc +324 -0
- package/cursor-rules/09-operations.mdc +365 -0
- package/dist/DataTable-7PMH7XN7.js +15 -0
- package/dist/{DataTable-2N_tqbfq.d.ts → DataTable-DRUIgtUH.d.ts} +1 -1
- package/dist/{PublicPageProvider-BBH6Vqg7.d.ts → PublicPageProvider-DlsCaR5v.d.ts} +26 -16
- package/dist/{chunk-FENMYN2U.js → chunk-5X4QLXRG.js} +1 -3
- package/dist/{chunk-4T7OBVTU.js → chunk-6F3IILHI.js} +1 -1
- package/dist/{chunk-SD6WQY43.js → chunk-7ILTDCL2.js} +9 -1
- package/dist/{chunk-3QC3KRHK.js → chunk-A3W6LW53.js} +16 -1
- package/dist/{chunk-7TYHROIV.js → chunk-BM4CQ5P3.js} +50 -8
- package/dist/{chunk-2HGJFNAH.js → chunk-FEJLJNWA.js} +1 -15
- package/dist/{chunk-OHIK3MIO.js → chunk-GHYHJTYV.js} +2 -2
- package/dist/{chunk-UIYSCEV7.js → chunk-IUBRCBSY.js} +1 -1
- package/dist/{chunk-LAZMKTTF.js → chunk-JGWDVX64.js} +281 -347
- package/dist/{chunk-MAGBIDNS.js → chunk-L4XMVJKY.js} +2 -2
- package/dist/{chunk-A55DK444.js → chunk-OJ4SKRSV.js} +1 -7
- package/dist/{chunk-ZS5VO5JB.js → chunk-Q7Q7V5NV.js} +406 -451
- package/dist/{chunk-3O3WHILE.js → chunk-VBCS3DUA.js} +236 -60
- package/dist/{chunk-BVP2BCJF.js → chunk-ZKAWKYT4.js} +8 -8
- package/dist/components.d.ts +5 -4
- package/dist/components.js +27 -32
- package/dist/eslint-rules/index.cjs +22 -9
- package/{src/eslint-rules/rules/compliance.cjs → dist/eslint-rules/rules/01-pace-core-compliance.cjs} +184 -23
- package/dist/eslint-rules/rules/04-code-quality.cjs +290 -0
- package/dist/eslint-rules/rules/05-styling.cjs +61 -0
- package/dist/eslint-rules/rules/{rbac.cjs → 06-security-rbac.cjs} +26 -10
- package/dist/eslint-rules/rules/07-api-tech-stack.cjs +263 -0
- package/dist/eslint-rules/rules/08-testing.cjs +94 -0
- package/dist/hooks.d.ts +5 -5
- package/dist/hooks.js +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +18 -17
- package/dist/rbac/index.js +6 -6
- package/dist/theming/runtime.d.ts +14 -1
- package/dist/theming/runtime.js +1 -1
- package/dist/{types-B-K_5VnO.d.ts → types-DXstZpNI.d.ts} +0 -17
- package/dist/{usePublicRouteParams-COZ28Mvq.d.ts → usePublicRouteParams-MamNgwqe.d.ts} +19 -19
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +8 -8
- package/docs/README.md +1 -1
- package/docs/api/modules.md +47 -31
- package/docs/api-reference/components.md +18 -20
- package/docs/api-reference/hooks.md +80 -80
- package/docs/api-reference/types.md +1 -1
- package/docs/api-reference/utilities.md +1 -1
- package/docs/architecture/README.md +1 -1
- package/docs/core-concepts/events.md +3 -3
- package/docs/core-concepts/organisations.md +6 -6
- package/docs/core-concepts/permissions.md +6 -6
- package/docs/documentation-index.md +12 -18
- package/docs/getting-started/documentation-index.md +1 -1
- package/docs/getting-started/examples/README.md +4 -4
- package/docs/getting-started/examples/full-featured-app.md +1 -1
- package/docs/getting-started/faq.md +2 -2
- package/docs/getting-started/quick-reference.md +4 -4
- package/docs/implementation-guides/authentication.md +15 -15
- package/docs/implementation-guides/component-styling.md +1 -1
- package/docs/implementation-guides/data-tables.md +126 -33
- package/docs/implementation-guides/datatable-rbac-usage.md +1 -1
- package/docs/implementation-guides/dynamic-colors.md +3 -3
- package/docs/implementation-guides/file-upload-storage.md +2 -2
- package/docs/implementation-guides/hierarchical-datatable.md +40 -60
- package/docs/implementation-guides/inactivity-tracking.md +3 -3
- package/docs/implementation-guides/large-datasets.md +3 -2
- package/docs/implementation-guides/organisation-security.md +2 -2
- package/docs/implementation-guides/performance.md +2 -2
- package/docs/implementation-guides/permission-enforcement.md +1 -1
- package/docs/migration/V0.3.44_organisation-context-timing-fix.md +1 -1
- package/docs/migration/V0.4.0_rbac-migration.md +6 -6
- package/docs/rbac/README.md +5 -5
- package/docs/rbac/advanced-patterns.md +6 -6
- package/docs/rbac/api-reference.md +20 -20
- package/docs/rbac/event-based-apps.md +3 -3
- package/docs/rbac/examples.md +41 -41
- package/docs/rbac/getting-started.md +37 -37
- package/docs/rbac/performance.md +1 -1
- package/docs/rbac/quick-start.md +52 -52
- package/docs/rbac/secure-client-protection.md +1 -1
- package/docs/rbac/troubleshooting.md +1 -1
- package/docs/security/README.md +5 -5
- package/docs/standards/0-standards-overview.md +220 -0
- package/docs/standards/{00-pace-core-compliance.md → 1-pace-core-compliance-standards.md} +204 -185
- package/docs/standards/{02-project-structure.md → 2-project-structure-standards.md} +11 -47
- package/docs/standards/3-architecture-standards.md +606 -0
- package/docs/standards/4-code-quality-standards.md +728 -0
- package/docs/standards/{08-markup-quality.md → 5-styling-standards.md} +12 -9
- package/docs/standards/{09-rbac-compliance.md → 6-security-rbac-standards.md} +126 -18
- package/docs/standards/7-api-tech-stack-standards.md +662 -0
- package/docs/standards/8-testing-documentation-standards.md +401 -0
- package/docs/standards/9-operations-standards.md +1102 -0
- package/docs/standards/README.md +203 -104
- package/docs/troubleshooting/README.md +4 -4
- package/docs/troubleshooting/common-issues.md +2 -2
- package/docs/troubleshooting/debugging.md +9 -9
- package/docs/troubleshooting/migration.md +4 -4
- package/eslint-config-pace-core.cjs +21 -10
- package/package.json +6 -5
- package/scripts/install-cursor-rules.cjs +11 -243
- package/scripts/install-eslint-config.cjs +284 -0
- package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +2 -2
- package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -2
- package/src/__tests__/helpers/__tests__/test-utils.test.tsx +10 -10
- package/src/__tests__/integration/UserProfile.test.tsx +14 -14
- package/src/__tests__/rbac/PagePermissionGuard.test.tsx +6 -6
- package/src/__tests__/templates/accessibility.test.template.tsx +9 -9
- package/src/__tests__/templates/component.test.template.tsx +18 -15
- package/src/components/Calendar/Calendar.tsx +201 -47
- package/src/components/ContextSelector/ContextSelector.tsx +137 -153
- package/src/components/DataTable/AUDIT_REPORT.md +293 -0
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +10 -2
- package/src/components/DataTable/__tests__/a11y.basic.test.tsx +10 -4
- package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +9 -9
- package/src/components/DataTable/components/ColumnFilter.tsx +63 -74
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +43 -41
- package/src/components/DataTable/components/DataTableErrorBoundary.tsx +9 -11
- package/src/components/DataTable/components/DataTableLayout.tsx +5 -16
- package/src/components/DataTable/components/EditableRow.tsx +5 -7
- package/src/components/DataTable/components/EmptyState.tsx +10 -9
- package/src/components/DataTable/components/FilterRow.tsx +2 -4
- package/src/components/DataTable/components/ImportModal.tsx +124 -126
- package/src/components/DataTable/components/LoadingState.tsx +5 -6
- package/src/components/DataTable/components/SortIndicator.tsx +50 -0
- package/src/components/DataTable/components/__tests__/COVERAGE_NOTE.md +4 -4
- package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +23 -82
- package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +37 -9
- package/src/components/DataTable/components/__tests__/EmptyState.test.tsx +7 -4
- package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +12 -4
- package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +41 -27
- package/src/components/DataTable/components/index.ts +2 -1
- package/src/components/DataTable/types.ts +0 -18
- package/src/components/DataTable/utils/a11yUtils.ts +17 -0
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +2 -1
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +11 -15
- package/src/components/DateTimeField/DateTimeField.tsx +7 -8
- package/src/components/Dialog/Dialog.test.tsx +1 -0
- package/src/components/Dialog/Dialog.tsx +25 -8
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +77 -79
- package/src/components/FileUpload/FileUpload.test.tsx +52 -14
- package/src/components/FileUpload/FileUpload.tsx +112 -130
- package/src/components/Progress/Progress.tsx +2 -4
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +8 -8
- package/src/components/Select/Select.tsx +86 -77
- package/src/components/Select/types.ts +3 -0
- package/src/hooks/__tests__/ServiceHooks.test.tsx +16 -16
- package/src/hooks/__tests__/hooks.integration.test.tsx +49 -49
- package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +97 -97
- package/src/hooks/public/usePublicEvent.ts +5 -5
- package/src/hooks/public/usePublicEventLogo.ts +5 -5
- package/src/hooks/public/usePublicFileDisplay.ts +2 -2
- package/src/hooks/public/usePublicRouteParams.ts +5 -5
- package/src/hooks/useAppConfig.ts +2 -2
- package/src/hooks/useEventTheme.test.ts +7 -7
- package/src/hooks/useEventTheme.ts +1 -4
- package/src/hooks/useFileDisplay.ts +2 -2
- package/src/providers/UnifiedAuthProvider.smoke.test.tsx +21 -21
- package/src/providers/__tests__/AuthProvider.test.tsx +21 -21
- package/src/providers/__tests__/EventProvider.test.tsx +61 -61
- package/src/providers/__tests__/InactivityProvider.test.tsx +56 -56
- package/src/providers/__tests__/OrganisationProvider.test.tsx +75 -75
- package/src/providers/__tests__/ProviderLifecycle.test.tsx +37 -37
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +103 -103
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +7 -7
- package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +10 -10
- package/src/styles/core.css +7 -0
- package/src/theming/__tests__/parseEventColours.test.ts +9 -3
- package/src/theming/parseEventColours.ts +22 -10
- package/src/utils/__tests__/lazyLoad.unit.test.tsx +42 -39
- package/src/utils/storage/README.md +1 -1
- package/cursor-rules/01-standards-compliance.mdc +0 -285
- package/cursor-rules/04-testing-standards.mdc +0 -270
- package/cursor-rules/05-bug-reports-and-features.mdc +0 -248
- package/cursor-rules/06-code-quality.mdc +0 -311
- package/cursor-rules/07-tech-stack-compliance.mdc +0 -216
- package/cursor-rules/10-error-handling-patterns.mdc +0 -179
- package/cursor-rules/11-performance-optimization.mdc +0 -169
- package/cursor-rules/12-ci-cd-integration.mdc +0 -150
- package/dist/DataTable-LRJL4IRV.js +0 -15
- package/dist/eslint-rules/rules/compliance.cjs +0 -348
- package/dist/eslint-rules/rules/components.cjs +0 -113
- package/dist/eslint-rules/rules/imports.cjs +0 -102
- package/docs/best-practices/README.md +0 -472
- package/docs/best-practices/accessibility.md +0 -604
- package/docs/best-practices/common-patterns.md +0 -516
- package/docs/best-practices/deployment.md +0 -1103
- package/docs/best-practices/performance.md +0 -1328
- package/docs/best-practices/security.md +0 -940
- package/docs/best-practices/testing.md +0 -1034
- package/docs/rbac/compliance/compliance-guide.md +0 -544
- package/docs/standards/01-standards-compliance.md +0 -188
- package/docs/standards/03-solid-principles.md +0 -39
- package/docs/standards/04-testing-standards.md +0 -36
- package/docs/standards/05-bug-reports-and-features.md +0 -27
- package/docs/standards/06-code-quality.md +0 -34
- package/docs/standards/07-tech-stack-compliance.md +0 -30
- package/docs/standards/10-error-handling-patterns.md +0 -401
- package/docs/standards/11-performance-optimization.md +0 -348
- package/docs/standards/12-ci-cd-integration.md +0 -370
- package/docs/standards/ALIGNMENT_REVIEW_SUMMARY.md +0 -192
- package/scripts/audit/audit-compliance.cjs +0 -1295
- package/scripts/audit/audit-components.cjs +0 -260
- package/scripts/audit/audit-rbac.cjs +0 -954
- package/scripts/audit/audit-standards.cjs +0 -1268
- package/scripts/audit/index.cjs +0 -1927
- package/src/components/DataTable/components/DataTableBody.tsx +0 -478
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +0 -156
- package/src/components/DataTable/components/ExpandButton.tsx +0 -113
- package/src/components/DataTable/components/GroupHeader.tsx +0 -54
- package/src/components/DataTable/components/ViewRowModal.tsx +0 -68
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +0 -525
- package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +0 -462
- package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +0 -393
- package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +0 -476
- package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +0 -128
- package/src/components/DataTable/core/DataTableContext.tsx +0 -216
- package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +0 -136
- package/src/components/DataTable/hooks/__tests__/useColumnReordering.test.ts +0 -570
- package/src/components/DataTable/hooks/useColumnReordering.ts +0 -123
- package/src/components/DataTable/utils/debugTools.ts +0 -514
- package/src/eslint-rules/index.cjs +0 -22
- package/src/eslint-rules/rules/components.cjs +0 -113
- package/src/eslint-rules/rules/imports.cjs +0 -102
- package/src/eslint-rules/rules/rbac.cjs +0 -790
- package/src/eslint-rules/utils/helpers.cjs +0 -42
- package/src/eslint-rules/utils/manifest-loader.cjs +0 -75
|
@@ -1,525 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file Virtualized DataTable Component
|
|
3
|
-
* @package @jmruthers/pace-core
|
|
4
|
-
* @module Components/DataTable/VirtualizedDataTable
|
|
5
|
-
* @since 0.3.0
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import React, { useMemo, useRef, useCallback, memo, useLayoutEffect, useState } from 'react';
|
|
9
|
-
import { useVirtualizer } from '@tanstack/react-virtual';
|
|
10
|
-
import { flexRender, type Table as TanStackTable, type Cell, type Row } from '@tanstack/react-table';
|
|
11
|
-
import { cn } from '../../../utils/core/cn';
|
|
12
|
-
import type { DataRecord } from '../types';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Props for the VirtualizedDataTable component.
|
|
16
|
-
* @template TData - The type of data records in the table
|
|
17
|
-
*/
|
|
18
|
-
export interface VirtualizedDataTableProps<TData extends DataRecord> {
|
|
19
|
-
table: TanStackTable<TData>;
|
|
20
|
-
height?: number;
|
|
21
|
-
overscan?: number;
|
|
22
|
-
className?: string;
|
|
23
|
-
onVisibilityChange?: (visibleRange: { start: number; end: number }) => void;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Memoized cell component for performance
|
|
28
|
-
*/
|
|
29
|
-
const MemoizedCell = memo(<TData extends DataRecord>({ cell, style }: { cell: Cell<TData, unknown>; style?: React.CSSProperties }) => {
|
|
30
|
-
return (
|
|
31
|
-
<td
|
|
32
|
-
key={cell.id}
|
|
33
|
-
className={cn(
|
|
34
|
-
"px-4 py-2 text-sm border-b border-gray-200 overflow-hidden",
|
|
35
|
-
cell.column?.getCanSort && cell.column.getCanSort() && "cursor-pointer select-none"
|
|
36
|
-
)}
|
|
37
|
-
style={style}
|
|
38
|
-
>
|
|
39
|
-
{flexRender(cell.column?.columnDef?.cell, cell.getContext?.() || {})}
|
|
40
|
-
</td>
|
|
41
|
-
);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
MemoizedCell.displayName = 'MemoizedCell';
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Memoized row component for performance
|
|
48
|
-
*/
|
|
49
|
-
const MemoizedRow = memo(({ row, style }: {
|
|
50
|
-
row: Row<DataRecord>;
|
|
51
|
-
style: React.CSSProperties;
|
|
52
|
-
}) => {
|
|
53
|
-
return (
|
|
54
|
-
<tr
|
|
55
|
-
key={row.id}
|
|
56
|
-
className={cn(
|
|
57
|
-
"hover:bg-app-sec-50 transition-colors",
|
|
58
|
-
row.getIsSelected && row.getIsSelected() && "bg-app-main-50"
|
|
59
|
-
)}
|
|
60
|
-
style={style}
|
|
61
|
-
data-testid={`data-table-row-${row.id}`}
|
|
62
|
-
>
|
|
63
|
-
{row.getVisibleCells && row.getVisibleCells().map((cell) => (
|
|
64
|
-
<MemoizedCell
|
|
65
|
-
key={cell.id}
|
|
66
|
-
cell={cell as unknown as Cell<DataRecord, unknown>}
|
|
67
|
-
style={{}}
|
|
68
|
-
/>
|
|
69
|
-
))}
|
|
70
|
-
</tr>
|
|
71
|
-
);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
MemoizedRow.displayName = 'MemoizedRow';
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* High-performance virtualized DataTable component with fixed column alignment
|
|
78
|
-
*/
|
|
79
|
-
export function VirtualizedDataTable<TData extends DataRecord>({
|
|
80
|
-
table,
|
|
81
|
-
height = 400,
|
|
82
|
-
overscan = 5,
|
|
83
|
-
className,
|
|
84
|
-
onVisibilityChange
|
|
85
|
-
}: VirtualizedDataTableProps<TData>) {
|
|
86
|
-
const parentRef = useRef<HTMLDivElement>(null);
|
|
87
|
-
const headerRef = useRef<HTMLTableElement>(null);
|
|
88
|
-
const bodyRef = useRef<HTMLTableElement>(null);
|
|
89
|
-
const rows = table.getRowModel().rows;
|
|
90
|
-
|
|
91
|
-
// Virtual scrolling setup
|
|
92
|
-
const virtualizer = useVirtualizer({
|
|
93
|
-
count: rows.length,
|
|
94
|
-
getScrollElement: () => parentRef.current,
|
|
95
|
-
estimateSize: () => 40, // Estimated row height
|
|
96
|
-
overscan,
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
const virtualRows = virtualizer.getVirtualItems();
|
|
100
|
-
|
|
101
|
-
// Get table headers
|
|
102
|
-
const headerGroups = table.getHeaderGroups();
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
// Notify parent of visibility changes
|
|
106
|
-
const handleVisibilityChange = useCallback(() => {
|
|
107
|
-
if (virtualRows.length > 0 && onVisibilityChange) {
|
|
108
|
-
const start = virtualRows[0].index;
|
|
109
|
-
const end = virtualRows[virtualRows.length - 1].index;
|
|
110
|
-
onVisibilityChange({ start, end });
|
|
111
|
-
}
|
|
112
|
-
}, [virtualRows, onVisibilityChange]);
|
|
113
|
-
|
|
114
|
-
// Call visibility change handler when virtual rows change
|
|
115
|
-
React.useEffect(() => {
|
|
116
|
-
handleVisibilityChange();
|
|
117
|
-
}, [handleVisibilityChange]);
|
|
118
|
-
|
|
119
|
-
// Calculate total size for proper scrollbar
|
|
120
|
-
const totalSize = virtualizer.getTotalSize();
|
|
121
|
-
|
|
122
|
-
// Handle empty state
|
|
123
|
-
if (rows.length === 0) {
|
|
124
|
-
return (
|
|
125
|
-
<div className={cn("border rounded-lg overflow-hidden", className)}>
|
|
126
|
-
{/* Fixed Header */}
|
|
127
|
-
<div className="bg-app-sec-50 border-b">
|
|
128
|
-
<table ref={headerRef} className="w-full table-fixed">
|
|
129
|
-
<thead>
|
|
130
|
-
{headerGroups.map((headerGroup) => (
|
|
131
|
-
<tr key={headerGroup.id}>
|
|
132
|
-
{headerGroup.headers.map((header) => (
|
|
133
|
-
<th
|
|
134
|
-
key={header.id}
|
|
135
|
-
className={cn(
|
|
136
|
-
"px-4 py-3 text-left text-xs font-medium text-app-sec-500 uppercase tracking-wider",
|
|
137
|
-
header.column?.getCanSort && header.column.getCanSort() && "cursor-pointer select-none hover:bg-app-sec-100"
|
|
138
|
-
)}
|
|
139
|
-
style={{}}
|
|
140
|
-
onClick={header.column?.getToggleSortingHandler ? header.column.getToggleSortingHandler() : undefined}
|
|
141
|
-
>
|
|
142
|
-
<div className="flex items-center space-x-1">
|
|
143
|
-
{header.isPlaceholder
|
|
144
|
-
? null
|
|
145
|
-
: flexRender(header.column?.columnDef?.header, header.getContext?.() || {})}
|
|
146
|
-
{header.column?.getCanSort && header.column.getCanSort() && (
|
|
147
|
-
<span className="ml-1">
|
|
148
|
-
{{
|
|
149
|
-
asc: '↑',
|
|
150
|
-
desc: '↓',
|
|
151
|
-
}[header.column?.getIsSorted ? header.column.getIsSorted() as string : ''] ?? '↕'}
|
|
152
|
-
</span>
|
|
153
|
-
)}
|
|
154
|
-
</div>
|
|
155
|
-
</th>
|
|
156
|
-
))}
|
|
157
|
-
</tr>
|
|
158
|
-
))}
|
|
159
|
-
</thead>
|
|
160
|
-
</table>
|
|
161
|
-
</div>
|
|
162
|
-
|
|
163
|
-
{/* Empty State */}
|
|
164
|
-
<div className="flex items-center justify-center py-12">
|
|
165
|
-
<div className="text-center">
|
|
166
|
-
<div className="text-app-sec-400 text-lg mb-2">📊</div>
|
|
167
|
-
<p className="text-app-sec-500">No data available</p>
|
|
168
|
-
</div>
|
|
169
|
-
</div>
|
|
170
|
-
</div>
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return (
|
|
175
|
-
<div className={cn("border rounded-lg overflow-hidden", className)}>
|
|
176
|
-
{/* Fixed Header */}
|
|
177
|
-
<div className="bg-app-sec-50 border-b sticky top-0 z-10">
|
|
178
|
-
<table ref={headerRef} className="w-full table-fixed">
|
|
179
|
-
<thead>
|
|
180
|
-
{headerGroups.map((headerGroup) => (
|
|
181
|
-
<tr key={headerGroup.id}>
|
|
182
|
-
{headerGroup.headers.map((header) => (
|
|
183
|
-
<th
|
|
184
|
-
key={header.id}
|
|
185
|
-
className={cn(
|
|
186
|
-
"px-4 py-3 text-left text-xs font-medium text-app-sec-500 uppercase tracking-wider",
|
|
187
|
-
header.column?.getCanSort && header.column.getCanSort() && "cursor-pointer select-none hover:bg-app-sec-100"
|
|
188
|
-
)}
|
|
189
|
-
style={{}}
|
|
190
|
-
onClick={header.column?.getToggleSortingHandler ? header.column.getToggleSortingHandler() : undefined}
|
|
191
|
-
>
|
|
192
|
-
<div className="flex items-center space-x-1">
|
|
193
|
-
{header.isPlaceholder
|
|
194
|
-
? null
|
|
195
|
-
: flexRender(header.column?.columnDef?.header, header.getContext?.() || {})}
|
|
196
|
-
{header.column?.getCanSort && header.column.getCanSort() && (
|
|
197
|
-
<span className="ml-1">
|
|
198
|
-
{{
|
|
199
|
-
asc: '↑',
|
|
200
|
-
desc: '↓',
|
|
201
|
-
}[header.column?.getIsSorted ? header.column.getIsSorted() as string : ''] ?? '↕'}
|
|
202
|
-
</span>
|
|
203
|
-
)}
|
|
204
|
-
</div>
|
|
205
|
-
</th>
|
|
206
|
-
))}
|
|
207
|
-
</tr>
|
|
208
|
-
))}
|
|
209
|
-
</thead>
|
|
210
|
-
</table>
|
|
211
|
-
</div>
|
|
212
|
-
|
|
213
|
-
{/* Virtualized Body */}
|
|
214
|
-
<div
|
|
215
|
-
ref={parentRef}
|
|
216
|
-
className="overflow-auto"
|
|
217
|
-
style={{ height: `${height}px` }}
|
|
218
|
-
>
|
|
219
|
-
<div
|
|
220
|
-
style={{
|
|
221
|
-
height: `${totalSize}px`,
|
|
222
|
-
width: '100%',
|
|
223
|
-
position: 'relative',
|
|
224
|
-
}}
|
|
225
|
-
>
|
|
226
|
-
<table ref={bodyRef} className="w-full table-fixed">
|
|
227
|
-
<tbody>
|
|
228
|
-
{virtualRows.map((virtualRow) => {
|
|
229
|
-
const row = rows[virtualRow.index];
|
|
230
|
-
if (!row) return null;
|
|
231
|
-
|
|
232
|
-
return (
|
|
233
|
-
<MemoizedRow
|
|
234
|
-
key={row.id}
|
|
235
|
-
row={row as unknown as Row<DataRecord>}
|
|
236
|
-
style={{
|
|
237
|
-
position: 'absolute',
|
|
238
|
-
top: 0,
|
|
239
|
-
left: 0,
|
|
240
|
-
width: '100%',
|
|
241
|
-
height: `${virtualRow.size}px`,
|
|
242
|
-
transform: `translateY(${virtualRow.start}px)`,
|
|
243
|
-
}}
|
|
244
|
-
/>
|
|
245
|
-
);
|
|
246
|
-
})}
|
|
247
|
-
</tbody>
|
|
248
|
-
</table>
|
|
249
|
-
</div>
|
|
250
|
-
</div>
|
|
251
|
-
|
|
252
|
-
{/* Footer with row count */}
|
|
253
|
-
<div className="bg-app-sec-50 border-t px-4 py-2">
|
|
254
|
-
<div className="flex items-center justify-between text-sm text-app-sec-500">
|
|
255
|
-
<span>
|
|
256
|
-
Showing {virtualRows.length > 0 ? virtualRows[0].index + 1 : 0} to{' '}
|
|
257
|
-
{virtualRows.length > 0 ? virtualRows[virtualRows.length - 1].index + 1 : 0} of{' '}
|
|
258
|
-
{rows.length} rows
|
|
259
|
-
</span>
|
|
260
|
-
<span>
|
|
261
|
-
Virtual rows: {virtualRows.length} / {rows.length}
|
|
262
|
-
</span>
|
|
263
|
-
</div>
|
|
264
|
-
</div>
|
|
265
|
-
</div>
|
|
266
|
-
);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Performance-optimized virtualized table with additional features
|
|
271
|
-
*/
|
|
272
|
-
export interface EnhancedVirtualizedDataTableProps<TData extends DataRecord>
|
|
273
|
-
extends VirtualizedDataTableProps<TData> {
|
|
274
|
-
enableRowSelection?: boolean;
|
|
275
|
-
enableHover?: boolean;
|
|
276
|
-
stickyHeader?: boolean;
|
|
277
|
-
loadingRows?: number;
|
|
278
|
-
isLoading?: boolean;
|
|
279
|
-
emptyMessage?: string;
|
|
280
|
-
// Additional props for DataTable integration
|
|
281
|
-
variant?: 'default' | 'compact' | 'spacious';
|
|
282
|
-
enableGrouping?: boolean;
|
|
283
|
-
actions?: unknown[];
|
|
284
|
-
enableEditing?: boolean;
|
|
285
|
-
enableDeletion?: boolean;
|
|
286
|
-
onEditRow?: (row: TData, data: Partial<TData>) => void;
|
|
287
|
-
onDeleteRow?: (row: TData) => void;
|
|
288
|
-
getRowId?: (row: TData) => string;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* Enhanced virtualized DataTable component.
|
|
293
|
-
* Provides virtual scrolling for large datasets with performance optimizations.
|
|
294
|
-
*
|
|
295
|
-
* @template TData - The type of data records in the table
|
|
296
|
-
* @param props - Virtualized table configuration
|
|
297
|
-
* @returns The rendered virtualized table
|
|
298
|
-
*/
|
|
299
|
-
export function EnhancedVirtualizedDataTable<TData extends DataRecord>({
|
|
300
|
-
table,
|
|
301
|
-
height = 400,
|
|
302
|
-
overscan = 5,
|
|
303
|
-
className,
|
|
304
|
-
enableRowSelection = false,
|
|
305
|
-
enableHover = true,
|
|
306
|
-
stickyHeader = true,
|
|
307
|
-
loadingRows = 10,
|
|
308
|
-
isLoading = false,
|
|
309
|
-
emptyMessage = "No data available",
|
|
310
|
-
onVisibilityChange,
|
|
311
|
-
// Additional props for DataTable integration
|
|
312
|
-
variant = 'default',
|
|
313
|
-
enableGrouping = false,
|
|
314
|
-
actions = [],
|
|
315
|
-
enableEditing = false,
|
|
316
|
-
enableDeletion = false,
|
|
317
|
-
onEditRow,
|
|
318
|
-
onDeleteRow,
|
|
319
|
-
getRowId
|
|
320
|
-
}: EnhancedVirtualizedDataTableProps<TData>) {
|
|
321
|
-
const parentRef = useRef<HTMLDivElement>(null);
|
|
322
|
-
const headerRef = useRef<HTMLTableElement>(null);
|
|
323
|
-
const bodyRef = useRef<HTMLTableElement>(null);
|
|
324
|
-
const rows = table.getRowModel().rows;
|
|
325
|
-
|
|
326
|
-
// Show loading skeleton if loading
|
|
327
|
-
const displayRows = useMemo(() => {
|
|
328
|
-
if (isLoading) {
|
|
329
|
-
return Array.from({ length: loadingRows }, (_, index) => ({
|
|
330
|
-
id: `loading-${index}`,
|
|
331
|
-
isLoading: true,
|
|
332
|
-
}));
|
|
333
|
-
}
|
|
334
|
-
return rows;
|
|
335
|
-
}, [isLoading, loadingRows, rows]);
|
|
336
|
-
|
|
337
|
-
const virtualizer = useVirtualizer({
|
|
338
|
-
count: displayRows.length,
|
|
339
|
-
getScrollElement: () => parentRef.current,
|
|
340
|
-
estimateSize: () => 40,
|
|
341
|
-
overscan,
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
const virtualRows = virtualizer.getVirtualItems();
|
|
345
|
-
const headerGroups = table.getHeaderGroups();
|
|
346
|
-
const totalSize = virtualizer.getTotalSize();
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
// Handle visibility changes
|
|
350
|
-
React.useEffect(() => {
|
|
351
|
-
if (virtualRows.length > 0 && onVisibilityChange && !isLoading) {
|
|
352
|
-
const start = virtualRows[0].index;
|
|
353
|
-
const end = virtualRows[virtualRows.length - 1].index;
|
|
354
|
-
onVisibilityChange({ start, end });
|
|
355
|
-
}
|
|
356
|
-
}, [virtualRows, onVisibilityChange, isLoading]);
|
|
357
|
-
|
|
358
|
-
// Loading row component
|
|
359
|
-
const LoadingRow = memo(({ style }: { style: React.CSSProperties }) => (
|
|
360
|
-
<tr style={style} className="animate-pulse">
|
|
361
|
-
{headerGroups[0]?.headers.map((header) => (
|
|
362
|
-
<td
|
|
363
|
-
key={header.id}
|
|
364
|
-
className="px-4 py-2 border-b border-app-sec-200"
|
|
365
|
-
style={{}}
|
|
366
|
-
>
|
|
367
|
-
<div className="h-4 bg-app-sec-200 rounded"></div>
|
|
368
|
-
</td>
|
|
369
|
-
))}
|
|
370
|
-
</tr>
|
|
371
|
-
));
|
|
372
|
-
|
|
373
|
-
LoadingRow.displayName = 'LoadingRow';
|
|
374
|
-
|
|
375
|
-
// Empty state
|
|
376
|
-
if (!isLoading && rows.length === 0) {
|
|
377
|
-
return (
|
|
378
|
-
<div className={cn("border rounded-lg overflow-hidden", className)} data-testid="enhanced-virtualized-table">
|
|
379
|
-
<div className="bg-app-sec-50 border-b">
|
|
380
|
-
<table ref={headerRef} className="w-full table-fixed">
|
|
381
|
-
<thead>
|
|
382
|
-
{headerGroups.map((headerGroup) => (
|
|
383
|
-
<tr key={headerGroup.id}>
|
|
384
|
-
{headerGroup.headers.map((header) => (
|
|
385
|
-
<th
|
|
386
|
-
key={header.id}
|
|
387
|
-
className="px-4 py-3 text-left text-xs font-medium text-app-sec-500 uppercase tracking-wider"
|
|
388
|
-
style={{}}
|
|
389
|
-
>
|
|
390
|
-
{header.isPlaceholder
|
|
391
|
-
? null
|
|
392
|
-
: flexRender(header.column?.columnDef?.header, header.getContext?.() || {})}
|
|
393
|
-
</th>
|
|
394
|
-
))}
|
|
395
|
-
</tr>
|
|
396
|
-
))}
|
|
397
|
-
</thead>
|
|
398
|
-
</table>
|
|
399
|
-
</div>
|
|
400
|
-
<div className="flex items-center justify-center py-12">
|
|
401
|
-
<div className="text-center">
|
|
402
|
-
<div className="text-app-sec-400 text-lg mb-2">📊</div>
|
|
403
|
-
<p className="text-app-sec-500">{emptyMessage}</p>
|
|
404
|
-
</div>
|
|
405
|
-
</div>
|
|
406
|
-
</div>
|
|
407
|
-
);
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
return (
|
|
411
|
-
<div className={cn("border rounded-lg overflow-hidden", className)} data-testid="enhanced-virtualized-table">
|
|
412
|
-
{/* Sticky Header */}
|
|
413
|
-
<div className={cn("bg-app-sec-50 border-b", stickyHeader && "sticky top-0 z-10")}>
|
|
414
|
-
<table ref={headerRef} className="w-full table-fixed">
|
|
415
|
-
<thead>
|
|
416
|
-
{headerGroups.map((headerGroup) => (
|
|
417
|
-
<tr key={headerGroup.id}>
|
|
418
|
-
{headerGroup.headers.map((header) => (
|
|
419
|
-
<th
|
|
420
|
-
key={header.id}
|
|
421
|
-
className={cn(
|
|
422
|
-
"px-4 py-3 text-left text-xs font-medium text-app-sec-500 uppercase tracking-wider",
|
|
423
|
-
header.column?.getCanSort && header.column.getCanSort() && "cursor-pointer select-none hover:bg-app-sec-100"
|
|
424
|
-
)}
|
|
425
|
-
style={{}}
|
|
426
|
-
onClick={header.column?.getToggleSortingHandler ? header.column.getToggleSortingHandler() : undefined}
|
|
427
|
-
>
|
|
428
|
-
<div className="flex items-center space-x-1">
|
|
429
|
-
{header.isPlaceholder
|
|
430
|
-
? null
|
|
431
|
-
: flexRender(header.column?.columnDef?.header, header.getContext?.() || {})}
|
|
432
|
-
{header.column?.getCanSort && header.column.getCanSort() && (
|
|
433
|
-
<span className="ml-1">
|
|
434
|
-
{{
|
|
435
|
-
asc: '↑',
|
|
436
|
-
desc: '↓',
|
|
437
|
-
}[header.column?.getIsSorted ? header.column.getIsSorted() as string : ''] ?? '↕'}
|
|
438
|
-
</span>
|
|
439
|
-
)}
|
|
440
|
-
</div>
|
|
441
|
-
</th>
|
|
442
|
-
))}
|
|
443
|
-
</tr>
|
|
444
|
-
))}
|
|
445
|
-
</thead>
|
|
446
|
-
</table>
|
|
447
|
-
</div>
|
|
448
|
-
|
|
449
|
-
{/* Virtualized Body */}
|
|
450
|
-
<div
|
|
451
|
-
ref={parentRef}
|
|
452
|
-
className="overflow-auto"
|
|
453
|
-
style={{ height: `${height}px` }}
|
|
454
|
-
>
|
|
455
|
-
<div
|
|
456
|
-
style={{
|
|
457
|
-
height: `${totalSize}px`,
|
|
458
|
-
width: '100%',
|
|
459
|
-
position: 'relative',
|
|
460
|
-
}}
|
|
461
|
-
>
|
|
462
|
-
<table ref={bodyRef} className="w-full table-fixed">
|
|
463
|
-
<tbody>
|
|
464
|
-
{virtualRows.map((virtualRow) => {
|
|
465
|
-
const displayRow = displayRows[virtualRow.index];
|
|
466
|
-
|
|
467
|
-
if ('isLoading' in displayRow) {
|
|
468
|
-
return (
|
|
469
|
-
<LoadingRow
|
|
470
|
-
key={displayRow.id}
|
|
471
|
-
style={{
|
|
472
|
-
position: 'absolute',
|
|
473
|
-
top: 0,
|
|
474
|
-
left: 0,
|
|
475
|
-
width: '100%',
|
|
476
|
-
height: `${virtualRow.size}px`,
|
|
477
|
-
transform: `translateY(${virtualRow.start}px)`,
|
|
478
|
-
}}
|
|
479
|
-
/>
|
|
480
|
-
);
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
const row = displayRow as Row<TData>;
|
|
484
|
-
return (
|
|
485
|
-
<MemoizedRow
|
|
486
|
-
key={row.id}
|
|
487
|
-
row={row as unknown as Row<DataRecord>}
|
|
488
|
-
style={{
|
|
489
|
-
position: 'absolute',
|
|
490
|
-
top: 0,
|
|
491
|
-
left: 0,
|
|
492
|
-
width: '100%',
|
|
493
|
-
height: `${virtualRow.size}px`,
|
|
494
|
-
transform: `translateY(${virtualRow.start}px)`,
|
|
495
|
-
}}
|
|
496
|
-
/>
|
|
497
|
-
);
|
|
498
|
-
})}
|
|
499
|
-
</tbody>
|
|
500
|
-
</table>
|
|
501
|
-
</div>
|
|
502
|
-
</div>
|
|
503
|
-
|
|
504
|
-
{/* Performance Footer */}
|
|
505
|
-
<div className="bg-app-sec-50 border-t px-4 py-2">
|
|
506
|
-
<div className="flex items-center justify-between text-sm text-app-sec-500">
|
|
507
|
-
<span>
|
|
508
|
-
{isLoading ? (
|
|
509
|
-
"Loading..."
|
|
510
|
-
) : (
|
|
511
|
-
<>
|
|
512
|
-
Showing {virtualRows.length > 0 ? virtualRows[0].index + 1 : 0} to{' '}
|
|
513
|
-
{virtualRows.length > 0 ? virtualRows[virtualRows.length - 1].index + 1 : 0} of{' '}
|
|
514
|
-
{rows.length} rows
|
|
515
|
-
</>
|
|
516
|
-
)}
|
|
517
|
-
</span>
|
|
518
|
-
<span>
|
|
519
|
-
Virtual: {virtualRows.length} / {displayRows.length}
|
|
520
|
-
</span>
|
|
521
|
-
</div>
|
|
522
|
-
</div>
|
|
523
|
-
</div>
|
|
524
|
-
);
|
|
525
|
-
}
|