@jmruthers/pace-core 0.5.136 → 0.5.139
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/dist/{DataTable-CYOHOX3O.js → DataTable-JXFCA2BJ.js} +10 -9
- package/dist/{EventLogo-801uofbR.d.ts → EventLogo-rFL_kRjk.d.ts} +73 -1
- package/dist/{UnifiedAuthProvider-5E5TUNMS.js → UnifiedAuthProvider-XIQQ7LVU.js} +4 -5
- package/dist/{chunk-YLKIDTUK.js → chunk-22WKWKRX.js} +4 -4
- package/dist/{chunk-TVYPTYOY.js → chunk-4C7EXCAR.js} +60 -24
- package/dist/chunk-4C7EXCAR.js.map +1 -0
- package/dist/{chunk-NOHEVYVX.js → chunk-5JMOHWDI.js} +417 -319
- package/dist/chunk-5JMOHWDI.js.map +1 -0
- package/dist/{chunk-FHWWBIHA.js → chunk-6DXZ6V5Q.js} +5 -5
- package/dist/{chunk-2TWNJ46Y.js → chunk-6LAAY47Q.js} +2 -2
- package/dist/{chunk-444EZN6N.js → chunk-7QCC6MCP.js} +88 -1
- package/dist/chunk-7QCC6MCP.js.map +1 -0
- package/dist/chunk-BJPBT3CU.js +21 -0
- package/dist/chunk-BJPBT3CU.js.map +1 -0
- package/dist/{chunk-L6PGMCMD.js → chunk-BOOI7GK2.js} +38 -12
- package/dist/chunk-BOOI7GK2.js.map +1 -0
- package/dist/{chunk-XARJS7CD.js → chunk-INQLMHPF.js} +2 -2
- package/dist/chunk-JISYG63F.js +70 -0
- package/dist/chunk-JISYG63F.js.map +1 -0
- package/dist/{chunk-SL2YQDR6.js → chunk-MA6EPSGZ.js} +2 -2
- package/dist/{chunk-5DPZ5EAT.js → chunk-OWAG3GSU.js} +1 -3
- package/dist/{chunk-LTV3XIJJ.js → chunk-T6JN6LH6.js} +4 -4
- package/dist/{chunk-HJGGOMQ6.js → chunk-TLT2ZR3L.js} +147 -103
- package/dist/chunk-TLT2ZR3L.js.map +1 -0
- package/dist/{chunk-4MT5BGGL.js → chunk-YCWDTTUK.js} +4 -6
- package/dist/{chunk-4MT5BGGL.js.map → chunk-YCWDTTUK.js.map} +1 -1
- package/dist/components.d.ts +1 -1
- package/dist/components.js +12 -11
- package/dist/components.js.map +1 -1
- package/dist/hooks.js +8 -9
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +15 -14
- package/dist/index.js.map +1 -1
- package/dist/providers.js +3 -4
- package/dist/rbac/index.js +8 -9
- package/dist/schema-DTDZQe2u.d.ts +28 -0
- package/dist/types.d.ts +152 -3
- package/dist/types.js +51 -16
- package/dist/types.js.map +1 -1
- package/dist/utils.d.ts +89 -4
- package/dist/utils.js +214 -96
- package/dist/utils.js.map +1 -1
- package/dist/validation.d.ts +1 -343
- package/dist/validation.js +3 -100
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/BadgeProps.md +27 -0
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/EventLogoProps.md +1 -1
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +84 -15
- package/docs/architecture/README.md +0 -1
- package/docs/styles/README.md +0 -2
- package/examples/RBAC/CompleteRBACExample.tsx +324 -0
- package/examples/RBAC/EventBasedApp.tsx +239 -0
- package/examples/RBAC/PermissionExample.tsx +151 -0
- package/examples/RBAC/index.ts +13 -0
- package/examples/public-pages/CorrectPublicPageImplementation.tsx +301 -0
- package/examples/public-pages/PublicEventPage.tsx +274 -0
- package/examples/public-pages/PublicPageApp.tsx +308 -0
- package/examples/public-pages/PublicPageUsageExample.tsx +216 -0
- package/examples/public-pages/index.ts +14 -0
- package/package.json +1 -10
- package/src/__tests__/TEST_STANDARD.md +92 -0
- package/src/components/Badge/Badge.test.tsx +314 -0
- package/src/components/Badge/Badge.tsx +304 -0
- package/src/components/Badge/index.ts +3 -0
- package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +217 -0
- package/src/components/DataTable/__tests__/styles.test.ts +1 -1
- package/src/components/DataTable/components/ColumnFilter.tsx +8 -4
- package/src/components/DataTable/components/DataTableBody.tsx +461 -0
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +144 -0
- package/src/components/DataTable/components/FilterRow.tsx +9 -3
- package/src/components/DataTable/components/PaginationControls.tsx +1 -0
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +513 -0
- package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +14 -68
- package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +62 -0
- package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +43 -0
- package/src/components/DataTable/core/ActionManager.ts +235 -0
- package/src/components/DataTable/core/ColumnManager.ts +205 -0
- package/src/components/DataTable/core/DataManager.ts +188 -0
- package/src/components/DataTable/core/DataTableContext.tsx +181 -0
- package/src/components/DataTable/core/LocalDataAdapter.ts +273 -0
- package/src/components/DataTable/core/PluginRegistry.ts +229 -0
- package/src/components/DataTable/core/StateManager.ts +311 -0
- package/src/components/DataTable/core/interfaces.ts +338 -0
- package/src/components/DataTable/styles.ts +27 -6
- package/src/components/DataTable/utils/__tests__/columnUtils.test.ts +94 -0
- package/src/components/DataTable/utils/columnUtils.ts +40 -0
- package/src/components/DataTable/utils/debugTools.ts +609 -0
- package/src/components/DataTable/utils/index.ts +1 -0
- package/src/components/Dialog/README.md +804 -0
- package/src/components/Dialog/utils/__tests__/safeHtml.unit.test.ts +611 -0
- package/src/components/Dialog/utils/safeHtml.ts +185 -0
- package/src/components/Footer/Footer.test.tsx +1 -1
- package/src/components/Form/Form.test.tsx +1 -1
- package/src/components/Form/FormErrorSummary.tsx +113 -0
- package/src/components/Form/FormFieldset.tsx +127 -0
- package/src/components/Form/FormLiveRegion.tsx +198 -0
- package/src/components/LoginForm/LoginForm.test.tsx +1 -1
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.performance.test.tsx +76 -10
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +1 -1
- package/src/components/PasswordReset/PasswordResetForm.test.tsx +597 -0
- package/src/components/PasswordReset/PasswordResetForm.tsx +201 -0
- package/src/components/PublicLayout/PublicPageDebugger.tsx +104 -0
- package/src/components/PublicLayout/PublicPageDiagnostic.tsx +162 -0
- package/src/components/PublicLayout/__tests__/PublicPageFooter.test.tsx +1 -1
- package/src/components/Select/Select.test.tsx +1 -1
- package/src/components/Select/Select.tsx +20 -8
- package/src/components/Table/__tests__/Table.test.tsx +1 -1
- package/src/components/index.ts +3 -0
- package/src/hooks/__tests__/useFileUrl.unit.test.ts +83 -85
- package/src/index.ts +4 -0
- package/src/rbac/hooks/useCan.test.ts +24 -0
- package/src/rbac/hooks/usePermissions.ts +49 -12
- package/src/styles/core.css +3 -0
- package/src/utils/appConfig.ts +47 -0
- package/src/utils/appIdResolver.test.ts +499 -0
- package/src/utils/appIdResolver.ts +130 -0
- package/src/utils/appNameResolver.simple.test.ts +212 -0
- package/src/utils/appNameResolver.test.ts +121 -0
- package/src/utils/appNameResolver.ts +191 -0
- package/src/utils/audit.ts +127 -0
- package/src/utils/auth-utils.ts +96 -0
- package/src/utils/bundleAnalysis.ts +129 -0
- package/src/utils/cn.ts +7 -0
- package/src/utils/debugLogger.ts +67 -0
- package/src/utils/deviceFingerprint.ts +215 -0
- package/src/utils/dynamicUtils.ts +105 -0
- package/src/utils/file-reference.test.ts +788 -0
- package/src/utils/file-reference.ts +519 -0
- package/src/utils/formatDate.test.ts +237 -0
- package/src/utils/formatting.ts +133 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/lazyLoad.tsx +44 -0
- package/src/utils/logger.ts +179 -0
- package/src/utils/organisationContext.test.ts +322 -0
- package/src/utils/organisationContext.ts +153 -0
- package/src/utils/performanceBenchmark.ts +64 -0
- package/src/utils/performanceBudgets.ts +110 -0
- package/src/utils/permissionTypes.ts +37 -0
- package/src/utils/permissionUtils.test.ts +393 -0
- package/src/utils/permissionUtils.ts +34 -0
- package/src/utils/sanitization.ts +264 -0
- package/src/utils/schemaUtils.ts +37 -0
- package/src/utils/secureDataAccess.test.ts +711 -0
- package/src/utils/secureDataAccess.ts +377 -0
- package/src/utils/secureErrors.ts +79 -0
- package/src/utils/secureStorage.ts +244 -0
- package/src/utils/security.ts +156 -0
- package/src/utils/securityMonitor.ts +45 -0
- package/src/utils/sessionTracking.ts +126 -0
- package/src/utils/validation.ts +111 -0
- package/src/utils/validationUtils.ts +120 -0
- package/src/validation/index.ts +2 -2
- package/dist/chunk-444EZN6N.js.map +0 -1
- package/dist/chunk-APIBCTL2.js +0 -670
- package/dist/chunk-APIBCTL2.js.map +0 -1
- package/dist/chunk-HJGGOMQ6.js.map +0 -1
- package/dist/chunk-K2WWTH7O.js +0 -94
- package/dist/chunk-K2WWTH7O.js.map +0 -1
- package/dist/chunk-L6PGMCMD.js.map +0 -1
- package/dist/chunk-LMC26NLJ.js +0 -84
- package/dist/chunk-LMC26NLJ.js.map +0 -1
- package/dist/chunk-NOHEVYVX.js.map +0 -1
- package/dist/chunk-TVYPTYOY.js.map +0 -1
- package/dist/validation-8npbysjg.d.ts +0 -177
- /package/dist/{DataTable-CYOHOX3O.js.map → DataTable-JXFCA2BJ.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-5E5TUNMS.js.map → UnifiedAuthProvider-XIQQ7LVU.js.map} +0 -0
- /package/dist/{chunk-YLKIDTUK.js.map → chunk-22WKWKRX.js.map} +0 -0
- /package/dist/{chunk-FHWWBIHA.js.map → chunk-6DXZ6V5Q.js.map} +0 -0
- /package/dist/{chunk-2TWNJ46Y.js.map → chunk-6LAAY47Q.js.map} +0 -0
- /package/dist/{chunk-XARJS7CD.js.map → chunk-INQLMHPF.js.map} +0 -0
- /package/dist/{chunk-SL2YQDR6.js.map → chunk-MA6EPSGZ.js.map} +0 -0
- /package/dist/{chunk-5DPZ5EAT.js.map → chunk-OWAG3GSU.js.map} +0 -0
- /package/dist/{chunk-LTV3XIJJ.js.map → chunk-T6JN6LH6.js.map} +0 -0
- /package/examples/{components → components 2}/DataTable/HierarchicalActionsExample.tsx +0 -0
- /package/examples/{components → components 2}/DataTable/HierarchicalExample.tsx +0 -0
- /package/examples/{components → components 2}/DataTable/InitialPageSizeExample.tsx +0 -0
- /package/examples/{components → components 2}/DataTable/PerformanceExample.tsx +0 -0
- /package/examples/{components → components 2}/DataTable/index.ts +0 -0
- /package/examples/{components → components 2}/Dialog/BasicHtmlTest.tsx +0 -0
- /package/examples/{components → components 2}/Dialog/DebugHtmlExample.tsx +0 -0
- /package/examples/{components → components 2}/Dialog/HtmlDialogExample.tsx +0 -0
- /package/examples/{components → components 2}/Dialog/ScrollableDialogExample.tsx +0 -0
- /package/examples/{components → components 2}/Dialog/SimpleHtmlTest.tsx +0 -0
- /package/examples/{components → components 2}/Dialog/SmartDialogExample.tsx +0 -0
- /package/examples/{components → components 2}/Dialog/index.ts +0 -0
- /package/examples/{components → components 2}/index.ts +0 -0
|
@@ -147,6 +147,21 @@ describe('[component] FilterRow', () => {
|
|
|
147
147
|
const filters = screen.getAllByTestId('column-filter');
|
|
148
148
|
expect(filters[0]).toHaveAttribute('data-placeholder', expect.stringContaining('Filter'));
|
|
149
149
|
});
|
|
150
|
+
|
|
151
|
+
it('uses column header text in filter placeholder', () => {
|
|
152
|
+
const columns = [
|
|
153
|
+
columnHelper.accessor('name', {
|
|
154
|
+
header: 'Full Name',
|
|
155
|
+
}),
|
|
156
|
+
];
|
|
157
|
+
const table = createTable(columns);
|
|
158
|
+
const visibleColumns = table.getHeaderGroups()[0]?.headers || [];
|
|
159
|
+
|
|
160
|
+
render(<FilterRow table={table} visibleColumns={visibleColumns} />);
|
|
161
|
+
|
|
162
|
+
const filter = screen.getByTestId('column-filter');
|
|
163
|
+
expect(filter).toHaveAttribute('data-placeholder', 'Filter Full Name...');
|
|
164
|
+
});
|
|
150
165
|
});
|
|
151
166
|
|
|
152
167
|
describe('Filter Type Detection', () => {
|
|
@@ -245,6 +260,34 @@ describe('[component] FilterRow', () => {
|
|
|
245
260
|
expect(filter).toHaveAttribute('data-filter-type', 'select');
|
|
246
261
|
});
|
|
247
262
|
|
|
263
|
+
it('respects explicit filterType: text even when unique values ≤ 10', () => {
|
|
264
|
+
// This test reproduces the bug: filterType: 'text' should not be overridden
|
|
265
|
+
// by auto-detection when there are ≤10 unique values
|
|
266
|
+
const limitedData: TestData[] = Array.from({ length: 7 }, (_, i) => ({
|
|
267
|
+
id: String(i),
|
|
268
|
+
name: `Brand ${i % 7}`,
|
|
269
|
+
email: `user${i}@example.com`,
|
|
270
|
+
age: 20 + i,
|
|
271
|
+
status: ['active', 'inactive', 'pending'][i % 3],
|
|
272
|
+
createdAt: new Date(),
|
|
273
|
+
}));
|
|
274
|
+
|
|
275
|
+
const columns = [
|
|
276
|
+
columnHelper.accessor('name', {
|
|
277
|
+
header: 'Brand',
|
|
278
|
+
filterType: 'text', // Explicitly set to text
|
|
279
|
+
}),
|
|
280
|
+
];
|
|
281
|
+
const table = createTable(columns, limitedData);
|
|
282
|
+
const visibleColumns = table.getHeaderGroups()[0]?.headers || [];
|
|
283
|
+
|
|
284
|
+
render(<FilterRow table={table} visibleColumns={visibleColumns} />);
|
|
285
|
+
|
|
286
|
+
const filter = screen.getByTestId('column-filter');
|
|
287
|
+
// Should remain as 'text' filter, NOT auto-detect to 'select'
|
|
288
|
+
expect(filter).toHaveAttribute('data-filter-type', 'text');
|
|
289
|
+
});
|
|
290
|
+
|
|
248
291
|
it('defaults to text filter when no auto-detection matches', () => {
|
|
249
292
|
// Use data with many unique values (>10) to avoid auto-detection as select
|
|
250
293
|
const manyUniqueData: TestData[] = Array.from({ length: 15 }, (_, i) => ({
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file DataTable Action Manager
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Components/DataTable/Architecture/Managers
|
|
5
|
+
* @since 0.3.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ActionManager } from './interfaces';
|
|
9
|
+
import type { DataTableAction, DataRecord } from '../types';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Action manager implementation following SRP
|
|
13
|
+
* Responsible only for action operations
|
|
14
|
+
*/
|
|
15
|
+
export class ActionManagerImpl<TData extends DataRecord> implements ActionManager<TData> {
|
|
16
|
+
private actions: DataTableAction<TData>[] = [];
|
|
17
|
+
private actionMap = new Map<string, DataTableAction<TData>>();
|
|
18
|
+
|
|
19
|
+
constructor(initialActions: DataTableAction<TData>[] = []) {
|
|
20
|
+
this.setActions(initialActions);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get all actions
|
|
25
|
+
*/
|
|
26
|
+
getActions(): DataTableAction<TData>[] {
|
|
27
|
+
return [...this.actions];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Add a new action
|
|
32
|
+
*/
|
|
33
|
+
addAction(action: DataTableAction<TData>): void {
|
|
34
|
+
const actionId = this.getActionId(action);
|
|
35
|
+
|
|
36
|
+
// Remove existing action with same ID if it exists
|
|
37
|
+
this.removeAction(actionId);
|
|
38
|
+
|
|
39
|
+
// Add new action
|
|
40
|
+
this.actions.push(action);
|
|
41
|
+
this.actionMap.set(actionId, action);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Remove an action by ID
|
|
46
|
+
*/
|
|
47
|
+
removeAction(id: string): void {
|
|
48
|
+
const index = this.actions.findIndex(action => this.getActionId(action) === id);
|
|
49
|
+
if (index !== -1) {
|
|
50
|
+
this.actions.splice(index, 1);
|
|
51
|
+
this.actionMap.delete(id);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Update an existing action
|
|
57
|
+
*/
|
|
58
|
+
updateAction(id: string, updates: Partial<DataTableAction<TData>>): void {
|
|
59
|
+
const existingAction = this.actionMap.get(id);
|
|
60
|
+
if (!existingAction) {
|
|
61
|
+
throw new Error(`Action with ID "${id}" not found`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const updatedAction = { ...existingAction, ...updates };
|
|
65
|
+
const index = this.actions.findIndex(action => this.getActionId(action) === id);
|
|
66
|
+
|
|
67
|
+
if (index !== -1) {
|
|
68
|
+
this.actions[index] = updatedAction;
|
|
69
|
+
this.actionMap.set(id, updatedAction);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get action by ID
|
|
75
|
+
*/
|
|
76
|
+
getAction(id: string): DataTableAction<TData> | undefined {
|
|
77
|
+
return this.actionMap.get(id);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Check if action exists
|
|
82
|
+
*/
|
|
83
|
+
hasAction(id: string): boolean {
|
|
84
|
+
return this.actionMap.has(id);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get action IDs
|
|
89
|
+
*/
|
|
90
|
+
getActionIds(): string[] {
|
|
91
|
+
return Array.from(this.actionMap.keys());
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Get enabled actions
|
|
96
|
+
*/
|
|
97
|
+
getEnabledActions(): DataTableAction<TData>[] {
|
|
98
|
+
return this.actions.filter(action => {
|
|
99
|
+
if (!action.disabled) return true;
|
|
100
|
+
if (typeof action.disabled === 'function') {
|
|
101
|
+
// For function-based disabled, we need a row to check against
|
|
102
|
+
// Since we don't have a specific row here, we'll create a mock row
|
|
103
|
+
// This is not ideal but allows the method to work
|
|
104
|
+
const mockRow = {} as TData;
|
|
105
|
+
return !action.disabled(mockRow);
|
|
106
|
+
}
|
|
107
|
+
return !action.disabled;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get actions by variant
|
|
113
|
+
*/
|
|
114
|
+
getActionsByVariant(variant: DataTableAction<TData>['variant']): DataTableAction<TData>[] {
|
|
115
|
+
return this.actions.filter(action => action.variant === variant);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Get destructive actions
|
|
120
|
+
*/
|
|
121
|
+
getDestructiveActions(): DataTableAction<TData>[] {
|
|
122
|
+
return this.getActionsByVariant('destructive');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get primary actions
|
|
127
|
+
*/
|
|
128
|
+
getPrimaryActions(): DataTableAction<TData>[] {
|
|
129
|
+
return this.getActionsByVariant('default');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get secondary actions
|
|
134
|
+
*/
|
|
135
|
+
getSecondaryActions(): DataTableAction<TData>[] {
|
|
136
|
+
return this.getActionsByVariant('secondary');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Set all actions at once
|
|
141
|
+
*/
|
|
142
|
+
setActions(actions: DataTableAction<TData>[]): void {
|
|
143
|
+
this.actions = [...actions];
|
|
144
|
+
this.actionMap.clear();
|
|
145
|
+
|
|
146
|
+
actions.forEach(action => {
|
|
147
|
+
const id = this.getActionId(action);
|
|
148
|
+
this.actionMap.set(id, action);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Clear all actions
|
|
154
|
+
*/
|
|
155
|
+
clearActions(): void {
|
|
156
|
+
this.actions = [];
|
|
157
|
+
this.actionMap.clear();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Get action count
|
|
162
|
+
*/
|
|
163
|
+
getActionCount(): number {
|
|
164
|
+
return this.actions.length;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get action by index
|
|
169
|
+
*/
|
|
170
|
+
getActionByIndex(index: number): DataTableAction<TData> | undefined {
|
|
171
|
+
return this.actions[index];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Get action index by ID
|
|
176
|
+
*/
|
|
177
|
+
getActionIndex(id: string): number {
|
|
178
|
+
return this.actions.findIndex(action => this.getActionId(action) === id);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Move action to new position
|
|
183
|
+
*/
|
|
184
|
+
moveAction(id: string, newIndex: number): void {
|
|
185
|
+
const currentIndex = this.getActionIndex(id);
|
|
186
|
+
if (currentIndex === -1) {
|
|
187
|
+
throw new Error(`Action with ID "${id}" not found`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (newIndex < 0 || newIndex >= this.actions.length) {
|
|
191
|
+
throw new Error(`Invalid index: ${newIndex}`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const action = this.actions[currentIndex];
|
|
195
|
+
this.actions.splice(currentIndex, 1);
|
|
196
|
+
this.actions.splice(newIndex, 0, action);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Execute an action
|
|
201
|
+
*/
|
|
202
|
+
executeAction(id: string, row: TData): void {
|
|
203
|
+
const action = this.actionMap.get(id);
|
|
204
|
+
if (!action) {
|
|
205
|
+
throw new Error(`Action with ID "${id}" not found`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (action.disabled && typeof action.disabled === 'function' && action.disabled(row)) {
|
|
209
|
+
throw new Error(`Action "${id}" is disabled for this row`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
action.onClick(row);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Check if action is disabled for a specific row
|
|
217
|
+
*/
|
|
218
|
+
isActionDisabled(id: string, row: TData): boolean {
|
|
219
|
+
const action = this.actionMap.get(id);
|
|
220
|
+
if (!action) return true;
|
|
221
|
+
|
|
222
|
+
if (typeof action.disabled === 'function') {
|
|
223
|
+
return action.disabled(row);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return !!action.disabled;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Get action ID from action definition
|
|
231
|
+
*/
|
|
232
|
+
private getActionId(action: DataTableAction<TData>): string {
|
|
233
|
+
return action.testId || action.label.toLowerCase().replace(/\s+/g, '-') || `action-${Math.random().toString(36).substr(2, 9)}`;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file DataTable Column Manager
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Components/DataTable/Architecture/Managers
|
|
5
|
+
* @since 0.3.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ColumnManager } from './interfaces';
|
|
9
|
+
import type { ColumnDef } from '@tanstack/react-table';
|
|
10
|
+
import type { DataRecord } from '../types';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Column manager implementation following SRP
|
|
14
|
+
* Responsible only for column operations
|
|
15
|
+
*/
|
|
16
|
+
export class ColumnManagerImpl<TData extends DataRecord> implements ColumnManager<TData> {
|
|
17
|
+
private columns: ColumnDef<TData>[] = [];
|
|
18
|
+
private columnMap = new Map<string, ColumnDef<TData>>();
|
|
19
|
+
|
|
20
|
+
constructor(initialColumns: ColumnDef<TData>[] = []) {
|
|
21
|
+
this.setColumns(initialColumns);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get all columns
|
|
26
|
+
*/
|
|
27
|
+
getColumns(): ColumnDef<TData>[] {
|
|
28
|
+
return [...this.columns];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Add a new column
|
|
33
|
+
*/
|
|
34
|
+
addColumn(column: ColumnDef<TData>): void {
|
|
35
|
+
const columnId = this.getColumnId(column);
|
|
36
|
+
|
|
37
|
+
// Remove existing column with same ID if it exists
|
|
38
|
+
this.removeColumn(columnId);
|
|
39
|
+
|
|
40
|
+
// Add new column
|
|
41
|
+
this.columns.push(column);
|
|
42
|
+
this.columnMap.set(columnId, column);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Remove a column by ID
|
|
47
|
+
*/
|
|
48
|
+
removeColumn(id: string): void {
|
|
49
|
+
const index = this.columns.findIndex(col => this.getColumnId(col) === id);
|
|
50
|
+
if (index !== -1) {
|
|
51
|
+
this.columns.splice(index, 1);
|
|
52
|
+
this.columnMap.delete(id);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Update an existing column
|
|
58
|
+
*/
|
|
59
|
+
updateColumn(id: string, updates: Partial<ColumnDef<TData>>): void {
|
|
60
|
+
const existingColumn = this.columnMap.get(id);
|
|
61
|
+
if (!existingColumn) {
|
|
62
|
+
throw new Error(`Column with ID "${id}" not found`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const updatedColumn = { ...existingColumn, ...updates } as ColumnDef<TData>;
|
|
66
|
+
const index = this.columns.findIndex(col => this.getColumnId(col) === id);
|
|
67
|
+
|
|
68
|
+
if (index !== -1) {
|
|
69
|
+
this.columns[index] = updatedColumn;
|
|
70
|
+
this.columnMap.set(id, updatedColumn);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get a specific column by ID
|
|
76
|
+
*/
|
|
77
|
+
getColumn(id: string): ColumnDef<TData> | undefined {
|
|
78
|
+
return this.columnMap.get(id);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Check if a column exists
|
|
83
|
+
*/
|
|
84
|
+
hasColumn(id: string): boolean {
|
|
85
|
+
return this.columnMap.has(id);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get column IDs
|
|
90
|
+
*/
|
|
91
|
+
getColumnIds(): string[] {
|
|
92
|
+
return Array.from(this.columnMap.keys());
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get visible columns
|
|
97
|
+
*/
|
|
98
|
+
getVisibleColumns(): ColumnDef<TData>[] {
|
|
99
|
+
return this.columns.filter(column => {
|
|
100
|
+
const id = this.getColumnId(column);
|
|
101
|
+
return column.enableHiding !== false;
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get sortable columns
|
|
107
|
+
*/
|
|
108
|
+
getSortableColumns(): ColumnDef<TData>[] {
|
|
109
|
+
return this.columns.filter(column => column.enableSorting === true);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get filterable columns
|
|
114
|
+
*/
|
|
115
|
+
getFilterableColumns(): ColumnDef<TData>[] {
|
|
116
|
+
return this.columns.filter(column => column.enableColumnFilter === true);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Get groupable columns
|
|
121
|
+
*/
|
|
122
|
+
getGroupableColumns(): ColumnDef<TData>[] {
|
|
123
|
+
return this.columns.filter(column => column.enableGrouping === true);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Set all columns at once
|
|
128
|
+
*/
|
|
129
|
+
setColumns(columns: ColumnDef<TData>[]): void {
|
|
130
|
+
this.columns = [...columns];
|
|
131
|
+
this.columnMap.clear();
|
|
132
|
+
|
|
133
|
+
columns.forEach(column => {
|
|
134
|
+
const id = this.getColumnId(column);
|
|
135
|
+
this.columnMap.set(id, column);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Clear all columns
|
|
141
|
+
*/
|
|
142
|
+
clearColumns(): void {
|
|
143
|
+
this.columns = [];
|
|
144
|
+
this.columnMap.clear();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get column count
|
|
149
|
+
*/
|
|
150
|
+
getColumnCount(): number {
|
|
151
|
+
return this.columns.length;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Get column by index
|
|
156
|
+
*/
|
|
157
|
+
getColumnByIndex(index: number): ColumnDef<TData> | undefined {
|
|
158
|
+
return this.columns[index];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Get column index by ID
|
|
163
|
+
*/
|
|
164
|
+
getColumnIndex(id: string): number {
|
|
165
|
+
return this.columns.findIndex(col => this.getColumnId(col) === id);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Move column to new position
|
|
170
|
+
*/
|
|
171
|
+
moveColumn(id: string, newIndex: number): void {
|
|
172
|
+
const currentIndex = this.getColumnIndex(id);
|
|
173
|
+
if (currentIndex === -1) {
|
|
174
|
+
throw new Error(`Column with ID "${id}" not found`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (newIndex < 0 || newIndex >= this.columns.length) {
|
|
178
|
+
throw new Error(`Invalid index: ${newIndex}`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const column = this.columns[currentIndex];
|
|
182
|
+
this.columns.splice(currentIndex, 1);
|
|
183
|
+
this.columns.splice(newIndex, 0, column);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Get column ID from column definition
|
|
188
|
+
*/
|
|
189
|
+
private getColumnId(column: ColumnDef<TData>): string {
|
|
190
|
+
// Handle different column types
|
|
191
|
+
if ('id' in column && column.id) {
|
|
192
|
+
return column.id;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if ('accessorKey' in column && column.accessorKey) {
|
|
196
|
+
return String(column.accessorKey);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if ('accessorFn' in column && typeof column.accessorFn === 'function') {
|
|
200
|
+
return `fn-${Math.random().toString(36).substr(2, 9)}`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return `fn-${Math.random().toString(36).substr(2, 9)}`;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file DataTable Data Manager
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Components/DataTable/Architecture/Managers
|
|
5
|
+
* @since 0.3.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { DataTableDataManager, DataAdapter, FetchOptions } from './interfaces';
|
|
9
|
+
import type { DataRecord } from '../types';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Data manager implementation following SRP
|
|
13
|
+
* Responsible only for data operations
|
|
14
|
+
*/
|
|
15
|
+
export class DataManager<TData extends DataRecord> implements DataTableDataManager<TData> {
|
|
16
|
+
private data: TData[] = [];
|
|
17
|
+
private _isLoading = false;
|
|
18
|
+
private error: Error | null = null;
|
|
19
|
+
private adapter: DataAdapter<TData>;
|
|
20
|
+
private cache = new Map<string, { data: TData[]; timestamp: number }>();
|
|
21
|
+
private cacheTimeout: number;
|
|
22
|
+
|
|
23
|
+
constructor(adapter: DataAdapter<TData>, cacheTimeout = 5 * 60 * 1000) {
|
|
24
|
+
this.adapter = adapter;
|
|
25
|
+
this.cacheTimeout = cacheTimeout;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get current data
|
|
30
|
+
*/
|
|
31
|
+
getData(): TData[] {
|
|
32
|
+
return this.data;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Update a data item
|
|
37
|
+
*/
|
|
38
|
+
async updateData(id: string, data: Partial<TData>): Promise<void> {
|
|
39
|
+
try {
|
|
40
|
+
this.setLoading(true);
|
|
41
|
+
this.setError(null);
|
|
42
|
+
|
|
43
|
+
await this.adapter.updateData(id, data);
|
|
44
|
+
|
|
45
|
+
// Update local data
|
|
46
|
+
const index = this.data.findIndex(item => this.getRowId(item) === id);
|
|
47
|
+
if (index !== -1) {
|
|
48
|
+
this.data[index] = { ...this.data[index], ...data };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.clearCache();
|
|
52
|
+
} catch (error) {
|
|
53
|
+
this.setError(error as Error);
|
|
54
|
+
throw error;
|
|
55
|
+
} finally {
|
|
56
|
+
this.setLoading(false);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Delete a data item
|
|
62
|
+
*/
|
|
63
|
+
async deleteData(id: string): Promise<void> {
|
|
64
|
+
try {
|
|
65
|
+
this.setLoading(true);
|
|
66
|
+
this.setError(null);
|
|
67
|
+
|
|
68
|
+
await this.adapter.deleteData(id);
|
|
69
|
+
|
|
70
|
+
// Remove from local data
|
|
71
|
+
this.data = this.data.filter(item => this.getRowId(item) !== id);
|
|
72
|
+
|
|
73
|
+
this.clearCache();
|
|
74
|
+
} catch (error) {
|
|
75
|
+
this.setError(error as Error);
|
|
76
|
+
throw error;
|
|
77
|
+
} finally {
|
|
78
|
+
this.setLoading(false);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Create a new data item
|
|
84
|
+
*/
|
|
85
|
+
async createData(data: Partial<TData>): Promise<TData> {
|
|
86
|
+
try {
|
|
87
|
+
this.setLoading(true);
|
|
88
|
+
this.setError(null);
|
|
89
|
+
|
|
90
|
+
const newItem = await this.adapter.createData(data);
|
|
91
|
+
|
|
92
|
+
// Add to local data
|
|
93
|
+
this.data.push(newItem);
|
|
94
|
+
|
|
95
|
+
this.clearCache();
|
|
96
|
+
return newItem;
|
|
97
|
+
} catch (error) {
|
|
98
|
+
this.setError(error as Error);
|
|
99
|
+
throw error;
|
|
100
|
+
} finally {
|
|
101
|
+
this.setLoading(false);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get cached data if available
|
|
108
|
+
*/
|
|
109
|
+
async getCachedData(cacheKey: string): Promise<TData[] | null> {
|
|
110
|
+
const cached = this.cache.get(cacheKey);
|
|
111
|
+
if (!cached) return null;
|
|
112
|
+
|
|
113
|
+
const isExpired = Date.now() - cached.timestamp > this.cacheTimeout;
|
|
114
|
+
if (isExpired) {
|
|
115
|
+
this.cache.delete(cacheKey);
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return cached.data;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Set cached data
|
|
124
|
+
*/
|
|
125
|
+
setCachedData(cacheKey: string, data: TData[]): void {
|
|
126
|
+
this.cache.set(cacheKey, {
|
|
127
|
+
data,
|
|
128
|
+
timestamp: Date.now(),
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Clear all cached data
|
|
134
|
+
*/
|
|
135
|
+
clearCache(): void {
|
|
136
|
+
this.cache.clear();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Check if currently loading
|
|
141
|
+
*/
|
|
142
|
+
isLoading(): boolean {
|
|
143
|
+
return this._isLoading;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get current error
|
|
148
|
+
*/
|
|
149
|
+
getError(): Error | null {
|
|
150
|
+
return this.error;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Set loading state
|
|
155
|
+
*/
|
|
156
|
+
private setLoading(loading: boolean): void {
|
|
157
|
+
this._isLoading = loading;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Set error state
|
|
162
|
+
*/
|
|
163
|
+
private setError(error: Error | null): void {
|
|
164
|
+
this.error = error;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get row ID from data item
|
|
169
|
+
*/
|
|
170
|
+
private getRowId(item: TData): string {
|
|
171
|
+
return (item as any).id || (item as any).key || JSON.stringify(item);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Set adapter
|
|
176
|
+
*/
|
|
177
|
+
setAdapter(adapter: DataAdapter<TData>): void {
|
|
178
|
+
this.adapter = adapter;
|
|
179
|
+
this.clearCache();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Get adapter
|
|
184
|
+
*/
|
|
185
|
+
getAdapter(): DataAdapter<TData> {
|
|
186
|
+
return this.adapter;
|
|
187
|
+
}
|
|
188
|
+
}
|