@jmruthers/pace-core 0.5.4 → 0.5.6
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-ZQDRE46Q.js → DataTable-BEMN72L5.js} +2 -2
- package/dist/{chunk-5H3C2SWM.js → chunk-4EIBJ6DF.js} +2 -2
- package/dist/{chunk-M4RW7PIP.js → chunk-SFGUMWEE.js} +105 -81
- package/dist/chunk-SFGUMWEE.js.map +1 -0
- package/dist/components.js +2 -2
- package/dist/index.js +2 -2
- package/dist/utils.js +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/interfaces/AggregateConfig.md +1 -1
- 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/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +34 -34
- 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/EventContextType.md +1 -1
- package/docs/api/interfaces/EventLogoProps.md +1 -1
- package/docs/api/interfaces/EventProviderProps.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.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/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/RBACContextType.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACProviderProps.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.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/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/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/UsePublicEventLogoOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.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 +3 -3
- package/docs/implementation-guides/data-tables.md +20 -0
- package/docs/quick-reference.md +9 -0
- package/docs/rbac/examples.md +4 -0
- package/package.json +1 -1
- package/src/__tests__/helpers/test-utils.tsx +147 -1
- package/src/components/DataTable/DataTable.tsx +20 -0
- package/src/components/DataTable/__tests__/DataTable.hooks.test 2.tsx +191 -0
- package/src/components/DataTable/__tests__/DataTable.hooks.test.tsx +191 -0
- package/src/components/DataTable/components/DataTableCore.tsx +164 -131
- package/src/hooks/__tests__/hooks.integration.test.tsx +575 -0
- package/src/hooks/__tests__/useApiFetch.unit.test.ts +115 -0
- package/src/hooks/__tests__/useComponentPerformance.unit.test.tsx +133 -0
- package/src/hooks/__tests__/useDebounce.unit.test.ts +82 -0
- package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +293 -0
- package/src/hooks/__tests__/useInactivityTracker.unit.test.ts +385 -0
- package/src/hooks/__tests__/useOrganisationPermissions.unit.test.tsx +286 -0
- package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +838 -0
- package/src/hooks/__tests__/usePermissionCache.simple.test.ts +104 -0
- package/src/hooks/__tests__/usePermissionCache.unit.test.ts +633 -0
- package/src/hooks/__tests__/useRBAC.unit.test.ts +856 -0
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +537 -0
- package/src/hooks/__tests__/useToast.unit.test.tsx +62 -0
- package/src/hooks/__tests__/useZodForm.unit.test.tsx +37 -0
- package/src/rbac/utils/__tests__/eventContext.test.ts +428 -0
- package/src/rbac/utils/__tests__/eventContext.unit.test.ts +428 -0
- package/src/utils/__tests__/appConfig.unit.test.ts +55 -0
- package/src/utils/__tests__/audit.unit.test.ts +69 -0
- package/src/utils/__tests__/auth-utils.unit.test.ts +70 -0
- package/src/utils/__tests__/bundleAnalysis.unit.test.ts +317 -0
- package/src/utils/__tests__/cn.unit.test.ts +34 -0
- package/src/utils/__tests__/deviceFingerprint.unit.test.ts +503 -0
- package/src/utils/__tests__/dynamicUtils.unit.test.ts +322 -0
- package/src/utils/__tests__/formatDate.unit.test.ts +109 -0
- package/src/utils/__tests__/formatting.unit.test.ts +66 -0
- package/src/utils/__tests__/index.unit.test.ts +251 -0
- package/src/utils/__tests__/lazyLoad.unit.test.tsx +309 -0
- package/src/utils/__tests__/organisationContext.unit.test.ts +192 -0
- package/src/utils/__tests__/performanceBudgets.unit.test.ts +259 -0
- package/src/utils/__tests__/permissionTypes.unit.test.ts +250 -0
- package/src/utils/__tests__/permissionUtils.unit.test.ts +362 -0
- package/src/utils/__tests__/sanitization.unit.test.ts +346 -0
- package/src/utils/__tests__/schemaUtils.unit.test.ts +441 -0
- package/src/utils/__tests__/secureDataAccess.unit.test.ts +334 -0
- package/src/utils/__tests__/secureErrors.unit.test.ts +377 -0
- package/src/utils/__tests__/secureStorage.unit.test.ts +293 -0
- package/src/utils/__tests__/security.unit.test.ts +127 -0
- package/src/utils/__tests__/securityMonitor.unit.test.ts +280 -0
- package/src/utils/__tests__/sessionTracking.unit.test.ts +356 -0
- package/src/utils/__tests__/validation.unit.test.ts +84 -0
- package/src/utils/__tests__/validationUtils.unit.test.ts +571 -0
- package/src/validation/__tests__/common.unit.test.ts +101 -0
- package/src/validation/__tests__/csrf.unit.test.ts +302 -0
- package/src/validation/__tests__/passwordSchema.unit.test 2.ts +98 -0
- package/src/validation/__tests__/passwordSchema.unit.test.ts +98 -0
- package/src/validation/__tests__/sqlInjectionProtection.unit.test.ts +466 -0
- package/dist/chunk-M4RW7PIP.js.map +0 -1
- /package/dist/{DataTable-ZQDRE46Q.js.map → DataTable-BEMN72L5.js.map} +0 -0
- /package/dist/{chunk-5H3C2SWM.js.map → chunk-4EIBJ6DF.js.map} +0 -0
|
@@ -171,66 +171,15 @@ function DataTableInternal<TData extends DataRecord>({
|
|
|
171
171
|
}: DataTableCoreProps<TData>) {
|
|
172
172
|
|
|
173
173
|
// ============================================================================
|
|
174
|
-
//
|
|
174
|
+
// ALL HOOKS MUST BE CALLED IN THE SAME ORDER EVERY RENDER
|
|
175
175
|
// ============================================================================
|
|
176
176
|
|
|
177
|
-
// MANDATORY: Get authenticated user
|
|
177
|
+
// MANDATORY: Get authenticated user - ALWAYS call this hook
|
|
178
178
|
const authResult = useUnifiedAuth();
|
|
179
179
|
const user = authResult.user;
|
|
180
180
|
|
|
181
|
-
// MANDATORY: Every DataTable must have a user and RBAC config
|
|
182
|
-
if (!user) {
|
|
183
|
-
throw new Error('DataTable requires authenticated user for RBAC');
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (!rbac?.resource) {
|
|
187
|
-
throw new Error('DataTable requires rbac.resource for permission checking');
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const scope = {
|
|
191
|
-
organisationId: user?.user_metadata?.organisationId || user?.app_metadata?.organisationId,
|
|
192
|
-
eventId: user?.user_metadata?.eventId || user?.app_metadata?.eventId,
|
|
193
|
-
appId: user?.user_metadata?.appId || user?.app_metadata?.appId,
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
// MANDATORY: Check all permissions upfront - ALWAYS enforce RBAC
|
|
197
|
-
const permissions = {
|
|
198
|
-
canRead: useCan(user.id, scope, `read:${rbac.resource}` as any, rbac.pageId),
|
|
199
|
-
canCreate: useCan(user.id, scope, `create:${rbac.resource}` as any, rbac.pageId),
|
|
200
|
-
canUpdate: useCan(user.id, scope, `update:${rbac.resource}` as any, rbac.pageId),
|
|
201
|
-
canDelete: useCan(user.id, scope, `delete:${rbac.resource}` as any, rbac.pageId),
|
|
202
|
-
canExport: useCan(user.id, scope, `manage:${rbac.resource}` as any, rbac.pageId), // Using manage for export/import
|
|
203
|
-
canImport: useCan(user.id, scope, `manage:${rbac.resource}` as any, rbac.pageId), // Using manage for export/import
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
// MANDATORY: No data access without read permission
|
|
207
|
-
if (!permissions.canRead.can) {
|
|
208
|
-
return <AccessDeniedPage resource={rbac?.resource || 'test-resource'} operation="read" />;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// MANDATORY: Features are automatically filtered by permissions
|
|
212
|
-
const secureFeatures: DataTableFeatureConfig = {
|
|
213
|
-
...features,
|
|
214
|
-
creation: features.creation && permissions.canCreate.can,
|
|
215
|
-
editing: features.editing && permissions.canUpdate.can,
|
|
216
|
-
deletion: features.deletion && permissions.canDelete.can,
|
|
217
|
-
deleteSelected: features.deleteSelected && permissions.canDelete.can,
|
|
218
|
-
export: features.export && permissions.canExport.can,
|
|
219
|
-
import: features.import && permissions.canImport.can,
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
// MANDATORY: Handlers are automatically secured
|
|
223
|
-
const secureHandlers = {
|
|
224
|
-
onEditRow: permissions.canUpdate.can ? onEditRow : undefined,
|
|
225
|
-
onDeleteRow: permissions.canDelete.can ? onDeleteRow : undefined,
|
|
226
|
-
onCreateRow: permissions.canCreate.can ? onCreateRow : undefined,
|
|
227
|
-
onImport: permissions.canImport.can ? onImport : undefined,
|
|
228
|
-
onExport: permissions.canExport.can ? onExport : undefined,
|
|
229
|
-
onDeleteSelected: permissions.canDelete.can ? onDeleteSelected : undefined,
|
|
230
|
-
};
|
|
231
|
-
|
|
232
181
|
// ============================================================================
|
|
233
|
-
// TABLE STATE MANAGEMENT (Single State Source)
|
|
182
|
+
// TABLE STATE MANAGEMENT (Single State Source) - ALWAYS call these hooks
|
|
234
183
|
// ============================================================================
|
|
235
184
|
|
|
236
185
|
const [sorting, setSorting] = useState<SortingState>([]);
|
|
@@ -257,7 +206,7 @@ function DataTableInternal<TData extends DataRecord>({
|
|
|
257
206
|
const [expanded, setExpanded] = useState<ExpandedState>({});
|
|
258
207
|
|
|
259
208
|
// ============================================================================
|
|
260
|
-
// PAGINATION STATE MANAGEMENT
|
|
209
|
+
// PAGINATION STATE MANAGEMENT - ALWAYS call these hooks
|
|
261
210
|
// ============================================================================
|
|
262
211
|
|
|
263
212
|
const [pagination, setPagination] = useState<PaginationState>({
|
|
@@ -266,7 +215,7 @@ function DataTableInternal<TData extends DataRecord>({
|
|
|
266
215
|
});
|
|
267
216
|
|
|
268
217
|
// ============================================================================
|
|
269
|
-
// EDITING STATE MANAGEMENT
|
|
218
|
+
// EDITING STATE MANAGEMENT - ALWAYS call these hooks
|
|
270
219
|
// ============================================================================
|
|
271
220
|
|
|
272
221
|
// Use the centralized state management hook
|
|
@@ -284,19 +233,19 @@ function DataTableInternal<TData extends DataRecord>({
|
|
|
284
233
|
const editingData = tableState.editingData;
|
|
285
234
|
|
|
286
235
|
// ============================================================================
|
|
287
|
-
// IMPORT MODAL STATE MANAGEMENT
|
|
236
|
+
// IMPORT MODAL STATE MANAGEMENT - ALWAYS call these hooks
|
|
288
237
|
// ============================================================================
|
|
289
238
|
|
|
290
239
|
const [showImportModal, setShowImportModal] = useState(false);
|
|
291
240
|
|
|
292
241
|
// ============================================================================
|
|
293
|
-
// FILTER ROW VISIBILITY STATE MANAGEMENT
|
|
242
|
+
// FILTER ROW VISIBILITY STATE MANAGEMENT - ALWAYS call these hooks
|
|
294
243
|
// ============================================================================
|
|
295
244
|
|
|
296
245
|
const [showFilterRow, setShowFilterRow] = useState(false);
|
|
297
246
|
|
|
298
247
|
// ============================================================================
|
|
299
|
-
// PERFORMANCE HOOK
|
|
248
|
+
// PERFORMANCE HOOK - ALWAYS call this hook
|
|
300
249
|
// ============================================================================
|
|
301
250
|
|
|
302
251
|
const performanceHook = useDataTablePerformance({
|
|
@@ -322,7 +271,7 @@ function DataTableInternal<TData extends DataRecord>({
|
|
|
322
271
|
} = performanceHook;
|
|
323
272
|
|
|
324
273
|
// ============================================================================
|
|
325
|
-
// HIERARCHICAL DATA VALIDATION AND PROCESSING
|
|
274
|
+
// HIERARCHICAL DATA VALIDATION AND PROCESSING - ALWAYS call these hooks
|
|
326
275
|
// ============================================================================
|
|
327
276
|
|
|
328
277
|
const hierarchicalValidation = useMemo(() => {
|
|
@@ -340,7 +289,7 @@ function DataTableInternal<TData extends DataRecord>({
|
|
|
340
289
|
}, [features.hierarchical, hierarchical?.enabled, hierarchicalValidation.isValid, data]);
|
|
341
290
|
|
|
342
291
|
// ============================================================================
|
|
343
|
-
// HIERARCHICAL STATE MANAGEMENT
|
|
292
|
+
// HIERARCHICAL STATE MANAGEMENT - ALWAYS call these hooks
|
|
344
293
|
// ============================================================================
|
|
345
294
|
|
|
346
295
|
const hierarchicalState = useHierarchicalState(
|
|
@@ -349,7 +298,7 @@ function DataTableInternal<TData extends DataRecord>({
|
|
|
349
298
|
);
|
|
350
299
|
|
|
351
300
|
// ============================================================================
|
|
352
|
-
// COLUMN ORDER PERSISTENCE
|
|
301
|
+
// COLUMN ORDER PERSISTENCE - ALWAYS call these hooks
|
|
353
302
|
// ============================================================================
|
|
354
303
|
|
|
355
304
|
const {
|
|
@@ -363,7 +312,45 @@ function DataTableInternal<TData extends DataRecord>({
|
|
|
363
312
|
});
|
|
364
313
|
|
|
365
314
|
// ============================================================================
|
|
366
|
-
//
|
|
315
|
+
// RBAC PERMISSION CHECKS - ALWAYS call these hooks
|
|
316
|
+
// ============================================================================
|
|
317
|
+
|
|
318
|
+
// MANDATORY: Check all permissions upfront - ALWAYS enforce RBAC
|
|
319
|
+
const permissions = {
|
|
320
|
+
canRead: useCan(user?.id || '', {
|
|
321
|
+
organisationId: user?.user_metadata?.organisationId || user?.app_metadata?.organisationId,
|
|
322
|
+
eventId: user?.user_metadata?.eventId || user?.app_metadata?.eventId,
|
|
323
|
+
appId: user?.user_metadata?.appId || user?.app_metadata?.appId,
|
|
324
|
+
}, `read:${rbac.resource}` as any, rbac.pageId),
|
|
325
|
+
canCreate: useCan(user?.id || '', {
|
|
326
|
+
organisationId: user?.user_metadata?.organisationId || user?.app_metadata?.organisationId,
|
|
327
|
+
eventId: user?.user_metadata?.eventId || user?.app_metadata?.eventId,
|
|
328
|
+
appId: user?.user_metadata?.appId || user?.app_metadata?.appId,
|
|
329
|
+
}, `create:${rbac.resource}` as any, rbac.pageId),
|
|
330
|
+
canUpdate: useCan(user?.id || '', {
|
|
331
|
+
organisationId: user?.user_metadata?.organisationId || user?.app_metadata?.organisationId,
|
|
332
|
+
eventId: user?.user_metadata?.eventId || user?.app_metadata?.eventId,
|
|
333
|
+
appId: user?.user_metadata?.appId || user?.app_metadata?.appId,
|
|
334
|
+
}, `update:${rbac.resource}` as any, rbac.pageId),
|
|
335
|
+
canDelete: useCan(user?.id || '', {
|
|
336
|
+
organisationId: user?.user_metadata?.organisationId || user?.app_metadata?.organisationId,
|
|
337
|
+
eventId: user?.user_metadata?.eventId || user?.app_metadata?.eventId,
|
|
338
|
+
appId: user?.user_metadata?.appId || user?.app_metadata?.appId,
|
|
339
|
+
}, `delete:${rbac.resource}` as any, rbac.pageId),
|
|
340
|
+
canExport: useCan(user?.id || '', {
|
|
341
|
+
organisationId: user?.user_metadata?.organisationId || user?.app_metadata?.organisationId,
|
|
342
|
+
eventId: user?.user_metadata?.eventId || user?.app_metadata?.eventId,
|
|
343
|
+
appId: user?.user_metadata?.appId || user?.app_metadata?.appId,
|
|
344
|
+
}, `manage:${rbac.resource}` as any, rbac.pageId), // Using manage for export/import
|
|
345
|
+
canImport: useCan(user?.id || '', {
|
|
346
|
+
organisationId: user?.user_metadata?.organisationId || user?.app_metadata?.organisationId,
|
|
347
|
+
eventId: user?.user_metadata?.eventId || user?.app_metadata?.eventId,
|
|
348
|
+
appId: user?.user_metadata?.appId || user?.app_metadata?.appId,
|
|
349
|
+
}, `manage:${rbac.resource}` as any, rbac.pageId), // Using manage for export/import
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
// ============================================================================
|
|
353
|
+
// CONFIGURATION RESOLUTION - ALWAYS call these hooks
|
|
367
354
|
// ============================================================================
|
|
368
355
|
|
|
369
356
|
const finalPaginationMode = paginationMode || detectedMode;
|
|
@@ -393,7 +380,7 @@ function DataTableInternal<TData extends DataRecord>({
|
|
|
393
380
|
const isLoading = externalIsLoading || performanceLoading;
|
|
394
381
|
|
|
395
382
|
// ============================================================================
|
|
396
|
-
// DATA PROCESSING
|
|
383
|
+
// DATA PROCESSING - ALWAYS call these hooks
|
|
397
384
|
// ============================================================================
|
|
398
385
|
|
|
399
386
|
// Data comparison utility to prevent unnecessary re-renders
|
|
@@ -459,7 +446,7 @@ function DataTableInternal<TData extends DataRecord>({
|
|
|
459
446
|
}, [finalPaginationMode, serverData?.totalCount, data?.length]);
|
|
460
447
|
|
|
461
448
|
// ============================================================================
|
|
462
|
-
// HIERARCHICAL SORTING
|
|
449
|
+
// HIERARCHICAL SORTING - ALWAYS call these hooks
|
|
463
450
|
// ============================================================================
|
|
464
451
|
|
|
465
452
|
const sortedTableData = useMemo(() => {
|
|
@@ -479,9 +466,101 @@ function DataTableInternal<TData extends DataRecord>({
|
|
|
479
466
|
const finalDataCount = finalPaginationMode === 'server' ? (serverData?.totalCount || 0) : finalTableData?.length || 0;
|
|
480
467
|
|
|
481
468
|
// ============================================================================
|
|
482
|
-
// ACTIONS PROCESSING
|
|
469
|
+
// ACTIONS PROCESSING - ALWAYS call these hooks
|
|
470
|
+
// ============================================================================
|
|
471
|
+
|
|
483
472
|
// ============================================================================
|
|
473
|
+
// COLUMN PROCESSING - ALWAYS call these hooks
|
|
474
|
+
// ============================================================================
|
|
475
|
+
|
|
476
|
+
// ============================================================================
|
|
477
|
+
// TABLE CONFIGURATION - ALWAYS call these hooks
|
|
478
|
+
// ============================================================================
|
|
479
|
+
|
|
480
|
+
// ============================================================================
|
|
481
|
+
// SEARCH HANDLERS - ALWAYS call these hooks
|
|
482
|
+
// ============================================================================
|
|
483
|
+
|
|
484
|
+
const handleSearch = useCallback((value: string) => {
|
|
485
|
+
setSearchQuery(value);
|
|
486
|
+
|
|
487
|
+
if (features.pagination) {
|
|
488
|
+
setPagination(prev => ({ ...prev, pageIndex: 0 }));
|
|
489
|
+
}
|
|
490
|
+
}, [setSearchQuery, features.pagination]);
|
|
491
|
+
|
|
492
|
+
// ============================================================================
|
|
493
|
+
// SERVER-SIDE DATA FETCHING - ALWAYS call these hooks
|
|
494
|
+
// ============================================================================
|
|
495
|
+
|
|
496
|
+
const handleServerSideChange = useCallback(async () => {
|
|
497
|
+
if (finalPaginationMode !== 'server' || !serverSide) return;
|
|
498
|
+
|
|
499
|
+
const params: ServerSideParams = {
|
|
500
|
+
pageIndex: pagination.pageIndex,
|
|
501
|
+
pageSize: pagination.pageSize,
|
|
502
|
+
sorting,
|
|
503
|
+
columnFilters,
|
|
504
|
+
globalFilter: searchQuery,
|
|
505
|
+
grouping,
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
await fetchServerData(params);
|
|
509
|
+
}, [
|
|
510
|
+
finalPaginationMode,
|
|
511
|
+
serverSide,
|
|
512
|
+
pagination,
|
|
513
|
+
sorting,
|
|
514
|
+
columnFilters,
|
|
515
|
+
searchQuery,
|
|
516
|
+
grouping,
|
|
517
|
+
fetchServerData,
|
|
518
|
+
]);
|
|
519
|
+
|
|
520
|
+
// ============================================================================
|
|
521
|
+
// EFFECTS - ALWAYS call these hooks
|
|
522
|
+
// ============================================================================
|
|
523
|
+
|
|
524
|
+
useEffect(() => {
|
|
525
|
+
if ((tableData?.length || 0) > 0 || finalPaginationMode === 'server') {
|
|
526
|
+
handleServerSideChange();
|
|
527
|
+
}
|
|
528
|
+
}, [handleServerSideChange, tableData?.length, finalPaginationMode]);
|
|
484
529
|
|
|
530
|
+
useEffect(() => {
|
|
531
|
+
return () => {
|
|
532
|
+
if (typeof cleanup === 'function') {
|
|
533
|
+
cleanup();
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
}, [cleanup]);
|
|
537
|
+
|
|
538
|
+
// ============================================================================
|
|
539
|
+
// RBAC VALIDATION AND SECURE CONFIGURATION - ALWAYS call these hooks
|
|
540
|
+
// ============================================================================
|
|
541
|
+
|
|
542
|
+
// MANDATORY: Features are automatically filtered by permissions
|
|
543
|
+
const secureFeatures: DataTableFeatureConfig = useMemo(() => ({
|
|
544
|
+
...features,
|
|
545
|
+
creation: features.creation && permissions.canCreate.can,
|
|
546
|
+
editing: features.editing && permissions.canUpdate.can,
|
|
547
|
+
deletion: features.deletion && permissions.canDelete.can,
|
|
548
|
+
deleteSelected: features.deleteSelected && permissions.canDelete.can,
|
|
549
|
+
export: features.export && permissions.canExport.can,
|
|
550
|
+
import: features.import && permissions.canImport.can,
|
|
551
|
+
}), [features, permissions.canCreate.can, permissions.canUpdate.can, permissions.canDelete.can, permissions.canExport.can, permissions.canImport.can]);
|
|
552
|
+
|
|
553
|
+
// MANDATORY: Handlers are automatically secured
|
|
554
|
+
const secureHandlers = useMemo(() => ({
|
|
555
|
+
onEditRow: permissions.canUpdate.can ? onEditRow : undefined,
|
|
556
|
+
onDeleteRow: permissions.canDelete.can ? onDeleteRow : undefined,
|
|
557
|
+
onCreateRow: permissions.canCreate.can ? onCreateRow : undefined,
|
|
558
|
+
onImport: permissions.canImport.can ? onImport : undefined,
|
|
559
|
+
onExport: permissions.canExport.can ? onExport : undefined,
|
|
560
|
+
onDeleteSelected: permissions.canDelete.can ? onDeleteSelected : undefined,
|
|
561
|
+
}), [permissions.canUpdate.can, permissions.canDelete.can, permissions.canCreate.can, permissions.canImport.can, permissions.canExport.can, onEditRow, onDeleteRow, onCreateRow, onImport, onExport, onDeleteSelected]);
|
|
562
|
+
|
|
563
|
+
// MANDATORY: Process actions with RBAC checks
|
|
485
564
|
const effectiveActions = useMemo(() => {
|
|
486
565
|
// Create a new array to avoid mutating the original
|
|
487
566
|
const result = [...actions];
|
|
@@ -525,10 +604,7 @@ function DataTableInternal<TData extends DataRecord>({
|
|
|
525
604
|
return result;
|
|
526
605
|
}, [actions, secureFeatures, permissions, secureHandlers, getRowId, tableActions]);
|
|
527
606
|
|
|
528
|
-
//
|
|
529
|
-
// COLUMN PROCESSING
|
|
530
|
-
// ============================================================================
|
|
531
|
-
|
|
607
|
+
// MANDATORY: Process columns with actions
|
|
532
608
|
const enhancedColumns = useMemo(() => {
|
|
533
609
|
// Create enhanced base columns
|
|
534
610
|
const baseColumns: ColumnDef<TData>[] = [...columns].map(column => ({
|
|
@@ -629,16 +705,7 @@ function DataTableInternal<TData extends DataRecord>({
|
|
|
629
705
|
return finalColumns;
|
|
630
706
|
}, [columns, features, effectiveActions, columnOrder]);
|
|
631
707
|
|
|
632
|
-
//
|
|
633
|
-
// COLUMN WIDTHS
|
|
634
|
-
// ============================================================================
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
// ============================================================================
|
|
638
|
-
// TABLE CONFIGURATION
|
|
639
|
-
// ============================================================================
|
|
640
|
-
|
|
641
|
-
// Memoize table configuration to prevent unnecessary re-creation
|
|
708
|
+
// MANDATORY: Memoize table configuration to prevent unnecessary re-creation
|
|
642
709
|
const tableConfig = useMemo(() => ({
|
|
643
710
|
data: finalTableData as TData[],
|
|
644
711
|
columns: enhancedColumns,
|
|
@@ -706,62 +773,28 @@ function DataTableInternal<TData extends DataRecord>({
|
|
|
706
773
|
const table = useReactTable(tableConfig);
|
|
707
774
|
|
|
708
775
|
// ============================================================================
|
|
709
|
-
//
|
|
776
|
+
// RBAC VALIDATION AND EARLY RETURNS - AFTER ALL HOOKS
|
|
710
777
|
// ============================================================================
|
|
711
778
|
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
setPagination(prev => ({ ...prev, pageIndex: 0 }));
|
|
717
|
-
}
|
|
718
|
-
}, [setSearchQuery, features.pagination]);
|
|
719
|
-
|
|
720
|
-
// ============================================================================
|
|
721
|
-
// SERVER-SIDE DATA FETCHING
|
|
722
|
-
// ============================================================================
|
|
779
|
+
// MANDATORY: Every DataTable must have a user and RBAC config
|
|
780
|
+
if (!user) {
|
|
781
|
+
throw new Error('DataTable requires authenticated user for RBAC');
|
|
782
|
+
}
|
|
723
783
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
const params: ServerSideParams = {
|
|
728
|
-
pageIndex: pagination.pageIndex,
|
|
729
|
-
pageSize: pagination.pageSize,
|
|
730
|
-
sorting,
|
|
731
|
-
columnFilters,
|
|
732
|
-
globalFilter: searchQuery,
|
|
733
|
-
grouping,
|
|
734
|
-
};
|
|
735
|
-
|
|
736
|
-
await fetchServerData(params);
|
|
737
|
-
}, [
|
|
738
|
-
finalPaginationMode,
|
|
739
|
-
serverSide,
|
|
740
|
-
pagination,
|
|
741
|
-
sorting,
|
|
742
|
-
columnFilters,
|
|
743
|
-
searchQuery,
|
|
744
|
-
grouping,
|
|
745
|
-
fetchServerData,
|
|
746
|
-
]);
|
|
784
|
+
if (!rbac?.resource) {
|
|
785
|
+
throw new Error('DataTable requires rbac.resource for permission checking');
|
|
786
|
+
}
|
|
747
787
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
if ((tableData?.length || 0) > 0 || finalPaginationMode === 'server') {
|
|
754
|
-
handleServerSideChange();
|
|
755
|
-
}
|
|
756
|
-
}, [handleServerSideChange, tableData?.length, finalPaginationMode]);
|
|
788
|
+
const scope = {
|
|
789
|
+
organisationId: user?.user_metadata?.organisationId || user?.app_metadata?.organisationId,
|
|
790
|
+
eventId: user?.user_metadata?.eventId || user?.app_metadata?.eventId,
|
|
791
|
+
appId: user?.user_metadata?.appId || user?.app_metadata?.appId,
|
|
792
|
+
};
|
|
757
793
|
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
}
|
|
763
|
-
};
|
|
764
|
-
}, [cleanup]);
|
|
794
|
+
// MANDATORY: No data access without read permission
|
|
795
|
+
if (!permissions.canRead.can) {
|
|
796
|
+
return <AccessDeniedPage resource={rbac?.resource || 'test-resource'} operation="read" />;
|
|
797
|
+
}
|
|
765
798
|
|
|
766
799
|
// ============================================================================
|
|
767
800
|
// RENDER
|