@jmruthers/pace-core 0.5.75 → 0.5.77
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/CHANGELOG.md +8 -0
- package/dist/{RBACService-C4udt_Zp.d.ts → AuthService-SBHZQtCH.d.ts} +5 -118
- package/dist/{DataTable-ntgmhO2W.d.ts → DataTable-BE0OXZKQ.d.ts} +9 -2
- package/dist/DataTable-QCNCV6IK.js +157 -0
- package/dist/{PublicLoadingSpinner-BKNBT6b6.d.ts → PublicLoadingSpinner-CnUaz0vG.d.ts} +33 -19
- package/dist/{UnifiedAuthProvider-Bj6YCf7c.d.ts → UnifiedAuthProvider-B391Aqum.d.ts} +42 -45
- package/dist/{UnifiedAuthProvider-3NKDOSOK.js → UnifiedAuthProvider-Z2FWNW7O.js} +4 -5
- package/dist/{api-DDMUKIUD.js → api-KG4A2X7P.js} +9 -3
- package/dist/{audit-6TOCAMKO.js → audit-65VNHEV2.js} +2 -2
- package/dist/{chunk-2DFZ432F.js → chunk-7PX43UYN.js} +197 -629
- package/dist/chunk-7PX43UYN.js.map +1 -0
- package/dist/{chunk-DAXLNIDY.js → chunk-C4RQ3GQA.js} +108 -32
- package/dist/chunk-C4RQ3GQA.js.map +1 -0
- package/dist/{chunk-LW7MMEAQ.js → chunk-CRKP3HXI.js} +2 -2
- package/dist/{chunk-XLZ7U46Z.js → chunk-CVMVPYAL.js} +9 -60
- package/dist/chunk-CVMVPYAL.js.map +1 -0
- package/dist/{chunk-CY3AHGO4.js → chunk-DDPG7FCX.js} +3395 -3254
- package/dist/chunk-DDPG7FCX.js.map +1 -0
- package/dist/{chunk-URUTVZ7N.js → chunk-DVHZ5L55.js} +2 -2
- package/dist/{chunk-5BSLGBYI.js → chunk-JCQZ6LA7.js} +2 -8
- package/dist/{chunk-5BSLGBYI.js.map → chunk-JCQZ6LA7.js.map} +1 -1
- package/dist/{chunk-WN6XJWOS.js → chunk-JDQ7T3QB.js} +256 -743
- package/dist/chunk-JDQ7T3QB.js.map +1 -0
- package/dist/{chunk-ZTT2AXMX.js → chunk-LMYTEMUH.js} +153 -132
- package/dist/chunk-LMYTEMUH.js.map +1 -0
- package/dist/{chunk-33PHABLB.js → chunk-NKT2DLZI.js} +13 -130
- package/dist/chunk-NKT2DLZI.js.map +1 -0
- package/dist/chunk-PUKTJMRT.js +732 -0
- package/dist/chunk-PUKTJMRT.js.map +1 -0
- package/dist/{chunk-B2WTCLCV.js → chunk-Q7APDV6H.js} +18 -8
- package/dist/chunk-Q7APDV6H.js.map +1 -0
- package/dist/{chunk-FGMFQSHX.js → chunk-S63MFSY6.js} +500 -551
- package/dist/chunk-S63MFSY6.js.map +1 -0
- package/dist/{chunk-NTNILOBC.js → chunk-TLD5BEU6.js} +4 -4
- package/dist/chunk-WUXCWRL6.js +20 -0
- package/dist/chunk-WUXCWRL6.js.map +1 -0
- package/dist/{chunk-YNUBMSMV.js → chunk-YCKPEMJA.js} +186 -263
- package/dist/chunk-YCKPEMJA.js.map +1 -0
- package/dist/{chunk-A4FUBC7B.js → chunk-Z3T6RK3K.js} +2 -4
- package/dist/{chunk-A4FUBC7B.js.map → chunk-Z3T6RK3K.js.map} +1 -1
- package/dist/components.d.ts +6 -6
- package/dist/components.js +17 -20
- package/dist/components.js.map +1 -1
- package/dist/{database-C3Szpi5J.d.ts → database-BXAfr2Y_.d.ts} +18 -0
- package/dist/hooks.d.ts +21 -44
- package/dist/hooks.js +12 -13
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +19 -27
- package/dist/index.js +27 -33
- package/dist/index.js.map +1 -1
- package/dist/{organisation-BtshODVF.d.ts → organisation-D6qRDtbF.d.ts} +1 -1
- package/dist/providers.d.ts +7 -21
- package/dist/providers.js +3 -10
- package/dist/rbac/index.d.ts +118 -215
- package/dist/rbac/index.js +18 -18
- package/dist/{types-CGX9Vyf5.d.ts → types-BDg1mAGG.d.ts} +36 -6
- package/dist/types.d.ts +3 -3
- package/dist/types.js +61 -18
- package/dist/types.js.map +1 -1
- package/dist/{unified-CM7T0aTK.d.ts → unified-DQ4VcT7H.d.ts} +1 -1
- package/dist/{usePublicRouteParams-B-CumWRc.d.ts → usePublicRouteParams-BlgwXweB.d.ts} +3 -3
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +52 -9
- package/dist/utils.js.map +1 -1
- package/docs/CONTENT_AUDIT_REPORT.md +253 -0
- package/docs/DOCUMENTATION_AUDIT.md +172 -0
- package/docs/README.md +142 -147
- package/docs/STYLE_GUIDE.md +37 -0
- package/docs/api/classes/ColumnFactory.md +17 -17
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +4 -4
- package/docs/api/classes/MissingUserContextError.md +4 -4
- package/docs/api/classes/OrganisationContextRequiredError.md +4 -4
- package/docs/api/classes/PermissionDeniedError.md +5 -5
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +8 -8
- package/docs/api/classes/RBACCache.md +35 -5
- package/docs/api/classes/RBACEngine.md +49 -20
- package/docs/api/classes/RBACError.md +4 -4
- package/docs/api/classes/RBACNotInitializedError.md +4 -4
- 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 +4 -4
- 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 +11 -0
- package/docs/api/interfaces/DataTableAction.md +65 -29
- package/docs/api/interfaces/DataTableColumn.md +36 -23
- package/docs/api/interfaces/DataTableProps.md +80 -38
- package/docs/api/interfaces/DataTableToolbarButton.md +7 -7
- package/docs/api/interfaces/EmptyStateConfig.md +5 -5
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventLogoProps.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/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 +11 -11
- package/docs/api/interfaces/NavigationContextType.md +9 -9
- 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 +7 -7
- 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 +16 -3
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +2 -2
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +4 -4
- 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/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +2 -2
- package/docs/api/interfaces/RouteConfig.md +2 -2
- 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/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 +94 -521
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +16 -16
- 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/UseResolvedScopeOptions.md +47 -0
- package/docs/api/interfaces/UseResolvedScopeReturn.md +47 -0
- package/docs/api/interfaces/UserEventAccess.md +11 -11
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +303 -275
- package/docs/api-reference/components.md +193 -0
- package/docs/api-reference/hooks.md +265 -0
- package/docs/api-reference/providers.md +32 -7
- package/docs/api-reference/types.md +6 -0
- package/docs/api-reference/utilities.md +207 -0
- package/docs/architecture/README.md +6 -0
- package/docs/{database-schema-requirements.md → architecture/database-schema-requirements.md} +6 -0
- package/docs/architecture/rbac-security-architecture.md +258 -0
- package/docs/architecture/services.md +9 -1
- package/docs/best-practices/README.md +26 -0
- package/docs/best-practices/accessibility.md +572 -0
- package/docs/{common-patterns.md → best-practices/common-patterns.md} +6 -0
- package/docs/best-practices/deployment.md +6 -0
- package/docs/best-practices/performance.md +475 -2
- package/docs/best-practices/security.md +6 -0
- package/docs/best-practices/testing.md +6 -0
- package/docs/core-concepts/authentication.md +21 -7
- package/docs/core-concepts/events.md +6 -0
- package/docs/core-concepts/organisations.md +6 -0
- package/docs/core-concepts/permissions.md +6 -0
- package/docs/core-concepts/rbac-system.md +6 -0
- package/docs/documentation-index.md +121 -182
- package/docs/{consuming-app-vite-config.md → getting-started/consuming-app-vite-config.md} +6 -0
- package/docs/getting-started/documentation-index.md +40 -0
- package/docs/getting-started/examples/README.md +878 -35
- package/docs/{faq.md → getting-started/faq.md} +7 -1
- package/docs/getting-started/installation-guide.md +6 -0
- package/docs/{quick-reference.md → getting-started/quick-reference.md} +6 -0
- package/docs/implementation-guides/app-layout.md +6 -0
- package/docs/implementation-guides/authentication.md +1021 -0
- package/docs/implementation-guides/component-styling.md +416 -0
- package/docs/implementation-guides/data-tables.md +1264 -2076
- package/docs/implementation-guides/dynamic-colors.md +6 -0
- package/docs/implementation-guides/event-theming-summary.md +6 -0
- package/docs/{file-reference-system.md → implementation-guides/file-reference-system.md} +6 -0
- package/docs/implementation-guides/file-upload-storage.md +6 -0
- package/docs/implementation-guides/forms.md +6 -0
- package/docs/implementation-guides/inactivity-tracking.md +6 -0
- package/docs/implementation-guides/navigation.md +6 -0
- package/docs/implementation-guides/organisation-security.md +6 -0
- package/docs/implementation-guides/permission-enforcement.md +6 -0
- package/docs/implementation-guides/public-pages-advanced.md +6 -0
- package/docs/implementation-guides/public-pages.md +6 -0
- package/docs/migration/MIGRATION_GUIDE.md +827 -351
- package/docs/migration/README.md +7 -1
- package/docs/migration/organisation-context-timing-fix.md +6 -0
- package/docs/migration/rbac-migration.md +44 -1
- package/docs/migration/service-architecture.md +6 -0
- package/docs/migration/v0.4.15-tailwind-scanning.md +6 -0
- package/docs/migration/v0.4.16-css-first-approach.md +6 -0
- package/docs/migration/v0.4.17-source-path-fix.md +6 -0
- package/docs/rbac/README-rbac-rls-integration.md +6 -0
- package/docs/rbac/README.md +6 -0
- package/docs/rbac/advanced-patterns.md +6 -0
- package/docs/rbac/api-reference.md +7 -1
- package/docs/rbac/breaking-changes-v3.md +222 -0
- package/docs/rbac/examples/rbac-rls-integration-example.md +6 -0
- package/docs/rbac/examples.md +6 -0
- package/docs/rbac/getting-started.md +6 -0
- package/docs/rbac/migration-guide.md +260 -0
- package/docs/rbac/quick-start.md +6 -0
- package/docs/rbac/rbac-rls-integration.md +6 -0
- package/docs/rbac/super-admin-guide.md +6 -0
- package/docs/rbac/troubleshooting.md +6 -0
- package/docs/security/README.md +6 -0
- package/docs/security/checklist.md +6 -0
- package/docs/styles/README.md +7 -1
- package/docs/{usage.md → styles/usage.md} +6 -0
- package/docs/testing/README.md +6 -0
- package/docs/{visual-testing.md → testing/visual-testing.md} +6 -0
- package/docs/troubleshooting/README.md +387 -5
- package/docs/troubleshooting/cake-page-permission-guard-issue-summary.md +6 -0
- package/docs/troubleshooting/common-issues.md +6 -0
- package/docs/troubleshooting/database-view-compatibility.md +6 -0
- package/docs/troubleshooting/organisation-context-setup.md +6 -0
- package/docs/troubleshooting/react-hooks-issue-analysis.md +6 -0
- package/docs/troubleshooting/styling-issues.md +6 -0
- package/docs/troubleshooting/tailwind-content-scanning.md +6 -0
- package/package.json +1 -1
- package/src/__tests__/TEST_GUIDE_CURSOR.md +290 -0
- package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -1
- package/src/__tests__/helpers/supabaseMock.ts +48 -2
- package/src/__tests__/helpers/test-providers.tsx +3 -53
- package/src/components/DataTable/DataTable.test.tsx +319 -0
- package/src/components/DataTable/DataTable.tsx +32 -11
- package/src/components/DataTable/__tests__/{DataTable.comprehensive.test.tsx → DataTable.comprehensive.test.tsx.skip} +6 -4
- package/src/components/DataTable/__tests__/DataTable.default-state.test.tsx +17 -6
- package/src/components/DataTable/__tests__/{DataTable.test.tsx → DataTable.test.tsx.skip} +6 -4
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +96 -10
- package/src/components/DataTable/__tests__/a11y.basic.test.tsx +601 -0
- package/src/components/DataTable/__tests__/keyboard.test.tsx +615 -0
- package/src/components/DataTable/__tests__/pagination.modes.test.tsx +639 -0
- package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx.skip +330 -0
- package/src/components/DataTable/components/AccessDeniedPage.tsx +2 -2
- package/src/components/DataTable/components/ActionButtons.tsx +88 -104
- package/src/components/DataTable/components/DataTableCore.tsx +442 -665
- package/src/components/DataTable/components/DataTableErrorBoundary.tsx +4 -2
- package/src/components/DataTable/components/DataTableModals.tsx +22 -1
- package/src/components/DataTable/components/EditableRow.tsx +69 -84
- package/src/components/DataTable/components/EmptyState.tsx +5 -1
- package/src/components/DataTable/components/ImportModal.tsx +65 -36
- package/src/components/DataTable/components/PaginationControls.tsx +40 -100
- package/src/components/DataTable/components/UnifiedTableBody.tsx +222 -278
- package/src/components/DataTable/components/index.ts +1 -2
- package/src/components/DataTable/context/DataTableContext.tsx +1 -1
- package/src/components/DataTable/context/__tests__/DataTableContext.test.tsx +208 -275
- package/src/components/DataTable/core/ColumnFactory.ts +5 -0
- package/src/components/DataTable/core/index.ts +1 -8
- package/src/components/DataTable/examples/HierarchicalActionsExample.tsx +12 -10
- package/src/components/DataTable/examples/HierarchicalExample.tsx +1 -1
- package/src/components/DataTable/examples/InitialPageSizeExample.tsx +1 -0
- package/src/components/DataTable/examples/PerformanceExample.tsx +1 -0
- package/src/components/DataTable/hooks/__tests__/useColumnOrderPersistence.test.ts +521 -0
- package/src/components/DataTable/hooks/__tests__/useColumnReordering.test.ts +570 -0
- package/src/components/DataTable/hooks/__tests__/useColumnVisibilityPersistence.test.ts +167 -0
- package/src/components/DataTable/hooks/__tests__/useHierarchicalState.test.ts +214 -0
- package/src/components/DataTable/hooks/__tests__/useTableColumns.test.ts +224 -0
- package/src/components/DataTable/hooks/index.ts +13 -0
- package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +32 -15
- package/src/components/DataTable/hooks/useColumnReordering.ts +1 -0
- package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +102 -0
- package/src/components/DataTable/hooks/useDataTableConfiguration.ts +89 -0
- package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +117 -0
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +193 -0
- package/src/components/DataTable/hooks/useDataTableState.ts +51 -17
- package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +33 -0
- package/src/components/DataTable/hooks/useHierarchicalState.ts +41 -9
- package/src/components/DataTable/hooks/useKeyboardNavigation.ts +447 -0
- package/src/components/DataTable/hooks/useServerSideDataEffect.ts +94 -0
- package/src/components/DataTable/hooks/useTableColumns.ts +156 -0
- package/src/components/DataTable/hooks/useTableHandlers.ts +174 -0
- package/src/components/DataTable/index.ts +13 -12
- package/src/components/DataTable/types.ts +129 -9
- package/src/components/DataTable/utils/__tests__/COVERAGE_NOTE.md +89 -0
- package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +162 -28
- package/src/components/DataTable/utils/__tests__/flexibleImport.test.ts +573 -0
- package/src/components/DataTable/utils/__tests__/hierarchicalSorting.test.ts +247 -0
- package/src/components/DataTable/utils/__tests__/hierarchicalUtils.test.ts +8 -6
- package/src/components/DataTable/utils/__tests__/performanceUtils.test.ts +466 -0
- package/src/components/DataTable/utils/__tests__/rowUtils.test.ts +251 -0
- package/src/components/DataTable/utils/a11yUtils.ts +244 -0
- package/src/components/DataTable/utils/debugTools.ts +47 -21
- package/src/components/DataTable/utils/errorHandling.ts +52 -460
- package/src/components/DataTable/utils/exportUtils.ts +157 -28
- package/src/components/DataTable/utils/flexibleImport.ts +202 -32
- package/src/components/DataTable/utils/hierarchicalSorting.ts +50 -3
- package/src/components/DataTable/utils/hierarchicalUtils.ts +167 -34
- package/src/components/DataTable/utils/index.ts +7 -0
- package/src/components/DataTable/utils/paginationUtils.ts +350 -0
- package/src/components/DataTable/utils/rowUtils.ts +69 -0
- package/src/components/EventSelector/EventSelector.test.tsx +672 -0
- package/src/components/Label/__tests__/Label.test.tsx +434 -0
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +19 -24
- package/src/components/NavigationMenu/NavigationMenu.tsx +19 -8
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.security.test.tsx +1 -23
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +56 -6
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +137 -13
- package/src/components/PublicLayout/__tests__/PublicPageContextChecker.test.tsx +190 -0
- package/src/components/PublicLayout/__tests__/PublicPageDebugger.test.tsx +185 -0
- package/src/components/PublicLayout/__tests__/PublicPageHeader.test.tsx +1 -1
- package/src/components/PublicLayout/__tests__/PublicPageProvider.test.tsx +313 -0
- package/src/components/Select/Select.test.tsx +143 -120
- package/src/components/Select/Select.tsx +48 -212
- package/src/components/Select/hooks.ts +36 -1
- package/src/components/Select/index.ts +2 -1
- package/src/components/examples/PermissionExample.tsx +173 -0
- package/src/examples/CorrectPublicPageImplementation.tsx +301 -0
- package/src/examples/PublicEventPage.tsx +274 -0
- package/src/examples/PublicPageApp.tsx +308 -0
- package/src/examples/PublicPageUsageExample.tsx +216 -0
- package/src/hooks/__tests__/useOrganisationPermissions.unit.test.tsx +12 -1
- package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +129 -17
- package/src/hooks/__tests__/useRBAC.unit.test.ts +151 -846
- package/src/hooks/useOrganisationPermissions.test.ts +42 -18
- package/src/hooks/useOrganisationPermissions.ts +12 -6
- package/src/hooks/useOrganisationSecurity.test.ts +138 -85
- package/src/hooks/useOrganisationSecurity.ts +41 -10
- package/src/hooks/useSecureDataAccess.test.ts +32 -29
- package/src/index.ts +0 -1
- package/src/providers/AuthProvider.simplified.tsx +880 -0
- package/src/providers/UnifiedAuthProvider.test.simple.tsx +8 -8
- package/src/providers/__tests__/ProviderLifecycle.test.tsx +341 -0
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +29 -19
- package/src/providers/index.ts +0 -1
- package/src/providers/services/EventServiceProvider.tsx +19 -15
- package/src/providers/services/InactivityServiceProvider.tsx +19 -15
- package/src/providers/services/OrganisationServiceProvider.tsx +19 -15
- package/src/providers/services/UnifiedAuthProvider.tsx +156 -127
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +1 -1
- package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +3 -3
- package/src/rbac/README.md +1 -1
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +25 -27
- package/src/rbac/__tests__/auth-rbac-security.integration.test.tsx +313 -0
- package/src/rbac/__tests__/engine.comprehensive.test.ts +114 -348
- package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +28 -110
- package/src/rbac/__tests__/rbac-engine-simplified.test.ts +33 -85
- package/src/rbac/__tests__/scenarios.user-role.test.tsx +2 -2
- package/src/rbac/adapters.tsx +26 -69
- package/src/rbac/api.test.ts +90 -27
- package/src/rbac/api.ts +61 -10
- package/src/rbac/audit.test.ts +33 -38
- package/src/rbac/audit.ts +21 -6
- package/src/rbac/cache.ts +33 -1
- package/src/rbac/components/NavigationGuard.tsx +11 -11
- package/src/rbac/components/NavigationProvider.test.tsx +11 -5
- package/src/rbac/components/NavigationProvider.tsx +37 -13
- package/src/rbac/components/PagePermissionGuard.tsx +111 -50
- package/src/rbac/components/PagePermissionProvider.tsx +5 -5
- package/src/rbac/components/PermissionEnforcer.tsx +11 -11
- package/src/rbac/components/RoleBasedRouter.tsx +5 -5
- package/src/rbac/components/SecureDataProvider.tsx +5 -5
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +8 -8
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +14 -14
- package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +12 -12
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +6 -6
- package/src/rbac/engine.test.simple.ts +19 -13
- package/src/rbac/engine.test.ts +1 -0
- package/src/rbac/engine.ts +330 -766
- package/src/rbac/errors.ts +156 -0
- package/src/rbac/hooks/__tests__/usePermissions.integration.test.ts +437 -0
- package/src/rbac/hooks/index.ts +2 -0
- package/src/rbac/hooks/usePermissions.ts +32 -10
- package/src/rbac/hooks/useRBAC.test.ts +126 -512
- package/src/rbac/hooks/useRBAC.ts +147 -193
- package/src/rbac/hooks/useResolvedScope.ts +244 -0
- package/src/rbac/index.ts +7 -4
- package/src/rbac/security.ts +109 -18
- package/src/rbac/types.ts +12 -1
- package/src/services/AuthService.ts +2 -15
- package/src/services/EventService.ts +26 -46
- package/src/services/OrganisationService.ts +51 -31
- package/src/services/__tests__/AuthService.test.ts +1 -1
- package/src/services/__tests__/EventService.test.ts +1 -1
- package/src/services/__tests__/InactivityService.lifecycle.test.ts +411 -0
- package/src/services/__tests__/OrganisationService.pagination.test.ts +375 -0
- package/src/services/__tests__/OrganisationService.test.ts +1 -1
- package/src/styles/base.css +208 -0
- package/src/styles/semantic.css +24 -0
- package/src/types/__tests__/README.md +114 -0
- package/src/types/__tests__/validation.test.ts +731 -0
- package/src/types/database.generated.ts +7347 -0
- package/src/types/database.ts +20 -0
- package/src/utils/__tests__/file-reference.test.ts +383 -0
- package/src/utils/__tests__/performanceBenchmark.test.ts +175 -0
- package/src/utils/appNameResolver.test.ts +54 -0
- package/src/utils/logger.ts +179 -0
- package/src/utils/organisationContext.ts +11 -4
- package/src/utils/storage/__tests__/helpers.unit.test.ts +6 -2
- package/src/validation/__tests__/csrf.unit.test.ts +63 -0
- package/src/validation/__tests__/passwordSchema.unit.test.ts +105 -0
- package/dist/DataTable-HWZQGASI.js +0 -102
- package/dist/appNameResolver-UURKN7NF.js +0 -22
- package/dist/audit-6TOCAMKO.js.map +0 -1
- package/dist/chunk-2CHATWBF.js +0 -523
- package/dist/chunk-2CHATWBF.js.map +0 -1
- package/dist/chunk-2DFZ432F.js.map +0 -1
- package/dist/chunk-33PHABLB.js.map +0 -1
- package/dist/chunk-B2WTCLCV.js.map +0 -1
- package/dist/chunk-CY3AHGO4.js.map +0 -1
- package/dist/chunk-DAXLNIDY.js.map +0 -1
- package/dist/chunk-FGMFQSHX.js.map +0 -1
- package/dist/chunk-TYHR5X4W.js +0 -33
- package/dist/chunk-TYHR5X4W.js.map +0 -1
- package/dist/chunk-ULBI5JGB.js +0 -109
- package/dist/chunk-ULBI5JGB.js.map +0 -1
- package/dist/chunk-WN6XJWOS.js.map +0 -1
- package/dist/chunk-XLZ7U46Z.js.map +0 -1
- package/dist/chunk-YNUBMSMV.js.map +0 -1
- package/dist/chunk-ZTT2AXMX.js.map +0 -1
- package/dist/eventContext-BBA42P6G.js +0 -14
- package/dist/eventContext-BBA42P6G.js.map +0 -1
- package/docs/DOCUMENTATION_CHECKLIST.md +0 -281
- package/docs/api/interfaces/RBACContextType.md +0 -468
- package/docs/api/interfaces/RBACProviderProps.md +0 -107
- package/docs/breaking-changes.md +0 -179
- package/docs/consuming-app-example.md +0 -290
- package/docs/documentation-style-checklist.md +0 -294
- package/docs/examples/navigation-menu-auth-fix.md +0 -344
- package/docs/getting-started/examples/basic-auth-app.md +0 -520
- package/docs/getting-started/examples/full-featured-app.md +0 -616
- package/docs/getting-started/quick-start.md +0 -426
- package/docs/implementation-guides/datatable-filtering.md +0 -313
- package/docs/implementation-guides/datatable-rbac-usage.md +0 -317
- package/docs/implementation-guides/hierarchical-datatable.md +0 -850
- package/docs/implementation-guides/large-datasets.md +0 -281
- package/docs/implementation-guides/performance.md +0 -403
- package/docs/migration/quick-migration-guide.md +0 -320
- package/docs/migration-guide.md +0 -193
- package/docs/migration-guides/unified-auth-provider-mandatory-timeouts.md +0 -226
- package/docs/performance/README.md +0 -551
- package/docs/style-guide.md +0 -925
- package/docs/troubleshooting/authentication-issues.md +0 -334
- package/docs/troubleshooting/debugging.md +0 -1117
- package/docs/troubleshooting/migration.md +0 -918
- package/src/__tests__/hooks/usePermissions.test.ts +0 -261
- package/src/components/DataTable/components/DataTableBody.tsx +0 -488
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +0 -144
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +0 -515
- package/src/components/DataTable/core/ActionManager.ts +0 -235
- package/src/components/DataTable/core/ColumnManager.ts +0 -215
- package/src/components/DataTable/core/DataManager.ts +0 -188
- package/src/components/DataTable/core/DataTableContext.tsx +0 -181
- package/src/components/DataTable/core/LocalDataAdapter.ts +0 -264
- package/src/components/DataTable/core/PluginRegistry.ts +0 -229
- package/src/components/DataTable/core/StateManager.ts +0 -311
- package/src/components/DataTable/core/__tests__/ActionManager.test.ts +0 -634
- package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +0 -193
- package/src/components/DataTable/core/__tests__/DataManager.test.ts +0 -519
- package/src/components/DataTable/core/__tests__/StateManager.test.ts +0 -714
- package/src/components/DataTable/core/interfaces.ts +0 -338
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.rbac.test.tsx +0 -574
- package/src/components/Select/Select.bug-test.tsx +0 -69
- package/src/components/Select/Select.refactored.tsx +0 -497
- package/src/hooks/__tests__/ServiceHooks.test.tsx +0 -613
- package/src/hooks/services/usePermissions.ts +0 -70
- package/src/hooks/services/useRBACService.ts +0 -30
- package/src/hooks/usePermissionCheck.ts +0 -150
- package/src/providers/__tests__/ServiceProviders.test.tsx +0 -477
- package/src/providers/services/RBACServiceProvider.tsx +0 -79
- package/src/rbac/__tests__/integration.authflow.test.tsx +0 -119
- package/src/rbac/__tests__/integration.navigation.test.tsx +0 -69
- package/src/rbac/__tests__/integration.securedata.test.tsx +0 -92
- package/src/rbac/__tests__/integration.smoke.test.tsx +0 -73
- package/src/rbac/providers/RBACProvider.tsx +0 -645
- package/src/rbac/providers/__tests__/RBACProvider.integration.test.tsx +0 -688
- package/src/rbac/providers/__tests__/RBACProvider.test.tsx +0 -1186
- package/src/rbac/providers/index.ts +0 -11
- package/src/services/RBACService.ts +0 -522
- package/src/services/__tests__/RBACService.test.ts +0 -492
- package/src/services/interfaces/IRBACService.ts +0 -62
- package/src/utils/appNameResolver.test 2.ts +0 -494
- /package/dist/{DataTable-HWZQGASI.js.map → DataTable-QCNCV6IK.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-3NKDOSOK.js.map → UnifiedAuthProvider-Z2FWNW7O.js.map} +0 -0
- /package/dist/{api-DDMUKIUD.js.map → api-KG4A2X7P.js.map} +0 -0
- /package/dist/{appNameResolver-UURKN7NF.js.map → audit-65VNHEV2.js.map} +0 -0
- /package/dist/{chunk-LW7MMEAQ.js.map → chunk-CRKP3HXI.js.map} +0 -0
- /package/dist/{chunk-URUTVZ7N.js.map → chunk-DVHZ5L55.js.map} +0 -0
- /package/dist/{chunk-NTNILOBC.js.map → chunk-TLD5BEU6.js.map} +0 -0
- /package/docs/{app.css.example → styles/app.css.example} +0 -0
|
@@ -13,7 +13,12 @@
|
|
|
13
13
|
* - Data sanitization
|
|
14
14
|
* - Custom filename support
|
|
15
15
|
* - Browser download handling
|
|
16
|
-
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { createLogger } from '../../../utils/logger';
|
|
19
|
+
import type { DataRecord } from '../types';
|
|
20
|
+
|
|
21
|
+
/**
|
|
17
22
|
* @example
|
|
18
23
|
* ```tsx
|
|
19
24
|
* // Basic export
|
|
@@ -37,6 +42,73 @@
|
|
|
37
42
|
* ```
|
|
38
43
|
*/
|
|
39
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Column definition for export
|
|
47
|
+
*/
|
|
48
|
+
export interface ExportColumn {
|
|
49
|
+
header?: string;
|
|
50
|
+
id?: string;
|
|
51
|
+
accessorKey?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Escapes a value for CSV format according to RFC 4180
|
|
56
|
+
* - Encloses in quotes if value contains comma, newline, or quote
|
|
57
|
+
* - Escapes existing quotes by doubling them
|
|
58
|
+
* - Prevents CSV injection by sanitizing dangerous characters
|
|
59
|
+
*/
|
|
60
|
+
function escapeCSVValue(value: unknown, sanitizeForSecurity: boolean = true): string {
|
|
61
|
+
if (value === null || value === undefined) {
|
|
62
|
+
return sanitizeForSecurity ? '""' : '';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let stringValue = String(value);
|
|
66
|
+
|
|
67
|
+
if (sanitizeForSecurity) {
|
|
68
|
+
// Sanitize to prevent CSV injection
|
|
69
|
+
// Check for dangerous patterns that could be interpreted as formulas
|
|
70
|
+
// If starts with =, +, -, @, or \t, it could be a formula
|
|
71
|
+
if (/^[=+\-@]/.test(stringValue) || stringValue.startsWith('\t')) {
|
|
72
|
+
// Prefix with single quote to prevent formula interpretation in Excel
|
|
73
|
+
stringValue = "'" + stringValue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Remove control characters except newline (which is handled below)
|
|
77
|
+
stringValue = stringValue.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F]/g, '');
|
|
78
|
+
|
|
79
|
+
// Always quote values for consistency and safety
|
|
80
|
+
const escaped = stringValue.replace(/"/g, '""');
|
|
81
|
+
return `"${escaped}"`;
|
|
82
|
+
} else {
|
|
83
|
+
// Minimal escaping - only escape quotes in quoted strings
|
|
84
|
+
const escaped = stringValue.replace(/"/g, '""');
|
|
85
|
+
return `"${escaped}"`;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Formats a value according to locale preferences
|
|
91
|
+
*/
|
|
92
|
+
function formatLocaleValue(value: unknown, locale?: string): string {
|
|
93
|
+
if (value === null || value === undefined) {
|
|
94
|
+
return '';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (typeof value === 'number') {
|
|
98
|
+
return new Intl.NumberFormat(locale).format(value);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (value instanceof Date) {
|
|
102
|
+
return new Intl.DateTimeFormat(locale).format(value);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (typeof value === 'boolean') {
|
|
106
|
+
return ''; // Don't format booleans in formatLocaleValue, let them pass through as-is
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return String(value);
|
|
110
|
+
}
|
|
111
|
+
|
|
40
112
|
/**
|
|
41
113
|
* Generates CSV content from data without triggering download
|
|
42
114
|
*
|
|
@@ -45,26 +117,42 @@
|
|
|
45
117
|
* @param options - Export options
|
|
46
118
|
* @returns CSV content as string
|
|
47
119
|
*/
|
|
48
|
-
export function generateCSVContent<TData>(
|
|
120
|
+
export function generateCSVContent<TData extends DataRecord>(
|
|
49
121
|
data: TData[],
|
|
50
|
-
columns:
|
|
51
|
-
options: {
|
|
122
|
+
columns: ExportColumn[],
|
|
123
|
+
options: {
|
|
124
|
+
includeHeaders?: boolean;
|
|
125
|
+
locale?: string;
|
|
126
|
+
sanitizeForSecurity?: boolean; // Default: true
|
|
127
|
+
} = {}
|
|
52
128
|
): string {
|
|
53
129
|
if (!data.length) return '';
|
|
54
130
|
|
|
55
|
-
const {
|
|
131
|
+
const {
|
|
132
|
+
includeHeaders = true,
|
|
133
|
+
locale,
|
|
134
|
+
sanitizeForSecurity = true
|
|
135
|
+
} = options;
|
|
56
136
|
|
|
57
137
|
// Create CSV header row
|
|
58
|
-
const headers = columns.map(col =>
|
|
138
|
+
const headers = columns.map(col => {
|
|
139
|
+
const headerValue = col.header || col.id || "Column";
|
|
140
|
+
return escapeCSVValue(headerValue, sanitizeForSecurity);
|
|
141
|
+
});
|
|
59
142
|
|
|
60
143
|
// Format data into CSV rows
|
|
61
144
|
const csvData = data.map(row => {
|
|
62
145
|
return columns.map(col => {
|
|
63
146
|
const key = col.accessorKey || col.id;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
147
|
+
let value = key ? row[key] : undefined;
|
|
148
|
+
|
|
149
|
+
// Format according to locale if provided
|
|
150
|
+
if (locale && (typeof value === 'number' || value instanceof Date || typeof value === 'boolean')) {
|
|
151
|
+
value = formatLocaleValue(value, locale);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Escape the value for CSV
|
|
155
|
+
return escapeCSVValue(value, sanitizeForSecurity);
|
|
68
156
|
}).join(",");
|
|
69
157
|
});
|
|
70
158
|
|
|
@@ -82,6 +170,8 @@ export function generateCSVContent<TData>(
|
|
|
82
170
|
* @param data - Array of data objects to export
|
|
83
171
|
* @param columns - Column definitions for mapping
|
|
84
172
|
* @param filename - Optional filename for download (default: "download.csv")
|
|
173
|
+
* @param options - Optional export configuration
|
|
174
|
+
* @returns Promise that resolves when export is complete, rejects on error
|
|
85
175
|
*
|
|
86
176
|
* @example
|
|
87
177
|
* ```tsx
|
|
@@ -96,7 +186,12 @@ export function generateCSVContent<TData>(
|
|
|
96
186
|
* { accessorKey: 'email', header: 'Email' }
|
|
97
187
|
* ];
|
|
98
188
|
*
|
|
99
|
-
*
|
|
189
|
+
* try {
|
|
190
|
+
* await exportToCSV(users, columns, 'users.csv', { locale: 'en-US' });
|
|
191
|
+
* showSuccessToast('Data exported successfully');
|
|
192
|
+
* } catch (error) {
|
|
193
|
+
* showErrorToast('Failed to export data');
|
|
194
|
+
* }
|
|
100
195
|
* ```
|
|
101
196
|
*
|
|
102
197
|
* @remarks
|
|
@@ -104,23 +199,57 @@ export function generateCSVContent<TData>(
|
|
|
104
199
|
* - Uses column headers for CSV headers
|
|
105
200
|
* - Sanitizes data to prevent CSV injection
|
|
106
201
|
* - Triggers browser download
|
|
202
|
+
* - Throws error if export fails
|
|
107
203
|
*/
|
|
108
|
-
export function exportToCSV<TData>(
|
|
204
|
+
export function exportToCSV<TData extends DataRecord>(
|
|
109
205
|
data: TData[],
|
|
110
|
-
columns:
|
|
111
|
-
filename: string = "download.csv"
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
206
|
+
columns: ExportColumn[],
|
|
207
|
+
filename: string = "download.csv",
|
|
208
|
+
options: {
|
|
209
|
+
locale?: string;
|
|
210
|
+
sanitizeForSecurity?: boolean;
|
|
211
|
+
} = {}
|
|
212
|
+
): Promise<void> {
|
|
213
|
+
const logger = createLogger('ExportUtils');
|
|
214
|
+
return new Promise((resolve, reject) => {
|
|
215
|
+
try {
|
|
216
|
+
if (typeof window === 'undefined') {
|
|
217
|
+
throw new Error('CSV export is only available in browser environments');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (!data || data.length === 0) {
|
|
221
|
+
throw new Error('No data to export');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (!columns || columns.length === 0) {
|
|
225
|
+
throw new Error('No columns defined for export');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const csvContent = generateCSVContent(data, columns, options);
|
|
229
|
+
|
|
230
|
+
// Create and trigger download
|
|
231
|
+
const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
|
|
232
|
+
const link = document.createElement("a");
|
|
233
|
+
const url = URL.createObjectURL(blob);
|
|
234
|
+
|
|
235
|
+
link.setAttribute("href", url);
|
|
236
|
+
link.setAttribute("download", filename);
|
|
237
|
+
link.style.display = "none";
|
|
238
|
+
|
|
239
|
+
// Handle click event
|
|
240
|
+
link.onclick = () => {
|
|
241
|
+
setTimeout(() => {
|
|
242
|
+
URL.revokeObjectURL(url);
|
|
243
|
+
resolve();
|
|
244
|
+
}, 100);
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
document.body.appendChild(link);
|
|
248
|
+
link.click();
|
|
249
|
+
document.body.removeChild(link);
|
|
250
|
+
} catch (error) {
|
|
251
|
+
logger.error('Failed to export data to CSV:', error);
|
|
252
|
+
reject(error);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
126
255
|
}
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
* This utility can be used across different data tables with different column structures.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { createLogger } from '../../../utils/logger';
|
|
12
|
+
import type { CellValue } from '../types';
|
|
13
|
+
|
|
11
14
|
export interface ColumnMapping {
|
|
12
15
|
[targetField: string]: string[];
|
|
13
16
|
}
|
|
@@ -16,9 +19,20 @@ export interface ImportOptions {
|
|
|
16
19
|
columnMappings?: ColumnMapping;
|
|
17
20
|
dateFormats?: readonly string[];
|
|
18
21
|
dateFormatHints?: Record<string, readonly string[]>; // Field-specific date format hints
|
|
19
|
-
defaultValues?: Record<string,
|
|
22
|
+
defaultValues?: Record<string, CellValue>;
|
|
20
23
|
timezone?: string; // Default timezone for date parsing
|
|
21
24
|
strictDateParsing?: boolean; // Whether to be strict about date format matching
|
|
25
|
+
maxRows?: number; // Maximum number of rows to import (default: 10000)
|
|
26
|
+
maxStringLength?: number; // Maximum length for string values (default: 10000)
|
|
27
|
+
allowControlChars?: boolean; // Whether to allow control characters (default: false)
|
|
28
|
+
schemaValidation?: Record<string, {
|
|
29
|
+
required?: boolean;
|
|
30
|
+
type?: 'string' | 'number' | 'boolean' | 'date';
|
|
31
|
+
min?: number;
|
|
32
|
+
max?: number;
|
|
33
|
+
pattern?: RegExp;
|
|
34
|
+
allowEmpty?: boolean;
|
|
35
|
+
}>; // Schema validation rules
|
|
22
36
|
}
|
|
23
37
|
|
|
24
38
|
// Predefined date format sets for common use cases
|
|
@@ -140,15 +154,117 @@ export const DATE_FORMAT_SETS = {
|
|
|
140
154
|
* });
|
|
141
155
|
* ```
|
|
142
156
|
*/
|
|
143
|
-
export
|
|
144
|
-
|
|
157
|
+
export interface ImportError {
|
|
158
|
+
row: number;
|
|
159
|
+
field: string;
|
|
160
|
+
message: string;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export interface ImportWarning {
|
|
164
|
+
row: number;
|
|
165
|
+
field: string;
|
|
166
|
+
message: string;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Sanitizes a string value by removing or escaping dangerous characters
|
|
171
|
+
*/
|
|
172
|
+
function sanitizeString(value: string, options: ImportOptions): string {
|
|
173
|
+
const maxLength = options.maxStringLength || 10000;
|
|
174
|
+
let sanitized = value;
|
|
175
|
+
|
|
176
|
+
// Truncate to max length
|
|
177
|
+
if (sanitized.length > maxLength) {
|
|
178
|
+
sanitized = sanitized.substring(0, maxLength);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Remove control characters if not allowed
|
|
182
|
+
if (!options.allowControlChars) {
|
|
183
|
+
// Keep tabs, newlines, carriage returns
|
|
184
|
+
sanitized = sanitized.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/g, '');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Remove null bytes
|
|
188
|
+
sanitized = sanitized.replace(/\0/g, '');
|
|
189
|
+
|
|
190
|
+
// Remove SQL injection attempts
|
|
191
|
+
sanitized = sanitized.replace(/['";]/g, (char) => {
|
|
192
|
+
// Escape quotes and semicolons
|
|
193
|
+
return char === '"' ? '"' : char === "'" ? ''' : ';';
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
return sanitized;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Validates a value against schema rules
|
|
201
|
+
*/
|
|
202
|
+
function validateValue(field: string, value: unknown, rules: ImportOptions['schemaValidation']): {
|
|
203
|
+
valid: boolean;
|
|
204
|
+
message?: string;
|
|
205
|
+
} {
|
|
206
|
+
if (!rules || !rules[field]) {
|
|
207
|
+
return { valid: true };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const rule = rules[field];
|
|
211
|
+
|
|
212
|
+
// Check required
|
|
213
|
+
if (rule.required && (value === null || value === undefined || value === '')) {
|
|
214
|
+
return { valid: false, message: `Field '${field}' is required` };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Check empty allowed
|
|
218
|
+
if (!rule.allowEmpty && value === '') {
|
|
219
|
+
return { valid: false, message: `Field '${field}' cannot be empty` };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Check type
|
|
223
|
+
if (rule.type) {
|
|
224
|
+
if (rule.type === 'number') {
|
|
225
|
+
if (typeof value !== 'number' && (typeof value !== 'string' || isNaN(Number(value)))) {
|
|
226
|
+
return { valid: false, message: `Field '${field}' must be a number` };
|
|
227
|
+
}
|
|
228
|
+
const numValue = typeof value === 'number' ? value : Number(value);
|
|
229
|
+
|
|
230
|
+
if (rule.min !== undefined && numValue < rule.min) {
|
|
231
|
+
return { valid: false, message: `Field '${field}' must be at least ${rule.min}` };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (rule.max !== undefined && numValue > rule.max) {
|
|
235
|
+
return { valid: false, message: `Field '${field}' must be at most ${rule.max}` };
|
|
236
|
+
}
|
|
237
|
+
} else if (rule.type === 'boolean') {
|
|
238
|
+
if (typeof value !== 'boolean' && value !== 'true' && value !== 'false') {
|
|
239
|
+
return { valid: false, message: `Field '${field}' must be a boolean` };
|
|
240
|
+
}
|
|
241
|
+
} else if (rule.type === 'date') {
|
|
242
|
+
const dateValue = typeof value === 'string' ? new Date(value) : value;
|
|
243
|
+
if (!(dateValue instanceof Date) || isNaN(dateValue.getTime())) {
|
|
244
|
+
return { valid: false, message: `Field '${field}' must be a valid date` };
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Check pattern
|
|
250
|
+
if (rule.pattern && typeof value === 'string') {
|
|
251
|
+
if (!rule.pattern.test(value)) {
|
|
252
|
+
return { valid: false, message: `Field '${field}' does not match required pattern` };
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return { valid: true };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function flexibleImport<T extends Record<string, unknown>>(
|
|
260
|
+
csvData: Record<string, unknown>[],
|
|
145
261
|
targetFields: string[],
|
|
146
262
|
options: ImportOptions = {}
|
|
147
263
|
): {
|
|
148
264
|
mappedData: T[];
|
|
149
265
|
mappings: Record<string, string | null>;
|
|
150
|
-
errors:
|
|
151
|
-
warnings:
|
|
266
|
+
errors: ImportError[];
|
|
267
|
+
warnings: ImportWarning[];
|
|
152
268
|
stats: {
|
|
153
269
|
totalRows: number;
|
|
154
270
|
successfulRows: number;
|
|
@@ -156,9 +272,34 @@ export function flexibleImport<T = any>(
|
|
|
156
272
|
warningRows: number;
|
|
157
273
|
};
|
|
158
274
|
} {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
275
|
+
const logger = createLogger('FlexibleImport');
|
|
276
|
+
logger.info('Starting flexible import...');
|
|
277
|
+
logger.debug('Available CSV columns:', Object.keys(csvData[0] || {}));
|
|
278
|
+
logger.debug('Target fields:', targetFields);
|
|
279
|
+
|
|
280
|
+
const maxRows = options.maxRows || 10000;
|
|
281
|
+
|
|
282
|
+
// Check row limit
|
|
283
|
+
if (csvData.length > maxRows) {
|
|
284
|
+
const error: ImportError = {
|
|
285
|
+
row: 0,
|
|
286
|
+
field: 'global',
|
|
287
|
+
message: `Import exceeds maximum row limit of ${maxRows}. Found ${csvData.length} rows.`
|
|
288
|
+
};
|
|
289
|
+
logger.error('Import exceeds row limit', { rowCount: csvData.length, limit: maxRows });
|
|
290
|
+
return {
|
|
291
|
+
mappedData: [],
|
|
292
|
+
mappings: {},
|
|
293
|
+
errors: [error],
|
|
294
|
+
warnings: [],
|
|
295
|
+
stats: {
|
|
296
|
+
totalRows: csvData.length,
|
|
297
|
+
successfulRows: 0,
|
|
298
|
+
errorRows: csvData.length,
|
|
299
|
+
warningRows: 0
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
}
|
|
162
303
|
|
|
163
304
|
// Default column mappings
|
|
164
305
|
const defaultMappings: ColumnMapping = {
|
|
@@ -192,11 +333,11 @@ export function flexibleImport<T = any>(
|
|
|
192
333
|
possibleName.toLowerCase().includes(col.toLowerCase())
|
|
193
334
|
);
|
|
194
335
|
if (found) {
|
|
195
|
-
|
|
336
|
+
logger.debug(`Mapped ${targetField} -> ${found}`);
|
|
196
337
|
return found;
|
|
197
338
|
}
|
|
198
339
|
}
|
|
199
|
-
|
|
340
|
+
logger.warn(`No mapping found for ${targetField}`);
|
|
200
341
|
return null;
|
|
201
342
|
};
|
|
202
343
|
|
|
@@ -206,7 +347,7 @@ export function flexibleImport<T = any>(
|
|
|
206
347
|
mappings[field] = findColumn(field);
|
|
207
348
|
});
|
|
208
349
|
|
|
209
|
-
|
|
350
|
+
logger.debug('Column mappings:', mappings);
|
|
210
351
|
|
|
211
352
|
// Enhanced date parsing with multiple format support
|
|
212
353
|
const parseDate = (dateStr: string, fieldName?: string): Date | null => {
|
|
@@ -320,16 +461,19 @@ export function flexibleImport<T = any>(
|
|
|
320
461
|
|
|
321
462
|
|
|
322
463
|
// Parse different data types
|
|
323
|
-
const parseValue = (value:
|
|
464
|
+
const parseValue = (value: unknown, fieldName: string): unknown => {
|
|
324
465
|
if (value === null || value === undefined || value === '') {
|
|
325
466
|
return options.defaultValues?.[fieldName] || '';
|
|
326
467
|
}
|
|
327
468
|
|
|
328
469
|
// Auto-detect and parse different data types
|
|
329
470
|
if (typeof value === 'string') {
|
|
471
|
+
// Sanitize string values
|
|
472
|
+
const sanitized = sanitizeString(value, options);
|
|
473
|
+
|
|
330
474
|
// Try to parse as date first (for fields that might contain dates)
|
|
331
475
|
if (fieldName.toLowerCase().includes('date') || fieldName.toLowerCase().includes('time')) {
|
|
332
|
-
const dateValue = parseDate(
|
|
476
|
+
const dateValue = parseDate(sanitized, fieldName);
|
|
333
477
|
if (dateValue) {
|
|
334
478
|
// Return ISO string for consistent date handling
|
|
335
479
|
return dateValue.toISOString();
|
|
@@ -337,57 +481,83 @@ export function flexibleImport<T = any>(
|
|
|
337
481
|
}
|
|
338
482
|
|
|
339
483
|
// Try to parse as boolean
|
|
340
|
-
if (
|
|
341
|
-
if (
|
|
484
|
+
if (sanitized.toLowerCase() === 'true') return true;
|
|
485
|
+
if (sanitized.toLowerCase() === 'false') return false;
|
|
342
486
|
|
|
343
487
|
// Try to parse as array (comma-separated)
|
|
344
|
-
if (
|
|
345
|
-
return
|
|
488
|
+
if (sanitized.includes(',') && (fieldName.toLowerCase().includes('tag') || fieldName.toLowerCase().includes('category'))) {
|
|
489
|
+
return sanitized.split(',').map((item: string) => item.trim()).filter((item: string) => item);
|
|
346
490
|
}
|
|
347
491
|
|
|
348
492
|
// Try to parse as number (only if not a date field)
|
|
349
493
|
if (!fieldName.toLowerCase().includes('date') && !fieldName.toLowerCase().includes('time')) {
|
|
350
|
-
const numValue = parseFloat(
|
|
494
|
+
const numValue = parseFloat(sanitized);
|
|
351
495
|
if (!isNaN(numValue) && isFinite(numValue)) {
|
|
352
496
|
return numValue;
|
|
353
497
|
}
|
|
354
498
|
}
|
|
499
|
+
|
|
500
|
+
return sanitized;
|
|
355
501
|
}
|
|
356
502
|
|
|
357
503
|
return value;
|
|
358
504
|
};
|
|
359
505
|
|
|
360
|
-
const
|
|
361
|
-
|
|
506
|
+
const validationErrors: ImportError[] = [];
|
|
507
|
+
const validationWarnings: ImportWarning[] = [];
|
|
508
|
+
const mappedData: T[] = [];
|
|
509
|
+
|
|
510
|
+
for (let index = 0; index < csvData.length; index++) {
|
|
511
|
+
const row = csvData[index];
|
|
512
|
+
logger.debug(`Processing row ${index + 1}:`, row);
|
|
362
513
|
|
|
363
|
-
const transformedRow:
|
|
514
|
+
const transformedRow: Record<string, unknown> = {};
|
|
515
|
+
let hasErrors = false;
|
|
364
516
|
|
|
365
517
|
targetFields.forEach(field => {
|
|
366
518
|
const sourceColumn = mappings[field];
|
|
519
|
+
let value: unknown;
|
|
520
|
+
|
|
367
521
|
if (sourceColumn && row[sourceColumn] !== undefined) {
|
|
368
|
-
|
|
522
|
+
value = parseValue(row[sourceColumn], field);
|
|
369
523
|
} else {
|
|
370
|
-
|
|
524
|
+
value = options.defaultValues?.[field] || '';
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
transformedRow[field] = value;
|
|
528
|
+
|
|
529
|
+
// Validate against schema
|
|
530
|
+
const validation = validateValue(field, value, options.schemaValidation);
|
|
531
|
+
if (!validation.valid) {
|
|
532
|
+
validationErrors.push({
|
|
533
|
+
row: index + 1,
|
|
534
|
+
field,
|
|
535
|
+
message: validation.message || `Validation failed for field '${field}'`
|
|
536
|
+
});
|
|
537
|
+
hasErrors = true;
|
|
371
538
|
}
|
|
372
539
|
});
|
|
373
540
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
541
|
+
// Only add row if validation passed (or if no validation rules)
|
|
542
|
+
if (!options.schemaValidation || !hasErrors) {
|
|
543
|
+
mappedData.push(transformedRow as T);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
logger.debug(`Transformed row ${index + 1}:`, transformedRow);
|
|
547
|
+
}
|
|
381
548
|
|
|
549
|
+
// Calculate unique error rows (multiple errors per row still count as 1 error row)
|
|
550
|
+
const errorRowSet = new Set(validationErrors.map(e => e.row));
|
|
551
|
+
|
|
382
552
|
return {
|
|
383
|
-
mappedData,
|
|
553
|
+
mappedData: mappedData,
|
|
384
554
|
mappings,
|
|
385
555
|
errors: validationErrors,
|
|
386
556
|
warnings: validationWarnings,
|
|
387
557
|
stats: {
|
|
388
558
|
totalRows: csvData.length,
|
|
389
559
|
successfulRows: mappedData.length,
|
|
390
|
-
errorRows:
|
|
560
|
+
errorRows: errorRowSet.size,
|
|
391
561
|
warningRows: validationWarnings.length,
|
|
392
562
|
}
|
|
393
563
|
};
|
|
@@ -9,10 +9,57 @@ import type { HierarchicalDataRow } from '../types';
|
|
|
9
9
|
import type { SortingState } from '@tanstack/react-table';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* Sorts hierarchical data
|
|
13
|
-
*
|
|
12
|
+
* Sorts hierarchical data by structure only (parents before children).
|
|
13
|
+
* This is the basic hierarchical ordering without any column-based sorting.
|
|
14
|
+
*
|
|
15
|
+
* @param data - Array of hierarchical data rows
|
|
16
|
+
* @returns Data sorted with parents first, then their children
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* const sorted = sortHierarchicalDataByStructure([...data]);
|
|
21
|
+
* ```
|
|
14
22
|
*/
|
|
15
|
-
export function
|
|
23
|
+
export function sortHierarchicalDataByStructure<TData extends HierarchicalDataRow>(
|
|
24
|
+
data: TData[]
|
|
25
|
+
): TData[] {
|
|
26
|
+
const sorted: TData[] = [];
|
|
27
|
+
const processed = new Set<string>();
|
|
28
|
+
|
|
29
|
+
// First, add all parent rows
|
|
30
|
+
data.forEach(row => {
|
|
31
|
+
if (row.isParent && !processed.has(row.id)) {
|
|
32
|
+
sorted.push(row);
|
|
33
|
+
processed.add(row.id);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Then, add children in order
|
|
38
|
+
data.forEach(row => {
|
|
39
|
+
if (!row.isParent && !processed.has(row.id)) {
|
|
40
|
+
sorted.push(row);
|
|
41
|
+
processed.add(row.id);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return sorted;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Sorts hierarchical data while preserving parent-child relationships.
|
|
50
|
+
* Parent rows maintain their order, child rows are sorted within their parent groups.
|
|
51
|
+
* This applies column-based sorting based on the provided sorting state.
|
|
52
|
+
*
|
|
53
|
+
* @param data - Array of hierarchical data rows
|
|
54
|
+
* @param sorting - TanStack Table sorting state (column + direction)
|
|
55
|
+
* @returns Data sorted with parents first, children sorted within each parent
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```tsx
|
|
59
|
+
* const sorted = sortHierarchicalDataWithSorting(data, [{ id: 'name', desc: false }]);
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export function sortHierarchicalDataWithSorting<TData extends HierarchicalDataRow>(
|
|
16
63
|
data: TData[],
|
|
17
64
|
sorting: SortingState
|
|
18
65
|
): TData[] {
|