@jmruthers/pace-core 0.6.10 → 0.6.11
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 +21 -0
- package/audit-tool/00-dependencies.cjs +46 -13
- package/audit-tool/audits/01-pace-core-compliance.cjs +96 -21
- package/audit-tool/audits/02-project-structure.cjs +13 -3
- package/audit-tool/audits/03-architecture.cjs +78 -4
- package/audit-tool/audits/04-code-quality.cjs +9 -2
- package/audit-tool/audits/05-styling.cjs +19 -7
- package/audit-tool/audits/06-security-rbac.cjs +105 -14
- package/audit-tool/audits/07-api-tech-stack.cjs +31 -15
- package/audit-tool/audits/08-testing-documentation.cjs +11 -3
- package/audit-tool/audits/09-operations.cjs +19 -7
- package/audit-tool/index.cjs +22 -11
- package/audit-tool/utils/report-utils.cjs +4 -0
- package/cursor-rules/01-pace-core-compliance.mdc +1 -0
- package/cursor-rules/02-project-structure.mdc +1 -0
- package/cursor-rules/03-architecture.mdc +3 -1
- package/cursor-rules/04-code-quality.mdc +1 -0
- package/cursor-rules/05-styling.mdc +41 -7
- package/cursor-rules/06-security-rbac.mdc +2 -1
- package/cursor-rules/07-api-tech-stack.mdc +1 -0
- package/cursor-rules/08-testing-documentation.mdc +1 -0
- package/cursor-rules/09-operations.mdc +1 -0
- package/dist/{DataTable-SAXFG4XI.js → DataTable-EFYP2QLE.js} +10 -7
- package/dist/{InactivityServiceProvider-DHryoh6K.d.ts → InactivityServiceProvider-BbxwwDz1.d.ts} +10 -1
- package/dist/{UnifiedAuthProvider-CiBAl9-s.d.ts → UnifiedAuthProvider-Bkt_tzdS.d.ts} +56 -24
- package/dist/{api-F47QJ7FX.js → api-BZR2CYXL.js} +3 -2
- package/dist/api-result-USV1Czr-.d.ts +51 -0
- package/dist/{audit-Z6ZZBWLU.js → audit-HI2DHUVU.js} +2 -1
- package/dist/{auth-BZOJqrdd.d.ts → auth-JvdRVaud.d.ts} +1 -1
- package/dist/{chunk-KSNLMI7N.js → chunk-2DL2WSOE.js} +1 -155
- package/dist/{chunk-MPY44PWB.js → chunk-2OEVOGGR.js} +4648 -3560
- package/dist/chunk-44CNXN4P.js +15 -0
- package/dist/{chunk-Y4PF6HIM.js → chunk-4R3T5ENU.js} +867 -786
- package/dist/{chunk-LNHFAF4X.js → chunk-7A6IMHH2.js} +289 -247
- package/dist/chunk-CU2BU2MQ.js +2 -0
- package/dist/{chunk-JJEYZ3DX.js → chunk-D6BMFMQZ.js} +37 -2
- package/dist/{chunk-BCTXBU6U.js → chunk-ENLXB7GP.js} +88 -71
- package/dist/{chunk-FBZ7U3ID.js → chunk-J2KQK6DG.js} +937 -987
- package/dist/{chunk-TFIPNIPE.js → chunk-KJXRL3XE.js} +3300 -2245
- package/dist/{chunk-3GWSPISD.js → chunk-L5LFKKLJ.js} +1 -1
- package/dist/{chunk-X5EAU5G7.js → chunk-PCSHBLPB.js} +132 -114
- package/dist/{chunk-NIU6DPQV.js → chunk-QRYSEPHB.js} +2 -0
- package/dist/{chunk-KYURMOQM.js → chunk-V7FTM2LU.js} +423 -320
- package/dist/chunk-WY6Y7KC3.js +264 -0
- package/dist/{chunk-FN52B75D.js → chunk-XOJME5T7.js} +176 -15
- package/dist/{chunk-7YDC7LMU.js → chunk-XPFVT3GN.js} +71 -66
- package/dist/{chunk-66R6RLUZ.js → chunk-YFTFFJIV.js} +3 -3
- package/dist/{chunk-W46INAVW.js → chunk-YYTWKVHO.js} +688 -570
- package/dist/components.d.ts +8 -7
- package/dist/components.js +17 -15
- package/dist/{database.generated-DT8JTZiP.d.ts → database.generated-qkdoiVrJ.d.ts} +45 -10
- package/dist/eslint-rules/index.cjs +3 -0
- package/dist/eslint-rules/rules/03-architecture.cjs +74 -0
- package/dist/eslint-rules/rules/06-security-rbac.cjs +74 -0
- package/dist/{event-WTAQuGcq.d.ts → event-BfCox3N2.d.ts} +36 -10
- package/dist/{file-reference-BavO2eQj.d.ts → file-reference-DU1hcawx.d.ts} +29 -13
- package/dist/hooks.d.ts +22 -9
- package/dist/hooks.js +34 -25
- package/dist/icons/index.d.ts +1 -0
- package/dist/icons/index.js +1 -0
- package/dist/index.d.ts +66 -177
- package/dist/index.js +316 -340
- package/dist/pagination-BW1mqywp.d.ts +201 -0
- package/dist/providers.d.ts +6 -5
- package/dist/providers.js +5 -3
- package/dist/rbac/index.d.ts +123 -138
- package/dist/rbac/index.js +10 -8
- package/dist/theming/runtime.d.ts +19 -2
- package/dist/theming/runtime.js +1 -1
- package/dist/{timezone-K-ptz3HO.d.ts → timezone-BTWWXKVY.d.ts} +1 -1
- package/dist/types.d.ts +17 -10
- package/dist/types.js +1 -0
- package/dist/{usePublicPageContext-vxBlEHO9.d.ts → usePublicPageContext-B91dGYW1.d.ts} +433 -356
- package/dist/{usePublicRouteParams-G3Ks53mk.d.ts → usePublicRouteParams-BgV6VhMi.d.ts} +73 -4
- package/dist/utils.d.ts +163 -145
- package/dist/utils.js +42 -25
- package/docs/api/modules.md +782 -643
- package/docs/api-reference/rpc-functions.md +12 -3
- package/docs/core-concepts/rbac-system.md +8 -0
- package/docs/getting-started/cursor-rules.md +17 -20
- package/docs/getting-started/dependencies.md +1 -1
- package/docs/getting-started/setup.md +235 -0
- package/docs/implementation-guides/authentication.md +27 -0
- package/docs/implementation-guides/data-tables.md +176 -3
- package/docs/migration/ApiResult-migration.md +25 -0
- package/docs/rbac/api-reference.md +33 -31
- package/docs/standards/0-standards-overview.md +50 -15
- package/docs/standards/1-pace-core-compliance-standards.md +62 -57
- package/docs/standards/2-project-structure-standards.md +33 -16
- package/docs/standards/3-architecture-standards.md +41 -1
- package/docs/standards/4-code-quality-standards.md +26 -6
- package/docs/standards/5-styling-standards.md +35 -1
- package/docs/standards/6-security-rbac-standards.md +66 -0
- package/docs/standards/7-api-tech-stack-standards.md +25 -14
- package/docs/standards/8-testing-documentation-standards.md +31 -0
- package/docs/standards/9-operations-standards.md +19 -0
- package/docs/standards/README.md +20 -201
- package/docs/testing/test-setup-for-consumers.md +2 -0
- package/docs/troubleshooting/common-issues.md +17 -1
- package/docs/troubleshooting/organisation-context-setup.md +8 -0
- package/docs/troubleshooting/print-event-name-css-variable-analysis.md +217 -0
- package/eslint-config-pace-core.cjs +20 -0
- package/package.json +14 -20
- package/scripts/{build-docs-incremental.js → build-docs.js} +3 -2
- package/scripts/setup.cjs +536 -0
- package/scripts/validate.cjs +480 -0
- package/src/__tests__/helpers/{__tests__/component-test-utils.test.tsx → component-test-utils.test.tsx} +3 -3
- package/src/__tests__/helpers/{__tests__/optimized-test-setup.test.ts → optimized-test-setup.test.ts} +2 -2
- package/src/__tests__/helpers/{__tests__/supabaseMock.test.ts → supabaseMock.test.ts} +2 -2
- package/src/__tests__/helpers/{__tests__/test-providers.test.tsx → test-providers.test.tsx} +1 -1
- package/src/__tests__/helpers/test-providers.tsx +37 -39
- package/src/__tests__/helpers/{__tests__/test-utils.test.tsx → test-utils.test.tsx} +4 -3
- package/src/__tests__/helpers/{__tests__/timer-utils.test.ts → timer-utils.test.ts} +2 -2
- package/src/assets/app-icons/index.test.ts +304 -0
- package/src/components/AddressField/AddressField.test.tsx +1 -1
- package/src/components/AddressField/AddressField.tsx +238 -212
- package/src/components/Button/Button.tsx +1 -1
- package/src/components/Card/Card.test.tsx +172 -17
- package/src/components/Card/Card.tsx +19 -10
- package/src/components/ContextSelector/ContextSelector.internals.tsx +204 -0
- package/src/components/ContextSelector/{__tests__/ContextSelector.test.tsx → ContextSelector.test.tsx} +6 -6
- package/src/components/ContextSelector/ContextSelector.tsx +66 -280
- package/src/components/ContextSelector/ContextSelector.types.ts +35 -0
- package/src/components/ContextSelector/useContextSelectorState.tsx +195 -0
- package/src/components/DataTable/AUDIT_REPORT.md +59 -44
- package/src/components/DataTable/{__tests__/DataTable.comprehensive.test.tsx → DataTable.comprehensive.test.tsx} +6 -6
- package/src/components/DataTable/{__tests__/DataTable.default-state.test.tsx → DataTable.default-state.test.tsx} +5 -5
- package/src/components/DataTable/{__tests__/DataTable.export.test.tsx → DataTable.export.test.tsx} +10 -10
- package/src/components/DataTable/{__tests__/DataTable.grouping-aggregation.test.tsx → DataTable.grouping-aggregation.test.tsx} +6 -6
- package/src/components/DataTable/{__tests__/DataTable.hooks.test.tsx → DataTable.hooks.test.tsx} +6 -6
- package/src/components/DataTable/{__tests__/DataTable.select-label-display.test.tsx → DataTable.select-label-display.test.tsx} +6 -6
- package/src/components/DataTable/DataTable.test.tsx +787 -416
- package/src/components/DataTable/DataTable.tsx +12 -12
- package/src/components/DataTable/DataTableCore.integration.test.tsx +458 -0
- package/src/components/DataTable/{__tests__/DataTableCore.test-setup.ts → DataTableCore.test-setup.ts} +10 -9
- package/src/components/DataTable/{__tests__/DataTableCore.test.tsx → DataTableCore.test.tsx} +8 -8
- package/src/components/DataTable/{__tests__/README.md → README.md} +17 -7
- package/src/components/DataTable/TESTING.md +101 -0
- package/src/components/DataTable/{__tests__/a11y.basic.test.tsx → a11y.basic.test.tsx} +34 -34
- package/src/components/DataTable/components/DataTableCore.tsx +104 -864
- package/src/components/DataTable/components/{__tests__/GroupingDropdown.test.tsx → GroupingDropdown.test.tsx} +17 -8
- package/src/components/DataTable/components/GroupingDropdown.tsx +2 -2
- package/src/components/DataTable/components/ImportModal.tsx +61 -559
- package/src/components/DataTable/components/ImportModalFileSection.tsx +148 -0
- package/src/components/DataTable/context/{__tests__/DataTableContext.test.tsx → DataTableContext.test.tsx} +2 -2
- package/src/components/DataTable/context/DataTableContext.tsx +7 -6
- package/src/components/DataTable/core/{__tests__/ColumnFactory.test.ts → ColumnFactory.test.ts} +2 -2
- package/src/components/DataTable/hooks/{__tests__/useColumnOrderPersistence.test.ts → useColumnOrderPersistence.test.ts} +2 -2
- package/src/components/DataTable/hooks/{__tests__/useColumnVisibilityPersistence.test.ts → useColumnVisibilityPersistence.test.ts} +2 -2
- package/src/components/DataTable/hooks/{__tests__/useDataTableConfiguration.test.ts → useDataTableConfiguration.test.ts} +3 -3
- package/src/components/DataTable/hooks/useDataTableConfiguration.ts +14 -2
- package/src/components/DataTable/hooks/{__tests__/useDataTableDataPipeline.test.ts → useDataTableDataPipeline.test.ts} +6 -6
- package/src/components/DataTable/hooks/useDataTableDeletionBatching.test.ts +127 -0
- package/src/components/DataTable/hooks/useDataTableDeletionBatching.ts +106 -0
- package/src/components/DataTable/hooks/useDataTableEffectiveActions.test.ts +461 -0
- package/src/components/DataTable/hooks/useDataTableEffectiveActions.ts +238 -0
- package/src/components/DataTable/hooks/useDataTableLayoutHandlers.test.ts +296 -0
- package/src/components/DataTable/hooks/useDataTableLayoutHandlers.ts +175 -0
- package/src/components/DataTable/hooks/useDataTablePaginationSync.test.ts +203 -0
- package/src/components/DataTable/hooks/useDataTablePaginationSync.ts +109 -0
- package/src/components/DataTable/hooks/{__tests__/useDataTablePermissions.test.ts → useDataTablePermissions.test.ts} +11 -11
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +79 -247
- package/src/components/DataTable/hooks/useDataTablePipeline.test.tsx +219 -0
- package/src/components/DataTable/hooks/useDataTablePipeline.tsx +239 -0
- package/src/components/DataTable/hooks/useDataTableRenderGuard.test.tsx +316 -0
- package/src/components/DataTable/hooks/useDataTableRenderGuard.tsx +195 -0
- package/src/components/DataTable/hooks/useDataTableScope.test.ts +110 -0
- package/src/components/DataTable/hooks/useDataTableScope.ts +123 -0
- package/src/components/DataTable/hooks/{__tests__/useDataTableState.test.ts → useDataTableState.test.ts} +47 -5
- package/src/components/DataTable/hooks/useDataTableState.ts +145 -94
- package/src/components/DataTable/hooks/useDataTableStateAndPersistence.test.ts +277 -0
- package/src/components/DataTable/hooks/useDataTableStateAndPersistence.ts +222 -0
- package/src/components/DataTable/hooks/useDataTableSuperAdmin.test.ts +93 -0
- package/src/components/DataTable/hooks/useDataTableSuperAdmin.ts +86 -0
- package/src/components/DataTable/hooks/useDataTableTableInstance.test.ts +185 -0
- package/src/components/DataTable/hooks/useDataTableTableInstance.ts +178 -0
- package/src/components/DataTable/hooks/{__tests__/useEffectiveColumnOrder.test.ts → useEffectiveColumnOrder.test.ts} +2 -2
- package/src/components/DataTable/hooks/{__tests__/useHierarchicalState.test.ts → useHierarchicalState.test.ts} +2 -2
- package/src/components/DataTable/{components/hooks → hooks}/useImportModalFocus.test.ts +3 -3
- package/src/components/DataTable/{components/hooks → hooks}/useImportModalFocus.ts +2 -2
- package/src/components/DataTable/hooks/useImportModalState.test.ts +390 -0
- package/src/components/DataTable/hooks/useImportModalState.ts +345 -0
- package/src/components/DataTable/hooks/{__tests__/useKeyboardNavigation.test.ts → useKeyboardNavigation.test.ts} +3 -3
- package/src/components/DataTable/hooks/useKeyboardNavigation.ts +309 -269
- package/src/components/DataTable/{components/hooks → hooks}/usePermissionTracking.test.ts +3 -3
- package/src/components/DataTable/{components/hooks → hooks}/usePermissionTracking.ts +3 -3
- package/src/components/DataTable/hooks/{__tests__/useServerSideDataEffect.test.ts → useServerSideDataEffect.test.ts} +2 -2
- package/src/components/DataTable/hooks/useServerSideDataEffect.ts +14 -3
- package/src/components/DataTable/hooks/{__tests__/useTableColumns.test.ts → useTableColumns.test.ts} +2 -2
- package/src/components/DataTable/hooks/{__tests__/useTableHandlers.test.ts → useTableHandlers.test.ts} +25 -4
- package/src/components/DataTable/hooks/useTableHandlers.ts +5 -2
- package/src/components/DataTable/index.ts +18 -17
- package/src/components/DataTable/{__tests__/keyboard.test.tsx → keyboard.test.tsx} +41 -63
- package/src/components/DataTable/{__tests__/mocks → mocks}/MockRBACProvider.tsx +1 -1
- package/src/components/DataTable/{__tests__/pagination.modes.test.tsx → pagination.modes.test.tsx} +6 -6
- package/src/components/DataTable/{__tests__/ssr.strict-mode.test.tsx → ssr.strict-mode.test.tsx} +2 -2
- package/src/components/DataTable/{__tests__/styles.test.ts → styles.test.ts} +1 -4
- package/src/components/DataTable/styles.ts +0 -1
- package/src/components/DataTable/test-utils/MockDataTableComponents.tsx +55 -0
- package/src/components/DataTable/{__tests__/test-utils → test-utils}/dataFactories.ts +2 -2
- package/src/components/DataTable/test-utils/featureConfig.ts +10 -0
- package/src/components/DataTable/{__tests__/test-utils/sharedTestUtils.tsx → test-utils/sharedTestUtils.ts} +97 -66
- package/src/components/DataTable/{__tests__/test-utils.ts → test-utils.ts} +1 -1
- package/src/components/DataTable/types/actions.ts +71 -0
- package/src/components/DataTable/types/base.ts +39 -0
- package/src/components/DataTable/types/columns.ts +125 -0
- package/src/components/DataTable/types/export.ts +32 -0
- package/src/components/DataTable/types/features.ts +81 -0
- package/src/components/DataTable/types/hierarchical.ts +44 -0
- package/src/components/DataTable/types/index.ts +43 -0
- package/src/components/DataTable/types/pagination.ts +85 -0
- package/src/components/DataTable/types/performance.ts +47 -0
- package/src/components/DataTable/types/props.ts +62 -0
- package/src/components/DataTable/types/rbac.ts +45 -0
- package/src/components/DataTable/{components/__tests__ → ui/layout}/DataTableCore.test.tsx +430 -28
- package/src/components/DataTable/ui/layout/DataTableCore.tsx +345 -0
- package/src/components/DataTable/{components/__tests__ → ui/layout}/DataTableErrorBoundary.test.tsx +4 -4
- package/src/components/DataTable/{components → ui/layout}/DataTableErrorBoundary.tsx +7 -7
- package/src/components/DataTable/ui/layout/DataTableLayout.test.tsx +1352 -0
- package/src/components/DataTable/ui/layout/DataTableLayout.tsx +661 -0
- package/src/components/DataTable/ui/modals/BulkDeleteConfirmDialog.test.tsx +91 -0
- package/src/components/DataTable/ui/modals/BulkDeleteConfirmDialog.tsx +43 -0
- package/src/components/DataTable/ui/modals/DataTableModals.test.tsx +749 -0
- package/src/components/DataTable/{components → ui/modals}/DataTableModals.tsx +36 -28
- package/src/components/DataTable/ui/modals/ImportModal.test.tsx +1834 -0
- package/src/components/DataTable/ui/modals/ImportModal.tsx +197 -0
- package/src/components/DataTable/ui/modals/ImportModalFailedRowsSection.tsx +60 -0
- package/src/components/DataTable/ui/modals/ImportModalFileSection.tsx +148 -0
- package/src/components/DataTable/ui/modals/ImportModalPreviewSection.tsx +60 -0
- package/src/components/DataTable/ui/modals/ImportModalSummarySection.tsx +59 -0
- package/src/components/DataTable/ui/modals/importModalPersistence.ts +73 -0
- package/src/components/DataTable/{components/__tests__ → ui/shared}/AccessDeniedPage.test.tsx +2 -2
- package/src/components/DataTable/{components → ui/shared}/AccessDeniedPage.tsx +2 -2
- package/src/components/DataTable/{components/__tests__ → ui/shared}/ActionButtons.test.tsx +6 -4
- package/src/components/DataTable/{components → ui/shared}/ActionButtons.tsx +4 -4
- package/src/components/DataTable/{components/__tests__ → ui/shared}/ColumnFilter.test.tsx +29 -16
- package/src/components/DataTable/{components → ui/shared}/ColumnFilter.tsx +4 -4
- package/src/components/DataTable/{components/__tests__ → ui/shared}/PaginationControls.test.tsx +38 -16
- package/src/components/DataTable/{components → ui/shared}/PaginationControls.tsx +21 -15
- package/src/components/DataTable/{components/__tests__ → ui/shared}/SortIndicator.test.tsx +2 -2
- package/src/components/DataTable/{components → ui/shared}/SortIndicator.tsx +1 -1
- package/src/components/DataTable/{components/__tests__ → ui/table}/EditFields.test.tsx +3 -3
- package/src/components/DataTable/{components → ui/table}/EditFields.tsx +138 -69
- package/src/components/DataTable/{components/__tests__ → ui/table}/EditableRow.test.tsx +36 -27
- package/src/components/DataTable/{components → ui/table}/EditableRow.tsx +86 -104
- package/src/components/DataTable/{components/__tests__ → ui/table}/EmptyState.test.tsx +2 -62
- package/src/components/DataTable/{components → ui/table}/EmptyState.tsx +7 -15
- package/src/components/DataTable/{components/__tests__ → ui/table}/FilterRow.test.tsx +5 -4
- package/src/components/DataTable/{components → ui/table}/FilterRow.tsx +3 -3
- package/src/components/DataTable/{components/__tests__ → ui/table}/LoadingState.test.tsx +6 -10
- package/src/components/DataTable/{components → ui/table}/LoadingState.tsx +4 -4
- package/src/components/DataTable/{components/__tests__ → ui/table}/RowComponent.test.tsx +412 -17
- package/src/components/DataTable/{components → ui/table}/RowComponent.tsx +183 -177
- package/src/components/DataTable/{components/__tests__ → ui/table}/UnifiedTableBody.test.tsx +425 -16
- package/src/components/DataTable/ui/table/UnifiedTableBody.tsx +440 -0
- package/src/components/DataTable/{components/__tests__ → ui/table}/cellValueUtils.test.ts +2 -2
- package/src/components/DataTable/{components → ui/table}/cellValueUtils.ts +1 -1
- package/src/components/DataTable/{components/__tests__ → ui/toolbar}/BulkOperationsDropdown.test.tsx +12 -5
- package/src/components/DataTable/{components → ui/toolbar}/BulkOperationsDropdown.tsx +3 -3
- package/src/components/DataTable/{components/__tests__ → ui/toolbar}/ColumnVisibilityDropdown.test.tsx +7 -4
- package/src/components/DataTable/{components → ui/toolbar}/ColumnVisibilityDropdown.tsx +7 -7
- package/src/components/DataTable/{components/__tests__ → ui/toolbar}/DataTableToolbar.test.tsx +4 -4
- package/src/components/DataTable/{components → ui/toolbar}/DataTableToolbar.tsx +4 -4
- package/src/components/DataTable/ui/toolbar/GroupingDropdown.test.tsx +621 -0
- package/src/components/DataTable/ui/toolbar/GroupingDropdown.tsx +107 -0
- package/src/components/DataTable/utils/{__tests__/a11yUtils.test.ts → a11yUtils.test.ts} +2 -2
- package/src/components/DataTable/utils/{__tests__/aggregationUtils.test.ts → aggregationUtils.test.ts} +3 -3
- package/src/components/DataTable/utils/{__tests__/columnUtils.test.ts → columnUtils.test.ts} +2 -2
- package/src/components/DataTable/utils/csvParse.test.ts +74 -0
- package/src/components/DataTable/utils/csvParse.ts +65 -0
- package/src/components/DataTable/utils/{__tests__/errorHandling.test.ts → errorHandling.test.ts} +2 -2
- package/src/components/DataTable/utils/{__tests__/exportUtils.test.ts → exportUtils.test.ts} +3 -3
- package/src/components/DataTable/utils/{__tests__/flexibleImport.test.ts → flexibleImport.test.ts} +2 -2
- package/src/components/DataTable/utils/flexibleImport.ts +3 -186
- package/src/components/DataTable/utils/{__tests__/hierarchicalSorting.test.ts → hierarchicalSorting.test.ts} +3 -3
- package/src/components/DataTable/utils/{__tests__/hierarchicalUtils.test.ts → hierarchicalUtils.test.ts} +3 -3
- package/src/components/DataTable/utils/importDateParser.test.ts +162 -0
- package/src/components/DataTable/utils/importDateParser.ts +114 -0
- package/src/components/DataTable/utils/importValueParser.test.ts +138 -0
- package/src/components/DataTable/utils/importValueParser.ts +91 -0
- package/src/components/DataTable/utils/{__tests__/paginationUtils.test.ts → paginationUtils.test.ts} +2 -2
- package/src/components/DataTable/utils/paginationUtils.ts +6 -3
- package/src/components/DataTable/utils/{__tests__/performanceUtils.test.ts → performanceUtils.test.ts} +3 -3
- package/src/components/DataTable/utils/{__tests__/rowUtils.test.ts → rowUtils.test.ts} +3 -3
- package/src/components/DataTable/utils/{__tests__/selectFieldUtils.test.ts → selectFieldUtils.test.ts} +66 -3
- package/src/components/DataTable/utils/selectFieldUtils.ts +97 -60
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +1 -1
- package/src/components/DateTimeField/DateTimeField.test.tsx +1 -1
- package/src/components/Dialog/Dialog.test-utils.ts +49 -0
- package/src/components/Dialog/Dialog.test.tsx +896 -89
- package/src/components/Dialog/Dialog.tsx +174 -882
- package/src/components/Dialog/dialogLock.test.ts +238 -0
- package/src/components/Dialog/dialogLock.ts +98 -0
- package/src/components/Dialog/index.ts +2 -0
- package/src/components/Dialog/useDialogDimensions.test.ts +163 -0
- package/src/components/Dialog/useDialogDimensions.ts +140 -0
- package/src/components/Dialog/useDialogLifecycle.test.ts +358 -0
- package/src/components/Dialog/useDialogLifecycle.ts +135 -0
- package/src/components/Dialog/useDialogPersistence.test.ts +381 -0
- package/src/components/Dialog/useDialogPersistence.ts +357 -0
- package/src/components/FileDisplay/FileDisplay.test.tsx +40 -40
- package/src/components/FileDisplay/FileDisplay.tsx +24 -656
- package/src/components/FileDisplay/FileDisplayContent.test.tsx +395 -0
- package/src/components/FileDisplay/FileDisplayContent.tsx +242 -0
- package/src/components/FileDisplay/FileDisplayDeleteConfirmDialog.test.tsx +74 -0
- package/src/components/FileDisplay/FileDisplayDeleteConfirmDialog.tsx +38 -0
- package/src/components/FileDisplay/FileDisplayEmptyView.test.tsx +33 -0
- package/src/components/FileDisplay/FileDisplayEmptyView.tsx +33 -0
- package/src/components/FileDisplay/FileDisplayErrorView.test.tsx +71 -0
- package/src/components/FileDisplay/FileDisplayErrorView.tsx +50 -0
- package/src/components/FileDisplay/FileDisplayLoadingFallbackView.test.tsx +22 -0
- package/src/components/FileDisplay/FileDisplayLoadingFallbackView.tsx +22 -0
- package/src/components/FileDisplay/FileDisplayLoadingView.test.tsx +21 -0
- package/src/components/FileDisplay/FileDisplayLoadingView.tsx +23 -0
- package/src/components/FileDisplay/FileDisplayMultipleFilesView.test.tsx +101 -0
- package/src/components/FileDisplay/FileDisplayMultipleFilesView.tsx +109 -0
- package/src/components/FileDisplay/FileDisplaySingleDocumentLinkView.test.tsx +58 -0
- package/src/components/FileDisplay/FileDisplaySingleDocumentLinkView.tsx +48 -0
- package/src/components/FileDisplay/FileDisplaySingleFileWithActionsView.test.tsx +111 -0
- package/src/components/FileDisplay/FileDisplaySingleFileWithActionsView.tsx +270 -0
- package/src/components/FileDisplay/FileDisplaySingleImageView.test.tsx +78 -0
- package/src/components/FileDisplay/FileDisplaySingleImageView.tsx +67 -0
- package/src/components/FileDisplay/fallbackUtils.test.ts +50 -0
- package/src/components/FileDisplay/fallbackUtils.ts +44 -0
- package/src/components/FileDisplay/fetchFileDisplayData.ts +24 -0
- package/src/components/FileDisplay/fetchFileDisplayData.unit.test.ts +183 -0
- package/src/components/FileDisplay/fileDisplayUtils.test.ts +58 -0
- package/src/components/FileDisplay/fileDisplayUtils.ts +24 -0
- package/src/{hooks/__tests__ → components/FileDisplay}/useFileDisplay.test.ts +40 -42
- package/src/components/FileDisplay/useFileDisplay.ts +515 -0
- package/src/{hooks/__tests__ → components/FileDisplay}/useFileDisplay.unit.test.ts +406 -77
- package/src/components/FileDisplay/useFileDisplayData.ts +126 -0
- package/src/{hooks/public → components/FileDisplay}/usePublicFileDisplay.test.ts +94 -88
- package/src/components/FileDisplay/usePublicFileDisplay.ts +579 -0
- package/src/components/FileUpload/FileUpload.test.tsx +16 -10
- package/src/components/FileUpload/FileUpload.tsx +107 -525
- package/src/components/FileUpload/FileUploadDropZone.tsx +112 -0
- package/src/components/FileUpload/FileUploadProgressItem.tsx +86 -0
- package/src/components/FileUpload/FileUploadProgressList.tsx +40 -0
- package/src/components/FileUpload/useFileUploadManager.test.ts +308 -0
- package/src/components/FileUpload/useFileUploadManager.ts +454 -0
- package/src/components/FileUpload/useResolvedAppId.test.ts +102 -0
- package/src/components/FileUpload/useResolvedAppId.ts +77 -0
- package/src/components/Footer/Footer.test.tsx +6 -292
- package/src/components/Footer/Footer.tsx +8 -125
- package/src/components/Form/Form.test.tsx +44 -27
- package/src/components/Form/Form.tsx +64 -287
- package/src/components/Form/useFormPersistence.ts +257 -0
- package/src/components/Header/Header.test.tsx +17 -18
- package/src/components/Header/Header.tsx +10 -1
- package/src/components/Input/Input.tsx +1 -1
- package/src/components/Label/Label.test.tsx +1 -1
- package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +1 -1
- package/src/components/NavigationMenu/HierarchicalNavItem.tsx +104 -0
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +1029 -26
- package/src/components/NavigationMenu/NavigationMenu.tsx +61 -361
- package/src/components/NavigationMenu/index.ts +6 -1
- package/src/components/NavigationMenu/navigationPermissionHelper.ts +188 -0
- package/src/components/NavigationMenu/{__tests__/useNavigationFiltering.test.ts → useNavigationFiltering.test.ts} +68 -53
- package/src/components/NavigationMenu/useNavigationFiltering.ts +197 -296
- package/src/components/NavigationMenu/useNavigationScope.ts +125 -0
- package/src/components/PaceAppLayout/PaceAppLayout.edge-cases.test.tsx +77 -62
- package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +3 -3
- package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +16 -19
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +529 -5
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +280 -756
- package/src/components/PaceAppLayout/useFilteredNavItems.ts +304 -0
- package/src/components/PaceAppLayout/usePaceAppLayoutConfig.ts +142 -0
- package/src/components/PaceAppLayout/usePaceAppLayoutGate.tsx +150 -0
- package/src/components/PaceAppLayout/usePaceAppLayoutPermissions.ts +162 -0
- package/src/components/PaceAppLayout/usePaceAppLayoutScope.ts +79 -0
- package/src/components/PaceAppLayout/useRoleBasedRouteAccess.ts +157 -0
- package/src/components/PaceAppLayout/useSuperAdminFallback.ts +58 -0
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +31 -25
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +31 -122
- package/src/components/PaceLoginPage/useLoginAppAccess.ts +153 -0
- package/src/components/Progress/Progress.tsx +1 -2
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +29 -235
- package/src/components/ProtectedRoute/useProtectedRouteState.ts +128 -0
- package/src/components/ProtectedRoute/useVisibilityRedirectGrace.ts +89 -0
- package/src/components/PublicLayout/PublicLayout.test.tsx +217 -36
- package/src/components/PublicLayout/PublicPageLayout.tsx +132 -73
- package/src/components/PublicLayout/PublicPageProvider.tsx +5 -1
- package/src/components/Select/Select.test.tsx +1 -1
- package/src/components/Select/Select.tsx +28 -18
- package/src/components/Select/{__tests__/context.test.tsx → context.test.tsx} +3 -3
- package/src/components/Select/{utils/__tests__/text.test.tsx → text.test.tsx} +2 -2
- package/src/components/Select/{utils/text.ts → text.ts} +1 -1
- package/src/components/Select/{hooks/__tests__/useSelectEvents.test.ts → useSelectEvents.test.ts} +5 -5
- package/src/components/Select/{hooks/useSelectEvents.ts → useSelectEvents.ts} +2 -2
- package/src/components/Select/{hooks/__tests__/useSelectSearch.test.tsx → useSelectSearch.test.tsx} +7 -7
- package/src/components/Select/{hooks/useSelectSearch.ts → useSelectSearch.ts} +2 -2
- package/src/components/Select/{hooks/__tests__/useSelectState.test.ts → useSelectState.test.ts} +16 -2
- package/src/components/Select/{hooks/useSelectState.ts → useSelectState.ts} +3 -3
- package/src/components/Table/Table.test.tsx +348 -0
- package/src/components/Tabs/Tabs.test.tsx +270 -0
- package/src/components/Tabs/Tabs.tsx +1 -1
- package/src/components/Toast/Toast.test.tsx +420 -0
- package/src/components/{__tests__/index.test.ts → index.test.ts} +2 -2
- package/src/constants/{__tests__/performance.test.ts → performance.test.ts} +2 -2
- package/src/hooks/{__tests__/ServiceHooks.test.tsx → ServiceHooks.test.tsx} +8 -8
- package/src/hooks/{__tests__/hooks.integration.test.tsx → hooks.integration.test.tsx} +11 -11
- package/src/hooks/index.ts +7 -4
- package/src/hooks/{__tests__/index.unit.test.ts → index.unit.test.ts} +2 -2
- package/src/hooks/public/usePublicEvent.test.ts +1 -1
- package/src/hooks/public/usePublicEventLogo.test.ts +1 -1
- package/src/hooks/public/usePublicRouteParams.test.ts +1 -1
- package/src/hooks/services/useAuth.ts +9 -7
- package/src/hooks/useAddressAutocomplete.test.ts +22 -22
- package/src/hooks/useAddressAutocomplete.ts +90 -75
- package/src/hooks/{__tests__/useAppConfig.unit.test.ts → useAppConfig.unit.test.ts} +328 -22
- package/src/hooks/{__tests__/useComponentPerformance.unit.test.tsx → useComponentPerformance.unit.test.tsx} +27 -41
- package/src/hooks/useDataTablePerformance.ts +100 -120
- package/src/hooks/{__tests__/useDataTablePerformance.unit.test.ts → useDataTablePerformance.unit.test.ts} +5 -5
- package/src/hooks/{__tests__/useDataTableState.test.ts → useDataTableState.test.ts} +2 -2
- package/src/hooks/{__tests__/useDebounce.unit.test.ts → useDebounce.unit.test.ts} +2 -2
- package/src/hooks/useEventTheme.test.ts +4 -1
- package/src/hooks/useEventTheme.ts +49 -21
- package/src/hooks/useEvents.ts +41 -1
- package/src/hooks/{__tests__/useEvents.unit.test.ts → useEvents.unit.test.ts} +5 -5
- package/src/hooks/useFileReference.test.ts +44 -41
- package/src/hooks/useFileReference.ts +182 -173
- package/src/hooks/useFileUrl.ts +1 -1
- package/src/hooks/{__tests__/useFileUrl.unit.test.ts → useFileUrl.unit.test.ts} +26 -36
- package/src/hooks/{__tests__/useFileUrlCache.test.ts → useFileUrlCache.test.ts} +8 -8
- package/src/hooks/useFileUrlCache.ts +1 -1
- package/src/hooks/{__tests__/useFocusManagement.unit.test.ts → useFocusManagement.unit.test.ts} +2 -2
- package/src/hooks/{__tests__/useFocusTrap.unit.test.tsx → useFocusTrap.unit.test.tsx} +2 -2
- package/src/hooks/{__tests__/useFormDialog.test.ts → useFormDialog.test.ts} +2 -2
- package/src/hooks/useInactivityTracker.ts +138 -131
- package/src/hooks/{__tests__/useInactivityTracker.unit.test.ts → useInactivityTracker.unit.test.ts} +3 -3
- package/src/hooks/{__tests__/useIsMobile.unit.test.ts → useIsMobile.unit.test.ts} +2 -2
- package/src/hooks/useIsPrint.ts +62 -0
- package/src/hooks/useIsPrint.unit.test.ts +545 -0
- package/src/hooks/{__tests__/useKeyboardShortcuts.unit.test.ts → useKeyboardShortcuts.unit.test.ts} +2 -2
- package/src/hooks/{__tests__/useOrganisationPermissions.unit.test.tsx → useOrganisationPermissions.unit.test.tsx} +4 -4
- package/src/hooks/useOrganisationSecurity.test.ts +3 -3
- package/src/hooks/useOrganisationSecurity.ts +190 -201
- package/src/hooks/{__tests__/useOrganisationSecurity.unit.test.tsx → useOrganisationSecurity.unit.test.tsx} +61 -63
- package/src/hooks/{__tests__/useOrganisations.unit.test.ts → useOrganisations.unit.test.ts} +5 -5
- package/src/hooks/{__tests__/usePerformanceMonitor.unit.test.ts → usePerformanceMonitor.unit.test.ts} +13 -14
- package/src/hooks/{__tests__/usePermissionCache.test.ts → usePermissionCache.test.ts} +26 -27
- package/src/hooks/usePermissionCache.ts +276 -271
- package/src/hooks/{__tests__/usePreventTabReload.test.ts → usePreventTabReload.test.ts} +2 -2
- package/src/hooks/{__tests__/usePublicEvent.simple.test.ts → usePublicEvent.simple.test.ts} +4 -4
- package/src/hooks/{__tests__/usePublicEvent.test.ts → usePublicEvent.test.ts} +4 -4
- package/src/hooks/{__tests__/usePublicEvent.unit.test.ts → usePublicEvent.unit.test.ts} +4 -4
- package/src/hooks/{__tests__/usePublicFileDisplay.test.ts → usePublicFileDisplay.test.ts} +12 -12
- package/src/hooks/{__tests__/usePublicRouteParams.unit.test.ts → usePublicRouteParams.unit.test.ts} +3 -3
- package/src/hooks/{__tests__/useQueryCache.test.ts → useQueryCache.test.ts} +2 -2
- package/src/hooks/useQueryCache.ts +0 -2
- package/src/hooks/{__tests__/useRBAC.unit.test.ts → useRBAC.unit.test.ts} +55 -38
- package/src/hooks/{__tests__/useSessionDraft.test.ts → useSessionDraft.test.ts} +2 -2
- package/src/hooks/{__tests__/useSessionRestoration.unit.test.tsx → useSessionRestoration.unit.test.tsx} +10 -19
- package/src/hooks/useStorage.ts +21 -16
- package/src/hooks/{__tests__/useStorage.unit.test.ts → useStorage.unit.test.ts} +38 -75
- package/src/hooks/{__tests__/useToast.test.ts → useToast.test.ts} +2 -2
- package/src/hooks/{__tests__/useToast.unit.test.tsx → useToast.unit.test.tsx} +2 -2
- package/src/hooks/{__tests__/useZodForm.unit.test.tsx → useZodForm.unit.test.tsx} +2 -2
- package/src/icons/{__tests__/index.test.ts → index.test.ts} +2 -2
- package/src/icons/index.ts +2 -0
- package/src/{__tests__/index.test.ts → index.test.ts} +3 -7
- package/src/index.ts +15 -7
- package/src/providers/{__tests__/AuthProvider.test.tsx → AuthProvider.test.tsx} +3 -3
- package/src/providers/{__tests__/EventProvider.test.tsx → EventProvider.test.tsx} +3 -3
- package/src/providers/InactivityProvider.test-helper.tsx +40 -0
- package/src/providers/{__tests__/InactivityProvider.test.tsx → InactivityProvider.test.tsx} +14 -21
- package/src/providers/{__tests__/ProviderLifecycle.test.tsx → ProviderLifecycle.test.tsx} +4 -4
- package/src/providers/{__tests__/UnifiedAuthProvider.test.tsx → UnifiedAuthProvider.test.tsx} +1 -1
- package/src/providers/{__tests__/index.test.ts → index.test.ts} +2 -2
- package/src/providers/services/{__tests__/AuthServiceProvider.integration.test.tsx → AuthServiceProvider.integration.test.tsx} +4 -4
- package/src/providers/services/{__tests__/AuthServiceProvider.test.tsx → AuthServiceProvider.test.tsx} +7 -7
- package/src/providers/services/{__tests__/EventServiceProvider.test.tsx → EventServiceProvider.test.tsx} +7 -7
- package/src/providers/services/{__tests__/InactivityServiceProvider.test.tsx → InactivityServiceProvider.test.tsx} +5 -5
- package/src/providers/services/{__tests__/OrganisationServiceProvider.test.tsx → OrganisationServiceProvider.test.tsx} +6 -6
- package/src/providers/services/UnifiedAuthContext.ts +30 -27
- package/src/providers/services/{__tests__/UnifiedAuthProvider.advanced.test.tsx → UnifiedAuthProvider.advanced.test.tsx} +8 -9
- package/src/providers/services/{__tests__/UnifiedAuthProvider.appId.test.tsx → UnifiedAuthProvider.appId.test.tsx} +25 -25
- package/src/providers/services/{__tests__/UnifiedAuthProvider.integration.test.tsx → UnifiedAuthProvider.integration.test.tsx} +14 -11
- package/src/providers/services/UnifiedAuthProvider.tsx +115 -360
- package/src/providers/services/{__tests__/contexts.test.tsx → contexts.test.tsx} +6 -6
- package/src/providers/services/{__tests__/useUnifiedAuth.test.tsx → useUnifiedAuth.test.tsx} +6 -6
- package/src/providers/services/useUnifiedAuthContextValue.ts +279 -0
- package/src/providers/useInactivity.test-helper.ts +27 -0
- package/src/rbac/{__tests__/adapters.comprehensive.test.tsx → adapters.comprehensive.test.tsx} +24 -24
- package/src/rbac/adapters.test.tsx +22 -22
- package/src/rbac/adapters.tsx +29 -29
- package/src/rbac/api.test.ts +973 -42
- package/src/rbac/api.ts +228 -253
- package/src/rbac/{__tests__/audit-batched.test.ts → audit-batched.test.ts} +6 -6
- package/src/rbac/audit.ts +4 -1
- package/src/rbac/{__tests__/auth-rbac-security.integration.test.tsx → auth-rbac-security.integration.test.tsx} +1 -1
- package/src/rbac/{__tests__/auth-rbac.e2e.test.tsx → auth-rbac.e2e.test.tsx} +27 -34
- package/src/rbac/cache-invalidation.test.ts +715 -0
- package/src/rbac/components/{__tests__/AccessDenied.test.tsx → AccessDenied.test.tsx} +3 -3
- package/src/rbac/components/{__tests__/NavigationGuard.test.tsx → NavigationGuard.test.tsx} +13 -11
- package/src/{__tests__/rbac/PagePermissionGuard.test.tsx → rbac/components/PagePermissionGuard.guard.test.tsx} +33 -19
- package/src/rbac/components/{__tests__/PagePermissionGuard.performance.test.tsx → PagePermissionGuard.performance.test.tsx} +30 -9
- package/src/rbac/components/{__tests__/PagePermissionGuard.race-condition.test.tsx → PagePermissionGuard.race-condition.test.tsx} +7 -7
- package/src/rbac/components/{__tests__/PagePermissionGuard.test.tsx → PagePermissionGuard.test.tsx} +10 -10
- package/src/rbac/components/PagePermissionGuard.tsx +177 -372
- package/src/rbac/components/{__tests__/PagePermissionGuard.verification.test.tsx → PagePermissionGuard.verification.test.tsx} +7 -7
- package/src/rbac/config.ts +58 -18
- package/src/rbac/{__tests__/engine.comprehensive.test.ts → engine.comprehensive.test.ts} +3 -3
- package/src/rbac/engine.test.ts +494 -0
- package/src/rbac/errors.ts +89 -55
- package/src/rbac/hooks/permissions/runPermissionCheck.ts +77 -0
- package/src/rbac/hooks/permissions/{__tests__/useAccessLevel.test.ts → useAccessLevel.test.ts} +40 -40
- package/src/rbac/hooks/permissions/useAccessLevel.ts +16 -6
- package/src/rbac/hooks/permissions/{__tests__/useCan.test.ts → useCan.test.ts} +41 -41
- package/src/rbac/hooks/permissions/useCan.ts +170 -252
- package/src/rbac/hooks/permissions/{__tests__/useMultiplePermissions.test.ts → useMultiplePermissions.test.ts} +49 -49
- package/src/rbac/hooks/permissions/useMultiplePermissions.ts +6 -2
- package/src/rbac/hooks/permissions/{__tests__/usePermissions.test.ts → usePermissions.test.ts} +10 -12
- package/src/rbac/hooks/permissions/usePermissions.ts +36 -65
- package/src/rbac/hooks/useCan.test.ts +42 -42
- package/src/rbac/hooks/usePageAccessLogging.ts +160 -0
- package/src/rbac/hooks/usePageGuardScope.ts +117 -0
- package/src/rbac/hooks/usePagePermissionCheck.ts +67 -0
- package/src/rbac/hooks/{__tests__/usePermissions.integration.test.ts → usePermissions.integration.test.ts} +9 -9
- package/src/{__tests__/hooks/usePermissions.test.ts → rbac/hooks/usePermissions.stability.test.ts} +18 -18
- package/src/rbac/hooks/usePermissions.test.ts +54 -54
- package/src/rbac/hooks/useRBAC.test.ts +313 -217
- package/src/rbac/hooks/useRBAC.ts +145 -81
- package/src/rbac/hooks/useResourcePermissions.test.ts +25 -25
- package/src/rbac/hooks/useResourcePermissions.ts +68 -134
- package/src/rbac/hooks/useResourcePermissionsSuperAdmin.ts +67 -0
- package/src/rbac/hooks/useRoleManagement.test.ts +27 -112
- package/src/rbac/hooks/useRoleManagement.ts +153 -585
- package/src/rbac/hooks/{__tests__/useSecureSupabase.test.ts → useSecureSupabase.test.ts} +17 -17
- package/src/rbac/hooks/useSecureSupabase.ts +10 -2
- package/src/rbac/hooks/useSuperAdminCheck.ts +80 -0
- package/src/rbac/{__tests__/performance.test.ts → performance.test.ts} +1 -1
- package/src/rbac/{__tests__/rbac-core.test.tsx → rbac-core.test.tsx} +3 -3
- package/src/rbac/{__tests__/rbac-engine-core-logic.test.ts → rbac-engine-core-logic.test.ts} +2 -2
- package/src/rbac/{__tests__/rbac-engine-simplified.test.ts → rbac-engine-simplified.test.ts} +3 -3
- package/src/rbac/{__tests__/rbac-functions.test.ts → rbac-functions.test.ts} +57 -0
- package/src/rbac/{__tests__/rbac-role-isolation.test.ts → rbac-role-isolation.test.ts} +2 -2
- package/src/rbac/request-deduplication.test.ts +14 -9
- package/src/rbac/request-deduplication.ts +5 -4
- package/src/rbac/{__tests__/scenarios.user-role.test.tsx → scenarios.user-role.test.tsx} +23 -23
- package/src/rbac/secureClient.test.ts +514 -83
- package/src/rbac/secureClient.ts +8 -2
- package/src/rbac/security.test.ts +323 -0
- package/src/rbac/types/roleManagement.ts +66 -0
- package/src/rbac/utils/{__tests__/clientSecurity.test.ts → clientSecurity.test.ts} +4 -4
- package/src/rbac/utils/{__tests__/contextValidator.test.ts → contextValidator.test.ts} +4 -4
- package/src/rbac/utils/contextValidator.ts +5 -1
- package/src/rbac/utils/{__tests__/deep-equal.test.ts → deep-equal.test.ts} +1 -1
- package/src/rbac/utils/{__tests__/eventContext.test.ts → eventContext.test.ts} +36 -21
- package/src/rbac/utils/eventContext.ts +37 -33
- package/src/rbac/utils/fetchPermissionMap.ts +13 -0
- package/src/rbac/utils/permissionMapHelpers.ts +34 -0
- package/src/rbac/utils/roleManagementRpc.ts +303 -0
- package/src/services/{__tests__/AuthService.edge-cases.test.ts → AuthService.edge-cases.test.ts} +19 -19
- package/src/services/{__tests__/AuthService.restoreSession.test.ts → AuthService.restoreSession.test.ts} +2 -2
- package/src/services/{__tests__/AuthService.test.ts → AuthService.test.ts} +89 -55
- package/src/services/AuthService.ts +184 -205
- package/src/services/{__tests__/BaseService.edge-cases.test.ts → BaseService.edge-cases.test.ts} +3 -3
- package/src/services/{__tests__/BaseService.test.ts → BaseService.test.ts} +2 -2
- package/src/services/{__tests__/EventService.edge-cases.test.ts → EventService.edge-cases.test.ts} +27 -24
- package/src/services/{__tests__/EventService.eventColours.test.ts → EventService.eventColours.test.ts} +1 -1
- package/src/services/{__tests__/EventService.test.ts → EventService.test.ts} +256 -24
- package/src/services/EventService.ts +242 -312
- package/src/services/{__tests__/InactivityService.edge-cases.test.ts → InactivityService.edge-cases.test.ts} +3 -3
- package/src/services/{__tests__/InactivityService.lifecycle.test.ts → InactivityService.lifecycle.test.ts} +2 -2
- package/src/services/{__tests__/InactivityService.test.ts → InactivityService.test.ts} +179 -4
- package/src/services/InactivityService.ts +172 -213
- package/src/services/{__tests__/OrganisationService.edge-cases.test.ts → OrganisationService.edge-cases.test.ts} +5 -5
- package/src/services/{__tests__/OrganisationService.pagination.test.ts → OrganisationService.pagination.test.ts} +4 -4
- package/src/services/{__tests__/OrganisationService.test.ts → OrganisationService.test.ts} +410 -7
- package/src/services/OrganisationService.ts +184 -238
- package/src/services/base/BaseService.test.ts +1 -1
- package/src/services/interfaces/{__tests__/IAuthService.test.ts → IAuthService.test.ts} +21 -27
- package/src/services/interfaces/IAuthService.ts +10 -9
- package/src/services/interfaces/{__tests__/IEventService.test.ts → IEventService.test.ts} +4 -4
- package/src/services/interfaces/{__tests__/IInactivityService.test.ts → IInactivityService.test.ts} +3 -3
- package/src/services/interfaces/{__tests__/IOrganisationService.test.ts → IOrganisationService.test.ts} +3 -3
- package/src/styles/core.css +243 -12
- package/src/theming/{__tests__/parseEventColours.test.ts → parseEventColours.test.ts} +1 -1
- package/src/theming/{__tests__/runtime.test.ts → runtime.test.ts} +8 -17
- package/src/theming/runtime.ts +71 -2
- package/src/types/api-result.ts +53 -0
- package/src/types/{__tests__/core.test.ts → core.test.ts} +2 -2
- package/src/types/{__tests__/database-generated.test.ts → database-generated.test.ts} +3 -3
- package/src/types/database.generated.ts +45 -10
- package/src/types/event.ts +38 -18
- package/src/types/{__tests__/file-reference.test.ts → file-reference.test.ts} +13 -13
- package/src/types/file-reference.ts +37 -12
- package/src/types/{__tests__/guards.test.ts → guards.test.ts} +2 -2
- package/src/types/{__tests__/index.test.ts → index.test.ts} +2 -2
- package/src/types/index.ts +3 -0
- package/src/types/{__tests__/organisation.roles.test.ts → organisation.roles.test.ts} +1 -1
- package/src/types/{__tests__/organisation.test.ts → organisation.test.ts} +3 -31
- package/src/types/organisation.ts +15 -15
- package/src/types/supabase.ts +13 -4
- package/src/types/{__tests__/theme.test.ts → theme.test.ts} +1 -1
- package/src/types/{__tests__/type-validation.test.ts → type-validation.test.ts} +1 -1
- package/src/types/{__tests__/validation.test.ts → validation.test.ts} +2 -2
- package/src/utils/app/appIdResolver.test.ts +98 -71
- package/src/utils/app/appIdResolver.ts +31 -20
- package/src/utils/{__tests__/appConfig.unit.test.ts → appConfig.unit.test.ts} +1 -1
- package/src/utils/{__tests__/audit.unit.test.ts → audit.unit.test.ts} +1 -1
- package/src/utils/{__tests__/auth-utils.unit.test.ts → auth-utils.unit.test.ts} +16 -17
- package/src/utils/{__tests__/bundleAnalysis.unit.test.ts → bundleAnalysis.unit.test.ts} +35 -35
- package/src/utils/{__tests__/cn.unit.test.ts → cn.unit.test.ts} +1 -1
- package/src/utils/context/organisationContext.test.ts +105 -91
- package/src/utils/context/organisationContext.ts +29 -40
- package/src/utils/core/{__tests__/cn.test.ts → cn.test.ts} +3 -3
- package/src/utils/core/{__tests__/debugLogger.test.ts → debugLogger.test.ts} +2 -2
- package/src/utils/core/{__tests__/logger.test.ts → logger.test.ts} +2 -2
- package/src/utils/core/mergeRefs.ts +24 -0
- package/src/utils/{__tests__/debugLogger.test.ts → debugLogger.test.ts} +1 -1
- package/src/utils/{__tests__/deviceFingerprint.unit.test.ts → deviceFingerprint.unit.test.ts} +1 -1
- package/src/utils/dynamic/createLazyComponent.tsx +9 -1
- package/src/utils/dynamic/{__tests__/dynamicUtils.test.ts → dynamicUtils.test.ts} +2 -2
- package/src/utils/dynamic/{__tests__/lazyLoad.test.tsx → lazyLoad.test.tsx} +2 -2
- package/src/utils/{__tests__/dynamicUtils.unit.test.ts → dynamicUtils.unit.test.ts} +1 -1
- package/src/utils/file-reference/{__tests__/file-reference.test.ts → file-reference.test.ts} +214 -289
- package/src/utils/file-reference/index.ts +330 -347
- package/src/utils/{__tests__/formatDate.unit.test.ts → formatDate.unit.test.ts} +2 -2
- package/src/utils/formatting/formatDateTimeTimezone.test.ts +1 -1
- package/src/utils/formatting/formatNumber.test.ts +1 -1
- package/src/utils/{__tests__/formatting.unit.test.ts → formatting.unit.test.ts} +1 -1
- package/src/utils/google-places/googlePlacesUtils.test.ts +70 -48
- package/src/utils/google-places/googlePlacesUtils.ts +67 -99
- package/src/utils/google-places/loadGoogleMapsScript.test.ts +25 -22
- package/src/utils/google-places/loadGoogleMapsScript.ts +138 -117
- package/src/utils/{__tests__/index.unit.test.ts → index.unit.test.ts} +1 -1
- package/src/utils/{__tests__/lazyLoad.unit.test.tsx → lazyLoad.unit.test.tsx} +13 -14
- package/src/utils/location/location.test.ts +1 -1
- package/src/utils/{__tests__/logger.unit.test.ts → logger.unit.test.ts} +1 -1
- package/src/utils/{__tests__/organisationContext.unit.test.ts → organisationContext.unit.test.ts} +37 -48
- package/src/utils/performance/{__tests__/bundleAnalysis.test.ts → bundleAnalysis.test.ts} +2 -2
- package/src/utils/performance/{__tests__/performanceBenchmark.test.ts → performanceBenchmark.test.ts} +2 -2
- package/src/utils/performance/{__tests__/performanceBudgets.test.ts → performanceBudgets.test.ts} +2 -2
- package/src/utils/{__tests__/performanceBenchmark.test.ts → performanceBenchmark.test.ts} +2 -2
- package/src/utils/{__tests__/performanceBudgets.unit.test.ts → performanceBudgets.unit.test.ts} +2 -2
- package/src/utils/{__tests__/permissionTypes.unit.test.ts → permissionTypes.unit.test.ts} +1 -1
- package/src/utils/{__tests__/permissionUtils.unit.test.ts → permissionUtils.unit.test.ts} +1 -1
- package/src/utils/permissions/{__tests__/permissionTypes.test.ts → permissionTypes.test.ts} +2 -2
- package/src/utils/persistence/{__tests__/keyDerivation.test.ts → keyDerivation.test.ts} +2 -2
- package/src/utils/persistence/{__tests__/sensitiveFieldDetection.test.ts → sensitiveFieldDetection.test.ts} +2 -2
- package/src/utils/{__tests__/request-deduplication.test.ts → request-deduplication.test.ts} +2 -2
- package/src/utils/{__tests__/sanitization.unit.test.ts → sanitization.unit.test.ts} +1 -1
- package/src/utils/{__tests__/schemaUtils.unit.test.ts → schemaUtils.unit.test.ts} +1 -1
- package/src/utils/{__tests__/secureDataAccess.unit.test.ts → secureDataAccess.unit.test.ts} +2 -2
- package/src/utils/{__tests__/secureErrors.unit.test.ts → secureErrors.unit.test.ts} +4 -4
- package/src/utils/{__tests__/secureStorage.unit.test.ts → secureStorage.unit.test.ts} +1 -1
- package/src/utils/security/auth-utils.ts +34 -23
- package/src/utils/security/secureDataAccess.ts +241 -281
- package/src/utils/security/secureErrors.test.ts +1 -1
- package/src/utils/security/secureStorage.test.ts +1 -1
- package/src/utils/security/security.test.ts +25 -17
- package/src/utils/security/security.ts +15 -18
- package/src/utils/security/securityMonitor.test.ts +1 -1
- package/src/utils/{__tests__/security.unit.test.ts → security.unit.test.ts} +21 -15
- package/src/utils/{__tests__/securityMonitor.unit.test.ts → securityMonitor.unit.test.ts} +1 -1
- package/src/utils/{__tests__/sessionTracking.unit.test.ts → sessionTracking.unit.test.ts} +12 -12
- package/src/utils/storage/{__tests__/config.unit.test.ts → config.unit.test.ts} +2 -2
- package/src/utils/storage/helpers.test.ts +88 -102
- package/src/utils/storage/helpers.ts +173 -251
- package/src/utils/storage/{__tests__/index.unit.test.ts → index.unit.test.ts} +3 -3
- package/src/utils/storage/types.ts +7 -0
- package/src/utils/supabase/createBaseClient.test.ts +1 -1
- package/src/utils/timezone/timezone.test.ts +1 -1
- package/src/utils/{__tests__/timezone.test.ts → timezone.test.ts} +2 -2
- package/src/utils/validation/{__tests__/common.test.ts → common.test.ts} +2 -2
- package/src/utils/validation/{__tests__/csrf.test.ts → csrf.test.ts} +56 -28
- package/src/utils/validation/csrf.ts +42 -41
- package/src/utils/validation/{__tests__/htmlSanitization.unit.test.ts → htmlSanitization.unit.test.ts} +2 -2
- package/src/utils/validation/{__tests__/passwordSchema.test.ts → passwordSchema.test.ts} +2 -2
- package/src/utils/validation/{__tests__/schema.test.ts → schema.test.ts} +2 -2
- package/src/utils/validation/{__tests__/sqlInjectionProtection.test.ts → sqlInjectionProtection.test.ts} +2 -2
- package/src/utils/validation/{__tests__/user.test.ts → user.test.ts} +2 -2
- package/src/utils/validation/{__tests__/validation.test.ts → validation.test.ts} +2 -2
- package/src/utils/validation/{__tests__/validationUtils.test.ts → validationUtils.test.ts} +2 -2
- package/src/utils/{__tests__/validation.unit.test.ts → validation.unit.test.ts} +1 -1
- package/src/utils/{__tests__/validationUtils.unit.test.ts → validationUtils.unit.test.ts} +5 -2
- package/dist/UnifiedAuthProvider-BBD2PS3Q.js +0 -7
- package/dist/chunk-KPYQWGFQ.js +0 -183
- package/dist/types-D05dCGma.d.ts +0 -521
- package/scripts/eslint-audit.cjs +0 -222
- package/scripts/generate-docs.js +0 -157
- package/scripts/install-cursor-rules.cjs +0 -255
- package/scripts/install-eslint-config.cjs +0 -349
- package/scripts/setup-build-cache.js +0 -73
- package/scripts/validate-pre-publish.js +0 -145
- package/src/__tests__/integration/UserProfile.test.tsx +0 -124
- package/src/__tests__/public-recipe-view.test.ts +0 -228
- package/src/__tests__/rls-policies.test.ts +0 -472
- package/src/components/DataTable/__tests__/DataTable.test.tsx +0 -876
- package/src/components/DataTable/components/DataTableLayout.tsx +0 -584
- package/src/components/DataTable/components/UnifiedTableBody.tsx +0 -395
- package/src/components/DataTable/components/__tests__/DataTableLayout.test.tsx +0 -467
- package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +0 -358
- package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +0 -957
- package/src/components/DataTable/core/ActionManager.ts +0 -235
- package/src/components/DataTable/core/ColumnManager.ts +0 -204
- package/src/components/DataTable/core/DataManager.ts +0 -190
- package/src/components/DataTable/core/LocalDataAdapter.ts +0 -274
- package/src/components/DataTable/core/PluginRegistry.ts +0 -229
- package/src/components/DataTable/core/StateManager.ts +0 -312
- package/src/components/DataTable/core/__tests__/ActionManager.test.ts +0 -235
- package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +0 -141
- package/src/components/DataTable/core/__tests__/DataManager.test.ts +0 -178
- package/src/components/DataTable/core/__tests__/LocalDataAdapter.test.ts +0 -133
- package/src/components/DataTable/core/__tests__/PluginRegistry.test.ts +0 -142
- package/src/components/DataTable/core/__tests__/StateManager.test.ts +0 -158
- package/src/components/DataTable/core/interfaces.ts +0 -338
- package/src/components/DataTable/types.ts +0 -764
- package/src/hooks/public/usePublicFileDisplay.ts +0 -534
- package/src/hooks/useFileDisplay.ts +0 -748
- package/src/providers/OrganisationProvider.test.tsx +0 -40
- package/src/providers/OrganisationProvider.tsx +0 -92
- package/src/providers/__tests__/InactivityProvider.test-helper.tsx +0 -65
- package/src/providers/__tests__/OrganisationProvider.test.tsx +0 -616
- package/src/providers/__tests__/OrganisationProvider.wrapper.test.tsx +0 -591
- package/src/rbac/__tests__/cache-invalidation.test.ts +0 -393
- /package/src/components/DataTable/{components/__tests__ → ui}/COVERAGE_NOTE.md +0 -0
- /package/src/components/DataTable/utils/{__tests__/COVERAGE_NOTE.md → COVERAGE_NOTE.md} +0 -0
- /package/src/hooks/{__tests__/useApiFetch.unit.test.ts → useApiFetch.unit.test.ts} +0 -0
- /package/src/providers/{__tests__/README.md → README.md} +0 -0
- /package/src/rbac/{__tests__/index.test.ts → index.test.ts} +0 -0
- /package/src/rbac/{__tests__/rbac-integration.test.ts → rbac-integration.test.ts} +0 -0
- /package/src/types/{__tests__/README.md → README.md} +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { EventServiceContext } from './chunk-4R3T5ENU.js';
|
|
2
|
+
import { clearPalette, parseAndNormalizeEventColours, applyPalette } from './chunk-D6BMFMQZ.js';
|
|
3
3
|
import { assertAppId } from './chunk-4SXLQIZO.js';
|
|
4
|
-
import { fetchPlaceDetails, createAddressFromPlaceResult, getAddressByPlaceId, fetchPlaceAutocomplete, setOrganisationContext } from './chunk-
|
|
4
|
+
import { fetchPlaceDetails, createAddressFromPlaceResult, getAddressByPlaceId, fetchPlaceAutocomplete, setOrganisationContext } from './chunk-XPFVT3GN.js';
|
|
5
5
|
import { createLogger, logger } from './chunk-BTHN5MKC.js';
|
|
6
|
+
import { ok, err } from './chunk-44CNXN4P.js';
|
|
6
7
|
import { useState, useEffect, useCallback, useMemo, useRef, useContext } from 'react';
|
|
7
8
|
import { useLocation } from 'react-router-dom';
|
|
8
9
|
|
|
@@ -41,7 +42,6 @@ if (typeof window !== "undefined" && !cleanupTimer) {
|
|
|
41
42
|
if (cleanupTimer) {
|
|
42
43
|
clearInterval(cleanupTimer);
|
|
43
44
|
cleanupTimer = null;
|
|
44
|
-
log.debug("Query cache cleanup timer cleared on page unload.");
|
|
45
45
|
}
|
|
46
46
|
});
|
|
47
47
|
}
|
|
@@ -49,7 +49,6 @@ function cleanupQueryCache() {
|
|
|
49
49
|
if (cleanupTimer) {
|
|
50
50
|
clearInterval(cleanupTimer);
|
|
51
51
|
cleanupTimer = null;
|
|
52
|
-
log.debug("Query cache cleanup timer cleared.");
|
|
53
52
|
}
|
|
54
53
|
}
|
|
55
54
|
function useQueryCache(_supabase) {
|
|
@@ -200,6 +199,44 @@ var queryCacheHelpers = {
|
|
|
200
199
|
return promise;
|
|
201
200
|
}
|
|
202
201
|
};
|
|
202
|
+
async function fetchSuggestionsForInput(debouncedInput, apiKey, memoizedAutocompleteOptions, cacheEnabled, cacheTTLAutocomplete, getCachedQuery, isCancelledRef, setters) {
|
|
203
|
+
try {
|
|
204
|
+
let result;
|
|
205
|
+
if (cacheEnabled) {
|
|
206
|
+
result = await getCachedQuery(
|
|
207
|
+
"google-places-autocomplete",
|
|
208
|
+
"query",
|
|
209
|
+
debouncedInput,
|
|
210
|
+
async () => {
|
|
211
|
+
if (isCancelledRef.current) {
|
|
212
|
+
throw new Error("Request cancelled");
|
|
213
|
+
}
|
|
214
|
+
return fetchPlaceAutocomplete(debouncedInput, apiKey, memoizedAutocompleteOptions);
|
|
215
|
+
},
|
|
216
|
+
{ ttl: cacheTTLAutocomplete, enabled: true }
|
|
217
|
+
);
|
|
218
|
+
} else {
|
|
219
|
+
result = await fetchPlaceAutocomplete(debouncedInput, apiKey, memoizedAutocompleteOptions);
|
|
220
|
+
}
|
|
221
|
+
if (isCancelledRef.current) return;
|
|
222
|
+
if (!result.ok) {
|
|
223
|
+
setters.setError(new Error(result.error.message));
|
|
224
|
+
setters.setSuggestions([]);
|
|
225
|
+
setters.setIsLoading(false);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
setters.setSuggestions(result.data);
|
|
229
|
+
setters.setIsLoading(false);
|
|
230
|
+
} catch (err2) {
|
|
231
|
+
if (isCancelledRef.current || err2 instanceof Error && err2.message === "Request cancelled") {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const error = err2 instanceof Error ? err2 : new Error("Failed to fetch autocomplete suggestions");
|
|
235
|
+
setters.setError(error);
|
|
236
|
+
setters.setSuggestions([]);
|
|
237
|
+
setters.setIsLoading(false);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
203
240
|
function useAddressAutocomplete(apiKey, inputValue, options = {}) {
|
|
204
241
|
const {
|
|
205
242
|
debounceDelay = 300,
|
|
@@ -244,45 +281,19 @@ function useAddressAutocomplete(apiKey, inputValue, options = {}) {
|
|
|
244
281
|
}
|
|
245
282
|
setIsLoading(true);
|
|
246
283
|
setError(null);
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
throw new Error("Request cancelled");
|
|
259
|
-
}
|
|
260
|
-
return fetchPlaceAutocomplete(debouncedInput, apiKey, memoizedAutocompleteOptions);
|
|
261
|
-
},
|
|
262
|
-
{ ttl: cacheTTL.autocomplete, enabled: true }
|
|
263
|
-
);
|
|
264
|
-
} else {
|
|
265
|
-
predictions = await fetchPlaceAutocomplete(debouncedInput, apiKey, memoizedAutocompleteOptions);
|
|
266
|
-
}
|
|
267
|
-
if (!isCancelled) {
|
|
268
|
-
setSuggestions(predictions);
|
|
269
|
-
setIsLoading(false);
|
|
270
|
-
}
|
|
271
|
-
} catch (err) {
|
|
272
|
-
if (isCancelled || err instanceof Error && err.message === "Request cancelled") {
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
if (!isCancelled) {
|
|
276
|
-
const error2 = err instanceof Error ? err : new Error("Failed to fetch autocomplete suggestions");
|
|
277
|
-
setError(error2);
|
|
278
|
-
setSuggestions([]);
|
|
279
|
-
setIsLoading(false);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
};
|
|
283
|
-
fetchSuggestions();
|
|
284
|
+
const isCancelledRef = { current: false };
|
|
285
|
+
fetchSuggestionsForInput(
|
|
286
|
+
debouncedInput,
|
|
287
|
+
apiKey,
|
|
288
|
+
memoizedAutocompleteOptions,
|
|
289
|
+
cacheEnabled,
|
|
290
|
+
cacheTTL.autocomplete ?? 3600,
|
|
291
|
+
getCachedQuery,
|
|
292
|
+
isCancelledRef,
|
|
293
|
+
{ setSuggestions, setError, setIsLoading }
|
|
294
|
+
);
|
|
284
295
|
return () => {
|
|
285
|
-
|
|
296
|
+
isCancelledRef.current = true;
|
|
286
297
|
};
|
|
287
298
|
}, [debouncedInput, apiKey, cacheEnabled, cacheTTL.autocomplete, memoizedAutocompleteOptions, getCachedQuery]);
|
|
288
299
|
const selectAddress = useCallback(
|
|
@@ -293,25 +304,28 @@ function useAddressAutocomplete(apiKey, inputValue, options = {}) {
|
|
|
293
304
|
setIsLoading(true);
|
|
294
305
|
setError(null);
|
|
295
306
|
try {
|
|
296
|
-
let
|
|
307
|
+
let placeResult;
|
|
297
308
|
if (cacheEnabled) {
|
|
298
|
-
|
|
309
|
+
placeResult = await getCachedQuery(
|
|
299
310
|
"google-places-details",
|
|
300
311
|
"place_id",
|
|
301
312
|
placeId,
|
|
302
|
-
async () =>
|
|
303
|
-
return fetchPlaceDetails(placeId, apiKey);
|
|
304
|
-
},
|
|
313
|
+
async () => fetchPlaceDetails(placeId, apiKey),
|
|
305
314
|
{ ttl: cacheTTL.placeDetails, enabled: true }
|
|
306
315
|
);
|
|
307
316
|
} else {
|
|
308
|
-
|
|
317
|
+
placeResult = await fetchPlaceDetails(placeId, apiKey);
|
|
318
|
+
}
|
|
319
|
+
if (!placeResult.ok) {
|
|
320
|
+
setError(new Error(placeResult.error.message));
|
|
321
|
+
setIsLoading(false);
|
|
322
|
+
return null;
|
|
309
323
|
}
|
|
310
|
-
const parsedAddress = createAddressFromPlaceResult(
|
|
324
|
+
const parsedAddress = createAddressFromPlaceResult(placeResult.data);
|
|
311
325
|
setIsLoading(false);
|
|
312
326
|
return parsedAddress;
|
|
313
|
-
} catch (
|
|
314
|
-
const error2 =
|
|
327
|
+
} catch (err2) {
|
|
328
|
+
const error2 = err2 instanceof Error ? err2 : new Error("Failed to fetch place details");
|
|
315
329
|
setError(error2);
|
|
316
330
|
setIsLoading(false);
|
|
317
331
|
return null;
|
|
@@ -326,25 +340,25 @@ function useAddressAutocomplete(apiKey, inputValue, options = {}) {
|
|
|
326
340
|
return null;
|
|
327
341
|
}
|
|
328
342
|
try {
|
|
343
|
+
let result;
|
|
329
344
|
if (cacheEnabled) {
|
|
330
|
-
|
|
345
|
+
result = await getCachedQuery(
|
|
331
346
|
"google-places-details",
|
|
332
347
|
"place_id",
|
|
333
348
|
placeId,
|
|
334
|
-
async () =>
|
|
335
|
-
const result = await getAddressByPlaceId(placeId, apiKey);
|
|
336
|
-
if (!result) {
|
|
337
|
-
throw new Error("Failed to fetch address");
|
|
338
|
-
}
|
|
339
|
-
return result;
|
|
340
|
-
},
|
|
349
|
+
async () => getAddressByPlaceId(placeId, apiKey),
|
|
341
350
|
{ ttl: cacheTTL.placeDetails, enabled: true }
|
|
342
351
|
);
|
|
343
352
|
} else {
|
|
344
|
-
|
|
353
|
+
result = await getAddressByPlaceId(placeId, apiKey);
|
|
345
354
|
}
|
|
346
|
-
|
|
347
|
-
|
|
355
|
+
if (!result.ok) {
|
|
356
|
+
setError(new Error(result.error.message));
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
return result.data;
|
|
360
|
+
} catch (err2) {
|
|
361
|
+
const error2 = err2 instanceof Error ? err2 : new Error("Failed to get address by place_id");
|
|
348
362
|
setError(error2);
|
|
349
363
|
return null;
|
|
350
364
|
}
|
|
@@ -467,31 +481,38 @@ function generateUniqueFileName(originalName) {
|
|
|
467
481
|
return `${timestamp}-${uuid}-${baseName}.${extension}`;
|
|
468
482
|
}
|
|
469
483
|
async function extractFileMetadata(file, options, uploadedBy) {
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
484
|
+
try {
|
|
485
|
+
const metadata = {
|
|
486
|
+
mimeType: file.type,
|
|
487
|
+
size: file.size,
|
|
488
|
+
...options.orgId && { orgId: options.orgId },
|
|
489
|
+
...options.userId && { userId: options.userId },
|
|
490
|
+
appName: options.appName || "pace-core",
|
|
491
|
+
uploadedBy,
|
|
492
|
+
uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
493
|
+
tags: options.tags || [],
|
|
494
|
+
isPublic: options.isPublic || false,
|
|
495
|
+
customMetadata: options.metadata || {}
|
|
496
|
+
};
|
|
497
|
+
if (file.type.startsWith("image/")) {
|
|
498
|
+
try {
|
|
499
|
+
const dimensions = await getImageDimensions(file);
|
|
500
|
+
metadata.width = dimensions.width;
|
|
501
|
+
metadata.height = dimensions.height;
|
|
502
|
+
} catch (_error) {
|
|
503
|
+
}
|
|
504
|
+
}
|
|
483
505
|
try {
|
|
484
|
-
|
|
485
|
-
metadata.width = dimensions.width;
|
|
486
|
-
metadata.height = dimensions.height;
|
|
506
|
+
metadata.hash = await generateFileHash(file);
|
|
487
507
|
} catch (_error) {
|
|
488
508
|
}
|
|
509
|
+
return ok(metadata);
|
|
510
|
+
} catch (error) {
|
|
511
|
+
return err({
|
|
512
|
+
code: "EXTRACT_METADATA_FAILED",
|
|
513
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
514
|
+
});
|
|
489
515
|
}
|
|
490
|
-
try {
|
|
491
|
-
metadata.hash = await generateFileHash(file);
|
|
492
|
-
} catch (_error) {
|
|
493
|
-
}
|
|
494
|
-
return metadata;
|
|
495
516
|
}
|
|
496
517
|
async function getImageDimensions(file) {
|
|
497
518
|
return new Promise((resolve, reject) => {
|
|
@@ -546,44 +567,37 @@ async function uploadFile(supabase, file, options) {
|
|
|
546
567
|
try {
|
|
547
568
|
const sizeValidation = validateFileSize(file);
|
|
548
569
|
if (!sizeValidation.isValid) {
|
|
549
|
-
return {
|
|
550
|
-
success: false,
|
|
551
|
-
error: sizeValidation.error
|
|
552
|
-
};
|
|
570
|
+
return err({ code: "FILE_SIZE_INVALID", message: sizeValidation.error ?? "File size validation failed" });
|
|
553
571
|
}
|
|
554
572
|
const uniqueFileName = generateUniqueFileName(file.name);
|
|
555
573
|
const filePath = generateFilePath(options, uniqueFileName);
|
|
556
574
|
const folderPath = filePath.substring(0, filePath.lastIndexOf("/"));
|
|
557
|
-
const
|
|
575
|
+
const metadataResult = await extractFileMetadata(file, options, "current-user");
|
|
576
|
+
if (!metadataResult.ok) {
|
|
577
|
+
return err(metadataResult.error);
|
|
578
|
+
}
|
|
579
|
+
const metadata = metadataResult.data;
|
|
558
580
|
const bucketName = getBucketName(options.isPublic || false);
|
|
559
581
|
await ensureFolderExists(supabase, folderPath, bucketName);
|
|
560
|
-
const {
|
|
582
|
+
const { error } = await supabase.storage.from(bucketName).upload(filePath, file, {
|
|
561
583
|
cacheControl: "3600",
|
|
562
584
|
upsert: false,
|
|
563
585
|
contentType: file.type
|
|
564
586
|
});
|
|
565
587
|
if (error) {
|
|
566
|
-
return {
|
|
567
|
-
success: false,
|
|
568
|
-
error: `Upload failed: ${error.message}`
|
|
569
|
-
};
|
|
588
|
+
return err({ code: "UPLOAD_FAILED", message: error.message });
|
|
570
589
|
}
|
|
571
590
|
let publicUrl;
|
|
572
591
|
if (options.isPublic) {
|
|
573
592
|
const { data: urlData } = supabase.storage.from(bucketName).getPublicUrl(filePath);
|
|
574
593
|
publicUrl = urlData.publicUrl;
|
|
575
594
|
}
|
|
576
|
-
return {
|
|
577
|
-
success: true,
|
|
578
|
-
path: filePath,
|
|
579
|
-
publicUrl,
|
|
580
|
-
metadata
|
|
581
|
-
};
|
|
595
|
+
return ok({ path: filePath, publicUrl, metadata });
|
|
582
596
|
} catch (error) {
|
|
583
|
-
return {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
};
|
|
597
|
+
return err({
|
|
598
|
+
code: "UPLOAD_FAILED",
|
|
599
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
600
|
+
});
|
|
587
601
|
}
|
|
588
602
|
}
|
|
589
603
|
function getPublicUrl(supabase, path, isPublic = true) {
|
|
@@ -646,15 +660,18 @@ async function getSignedUrl(supabase, path, options) {
|
|
|
646
660
|
const { data, error } = await supabase.storage.from(bucketName).createSignedUrl(path, options.expiresIn || 3600);
|
|
647
661
|
if (error) {
|
|
648
662
|
log2.error("Failed to create signed URL:", error);
|
|
649
|
-
return
|
|
663
|
+
return err({ code: "SIGNED_URL_FAILED", message: error.message });
|
|
650
664
|
}
|
|
651
|
-
return {
|
|
665
|
+
return ok({
|
|
652
666
|
url: data.signedUrl,
|
|
653
667
|
expiresAt: new Date(Date.now() + (options.expiresIn || 3600) * 1e3).toISOString()
|
|
654
|
-
};
|
|
668
|
+
});
|
|
655
669
|
} catch (error) {
|
|
656
670
|
log2.error("Failed to create signed URL:", error);
|
|
657
|
-
return
|
|
671
|
+
return err({
|
|
672
|
+
code: "SIGNED_URL_FAILED",
|
|
673
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
674
|
+
});
|
|
658
675
|
}
|
|
659
676
|
}
|
|
660
677
|
var globalUrlCache = /* @__PURE__ */ new Map();
|
|
@@ -679,101 +696,94 @@ function cleanupUrlCache() {
|
|
|
679
696
|
}
|
|
680
697
|
}
|
|
681
698
|
async function generateFileUrlsBatch(supabase, fileReferences, options) {
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
const now = Date.now();
|
|
687
|
-
const ttl = (options.expiresIn || 3600) * 1e3;
|
|
688
|
-
const publicFiles = [];
|
|
689
|
-
const privateFiles = [];
|
|
690
|
-
const uncachedFiles = [];
|
|
691
|
-
for (const fileRef of fileReferences) {
|
|
692
|
-
const cacheKey = getCacheKey(fileRef.id, fileRef.file_path, fileRef.is_public);
|
|
693
|
-
const cached = globalUrlCache.get(cacheKey);
|
|
694
|
-
if (cached && cached.expiresAt > now) {
|
|
695
|
-
urlMap.set(fileRef.id, cached.url);
|
|
696
|
-
continue;
|
|
697
|
-
}
|
|
698
|
-
if (fileRef.is_public) {
|
|
699
|
-
publicFiles.push({ id: fileRef.id, file_path: fileRef.file_path });
|
|
700
|
-
} else {
|
|
701
|
-
privateFiles.push({ id: fileRef.id, file_path: fileRef.file_path });
|
|
699
|
+
try {
|
|
700
|
+
const urlMap = /* @__PURE__ */ new Map();
|
|
701
|
+
if (fileReferences.length === 0) {
|
|
702
|
+
return ok(urlMap);
|
|
702
703
|
}
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
704
|
+
const now = Date.now();
|
|
705
|
+
const ttl = (options.expiresIn || 3600) * 1e3;
|
|
706
|
+
const publicFiles = [];
|
|
707
|
+
const privateFiles = [];
|
|
708
|
+
const uncachedFiles = [];
|
|
709
|
+
for (const fileRef of fileReferences) {
|
|
710
|
+
const cacheKey = getCacheKey(fileRef.id, fileRef.file_path, fileRef.is_public);
|
|
711
|
+
const cached = globalUrlCache.get(cacheKey);
|
|
712
|
+
if (cached && cached.expiresAt > now) {
|
|
713
|
+
urlMap.set(fileRef.id, cached.url);
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
if (fileRef.is_public) {
|
|
717
|
+
publicFiles.push({ id: fileRef.id, file_path: fileRef.file_path });
|
|
718
|
+
} else {
|
|
719
|
+
privateFiles.push({ id: fileRef.id, file_path: fileRef.file_path });
|
|
715
720
|
}
|
|
716
|
-
|
|
717
|
-
log2.error(`Failed to generate public URL for file ${file.id}:`, err);
|
|
721
|
+
uncachedFiles.push(fileRef);
|
|
718
722
|
}
|
|
719
|
-
|
|
720
|
-
if (privateFiles.length > 0) {
|
|
721
|
-
const signedUrlPromises = privateFiles.map(async (file) => {
|
|
723
|
+
for (const file of publicFiles) {
|
|
722
724
|
try {
|
|
725
|
+
const url = getPublicUrl(supabase, file.file_path, true);
|
|
726
|
+
if (url) {
|
|
727
|
+
urlMap.set(file.id, url);
|
|
728
|
+
const cacheKey = getCacheKey(file.id, file.file_path, true);
|
|
729
|
+
globalUrlCache.set(cacheKey, { url, expiresAt: now + ttl });
|
|
730
|
+
}
|
|
731
|
+
} catch (e) {
|
|
732
|
+
log2.error(`Failed to generate public URL for file ${file.id}:`, e);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
if (privateFiles.length > 0) {
|
|
736
|
+
const signedUrlPromises = privateFiles.map(async (file) => {
|
|
723
737
|
const signedUrlResult = await getSignedUrl(supabase, file.file_path, {
|
|
724
738
|
appName: options.appName || "pace-core",
|
|
725
739
|
orgId: options.orgId,
|
|
726
740
|
expiresIn: options.expiresIn || 3600
|
|
727
741
|
});
|
|
728
|
-
const url = signedUrlResult
|
|
742
|
+
const url = signedUrlResult.ok ? signedUrlResult.data.url : null;
|
|
729
743
|
if (url) {
|
|
730
744
|
const cacheKey = getCacheKey(file.id, file.file_path, false);
|
|
731
|
-
globalUrlCache.set(cacheKey, {
|
|
732
|
-
url,
|
|
733
|
-
expiresAt: now + ttl
|
|
734
|
-
});
|
|
745
|
+
globalUrlCache.set(cacheKey, { url, expiresAt: now + ttl });
|
|
735
746
|
}
|
|
736
747
|
return { id: file.id, url };
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
for (const result of signedUrlResults) {
|
|
744
|
-
if (result.url) {
|
|
745
|
-
urlMap.set(result.id, result.url);
|
|
748
|
+
});
|
|
749
|
+
const signedUrlResults = await Promise.all(signedUrlPromises);
|
|
750
|
+
for (const result of signedUrlResults) {
|
|
751
|
+
if (result.url) {
|
|
752
|
+
urlMap.set(result.id, result.url);
|
|
753
|
+
}
|
|
746
754
|
}
|
|
747
755
|
}
|
|
756
|
+
if (uncachedFiles.length > 0) {
|
|
757
|
+
cleanupUrlCache();
|
|
758
|
+
}
|
|
759
|
+
return ok(urlMap);
|
|
760
|
+
} catch (error) {
|
|
761
|
+
return err({
|
|
762
|
+
code: "BATCH_URLS_FAILED",
|
|
763
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
764
|
+
});
|
|
748
765
|
}
|
|
749
|
-
if (uncachedFiles.length > 0) {
|
|
750
|
-
cleanupUrlCache();
|
|
751
|
-
}
|
|
752
|
-
return urlMap;
|
|
753
766
|
}
|
|
754
767
|
async function deleteFile(supabase, path, isPublic = false) {
|
|
755
768
|
try {
|
|
756
769
|
const bucketName = getBucketName(isPublic);
|
|
757
770
|
const { error } = await supabase.storage.from(bucketName).remove([path]);
|
|
758
771
|
if (error) {
|
|
759
|
-
return {
|
|
760
|
-
success: false,
|
|
761
|
-
error: `Delete failed: ${error.message}`
|
|
762
|
-
};
|
|
772
|
+
return err({ code: "DELETE_FAILED", message: error.message });
|
|
763
773
|
}
|
|
764
|
-
return
|
|
774
|
+
return ok(void 0);
|
|
765
775
|
} catch (error) {
|
|
766
|
-
return {
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
};
|
|
776
|
+
return err({
|
|
777
|
+
code: "DELETE_FAILED",
|
|
778
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
779
|
+
});
|
|
770
780
|
}
|
|
771
781
|
}
|
|
772
782
|
async function listFiles(supabase, options) {
|
|
773
783
|
try {
|
|
774
784
|
const bucketName = getBucketName(options.isPublic || false);
|
|
775
785
|
if (!options.orgId && !options.userId) {
|
|
776
|
-
|
|
786
|
+
return err({ code: "LIST_FILES_INVALID_OPTIONS", message: "Either orgId or userId is required for listing files" });
|
|
777
787
|
}
|
|
778
788
|
const basePath = options.orgId ? options.orgId : `users/${options.userId}`;
|
|
779
789
|
const pathPrefix = `${basePath}/`;
|
|
@@ -785,7 +795,7 @@ async function listFiles(supabase, options) {
|
|
|
785
795
|
});
|
|
786
796
|
if (error) {
|
|
787
797
|
log2.error("Failed to list files:", error);
|
|
788
|
-
return {
|
|
798
|
+
return err({ code: "LIST_FILES_FAILED", message: error.message });
|
|
789
799
|
}
|
|
790
800
|
const files = (data || []).map((item) => ({
|
|
791
801
|
name: item.name,
|
|
@@ -804,14 +814,17 @@ async function listFiles(supabase, options) {
|
|
|
804
814
|
isPublic: options.isPublic || false
|
|
805
815
|
}
|
|
806
816
|
}));
|
|
807
|
-
return {
|
|
817
|
+
return ok({
|
|
808
818
|
files,
|
|
809
819
|
totalCount: files.length,
|
|
810
820
|
hasMore: files.length >= (options.limit || 100)
|
|
811
|
-
};
|
|
821
|
+
});
|
|
812
822
|
} catch (error) {
|
|
813
823
|
log2.error("Failed to list files:", error);
|
|
814
|
-
return {
|
|
824
|
+
return err({
|
|
825
|
+
code: "LIST_FILES_FAILED",
|
|
826
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
827
|
+
});
|
|
815
828
|
}
|
|
816
829
|
}
|
|
817
830
|
async function downloadFile(supabase, path, isPublic = false) {
|
|
@@ -820,27 +833,28 @@ async function downloadFile(supabase, path, isPublic = false) {
|
|
|
820
833
|
const { data, error } = await supabase.storage.from(bucketName).download(path);
|
|
821
834
|
if (error) {
|
|
822
835
|
log2.error("Failed to download file:", error);
|
|
823
|
-
return
|
|
836
|
+
return err({ code: "DOWNLOAD_FAILED", message: error.message });
|
|
824
837
|
}
|
|
825
838
|
if (!data) {
|
|
826
|
-
return
|
|
839
|
+
return err({ code: "DOWNLOAD_FAILED", message: "No data returned" });
|
|
827
840
|
}
|
|
828
841
|
const fileName = path.split("/").pop() || "download";
|
|
829
|
-
const { data: fileInfo } = await supabase.storage.from(bucketName).list(path.split("/").slice(0, -1).join("/"), {
|
|
830
|
-
search: fileName
|
|
831
|
-
});
|
|
842
|
+
const { data: fileInfo } = await supabase.storage.from(bucketName).list(path.split("/").slice(0, -1).join("/"), { search: fileName });
|
|
832
843
|
const metadata = fileInfo?.[0]?.metadata || {};
|
|
833
|
-
return {
|
|
844
|
+
return ok({
|
|
834
845
|
blob: data,
|
|
835
846
|
metadata: {
|
|
836
847
|
name: fileName,
|
|
837
848
|
size: metadata.size || data.size,
|
|
838
849
|
type: metadata.mimetype || "application/octet-stream"
|
|
839
850
|
}
|
|
840
|
-
};
|
|
851
|
+
});
|
|
841
852
|
} catch (error) {
|
|
842
853
|
log2.error("Failed to download file:", error);
|
|
843
|
-
return
|
|
854
|
+
return err({
|
|
855
|
+
code: "DOWNLOAD_FAILED",
|
|
856
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
857
|
+
});
|
|
844
858
|
}
|
|
845
859
|
}
|
|
846
860
|
async function archiveFile(supabase, path, options) {
|
|
@@ -852,25 +866,22 @@ async function archiveFile(supabase, path, options) {
|
|
|
852
866
|
} else if (options.userId) {
|
|
853
867
|
archivedPath = path.replace(`users/${options.userId}/`, `archived/users/${options.userId}/`);
|
|
854
868
|
} else {
|
|
855
|
-
|
|
869
|
+
return err({ code: "ARCHIVE_FAILED", message: "Either orgId or userId is required for archiving files" });
|
|
856
870
|
}
|
|
857
871
|
const { error: copyError } = await supabase.storage.from(bucketName).copy(path, archivedPath);
|
|
858
872
|
if (copyError) {
|
|
859
|
-
return {
|
|
860
|
-
success: false,
|
|
861
|
-
error: `Archive failed: ${copyError.message}`
|
|
862
|
-
};
|
|
873
|
+
return err({ code: "ARCHIVE_FAILED", message: copyError.message });
|
|
863
874
|
}
|
|
864
875
|
const deleteResult = await deleteFile(supabase, path, options.isPublic || false);
|
|
865
|
-
if (!deleteResult.
|
|
866
|
-
return deleteResult;
|
|
876
|
+
if (!deleteResult.ok) {
|
|
877
|
+
return err(deleteResult.error);
|
|
867
878
|
}
|
|
868
|
-
return
|
|
879
|
+
return ok(void 0);
|
|
869
880
|
} catch (error) {
|
|
870
|
-
return {
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
};
|
|
881
|
+
return err({
|
|
882
|
+
code: "ARCHIVE_FAILED",
|
|
883
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
884
|
+
});
|
|
874
885
|
}
|
|
875
886
|
}
|
|
876
887
|
var publicFileCache = /* @__PURE__ */ new Map();
|
|
@@ -889,263 +900,80 @@ function usePublicFileDisplay(table_name, record_id, organisation_id, category,
|
|
|
889
900
|
const [isLoading, setIsLoading] = useState(false);
|
|
890
901
|
const [error, setError] = useState(null);
|
|
891
902
|
const fetchFiles = useCallback(async () => {
|
|
903
|
+
const emptySetters = () => {
|
|
904
|
+
applyEmptyPublicFileState(
|
|
905
|
+
setFileUrl,
|
|
906
|
+
setFileReference,
|
|
907
|
+
setFileReferences,
|
|
908
|
+
setFileUrls,
|
|
909
|
+
setFileCount,
|
|
910
|
+
setIsLoading,
|
|
911
|
+
setError
|
|
912
|
+
);
|
|
913
|
+
};
|
|
892
914
|
if (!table_name || !record_id || !supabase) {
|
|
893
|
-
|
|
894
|
-
setFileReference(null);
|
|
895
|
-
setFileReferences([]);
|
|
896
|
-
setFileUrls(/* @__PURE__ */ new Map());
|
|
897
|
-
setFileCount(0);
|
|
898
|
-
setIsLoading(false);
|
|
915
|
+
emptySetters();
|
|
899
916
|
return;
|
|
900
917
|
}
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
setFileCount(cachedData.fileCount || 0);
|
|
917
|
-
setIsLoading(false);
|
|
918
|
-
setError(null);
|
|
919
|
-
return;
|
|
920
|
-
}
|
|
918
|
+
warnInvalidOrganisationIdUuid(organisation_id ?? void 0);
|
|
919
|
+
const cacheKey = buildPublicFileCacheKey(table_name, record_id, organisation_id, category);
|
|
920
|
+
const cached = getCachedPublicFileData(cacheKey, enableCache);
|
|
921
|
+
if (cached) {
|
|
922
|
+
applyCachedPublicFileState(
|
|
923
|
+
cached,
|
|
924
|
+
setFileUrl,
|
|
925
|
+
setFileReference,
|
|
926
|
+
setFileReferences,
|
|
927
|
+
setFileUrls,
|
|
928
|
+
setFileCount,
|
|
929
|
+
setIsLoading,
|
|
930
|
+
setError
|
|
931
|
+
);
|
|
932
|
+
return;
|
|
921
933
|
}
|
|
922
934
|
try {
|
|
923
935
|
setIsLoading(true);
|
|
924
936
|
setError(null);
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
category
|
|
931
|
-
});
|
|
932
|
-
let userScopedFiles = [];
|
|
933
|
-
const orgScopedFiles = [];
|
|
934
|
-
if (category) {
|
|
935
|
-
const { data: userData, error: userRpcError } = await supabase.rpc("data_file_reference_by_category_list", {
|
|
936
|
-
p_table_name: table_name,
|
|
937
|
-
p_record_id: record_id,
|
|
938
|
-
p_category: category,
|
|
939
|
-
p_organisation_id: void 0
|
|
940
|
-
});
|
|
941
|
-
if (!userRpcError && userData) {
|
|
942
|
-
userScopedFiles = userData.filter((item) => item.is_public === true && item.id && item.file_path && item.file_metadata).map((item) => {
|
|
943
|
-
const metadata = item.file_metadata;
|
|
944
|
-
return {
|
|
945
|
-
id: item.id,
|
|
946
|
-
table_name,
|
|
947
|
-
record_id,
|
|
948
|
-
file_path: item.file_path,
|
|
949
|
-
file_metadata: item.file_metadata || null,
|
|
950
|
-
organisation_id: null,
|
|
951
|
-
app_id: metadata?.app_id || "",
|
|
952
|
-
is_public: true,
|
|
953
|
-
created_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
|
|
954
|
-
updated_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
|
|
955
|
-
created_by: null
|
|
956
|
-
};
|
|
957
|
-
});
|
|
958
|
-
}
|
|
959
|
-
} else {
|
|
960
|
-
const { data: userData, error: userRpcError } = await supabase.rpc("data_file_reference_list", {
|
|
961
|
-
p_table_name: table_name,
|
|
962
|
-
p_record_id: record_id,
|
|
963
|
-
p_organisation_id: void 0
|
|
964
|
-
});
|
|
965
|
-
if (!userRpcError && userData) {
|
|
966
|
-
const ids = userData.map((item) => item.id);
|
|
967
|
-
if (ids.length > 0) {
|
|
968
|
-
const { data: fullData } = await supabase.from("core_file_references").select("id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at, created_by").in("id", ids).eq("is_public", true);
|
|
969
|
-
userScopedFiles = fullData || [];
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
const allFiles = [...userScopedFiles, ...orgScopedFiles];
|
|
974
|
-
allFiles.sort((a, b) => {
|
|
975
|
-
const aTime = new Date(a.created_at || 0).getTime();
|
|
976
|
-
const bTime = new Date(b.created_at || 0).getTime();
|
|
977
|
-
return bTime - aTime;
|
|
978
|
-
});
|
|
979
|
-
files = allFiles;
|
|
980
|
-
logger.debug("usePublicFileDisplay", "Found files with undefined organisation_id:", {
|
|
981
|
-
userScopedCount: userScopedFiles.length,
|
|
982
|
-
orgScopedCount: orgScopedFiles.length,
|
|
983
|
-
totalCount: files.length
|
|
984
|
-
});
|
|
985
|
-
} else {
|
|
986
|
-
if (category) {
|
|
987
|
-
const rpcParams = {
|
|
988
|
-
p_table_name: table_name,
|
|
989
|
-
p_record_id: record_id,
|
|
990
|
-
p_category: category,
|
|
991
|
-
p_organisation_id: organisation_id ?? null
|
|
992
|
-
};
|
|
993
|
-
const { data, error: rpcError } = await supabase.rpc("data_file_reference_by_category_list", rpcParams);
|
|
994
|
-
if (rpcError) {
|
|
995
|
-
logger.error("usePublicFileDisplay", "RPC function error", {
|
|
996
|
-
function: "data_file_reference_by_category_list",
|
|
997
|
-
table_name,
|
|
998
|
-
record_id,
|
|
999
|
-
category,
|
|
1000
|
-
organisation_id,
|
|
1001
|
-
error: rpcError.message,
|
|
1002
|
-
errorCode: rpcError.code,
|
|
1003
|
-
errorDetails: rpcError.details,
|
|
1004
|
-
errorHint: rpcError.hint
|
|
1005
|
-
});
|
|
1006
|
-
throw new Error(rpcError.message || "Failed to fetch file reference");
|
|
1007
|
-
}
|
|
1008
|
-
if (!data || data.length === 0) {
|
|
1009
|
-
files = [];
|
|
1010
|
-
} else {
|
|
1011
|
-
files = data.filter((item) => {
|
|
1012
|
-
return item.is_public === true && item.id && item.file_path && item.file_metadata;
|
|
1013
|
-
}).map((item) => {
|
|
1014
|
-
const metadata = item.file_metadata;
|
|
1015
|
-
return {
|
|
1016
|
-
id: item.id,
|
|
1017
|
-
table_name,
|
|
1018
|
-
record_id,
|
|
1019
|
-
file_path: item.file_path,
|
|
1020
|
-
file_metadata: item.file_metadata || null,
|
|
1021
|
-
organisation_id: organisation_id ?? null,
|
|
1022
|
-
app_id: metadata?.app_id || "",
|
|
1023
|
-
is_public: true,
|
|
1024
|
-
// RPC already filtered for public files
|
|
1025
|
-
created_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
|
|
1026
|
-
updated_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
|
|
1027
|
-
created_by: null
|
|
1028
|
-
};
|
|
1029
|
-
});
|
|
1030
|
-
}
|
|
1031
|
-
} else {
|
|
1032
|
-
const { data: fileIds, error: rpcError } = await supabase.rpc("data_file_reference_list", {
|
|
1033
|
-
p_table_name: table_name,
|
|
1034
|
-
p_record_id: record_id,
|
|
1035
|
-
p_organisation_id: organisation_id ?? null
|
|
1036
|
-
});
|
|
1037
|
-
if (rpcError) {
|
|
1038
|
-
logger.error("usePublicFileDisplay", "RPC function error", {
|
|
1039
|
-
function: "data_file_reference_list",
|
|
1040
|
-
table_name,
|
|
1041
|
-
record_id,
|
|
1042
|
-
organisation_id,
|
|
1043
|
-
error: rpcError.message,
|
|
1044
|
-
errorCode: rpcError.code,
|
|
1045
|
-
errorDetails: rpcError.details,
|
|
1046
|
-
errorHint: rpcError.hint
|
|
1047
|
-
});
|
|
1048
|
-
throw new Error(rpcError.message || "Failed to fetch file references");
|
|
1049
|
-
}
|
|
1050
|
-
if (!fileIds || fileIds.length === 0) {
|
|
1051
|
-
files = [];
|
|
1052
|
-
} else {
|
|
1053
|
-
const ids = fileIds.map((item) => item.id);
|
|
1054
|
-
const { data: fullData, error: fetchError } = await supabase.from("core_file_references").select("id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at, created_by").in("id", ids).eq("is_public", true);
|
|
1055
|
-
if (fetchError) {
|
|
1056
|
-
throw new Error(fetchError.message || "Failed to fetch file references");
|
|
1057
|
-
}
|
|
1058
|
-
files = fullData || [];
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
const publicFiles = files.filter((f) => f.is_public === true);
|
|
1063
|
-
if (publicFiles.length === 0) {
|
|
1064
|
-
setFileUrl(null);
|
|
1065
|
-
setFileReference(null);
|
|
1066
|
-
setFileReferences([]);
|
|
1067
|
-
setFileUrls(/* @__PURE__ */ new Map());
|
|
1068
|
-
setFileCount(0);
|
|
1069
|
-
if (enableCache) {
|
|
1070
|
-
publicFileCache.set(cacheKey, {
|
|
1071
|
-
data: { fileUrl: null, fileReference: null, fileReferences: [], fileUrls: /* @__PURE__ */ new Map(), fileCount: 0 },
|
|
1072
|
-
timestamp: Date.now(),
|
|
1073
|
-
ttl: cacheTtl
|
|
1074
|
-
});
|
|
1075
|
-
}
|
|
937
|
+
const rows = await fetchPublicFileRows(supabase, table_name, record_id, organisation_id, category);
|
|
938
|
+
const publicRows = rows.filter((f) => f.is_public === true);
|
|
939
|
+
if (publicRows.length === 0) {
|
|
940
|
+
emptySetters();
|
|
941
|
+
setCachedPublicFileData(cacheKey, emptyCacheData, enableCache, cacheTtl);
|
|
1076
942
|
return;
|
|
1077
943
|
}
|
|
1078
|
-
const fileRefs =
|
|
1079
|
-
const fileName = f.file_path.split("/").pop() || "unknown";
|
|
1080
|
-
const fileType = fileName.split(".").pop() || "unknown";
|
|
1081
|
-
const metadata = f.file_metadata;
|
|
1082
|
-
return {
|
|
1083
|
-
id: f.id,
|
|
1084
|
-
table_name: f.table_name,
|
|
1085
|
-
record_id: f.record_id,
|
|
1086
|
-
file_path: f.file_path,
|
|
1087
|
-
file_metadata: {
|
|
1088
|
-
fileName: metadata?.fileName || fileName,
|
|
1089
|
-
fileType: metadata?.fileType || fileType,
|
|
1090
|
-
category: metadata?.category || "general_documents" /* GENERAL_DOCUMENTS */,
|
|
1091
|
-
...metadata || {}
|
|
1092
|
-
},
|
|
1093
|
-
organisation_id: f.organisation_id,
|
|
1094
|
-
app_id: f.app_id,
|
|
1095
|
-
is_public: f.is_public ?? true,
|
|
1096
|
-
created_at: f.created_at || (/* @__PURE__ */ new Date()).toISOString(),
|
|
1097
|
-
updated_at: f.updated_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
1098
|
-
};
|
|
1099
|
-
});
|
|
944
|
+
const fileRefs = mapDbRowsToFileReferences(publicRows);
|
|
1100
945
|
setFileReferences(fileRefs);
|
|
1101
946
|
setFileCount(fileRefs.length);
|
|
1102
947
|
if (category && fileRefs.length > 0) {
|
|
1103
948
|
const firstFile = fileRefs[0];
|
|
1104
949
|
setFileReference(firstFile);
|
|
1105
|
-
|
|
1106
|
-
setFileUrl(url);
|
|
950
|
+
setFileUrl(getPublicUrl(supabase, firstFile.file_path, true));
|
|
1107
951
|
} else {
|
|
1108
|
-
const
|
|
952
|
+
const urlResult = await generateFileUrlsBatch(supabase, fileRefs, {
|
|
1109
953
|
appName: "pace-core",
|
|
1110
954
|
orgId: organisation_id,
|
|
1111
955
|
expiresIn: 3600
|
|
1112
956
|
});
|
|
1113
|
-
setFileUrls(
|
|
957
|
+
setFileUrls(urlResult.ok ? urlResult.data : /* @__PURE__ */ new Map());
|
|
1114
958
|
setFileReference(null);
|
|
1115
959
|
setFileUrl(null);
|
|
1116
960
|
}
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
const urlMap = /* @__PURE__ */ new Map();
|
|
1125
|
-
for (const fileRef of fileRefs) {
|
|
1126
|
-
const url = getPublicUrl(supabase, fileRef.file_path, true);
|
|
1127
|
-
if (url) {
|
|
1128
|
-
urlMap.set(fileRef.id, url);
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
return urlMap;
|
|
1132
|
-
})(),
|
|
1133
|
-
fileCount: fileRefs.length
|
|
1134
|
-
},
|
|
1135
|
-
timestamp: Date.now(),
|
|
1136
|
-
ttl: cacheTtl
|
|
1137
|
-
});
|
|
1138
|
-
}
|
|
1139
|
-
} catch (err) {
|
|
961
|
+
setCachedPublicFileData(
|
|
962
|
+
cacheKey,
|
|
963
|
+
buildCacheDataFromRefs(fileRefs, category, supabase),
|
|
964
|
+
enableCache,
|
|
965
|
+
cacheTtl
|
|
966
|
+
);
|
|
967
|
+
} catch (err2) {
|
|
1140
968
|
logger.error("usePublicFileDisplay", "Error fetching files", {
|
|
1141
969
|
table_name,
|
|
1142
970
|
record_id,
|
|
1143
971
|
organisation_id,
|
|
1144
972
|
category,
|
|
1145
|
-
error:
|
|
1146
|
-
errorDetails:
|
|
973
|
+
error: err2 instanceof Error ? err2.message : "Unknown error",
|
|
974
|
+
errorDetails: err2 instanceof Error ? err2.stack : String(err2)
|
|
1147
975
|
});
|
|
1148
|
-
const error2 =
|
|
976
|
+
const error2 = err2 instanceof Error ? err2 : new Error("Unknown error occurred");
|
|
1149
977
|
setError(error2);
|
|
1150
978
|
setFileUrl(null);
|
|
1151
979
|
setFileReference(null);
|
|
@@ -1172,7 +1000,7 @@ function usePublicFileDisplay(table_name, record_id, organisation_id, category,
|
|
|
1172
1000
|
const refetch = useCallback(async () => {
|
|
1173
1001
|
if (!table_name || !record_id) return;
|
|
1174
1002
|
if (enableCache) {
|
|
1175
|
-
const cacheKey =
|
|
1003
|
+
const cacheKey = buildPublicFileCacheKey(table_name, record_id, organisation_id, category);
|
|
1176
1004
|
publicFileCache.delete(cacheKey);
|
|
1177
1005
|
}
|
|
1178
1006
|
await fetchFiles();
|
|
@@ -1188,6 +1016,217 @@ function usePublicFileDisplay(table_name, record_id, organisation_id, category,
|
|
|
1188
1016
|
refetch
|
|
1189
1017
|
};
|
|
1190
1018
|
}
|
|
1019
|
+
function buildPublicFileCacheKey(table_name, record_id, organisation_id, category) {
|
|
1020
|
+
return `public_file_${table_name}_${record_id}_${organisation_id === void 0 ? "undefined" : organisation_id ?? "null"}_${category || "all"}`;
|
|
1021
|
+
}
|
|
1022
|
+
function getCachedPublicFileData(cacheKey, enableCache) {
|
|
1023
|
+
if (!enableCache) return null;
|
|
1024
|
+
const cached = publicFileCache.get(cacheKey);
|
|
1025
|
+
if (!cached || Date.now() - cached.timestamp >= cached.ttl) return null;
|
|
1026
|
+
return cached.data;
|
|
1027
|
+
}
|
|
1028
|
+
function setCachedPublicFileData(cacheKey, data, enableCache, cacheTtl) {
|
|
1029
|
+
if (!enableCache) return;
|
|
1030
|
+
publicFileCache.set(cacheKey, {
|
|
1031
|
+
data,
|
|
1032
|
+
timestamp: Date.now(),
|
|
1033
|
+
ttl: cacheTtl
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
function mapRpcItemToRow(item, table_name, record_id, organisation_id) {
|
|
1037
|
+
const metadata = item.file_metadata;
|
|
1038
|
+
return {
|
|
1039
|
+
id: item.id,
|
|
1040
|
+
table_name,
|
|
1041
|
+
record_id,
|
|
1042
|
+
file_path: item.file_path,
|
|
1043
|
+
file_metadata: item.file_metadata || null,
|
|
1044
|
+
organisation_id,
|
|
1045
|
+
app_id: metadata?.app_id || "",
|
|
1046
|
+
is_public: true,
|
|
1047
|
+
created_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
|
|
1048
|
+
updated_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
|
|
1049
|
+
created_by: null
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
async function fetchPublicFileRowsWhenOrgUndefined(supabase, table_name, record_id, category) {
|
|
1053
|
+
let userScopedFiles = [];
|
|
1054
|
+
const orgScopedFiles = [];
|
|
1055
|
+
logger.debug("usePublicFileDisplay", "organisation_id is undefined, searching both user-scoped and organisation-scoped files:", {
|
|
1056
|
+
table_name,
|
|
1057
|
+
record_id,
|
|
1058
|
+
category
|
|
1059
|
+
});
|
|
1060
|
+
if (category) {
|
|
1061
|
+
const { data: userData, error: userRpcError } = await supabase.rpc("data_file_reference_by_category_list", {
|
|
1062
|
+
p_table_name: table_name,
|
|
1063
|
+
p_record_id: record_id,
|
|
1064
|
+
p_category: category,
|
|
1065
|
+
p_organisation_id: void 0
|
|
1066
|
+
});
|
|
1067
|
+
if (!userRpcError && userData) {
|
|
1068
|
+
userScopedFiles = userData.filter((item) => item.is_public === true && item.id && item.file_path && item.file_metadata).map((item) => mapRpcItemToRow(item, table_name, record_id, null));
|
|
1069
|
+
}
|
|
1070
|
+
} else {
|
|
1071
|
+
const { data: userData, error: userRpcError } = await supabase.rpc("data_file_reference_list", {
|
|
1072
|
+
p_table_name: table_name,
|
|
1073
|
+
p_record_id: record_id,
|
|
1074
|
+
p_organisation_id: void 0
|
|
1075
|
+
});
|
|
1076
|
+
if (!userRpcError && userData) {
|
|
1077
|
+
const ids = userData.map((item) => item.id);
|
|
1078
|
+
if (ids.length > 0) {
|
|
1079
|
+
const { data: fullData } = await supabase.from("core_file_references").select("id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at, created_by").in("id", ids).eq("is_public", true);
|
|
1080
|
+
userScopedFiles = fullData || [];
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
const allFiles = [...userScopedFiles, ...orgScopedFiles];
|
|
1085
|
+
allFiles.sort((a, b) => {
|
|
1086
|
+
const aTime = new Date(a.created_at || 0).getTime();
|
|
1087
|
+
const bTime = new Date(b.created_at || 0).getTime();
|
|
1088
|
+
return bTime - aTime;
|
|
1089
|
+
});
|
|
1090
|
+
logger.debug("usePublicFileDisplay", "Found files with undefined organisation_id:", {
|
|
1091
|
+
userScopedCount: userScopedFiles.length,
|
|
1092
|
+
orgScopedCount: orgScopedFiles.length,
|
|
1093
|
+
totalCount: allFiles.length
|
|
1094
|
+
});
|
|
1095
|
+
return allFiles;
|
|
1096
|
+
}
|
|
1097
|
+
async function fetchPublicFileRowsWhenOrgDefined(supabase, table_name, record_id, organisation_id, category) {
|
|
1098
|
+
if (category) {
|
|
1099
|
+
const rpcParams = {
|
|
1100
|
+
p_table_name: table_name,
|
|
1101
|
+
p_record_id: record_id,
|
|
1102
|
+
p_category: category,
|
|
1103
|
+
p_organisation_id: organisation_id ?? void 0
|
|
1104
|
+
};
|
|
1105
|
+
const { data, error: rpcError2 } = await supabase.rpc("data_file_reference_by_category_list", rpcParams);
|
|
1106
|
+
if (rpcError2) {
|
|
1107
|
+
logger.error("usePublicFileDisplay", "RPC function error", {
|
|
1108
|
+
function: "data_file_reference_by_category_list",
|
|
1109
|
+
table_name,
|
|
1110
|
+
record_id,
|
|
1111
|
+
category,
|
|
1112
|
+
organisation_id,
|
|
1113
|
+
error: rpcError2.message,
|
|
1114
|
+
errorCode: rpcError2.code,
|
|
1115
|
+
errorDetails: rpcError2.details,
|
|
1116
|
+
errorHint: rpcError2.hint
|
|
1117
|
+
});
|
|
1118
|
+
throw new Error(rpcError2.message || "Failed to fetch file reference");
|
|
1119
|
+
}
|
|
1120
|
+
if (!data || data.length === 0) return [];
|
|
1121
|
+
return data.filter((item) => item.is_public === true && item.id && item.file_path && item.file_metadata).map((item) => mapRpcItemToRow(item, table_name, record_id, organisation_id ?? null));
|
|
1122
|
+
}
|
|
1123
|
+
const { data: fileIds, error: rpcError } = await supabase.rpc("data_file_reference_list", {
|
|
1124
|
+
p_table_name: table_name,
|
|
1125
|
+
p_record_id: record_id,
|
|
1126
|
+
p_organisation_id: organisation_id ?? void 0
|
|
1127
|
+
});
|
|
1128
|
+
if (rpcError) {
|
|
1129
|
+
logger.error("usePublicFileDisplay", "RPC function error", {
|
|
1130
|
+
function: "data_file_reference_list",
|
|
1131
|
+
table_name,
|
|
1132
|
+
record_id,
|
|
1133
|
+
organisation_id,
|
|
1134
|
+
error: rpcError.message,
|
|
1135
|
+
errorCode: rpcError.code,
|
|
1136
|
+
errorDetails: rpcError.details,
|
|
1137
|
+
errorHint: rpcError.hint
|
|
1138
|
+
});
|
|
1139
|
+
throw new Error(rpcError.message || "Failed to fetch file references");
|
|
1140
|
+
}
|
|
1141
|
+
if (!fileIds || fileIds.length === 0) return [];
|
|
1142
|
+
const ids = fileIds.map((item) => item.id);
|
|
1143
|
+
const { data: fullData, error: fetchError } = await supabase.from("core_file_references").select("id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at, created_by").in("id", ids).eq("is_public", true);
|
|
1144
|
+
if (fetchError) throw new Error(fetchError.message || "Failed to fetch file references");
|
|
1145
|
+
return fullData || [];
|
|
1146
|
+
}
|
|
1147
|
+
async function fetchPublicFileRows(supabase, table_name, record_id, organisation_id, category) {
|
|
1148
|
+
if (organisation_id === void 0) {
|
|
1149
|
+
return fetchPublicFileRowsWhenOrgUndefined(supabase, table_name, record_id, category);
|
|
1150
|
+
}
|
|
1151
|
+
return fetchPublicFileRowsWhenOrgDefined(supabase, table_name, record_id, organisation_id, category);
|
|
1152
|
+
}
|
|
1153
|
+
function mapDbRowsToFileReferences(rows) {
|
|
1154
|
+
return rows.map((f) => {
|
|
1155
|
+
const fileName = f.file_path.split("/").pop() || "unknown";
|
|
1156
|
+
const fileType = fileName.split(".").pop() || "unknown";
|
|
1157
|
+
const metadata = f.file_metadata;
|
|
1158
|
+
return {
|
|
1159
|
+
id: f.id,
|
|
1160
|
+
table_name: f.table_name,
|
|
1161
|
+
record_id: f.record_id,
|
|
1162
|
+
file_path: f.file_path,
|
|
1163
|
+
file_metadata: {
|
|
1164
|
+
fileName: metadata?.fileName || fileName,
|
|
1165
|
+
fileType: metadata?.fileType || fileType,
|
|
1166
|
+
category: metadata?.category || "general_documents" /* GENERAL_DOCUMENTS */,
|
|
1167
|
+
...metadata || {}
|
|
1168
|
+
},
|
|
1169
|
+
organisation_id: f.organisation_id,
|
|
1170
|
+
app_id: f.app_id,
|
|
1171
|
+
is_public: f.is_public ?? true,
|
|
1172
|
+
created_at: f.created_at || (/* @__PURE__ */ new Date()).toISOString(),
|
|
1173
|
+
updated_at: f.updated_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
1174
|
+
};
|
|
1175
|
+
});
|
|
1176
|
+
}
|
|
1177
|
+
function buildCacheDataFromRefs(fileRefs, category, supabase) {
|
|
1178
|
+
const fileUrl = category && fileRefs.length > 0 ? getPublicUrl(supabase, fileRefs[0].file_path, true) : null;
|
|
1179
|
+
const fileReference = category && fileRefs.length > 0 ? fileRefs[0] : null;
|
|
1180
|
+
let fileUrls;
|
|
1181
|
+
if (category) {
|
|
1182
|
+
fileUrls = /* @__PURE__ */ new Map();
|
|
1183
|
+
} else {
|
|
1184
|
+
fileUrls = /* @__PURE__ */ new Map();
|
|
1185
|
+
for (const fileRef of fileRefs) {
|
|
1186
|
+
const url = getPublicUrl(supabase, fileRef.file_path, true);
|
|
1187
|
+
if (url) fileUrls.set(fileRef.id, url);
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
return {
|
|
1191
|
+
fileUrl,
|
|
1192
|
+
fileReference,
|
|
1193
|
+
fileReferences: fileRefs,
|
|
1194
|
+
fileUrls,
|
|
1195
|
+
fileCount: fileRefs.length
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
var emptyCacheData = {
|
|
1199
|
+
fileUrl: null,
|
|
1200
|
+
fileReference: null,
|
|
1201
|
+
fileReferences: [],
|
|
1202
|
+
fileUrls: /* @__PURE__ */ new Map(),
|
|
1203
|
+
fileCount: 0
|
|
1204
|
+
};
|
|
1205
|
+
function applyEmptyPublicFileState(setFileUrl, setFileReference, setFileReferences, setFileUrls, setFileCount, setIsLoading, setError) {
|
|
1206
|
+
setFileUrl(null);
|
|
1207
|
+
setFileReference(null);
|
|
1208
|
+
setFileReferences([]);
|
|
1209
|
+
setFileUrls(/* @__PURE__ */ new Map());
|
|
1210
|
+
setFileCount(0);
|
|
1211
|
+
setIsLoading(false);
|
|
1212
|
+
setError(null);
|
|
1213
|
+
}
|
|
1214
|
+
function applyCachedPublicFileState(cachedData, setFileUrl, setFileReference, setFileReferences, setFileUrls, setFileCount, setIsLoading, setError) {
|
|
1215
|
+
setFileUrl(cachedData.fileUrl || null);
|
|
1216
|
+
setFileReference(cachedData.fileReference || null);
|
|
1217
|
+
setFileReferences(cachedData.fileReferences || []);
|
|
1218
|
+
setFileUrls(cachedData.fileUrls || /* @__PURE__ */ new Map());
|
|
1219
|
+
setFileCount(cachedData.fileCount || 0);
|
|
1220
|
+
setIsLoading(false);
|
|
1221
|
+
setError(null);
|
|
1222
|
+
}
|
|
1223
|
+
function warnInvalidOrganisationIdUuid(organisation_id) {
|
|
1224
|
+
if (!organisation_id) return;
|
|
1225
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1226
|
+
if (!uuidRegex.test(organisation_id)) {
|
|
1227
|
+
logger.warn("usePublicFileDisplay", "Invalid organisationId format (not a valid UUID)", { organisation_id });
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1191
1230
|
function clearPublicFileDisplayCache() {
|
|
1192
1231
|
for (const [key] of publicFileCache) {
|
|
1193
1232
|
if (key.startsWith("public_file_")) {
|
|
@@ -1205,6 +1244,33 @@ function getPublicFileDisplayCacheStats() {
|
|
|
1205
1244
|
|
|
1206
1245
|
// src/utils/file-reference/index.ts
|
|
1207
1246
|
var log3 = createLogger("FileReferenceService");
|
|
1247
|
+
function toApiError(error) {
|
|
1248
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1249
|
+
const code = error instanceof Error && error.code ? error.code : "FILE_REFERENCE_ERROR";
|
|
1250
|
+
return { code, message };
|
|
1251
|
+
}
|
|
1252
|
+
function mapFileReferenceRowToFileReference(f) {
|
|
1253
|
+
const fileName = f.file_path.split("/").pop() || "unknown";
|
|
1254
|
+
const fileType = fileName.split(".").pop() || "unknown";
|
|
1255
|
+
const metadata = f.file_metadata;
|
|
1256
|
+
return {
|
|
1257
|
+
id: f.id,
|
|
1258
|
+
table_name: f.table_name,
|
|
1259
|
+
record_id: f.record_id,
|
|
1260
|
+
file_path: f.file_path,
|
|
1261
|
+
file_metadata: {
|
|
1262
|
+
fileName: metadata?.fileName || fileName,
|
|
1263
|
+
fileType: metadata?.fileType || fileType,
|
|
1264
|
+
category: metadata?.category || "general_documents" /* GENERAL_DOCUMENTS */,
|
|
1265
|
+
...metadata || {}
|
|
1266
|
+
},
|
|
1267
|
+
organisation_id: f.organisation_id,
|
|
1268
|
+
app_id: f.app_id,
|
|
1269
|
+
is_public: f.is_public ?? false,
|
|
1270
|
+
created_at: f.created_at || (/* @__PURE__ */ new Date()).toISOString(),
|
|
1271
|
+
updated_at: f.updated_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1208
1274
|
var FileReferenceServiceImpl = class {
|
|
1209
1275
|
constructor(supabase) {
|
|
1210
1276
|
this.supabase = supabase;
|
|
@@ -1223,6 +1289,114 @@ var FileReferenceServiceImpl = class {
|
|
|
1223
1289
|
return "unknown";
|
|
1224
1290
|
}
|
|
1225
1291
|
}
|
|
1292
|
+
validateCreateOptions(options) {
|
|
1293
|
+
const isUserScoped = !options.organisation_id && !!options.userId;
|
|
1294
|
+
if (!isUserScoped && !options.organisation_id) {
|
|
1295
|
+
return err({ code: "VALIDATION_ERROR", message: "organisation_id is required for file upload, or userId must be provided for user-scoped files" });
|
|
1296
|
+
}
|
|
1297
|
+
if (!options.table_name) {
|
|
1298
|
+
return err({ code: "VALIDATION_ERROR", message: "table_name is required for file upload" });
|
|
1299
|
+
}
|
|
1300
|
+
if (!options.record_id) {
|
|
1301
|
+
return err({ code: "VALIDATION_ERROR", message: "record_id is required for file upload" });
|
|
1302
|
+
}
|
|
1303
|
+
if (!options.folder) {
|
|
1304
|
+
return err({ code: "VALIDATION_ERROR", message: "folder is required for file upload. The folder prop determines the storage path." });
|
|
1305
|
+
}
|
|
1306
|
+
return ok({ isUserScoped });
|
|
1307
|
+
}
|
|
1308
|
+
async resolveAuthenticatedUserIdForUserScoped() {
|
|
1309
|
+
const { data: { user: authUser }, error: authError } = await this.supabase.auth.getUser();
|
|
1310
|
+
if (authError || !authUser) {
|
|
1311
|
+
return err({ code: "UNAUTHORIZED", message: "User must be authenticated to upload user-scoped files" });
|
|
1312
|
+
}
|
|
1313
|
+
log3.debug("Using authenticated user ID for user-scoped file upload", { userId: authUser.id });
|
|
1314
|
+
return ok(authUser.id);
|
|
1315
|
+
}
|
|
1316
|
+
async checkSuperAdminUser(userId) {
|
|
1317
|
+
if (!userId) return false;
|
|
1318
|
+
try {
|
|
1319
|
+
const { isSuperAdmin } = await import('./api-BZR2CYXL.js');
|
|
1320
|
+
const superResult = await isSuperAdmin(userId);
|
|
1321
|
+
if (superResult.ok && superResult.data) {
|
|
1322
|
+
log3.debug("Super admin detected - bypassing permission checks", { userId });
|
|
1323
|
+
return true;
|
|
1324
|
+
}
|
|
1325
|
+
return false;
|
|
1326
|
+
} catch (superAdminCheckError) {
|
|
1327
|
+
log3.warn("Failed to check super-admin status, proceeding with normal permission checks", superAdminCheckError);
|
|
1328
|
+
return false;
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
async uploadFileAndExtractMetadata(options, file, authenticatedUserId, isUserScoped) {
|
|
1332
|
+
const userId = authenticatedUserId || (isUserScoped ? void 0 : options.userId);
|
|
1333
|
+
const uploadResult = await uploadFile(this.supabase, file, {
|
|
1334
|
+
appName: "file-reference",
|
|
1335
|
+
orgId: options.organisation_id || void 0,
|
|
1336
|
+
userId,
|
|
1337
|
+
isPublic: options.is_public || false,
|
|
1338
|
+
customPath: options.folder
|
|
1339
|
+
});
|
|
1340
|
+
if (!uploadResult.ok) return err(uploadResult.error);
|
|
1341
|
+
const metadataResult = await extractFileMetadata(file, {
|
|
1342
|
+
appName: "file-reference",
|
|
1343
|
+
orgId: options.organisation_id || void 0,
|
|
1344
|
+
userId,
|
|
1345
|
+
isPublic: options.is_public || false
|
|
1346
|
+
}, "system");
|
|
1347
|
+
if (!metadataResult.ok) return err(metadataResult.error);
|
|
1348
|
+
return ok({ path: uploadResult.data.path, metadata: metadataResult.data });
|
|
1349
|
+
}
|
|
1350
|
+
async setOrgContextIfNeeded(isUserScoped, organisation_id) {
|
|
1351
|
+
if (isUserScoped || !organisation_id) return;
|
|
1352
|
+
const ctxResult = await setOrganisationContext(this.supabase, organisation_id);
|
|
1353
|
+
if (!ctxResult.ok) {
|
|
1354
|
+
log3.warn("setOrganisationContext failed (non-fatal):", ctxResult.error.message);
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
async resolveRpcUserId(authenticatedUserId, options) {
|
|
1358
|
+
if (authenticatedUserId) return authenticatedUserId;
|
|
1359
|
+
if (options.userId) return options.userId;
|
|
1360
|
+
const { data: { user: authUser } } = await this.supabase.auth.getUser();
|
|
1361
|
+
return authUser?.id ?? null;
|
|
1362
|
+
}
|
|
1363
|
+
async buildPermissionDeniedMessage(options, isSuperAdminUser) {
|
|
1364
|
+
const appName = await this.getAppName(options.app_id).catch(() => "unknown");
|
|
1365
|
+
const pageContextDisplay = options.pageContext || "undefined";
|
|
1366
|
+
return isSuperAdminUser ? `File upload failed for super-admin user. Page context: '${pageContextDisplay}', App: '${appName}'.` : `File upload denied: insufficient permissions. You need 'create:page.${pageContextDisplay}' or 'update:page.${pageContextDisplay}' for the '${pageContextDisplay}' page.`;
|
|
1367
|
+
}
|
|
1368
|
+
async rollbackUploadAndErr(filePath, isPublic, code, message) {
|
|
1369
|
+
await deleteFile(this.supabase, filePath, isPublic);
|
|
1370
|
+
return err({ code, message });
|
|
1371
|
+
}
|
|
1372
|
+
async fetchCreatedFileReference(createdId) {
|
|
1373
|
+
const { data: fileRef, error: fetchError } = await this.supabase.from("core_file_references").select("id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at").eq("id", createdId).single();
|
|
1374
|
+
if (fetchError || !fileRef) {
|
|
1375
|
+
return err({ code: "FETCH_FAILED", message: fetchError?.message ?? "Failed to fetch created file reference" });
|
|
1376
|
+
}
|
|
1377
|
+
return ok(fileRef);
|
|
1378
|
+
}
|
|
1379
|
+
buildCreateRpcPayload(options, filePath, file, metadata, rpcUserId) {
|
|
1380
|
+
return {
|
|
1381
|
+
p_table_name: options.table_name,
|
|
1382
|
+
p_record_id: options.record_id,
|
|
1383
|
+
p_file_path: filePath,
|
|
1384
|
+
p_organisation_id: options.organisation_id ?? null,
|
|
1385
|
+
p_app_id: options.app_id,
|
|
1386
|
+
p_page_context: options.pageContext,
|
|
1387
|
+
p_event_id: options.event_id || null,
|
|
1388
|
+
p_file_metadata: {
|
|
1389
|
+
fileName: file.name,
|
|
1390
|
+
fileType: file.type,
|
|
1391
|
+
fileSize: file.size,
|
|
1392
|
+
category: options.category,
|
|
1393
|
+
...metadata,
|
|
1394
|
+
...options.custom_metadata
|
|
1395
|
+
},
|
|
1396
|
+
p_is_public: options.is_public || false,
|
|
1397
|
+
p_user_id: rpcUserId
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1226
1400
|
/**
|
|
1227
1401
|
* Creates a file reference by uploading a file to storage and linking it in the database.
|
|
1228
1402
|
*
|
|
@@ -1239,122 +1413,38 @@ var FileReferenceServiceImpl = class {
|
|
|
1239
1413
|
*/
|
|
1240
1414
|
async createFileReference(options, file) {
|
|
1241
1415
|
try {
|
|
1242
|
-
const
|
|
1243
|
-
if (!
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
if (!options.table_name) {
|
|
1247
|
-
throw new Error("table_name is required for file upload");
|
|
1248
|
-
}
|
|
1249
|
-
if (!options.record_id) {
|
|
1250
|
-
throw new Error("record_id is required for file upload");
|
|
1251
|
-
}
|
|
1252
|
-
if (!options.folder) {
|
|
1253
|
-
throw new Error("folder is required for file upload. The folder prop determines the storage path.");
|
|
1254
|
-
}
|
|
1255
|
-
let authenticatedUserId = void 0;
|
|
1416
|
+
const validation = this.validateCreateOptions(options);
|
|
1417
|
+
if (!validation.ok) return validation;
|
|
1418
|
+
const { isUserScoped } = validation.data;
|
|
1419
|
+
let authenticatedUserId;
|
|
1256
1420
|
if (isUserScoped) {
|
|
1257
|
-
const
|
|
1258
|
-
if (
|
|
1259
|
-
|
|
1260
|
-
}
|
|
1261
|
-
authenticatedUserId = authUser.id;
|
|
1262
|
-
log3.debug("Using authenticated user ID for user-scoped file upload", { userId: authenticatedUserId });
|
|
1421
|
+
const authResult = await this.resolveAuthenticatedUserIdForUserScoped();
|
|
1422
|
+
if (!authResult.ok) return authResult;
|
|
1423
|
+
authenticatedUserId = authResult.data;
|
|
1263
1424
|
}
|
|
1264
|
-
|
|
1265
|
-
const
|
|
1266
|
-
if (
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
}
|
|
1273
|
-
} catch (superAdminCheckError) {
|
|
1274
|
-
log3.warn("Failed to check super-admin status, proceeding with normal permission checks", superAdminCheckError);
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
const uploadResult = await uploadFile(this.supabase, file, {
|
|
1278
|
-
appName: "file-reference",
|
|
1279
|
-
orgId: options.organisation_id || void 0,
|
|
1280
|
-
userId: authenticatedUserId || (isUserScoped ? void 0 : options.userId),
|
|
1281
|
-
// Use auth.uid() for user-scoped files
|
|
1282
|
-
isPublic: options.is_public || false,
|
|
1283
|
-
customPath: options.folder
|
|
1284
|
-
// Use folder prop as the custom path segment
|
|
1285
|
-
});
|
|
1286
|
-
if (!uploadResult.success) {
|
|
1287
|
-
throw new Error(`Failed to upload file: ${uploadResult.error}`);
|
|
1288
|
-
}
|
|
1289
|
-
if (!uploadResult.path) {
|
|
1290
|
-
throw new Error("File upload did not return a path");
|
|
1291
|
-
}
|
|
1292
|
-
const filePath = uploadResult.path;
|
|
1293
|
-
const metadata = await extractFileMetadata(file, {
|
|
1294
|
-
appName: "file-reference",
|
|
1295
|
-
orgId: options.organisation_id || void 0,
|
|
1296
|
-
userId: authenticatedUserId || (isUserScoped ? void 0 : options.userId),
|
|
1297
|
-
// Use auth.uid() for user-scoped files
|
|
1298
|
-
isPublic: options.is_public || false
|
|
1299
|
-
}, "system");
|
|
1300
|
-
if (!isUserScoped && options.organisation_id) {
|
|
1301
|
-
await setOrganisationContext(this.supabase, options.organisation_id);
|
|
1302
|
-
}
|
|
1303
|
-
let rpcUserId = null;
|
|
1304
|
-
if (authenticatedUserId) {
|
|
1305
|
-
rpcUserId = authenticatedUserId;
|
|
1306
|
-
} else if (options.userId) {
|
|
1307
|
-
rpcUserId = options.userId;
|
|
1308
|
-
} else {
|
|
1309
|
-
const { data: { user: authUser } } = await this.supabase.auth.getUser();
|
|
1310
|
-
if (authUser) {
|
|
1311
|
-
rpcUserId = authUser.id;
|
|
1312
|
-
}
|
|
1313
|
-
}
|
|
1314
|
-
const { data, error } = await this.supabase.rpc("data_file_reference_create", {
|
|
1315
|
-
p_table_name: options.table_name,
|
|
1316
|
-
p_record_id: options.record_id,
|
|
1317
|
-
p_file_path: filePath,
|
|
1318
|
-
// Storage path from step 1
|
|
1319
|
-
p_organisation_id: options.organisation_id ?? null,
|
|
1320
|
-
p_app_id: options.app_id,
|
|
1321
|
-
p_page_context: options.pageContext,
|
|
1322
|
-
p_event_id: options.event_id || null,
|
|
1323
|
-
// Pass event_id for event-based apps
|
|
1324
|
-
p_file_metadata: {
|
|
1325
|
-
fileName: file.name,
|
|
1326
|
-
fileType: file.type,
|
|
1327
|
-
fileSize: file.size,
|
|
1328
|
-
category: options.category,
|
|
1329
|
-
...metadata,
|
|
1330
|
-
...options.custom_metadata
|
|
1331
|
-
},
|
|
1332
|
-
p_is_public: options.is_public || false,
|
|
1333
|
-
p_user_id: rpcUserId
|
|
1334
|
-
// Always pass authenticated user ID for SECURITY DEFINER functions
|
|
1335
|
-
});
|
|
1425
|
+
const isSuperAdminUser = await this.checkSuperAdminUser(authenticatedUserId || options.userId);
|
|
1426
|
+
const uploadResult = await this.uploadFileAndExtractMetadata(options, file, authenticatedUserId, isUserScoped);
|
|
1427
|
+
if (!uploadResult.ok) return uploadResult;
|
|
1428
|
+
const { path: filePath, metadata } = uploadResult.data;
|
|
1429
|
+
await this.setOrgContextIfNeeded(isUserScoped, options.organisation_id ?? void 0);
|
|
1430
|
+
const rpcUserId = await this.resolveRpcUserId(authenticatedUserId, options);
|
|
1431
|
+
const payload = this.buildCreateRpcPayload(options, filePath, file, metadata, rpcUserId);
|
|
1432
|
+
const { data, error } = await this.supabase.rpc("data_file_reference_create", payload);
|
|
1336
1433
|
if (error) {
|
|
1337
|
-
await
|
|
1338
|
-
throw new Error(`Failed to create file reference: ${error.message}`);
|
|
1434
|
+
return await this.rollbackUploadAndErr(filePath, options.is_public ?? false, "CREATE_FAILED", error.message);
|
|
1339
1435
|
}
|
|
1340
1436
|
if (!data || data === null) {
|
|
1341
|
-
await
|
|
1342
|
-
|
|
1343
|
-
const pageContextDisplay = options.pageContext || "undefined";
|
|
1344
|
-
if (isSuperAdminUser) {
|
|
1345
|
-
throw new Error(
|
|
1346
|
-
`File upload failed for super-admin user. This may indicate a database issue. Page context: '${pageContextDisplay}', App: '${appName}'. Please check that the page '${pageContextDisplay}' exists in rbac_app_pages table for app '${appName}'.`
|
|
1347
|
-
);
|
|
1348
|
-
} else {
|
|
1349
|
-
throw new Error(
|
|
1350
|
-
`File upload denied: insufficient permissions. You need 'create:page.${pageContextDisplay}' or 'update:page.${pageContextDisplay}' permission for the '${pageContextDisplay}' page. Make sure the page exists in rbac_app_pages table for app '${appName}'.`
|
|
1351
|
-
);
|
|
1352
|
-
}
|
|
1437
|
+
const message = await this.buildPermissionDeniedMessage(options, isSuperAdminUser);
|
|
1438
|
+
return await this.rollbackUploadAndErr(filePath, options.is_public ?? false, "PERMISSION_DENIED", message);
|
|
1353
1439
|
}
|
|
1354
|
-
const
|
|
1355
|
-
if (
|
|
1356
|
-
await
|
|
1357
|
-
|
|
1440
|
+
const fetchResult = await this.fetchCreatedFileReference(data);
|
|
1441
|
+
if (!fetchResult.ok) {
|
|
1442
|
+
return await this.rollbackUploadAndErr(
|
|
1443
|
+
filePath,
|
|
1444
|
+
options.is_public ?? false,
|
|
1445
|
+
"FETCH_FAILED",
|
|
1446
|
+
fetchResult.error.message
|
|
1447
|
+
);
|
|
1358
1448
|
}
|
|
1359
1449
|
invalidateFileDisplayCache(
|
|
1360
1450
|
options.table_name,
|
|
@@ -1362,10 +1452,10 @@ var FileReferenceServiceImpl = class {
|
|
|
1362
1452
|
options.organisation_id || null,
|
|
1363
1453
|
options.category
|
|
1364
1454
|
);
|
|
1365
|
-
return
|
|
1455
|
+
return ok(fetchResult.data);
|
|
1366
1456
|
} catch (error) {
|
|
1367
1457
|
log3.error("Error creating file reference:", error);
|
|
1368
|
-
|
|
1458
|
+
return err(toApiError(error));
|
|
1369
1459
|
}
|
|
1370
1460
|
}
|
|
1371
1461
|
async getFileReference(table_name, record_id, organisation_id) {
|
|
@@ -1379,21 +1469,25 @@ var FileReferenceServiceImpl = class {
|
|
|
1379
1469
|
const { data, error } = await query.single();
|
|
1380
1470
|
if (error) {
|
|
1381
1471
|
if (error.code === "PGRST116") {
|
|
1382
|
-
return null;
|
|
1472
|
+
return ok(null);
|
|
1383
1473
|
}
|
|
1384
|
-
|
|
1474
|
+
return err({ code: "FETCH_FAILED", message: error.message });
|
|
1385
1475
|
}
|
|
1386
|
-
return data;
|
|
1476
|
+
return ok(data);
|
|
1387
1477
|
} catch (error) {
|
|
1388
1478
|
log3.error("Error getting file reference:", error);
|
|
1389
|
-
|
|
1479
|
+
return err(toApiError(error));
|
|
1390
1480
|
}
|
|
1391
1481
|
}
|
|
1392
1482
|
async getFileUrl(table_name, record_id, organisation_id) {
|
|
1393
1483
|
try {
|
|
1394
|
-
const
|
|
1484
|
+
const refResult = await this.getFileReference(table_name, record_id, organisation_id);
|
|
1485
|
+
if (!refResult.ok) {
|
|
1486
|
+
return refResult;
|
|
1487
|
+
}
|
|
1488
|
+
const fileRef = refResult.data;
|
|
1395
1489
|
if (!fileRef) {
|
|
1396
|
-
return null;
|
|
1490
|
+
return ok(null);
|
|
1397
1491
|
}
|
|
1398
1492
|
if (fileRef.is_public) {
|
|
1399
1493
|
const { data: pathData } = await this.supabase.rpc("data_file_reference_url_get", {
|
|
@@ -1402,15 +1496,14 @@ var FileReferenceServiceImpl = class {
|
|
|
1402
1496
|
p_organisation_id: organisation_id
|
|
1403
1497
|
});
|
|
1404
1498
|
if (!pathData) {
|
|
1405
|
-
return null;
|
|
1499
|
+
return ok(null);
|
|
1406
1500
|
}
|
|
1407
|
-
return getPublicUrl(this.supabase, pathData, true);
|
|
1408
|
-
} else {
|
|
1409
|
-
return await this.getSignedUrl(table_name, record_id, organisation_id);
|
|
1501
|
+
return ok(getPublicUrl(this.supabase, pathData, true));
|
|
1410
1502
|
}
|
|
1503
|
+
return this.getSignedUrl(table_name, record_id, organisation_id);
|
|
1411
1504
|
} catch (error) {
|
|
1412
1505
|
log3.error("Error getting file URL:", error);
|
|
1413
|
-
|
|
1506
|
+
return err(toApiError(error));
|
|
1414
1507
|
}
|
|
1415
1508
|
}
|
|
1416
1509
|
async getSignedUrl(table_name, record_id, organisation_id, expires_in = 3600) {
|
|
@@ -1422,10 +1515,10 @@ var FileReferenceServiceImpl = class {
|
|
|
1422
1515
|
p_expires_in: expires_in
|
|
1423
1516
|
});
|
|
1424
1517
|
if (error) {
|
|
1425
|
-
|
|
1518
|
+
return err({ code: "SIGNED_URL_FAILED", message: error.message });
|
|
1426
1519
|
}
|
|
1427
1520
|
if (!filePath) {
|
|
1428
|
-
return null;
|
|
1521
|
+
return ok(null);
|
|
1429
1522
|
}
|
|
1430
1523
|
const signedUrlResult = await getSignedUrl(this.supabase, filePath, {
|
|
1431
1524
|
appName: "file-reference",
|
|
@@ -1433,27 +1526,31 @@ var FileReferenceServiceImpl = class {
|
|
|
1433
1526
|
userId: organisation_id ? void 0 : record_id,
|
|
1434
1527
|
expiresIn: expires_in
|
|
1435
1528
|
});
|
|
1436
|
-
|
|
1529
|
+
if (!signedUrlResult.ok) {
|
|
1530
|
+
return err(signedUrlResult.error);
|
|
1531
|
+
}
|
|
1532
|
+
return ok(signedUrlResult.data.url ?? null);
|
|
1437
1533
|
} catch (error) {
|
|
1438
1534
|
log3.error("Error getting signed URL:", error);
|
|
1439
|
-
|
|
1535
|
+
return err(toApiError(error));
|
|
1440
1536
|
}
|
|
1441
1537
|
}
|
|
1442
1538
|
async updateFileReference(id, updates) {
|
|
1443
1539
|
try {
|
|
1444
1540
|
const { data, error } = await this.supabase.from("core_file_references").update(updates).eq("id", id).select().single();
|
|
1445
1541
|
if (error) {
|
|
1446
|
-
|
|
1542
|
+
return err({ code: "UPDATE_FAILED", message: error.message });
|
|
1447
1543
|
}
|
|
1448
|
-
return data;
|
|
1544
|
+
return ok(data);
|
|
1449
1545
|
} catch (error) {
|
|
1450
1546
|
log3.error("Error updating file reference:", error);
|
|
1451
|
-
|
|
1547
|
+
return err(toApiError(error));
|
|
1452
1548
|
}
|
|
1453
1549
|
}
|
|
1454
1550
|
async deleteFileReference(table_name, record_id, organisation_id, delete_file = false) {
|
|
1455
1551
|
try {
|
|
1456
|
-
const
|
|
1552
|
+
const refResult = await this.getFileReference(table_name, record_id, organisation_id);
|
|
1553
|
+
const fileRef = refResult.ok ? refResult.data : null;
|
|
1457
1554
|
const { error } = await this.supabase.rpc("data_file_reference_delete", {
|
|
1458
1555
|
p_table_name: table_name,
|
|
1459
1556
|
p_record_id: record_id,
|
|
@@ -1461,15 +1558,15 @@ var FileReferenceServiceImpl = class {
|
|
|
1461
1558
|
p_delete_file: delete_file
|
|
1462
1559
|
});
|
|
1463
1560
|
if (error) {
|
|
1464
|
-
|
|
1561
|
+
return err({ code: "DELETE_FAILED", message: error.message });
|
|
1465
1562
|
}
|
|
1466
1563
|
if (delete_file && fileRef) {
|
|
1467
1564
|
await deleteFile(this.supabase, fileRef.file_path, fileRef.is_public || false);
|
|
1468
1565
|
}
|
|
1469
|
-
return true;
|
|
1566
|
+
return ok(true);
|
|
1470
1567
|
} catch (error) {
|
|
1471
1568
|
log3.error("Error deleting file reference:", error);
|
|
1472
|
-
|
|
1569
|
+
return err(toApiError(error));
|
|
1473
1570
|
}
|
|
1474
1571
|
}
|
|
1475
1572
|
async listFileReferences(table_name, record_id, organisation_id) {
|
|
@@ -1480,38 +1577,31 @@ var FileReferenceServiceImpl = class {
|
|
|
1480
1577
|
p_organisation_id: organisation_id ?? null
|
|
1481
1578
|
});
|
|
1482
1579
|
if (error) {
|
|
1483
|
-
|
|
1580
|
+
return err({ code: "LIST_FAILED", message: error.message });
|
|
1484
1581
|
}
|
|
1485
1582
|
if (!data || data.length === 0) {
|
|
1486
|
-
return [];
|
|
1583
|
+
return ok([]);
|
|
1487
1584
|
}
|
|
1488
1585
|
const fileReferences = data.filter((item) => item.id && item.file_path && item.file_metadata).map((item) => {
|
|
1489
1586
|
const fileName = item.file_path.split("/").pop() || "unknown";
|
|
1490
1587
|
const fileType = fileName.split(".").pop() || "unknown";
|
|
1491
|
-
|
|
1588
|
+
return {
|
|
1492
1589
|
id: item.id,
|
|
1493
1590
|
table_name,
|
|
1494
1591
|
record_id,
|
|
1495
1592
|
file_path: item.file_path,
|
|
1496
|
-
file_metadata: {
|
|
1497
|
-
fileName,
|
|
1498
|
-
fileType,
|
|
1499
|
-
...item.file_metadata || {}
|
|
1500
|
-
},
|
|
1593
|
+
file_metadata: { fileName, fileType, ...item.file_metadata || {} },
|
|
1501
1594
|
organisation_id: organisation_id ?? null,
|
|
1502
1595
|
app_id: item.file_metadata?.app_id ? assertAppId(item.file_metadata.app_id) : assertAppId(""),
|
|
1503
|
-
// May not be in metadata, use empty string
|
|
1504
1596
|
is_public: item.is_public ?? false,
|
|
1505
1597
|
created_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
|
|
1506
1598
|
updated_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
1507
|
-
// RPC doesn't return updated_at, use created_at
|
|
1508
1599
|
};
|
|
1509
|
-
return fileRef;
|
|
1510
1600
|
});
|
|
1511
|
-
return fileReferences;
|
|
1601
|
+
return ok(fileReferences);
|
|
1512
1602
|
} catch (error) {
|
|
1513
1603
|
log3.error("Error listing file references:", error);
|
|
1514
|
-
|
|
1604
|
+
return err(toApiError(error));
|
|
1515
1605
|
}
|
|
1516
1606
|
}
|
|
1517
1607
|
async getFileCount(table_name, record_id, organisation_id) {
|
|
@@ -1522,12 +1612,12 @@ var FileReferenceServiceImpl = class {
|
|
|
1522
1612
|
p_organisation_id: organisation_id ?? null
|
|
1523
1613
|
});
|
|
1524
1614
|
if (error) {
|
|
1525
|
-
|
|
1615
|
+
return err({ code: "COUNT_FAILED", message: error.message });
|
|
1526
1616
|
}
|
|
1527
|
-
return data
|
|
1617
|
+
return ok(data ?? 0);
|
|
1528
1618
|
} catch (error) {
|
|
1529
1619
|
log3.error("Error getting file count:", error);
|
|
1530
|
-
|
|
1620
|
+
return err(toApiError(error));
|
|
1531
1621
|
}
|
|
1532
1622
|
}
|
|
1533
1623
|
async getFileReferenceById(id, organisation_id) {
|
|
@@ -1537,15 +1627,15 @@ var FileReferenceServiceImpl = class {
|
|
|
1537
1627
|
p_organisation_id: organisation_id ?? null
|
|
1538
1628
|
});
|
|
1539
1629
|
if (error) {
|
|
1540
|
-
|
|
1630
|
+
return err({ code: "FETCH_FAILED", message: error.message });
|
|
1541
1631
|
}
|
|
1542
1632
|
if (!data || data.length === 0) {
|
|
1543
|
-
return null;
|
|
1633
|
+
return ok(null);
|
|
1544
1634
|
}
|
|
1545
|
-
return data[0];
|
|
1635
|
+
return ok(data[0]);
|
|
1546
1636
|
} catch (error) {
|
|
1547
1637
|
log3.error("Error getting file reference by ID:", error);
|
|
1548
|
-
|
|
1638
|
+
return err(toApiError(error));
|
|
1549
1639
|
}
|
|
1550
1640
|
}
|
|
1551
1641
|
async getFilesByCategory(table_name, record_id, category, organisation_id) {
|
|
@@ -1557,61 +1647,36 @@ var FileReferenceServiceImpl = class {
|
|
|
1557
1647
|
p_organisation_id: organisation_id ?? null
|
|
1558
1648
|
});
|
|
1559
1649
|
if (error) {
|
|
1560
|
-
log3.error("RPC ERROR getting files by category:", {
|
|
1561
|
-
|
|
1562
|
-
errorCode: error.code,
|
|
1563
|
-
errorMessage: error.message,
|
|
1564
|
-
errorDetails: error.details,
|
|
1565
|
-
table_name,
|
|
1566
|
-
record_id,
|
|
1567
|
-
category,
|
|
1568
|
-
organisation_id,
|
|
1569
|
-
message: "CRITICAL: Category filtering MUST use RPC function data_file_reference_by_category_list. Direct queries with .eq('category', ...) will FAIL with HTTP 406 because category is stored in file_metadata JSONB field, not a direct column."
|
|
1570
|
-
});
|
|
1571
|
-
throw new Error(`Failed to get files by category: ${error.message}. Category filtering uses file_metadata JSONB field, not a direct column. RPC function required.`);
|
|
1650
|
+
log3.error("RPC ERROR getting files by category:", { error, table_name, record_id, category });
|
|
1651
|
+
return err({ code: "LIST_FAILED", message: error.message });
|
|
1572
1652
|
}
|
|
1573
1653
|
if (!data || data.length === 0) {
|
|
1574
|
-
return [];
|
|
1654
|
+
return ok([]);
|
|
1575
1655
|
}
|
|
1576
1656
|
const fileReferences = data.filter((item) => {
|
|
1577
1657
|
const fileCategory = item.file_metadata?.category;
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
fileType,
|
|
1598
|
-
category: item.file_metadata?.category || "general_documents" /* GENERAL_DOCUMENTS */,
|
|
1599
|
-
...item.file_metadata || {}
|
|
1600
|
-
},
|
|
1601
|
-
organisation_id: organisation_id ?? null,
|
|
1602
|
-
app_id: item.file_metadata?.app_id ? assertAppId(item.file_metadata.app_id) : assertAppId(""),
|
|
1603
|
-
// May not be in metadata, use empty string
|
|
1604
|
-
is_public: item.is_public ?? false,
|
|
1605
|
-
created_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
|
|
1606
|
-
updated_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
1607
|
-
// RPC doesn't return updated_at, use created_at
|
|
1608
|
-
};
|
|
1609
|
-
return fileRef;
|
|
1610
|
-
});
|
|
1611
|
-
return fileReferences;
|
|
1658
|
+
return fileCategory === category && item.id && item.file_path && item.file_metadata;
|
|
1659
|
+
}).map((item) => ({
|
|
1660
|
+
id: item.id,
|
|
1661
|
+
table_name,
|
|
1662
|
+
record_id,
|
|
1663
|
+
file_path: item.file_path,
|
|
1664
|
+
file_metadata: {
|
|
1665
|
+
fileName: item.file_path.split("/").pop() || "unknown",
|
|
1666
|
+
fileType: item.file_path.split(".").pop() || "unknown",
|
|
1667
|
+
category: item.file_metadata?.category || "general_documents" /* GENERAL_DOCUMENTS */,
|
|
1668
|
+
...item.file_metadata || {}
|
|
1669
|
+
},
|
|
1670
|
+
organisation_id: organisation_id ?? null,
|
|
1671
|
+
app_id: item.file_metadata?.app_id ? assertAppId(item.file_metadata.app_id) : assertAppId(""),
|
|
1672
|
+
is_public: item.is_public ?? false,
|
|
1673
|
+
created_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
|
|
1674
|
+
updated_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
1675
|
+
}));
|
|
1676
|
+
return ok(fileReferences);
|
|
1612
1677
|
} catch (error) {
|
|
1613
1678
|
log3.error("Error getting files by category:", error);
|
|
1614
|
-
|
|
1679
|
+
return err(toApiError(error));
|
|
1615
1680
|
}
|
|
1616
1681
|
}
|
|
1617
1682
|
async uploadMultipleFiles(options, files) {
|
|
@@ -1619,8 +1684,9 @@ var FileReferenceServiceImpl = class {
|
|
|
1619
1684
|
const failed = [];
|
|
1620
1685
|
const results = [];
|
|
1621
1686
|
for (const file of files) {
|
|
1622
|
-
|
|
1623
|
-
|
|
1687
|
+
const result = await this.createFileReference(options, file);
|
|
1688
|
+
if (result.ok) {
|
|
1689
|
+
const fileReference = result.data;
|
|
1624
1690
|
success.push(fileReference);
|
|
1625
1691
|
results.push({
|
|
1626
1692
|
file,
|
|
@@ -1629,19 +1695,18 @@ var FileReferenceServiceImpl = class {
|
|
|
1629
1695
|
file_url: fileReference.is_public ? getPublicUrl(this.supabase, fileReference.file_path, true) : ""
|
|
1630
1696
|
}
|
|
1631
1697
|
});
|
|
1632
|
-
}
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
results.push({ file, result: null, error: message });
|
|
1698
|
+
} else {
|
|
1699
|
+
failed.push({ file, error: result.error.message });
|
|
1700
|
+
results.push({ file, result: null, error: result.error.message });
|
|
1636
1701
|
}
|
|
1637
1702
|
}
|
|
1638
|
-
return {
|
|
1703
|
+
return ok({
|
|
1639
1704
|
success,
|
|
1640
1705
|
failed,
|
|
1641
1706
|
total: results.length,
|
|
1642
1707
|
successful: success.length,
|
|
1643
1708
|
results
|
|
1644
|
-
};
|
|
1709
|
+
});
|
|
1645
1710
|
}
|
|
1646
1711
|
};
|
|
1647
1712
|
function createFileReferenceService(supabase) {
|
|
@@ -1649,22 +1714,54 @@ function createFileReferenceService(supabase) {
|
|
|
1649
1714
|
}
|
|
1650
1715
|
async function uploadFileWithReference(supabase, options, file) {
|
|
1651
1716
|
const service = createFileReferenceService(supabase);
|
|
1652
|
-
const
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1717
|
+
const result = await service.createFileReference(options, file);
|
|
1718
|
+
if (!result.ok) {
|
|
1719
|
+
return result;
|
|
1720
|
+
}
|
|
1721
|
+
const fileReference = result.data;
|
|
1722
|
+
let urlString;
|
|
1723
|
+
if (options.is_public) {
|
|
1724
|
+
urlString = getPublicUrl(supabase, fileReference.file_path, true);
|
|
1725
|
+
} else {
|
|
1726
|
+
const signedResult = await getSignedUrl(supabase, fileReference.file_path, {
|
|
1727
|
+
appName: "file-reference",
|
|
1728
|
+
orgId: options.organisation_id || void 0,
|
|
1729
|
+
userId: options.userId || void 0,
|
|
1730
|
+
expiresIn: 3600
|
|
1731
|
+
});
|
|
1732
|
+
urlString = signedResult.ok ? signedResult.data.url ?? "" : "";
|
|
1733
|
+
}
|
|
1734
|
+
return ok({
|
|
1661
1735
|
file_reference: fileReference,
|
|
1662
1736
|
file_url: urlString,
|
|
1663
1737
|
signed_url: options.is_public ? void 0 : urlString || void 0
|
|
1664
|
-
};
|
|
1738
|
+
});
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
// src/components/FileDisplay/useFileDisplayData.ts
|
|
1742
|
+
async function fetchFileDisplayDataInternal(params) {
|
|
1743
|
+
const { table_name, record_id, organisation_id, category, supabase } = params;
|
|
1744
|
+
if (!supabase) {
|
|
1745
|
+
return [];
|
|
1746
|
+
}
|
|
1747
|
+
let query = supabase.from("core_file_references").select("id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at, created_by").eq("table_name", table_name).eq("record_id", record_id);
|
|
1748
|
+
if (organisation_id === void 0 || organisation_id === null) {
|
|
1749
|
+
query = query.is("organisation_id", null);
|
|
1750
|
+
} else {
|
|
1751
|
+
query = query.eq("organisation_id", organisation_id);
|
|
1752
|
+
}
|
|
1753
|
+
if (category !== void 0 && category !== null) {
|
|
1754
|
+
query = query.contains("file_metadata", { category });
|
|
1755
|
+
}
|
|
1756
|
+
const { data, error } = await query.order("created_at", { ascending: false });
|
|
1757
|
+
if (error) {
|
|
1758
|
+
throw new Error(error.message ?? "Failed to fetch file references");
|
|
1759
|
+
}
|
|
1760
|
+
const rows = data ?? [];
|
|
1761
|
+
return rows.map(mapFileReferenceRowToFileReference);
|
|
1665
1762
|
}
|
|
1666
1763
|
|
|
1667
|
-
// src/
|
|
1764
|
+
// src/components/FileDisplay/useFileDisplay.ts
|
|
1668
1765
|
var authenticatedFileCache = /* @__PURE__ */ new Map();
|
|
1669
1766
|
var MAX_CACHE_SIZE2 = 100;
|
|
1670
1767
|
function cleanupCache() {
|
|
@@ -1683,6 +1780,111 @@ function cleanupCache() {
|
|
|
1683
1780
|
toRemove.forEach(([key]) => authenticatedFileCache.delete(key));
|
|
1684
1781
|
}
|
|
1685
1782
|
}
|
|
1783
|
+
function getFileDisplayCacheKey(table_name, record_id, organisation_id, category) {
|
|
1784
|
+
return `file_${table_name}_${record_id}_${organisation_id === void 0 ? "undefined" : organisation_id ?? "null"}_${category || "all"}`;
|
|
1785
|
+
}
|
|
1786
|
+
async function tryApplyCacheHit(cacheKey, enableCache, cacheTtl, supabase, organisation_id, record_id, safeSetState, setters) {
|
|
1787
|
+
if (!enableCache || !supabase) return false;
|
|
1788
|
+
const cached = authenticatedFileCache.get(cacheKey);
|
|
1789
|
+
if (!cached || Date.now() - cached.timestamp >= cached.ttl) return false;
|
|
1790
|
+
const cachedData = cached.data;
|
|
1791
|
+
if (cachedData.fileReference && !cachedData.fileUrl && cachedData.fileReference.is_public === false && supabase) {
|
|
1792
|
+
try {
|
|
1793
|
+
const signedUrlResult = await getSignedUrl(supabase, cachedData.fileReference.file_path, {
|
|
1794
|
+
appName: "pace-core",
|
|
1795
|
+
orgId: organisation_id,
|
|
1796
|
+
userId: organisation_id ? void 0 : record_id,
|
|
1797
|
+
expiresIn: 3600
|
|
1798
|
+
});
|
|
1799
|
+
const regeneratedUrl = signedUrlResult.ok ? signedUrlResult.data.url : null;
|
|
1800
|
+
safeSetState(setters.setFileUrl, regeneratedUrl);
|
|
1801
|
+
safeSetState(setters.setFileReference, cachedData.fileReference);
|
|
1802
|
+
safeSetState(setters.setFileReferences, cachedData.fileReferences || []);
|
|
1803
|
+
safeSetState(setters.setFileUrls, cachedData.fileUrls || /* @__PURE__ */ new Map());
|
|
1804
|
+
safeSetState(setters.setFileCount, cachedData.fileCount || 0);
|
|
1805
|
+
safeSetState(setters.setIsLoading, false);
|
|
1806
|
+
safeSetState(setters.setError, null);
|
|
1807
|
+
return true;
|
|
1808
|
+
} catch (err2) {
|
|
1809
|
+
logger.warn("useFileDisplay", "Failed to regenerate signed URL from cache, falling back to fetch:", err2);
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
safeSetState(setters.setFileUrl, cachedData.fileUrl || null);
|
|
1813
|
+
safeSetState(setters.setFileReference, cachedData.fileReference || null);
|
|
1814
|
+
safeSetState(setters.setFileReferences, cachedData.fileReferences || []);
|
|
1815
|
+
safeSetState(setters.setFileUrls, cachedData.fileUrls || /* @__PURE__ */ new Map());
|
|
1816
|
+
safeSetState(setters.setFileCount, cachedData.fileCount || 0);
|
|
1817
|
+
safeSetState(setters.setIsLoading, false);
|
|
1818
|
+
safeSetState(setters.setError, null);
|
|
1819
|
+
return true;
|
|
1820
|
+
}
|
|
1821
|
+
async function applyFetchedFilesAndCache(files, category, table_name, record_id, organisation_id, supabase, cacheKey, enableCache, cacheTtl, safeSetState, setters) {
|
|
1822
|
+
safeSetState(setters.setFileReferences, files);
|
|
1823
|
+
safeSetState(setters.setFileCount, files.length);
|
|
1824
|
+
if (category && files.length > 0) {
|
|
1825
|
+
const firstFile = files[0];
|
|
1826
|
+
safeSetState(setters.setFileReference, firstFile);
|
|
1827
|
+
let url = null;
|
|
1828
|
+
if (firstFile.is_public) {
|
|
1829
|
+
url = getPublicUrl(supabase, firstFile.file_path, true);
|
|
1830
|
+
} else {
|
|
1831
|
+
const signedUrlResult = await getSignedUrl(supabase, firstFile.file_path, {
|
|
1832
|
+
appName: "pace-core",
|
|
1833
|
+
orgId: organisation_id,
|
|
1834
|
+
userId: organisation_id ? void 0 : record_id,
|
|
1835
|
+
expiresIn: 3600
|
|
1836
|
+
});
|
|
1837
|
+
url = signedUrlResult.ok ? signedUrlResult.data.url : null;
|
|
1838
|
+
if (!url) {
|
|
1839
|
+
logger.warn("useFileDisplay", "Failed to generate signed URL for file:", {
|
|
1840
|
+
file_path: firstFile.file_path,
|
|
1841
|
+
record_id,
|
|
1842
|
+
table_name
|
|
1843
|
+
});
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
safeSetState(setters.setFileUrl, url);
|
|
1847
|
+
} else {
|
|
1848
|
+
const urlResult = await generateFileUrlsBatch(supabase, files, {
|
|
1849
|
+
appName: "pace-core",
|
|
1850
|
+
orgId: organisation_id,
|
|
1851
|
+
userId: organisation_id ? void 0 : record_id,
|
|
1852
|
+
expiresIn: 3600
|
|
1853
|
+
});
|
|
1854
|
+
const urlMap = urlResult.ok ? urlResult.data : /* @__PURE__ */ new Map();
|
|
1855
|
+
safeSetState(setters.setFileUrls, urlMap);
|
|
1856
|
+
safeSetState(setters.setFileReference, null);
|
|
1857
|
+
safeSetState(setters.setFileUrl, null);
|
|
1858
|
+
}
|
|
1859
|
+
if (enableCache) {
|
|
1860
|
+
const cacheData = {
|
|
1861
|
+
fileUrl: null,
|
|
1862
|
+
fileReference: category && files.length > 0 ? files[0] : null,
|
|
1863
|
+
fileReferences: files,
|
|
1864
|
+
fileUrls: /* @__PURE__ */ new Map(),
|
|
1865
|
+
fileCount: files.length
|
|
1866
|
+
};
|
|
1867
|
+
if (category && files.length > 0) {
|
|
1868
|
+
const firstFile = files[0];
|
|
1869
|
+
cacheData.fileUrl = firstFile.is_public ? getPublicUrl(supabase, firstFile.file_path, true) : null;
|
|
1870
|
+
} else {
|
|
1871
|
+
const urlMap = /* @__PURE__ */ new Map();
|
|
1872
|
+
for (const fileRef of files) {
|
|
1873
|
+
if (fileRef.is_public) {
|
|
1874
|
+
const url = getPublicUrl(supabase, fileRef.file_path, true);
|
|
1875
|
+
if (url) urlMap.set(fileRef.id, url);
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
cacheData.fileUrls = urlMap;
|
|
1879
|
+
}
|
|
1880
|
+
authenticatedFileCache.set(cacheKey, {
|
|
1881
|
+
data: cacheData,
|
|
1882
|
+
timestamp: Date.now(),
|
|
1883
|
+
ttl: cacheTtl
|
|
1884
|
+
});
|
|
1885
|
+
cleanupCache();
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1686
1888
|
function useFileDisplay(table_name, record_id, organisation_id, category, options) {
|
|
1687
1889
|
const {
|
|
1688
1890
|
cacheTtl = 30 * 60 * 1e3,
|
|
@@ -1719,261 +1921,36 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
1719
1921
|
logger.warn("useFileDisplay", "Invalid organisationId format (not a valid UUID):", organisation_id);
|
|
1720
1922
|
}
|
|
1721
1923
|
}
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
safeSetState(setIsLoading, false);
|
|
1742
|
-
safeSetState(setError, null);
|
|
1743
|
-
return;
|
|
1744
|
-
} catch (err) {
|
|
1745
|
-
logger.warn("useFileDisplay", "Failed to regenerate signed URL from cache, falling back to fetch:", err);
|
|
1746
|
-
}
|
|
1747
|
-
}
|
|
1748
|
-
safeSetState(setFileUrl, cachedData.fileUrl || null);
|
|
1749
|
-
safeSetState(setFileReference, cachedData.fileReference || null);
|
|
1750
|
-
safeSetState(setFileReferences, cachedData.fileReferences || []);
|
|
1751
|
-
safeSetState(setFileUrls, cachedData.fileUrls || /* @__PURE__ */ new Map());
|
|
1752
|
-
safeSetState(setFileCount, cachedData.fileCount || 0);
|
|
1753
|
-
safeSetState(setIsLoading, false);
|
|
1754
|
-
safeSetState(setError, null);
|
|
1755
|
-
return;
|
|
1924
|
+
safeSetState(setIsLoading, true);
|
|
1925
|
+
safeSetState(setError, null);
|
|
1926
|
+
const cacheKey = getFileDisplayCacheKey(table_name, record_id, organisation_id, category);
|
|
1927
|
+
const applied = await tryApplyCacheHit(
|
|
1928
|
+
cacheKey,
|
|
1929
|
+
enableCache,
|
|
1930
|
+
cacheTtl,
|
|
1931
|
+
supabase,
|
|
1932
|
+
organisation_id,
|
|
1933
|
+
record_id,
|
|
1934
|
+
safeSetState,
|
|
1935
|
+
{
|
|
1936
|
+
setFileUrl,
|
|
1937
|
+
setFileReference,
|
|
1938
|
+
setFileReferences,
|
|
1939
|
+
setFileUrls,
|
|
1940
|
+
setFileCount,
|
|
1941
|
+
setIsLoading,
|
|
1942
|
+
setError
|
|
1756
1943
|
}
|
|
1757
|
-
|
|
1944
|
+
);
|
|
1945
|
+
if (applied) return;
|
|
1758
1946
|
try {
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
let orgScopedFiles = [];
|
|
1767
|
-
try {
|
|
1768
|
-
if (category) {
|
|
1769
|
-
userScopedFiles = await service.getFilesByCategory(
|
|
1770
|
-
table_name,
|
|
1771
|
-
record_id,
|
|
1772
|
-
category,
|
|
1773
|
-
void 0
|
|
1774
|
-
// Explicitly pass undefined for user-scoped files (service converts to null for RPC)
|
|
1775
|
-
);
|
|
1776
|
-
} else {
|
|
1777
|
-
userScopedFiles = await service.listFileReferences(
|
|
1778
|
-
table_name,
|
|
1779
|
-
record_id,
|
|
1780
|
-
void 0
|
|
1781
|
-
// Explicitly pass undefined for user-scoped files (service converts to null for RPC)
|
|
1782
|
-
);
|
|
1783
|
-
}
|
|
1784
|
-
} catch (err) {
|
|
1785
|
-
logger.warn("useFileDisplay", "Error querying user-scoped files:", err);
|
|
1786
|
-
userScopedFiles = [];
|
|
1787
|
-
}
|
|
1788
|
-
try {
|
|
1789
|
-
const { data: { user }, error: userError } = await supabase.auth.getUser();
|
|
1790
|
-
if (userError) {
|
|
1791
|
-
logger.warn("useFileDisplay", "Error getting user:", userError);
|
|
1792
|
-
}
|
|
1793
|
-
if (user) {
|
|
1794
|
-
const { data: memberships, error: membershipError } = await supabase.from("core_organisation_memberships").select("organisation_id").eq("user_id", user.id);
|
|
1795
|
-
if (membershipError) {
|
|
1796
|
-
logger.warn("useFileDisplay", "Error querying organisation memberships:", membershipError);
|
|
1797
|
-
}
|
|
1798
|
-
if (memberships && memberships.length > 0) {
|
|
1799
|
-
const orgIds = memberships.map((m) => m.organisation_id).filter(Boolean);
|
|
1800
|
-
const orgQueries = orgIds.map(async (orgId) => {
|
|
1801
|
-
try {
|
|
1802
|
-
if (category) {
|
|
1803
|
-
return await service.getFilesByCategory(
|
|
1804
|
-
table_name,
|
|
1805
|
-
record_id,
|
|
1806
|
-
category,
|
|
1807
|
-
orgId
|
|
1808
|
-
);
|
|
1809
|
-
} else {
|
|
1810
|
-
return await service.listFileReferences(
|
|
1811
|
-
table_name,
|
|
1812
|
-
record_id,
|
|
1813
|
-
orgId
|
|
1814
|
-
);
|
|
1815
|
-
}
|
|
1816
|
-
} catch (_err) {
|
|
1817
|
-
return [];
|
|
1818
|
-
}
|
|
1819
|
-
});
|
|
1820
|
-
const orgResults = await Promise.all(orgQueries);
|
|
1821
|
-
orgScopedFiles = orgResults.flat();
|
|
1822
|
-
} else {
|
|
1823
|
-
try {
|
|
1824
|
-
let fallbackQuery = supabase.from("core_file_references").select("id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at").eq("table_name", table_name).eq("record_id", record_id).order("created_at", { ascending: false });
|
|
1825
|
-
if (category) {
|
|
1826
|
-
fallbackQuery = fallbackQuery.eq("file_metadata->>category", category);
|
|
1827
|
-
}
|
|
1828
|
-
const { data: fallbackFiles } = await fallbackQuery;
|
|
1829
|
-
if (fallbackFiles && fallbackFiles.length > 0) {
|
|
1830
|
-
orgScopedFiles = fallbackFiles.map((f) => {
|
|
1831
|
-
const fileName = f.file_path.split("/").pop() || "unknown";
|
|
1832
|
-
const fileType = fileName.split(".").pop() || "unknown";
|
|
1833
|
-
const metadata = f.file_metadata;
|
|
1834
|
-
return {
|
|
1835
|
-
id: f.id,
|
|
1836
|
-
table_name: f.table_name,
|
|
1837
|
-
record_id: f.record_id,
|
|
1838
|
-
file_path: f.file_path,
|
|
1839
|
-
file_metadata: {
|
|
1840
|
-
fileName: metadata?.fileName || fileName,
|
|
1841
|
-
fileType: metadata?.fileType || fileType,
|
|
1842
|
-
category: metadata?.category || "general_documents" /* GENERAL_DOCUMENTS */,
|
|
1843
|
-
...metadata || {}
|
|
1844
|
-
},
|
|
1845
|
-
organisation_id: f.organisation_id,
|
|
1846
|
-
app_id: f.app_id,
|
|
1847
|
-
is_public: f.is_public ?? false,
|
|
1848
|
-
created_at: f.created_at || (/* @__PURE__ */ new Date()).toISOString(),
|
|
1849
|
-
updated_at: f.updated_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
1850
|
-
};
|
|
1851
|
-
});
|
|
1852
|
-
}
|
|
1853
|
-
} catch (_err) {
|
|
1854
|
-
}
|
|
1855
|
-
}
|
|
1856
|
-
}
|
|
1857
|
-
} catch (err) {
|
|
1858
|
-
logger.warn("useFileDisplay", "Error querying organisation-scoped files:", err);
|
|
1859
|
-
orgScopedFiles = [];
|
|
1860
|
-
}
|
|
1861
|
-
const allFiles = [...userScopedFiles, ...orgScopedFiles];
|
|
1862
|
-
allFiles.sort((a, b) => {
|
|
1863
|
-
const aTime = new Date(a.created_at).getTime();
|
|
1864
|
-
const bTime = new Date(b.created_at).getTime();
|
|
1865
|
-
return bTime - aTime;
|
|
1866
|
-
});
|
|
1867
|
-
if (orgScopedFiles.length > 0 && userScopedFiles.length > 0) {
|
|
1868
|
-
files = allFiles.filter((f) => f.organisation_id !== null);
|
|
1869
|
-
} else {
|
|
1870
|
-
files = allFiles;
|
|
1871
|
-
}
|
|
1872
|
-
if (files.length === 0) {
|
|
1873
|
-
try {
|
|
1874
|
-
let directQuery = supabase.from("core_file_references").select("id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at").eq("table_name", table_name).eq("record_id", record_id).order("created_at", { ascending: false });
|
|
1875
|
-
if (category) {
|
|
1876
|
-
directQuery = directQuery.eq("file_metadata->>category", category);
|
|
1877
|
-
}
|
|
1878
|
-
const { data: directFiles } = await directQuery;
|
|
1879
|
-
if (directFiles && directFiles.length > 0) {
|
|
1880
|
-
files = directFiles.map((f) => {
|
|
1881
|
-
const fileName = f.file_path.split("/").pop() || "unknown";
|
|
1882
|
-
const fileType = fileName.split(".").pop() || "unknown";
|
|
1883
|
-
const metadata = f.file_metadata;
|
|
1884
|
-
return {
|
|
1885
|
-
id: f.id,
|
|
1886
|
-
table_name: f.table_name,
|
|
1887
|
-
record_id: f.record_id,
|
|
1888
|
-
file_path: f.file_path,
|
|
1889
|
-
file_metadata: {
|
|
1890
|
-
fileName: metadata?.fileName || fileName,
|
|
1891
|
-
fileType: metadata?.fileType || fileType,
|
|
1892
|
-
category: metadata?.category || "general_documents" /* GENERAL_DOCUMENTS */,
|
|
1893
|
-
...metadata || {}
|
|
1894
|
-
},
|
|
1895
|
-
organisation_id: f.organisation_id,
|
|
1896
|
-
app_id: f.app_id,
|
|
1897
|
-
is_public: f.is_public ?? false,
|
|
1898
|
-
created_at: f.created_at || (/* @__PURE__ */ new Date()).toISOString(),
|
|
1899
|
-
updated_at: f.updated_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
1900
|
-
};
|
|
1901
|
-
});
|
|
1902
|
-
}
|
|
1903
|
-
} catch (_err) {
|
|
1904
|
-
}
|
|
1905
|
-
}
|
|
1906
|
-
} else {
|
|
1907
|
-
if (category) {
|
|
1908
|
-
files = await service.getFilesByCategory(
|
|
1909
|
-
table_name,
|
|
1910
|
-
record_id,
|
|
1911
|
-
category,
|
|
1912
|
-
organisation_id
|
|
1913
|
-
);
|
|
1914
|
-
} else {
|
|
1915
|
-
files = await service.listFileReferences(
|
|
1916
|
-
table_name,
|
|
1917
|
-
record_id,
|
|
1918
|
-
organisation_id
|
|
1919
|
-
);
|
|
1920
|
-
}
|
|
1921
|
-
if (files.length === 0 && (!organisation_id || organisation_id === "")) {
|
|
1922
|
-
let userScopedFiles = [];
|
|
1923
|
-
let orgScopedFiles = [];
|
|
1924
|
-
try {
|
|
1925
|
-
if (category) {
|
|
1926
|
-
userScopedFiles = await service.getFilesByCategory(
|
|
1927
|
-
table_name,
|
|
1928
|
-
record_id,
|
|
1929
|
-
category,
|
|
1930
|
-
void 0
|
|
1931
|
-
);
|
|
1932
|
-
} else {
|
|
1933
|
-
userScopedFiles = await service.listFileReferences(
|
|
1934
|
-
table_name,
|
|
1935
|
-
record_id,
|
|
1936
|
-
void 0
|
|
1937
|
-
);
|
|
1938
|
-
}
|
|
1939
|
-
} catch (_err) {
|
|
1940
|
-
}
|
|
1941
|
-
try {
|
|
1942
|
-
const { data: { user } } = await supabase.auth.getUser();
|
|
1943
|
-
if (user) {
|
|
1944
|
-
const { data: memberships } = await supabase.from("core_organisation_memberships").select("organisation_id").eq("user_id", user.id).or("status.is.null,status.eq.active");
|
|
1945
|
-
if (memberships && memberships.length > 0) {
|
|
1946
|
-
const orgIds = memberships.map((m) => m.organisation_id).filter(Boolean);
|
|
1947
|
-
const orgQueries = orgIds.map(async (orgId) => {
|
|
1948
|
-
try {
|
|
1949
|
-
if (category) {
|
|
1950
|
-
return await service.getFilesByCategory(table_name, record_id, category, orgId);
|
|
1951
|
-
} else {
|
|
1952
|
-
return await service.listFileReferences(table_name, record_id, orgId);
|
|
1953
|
-
}
|
|
1954
|
-
} catch (_err) {
|
|
1955
|
-
return [];
|
|
1956
|
-
}
|
|
1957
|
-
});
|
|
1958
|
-
const orgResults = await Promise.all(orgQueries);
|
|
1959
|
-
orgScopedFiles = orgResults.flat();
|
|
1960
|
-
}
|
|
1961
|
-
}
|
|
1962
|
-
} catch (_err) {
|
|
1963
|
-
}
|
|
1964
|
-
const allFiles = [...userScopedFiles, ...orgScopedFiles];
|
|
1965
|
-
allFiles.sort((a, b) => {
|
|
1966
|
-
const aTime = new Date(a.created_at).getTime();
|
|
1967
|
-
const bTime = new Date(b.created_at).getTime();
|
|
1968
|
-
return bTime - aTime;
|
|
1969
|
-
});
|
|
1970
|
-
if (orgScopedFiles.length > 0 && userScopedFiles.length > 0) {
|
|
1971
|
-
files = allFiles.filter((f) => f.organisation_id !== null);
|
|
1972
|
-
} else {
|
|
1973
|
-
files = allFiles;
|
|
1974
|
-
}
|
|
1975
|
-
}
|
|
1976
|
-
}
|
|
1947
|
+
const files = await fetchFileDisplayDataInternal({
|
|
1948
|
+
table_name,
|
|
1949
|
+
record_id,
|
|
1950
|
+
organisation_id,
|
|
1951
|
+
category,
|
|
1952
|
+
supabase
|
|
1953
|
+
});
|
|
1977
1954
|
if (files.length === 0) {
|
|
1978
1955
|
safeSetState(setFileUrl, null);
|
|
1979
1956
|
safeSetState(setFileReference, null);
|
|
@@ -1990,79 +1967,22 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
1990
1967
|
}
|
|
1991
1968
|
return;
|
|
1992
1969
|
}
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
if (!url) {
|
|
2010
|
-
logger.warn("useFileDisplay", "Failed to generate signed URL for file:", {
|
|
2011
|
-
file_path: firstFile.file_path,
|
|
2012
|
-
record_id,
|
|
2013
|
-
table_name
|
|
2014
|
-
});
|
|
2015
|
-
}
|
|
2016
|
-
}
|
|
2017
|
-
safeSetState(setFileUrl, url);
|
|
2018
|
-
} else {
|
|
2019
|
-
const urlMap = await generateFileUrlsBatch(supabase, files, {
|
|
2020
|
-
appName: "pace-core",
|
|
2021
|
-
orgId: organisation_id,
|
|
2022
|
-
userId: organisation_id ? void 0 : record_id,
|
|
2023
|
-
expiresIn: 3600
|
|
2024
|
-
});
|
|
2025
|
-
safeSetState(setFileUrls, urlMap);
|
|
2026
|
-
safeSetState(setFileReference, null);
|
|
2027
|
-
safeSetState(setFileUrl, null);
|
|
2028
|
-
}
|
|
2029
|
-
if (enableCache) {
|
|
2030
|
-
const cacheData = {
|
|
2031
|
-
fileUrl: null,
|
|
2032
|
-
fileReference: category && files.length > 0 ? files[0] : null,
|
|
2033
|
-
fileReferences: files,
|
|
2034
|
-
fileUrls: /* @__PURE__ */ new Map(),
|
|
2035
|
-
fileCount: files.length
|
|
2036
|
-
};
|
|
2037
|
-
if (category && files.length > 0) {
|
|
2038
|
-
const firstFile = files[0];
|
|
2039
|
-
let url = null;
|
|
2040
|
-
if (firstFile.is_public) {
|
|
2041
|
-
url = getPublicUrl(supabase, firstFile.file_path, true);
|
|
2042
|
-
}
|
|
2043
|
-
cacheData.fileUrl = url;
|
|
2044
|
-
} else {
|
|
2045
|
-
const urlMap = /* @__PURE__ */ new Map();
|
|
2046
|
-
for (const fileRef of files) {
|
|
2047
|
-
if (fileRef.is_public) {
|
|
2048
|
-
const url = getPublicUrl(supabase, fileRef.file_path, true);
|
|
2049
|
-
if (url) {
|
|
2050
|
-
urlMap.set(fileRef.id, url);
|
|
2051
|
-
}
|
|
2052
|
-
}
|
|
2053
|
-
}
|
|
2054
|
-
cacheData.fileUrls = urlMap;
|
|
2055
|
-
}
|
|
2056
|
-
authenticatedFileCache.set(cacheKey, {
|
|
2057
|
-
data: cacheData,
|
|
2058
|
-
timestamp: Date.now(),
|
|
2059
|
-
ttl: cacheTtl
|
|
2060
|
-
});
|
|
2061
|
-
cleanupCache();
|
|
2062
|
-
}
|
|
2063
|
-
} catch (err) {
|
|
2064
|
-
logger.error("useFileDisplay", "Error fetching files:", err);
|
|
2065
|
-
const error2 = err instanceof Error ? err : new Error("Unknown error occurred");
|
|
1970
|
+
await applyFetchedFilesAndCache(
|
|
1971
|
+
files,
|
|
1972
|
+
category,
|
|
1973
|
+
table_name,
|
|
1974
|
+
record_id,
|
|
1975
|
+
organisation_id,
|
|
1976
|
+
supabase,
|
|
1977
|
+
cacheKey,
|
|
1978
|
+
enableCache,
|
|
1979
|
+
cacheTtl,
|
|
1980
|
+
safeSetState,
|
|
1981
|
+
{ setFileUrl, setFileReference, setFileReferences, setFileUrls, setFileCount }
|
|
1982
|
+
);
|
|
1983
|
+
} catch (err2) {
|
|
1984
|
+
logger.error("useFileDisplay", "Error fetching files:", err2);
|
|
1985
|
+
const error2 = err2 instanceof Error ? err2 : new Error("Unknown error occurred");
|
|
2066
1986
|
safeSetState(setError, error2);
|
|
2067
1987
|
safeSetState(setFileUrl, null);
|
|
2068
1988
|
safeSetState(setFileReference, null);
|
|
@@ -2076,9 +1996,9 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
2076
1996
|
useEffect(() => {
|
|
2077
1997
|
isMountedRef.current = true;
|
|
2078
1998
|
if (table_name && record_id && supabase) {
|
|
2079
|
-
fetchFiles().catch((
|
|
1999
|
+
fetchFiles().catch((err2) => {
|
|
2080
2000
|
if (isMountedRef.current) {
|
|
2081
|
-
safeSetState(setError,
|
|
2001
|
+
safeSetState(setError, err2 instanceof Error ? err2 : new Error("Unknown error"));
|
|
2082
2002
|
}
|
|
2083
2003
|
});
|
|
2084
2004
|
} else {
|
|
@@ -2097,8 +2017,7 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
2097
2017
|
const refetch = useCallback(async () => {
|
|
2098
2018
|
if (!table_name || !record_id || !supabase) return;
|
|
2099
2019
|
if (enableCache) {
|
|
2100
|
-
|
|
2101
|
-
authenticatedFileCache.delete(cacheKey);
|
|
2020
|
+
authenticatedFileCache.delete(getFileDisplayCacheKey(table_name, record_id, organisation_id, category));
|
|
2102
2021
|
}
|
|
2103
2022
|
await fetchFiles();
|
|
2104
2023
|
}, [fetchFiles, table_name, record_id, organisation_id, category, supabase, enableCache]);
|
|
@@ -2128,18 +2047,48 @@ function getFileDisplayCacheStats() {
|
|
|
2128
2047
|
};
|
|
2129
2048
|
}
|
|
2130
2049
|
function invalidateFileDisplayCache(table_name, record_id, organisation_id, category) {
|
|
2131
|
-
const
|
|
2132
|
-
authenticatedFileCache.delete(
|
|
2050
|
+
const orgId = organisation_id ?? void 0;
|
|
2051
|
+
authenticatedFileCache.delete(getFileDisplayCacheKey(table_name, record_id, orgId, category));
|
|
2133
2052
|
if (category) {
|
|
2134
|
-
|
|
2135
|
-
authenticatedFileCache.delete(allCategoryKey);
|
|
2053
|
+
authenticatedFileCache.delete(getFileDisplayCacheKey(table_name, record_id, orgId, void 0));
|
|
2136
2054
|
}
|
|
2137
2055
|
}
|
|
2138
2056
|
var log4 = createLogger("useEventTheme");
|
|
2139
2057
|
function useEventTheme(event) {
|
|
2140
2058
|
const location = useLocation();
|
|
2141
2059
|
const eventServiceContext = useContext(EventServiceContext);
|
|
2142
|
-
const
|
|
2060
|
+
const eventService = eventServiceContext?.eventService ?? null;
|
|
2061
|
+
useEffect(() => {
|
|
2062
|
+
if (event !== void 0 || !eventService) return;
|
|
2063
|
+
const applyThemeFromService = () => {
|
|
2064
|
+
const isOnLoginRoute = location.pathname === "/login" || location.pathname.startsWith("/login/");
|
|
2065
|
+
if (isOnLoginRoute) {
|
|
2066
|
+
clearPalette();
|
|
2067
|
+
return;
|
|
2068
|
+
}
|
|
2069
|
+
const current = eventService.getSelectedEvent();
|
|
2070
|
+
if (!current?.event_colours) {
|
|
2071
|
+
clearPalette();
|
|
2072
|
+
return;
|
|
2073
|
+
}
|
|
2074
|
+
const normalized = parseAndNormalizeEventColours(current.event_colours);
|
|
2075
|
+
if (normalized) {
|
|
2076
|
+
try {
|
|
2077
|
+
applyPalette(normalized, current.event_name ?? "");
|
|
2078
|
+
} catch (err2) {
|
|
2079
|
+
log4.error("Failed to apply event palette (subscription):", err2);
|
|
2080
|
+
}
|
|
2081
|
+
} else {
|
|
2082
|
+
clearPalette();
|
|
2083
|
+
}
|
|
2084
|
+
};
|
|
2085
|
+
applyThemeFromService();
|
|
2086
|
+
const unsubscribe = eventService.subscribe(applyThemeFromService);
|
|
2087
|
+
return () => {
|
|
2088
|
+
unsubscribe();
|
|
2089
|
+
};
|
|
2090
|
+
}, [event, eventService, location.pathname]);
|
|
2091
|
+
const eventsContextSelectedEvent = eventService?.getSelectedEvent() ?? null;
|
|
2143
2092
|
const selectedEvent = event !== void 0 ? event : eventsContextSelectedEvent;
|
|
2144
2093
|
useEffect(() => {
|
|
2145
2094
|
const isOnLoginRoute = location.pathname === "/login" || location.pathname.startsWith("/login");
|
|
@@ -2147,6 +2096,9 @@ function useEventTheme(event) {
|
|
|
2147
2096
|
clearPalette();
|
|
2148
2097
|
return;
|
|
2149
2098
|
}
|
|
2099
|
+
if (event === void 0) {
|
|
2100
|
+
return;
|
|
2101
|
+
}
|
|
2150
2102
|
if (!selectedEvent) {
|
|
2151
2103
|
clearPalette();
|
|
2152
2104
|
return;
|
|
@@ -2158,13 +2110,11 @@ function useEventTheme(event) {
|
|
|
2158
2110
|
return;
|
|
2159
2111
|
}
|
|
2160
2112
|
try {
|
|
2161
|
-
applyPalette(normalized);
|
|
2113
|
+
applyPalette(normalized, selectedEvent.event_name ?? "");
|
|
2162
2114
|
} catch (error) {
|
|
2163
2115
|
log4.error("Failed to apply event palette:", error);
|
|
2164
2116
|
}
|
|
2165
|
-
|
|
2166
|
-
};
|
|
2167
|
-
}, [selectedEvent, location.pathname]);
|
|
2117
|
+
}, [selectedEvent, location.pathname, event]);
|
|
2168
2118
|
}
|
|
2169
2119
|
function usePreventTabReload(options = {}) {
|
|
2170
2120
|
const { enabled = true, gracePeriodMs = 2e3 } = options;
|