@freighttech/ui 0.13.18
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/LICENSE +21 -0
- package/README.md +31 -0
- package/dist/backend/AppShell.js +1289 -0
- package/dist/backend/AppShell.js.map +7 -0
- package/dist/backend/BackendChromeProvider.js +83 -0
- package/dist/backend/BackendChromeProvider.js.map +7 -0
- package/dist/backend/BackendPageSurface.js +19 -0
- package/dist/backend/BackendPageSurface.js.map +7 -0
- package/dist/backend/CollapsibleNavSection.js +149 -0
- package/dist/backend/CollapsibleNavSection.js.map +7 -0
- package/dist/backend/ContextHelp.js +33 -0
- package/dist/backend/ContextHelp.js.map +7 -0
- package/dist/backend/CrudForm.js +3163 -0
- package/dist/backend/CrudForm.js.map +7 -0
- package/dist/backend/DataTable.js +2091 -0
- package/dist/backend/DataTable.js.map +7 -0
- package/dist/backend/EmptyState.js +52 -0
- package/dist/backend/EmptyState.js.map +7 -0
- package/dist/backend/FilterBar.js +147 -0
- package/dist/backend/FilterBar.js.map +7 -0
- package/dist/backend/FilterOverlay.js +337 -0
- package/dist/backend/FilterOverlay.js.map +7 -0
- package/dist/backend/FlashMessages.js +158 -0
- package/dist/backend/FlashMessages.js.map +7 -0
- package/dist/backend/HtmlRichTextEditor.js +129 -0
- package/dist/backend/HtmlRichTextEditor.js.map +7 -0
- package/dist/backend/IntegrationsButton.js +25 -0
- package/dist/backend/IntegrationsButton.js.map +7 -0
- package/dist/backend/JsonBuilder.js +323 -0
- package/dist/backend/JsonBuilder.js.map +7 -0
- package/dist/backend/JsonDisplay.js +207 -0
- package/dist/backend/JsonDisplay.js.map +7 -0
- package/dist/backend/NextStepCallout.js +79 -0
- package/dist/backend/NextStepCallout.js.map +7 -0
- package/dist/backend/Page.js +27 -0
- package/dist/backend/Page.js.map +7 -0
- package/dist/backend/PerspectiveSidebar.js +456 -0
- package/dist/backend/PerspectiveSidebar.js.map +7 -0
- package/dist/backend/ProfileDropdown.js +341 -0
- package/dist/backend/ProfileDropdown.js.map +7 -0
- package/dist/backend/RowActions.js +155 -0
- package/dist/backend/RowActions.js.map +7 -0
- package/dist/backend/SectionHeader.js +74 -0
- package/dist/backend/SectionHeader.js.map +7 -0
- package/dist/backend/SettingsButton.js +25 -0
- package/dist/backend/SettingsButton.js.map +7 -0
- package/dist/backend/Sidebar.js +510 -0
- package/dist/backend/Sidebar.js.map +7 -0
- package/dist/backend/Sidebar.stories.js +166 -0
- package/dist/backend/Sidebar.stories.js.map +7 -0
- package/dist/backend/TruncatedCell.js +92 -0
- package/dist/backend/TruncatedCell.js.map +7 -0
- package/dist/backend/UserMenu.js +146 -0
- package/dist/backend/UserMenu.js.map +7 -0
- package/dist/backend/ValueIcons.js +34 -0
- package/dist/backend/ValueIcons.js.map +7 -0
- package/dist/backend/WebhookSetupGuide.js +79 -0
- package/dist/backend/WebhookSetupGuide.js.map +7 -0
- package/dist/backend/charts/BarChart.js +154 -0
- package/dist/backend/charts/BarChart.js.map +7 -0
- package/dist/backend/charts/ChartUtils.js +141 -0
- package/dist/backend/charts/ChartUtils.js.map +7 -0
- package/dist/backend/charts/ComboChart.js +261 -0
- package/dist/backend/charts/ComboChart.js.map +7 -0
- package/dist/backend/charts/ComboChart.stories.js +88 -0
- package/dist/backend/charts/ComboChart.stories.js.map +7 -0
- package/dist/backend/charts/KpiCard.js +96 -0
- package/dist/backend/charts/KpiCard.js.map +7 -0
- package/dist/backend/charts/LineChart.js +165 -0
- package/dist/backend/charts/LineChart.js.map +7 -0
- package/dist/backend/charts/PieChart.js +121 -0
- package/dist/backend/charts/PieChart.js.map +7 -0
- package/dist/backend/charts/ScatterChart.js +320 -0
- package/dist/backend/charts/ScatterChart.js.map +7 -0
- package/dist/backend/charts/TopNTable.js +91 -0
- package/dist/backend/charts/TopNTable.js.map +7 -0
- package/dist/backend/charts/WaterfallChart.js +136 -0
- package/dist/backend/charts/WaterfallChart.js.map +7 -0
- package/dist/backend/charts/index.js +30 -0
- package/dist/backend/charts/index.js.map +7 -0
- package/dist/backend/columns/ColumnChooserPanel.js +244 -0
- package/dist/backend/columns/ColumnChooserPanel.js.map +7 -0
- package/dist/backend/confirm-dialog/ConfirmDialog.js +214 -0
- package/dist/backend/confirm-dialog/ConfirmDialog.js.map +7 -0
- package/dist/backend/confirm-dialog/index.js +7 -0
- package/dist/backend/confirm-dialog/index.js.map +7 -0
- package/dist/backend/confirm-dialog/useConfirmDialog.js +113 -0
- package/dist/backend/confirm-dialog/useConfirmDialog.js.map +7 -0
- package/dist/backend/custom-fields/FieldDefinitionsEditor.js +1352 -0
- package/dist/backend/custom-fields/FieldDefinitionsEditor.js.map +7 -0
- package/dist/backend/custom-fields/FieldDefinitionsManager.js +332 -0
- package/dist/backend/custom-fields/FieldDefinitionsManager.js.map +7 -0
- package/dist/backend/dashboard/DashboardScreen.js +598 -0
- package/dist/backend/dashboard/DashboardScreen.js.map +7 -0
- package/dist/backend/dashboard/index.js +5 -0
- package/dist/backend/dashboard/index.js.map +7 -0
- package/dist/backend/dashboard/widgetRegistry.js +55 -0
- package/dist/backend/dashboard/widgetRegistry.js.map +7 -0
- package/dist/backend/date-range/DateRangeSelect.js +39 -0
- package/dist/backend/date-range/DateRangeSelect.js.map +7 -0
- package/dist/backend/date-range/InlineDateRangeSelect.js +42 -0
- package/dist/backend/date-range/InlineDateRangeSelect.js.map +7 -0
- package/dist/backend/date-range/InlineGranularitySelect.js +40 -0
- package/dist/backend/date-range/InlineGranularitySelect.js.map +7 -0
- package/dist/backend/date-range/dateRanges.js +176 -0
- package/dist/backend/date-range/dateRanges.js.map +7 -0
- package/dist/backend/date-range/index.js +25 -0
- package/dist/backend/date-range/index.js.map +7 -0
- package/dist/backend/detail/AccessDeniedMessage.js +28 -0
- package/dist/backend/detail/AccessDeniedMessage.js.map +7 -0
- package/dist/backend/detail/ActivitiesSection.js +947 -0
- package/dist/backend/detail/ActivitiesSection.js.map +7 -0
- package/dist/backend/detail/AddressEditor.js +417 -0
- package/dist/backend/detail/AddressEditor.js.map +7 -0
- package/dist/backend/detail/AddressTiles.js +441 -0
- package/dist/backend/detail/AddressTiles.js.map +7 -0
- package/dist/backend/detail/AddressesSection.js +274 -0
- package/dist/backend/detail/AddressesSection.js.map +7 -0
- package/dist/backend/detail/AttachmentDeleteDialog.js +41 -0
- package/dist/backend/detail/AttachmentDeleteDialog.js.map +7 -0
- package/dist/backend/detail/AttachmentMetadataDialog.js +519 -0
- package/dist/backend/detail/AttachmentMetadataDialog.js.map +7 -0
- package/dist/backend/detail/AttachmentVisualPreview.js +132 -0
- package/dist/backend/detail/AttachmentVisualPreview.js.map +7 -0
- package/dist/backend/detail/AttachmentsSection.js +281 -0
- package/dist/backend/detail/AttachmentsSection.js.map +7 -0
- package/dist/backend/detail/CustomDataSection.js +575 -0
- package/dist/backend/detail/CustomDataSection.js.map +7 -0
- package/dist/backend/detail/DetailFieldsSection.js +85 -0
- package/dist/backend/detail/DetailFieldsSection.js.map +7 -0
- package/dist/backend/detail/ErrorMessage.js +28 -0
- package/dist/backend/detail/ErrorMessage.js.map +7 -0
- package/dist/backend/detail/InlineEditors.js +697 -0
- package/dist/backend/detail/InlineEditors.js.map +7 -0
- package/dist/backend/detail/LoadingMessage.js +14 -0
- package/dist/backend/detail/LoadingMessage.js.map +7 -0
- package/dist/backend/detail/NotesSection.js +1022 -0
- package/dist/backend/detail/NotesSection.js.map +7 -0
- package/dist/backend/detail/TabEmptyState.js +25 -0
- package/dist/backend/detail/TabEmptyState.js.map +7 -0
- package/dist/backend/detail/TagsSection.js +375 -0
- package/dist/backend/detail/TagsSection.js.map +7 -0
- package/dist/backend/detail/addressFormat.js +77 -0
- package/dist/backend/detail/addressFormat.js.map +7 -0
- package/dist/backend/detail/index.js +36 -0
- package/dist/backend/detail/index.js.map +7 -0
- package/dist/backend/devtools/UmesDevToolsPanel.js +109 -0
- package/dist/backend/devtools/UmesDevToolsPanel.js.map +7 -0
- package/dist/backend/devtools/components/ConflictWarnings.js +32 -0
- package/dist/backend/devtools/components/ConflictWarnings.js.map +7 -0
- package/dist/backend/devtools/components/EnricherTiming.js +46 -0
- package/dist/backend/devtools/components/EnricherTiming.js.map +7 -0
- package/dist/backend/devtools/components/EventFlow.js +34 -0
- package/dist/backend/devtools/components/EventFlow.js.map +7 -0
- package/dist/backend/devtools/components/ExtensionPointList.js +58 -0
- package/dist/backend/devtools/components/ExtensionPointList.js.map +7 -0
- package/dist/backend/devtools/components/InterceptorActivity.js +36 -0
- package/dist/backend/devtools/components/InterceptorActivity.js.map +7 -0
- package/dist/backend/devtools/index.js +7 -0
- package/dist/backend/devtools/index.js.map +7 -0
- package/dist/backend/devtools/useUmesDevTools.js +125 -0
- package/dist/backend/devtools/useUmesDevTools.js.map +7 -0
- package/dist/backend/document-viewer/RegionOverlay.js +92 -0
- package/dist/backend/document-viewer/RegionOverlay.js.map +7 -0
- package/dist/backend/document-viewer/index.js +6 -0
- package/dist/backend/document-viewer/index.js.map +7 -0
- package/dist/backend/dynamic-grid/DynamicGrid.js +130 -0
- package/dist/backend/dynamic-grid/DynamicGrid.js.map +7 -0
- package/dist/backend/dynamic-grid/adapters.js +34 -0
- package/dist/backend/dynamic-grid/adapters.js.map +7 -0
- package/dist/backend/dynamic-grid/components/GridCell.js +149 -0
- package/dist/backend/dynamic-grid/components/GridCell.js.map +7 -0
- package/dist/backend/dynamic-grid/components/GridRow.js +32 -0
- package/dist/backend/dynamic-grid/components/GridRow.js.map +7 -0
- package/dist/backend/dynamic-grid/hooks/useGridKeyboardNav.js +115 -0
- package/dist/backend/dynamic-grid/hooks/useGridKeyboardNav.js.map +7 -0
- package/dist/backend/dynamic-grid/index.js +9 -0
- package/dist/backend/dynamic-grid/index.js.map +7 -0
- package/dist/backend/dynamic-grid/types.js +1 -0
- package/dist/backend/dynamic-grid/types.js.map +7 -0
- package/dist/backend/dynamic-table/DynamicTable.js +2607 -0
- package/dist/backend/dynamic-table/DynamicTable.js.map +7 -0
- package/dist/backend/dynamic-table/DynamicTable.stories.js +233 -0
- package/dist/backend/dynamic-table/DynamicTable.stories.js.map +7 -0
- package/dist/backend/dynamic-table/components/ActiveFilterChips.js +60 -0
- package/dist/backend/dynamic-table/components/ActiveFilterChips.js.map +7 -0
- package/dist/backend/dynamic-table/components/BulkActionsBar.js +58 -0
- package/dist/backend/dynamic-table/components/BulkActionsBar.js.map +7 -0
- package/dist/backend/dynamic-table/components/Cell.js +155 -0
- package/dist/backend/dynamic-table/components/Cell.js.map +7 -0
- package/dist/backend/dynamic-table/components/CellCommentDialog.js +726 -0
- package/dist/backend/dynamic-table/components/CellCommentDialog.js.map +7 -0
- package/dist/backend/dynamic-table/components/CellCommentHoverPopup.js +77 -0
- package/dist/backend/dynamic-table/components/CellCommentHoverPopup.js.map +7 -0
- package/dist/backend/dynamic-table/components/ColumnFilterPopover.js +485 -0
- package/dist/backend/dynamic-table/components/ColumnFilterPopover.js.map +7 -0
- package/dist/backend/dynamic-table/components/ColumnHeaderMenu.js +173 -0
- package/dist/backend/dynamic-table/components/ColumnHeaderMenu.js.map +7 -0
- package/dist/backend/dynamic-table/components/ColumnHeaders.js +528 -0
- package/dist/backend/dynamic-table/components/ColumnHeaders.js.map +7 -0
- package/dist/backend/dynamic-table/components/CompactLabelCell.js +64 -0
- package/dist/backend/dynamic-table/components/CompactLabelCell.js.map +7 -0
- package/dist/backend/dynamic-table/components/ConfigureViewFields.js +139 -0
- package/dist/backend/dynamic-table/components/ConfigureViewFields.js.map +7 -0
- package/dist/backend/dynamic-table/components/ConfigureViewFilters.js +305 -0
- package/dist/backend/dynamic-table/components/ConfigureViewFilters.js.map +7 -0
- package/dist/backend/dynamic-table/components/ConfigureViewFormatting.js +169 -0
- package/dist/backend/dynamic-table/components/ConfigureViewFormatting.js.map +7 -0
- package/dist/backend/dynamic-table/components/ConfigureViewFormulas.js +298 -0
- package/dist/backend/dynamic-table/components/ConfigureViewFormulas.js.map +7 -0
- package/dist/backend/dynamic-table/components/ConfigureViewGrouping.js +224 -0
- package/dist/backend/dynamic-table/components/ConfigureViewGrouping.js.map +7 -0
- package/dist/backend/dynamic-table/components/ConfigureViewLinkedColumns.js +167 -0
- package/dist/backend/dynamic-table/components/ConfigureViewLinkedColumns.js.map +7 -0
- package/dist/backend/dynamic-table/components/ConfigureViewPanel.js +592 -0
- package/dist/backend/dynamic-table/components/ConfigureViewPanel.js.map +7 -0
- package/dist/backend/dynamic-table/components/ConfigureViewRollups.js +269 -0
- package/dist/backend/dynamic-table/components/ConfigureViewRollups.js.map +7 -0
- package/dist/backend/dynamic-table/components/ConfigureViewSorting.js +107 -0
- package/dist/backend/dynamic-table/components/ConfigureViewSorting.js.map +7 -0
- package/dist/backend/dynamic-table/components/ContextMenu.js +91 -0
- package/dist/backend/dynamic-table/components/ContextMenu.js.map +7 -0
- package/dist/backend/dynamic-table/components/DateCell.js +80 -0
- package/dist/backend/dynamic-table/components/DateCell.js.map +7 -0
- package/dist/backend/dynamic-table/components/Debugger.js +311 -0
- package/dist/backend/dynamic-table/components/Debugger.js.map +7 -0
- package/dist/backend/dynamic-table/components/DensityControl.js +289 -0
- package/dist/backend/dynamic-table/components/DensityControl.js.map +7 -0
- package/dist/backend/dynamic-table/components/EmptyState.js +42 -0
- package/dist/backend/dynamic-table/components/EmptyState.js.map +7 -0
- package/dist/backend/dynamic-table/components/EntitySearchEditor.js +369 -0
- package/dist/backend/dynamic-table/components/EntitySearchEditor.js.map +7 -0
- package/dist/backend/dynamic-table/components/ExportMenu.js +63 -0
- package/dist/backend/dynamic-table/components/ExportMenu.js.map +7 -0
- package/dist/backend/dynamic-table/components/FilterDatePicker.js +165 -0
- package/dist/backend/dynamic-table/components/FilterDatePicker.js.map +7 -0
- package/dist/backend/dynamic-table/components/FilterValueInput.js +200 -0
- package/dist/backend/dynamic-table/components/FilterValueInput.js.map +7 -0
- package/dist/backend/dynamic-table/components/FooterTotalsRow.js +165 -0
- package/dist/backend/dynamic-table/components/FooterTotalsRow.js.map +7 -0
- package/dist/backend/dynamic-table/components/FullscreenOverlay.js +51 -0
- package/dist/backend/dynamic-table/components/FullscreenOverlay.js.map +7 -0
- package/dist/backend/dynamic-table/components/GroupHeaderRow.js +108 -0
- package/dist/backend/dynamic-table/components/GroupHeaderRow.js.map +7 -0
- package/dist/backend/dynamic-table/components/GroupSummaryRow.js +134 -0
- package/dist/backend/dynamic-table/components/GroupSummaryRow.js.map +7 -0
- package/dist/backend/dynamic-table/components/ImportPanel.js +341 -0
- package/dist/backend/dynamic-table/components/ImportPanel.js.map +7 -0
- package/dist/backend/dynamic-table/components/ImportPreviewGrid.js +114 -0
- package/dist/backend/dynamic-table/components/ImportPreviewGrid.js.map +7 -0
- package/dist/backend/dynamic-table/components/LoadErrorState.js +23 -0
- package/dist/backend/dynamic-table/components/LoadErrorState.js.map +7 -0
- package/dist/backend/dynamic-table/components/MentionPopup.js +149 -0
- package/dist/backend/dynamic-table/components/MentionPopup.js.map +7 -0
- package/dist/backend/dynamic-table/components/PerspectiveDeleteDialog.js +57 -0
- package/dist/backend/dynamic-table/components/PerspectiveDeleteDialog.js.map +7 -0
- package/dist/backend/dynamic-table/components/PerspectivePublishDialog.js +100 -0
- package/dist/backend/dynamic-table/components/PerspectivePublishDialog.js.map +7 -0
- package/dist/backend/dynamic-table/components/PerspectiveTabMenu.js +159 -0
- package/dist/backend/dynamic-table/components/PerspectiveTabMenu.js.map +7 -0
- package/dist/backend/dynamic-table/components/PerspectiveTabs.js +319 -0
- package/dist/backend/dynamic-table/components/PerspectiveTabs.js.map +7 -0
- package/dist/backend/dynamic-table/components/PerspectiveTemplateMenu.js +90 -0
- package/dist/backend/dynamic-table/components/PerspectiveTemplateMenu.js.map +7 -0
- package/dist/backend/dynamic-table/components/RowHeaderCell.js +58 -0
- package/dist/backend/dynamic-table/components/RowHeaderCell.js.map +7 -0
- package/dist/backend/dynamic-table/components/SearchBar.js +221 -0
- package/dist/backend/dynamic-table/components/SearchBar.js.map +7 -0
- package/dist/backend/dynamic-table/components/SelectMenu.js +186 -0
- package/dist/backend/dynamic-table/components/SelectMenu.js.map +7 -0
- package/dist/backend/dynamic-table/components/StaleDataBar.js +26 -0
- package/dist/backend/dynamic-table/components/StaleDataBar.js.map +7 -0
- package/dist/backend/dynamic-table/components/TableDeleteDialog.js +48 -0
- package/dist/backend/dynamic-table/components/TableDeleteDialog.js.map +7 -0
- package/dist/backend/dynamic-table/components/TablePagination.js +69 -0
- package/dist/backend/dynamic-table/components/TablePagination.js.map +7 -0
- package/dist/backend/dynamic-table/components/TableSkeleton.js +43 -0
- package/dist/backend/dynamic-table/components/TableSkeleton.js.map +7 -0
- package/dist/backend/dynamic-table/components/ToolbarOverflow.js +204 -0
- package/dist/backend/dynamic-table/components/ToolbarOverflow.js.map +7 -0
- package/dist/backend/dynamic-table/components/ViewModeSwitch.js +50 -0
- package/dist/backend/dynamic-table/components/ViewModeSwitch.js.map +7 -0
- package/dist/backend/dynamic-table/components/VirtualRow.js +214 -0
- package/dist/backend/dynamic-table/components/VirtualRow.js.map +7 -0
- package/dist/backend/dynamic-table/components/editors.js +1483 -0
- package/dist/backend/dynamic-table/components/editors.js.map +7 -0
- package/dist/backend/dynamic-table/components/renderers.js +131 -0
- package/dist/backend/dynamic-table/components/renderers.js.map +7 -0
- package/dist/backend/dynamic-table/events/events.js +73 -0
- package/dist/backend/dynamic-table/events/events.js.map +7 -0
- package/dist/backend/dynamic-table/formula/check.js +243 -0
- package/dist/backend/dynamic-table/formula/check.js.map +7 -0
- package/dist/backend/dynamic-table/formula/evaluate.js +302 -0
- package/dist/backend/dynamic-table/formula/evaluate.js.map +7 -0
- package/dist/backend/dynamic-table/formula/functions.js +602 -0
- package/dist/backend/dynamic-table/formula/functions.js.map +7 -0
- package/dist/backend/dynamic-table/formula/index.js +47 -0
- package/dist/backend/dynamic-table/formula/index.js.map +7 -0
- package/dist/backend/dynamic-table/formula/parse.js +293 -0
- package/dist/backend/dynamic-table/formula/parse.js.map +7 -0
- package/dist/backend/dynamic-table/formula/types.js +24 -0
- package/dist/backend/dynamic-table/formula/types.js.map +7 -0
- package/dist/backend/dynamic-table/handlers/cellWrites.js +143 -0
- package/dist/backend/dynamic-table/handlers/cellWrites.js.map +7 -0
- package/dist/backend/dynamic-table/handlers/index.js +504 -0
- package/dist/backend/dynamic-table/handlers/index.js.map +7 -0
- package/dist/backend/dynamic-table/handlers/perspectiveHandlers.js +377 -0
- package/dist/backend/dynamic-table/handlers/perspectiveHandlers.js.map +7 -0
- package/dist/backend/dynamic-table/hooks/index.js +524 -0
- package/dist/backend/dynamic-table/hooks/index.js.map +7 -0
- package/dist/backend/dynamic-table/hooks/useAbbreviations.js +19 -0
- package/dist/backend/dynamic-table/hooks/useAbbreviations.js.map +7 -0
- package/dist/backend/dynamic-table/hooks/useAnnotations.js +115 -0
- package/dist/backend/dynamic-table/hooks/useAnnotations.js.map +7 -0
- package/dist/backend/dynamic-table/hooks/useColumnVirtualizer.js +155 -0
- package/dist/backend/dynamic-table/hooks/useColumnVirtualizer.js.map +7 -0
- package/dist/backend/dynamic-table/hooks/useColumnWidthPersistence.js +135 -0
- package/dist/backend/dynamic-table/hooks/useColumnWidthPersistence.js.map +7 -0
- package/dist/backend/dynamic-table/hooks/useDensityPreference.js +203 -0
- package/dist/backend/dynamic-table/hooks/useDensityPreference.js.map +7 -0
- package/dist/backend/dynamic-table/hooks/useDynamicTablePage.js +1005 -0
- package/dist/backend/dynamic-table/hooks/useDynamicTablePage.js.map +7 -0
- package/dist/backend/dynamic-table/hooks/useEscapeLayer.js +32 -0
- package/dist/backend/dynamic-table/hooks/useEscapeLayer.js.map +7 -0
- package/dist/backend/dynamic-table/hooks/useGrouping.js +256 -0
- package/dist/backend/dynamic-table/hooks/useGrouping.js.map +7 -0
- package/dist/backend/dynamic-table/hooks/useSuggestionFetch.js +63 -0
- package/dist/backend/dynamic-table/hooks/useSuggestionFetch.js.map +7 -0
- package/dist/backend/dynamic-table/hooks/useTableDateFormat.js +11 -0
- package/dist/backend/dynamic-table/hooks/useTableDateFormat.js.map +7 -0
- package/dist/backend/dynamic-table/index.js +476 -0
- package/dist/backend/dynamic-table/index.js.map +7 -0
- package/dist/backend/dynamic-table/registry/ContentRegistryContext.js +126 -0
- package/dist/backend/dynamic-table/registry/ContentRegistryContext.js.map +7 -0
- package/dist/backend/dynamic-table/registry/TableRegistryContext.js +43 -0
- package/dist/backend/dynamic-table/registry/TableRegistryContext.js.map +7 -0
- package/dist/backend/dynamic-table/registry/filterAccessibleTables.js +13 -0
- package/dist/backend/dynamic-table/registry/filterAccessibleTables.js.map +7 -0
- package/dist/backend/dynamic-table/registry/index.js +44 -0
- package/dist/backend/dynamic-table/registry/index.js.map +7 -0
- package/dist/backend/dynamic-table/registry/lazyTable.js +55 -0
- package/dist/backend/dynamic-table/registry/lazyTable.js.map +7 -0
- package/dist/backend/dynamic-table/registry/types.js +1 -0
- package/dist/backend/dynamic-table/registry/types.js.map +7 -0
- package/dist/backend/dynamic-table/registry/widgetCatalog.js +96 -0
- package/dist/backend/dynamic-table/registry/widgetCatalog.js.map +7 -0
- package/dist/backend/dynamic-table/server/aggregate.js +261 -0
- package/dist/backend/dynamic-table/server/aggregate.js.map +7 -0
- package/dist/backend/dynamic-table/server/dateWindows.js +105 -0
- package/dist/backend/dynamic-table/server/dateWindows.js.map +7 -0
- package/dist/backend/dynamic-table/server/filterParser.js +97 -0
- package/dist/backend/dynamic-table/server/filterParser.js.map +7 -0
- package/dist/backend/dynamic-table/server/filterSuggestions.js +70 -0
- package/dist/backend/dynamic-table/server/filterSuggestions.js.map +7 -0
- package/dist/backend/dynamic-table/server/index.js +47 -0
- package/dist/backend/dynamic-table/server/index.js.map +7 -0
- package/dist/backend/dynamic-table/server/makeDynamicTableRoute.js +270 -0
- package/dist/backend/dynamic-table/server/makeDynamicTableRoute.js.map +7 -0
- package/dist/backend/dynamic-table/split-view/ContentPicker.js +171 -0
- package/dist/backend/dynamic-table/split-view/ContentPicker.js.map +7 -0
- package/dist/backend/dynamic-table/split-view/EmptySlot.js +47 -0
- package/dist/backend/dynamic-table/split-view/EmptySlot.js.map +7 -0
- package/dist/backend/dynamic-table/split-view/SplitViewHost.js +863 -0
- package/dist/backend/dynamic-table/split-view/SplitViewHost.js.map +7 -0
- package/dist/backend/dynamic-table/split-view/WidgetPane.js +280 -0
- package/dist/backend/dynamic-table/split-view/WidgetPane.js.map +7 -0
- package/dist/backend/dynamic-table/split-view/WorkspaceFilterBar.js +524 -0
- package/dist/backend/dynamic-table/split-view/WorkspaceFilterBar.js.map +7 -0
- package/dist/backend/dynamic-table/split-view/index.js +61 -0
- package/dist/backend/dynamic-table/split-view/index.js.map +7 -0
- package/dist/backend/dynamic-table/split-view/layoutPersistence.js +91 -0
- package/dist/backend/dynamic-table/split-view/layoutPersistence.js.map +7 -0
- package/dist/backend/dynamic-table/split-view/sharedCriteria.js +62 -0
- package/dist/backend/dynamic-table/split-view/sharedCriteria.js.map +7 -0
- package/dist/backend/dynamic-table/split-view/types.js +426 -0
- package/dist/backend/dynamic-table/split-view/types.js.map +7 -0
- package/dist/backend/dynamic-table/split-view/useSplitViewLayouts.js +79 -0
- package/dist/backend/dynamic-table/split-view/useSplitViewLayouts.js.map +7 -0
- package/dist/backend/dynamic-table/store/index.js +611 -0
- package/dist/backend/dynamic-table/store/index.js.map +7 -0
- package/dist/backend/dynamic-table/styles/ContextMenu.css.js +2 -0
- package/dist/backend/dynamic-table/styles/DynamicTable.css.js +2 -0
- package/dist/backend/dynamic-table/styles/DynamicTable.v2.css.js +2 -0
- package/dist/backend/dynamic-table/styles/SearchBar.css.js +2 -0
- package/dist/backend/dynamic-table/styles/dateCell.css.js +2 -0
- package/dist/backend/dynamic-table/styles/density.css.js +2 -0
- package/dist/backend/dynamic-table/types/density.js +29 -0
- package/dist/backend/dynamic-table/types/density.js.map +7 -0
- package/dist/backend/dynamic-table/types/filters.js +124 -0
- package/dist/backend/dynamic-table/types/filters.js.map +7 -0
- package/dist/backend/dynamic-table/types/grouping.js +34 -0
- package/dist/backend/dynamic-table/types/grouping.js.map +7 -0
- package/dist/backend/dynamic-table/types/import.js +7 -0
- package/dist/backend/dynamic-table/types/import.js.map +7 -0
- package/dist/backend/dynamic-table/types/index.js +46 -0
- package/dist/backend/dynamic-table/types/index.js.map +7 -0
- package/dist/backend/dynamic-table/types/perspective.js +79 -0
- package/dist/backend/dynamic-table/types/perspective.js.map +7 -0
- package/dist/backend/dynamic-table/types/rollup.js +13 -0
- package/dist/backend/dynamic-table/types/rollup.js.map +7 -0
- package/dist/backend/dynamic-table/utils/abbreviations.js +228 -0
- package/dist/backend/dynamic-table/utils/abbreviations.js.map +7 -0
- package/dist/backend/dynamic-table/utils/anchoredPosition.js +21 -0
- package/dist/backend/dynamic-table/utils/anchoredPosition.js.map +7 -0
- package/dist/backend/dynamic-table/utils/badgeAutoFit.js +35 -0
- package/dist/backend/dynamic-table/utils/badgeAutoFit.js.map +7 -0
- package/dist/backend/dynamic-table/utils/clipboard.js +172 -0
- package/dist/backend/dynamic-table/utils/clipboard.js.map +7 -0
- package/dist/backend/dynamic-table/utils/coerceCellValue.js +52 -0
- package/dist/backend/dynamic-table/utils/coerceCellValue.js.map +7 -0
- package/dist/backend/dynamic-table/utils/colorAdjacency.js +27 -0
- package/dist/backend/dynamic-table/utils/colorAdjacency.js.map +7 -0
- package/dist/backend/dynamic-table/utils/columnFill.js +36 -0
- package/dist/backend/dynamic-table/utils/columnFill.js.map +7 -0
- package/dist/backend/dynamic-table/utils/columnSpacerStyle.js +21 -0
- package/dist/backend/dynamic-table/utils/columnSpacerStyle.js.map +7 -0
- package/dist/backend/dynamic-table/utils/columnWindow.js +293 -0
- package/dist/backend/dynamic-table/utils/columnWindow.js.map +7 -0
- package/dist/backend/dynamic-table/utils/conditionalFormat.js +188 -0
- package/dist/backend/dynamic-table/utils/conditionalFormat.js.map +7 -0
- package/dist/backend/dynamic-table/utils/exportTable.js +82 -0
- package/dist/backend/dynamic-table/utils/exportTable.js.map +7 -0
- package/dist/backend/dynamic-table/utils/fillGeometry.js +92 -0
- package/dist/backend/dynamic-table/utils/fillGeometry.js.map +7 -0
- package/dist/backend/dynamic-table/utils/fillPatterns.js +241 -0
- package/dist/backend/dynamic-table/utils/fillPatterns.js.map +7 -0
- package/dist/backend/dynamic-table/utils/formatAggregate.js +82 -0
- package/dist/backend/dynamic-table/utils/formatAggregate.js.map +7 -0
- package/dist/backend/dynamic-table/utils/formatDateCompact.js +508 -0
- package/dist/backend/dynamic-table/utils/formatDateCompact.js.map +7 -0
- package/dist/backend/dynamic-table/utils/formatLabelCompact.js +312 -0
- package/dist/backend/dynamic-table/utils/formatLabelCompact.js.map +7 -0
- package/dist/backend/dynamic-table/utils/formulaColumns.js +158 -0
- package/dist/backend/dynamic-table/utils/formulaColumns.js.map +7 -0
- package/dist/backend/dynamic-table/utils/importMapping.js +118 -0
- package/dist/backend/dynamic-table/utils/importMapping.js.map +7 -0
- package/dist/backend/dynamic-table/utils/importParse.js +121 -0
- package/dist/backend/dynamic-table/utils/importParse.js.map +7 -0
- package/dist/backend/dynamic-table/utils/lookupColumns.js +28 -0
- package/dist/backend/dynamic-table/utils/lookupColumns.js.map +7 -0
- package/dist/backend/dynamic-table/utils/perspectiveTransforms.js +191 -0
- package/dist/backend/dynamic-table/utils/perspectiveTransforms.js.map +7 -0
- package/dist/backend/dynamic-table/utils/rollupColumns.js +81 -0
- package/dist/backend/dynamic-table/utils/rollupColumns.js.map +7 -0
- package/dist/backend/dynamic-table/validators/index.js +36 -0
- package/dist/backend/dynamic-table/validators/index.js.map +7 -0
- package/dist/backend/fields/registry.generated.js +8 -0
- package/dist/backend/fields/registry.generated.js.map +7 -0
- package/dist/backend/fields/registry.js +29 -0
- package/dist/backend/fields/registry.js.map +7 -0
- package/dist/backend/filters/AdvancedFilterBuilder.js +280 -0
- package/dist/backend/filters/AdvancedFilterBuilder.js.map +7 -0
- package/dist/backend/forms/ActionsDropdown.js +160 -0
- package/dist/backend/forms/ActionsDropdown.js.map +7 -0
- package/dist/backend/forms/FormActionButtons.js +59 -0
- package/dist/backend/forms/FormActionButtons.js.map +7 -0
- package/dist/backend/forms/FormFooter.js +18 -0
- package/dist/backend/forms/FormFooter.js.map +7 -0
- package/dist/backend/forms/FormHeader.js +125 -0
- package/dist/backend/forms/FormHeader.js.map +7 -0
- package/dist/backend/forms/index.js +11 -0
- package/dist/backend/forms/index.js.map +7 -0
- package/dist/backend/hooks/useAdvancedFilter.js +64 -0
- package/dist/backend/hooks/useAdvancedFilter.js.map +7 -0
- package/dist/backend/indexes/PartialIndexBanner.js +74 -0
- package/dist/backend/indexes/PartialIndexBanner.js.map +7 -0
- package/dist/backend/indexes/store.js +62 -0
- package/dist/backend/indexes/store.js.map +7 -0
- package/dist/backend/injection/ComponentOverrideProvider.js +55 -0
- package/dist/backend/injection/ComponentOverrideProvider.js.map +7 -0
- package/dist/backend/injection/InjectedField.js +166 -0
- package/dist/backend/injection/InjectedField.js.map +7 -0
- package/dist/backend/injection/InjectionSpot.js +323 -0
- package/dist/backend/injection/InjectionSpot.js.map +7 -0
- package/dist/backend/injection/InjectionWizard.js +256 -0
- package/dist/backend/injection/InjectionWizard.js.map +7 -0
- package/dist/backend/injection/PageInjectionBoundary.js +26 -0
- package/dist/backend/injection/PageInjectionBoundary.js.map +7 -0
- package/dist/backend/injection/SseEventIndicator.js +8 -0
- package/dist/backend/injection/SseEventIndicator.js.map +7 -0
- package/dist/backend/injection/StatusBadgeInjectionSpot.js +34 -0
- package/dist/backend/injection/StatusBadgeInjectionSpot.js.map +7 -0
- package/dist/backend/injection/StatusBadgeRenderer.js +58 -0
- package/dist/backend/injection/StatusBadgeRenderer.js.map +7 -0
- package/dist/backend/injection/WidgetSharedState.js +49 -0
- package/dist/backend/injection/WidgetSharedState.js.map +7 -0
- package/dist/backend/injection/WizardInjectionSpot.js +30 -0
- package/dist/backend/injection/WizardInjectionSpot.js.map +7 -0
- package/dist/backend/injection/eventBridge.js +129 -0
- package/dist/backend/injection/eventBridge.js.map +7 -0
- package/dist/backend/injection/helpers.js +26 -0
- package/dist/backend/injection/helpers.js.map +7 -0
- package/dist/backend/injection/mergeMenuItems.js +49 -0
- package/dist/backend/injection/mergeMenuItems.js.map +7 -0
- package/dist/backend/injection/mutationEvents.js +20 -0
- package/dist/backend/injection/mutationEvents.js.map +7 -0
- package/dist/backend/injection/resolveInjectedIcon.js +23 -0
- package/dist/backend/injection/resolveInjectedIcon.js.map +7 -0
- package/dist/backend/injection/spotIds.js +72 -0
- package/dist/backend/injection/spotIds.js.map +7 -0
- package/dist/backend/injection/useAppEvent.js +35 -0
- package/dist/backend/injection/useAppEvent.js.map +7 -0
- package/dist/backend/injection/useGuardedMutation.js +71 -0
- package/dist/backend/injection/useGuardedMutation.js.map +7 -0
- package/dist/backend/injection/useInjectedMenuItems.js +49 -0
- package/dist/backend/injection/useInjectedMenuItems.js.map +7 -0
- package/dist/backend/injection/useInjectionDataWidgets.js +36 -0
- package/dist/backend/injection/useInjectionDataWidgets.js.map +7 -0
- package/dist/backend/injection/useOperationProgress.js +64 -0
- package/dist/backend/injection/useOperationProgress.js.map +7 -0
- package/dist/backend/injection/useRegisteredComponent.js +89 -0
- package/dist/backend/injection/useRegisteredComponent.js.map +7 -0
- package/dist/backend/injection/useWidgetSharedState.js +26 -0
- package/dist/backend/injection/useWidgetSharedState.js.map +7 -0
- package/dist/backend/injection/visibility-utils.js +29 -0
- package/dist/backend/injection/visibility-utils.js.map +7 -0
- package/dist/backend/injection/widgetRegistry.js +55 -0
- package/dist/backend/injection/widgetRegistry.js.map +7 -0
- package/dist/backend/inputs/ComboboxInput.js +228 -0
- package/dist/backend/inputs/ComboboxInput.js.map +7 -0
- package/dist/backend/inputs/DatePicker.js +148 -0
- package/dist/backend/inputs/DatePicker.js.map +7 -0
- package/dist/backend/inputs/DateTimePicker.js +188 -0
- package/dist/backend/inputs/DateTimePicker.js.map +7 -0
- package/dist/backend/inputs/EventPatternInput.js +38 -0
- package/dist/backend/inputs/EventPatternInput.js.map +7 -0
- package/dist/backend/inputs/EventSelect.js +115 -0
- package/dist/backend/inputs/EventSelect.js.map +7 -0
- package/dist/backend/inputs/LookupSelect.js +197 -0
- package/dist/backend/inputs/LookupSelect.js.map +7 -0
- package/dist/backend/inputs/PhoneNumberField.js +133 -0
- package/dist/backend/inputs/PhoneNumberField.js.map +7 -0
- package/dist/backend/inputs/SwitchableMarkdownInput.js +73 -0
- package/dist/backend/inputs/SwitchableMarkdownInput.js.map +7 -0
- package/dist/backend/inputs/TagsInput.js +258 -0
- package/dist/backend/inputs/TagsInput.js.map +7 -0
- package/dist/backend/inputs/TimeInput.js +132 -0
- package/dist/backend/inputs/TimeInput.js.map +7 -0
- package/dist/backend/inputs/TimePicker.js +105 -0
- package/dist/backend/inputs/TimePicker.js.map +7 -0
- package/dist/backend/inputs/index.js +12 -0
- package/dist/backend/inputs/index.js.map +7 -0
- package/dist/backend/markdown/MarkdownContent.js +20 -0
- package/dist/backend/markdown/MarkdownContent.js.map +7 -0
- package/dist/backend/markdown/index.js +3 -0
- package/dist/backend/markdown/index.js.map +7 -0
- package/dist/backend/markdown/useMarkdownRemarkPlugins.js +33 -0
- package/dist/backend/markdown/useMarkdownRemarkPlugins.js.map +7 -0
- package/dist/backend/messages/MessageComposer.js +158 -0
- package/dist/backend/messages/MessageComposer.js.map +7 -0
- package/dist/backend/messages/MessageObjectDetail.js +81 -0
- package/dist/backend/messages/MessageObjectDetail.js.map +7 -0
- package/dist/backend/messages/MessageObjectPreview.js +48 -0
- package/dist/backend/messages/MessageObjectPreview.js.map +7 -0
- package/dist/backend/messages/MessageObjectRecordPicker.js +89 -0
- package/dist/backend/messages/MessageObjectRecordPicker.js.map +7 -0
- package/dist/backend/messages/MessagePrioritySelector.js +74 -0
- package/dist/backend/messages/MessagePrioritySelector.js.map +7 -0
- package/dist/backend/messages/MessagesIcon.js +22 -0
- package/dist/backend/messages/MessagesIcon.js.map +7 -0
- package/dist/backend/messages/SendObjectMessageDialog.js +65 -0
- package/dist/backend/messages/SendObjectMessageDialog.js.map +7 -0
- package/dist/backend/messages/index.js +17 -0
- package/dist/backend/messages/index.js.map +7 -0
- package/dist/backend/messages/message-compose-form-groups.js +333 -0
- package/dist/backend/messages/message-compose-form-groups.js.map +7 -0
- package/dist/backend/messages/message-composer.types.js +1 -0
- package/dist/backend/messages/message-composer.types.js.map +7 -0
- package/dist/backend/messages/message-priority.js +40 -0
- package/dist/backend/messages/message-priority.js.map +7 -0
- package/dist/backend/messages/useMessageCompose.js +488 -0
- package/dist/backend/messages/useMessageCompose.js.map +7 -0
- package/dist/backend/messages/useMessageComposeOperations.js +163 -0
- package/dist/backend/messages/useMessageComposeOperations.js.map +7 -0
- package/dist/backend/messages/useMessagesPoll.js +79 -0
- package/dist/backend/messages/useMessagesPoll.js.map +7 -0
- package/dist/backend/notifications/NotificationBell.js +83 -0
- package/dist/backend/notifications/NotificationBell.js.map +7 -0
- package/dist/backend/notifications/NotificationCountBadge.js +9 -0
- package/dist/backend/notifications/NotificationCountBadge.js.map +7 -0
- package/dist/backend/notifications/NotificationDispatcher.js +165 -0
- package/dist/backend/notifications/NotificationDispatcher.js.map +7 -0
- package/dist/backend/notifications/NotificationItem.js +187 -0
- package/dist/backend/notifications/NotificationItem.js.map +7 -0
- package/dist/backend/notifications/NotificationPanel.js +146 -0
- package/dist/backend/notifications/NotificationPanel.js.map +7 -0
- package/dist/backend/notifications/index.js +19 -0
- package/dist/backend/notifications/index.js.map +7 -0
- package/dist/backend/notifications/useNotificationActions.js +124 -0
- package/dist/backend/notifications/useNotificationActions.js.map +7 -0
- package/dist/backend/notifications/useNotificationEffect.js +13 -0
- package/dist/backend/notifications/useNotificationEffect.js.map +7 -0
- package/dist/backend/notifications/useNotifications.js +11 -0
- package/dist/backend/notifications/useNotifications.js.map +7 -0
- package/dist/backend/notifications/useNotificationsPoll.js +135 -0
- package/dist/backend/notifications/useNotificationsPoll.js.map +7 -0
- package/dist/backend/notifications/useNotificationsSse.js +173 -0
- package/dist/backend/notifications/useNotificationsSse.js.map +7 -0
- package/dist/backend/operations/LastOperationBanner.js +80 -0
- package/dist/backend/operations/LastOperationBanner.js.map +7 -0
- package/dist/backend/operations/store.js +183 -0
- package/dist/backend/operations/store.js.map +7 -0
- package/dist/backend/progress/ProgressTopBar.js +129 -0
- package/dist/backend/progress/ProgressTopBar.js.map +7 -0
- package/dist/backend/progress/useProgress.js +11 -0
- package/dist/backend/progress/useProgress.js.map +7 -0
- package/dist/backend/progress/useProgressPoll.js +69 -0
- package/dist/backend/progress/useProgressPoll.js.map +7 -0
- package/dist/backend/progress/useProgressSse.js +104 -0
- package/dist/backend/progress/useProgressSse.js.map +7 -0
- package/dist/backend/schedule/ScheduleAgenda.js +107 -0
- package/dist/backend/schedule/ScheduleAgenda.js.map +7 -0
- package/dist/backend/schedule/ScheduleGrid.js +107 -0
- package/dist/backend/schedule/ScheduleGrid.js.map +7 -0
- package/dist/backend/schedule/ScheduleToolbar.js +166 -0
- package/dist/backend/schedule/ScheduleToolbar.js.map +7 -0
- package/dist/backend/schedule/ScheduleView.js +168 -0
- package/dist/backend/schedule/ScheduleView.js.map +7 -0
- package/dist/backend/schedule/index.js +6 -0
- package/dist/backend/schedule/index.js.map +7 -0
- package/dist/backend/schedule/recurrence.js +83 -0
- package/dist/backend/schedule/recurrence.js.map +7 -0
- package/dist/backend/schedule/types.js +1 -0
- package/dist/backend/schedule/types.js.map +7 -0
- package/dist/backend/section-page/SectionNav.js +104 -0
- package/dist/backend/section-page/SectionNav.js.map +7 -0
- package/dist/backend/section-page/SectionPage.js +33 -0
- package/dist/backend/section-page/SectionPage.js.map +7 -0
- package/dist/backend/section-page/index.js +7 -0
- package/dist/backend/section-page/index.js.map +7 -0
- package/dist/backend/section-page/types.js +1 -0
- package/dist/backend/section-page/types.js.map +7 -0
- package/dist/backend/settings/SettingsNavigation.js +43 -0
- package/dist/backend/settings/SettingsNavigation.js.map +7 -0
- package/dist/backend/settings/SettingsPageWrapper.js +66 -0
- package/dist/backend/settings/SettingsPageWrapper.js.map +7 -0
- package/dist/backend/settings/index.js +6 -0
- package/dist/backend/settings/index.js.map +7 -0
- package/dist/backend/upgrades/UpgradeActionBanner.js +91 -0
- package/dist/backend/upgrades/UpgradeActionBanner.js.map +7 -0
- package/dist/backend/utils/api.js +178 -0
- package/dist/backend/utils/api.js.map +7 -0
- package/dist/backend/utils/apiCall.js +68 -0
- package/dist/backend/utils/apiCall.js.map +7 -0
- package/dist/backend/utils/crud.js +126 -0
- package/dist/backend/utils/crud.js.map +7 -0
- package/dist/backend/utils/customFieldColumns.js +100 -0
- package/dist/backend/utils/customFieldColumns.js.map +7 -0
- package/dist/backend/utils/customFieldDefs.js +143 -0
- package/dist/backend/utils/customFieldDefs.js.map +7 -0
- package/dist/backend/utils/customFieldFilters.js +151 -0
- package/dist/backend/utils/customFieldFilters.js.map +7 -0
- package/dist/backend/utils/customFieldForms.js +169 -0
- package/dist/backend/utils/customFieldForms.js.map +7 -0
- package/dist/backend/utils/customFieldRelationDisplay.js +239 -0
- package/dist/backend/utils/customFieldRelationDisplay.js.map +7 -0
- package/dist/backend/utils/customFieldValues.js +26 -0
- package/dist/backend/utils/customFieldValues.js.map +7 -0
- package/dist/backend/utils/flash.js +16 -0
- package/dist/backend/utils/flash.js.map +7 -0
- package/dist/backend/utils/nav.js +280 -0
- package/dist/backend/utils/nav.js.map +7 -0
- package/dist/backend/utils/richTextSanitizer.js +11 -0
- package/dist/backend/utils/richTextSanitizer.js.map +7 -0
- package/dist/backend/utils/scopedHeaderStack.js +44 -0
- package/dist/backend/utils/scopedHeaderStack.js.map +7 -0
- package/dist/backend/utils/serverErrors.js +237 -0
- package/dist/backend/utils/serverErrors.js.map +7 -0
- package/dist/backend/utils/useAutoDiscoveredFields.js +96 -0
- package/dist/backend/utils/useAutoDiscoveredFields.js.map +7 -0
- package/dist/backend/version-history/VersionHistoryAction.js +67 -0
- package/dist/backend/version-history/VersionHistoryAction.js.map +7 -0
- package/dist/backend/version-history/VersionHistoryDetail.js +57 -0
- package/dist/backend/version-history/VersionHistoryDetail.js.map +7 -0
- package/dist/backend/version-history/VersionHistoryPanel.js +270 -0
- package/dist/backend/version-history/VersionHistoryPanel.js.map +7 -0
- package/dist/backend/version-history/index.js +13 -0
- package/dist/backend/version-history/index.js.map +7 -0
- package/dist/backend/version-history/labels.js +52 -0
- package/dist/backend/version-history/labels.js.map +7 -0
- package/dist/backend/version-history/types.js +1 -0
- package/dist/backend/version-history/types.js.map +7 -0
- package/dist/backend/version-history/useAuditPermissions.js +72 -0
- package/dist/backend/version-history/useAuditPermissions.js.map +7 -0
- package/dist/backend/version-history/useVersionHistory.js +141 -0
- package/dist/backend/version-history/useVersionHistory.js.map +7 -0
- package/dist/backend/views/NewViewForm.js +56 -0
- package/dist/backend/views/NewViewForm.js.map +7 -0
- package/dist/backend/views/ShareForm.js +49 -0
- package/dist/backend/views/ShareForm.js.map +7 -0
- package/dist/backend/views/ViewChip.js +112 -0
- package/dist/backend/views/ViewChip.js.map +7 -0
- package/dist/backend/views/ViewChipMenu.js +89 -0
- package/dist/backend/views/ViewChipMenu.js.map +7 -0
- package/dist/backend/views/types.js +1 -0
- package/dist/backend/views/types.js.map +7 -0
- package/dist/frontend/AuthFooter.js +23 -0
- package/dist/frontend/AuthFooter.js.map +7 -0
- package/dist/frontend/LanguageSwitcher.js +57 -0
- package/dist/frontend/LanguageSwitcher.js.map +7 -0
- package/dist/frontend/Layout.js +14 -0
- package/dist/frontend/Layout.js.map +7 -0
- package/dist/hooks/useIsMobile.js +21 -0
- package/dist/hooks/useIsMobile.js.map +7 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +7 -0
- package/dist/portal/PortalContext.js +113 -0
- package/dist/portal/PortalContext.js.map +7 -0
- package/dist/portal/PortalLayoutProvider.js +5 -0
- package/dist/portal/PortalLayoutProvider.js.map +7 -0
- package/dist/portal/PortalLayoutShell.js +44 -0
- package/dist/portal/PortalLayoutShell.js.map +7 -0
- package/dist/portal/PortalShell.js +229 -0
- package/dist/portal/PortalShell.js.map +7 -0
- package/dist/portal/components/PortalCard.js +31 -0
- package/dist/portal/components/PortalCard.js.map +7 -0
- package/dist/portal/components/PortalEmptyState.js +16 -0
- package/dist/portal/components/PortalEmptyState.js.map +7 -0
- package/dist/portal/components/PortalFeatureCard.js +21 -0
- package/dist/portal/components/PortalFeatureCard.js.map +7 -0
- package/dist/portal/components/PortalNotificationBell.js +67 -0
- package/dist/portal/components/PortalNotificationBell.js.map +7 -0
- package/dist/portal/components/PortalNotificationPanel.js +158 -0
- package/dist/portal/components/PortalNotificationPanel.js.map +7 -0
- package/dist/portal/components/PortalPageHeader.js +16 -0
- package/dist/portal/components/PortalPageHeader.js.map +7 -0
- package/dist/portal/components/index.js +18 -0
- package/dist/portal/components/index.js.map +7 -0
- package/dist/portal/hooks/useCustomerAuth.js +69 -0
- package/dist/portal/hooks/useCustomerAuth.js.map +7 -0
- package/dist/portal/hooks/usePortalAppEvent.js +26 -0
- package/dist/portal/hooks/usePortalAppEvent.js.map +7 -0
- package/dist/portal/hooks/usePortalDashboardWidgets.js +37 -0
- package/dist/portal/hooks/usePortalDashboardWidgets.js.map +7 -0
- package/dist/portal/hooks/usePortalEventBridge.js +129 -0
- package/dist/portal/hooks/usePortalEventBridge.js.map +7 -0
- package/dist/portal/hooks/usePortalInjectedMenuItems.js +88 -0
- package/dist/portal/hooks/usePortalInjectedMenuItems.js.map +7 -0
- package/dist/portal/hooks/usePortalNotifications.js +85 -0
- package/dist/portal/hooks/usePortalNotifications.js.map +7 -0
- package/dist/portal/hooks/useTenantContext.js +53 -0
- package/dist/portal/hooks/useTenantContext.js.map +7 -0
- package/dist/primitives/DataLoader.js +68 -0
- package/dist/primitives/DataLoader.js.map +7 -0
- package/dist/primitives/ErrorNotice.js +14 -0
- package/dist/primitives/ErrorNotice.js.map +7 -0
- package/dist/primitives/Notice.js +55 -0
- package/dist/primitives/Notice.js.map +7 -0
- package/dist/primitives/alert.js +38 -0
- package/dist/primitives/alert.js.map +7 -0
- package/dist/primitives/badge.js +33 -0
- package/dist/primitives/badge.js.map +7 -0
- package/dist/primitives/button.js +45 -0
- package/dist/primitives/button.js.map +7 -0
- package/dist/primitives/calendar.js +62 -0
- package/dist/primitives/calendar.js.map +7 -0
- package/dist/primitives/card.js +91 -0
- package/dist/primitives/card.js.map +7 -0
- package/dist/primitives/checkbox.js +28 -0
- package/dist/primitives/checkbox.js.map +7 -0
- package/dist/primitives/dialog.js +101 -0
- package/dist/primitives/dialog.js.map +7 -0
- package/dist/primitives/form-field.js +78 -0
- package/dist/primitives/form-field.js.map +7 -0
- package/dist/primitives/icon-button.js +40 -0
- package/dist/primitives/icon-button.js.map +7 -0
- package/dist/primitives/input.js +22 -0
- package/dist/primitives/input.js.map +7 -0
- package/dist/primitives/label.js +21 -0
- package/dist/primitives/label.js.map +7 -0
- package/dist/primitives/popover.js +36 -0
- package/dist/primitives/popover.js.map +7 -0
- package/dist/primitives/primitives.stories.js +187 -0
- package/dist/primitives/primitives.stories.js.map +7 -0
- package/dist/primitives/progress.js +28 -0
- package/dist/primitives/progress.js.map +7 -0
- package/dist/primitives/separator.js +9 -0
- package/dist/primitives/separator.js.map +7 -0
- package/dist/primitives/sheet.js +103 -0
- package/dist/primitives/sheet.js.map +7 -0
- package/dist/primitives/spinner.js +24 -0
- package/dist/primitives/spinner.js.map +7 -0
- package/dist/primitives/status-badge.js +38 -0
- package/dist/primitives/status-badge.js.map +7 -0
- package/dist/primitives/switch.js +80 -0
- package/dist/primitives/switch.js.map +7 -0
- package/dist/primitives/table.js +29 -0
- package/dist/primitives/table.js.map +7 -0
- package/dist/primitives/tabs.js +90 -0
- package/dist/primitives/tabs.js.map +7 -0
- package/dist/primitives/textarea.js +21 -0
- package/dist/primitives/textarea.js.map +7 -0
- package/dist/primitives/tooltip.js +60 -0
- package/dist/primitives/tooltip.js.map +7 -0
- package/dist/primitives-v2/Alert.js +69 -0
- package/dist/primitives-v2/Alert.js.map +7 -0
- package/dist/primitives-v2/Alert.stories.js +42 -0
- package/dist/primitives-v2/Alert.stories.js.map +7 -0
- package/dist/primitives-v2/Avatar.js +93 -0
- package/dist/primitives-v2/Avatar.js.map +7 -0
- package/dist/primitives-v2/Avatar.stories.js +57 -0
- package/dist/primitives-v2/Avatar.stories.js.map +7 -0
- package/dist/primitives-v2/Badge.js +50 -0
- package/dist/primitives-v2/Badge.js.map +7 -0
- package/dist/primitives-v2/Badge.stories.js +66 -0
- package/dist/primitives-v2/Badge.stories.js.map +7 -0
- package/dist/primitives-v2/Breadcrumb.js +53 -0
- package/dist/primitives-v2/Breadcrumb.js.map +7 -0
- package/dist/primitives-v2/Breadcrumb.stories.js +70 -0
- package/dist/primitives-v2/Breadcrumb.stories.js.map +7 -0
- package/dist/primitives-v2/Button.js +85 -0
- package/dist/primitives-v2/Button.js.map +7 -0
- package/dist/primitives-v2/Button.stories.js +48 -0
- package/dist/primitives-v2/Button.stories.js.map +7 -0
- package/dist/primitives-v2/Checkbox.js +65 -0
- package/dist/primitives-v2/Checkbox.js.map +7 -0
- package/dist/primitives-v2/Checkbox.stories.js +31 -0
- package/dist/primitives-v2/Checkbox.stories.js.map +7 -0
- package/dist/primitives-v2/IconButton.js +13 -0
- package/dist/primitives-v2/IconButton.js.map +7 -0
- package/dist/primitives-v2/IconButton.stories.js +37 -0
- package/dist/primitives-v2/IconButton.stories.js.map +7 -0
- package/dist/primitives-v2/Input.js +74 -0
- package/dist/primitives-v2/Input.js.map +7 -0
- package/dist/primitives-v2/Input.stories.js +56 -0
- package/dist/primitives-v2/Input.stories.js.map +7 -0
- package/dist/primitives-v2/NavGroup.js +54 -0
- package/dist/primitives-v2/NavGroup.js.map +7 -0
- package/dist/primitives-v2/NavGroup.stories.js +39 -0
- package/dist/primitives-v2/NavGroup.stories.js.map +7 -0
- package/dist/primitives-v2/NavItem.js +61 -0
- package/dist/primitives-v2/NavItem.js.map +7 -0
- package/dist/primitives-v2/NavItem.stories.js +56 -0
- package/dist/primitives-v2/NavItem.stories.js.map +7 -0
- package/dist/primitives-v2/SearchInput.js +93 -0
- package/dist/primitives-v2/SearchInput.js.map +7 -0
- package/dist/primitives-v2/SearchInput.stories.js +41 -0
- package/dist/primitives-v2/SearchInput.stories.js.map +7 -0
- package/dist/primitives-v2/Sidebar.js +48 -0
- package/dist/primitives-v2/Sidebar.js.map +7 -0
- package/dist/primitives-v2/Sidebar.stories.js +76 -0
- package/dist/primitives-v2/Sidebar.stories.js.map +7 -0
- package/dist/primitives-v2/Tab.js +78 -0
- package/dist/primitives-v2/Tab.js.map +7 -0
- package/dist/primitives-v2/Tab.stories.js +51 -0
- package/dist/primitives-v2/Tab.stories.js.map +7 -0
- package/dist/primitives-v2/Tag.js +61 -0
- package/dist/primitives-v2/Tag.js.map +7 -0
- package/dist/primitives-v2/Tag.stories.js +121 -0
- package/dist/primitives-v2/Tag.stories.js.map +7 -0
- package/dist/primitives-v2/Toggle.js +72 -0
- package/dist/primitives-v2/Toggle.js.map +7 -0
- package/dist/primitives-v2/Toggle.stories.js +34 -0
- package/dist/primitives-v2/Toggle.stories.js.map +7 -0
- package/dist/primitives-v2/Topbar.js +24 -0
- package/dist/primitives-v2/Topbar.js.map +7 -0
- package/dist/primitives-v2/Topbar.stories.js +58 -0
- package/dist/primitives-v2/Topbar.stories.js.map +7 -0
- package/dist/primitives-v2/index.js +41 -0
- package/dist/primitives-v2/index.js.map +7 -0
- package/dist/primitives-v2/utils.js +7 -0
- package/dist/primitives-v2/utils.js.map +7 -0
- package/dist/theme/BrandThemeProvider.js +49 -0
- package/dist/theme/BrandThemeProvider.js.map +7 -0
- package/dist/theme/QueryProvider.js +44 -0
- package/dist/theme/QueryProvider.js.map +7 -0
- package/dist/theme/ThemeProvider.js +96 -0
- package/dist/theme/ThemeProvider.js.map +7 -0
- package/dist/theme/ThemeToggle.js +88 -0
- package/dist/theme/ThemeToggle.js.map +7 -0
- package/dist/theme/index.js +12 -0
- package/dist/theme/index.js.map +7 -0
- package/dist/theme/m3.css.js +2 -0
- package/dist/theme/tokens.v2.css.js +2 -0
- package/dist/theme/typography.css.js +2 -0
- package/dist/theme/typography.stories.js +123 -0
- package/dist/theme/typography.stories.js.map +7 -0
- package/dist/theme/typography.tokens.js +281 -0
- package/dist/theme/typography.tokens.js.map +7 -0
- package/dist/types/react-big-calendar.d.js +1 -0
- package/dist/types/react-big-calendar.d.js.map +7 -0
- package/package.json +411 -0
- package/src/backend/AppShell.tsx +1606 -0
- package/src/backend/BackendChromeProvider.tsx +99 -0
- package/src/backend/BackendPageSurface.tsx +57 -0
- package/src/backend/CollapsibleNavSection.tsx +201 -0
- package/src/backend/ContextHelp.tsx +40 -0
- package/src/backend/CrudForm.tsx +3796 -0
- package/src/backend/DataTable.tsx +2584 -0
- package/src/backend/EmptyState.tsx +65 -0
- package/src/backend/FilterBar.tsx +171 -0
- package/src/backend/FilterOverlay.tsx +388 -0
- package/src/backend/FlashMessages.tsx +189 -0
- package/src/backend/HtmlRichTextEditor.tsx +124 -0
- package/src/backend/IntegrationsButton.tsx +29 -0
- package/src/backend/JsonBuilder.tsx +359 -0
- package/src/backend/JsonDisplay.tsx +258 -0
- package/src/backend/NextStepCallout.tsx +134 -0
- package/src/backend/Page.tsx +30 -0
- package/src/backend/PerspectiveSidebar.tsx +506 -0
- package/src/backend/ProfileDropdown.tsx +369 -0
- package/src/backend/RowActions.tsx +158 -0
- package/src/backend/SectionHeader.tsx +119 -0
- package/src/backend/SettingsButton.tsx +28 -0
- package/src/backend/Sidebar.stories.tsx +183 -0
- package/src/backend/Sidebar.tsx +737 -0
- package/src/backend/TruncatedCell.tsx +133 -0
- package/src/backend/UserMenu.tsx +153 -0
- package/src/backend/ValueIcons.tsx +48 -0
- package/src/backend/WebhookSetupGuide.tsx +127 -0
- package/src/backend/__tests__/AppShell.test.tsx +392 -0
- package/src/backend/__tests__/CrudForm.custom-fields.test.tsx +180 -0
- package/src/backend/__tests__/CrudForm.datetime.test.tsx +95 -0
- package/src/backend/__tests__/CrudForm.defaults.test.tsx +195 -0
- package/src/backend/__tests__/CrudForm.navigation.test.tsx +156 -0
- package/src/backend/__tests__/CrudForm.render.test.tsx +146 -0
- package/src/backend/__tests__/CrudForm.transformData.test.tsx +231 -0
- package/src/backend/__tests__/CrudForm.unsavedNavigation.test.tsx +145 -0
- package/src/backend/__tests__/CrudForm.validation.test.tsx +178 -0
- package/src/backend/__tests__/CustomDataSection.test.tsx +465 -0
- package/src/backend/__tests__/DataTable.extensions.test.tsx +339 -0
- package/src/backend/__tests__/DataTable.namespaces.test.ts +32 -0
- package/src/backend/__tests__/DataTable.render.test.tsx +48 -0
- package/src/backend/__tests__/FieldDefinitionsEditor.test.tsx +64 -0
- package/src/backend/__tests__/FlashMessages.test.tsx +105 -0
- package/src/backend/__tests__/FormHeader.test.tsx +81 -0
- package/src/backend/__tests__/NotificationDispatcher.test.ts +172 -0
- package/src/backend/__tests__/VersionHistoryPanel.test.tsx +109 -0
- package/src/backend/__tests__/component-replacement.test.tsx +270 -0
- package/src/backend/__tests__/custom-field-filters.test.ts +72 -0
- package/src/backend/__tests__/custom-field-forms.test.ts +54 -0
- package/src/backend/__tests__/nav-utils.test.ts +175 -0
- package/src/backend/__tests__/serverErrors.test.ts +83 -0
- package/src/backend/__tests__/useAuditPermissions.test.tsx +51 -0
- package/src/backend/__tests__/useNotifications.strategy.test.ts +58 -0
- package/src/backend/__tests__/useProgress.strategy.test.ts +62 -0
- package/src/backend/charts/BarChart.tsx +208 -0
- package/src/backend/charts/ChartUtils.tsx +196 -0
- package/src/backend/charts/ComboChart.stories.tsx +103 -0
- package/src/backend/charts/ComboChart.tsx +350 -0
- package/src/backend/charts/KpiCard.tsx +155 -0
- package/src/backend/charts/LineChart.tsx +207 -0
- package/src/backend/charts/PieChart.tsx +157 -0
- package/src/backend/charts/ScatterChart.tsx +498 -0
- package/src/backend/charts/TopNTable.tsx +141 -0
- package/src/backend/charts/WaterfallChart.tsx +213 -0
- package/src/backend/charts/index.ts +21 -0
- package/src/backend/columns/ColumnChooserPanel.tsx +293 -0
- package/src/backend/confirm-dialog/ConfirmDialog.tsx +275 -0
- package/src/backend/confirm-dialog/index.ts +7 -0
- package/src/backend/confirm-dialog/useConfirmDialog.tsx +146 -0
- package/src/backend/custom-fields/FieldDefinitionsEditor.tsx +1413 -0
- package/src/backend/custom-fields/FieldDefinitionsManager.tsx +381 -0
- package/src/backend/dashboard/DashboardScreen.tsx +706 -0
- package/src/backend/dashboard/__tests__/DashboardScreen.test.tsx +114 -0
- package/src/backend/dashboard/index.ts +1 -0
- package/src/backend/dashboard/widgetRegistry.ts +68 -0
- package/src/backend/date-range/DateRangeSelect.tsx +50 -0
- package/src/backend/date-range/InlineDateRangeSelect.tsx +52 -0
- package/src/backend/date-range/InlineGranularitySelect.tsx +63 -0
- package/src/backend/date-range/__tests__/dateRanges.test.ts +224 -0
- package/src/backend/date-range/dateRanges.ts +218 -0
- package/src/backend/date-range/index.ts +15 -0
- package/src/backend/detail/AccessDeniedMessage.tsx +32 -0
- package/src/backend/detail/ActivitiesSection.tsx +1261 -0
- package/src/backend/detail/AddressEditor.tsx +479 -0
- package/src/backend/detail/AddressTiles.tsx +591 -0
- package/src/backend/detail/AddressesSection.tsx +362 -0
- package/src/backend/detail/AttachmentDeleteDialog.tsx +56 -0
- package/src/backend/detail/AttachmentMetadataDialog.tsx +674 -0
- package/src/backend/detail/AttachmentVisualPreview.tsx +155 -0
- package/src/backend/detail/AttachmentsSection.tsx +327 -0
- package/src/backend/detail/CustomDataSection.tsx +700 -0
- package/src/backend/detail/DetailFieldsSection.tsx +163 -0
- package/src/backend/detail/ErrorMessage.tsx +32 -0
- package/src/backend/detail/InlineEditors.tsx +894 -0
- package/src/backend/detail/LoadingMessage.tsx +14 -0
- package/src/backend/detail/NotesSection.tsx +1266 -0
- package/src/backend/detail/TabEmptyState.tsx +48 -0
- package/src/backend/detail/TagsSection.tsx +470 -0
- package/src/backend/detail/__tests__/InlineEditors.test.tsx +59 -0
- package/src/backend/detail/addressFormat.tsx +121 -0
- package/src/backend/detail/index.ts +46 -0
- package/src/backend/devtools/UmesDevToolsPanel.tsx +122 -0
- package/src/backend/devtools/components/ConflictWarnings.tsx +36 -0
- package/src/backend/devtools/components/EnricherTiming.tsx +50 -0
- package/src/backend/devtools/components/EventFlow.tsx +45 -0
- package/src/backend/devtools/components/ExtensionPointList.tsx +59 -0
- package/src/backend/devtools/components/InterceptorActivity.tsx +48 -0
- package/src/backend/devtools/index.ts +2 -0
- package/src/backend/devtools/useUmesDevTools.ts +188 -0
- package/src/backend/document-viewer/RegionOverlay.tsx +150 -0
- package/src/backend/document-viewer/index.ts +2 -0
- package/src/backend/dynamic-grid/DynamicGrid.tsx +161 -0
- package/src/backend/dynamic-grid/adapters.ts +57 -0
- package/src/backend/dynamic-grid/components/GridCell.tsx +194 -0
- package/src/backend/dynamic-grid/components/GridRow.tsx +40 -0
- package/src/backend/dynamic-grid/hooks/useGridKeyboardNav.ts +134 -0
- package/src/backend/dynamic-grid/index.ts +4 -0
- package/src/backend/dynamic-grid/types.ts +55 -0
- package/src/backend/dynamic-table/DynamicTable.stories.tsx +274 -0
- package/src/backend/dynamic-table/DynamicTable.tsx +4419 -0
- package/src/backend/dynamic-table/README.md +1199 -0
- package/src/backend/dynamic-table/URL_FILTERING.md +217 -0
- package/src/backend/dynamic-table/__e2e__/GridHarness.ts +1155 -0
- package/src/backend/dynamic-table/__e2e__/SplitViewHarness.ts +366 -0
- package/src/backend/dynamic-table/__tests__/BulkActionsBar.test.tsx +143 -0
- package/src/backend/dynamic-table/__tests__/ColumnQuickFilter.test.tsx +607 -0
- package/src/backend/dynamic-table/__tests__/ConfigureViewFilters.suggestions.test.tsx +177 -0
- package/src/backend/dynamic-table/__tests__/ConfigureViewFormatting.test.tsx +125 -0
- package/src/backend/dynamic-table/__tests__/ConfigureViewFormulas.test.tsx +228 -0
- package/src/backend/dynamic-table/__tests__/ConfigureViewGrouping.aggregations.test.tsx +131 -0
- package/src/backend/dynamic-table/__tests__/ConfigureViewPanel.baseView.test.tsx +162 -0
- package/src/backend/dynamic-table/__tests__/ConfigureViewPanel.focus.test.tsx +54 -0
- package/src/backend/dynamic-table/__tests__/ConfigureViewPanel.sections.test.tsx +142 -0
- package/src/backend/dynamic-table/__tests__/ConfigureViewRollups.test.tsx +228 -0
- package/src/backend/dynamic-table/__tests__/ContextMenu.test.tsx +89 -0
- package/src/backend/dynamic-table/__tests__/DynamicTable.density.test.tsx +299 -0
- package/src/backend/dynamic-table/__tests__/DynamicTable.exportTruncation.test.tsx +145 -0
- package/src/backend/dynamic-table/__tests__/DynamicTable.loadError.test.tsx +173 -0
- package/src/backend/dynamic-table/__tests__/DynamicTable.wiring.test.tsx +357 -0
- package/src/backend/dynamic-table/__tests__/EmptyState.test.tsx +70 -0
- package/src/backend/dynamic-table/__tests__/ExportMenu.test.tsx +82 -0
- package/src/backend/dynamic-table/__tests__/FooterTotalsRow.test.tsx +154 -0
- package/src/backend/dynamic-table/__tests__/GroupSummaryRow.render.test.tsx +131 -0
- package/src/backend/dynamic-table/__tests__/LoadErrorState.test.tsx +74 -0
- package/src/backend/dynamic-table/__tests__/MentionPopup.search.test.tsx +127 -0
- package/src/backend/dynamic-table/__tests__/PerspectiveTabs.test.tsx +170 -0
- package/src/backend/dynamic-table/__tests__/RowHeaderCell.selection.test.tsx +80 -0
- package/src/backend/dynamic-table/__tests__/abbreviationsSubscription.test.ts +171 -0
- package/src/backend/dynamic-table/__tests__/aggregate.spec.test.ts +224 -0
- package/src/backend/dynamic-table/__tests__/aggregateDimension.test.tsx +191 -0
- package/src/backend/dynamic-table/__tests__/anchoredPosition.test.ts +88 -0
- package/src/backend/dynamic-table/__tests__/badgeAutoFit.test.ts +121 -0
- package/src/backend/dynamic-table/__tests__/cellHandlers.save.test.ts +282 -0
- package/src/backend/dynamic-table/__tests__/cellWrites.test.ts +274 -0
- package/src/backend/dynamic-table/__tests__/clipboard.test.ts +230 -0
- package/src/backend/dynamic-table/__tests__/coerceCellValue.test.ts +117 -0
- package/src/backend/dynamic-table/__tests__/colorAdjacency.test.ts +151 -0
- package/src/backend/dynamic-table/__tests__/columnFill.test.ts +66 -0
- package/src/backend/dynamic-table/__tests__/columnHeaderHandlers.sort.test.ts +100 -0
- package/src/backend/dynamic-table/__tests__/columnHeaderReorder.test.tsx +209 -0
- package/src/backend/dynamic-table/__tests__/columnReorderPersistence.test.tsx +127 -0
- package/src/backend/dynamic-table/__tests__/columnVirtualization.render.test.tsx +420 -0
- package/src/backend/dynamic-table/__tests__/columnVisibilityPersistence.test.tsx +136 -0
- package/src/backend/dynamic-table/__tests__/columnWindow.test.ts +643 -0
- package/src/backend/dynamic-table/__tests__/commentAudience.test.tsx +137 -0
- package/src/backend/dynamic-table/__tests__/commentScope.test.tsx +214 -0
- package/src/backend/dynamic-table/__tests__/conditionalFormat.test.ts +175 -0
- package/src/backend/dynamic-table/__tests__/contentRegistry.test.ts +267 -0
- package/src/backend/dynamic-table/__tests__/dateWindows.test.ts +149 -0
- package/src/backend/dynamic-table/__tests__/density.test.ts +204 -0
- package/src/backend/dynamic-table/__tests__/exportTable.test.ts +146 -0
- package/src/backend/dynamic-table/__tests__/fillGeometry.test.ts +201 -0
- package/src/backend/dynamic-table/__tests__/fillPatterns.test.ts +272 -0
- package/src/backend/dynamic-table/__tests__/filterParser.presets.test.ts +64 -0
- package/src/backend/dynamic-table/__tests__/filterParser.relative.test.ts +109 -0
- package/src/backend/dynamic-table/__tests__/filterSuggestions.test.ts +152 -0
- package/src/backend/dynamic-table/__tests__/formatAggregate.test.ts +99 -0
- package/src/backend/dynamic-table/__tests__/formatDateCompact.test.ts +489 -0
- package/src/backend/dynamic-table/__tests__/formatLabelCompact.test.ts +717 -0
- package/src/backend/dynamic-table/__tests__/formula.check.test.ts +257 -0
- package/src/backend/dynamic-table/__tests__/formula.evaluate.test.ts +274 -0
- package/src/backend/dynamic-table/__tests__/formula.functions.test.ts +226 -0
- package/src/backend/dynamic-table/__tests__/formula.noEval.test.ts +92 -0
- package/src/backend/dynamic-table/__tests__/formula.parse.test.ts +233 -0
- package/src/backend/dynamic-table/__tests__/formulaColumns.test.tsx +213 -0
- package/src/backend/dynamic-table/__tests__/handlerIdentity.test.ts +77 -0
- package/src/backend/dynamic-table/__tests__/importMapping.test.ts +174 -0
- package/src/backend/dynamic-table/__tests__/importParse.test.ts +130 -0
- package/src/backend/dynamic-table/__tests__/importServerOnly.test.ts +37 -0
- package/src/backend/dynamic-table/__tests__/layoutPersistence.test.ts +372 -0
- package/src/backend/dynamic-table/__tests__/mouseHandlers.doubleClick.test.ts +155 -0
- package/src/backend/dynamic-table/__tests__/perspectivePersonalization.test.ts +188 -0
- package/src/backend/dynamic-table/__tests__/perspectiveResolver.test.ts +133 -0
- package/src/backend/dynamic-table/__tests__/perspectiveState.defaultGrouping.test.ts +306 -0
- package/src/backend/dynamic-table/__tests__/perspectiveState.defaults.test.ts +107 -0
- package/src/backend/dynamic-table/__tests__/perspectiveTemplates.test.tsx +269 -0
- package/src/backend/dynamic-table/__tests__/perspectiveTransforms.aggregations.test.ts +105 -0
- package/src/backend/dynamic-table/__tests__/perspectiveTransforms.conditionalFormats.test.ts +107 -0
- package/src/backend/dynamic-table/__tests__/perspectiveTransforms.dateFormat.test.ts +61 -0
- package/src/backend/dynamic-table/__tests__/perspectiveTransforms.formulas.test.ts +85 -0
- package/src/backend/dynamic-table/__tests__/perspectiveTransforms.frozenColumns.test.ts +124 -0
- package/src/backend/dynamic-table/__tests__/rollupColumns.test.ts +149 -0
- package/src/backend/dynamic-table/__tests__/sharedCriteria.test.ts +255 -0
- package/src/backend/dynamic-table/__tests__/splitViewTree.test.ts +324 -0
- package/src/backend/dynamic-table/__tests__/store.clipboardMarker.test.ts +143 -0
- package/src/backend/dynamic-table/__tests__/store.fillPreview.test.ts +168 -0
- package/src/backend/dynamic-table/__tests__/store.mutations.test.ts +345 -0
- package/src/backend/dynamic-table/__tests__/store.selection.test.ts +157 -0
- package/src/backend/dynamic-table/__tests__/store.undo.test.ts +225 -0
- package/src/backend/dynamic-table/__tests__/store.valueSource.test.tsx +204 -0
- package/src/backend/dynamic-table/__tests__/useAnnotations.legacyOptions.test.tsx +58 -0
- package/src/backend/dynamic-table/__tests__/useColumnVirtualizer.test.ts +290 -0
- package/src/backend/dynamic-table/__tests__/useDensityPreference.test.ts +435 -0
- package/src/backend/dynamic-table/__tests__/useDynamicTablePage.duplicate.test.tsx +99 -0
- package/src/backend/dynamic-table/__tests__/useDynamicTablePage.export.test.tsx +281 -0
- package/src/backend/dynamic-table/__tests__/useDynamicTablePage.personalization.test.tsx +313 -0
- package/src/backend/dynamic-table/__tests__/useDynamicTablePage.perspectiveRestore.test.tsx +126 -0
- package/src/backend/dynamic-table/__tests__/useGrouping.aggregations.test.ts +158 -0
- package/src/backend/dynamic-table/__tests__/useGrouping.groupKey.test.ts +58 -0
- package/src/backend/dynamic-table/__tests__/useKeyboardNavigation.test.ts +594 -0
- package/src/backend/dynamic-table/__tests__/useStickyOffsets.memo.test.tsx +75 -0
- package/src/backend/dynamic-table/__tests__/useStickyOffsets.test.ts +106 -0
- package/src/backend/dynamic-table/__tests__/widgetPane.test.tsx +338 -0
- package/src/backend/dynamic-table/components/ActiveFilterChips.tsx +109 -0
- package/src/backend/dynamic-table/components/BulkActionsBar.tsx +89 -0
- package/src/backend/dynamic-table/components/Cell.tsx +292 -0
- package/src/backend/dynamic-table/components/CellCommentDialog.tsx +887 -0
- package/src/backend/dynamic-table/components/CellCommentHoverPopup.tsx +94 -0
- package/src/backend/dynamic-table/components/ColumnFilterPopover.tsx +775 -0
- package/src/backend/dynamic-table/components/ColumnHeaderMenu.tsx +189 -0
- package/src/backend/dynamic-table/components/ColumnHeaders.tsx +867 -0
- package/src/backend/dynamic-table/components/CompactLabelCell.tsx +293 -0
- package/src/backend/dynamic-table/components/ConfigureViewFields.tsx +173 -0
- package/src/backend/dynamic-table/components/ConfigureViewFilters.tsx +346 -0
- package/src/backend/dynamic-table/components/ConfigureViewFormatting.tsx +205 -0
- package/src/backend/dynamic-table/components/ConfigureViewFormulas.tsx +423 -0
- package/src/backend/dynamic-table/components/ConfigureViewGrouping.tsx +264 -0
- package/src/backend/dynamic-table/components/ConfigureViewLinkedColumns.tsx +249 -0
- package/src/backend/dynamic-table/components/ConfigureViewPanel.tsx +857 -0
- package/src/backend/dynamic-table/components/ConfigureViewRollups.tsx +364 -0
- package/src/backend/dynamic-table/components/ConfigureViewSorting.tsx +121 -0
- package/src/backend/dynamic-table/components/ContextMenu.tsx +140 -0
- package/src/backend/dynamic-table/components/DateCell.tsx +205 -0
- package/src/backend/dynamic-table/components/Debugger.tsx +340 -0
- package/src/backend/dynamic-table/components/DensityControl.tsx +441 -0
- package/src/backend/dynamic-table/components/EmptyState.tsx +103 -0
- package/src/backend/dynamic-table/components/EntitySearchEditor.tsx +528 -0
- package/src/backend/dynamic-table/components/ExportMenu.tsx +90 -0
- package/src/backend/dynamic-table/components/FilterDatePicker.tsx +194 -0
- package/src/backend/dynamic-table/components/FilterValueInput.tsx +294 -0
- package/src/backend/dynamic-table/components/FooterTotalsRow.tsx +291 -0
- package/src/backend/dynamic-table/components/FullscreenOverlay.tsx +66 -0
- package/src/backend/dynamic-table/components/GroupHeaderRow.tsx +136 -0
- package/src/backend/dynamic-table/components/GroupSummaryRow.tsx +184 -0
- package/src/backend/dynamic-table/components/ImportPanel.tsx +445 -0
- package/src/backend/dynamic-table/components/ImportPreviewGrid.tsx +155 -0
- package/src/backend/dynamic-table/components/LoadErrorState.tsx +70 -0
- package/src/backend/dynamic-table/components/MentionPopup.tsx +224 -0
- package/src/backend/dynamic-table/components/PerspectiveDeleteDialog.tsx +96 -0
- package/src/backend/dynamic-table/components/PerspectivePublishDialog.tsx +152 -0
- package/src/backend/dynamic-table/components/PerspectiveTabMenu.tsx +215 -0
- package/src/backend/dynamic-table/components/PerspectiveTabs.tsx +426 -0
- package/src/backend/dynamic-table/components/PerspectiveTemplateMenu.tsx +135 -0
- package/src/backend/dynamic-table/components/RowHeaderCell.tsx +73 -0
- package/src/backend/dynamic-table/components/SearchBar.tsx +309 -0
- package/src/backend/dynamic-table/components/SelectMenu.tsx +237 -0
- package/src/backend/dynamic-table/components/StaleDataBar.tsx +74 -0
- package/src/backend/dynamic-table/components/TableDeleteDialog.tsx +74 -0
- package/src/backend/dynamic-table/components/TablePagination.tsx +96 -0
- package/src/backend/dynamic-table/components/TableSkeleton.tsx +123 -0
- package/src/backend/dynamic-table/components/ToolbarOverflow.tsx +284 -0
- package/src/backend/dynamic-table/components/ViewModeSwitch.tsx +92 -0
- package/src/backend/dynamic-table/components/VirtualRow.tsx +346 -0
- package/src/backend/dynamic-table/components/editors.tsx +1727 -0
- package/src/backend/dynamic-table/components/renderers.tsx +162 -0
- package/src/backend/dynamic-table/events/events.ts +107 -0
- package/src/backend/dynamic-table/formula/check.ts +359 -0
- package/src/backend/dynamic-table/formula/evaluate.ts +473 -0
- package/src/backend/dynamic-table/formula/functions.ts +737 -0
- package/src/backend/dynamic-table/formula/index.ts +57 -0
- package/src/backend/dynamic-table/formula/parse.ts +414 -0
- package/src/backend/dynamic-table/formula/types.ts +151 -0
- package/src/backend/dynamic-table/handlers/cellWrites.ts +362 -0
- package/src/backend/dynamic-table/handlers/index.ts +789 -0
- package/src/backend/dynamic-table/handlers/perspectiveHandlers.ts +684 -0
- package/src/backend/dynamic-table/hooks/index.ts +888 -0
- package/src/backend/dynamic-table/hooks/useAbbreviations.ts +67 -0
- package/src/backend/dynamic-table/hooks/useAnnotations.ts +215 -0
- package/src/backend/dynamic-table/hooks/useColumnVirtualizer.ts +303 -0
- package/src/backend/dynamic-table/hooks/useColumnWidthPersistence.ts +214 -0
- package/src/backend/dynamic-table/hooks/useDensityPreference.ts +451 -0
- package/src/backend/dynamic-table/hooks/useDynamicTablePage.tsx +1681 -0
- package/src/backend/dynamic-table/hooks/useEscapeLayer.ts +72 -0
- package/src/backend/dynamic-table/hooks/useGrouping.ts +428 -0
- package/src/backend/dynamic-table/hooks/useSuggestionFetch.ts +109 -0
- package/src/backend/dynamic-table/hooks/useTableDateFormat.tsx +19 -0
- package/src/backend/dynamic-table/index.ts +497 -0
- package/src/backend/dynamic-table/registry/ContentRegistryContext.tsx +244 -0
- package/src/backend/dynamic-table/registry/TableRegistryContext.tsx +84 -0
- package/src/backend/dynamic-table/registry/filterAccessibleTables.ts +36 -0
- package/src/backend/dynamic-table/registry/index.ts +38 -0
- package/src/backend/dynamic-table/registry/lazyTable.tsx +80 -0
- package/src/backend/dynamic-table/registry/types.ts +181 -0
- package/src/backend/dynamic-table/registry/widgetCatalog.ts +226 -0
- package/src/backend/dynamic-table/server/aggregate.ts +474 -0
- package/src/backend/dynamic-table/server/dateWindows.ts +201 -0
- package/src/backend/dynamic-table/server/filterParser.ts +132 -0
- package/src/backend/dynamic-table/server/filterSuggestions.ts +150 -0
- package/src/backend/dynamic-table/server/index.ts +47 -0
- package/src/backend/dynamic-table/server/makeDynamicTableRoute.ts +426 -0
- package/src/backend/dynamic-table/split-view/ContentPicker.tsx +251 -0
- package/src/backend/dynamic-table/split-view/EmptySlot.tsx +69 -0
- package/src/backend/dynamic-table/split-view/SplitViewHost.tsx +1336 -0
- package/src/backend/dynamic-table/split-view/WidgetPane.tsx +390 -0
- package/src/backend/dynamic-table/split-view/WorkspaceFilterBar.tsx +728 -0
- package/src/backend/dynamic-table/split-view/index.ts +48 -0
- package/src/backend/dynamic-table/split-view/layoutPersistence.ts +185 -0
- package/src/backend/dynamic-table/split-view/sharedCriteria.ts +196 -0
- package/src/backend/dynamic-table/split-view/types.ts +778 -0
- package/src/backend/dynamic-table/split-view/useSplitViewLayouts.ts +97 -0
- package/src/backend/dynamic-table/store/index.ts +1011 -0
- package/src/backend/dynamic-table/styles/ContextMenu.css +387 -0
- package/src/backend/dynamic-table/styles/DynamicTable.css +3659 -0
- package/src/backend/dynamic-table/styles/DynamicTable.v2.css +2651 -0
- package/src/backend/dynamic-table/styles/SearchBar.css +426 -0
- package/src/backend/dynamic-table/styles/dateCell.css +146 -0
- package/src/backend/dynamic-table/styles/density.css +690 -0
- package/src/backend/dynamic-table/types/density.ts +179 -0
- package/src/backend/dynamic-table/types/filters.ts +156 -0
- package/src/backend/dynamic-table/types/grouping.ts +189 -0
- package/src/backend/dynamic-table/types/import.ts +132 -0
- package/src/backend/dynamic-table/types/index.ts +1198 -0
- package/src/backend/dynamic-table/types/perspective.ts +436 -0
- package/src/backend/dynamic-table/types/rollup.ts +111 -0
- package/src/backend/dynamic-table/utils/abbreviations.ts +556 -0
- package/src/backend/dynamic-table/utils/anchoredPosition.ts +106 -0
- package/src/backend/dynamic-table/utils/badgeAutoFit.ts +108 -0
- package/src/backend/dynamic-table/utils/clipboard.ts +294 -0
- package/src/backend/dynamic-table/utils/coerceCellValue.ts +96 -0
- package/src/backend/dynamic-table/utils/colorAdjacency.ts +87 -0
- package/src/backend/dynamic-table/utils/columnFill.ts +93 -0
- package/src/backend/dynamic-table/utils/columnSpacerStyle.ts +53 -0
- package/src/backend/dynamic-table/utils/columnWindow.ts +668 -0
- package/src/backend/dynamic-table/utils/conditionalFormat.ts +300 -0
- package/src/backend/dynamic-table/utils/exportTable.ts +140 -0
- package/src/backend/dynamic-table/utils/fillGeometry.ts +187 -0
- package/src/backend/dynamic-table/utils/fillPatterns.ts +422 -0
- package/src/backend/dynamic-table/utils/formatAggregate.ts +145 -0
- package/src/backend/dynamic-table/utils/formatDateCompact.ts +879 -0
- package/src/backend/dynamic-table/utils/formatLabelCompact.ts +640 -0
- package/src/backend/dynamic-table/utils/formulaColumns.tsx +237 -0
- package/src/backend/dynamic-table/utils/importMapping.ts +202 -0
- package/src/backend/dynamic-table/utils/importParse.ts +220 -0
- package/src/backend/dynamic-table/utils/lookupColumns.tsx +47 -0
- package/src/backend/dynamic-table/utils/perspectiveTransforms.ts +368 -0
- package/src/backend/dynamic-table/utils/rollupColumns.ts +162 -0
- package/src/backend/dynamic-table/validators/index.ts +40 -0
- package/src/backend/fields/registry.generated.ts +8 -0
- package/src/backend/fields/registry.ts +38 -0
- package/src/backend/filters/AdvancedFilterBuilder.tsx +339 -0
- package/src/backend/forms/ActionsDropdown.tsx +198 -0
- package/src/backend/forms/FormActionButtons.tsx +98 -0
- package/src/backend/forms/FormFooter.tsx +27 -0
- package/src/backend/forms/FormHeader.tsx +204 -0
- package/src/backend/forms/index.ts +4 -0
- package/src/backend/hooks/useAdvancedFilter.ts +77 -0
- package/src/backend/indexes/PartialIndexBanner.tsx +86 -0
- package/src/backend/indexes/store.ts +88 -0
- package/src/backend/injection/ComponentOverrideProvider.tsx +66 -0
- package/src/backend/injection/InjectedField.tsx +196 -0
- package/src/backend/injection/InjectionSpot.tsx +457 -0
- package/src/backend/injection/InjectionWizard.tsx +335 -0
- package/src/backend/injection/PageInjectionBoundary.tsx +31 -0
- package/src/backend/injection/SseEventIndicator.tsx +11 -0
- package/src/backend/injection/StatusBadgeInjectionSpot.tsx +50 -0
- package/src/backend/injection/StatusBadgeRenderer.tsx +100 -0
- package/src/backend/injection/WidgetSharedState.ts +58 -0
- package/src/backend/injection/WizardInjectionSpot.tsx +46 -0
- package/src/backend/injection/eventBridge.ts +158 -0
- package/src/backend/injection/helpers.ts +35 -0
- package/src/backend/injection/mergeMenuItems.ts +80 -0
- package/src/backend/injection/mutationEvents.ts +22 -0
- package/src/backend/injection/resolveInjectedIcon.tsx +30 -0
- package/src/backend/injection/spotIds.ts +64 -0
- package/src/backend/injection/useAppEvent.ts +76 -0
- package/src/backend/injection/useGuardedMutation.ts +101 -0
- package/src/backend/injection/useInjectedMenuItems.ts +67 -0
- package/src/backend/injection/useInjectionDataWidgets.ts +41 -0
- package/src/backend/injection/useOperationProgress.ts +105 -0
- package/src/backend/injection/useRegisteredComponent.tsx +106 -0
- package/src/backend/injection/useWidgetSharedState.ts +28 -0
- package/src/backend/injection/visibility-utils.ts +31 -0
- package/src/backend/injection/widgetRegistry.ts +68 -0
- package/src/backend/inputs/ComboboxInput.tsx +272 -0
- package/src/backend/inputs/DatePicker.tsx +162 -0
- package/src/backend/inputs/DateTimePicker.tsx +199 -0
- package/src/backend/inputs/EventPatternInput.tsx +46 -0
- package/src/backend/inputs/EventSelect.tsx +183 -0
- package/src/backend/inputs/LookupSelect.tsx +253 -0
- package/src/backend/inputs/PhoneNumberField.tsx +166 -0
- package/src/backend/inputs/SwitchableMarkdownInput.tsx +106 -0
- package/src/backend/inputs/TagsInput.tsx +297 -0
- package/src/backend/inputs/TimeInput.tsx +146 -0
- package/src/backend/inputs/TimePicker.tsx +120 -0
- package/src/backend/inputs/__tests__/DatePicker.test.tsx +76 -0
- package/src/backend/inputs/__tests__/DateTimePicker.logic.test.ts +126 -0
- package/src/backend/inputs/__tests__/PhoneNumberField.test.tsx +73 -0
- package/src/backend/inputs/__tests__/TagsInput.test.tsx +78 -0
- package/src/backend/inputs/__tests__/TimeInput.test.tsx +134 -0
- package/src/backend/inputs/__tests__/TimePicker.test.tsx +50 -0
- package/src/backend/inputs/index.ts +11 -0
- package/src/backend/markdown/MarkdownContent.tsx +33 -0
- package/src/backend/markdown/index.ts +2 -0
- package/src/backend/markdown/useMarkdownRemarkPlugins.ts +39 -0
- package/src/backend/messages/MessageComposer.tsx +197 -0
- package/src/backend/messages/MessageObjectDetail.tsx +95 -0
- package/src/backend/messages/MessageObjectPreview.tsx +64 -0
- package/src/backend/messages/MessageObjectRecordPicker.tsx +149 -0
- package/src/backend/messages/MessagePrioritySelector.tsx +78 -0
- package/src/backend/messages/MessagesIcon.tsx +32 -0
- package/src/backend/messages/SendObjectMessageDialog.tsx +74 -0
- package/src/backend/messages/__tests__/MessageComposer.test.tsx +350 -0
- package/src/backend/messages/__tests__/SendObjectMessageDialog.test.tsx +77 -0
- package/src/backend/messages/index.ts +22 -0
- package/src/backend/messages/message-compose-form-groups.tsx +383 -0
- package/src/backend/messages/message-composer.types.ts +82 -0
- package/src/backend/messages/message-priority.ts +53 -0
- package/src/backend/messages/useMessageCompose.ts +666 -0
- package/src/backend/messages/useMessageComposeOperations.ts +259 -0
- package/src/backend/messages/useMessagesPoll.ts +118 -0
- package/src/backend/notifications/NotificationBell.tsx +91 -0
- package/src/backend/notifications/NotificationCountBadge.tsx +14 -0
- package/src/backend/notifications/NotificationDispatcher.ts +213 -0
- package/src/backend/notifications/NotificationItem.tsx +262 -0
- package/src/backend/notifications/NotificationPanel.tsx +225 -0
- package/src/backend/notifications/__tests__/NotificationCountBadge.test.tsx +34 -0
- package/src/backend/notifications/index.ts +20 -0
- package/src/backend/notifications/useNotificationActions.ts +161 -0
- package/src/backend/notifications/useNotificationEffect.ts +17 -0
- package/src/backend/notifications/useNotifications.ts +12 -0
- package/src/backend/notifications/useNotificationsPoll.ts +167 -0
- package/src/backend/notifications/useNotificationsSse.ts +220 -0
- package/src/backend/operations/LastOperationBanner.tsx +85 -0
- package/src/backend/operations/__tests__/LastOperationBanner.test.tsx +101 -0
- package/src/backend/operations/store.ts +230 -0
- package/src/backend/progress/ProgressTopBar.tsx +189 -0
- package/src/backend/progress/useProgress.ts +12 -0
- package/src/backend/progress/useProgressPoll.ts +100 -0
- package/src/backend/progress/useProgressSse.ts +118 -0
- package/src/backend/schedule/ScheduleAgenda.tsx +136 -0
- package/src/backend/schedule/ScheduleGrid.tsx +136 -0
- package/src/backend/schedule/ScheduleToolbar.tsx +178 -0
- package/src/backend/schedule/ScheduleView.tsx +201 -0
- package/src/backend/schedule/index.ts +5 -0
- package/src/backend/schedule/recurrence.ts +99 -0
- package/src/backend/schedule/types.ts +26 -0
- package/src/backend/section-page/SectionNav.tsx +140 -0
- package/src/backend/section-page/SectionPage.tsx +34 -0
- package/src/backend/section-page/index.ts +3 -0
- package/src/backend/section-page/types.ts +30 -0
- package/src/backend/settings/SettingsNavigation.tsx +74 -0
- package/src/backend/settings/SettingsPageWrapper.tsx +78 -0
- package/src/backend/settings/index.ts +3 -0
- package/src/backend/upgrades/UpgradeActionBanner.tsx +128 -0
- package/src/backend/utils/__tests__/api.scoped-headers.test.ts +58 -0
- package/src/backend/utils/__tests__/api.test.ts +148 -0
- package/src/backend/utils/__tests__/apiCall.scoped-request-headers.test.ts +58 -0
- package/src/backend/utils/__tests__/apiCall.test.ts +109 -0
- package/src/backend/utils/__tests__/crud.test.ts +87 -0
- package/src/backend/utils/__tests__/customFieldDefs.test.ts +25 -0
- package/src/backend/utils/__tests__/customFieldValues.test.ts +35 -0
- package/src/backend/utils/__tests__/richTextSanitizer.test.ts +57 -0
- package/src/backend/utils/api.ts +214 -0
- package/src/backend/utils/apiCall.ts +123 -0
- package/src/backend/utils/crud.ts +174 -0
- package/src/backend/utils/customFieldColumns.ts +118 -0
- package/src/backend/utils/customFieldDefs.ts +247 -0
- package/src/backend/utils/customFieldFilters.ts +174 -0
- package/src/backend/utils/customFieldForms.ts +203 -0
- package/src/backend/utils/customFieldRelationDisplay.ts +302 -0
- package/src/backend/utils/customFieldValues.ts +41 -0
- package/src/backend/utils/flash.ts +17 -0
- package/src/backend/utils/nav.ts +402 -0
- package/src/backend/utils/richTextSanitizer.ts +7 -0
- package/src/backend/utils/scopedHeaderStack.ts +49 -0
- package/src/backend/utils/serverErrors.ts +313 -0
- package/src/backend/utils/useAutoDiscoveredFields.ts +138 -0
- package/src/backend/version-history/VersionHistoryAction.tsx +76 -0
- package/src/backend/version-history/VersionHistoryDetail.tsx +87 -0
- package/src/backend/version-history/VersionHistoryPanel.tsx +331 -0
- package/src/backend/version-history/index.ts +9 -0
- package/src/backend/version-history/labels.ts +56 -0
- package/src/backend/version-history/types.ts +27 -0
- package/src/backend/version-history/useAuditPermissions.ts +94 -0
- package/src/backend/version-history/useVersionHistory.ts +173 -0
- package/src/backend/views/NewViewForm.tsx +54 -0
- package/src/backend/views/ShareForm.tsx +62 -0
- package/src/backend/views/ViewChip.tsx +123 -0
- package/src/backend/views/ViewChipMenu.tsx +94 -0
- package/src/backend/views/types.ts +4 -0
- package/src/frontend/AuthFooter.tsx +29 -0
- package/src/frontend/LanguageSwitcher.tsx +66 -0
- package/src/frontend/Layout.tsx +13 -0
- package/src/hooks/useIsMobile.ts +27 -0
- package/src/index.ts +40 -0
- package/src/portal/PortalContext.tsx +180 -0
- package/src/portal/PortalLayoutProvider.tsx +5 -0
- package/src/portal/PortalLayoutShell.tsx +64 -0
- package/src/portal/PortalShell.tsx +370 -0
- package/src/portal/components/PortalCard.tsx +77 -0
- package/src/portal/components/PortalEmptyState.tsx +32 -0
- package/src/portal/components/PortalFeatureCard.tsx +41 -0
- package/src/portal/components/PortalNotificationBell.tsx +81 -0
- package/src/portal/components/PortalNotificationPanel.tsx +230 -0
- package/src/portal/components/PortalPageHeader.tsx +33 -0
- package/src/portal/components/index.ts +6 -0
- package/src/portal/hooks/useCustomerAuth.ts +97 -0
- package/src/portal/hooks/usePortalAppEvent.ts +56 -0
- package/src/portal/hooks/usePortalDashboardWidgets.ts +50 -0
- package/src/portal/hooks/usePortalEventBridge.ts +162 -0
- package/src/portal/hooks/usePortalInjectedMenuItems.ts +130 -0
- package/src/portal/hooks/usePortalNotifications.ts +116 -0
- package/src/portal/hooks/useTenantContext.ts +83 -0
- package/src/primitives/DataLoader.tsx +93 -0
- package/src/primitives/ErrorNotice.tsx +17 -0
- package/src/primitives/Notice.tsx +83 -0
- package/src/primitives/alert.tsx +59 -0
- package/src/primitives/badge.tsx +43 -0
- package/src/primitives/button.tsx +60 -0
- package/src/primitives/calendar.tsx +69 -0
- package/src/primitives/card.tsx +92 -0
- package/src/primitives/checkbox.tsx +34 -0
- package/src/primitives/dialog.tsx +122 -0
- package/src/primitives/form-field.tsx +106 -0
- package/src/primitives/icon-button.tsx +48 -0
- package/src/primitives/input.tsx +26 -0
- package/src/primitives/label.tsx +18 -0
- package/src/primitives/popover.tsx +37 -0
- package/src/primitives/primitives.stories.tsx +223 -0
- package/src/primitives/progress.tsx +28 -0
- package/src/primitives/separator.tsx +7 -0
- package/src/primitives/sheet.tsx +137 -0
- package/src/primitives/spinner.tsx +27 -0
- package/src/primitives/status-badge.tsx +62 -0
- package/src/primitives/switch.tsx +92 -0
- package/src/primitives/table.tsx +27 -0
- package/src/primitives/tabs.tsx +131 -0
- package/src/primitives/textarea.tsx +20 -0
- package/src/primitives/tooltip.tsx +85 -0
- package/src/primitives-v2/AGENTS.md +66 -0
- package/src/primitives-v2/Alert.stories.tsx +56 -0
- package/src/primitives-v2/Alert.tsx +108 -0
- package/src/primitives-v2/Avatar.stories.tsx +60 -0
- package/src/primitives-v2/Avatar.tsx +139 -0
- package/src/primitives-v2/Badge.stories.tsx +80 -0
- package/src/primitives-v2/Badge.tsx +99 -0
- package/src/primitives-v2/Breadcrumb.stories.tsx +61 -0
- package/src/primitives-v2/Breadcrumb.tsx +96 -0
- package/src/primitives-v2/Button.stories.tsx +72 -0
- package/src/primitives-v2/Button.tsx +178 -0
- package/src/primitives-v2/Checkbox.stories.tsx +34 -0
- package/src/primitives-v2/Checkbox.tsx +103 -0
- package/src/primitives-v2/IconButton.stories.tsx +40 -0
- package/src/primitives-v2/IconButton.tsx +40 -0
- package/src/primitives-v2/Input.stories.tsx +60 -0
- package/src/primitives-v2/Input.tsx +113 -0
- package/src/primitives-v2/NavGroup.stories.tsx +50 -0
- package/src/primitives-v2/NavGroup.tsx +76 -0
- package/src/primitives-v2/NavItem.stories.tsx +68 -0
- package/src/primitives-v2/NavItem.tsx +105 -0
- package/src/primitives-v2/SearchInput.stories.tsx +44 -0
- package/src/primitives-v2/SearchInput.tsx +123 -0
- package/src/primitives-v2/Sidebar.stories.tsx +91 -0
- package/src/primitives-v2/Sidebar.tsx +92 -0
- package/src/primitives-v2/Tab.stories.tsx +62 -0
- package/src/primitives-v2/Tab.tsx +148 -0
- package/src/primitives-v2/Tag.stories.tsx +123 -0
- package/src/primitives-v2/Tag.tsx +115 -0
- package/src/primitives-v2/Toggle.stories.tsx +37 -0
- package/src/primitives-v2/Toggle.tsx +93 -0
- package/src/primitives-v2/Topbar.stories.tsx +65 -0
- package/src/primitives-v2/Topbar.tsx +37 -0
- package/src/primitives-v2/index.ts +31 -0
- package/src/primitives-v2/utils.ts +9 -0
- package/src/theme/BrandThemeProvider.tsx +122 -0
- package/src/theme/QueryProvider.tsx +46 -0
- package/src/theme/ThemeProvider.tsx +120 -0
- package/src/theme/ThemeToggle.tsx +88 -0
- package/src/theme/index.ts +4 -0
- package/src/theme/m3.css +622 -0
- package/src/theme/tokens.v2.css +559 -0
- package/src/theme/typography.css +163 -0
- package/src/theme/typography.stories.tsx +157 -0
- package/src/theme/typography.tokens.ts +326 -0
- package/src/types/react-big-calendar.d.ts +16 -0
|
@@ -0,0 +1,1681 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { useState, useMemo, useRef, useEffect, useCallback } from 'react'
|
|
5
|
+
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
|
6
|
+
import { apiCall } from '../../utils/apiCall'
|
|
7
|
+
import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
|
|
8
|
+
import { flash } from '../../FlashMessages'
|
|
9
|
+
import { dispatch, useEventHandlers } from '../events/events'
|
|
10
|
+
import {
|
|
11
|
+
apiToDynamicTable,
|
|
12
|
+
apiTemplateToDynamicTable,
|
|
13
|
+
dynamicTableToApi,
|
|
14
|
+
} from '../utils/perspectiveTransforms'
|
|
15
|
+
import TableDeleteDialog from '../components/TableDeleteDialog'
|
|
16
|
+
import type {
|
|
17
|
+
ColumnDef,
|
|
18
|
+
FilterRow,
|
|
19
|
+
DynamicTableProps,
|
|
20
|
+
PaginationProps,
|
|
21
|
+
TableUIConfig,
|
|
22
|
+
CellEditSaveEvent,
|
|
23
|
+
CellSaveStartEvent,
|
|
24
|
+
CellSaveSuccessEvent,
|
|
25
|
+
CellSaveErrorEvent,
|
|
26
|
+
NewRowSaveEvent,
|
|
27
|
+
NewRowSaveErrorEvent,
|
|
28
|
+
KeyboardShortcutsConfig,
|
|
29
|
+
OnRowAction,
|
|
30
|
+
LoadLookupSources,
|
|
31
|
+
ExportAllResult,
|
|
32
|
+
} from '../types/index'
|
|
33
|
+
import { TableEvents } from '../types/index'
|
|
34
|
+
import { mergeSharedFilters } from '../split-view/sharedCriteria'
|
|
35
|
+
import { lookupColumnsToParam } from '../utils/lookupColumns'
|
|
36
|
+
import { rollupColumnsToParam } from '../utils/rollupColumns'
|
|
37
|
+
import type { LoadRollupSources, RollupColumnRef } from '../types/rollup'
|
|
38
|
+
import type { AggregationRule, AggregateResult, GroupRule } from '../types/grouping'
|
|
39
|
+
import type {
|
|
40
|
+
PerspectiveConfig,
|
|
41
|
+
PerspectiveSaveEvent,
|
|
42
|
+
PerspectiveSelectEvent,
|
|
43
|
+
PerspectiveRenameEvent,
|
|
44
|
+
PerspectiveDeleteEvent,
|
|
45
|
+
PerspectiveChangeEvent,
|
|
46
|
+
PerspectiveDuplicateEvent,
|
|
47
|
+
PerspectiveSetDefaultEvent,
|
|
48
|
+
PerspectivePublishEvent,
|
|
49
|
+
PerspectiveTemplateCopyEvent,
|
|
50
|
+
PerspectiveTemplate,
|
|
51
|
+
SortRule,
|
|
52
|
+
LookupColumnRef,
|
|
53
|
+
TableViewMode,
|
|
54
|
+
} from '../types/perspective'
|
|
55
|
+
import { BASE_VIEW_PERSPECTIVE_NAME, PERSPECTIVE_NAME_MAX_LENGTH } from '../types/perspective'
|
|
56
|
+
import type {
|
|
57
|
+
PerspectivesIndexResponse,
|
|
58
|
+
PerspectiveDto,
|
|
59
|
+
PerspectiveSaveResponse,
|
|
60
|
+
} from '@open-mercato/shared/modules/perspectives/types'
|
|
61
|
+
|
|
62
|
+
// Stable identity so it can sit in effect dependency arrays without churning.
|
|
63
|
+
const EMPTY_COLUMNS: string[] = []
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The one perspective-write failure a user can actually act on.
|
|
67
|
+
*
|
|
68
|
+
* The upstream table SOFT-deletes views, but its unique index on
|
|
69
|
+
* `(user, tenant, org, table, name)` ignores `deleted_at` — so a name you used
|
|
70
|
+
* on a view you deleted earlier is still taken, forever, and the write comes
|
|
71
|
+
* back as a duplicate-key 500 with no field-level detail. Every generic
|
|
72
|
+
* "Failed to …" message sent the user round the same loop; naming the cause
|
|
73
|
+
* tells them the one thing that will work.
|
|
74
|
+
*/
|
|
75
|
+
function perspectiveWriteError(response: { result?: unknown }, fallback: string): string {
|
|
76
|
+
const message = (response.result as { message?: string } | undefined)?.message ?? ''
|
|
77
|
+
return message.includes('duplicate key')
|
|
78
|
+
? 'A view you deleted earlier still holds this name. Pick a different name.'
|
|
79
|
+
: fallback
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const LAST_PERSPECTIVE_KEY_PREFIX = 'dt:lastPerspective:'
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* How long a burst of single-cell saves is allowed to accumulate before ONE
|
|
86
|
+
* list refetch runs. Long enough to swallow a paste/fill (whose per-cell PUTs
|
|
87
|
+
* land within a few tens of ms of each other) and a human typing across
|
|
88
|
+
* adjacent cells; short enough that a server-derived column updates while the
|
|
89
|
+
* user is still looking at the row they changed.
|
|
90
|
+
*/
|
|
91
|
+
const LIST_REFETCH_COALESCE_MS = 400
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Remember which view the user was last on, per table. Session-scoped: it must
|
|
95
|
+
* survive navigating into a record and back (the actual complaint) without
|
|
96
|
+
* outliving the browser session or needing a server round-trip. Storage access
|
|
97
|
+
* is guarded — SSR has no `window`, and Safari private mode throws on write.
|
|
98
|
+
*/
|
|
99
|
+
function readLastPerspectiveId(tableId: string | undefined): string | null {
|
|
100
|
+
if (!tableId || typeof window === 'undefined') return null
|
|
101
|
+
try {
|
|
102
|
+
return window.sessionStorage.getItem(`${LAST_PERSPECTIVE_KEY_PREFIX}${tableId}`)
|
|
103
|
+
} catch {
|
|
104
|
+
return null
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function writeLastPerspectiveId(tableId: string | undefined, id: string | null): void {
|
|
109
|
+
if (!tableId || typeof window === 'undefined') return
|
|
110
|
+
try {
|
|
111
|
+
const key = `${LAST_PERSPECTIVE_KEY_PREFIX}${tableId}`
|
|
112
|
+
if (id) window.sessionStorage.setItem(key, id)
|
|
113
|
+
else window.sessionStorage.removeItem(key)
|
|
114
|
+
} catch {
|
|
115
|
+
/* storage unavailable — last-view memory is best-effort */
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ─── Config Types ────────────────────────────
|
|
120
|
+
|
|
121
|
+
export interface DynamicTablePageDeleteConfig<TRow = any> {
|
|
122
|
+
title?: string | ((row: TRow) => string)
|
|
123
|
+
description?: string | ((row: TRow) => string)
|
|
124
|
+
nameColumn?: string
|
|
125
|
+
/** Base URL or function returning full URL for the DELETE request */
|
|
126
|
+
url?: string | ((row: TRow) => string)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface DynamicTablePageCellEditConfig {
|
|
130
|
+
url?: string | ((payload: CellEditSaveEvent, rowData: any) => string)
|
|
131
|
+
method?: 'PUT' | 'PATCH' | 'POST'
|
|
132
|
+
mapPayload?: (payload: CellEditSaveEvent, rowData: any) => Record<string, unknown>
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface DynamicTableCreateHandlerContext {
|
|
136
|
+
tableRef: React.RefObject<HTMLElement>
|
|
137
|
+
invalidate: () => void
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface DynamicTablePageCreateConfig {
|
|
141
|
+
url?: string
|
|
142
|
+
mapPayload?: (rowData: any) => Record<string, unknown>
|
|
143
|
+
/** Fully custom create flow. Replaces built-in logic. Handler must dispatch NEW_ROW_SAVE_START/SUCCESS/ERROR events. */
|
|
144
|
+
handler?: (payload: NewRowSaveEvent, ctx: DynamicTableCreateHandlerContext) => Promise<void>
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface DynamicTablePageHooks<TRow = any> {
|
|
148
|
+
beforeCellEdit?: (
|
|
149
|
+
payload: CellEditSaveEvent,
|
|
150
|
+
rowData: any
|
|
151
|
+
) => { url?: string; payload?: Record<string, unknown>; method?: 'PUT' | 'PATCH' | 'POST' } | void
|
|
152
|
+
beforeCreate?: (rowData: any) => Record<string, unknown> | Promise<Record<string, unknown>> | void
|
|
153
|
+
validateCreate?: (rowData: any) => string | null
|
|
154
|
+
afterMutation?: (
|
|
155
|
+
type: 'cellEdit' | 'create' | 'delete',
|
|
156
|
+
context: any
|
|
157
|
+
) => void | Promise<void>
|
|
158
|
+
beforeDelete?: (row: TRow) => boolean | Promise<boolean>
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface DynamicTablePageConfig<TRow = any> {
|
|
162
|
+
source: string
|
|
163
|
+
columns: ColumnDef[]
|
|
164
|
+
tableName: string
|
|
165
|
+
|
|
166
|
+
perspectives?: string
|
|
167
|
+
/**
|
|
168
|
+
* Entity id for the generic `/api/entities/filter-suggestions` endpoint.
|
|
169
|
+
*
|
|
170
|
+
* Prefer `loadFilterSuggestions` for any list whose columns are denormalised
|
|
171
|
+
* or whose values are skewed: the generic endpoint samples a page of rows and
|
|
172
|
+
* de-duplicates what it sees, so it under-reports common columns and returns
|
|
173
|
+
* `[]` (silently) for anything that isn't a plain column on the entity's own
|
|
174
|
+
* table.
|
|
175
|
+
*/
|
|
176
|
+
filterSuggestions?: string
|
|
177
|
+
/**
|
|
178
|
+
* Custom suggestion loader — takes precedence over `filterSuggestions`.
|
|
179
|
+
* Point this at a module route backed by `distinctColumnValues` so the list
|
|
180
|
+
* offers real, scope-constrained values for every column it shows.
|
|
181
|
+
*/
|
|
182
|
+
loadFilterSuggestions?: (field: string, query: string) => Promise<string[]>
|
|
183
|
+
/**
|
|
184
|
+
* Prefix for this instance's per-table CLIENT storage keys — the last-used
|
|
185
|
+
* perspective (`sessionStorage`) and column widths (`localStorage`).
|
|
186
|
+
*
|
|
187
|
+
* Absent or `''` → today's keys, byte-for-byte, so no existing user loses a
|
|
188
|
+
* saved width. A host that can mount the same table twice (split view) passes
|
|
189
|
+
* something unique per instance, e.g. `pane:{paneId}:` — without it, both
|
|
190
|
+
* instances derive the same key from `tableId` and overwrite each other.
|
|
191
|
+
*
|
|
192
|
+
* Deliberately NOT applied to density: that stays a user-global preference,
|
|
193
|
+
* so an embedded table never silently gets a different row height from the
|
|
194
|
+
* same table full-page.
|
|
195
|
+
*
|
|
196
|
+
* Spec: .ai/specs/2026-08-04-module-table-registry.md
|
|
197
|
+
*/
|
|
198
|
+
storageScope?: string
|
|
199
|
+
/** Loads the table's FK lookup sources. When set, the Configure View drawer
|
|
200
|
+
* shows a "Linked columns" section and the list request carries the active
|
|
201
|
+
* perspective's linked columns as a `lookups` param for server enrichment. */
|
|
202
|
+
loadLookupSources?: LoadLookupSources
|
|
203
|
+
/**
|
|
204
|
+
* Loads the table's one-to-many rollup sources. When set, the Configure View
|
|
205
|
+
* drawer shows a "Summarised columns" section, and this hook serialises the
|
|
206
|
+
* active perspective's rollup columns into a `rollups` param for the list
|
|
207
|
+
* route to aggregate.
|
|
208
|
+
*/
|
|
209
|
+
loadRollupSources?: LoadRollupSources
|
|
210
|
+
|
|
211
|
+
defaultSort?: { field: string; direction: 'asc' | 'desc' }
|
|
212
|
+
defaultPageSize?: number
|
|
213
|
+
/** Initial filters to seed on mount (e.g., from URL params). Only read once. */
|
|
214
|
+
initialFilters?: FilterRow[]
|
|
215
|
+
/**
|
|
216
|
+
* Filters a WORKSPACE is driving this table with, already translated into
|
|
217
|
+
* this table's own field names by `mapCriteriaForTable`.
|
|
218
|
+
*
|
|
219
|
+
* Held in a different place from the user's own `filters` on purpose. Merging
|
|
220
|
+
* them into that state would overwrite what the user set in this pane, and
|
|
221
|
+
* turning the workspace toggle back off could then restore nothing — the
|
|
222
|
+
* local filters would be gone. These combine with `filters` ONLY where the
|
|
223
|
+
* list request is built, and a shared rule wins over a local rule on the same
|
|
224
|
+
* field for that request alone (see `mergeSharedFilters`).
|
|
225
|
+
*
|
|
226
|
+
* Absent ⇒ this hook behaves exactly as it did before the workspace existed.
|
|
227
|
+
*/
|
|
228
|
+
sharedFilters?: FilterRow[]
|
|
229
|
+
/**
|
|
230
|
+
* The workspace search box's value, while the workspace is driving this pane.
|
|
231
|
+
*
|
|
232
|
+
* PRESENCE IS THE SWITCH, and the distinction matters:
|
|
233
|
+
* `undefined` → no workspace search; the pane's own search box applies.
|
|
234
|
+
* `''` → the workspace bar IS on and its box is empty; the pane's
|
|
235
|
+
* own (now HIDDEN) search must not keep filtering, so this
|
|
236
|
+
* empty needle wins.
|
|
237
|
+
* Any non-empty string replaces the pane's own search for the request. This
|
|
238
|
+
* mirrors the UI: while the bar is on, each pane's search row is hidden, and
|
|
239
|
+
* filtering by a box nobody can see is how a user comes to distrust the grid.
|
|
240
|
+
* The pane's own `search` state is left untouched throughout, so switching the
|
|
241
|
+
* workspace off restores it.
|
|
242
|
+
*/
|
|
243
|
+
sharedSearch?: string
|
|
244
|
+
idColumn?: string
|
|
245
|
+
|
|
246
|
+
delete?: boolean | string | DynamicTablePageDeleteConfig<TRow>
|
|
247
|
+
create?: boolean | DynamicTablePageCreateConfig
|
|
248
|
+
cellEdit?: false | DynamicTablePageCellEditConfig
|
|
249
|
+
|
|
250
|
+
queryKey?: string
|
|
251
|
+
/** Extra values appended to the query key for cache invalidation (e.g., scopeVersion) */
|
|
252
|
+
queryKeyDeps?: unknown[]
|
|
253
|
+
extraParams?: Record<string, string> | (() => Record<string, string>)
|
|
254
|
+
mapApiItem?: (item: any) => TRow | null
|
|
255
|
+
|
|
256
|
+
hooks?: DynamicTablePageHooks<TRow>
|
|
257
|
+
|
|
258
|
+
tableProps?: Partial<
|
|
259
|
+
Omit<
|
|
260
|
+
DynamicTableProps,
|
|
261
|
+
'data' | 'columns' | 'tableRef' | 'pagination' | 'savedPerspectives' | 'activePerspectiveId' | 'tableName'
|
|
262
|
+
>
|
|
263
|
+
>
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ─── Return Type ─────────────────────────────
|
|
267
|
+
|
|
268
|
+
export interface DynamicTablePageResult<TRow = any> {
|
|
269
|
+
props: DynamicTableProps
|
|
270
|
+
/** Delete confirmation dialog. Render inline as {table.deleteDialog}. Returns null when no deletion is pending. */
|
|
271
|
+
deleteDialog: React.ReactNode
|
|
272
|
+
/** Trigger the delete dialog for a given row */
|
|
273
|
+
setRowToDelete: (row: TRow | null) => void
|
|
274
|
+
query: ReturnType<typeof useQuery>
|
|
275
|
+
/**
|
|
276
|
+
* Dataset-wide aggregate for the active view's aggregation rules. `null`
|
|
277
|
+
* until it lands, or when the list route has not adopted `?aggregate=`.
|
|
278
|
+
*/
|
|
279
|
+
aggregateResult: AggregateResult | null
|
|
280
|
+
aggregateLoading: boolean
|
|
281
|
+
aggregateError: boolean
|
|
282
|
+
/**
|
|
283
|
+
* The LIST request failed (HEDGE-119). Exposed for hosts that render their
|
|
284
|
+
* own body instead of the grid, so they can refuse to draw an "empty"
|
|
285
|
+
* affordance over a request that never landed.
|
|
286
|
+
*/
|
|
287
|
+
isError: boolean
|
|
288
|
+
isLoading: boolean
|
|
289
|
+
refresh: () => void
|
|
290
|
+
state: {
|
|
291
|
+
page: number
|
|
292
|
+
limit: number
|
|
293
|
+
search: string
|
|
294
|
+
filters: FilterRow[]
|
|
295
|
+
sortField: string
|
|
296
|
+
sortDir: 'asc' | 'desc'
|
|
297
|
+
/**
|
|
298
|
+
* The active grouping, mirrored up from the table.
|
|
299
|
+
*
|
|
300
|
+
* READ-ONLY, and it is a MIRROR, not the source: `DynamicTable` owns the
|
|
301
|
+
* grouping state and reports it in PERSPECTIVE_CHANGE. A host that wants to
|
|
302
|
+
* label something by the active grouping reads it here; a host that wants
|
|
303
|
+
* to CHANGE it uses the Configure View drawer like everything else.
|
|
304
|
+
*/
|
|
305
|
+
grouping: GroupRule[]
|
|
306
|
+
/** How the table is currently drawing its rows. Same mirror contract. */
|
|
307
|
+
viewMode: TableViewMode
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ─── Hook ────────────────────────────────────
|
|
312
|
+
|
|
313
|
+
export function useDynamicTablePage<TRow = any>(
|
|
314
|
+
config: DynamicTablePageConfig<TRow>
|
|
315
|
+
): DynamicTablePageResult<TRow> {
|
|
316
|
+
const {
|
|
317
|
+
source,
|
|
318
|
+
columns,
|
|
319
|
+
tableName,
|
|
320
|
+
perspectives: perspectivesTableId,
|
|
321
|
+
defaultSort,
|
|
322
|
+
defaultPageSize = 50,
|
|
323
|
+
idColumn = 'id',
|
|
324
|
+
storageScope = '',
|
|
325
|
+
hooks,
|
|
326
|
+
} = config
|
|
327
|
+
|
|
328
|
+
const tableRef = useRef<HTMLDivElement>(null)
|
|
329
|
+
const queryClient = useQueryClient()
|
|
330
|
+
|
|
331
|
+
// Identity for this INSTANCE's client-side storage (last-used perspective,
|
|
332
|
+
// column widths) — as opposed to `perspectivesTableId`, which identifies the
|
|
333
|
+
// TABLE and must stay unscoped so every instance loads the same saved views
|
|
334
|
+
// from the server. With an empty scope this is byte-identical to the old key.
|
|
335
|
+
const storageTableId = perspectivesTableId
|
|
336
|
+
? `${storageScope}${perspectivesTableId}`
|
|
337
|
+
: undefined
|
|
338
|
+
|
|
339
|
+
// Organization scope. Features AND rows are org-scoped, and switching org
|
|
340
|
+
// dispatches a client-side event with NO page reload — so without this in the
|
|
341
|
+
// query key a cached table goes on showing the PREVIOUS organization's rows.
|
|
342
|
+
// That is authorised data in the wrong scope, presented as current, which is
|
|
343
|
+
// how someone invoices the wrong customer.
|
|
344
|
+
//
|
|
345
|
+
// Placed in the DEFAULT key rather than left to each table's `queryKeyDeps`:
|
|
346
|
+
// only 3 of 20 tables opted in, so 17 carried the bug. One place fixes all.
|
|
347
|
+
const organizationScopeVersion = useOrganizationScopeVersion()
|
|
348
|
+
|
|
349
|
+
// ── State ──
|
|
350
|
+
|
|
351
|
+
const [page, setPage] = useState(1)
|
|
352
|
+
const [limit, setLimit] = useState(defaultPageSize)
|
|
353
|
+
const [sortField, setSortField] = useState(defaultSort?.field ?? 'id')
|
|
354
|
+
const [sortDir, setSortDir] = useState<'asc' | 'desc'>(defaultSort?.direction ?? 'asc')
|
|
355
|
+
const [search, setSearch] = useState('')
|
|
356
|
+
const [filters, setFilters] = useState<FilterRow[]>(() => config.initialFilters ?? [])
|
|
357
|
+
// Mirrors of table-owned view state, kept so a HOST can read them (a grid
|
|
358
|
+
// renderer, a heading, an export label). Written only from PERSPECTIVE_CHANGE
|
|
359
|
+
// — never set from here, because a second writer is a second source of truth.
|
|
360
|
+
const [grouping, setGrouping] = useState<GroupRule[]>([])
|
|
361
|
+
const [viewMode, setViewMode] = useState<TableViewMode>('table')
|
|
362
|
+
|
|
363
|
+
const [savedPerspectives, setSavedPerspectives] = useState<PerspectiveConfig[]>([])
|
|
364
|
+
// Views other people published for anyone to COPY. Never applied
|
|
365
|
+
// automatically — see `PerspectiveTemplate`.
|
|
366
|
+
const [sharedTemplates, setSharedTemplates] = useState<PerspectiveTemplate[]>([])
|
|
367
|
+
const [activePerspectiveId, setActivePerspectiveId] = useState<string | null>(null)
|
|
368
|
+
// Mirror of the active perspective's linked (lookup) columns. Synced from the
|
|
369
|
+
// DynamicTable's perspective events so the list request can carry them to the
|
|
370
|
+
// server for enrichment. The grid renders the columns itself from the same
|
|
371
|
+
// perspective state.
|
|
372
|
+
const [lookupColumns, setLookupColumns] = useState<LookupColumnRef[]>([])
|
|
373
|
+
// Mirror of the active perspective's rollup ("summarised") columns, for the
|
|
374
|
+
// same reason as `lookupColumns` — except the cost is higher: each distinct
|
|
375
|
+
// SOURCE is one GROUP BY on the server, so the param carries only what the
|
|
376
|
+
// active view actually asked for, and an empty param leaves the route
|
|
377
|
+
// behaving byte-identically to before rollups existed.
|
|
378
|
+
const [rollupColumns, setRollupColumns] = useState<RollupColumnRef[]>([])
|
|
379
|
+
// Mirror of the active perspective's aggregation rules. Same reason as
|
|
380
|
+
// `lookupColumns`: the grid owns the rules, but the DATASET-wide totals can
|
|
381
|
+
// only be computed on the server, so the request has to carry them.
|
|
382
|
+
const [aggregations, setAggregations] = useState<AggregationRule[]>([])
|
|
383
|
+
|
|
384
|
+
const [pendingDelete, setPendingDelete] = useState<TRow | null>(null)
|
|
385
|
+
const [isDeleting, setIsDeleting] = useState(false)
|
|
386
|
+
// Bulk delete drives a confirmation modal. The promise resolver bridges the
|
|
387
|
+
// modal outcome back to the DynamicTable so it only clears its selection once
|
|
388
|
+
// the rows are actually deleted (and keeps it on cancel).
|
|
389
|
+
const [pendingBulkDelete, setPendingBulkDelete] = useState<string[] | null>(null)
|
|
390
|
+
const bulkResolverRef = useRef<{ resolve: () => void; reject: (reason?: unknown) => void } | null>(null)
|
|
391
|
+
|
|
392
|
+
const queryKeyBase = config.queryKey ?? source
|
|
393
|
+
|
|
394
|
+
// ── List invalidation, COALESCED ──
|
|
395
|
+
//
|
|
396
|
+
// Every successful cell save used to call `invalidateQueries` on the spot, so
|
|
397
|
+
// a three-cell edit produced three PUTs and three IDENTICAL GETs of the same
|
|
398
|
+
// page (ledger row 9.7) — and a paste or a fill, which is one gesture, fires
|
|
399
|
+
// one per written cell. The refetch is not what makes the screen correct: the
|
|
400
|
+
// store writes the row object optimistically and the grid already renders the
|
|
401
|
+
// new value. The refetch exists to pick up SERVER-DERIVED changes (computed
|
|
402
|
+
// columns, a status recalculated by a subscriber), and for that a single
|
|
403
|
+
// trailing refetch after the burst is exactly as correct as N of them.
|
|
404
|
+
//
|
|
405
|
+
// Trailing edge, not leading: the last write in a burst must be included.
|
|
406
|
+
const listInvalidateTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
407
|
+
const cancelPendingListInvalidation = useCallback(() => {
|
|
408
|
+
if (listInvalidateTimerRef.current) {
|
|
409
|
+
clearTimeout(listInvalidateTimerRef.current)
|
|
410
|
+
listInvalidateTimerRef.current = null
|
|
411
|
+
}
|
|
412
|
+
}, [])
|
|
413
|
+
const scheduleListInvalidation = useCallback(() => {
|
|
414
|
+
cancelPendingListInvalidation()
|
|
415
|
+
listInvalidateTimerRef.current = setTimeout(() => {
|
|
416
|
+
listInvalidateTimerRef.current = null
|
|
417
|
+
queryClient.invalidateQueries({ queryKey: [queryKeyBase] })
|
|
418
|
+
}, LIST_REFETCH_COALESCE_MS)
|
|
419
|
+
}, [cancelPendingListInvalidation, queryClient, queryKeyBase])
|
|
420
|
+
/** Row-count changes (create / delete) refetch NOW — and supersede any pending burst. */
|
|
421
|
+
const invalidateListNow = useCallback(() => {
|
|
422
|
+
cancelPendingListInvalidation()
|
|
423
|
+
queryClient.invalidateQueries({ queryKey: [queryKeyBase] })
|
|
424
|
+
}, [cancelPendingListInvalidation, queryClient, queryKeyBase])
|
|
425
|
+
useEffect(() => cancelPendingListInvalidation, [cancelPendingListInvalidation])
|
|
426
|
+
|
|
427
|
+
// Columns the host table hides by default. Needed when rehydrating saved
|
|
428
|
+
// perspectives: one saved without column config must fall back to the table's
|
|
429
|
+
// defaults rather than revealing every hidden column.
|
|
430
|
+
const defaultHiddenColumns = config.tableProps?.defaultHiddenColumns ?? EMPTY_COLUMNS
|
|
431
|
+
|
|
432
|
+
// ── Perspectives ──
|
|
433
|
+
|
|
434
|
+
const { data: perspectivesData, isFetched: perspectivesFetched } = useQuery({
|
|
435
|
+
queryKey: ['perspectives', perspectivesTableId],
|
|
436
|
+
queryFn: async () => {
|
|
437
|
+
const response = await apiCall<PerspectivesIndexResponse>(
|
|
438
|
+
`/api/perspectives/${perspectivesTableId}`
|
|
439
|
+
)
|
|
440
|
+
return response.ok ? response.result : null
|
|
441
|
+
},
|
|
442
|
+
enabled: !!perspectivesTableId,
|
|
443
|
+
})
|
|
444
|
+
|
|
445
|
+
// ── The list query waits for the perspective to be resolved ──
|
|
446
|
+
//
|
|
447
|
+
// Ledger 4.9: a saved view carrying rollup columns rendered every rollup cell
|
|
448
|
+
// as "—" on the FIRST load. The columns come from the perspective and paint
|
|
449
|
+
// immediately; the VALUES come from `?rollups=` on the list request, and that
|
|
450
|
+
// param is derived from the same perspective — which had not been fetched yet
|
|
451
|
+
// when the list query fired. So load one produced a request WITHOUT the param
|
|
452
|
+
// (rollup columns present, all values null), and only the second request —
|
|
453
|
+
// triggered when `setRollupColumns` finally changed `queryParams` — carried
|
|
454
|
+
// it. Two round-trips, and a window in between where the grid confidently
|
|
455
|
+
// shows em-dashes for numbers that exist.
|
|
456
|
+
//
|
|
457
|
+
// Gating the list on the perspective removes the wasted request rather than
|
|
458
|
+
// re-timing it. `setPerspectiveHydrated(true)` is called in the SAME effect
|
|
459
|
+
// that applies the restored filters/sort/lookups/rollups, so React batches
|
|
460
|
+
// them into one re-render and the first request the user ever sees already
|
|
461
|
+
// carries every param the view asked for.
|
|
462
|
+
//
|
|
463
|
+
// Two escape hatches, because a list that never loads is far worse than a
|
|
464
|
+
// wasted request: a table with no `perspectives` id starts hydrated, and the
|
|
465
|
+
// effect below flips the flag as soon as the perspectives request SETTLES,
|
|
466
|
+
// whatever it returned (404, empty, or columns not built yet).
|
|
467
|
+
const [perspectiveHydrated, setPerspectiveHydrated] = useState(!perspectivesTableId)
|
|
468
|
+
|
|
469
|
+
useEffect(() => {
|
|
470
|
+
if (!perspectivesTableId || !perspectivesFetched) return
|
|
471
|
+
if (!perspectivesData?.perspectives || columns.length === 0) setPerspectiveHydrated(true)
|
|
472
|
+
}, [perspectivesTableId, perspectivesFetched, perspectivesData, columns.length])
|
|
473
|
+
|
|
474
|
+
// Guards the one-time initial perspective pick. Kept in a ref (not in the
|
|
475
|
+
// effect deps) because `activePerspectiveId` used to be both read and written
|
|
476
|
+
// here — which re-ran the effect and handed DynamicTable a brand-new
|
|
477
|
+
// `savedPerspectives` array identity on every load.
|
|
478
|
+
const initialPerspectivePickedRef = useRef(false)
|
|
479
|
+
|
|
480
|
+
useEffect(() => {
|
|
481
|
+
if (perspectivesData?.perspectives && columns.length > 0) {
|
|
482
|
+
const allCols = columns.map((c) => c.data)
|
|
483
|
+
const transformed = perspectivesData.perspectives.map((p) =>
|
|
484
|
+
apiToDynamicTable(p, allCols, defaultHiddenColumns)
|
|
485
|
+
)
|
|
486
|
+
setSavedPerspectives(transformed)
|
|
487
|
+
// Shared templates arrive on the SAME response the personal views do —
|
|
488
|
+
// `rolePerspectives` has always been in the payload and was simply ignored.
|
|
489
|
+
setSharedTemplates(
|
|
490
|
+
(perspectivesData.rolePerspectives ?? []).map((rp) =>
|
|
491
|
+
apiTemplateToDynamicTable(rp, allCols, defaultHiddenColumns)
|
|
492
|
+
)
|
|
493
|
+
)
|
|
494
|
+
if (!initialPerspectivePickedRef.current) {
|
|
495
|
+
// Prefer the view the user was last on over the server default, so
|
|
496
|
+
// navigating into a record and back doesn't silently reset the list.
|
|
497
|
+
// With neither, fall back to this user's saved personalization of the
|
|
498
|
+
// base tab, so "Default view" opens the way they last left it.
|
|
499
|
+
const remembered = readLastPerspectiveId(storageTableId)
|
|
500
|
+
const baseViewId = transformed.find((p) => p.isBaseView)?.id ?? null
|
|
501
|
+
const restored = remembered && transformed.some((p) => p.id === remembered)
|
|
502
|
+
? remembered
|
|
503
|
+
: (perspectivesData.defaultPerspectiveId ?? baseViewId)
|
|
504
|
+
// Close the gate as soon as the views have LOADED, whether or not one was
|
|
505
|
+
// restored. It used to close only inside `if (restored)`, which made the
|
|
506
|
+
// feature chicken-and-egg: with no remembered view and no server default
|
|
507
|
+
// the ref stayed false forever, the persist effect below returned early,
|
|
508
|
+
// and a manual tab click was never written — so the list could only
|
|
509
|
+
// remember a view if it already remembered one. `remembered` is read
|
|
510
|
+
// above, before the gate closes, so nothing is clobbered.
|
|
511
|
+
initialPerspectivePickedRef.current = true
|
|
512
|
+
if (restored) {
|
|
513
|
+
setActivePerspectiveId(restored)
|
|
514
|
+
// Apply the view's QUERY state here, not via the grid's
|
|
515
|
+
// PERSPECTIVE_CHANGE event.
|
|
516
|
+
//
|
|
517
|
+
// The grid applies the view to ITSELF (columns, the sort arrow in the
|
|
518
|
+
// header) and announces it with PERSPECTIVE_CHANGE. On a tab click
|
|
519
|
+
// that reaches us and the list refetches. On first load it does NOT:
|
|
520
|
+
// the grid's dispatch effect and this hook's listener registration
|
|
521
|
+
// race, child passive effects run before the parent's, and the one
|
|
522
|
+
// dispatch that carries the restored view fires before
|
|
523
|
+
// `useEventHandlers` has attached. Proven in the browser — a cold load
|
|
524
|
+
// of a view sorted by Reference # ↓ issued exactly one list request,
|
|
525
|
+
// `sortField=containerNumber` (the table default), while the header
|
|
526
|
+
// already painted the ↓ on Reference #; clicking the same tab then
|
|
527
|
+
// issued `sortField=referenceNumber&sortDir=desc`.
|
|
528
|
+
//
|
|
529
|
+
// Reading the config straight off the perspective we just restored
|
|
530
|
+
// removes the race entirely rather than re-timing it, and it is the
|
|
531
|
+
// same projection `handlePerspectiveSelect` performs for a click, so
|
|
532
|
+
// the two entry points cannot drift.
|
|
533
|
+
const restoredPerspective = transformed.find((p) => p.id === restored)
|
|
534
|
+
if (restoredPerspective) {
|
|
535
|
+
// Only when the view HAS filters: a view without them must not wipe
|
|
536
|
+
// `initialFilters` seeded from the URL (deep links carry a filter).
|
|
537
|
+
if (restoredPerspective.filters.length > 0) setFilters(restoredPerspective.filters)
|
|
538
|
+
setLookupColumns(restoredPerspective.lookupColumns ?? [])
|
|
539
|
+
setRollupColumns(restoredPerspective.rollupColumns ?? [])
|
|
540
|
+
setAggregations(restoredPerspective.aggregations ?? [])
|
|
541
|
+
if (restoredPerspective.sorting.length > 0) {
|
|
542
|
+
setSortField(restoredPerspective.sorting[0].field)
|
|
543
|
+
setSortDir(restoredPerspective.sorting[0].direction)
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
// Released in the SAME batch as the setters above, so the very first
|
|
548
|
+
// list request carries the restored view's rollups/lookups/sort/filters
|
|
549
|
+
// rather than being issued once without them and again with (4.9).
|
|
550
|
+
setPerspectiveHydrated(true)
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}, [perspectivesData, columns, defaultHiddenColumns, perspectivesTableId])
|
|
554
|
+
|
|
555
|
+
// Persist every subsequent switch (tab click, save, delete-active) so the
|
|
556
|
+
// next mount restores it. Gated on the initial pick so the mount-time `null`
|
|
557
|
+
// can't wipe the remembered view before it has been read back.
|
|
558
|
+
useEffect(() => {
|
|
559
|
+
if (!initialPerspectivePickedRef.current) return
|
|
560
|
+
writeLastPerspectiveId(storageTableId, activePerspectiveId)
|
|
561
|
+
}, [activePerspectiveId, perspectivesTableId])
|
|
562
|
+
|
|
563
|
+
// ── Workspace-shared criteria ──
|
|
564
|
+
//
|
|
565
|
+
// The ONLY place the workspace's criteria and this pane's own state meet.
|
|
566
|
+
// Both `search`/`filters` above stay exactly what the user set here, so
|
|
567
|
+
// turning the workspace off is a pure removal — nothing to restore, because
|
|
568
|
+
// nothing was overwritten.
|
|
569
|
+
const sharedFilters = config.sharedFilters
|
|
570
|
+
const sharedSearch = config.sharedSearch
|
|
571
|
+
// Identity note: with no shared filters this returns `filters` itself, so the
|
|
572
|
+
// memo below sees the same reference it always did.
|
|
573
|
+
const effectiveFilters = useMemo(
|
|
574
|
+
() => mergeSharedFilters(filters, sharedFilters),
|
|
575
|
+
[filters, sharedFilters],
|
|
576
|
+
)
|
|
577
|
+
const effectiveSearch = sharedSearch !== undefined ? sharedSearch : search
|
|
578
|
+
|
|
579
|
+
// ── Data Query ──
|
|
580
|
+
|
|
581
|
+
const queryParams = useMemo(() => {
|
|
582
|
+
const params = new URLSearchParams()
|
|
583
|
+
params.set('page', String(page))
|
|
584
|
+
params.set('limit', String(limit))
|
|
585
|
+
params.set('pageSize', String(limit))
|
|
586
|
+
params.set('sortField', sortField)
|
|
587
|
+
params.set('sortDir', sortDir)
|
|
588
|
+
if (effectiveSearch) {
|
|
589
|
+
params.set('q', effectiveSearch)
|
|
590
|
+
params.set('search', effectiveSearch)
|
|
591
|
+
}
|
|
592
|
+
if (effectiveFilters.length) params.set('filters', JSON.stringify(effectiveFilters))
|
|
593
|
+
if (lookupColumns.length) params.set('lookups', lookupColumnsToParam(lookupColumns))
|
|
594
|
+
if (rollupColumns.length) params.set('rollups', rollupColumnsToParam(rollupColumns))
|
|
595
|
+
|
|
596
|
+
const extra =
|
|
597
|
+
typeof config.extraParams === 'function' ? config.extraParams() : config.extraParams
|
|
598
|
+
if (extra) {
|
|
599
|
+
Object.entries(extra).forEach(([key, value]) => params.set(key, value))
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
return params.toString()
|
|
603
|
+
}, [page, limit, sortField, sortDir, effectiveSearch, effectiveFilters, lookupColumns, rollupColumns, config.extraParams])
|
|
604
|
+
|
|
605
|
+
// `organizationScopeVersion` leads the deps so EVERY table refetches when the
|
|
606
|
+
// user switches organization, whether or not it opted in via `queryKeyDeps`.
|
|
607
|
+
// The three tables that already pass `scopeVersion` themselves are unaffected
|
|
608
|
+
// — a repeated value in a query key is idempotent.
|
|
609
|
+
const queryKeyDeps = useMemo(
|
|
610
|
+
() => [organizationScopeVersion, ...(config.queryKeyDeps ?? [])],
|
|
611
|
+
[organizationScopeVersion, config.queryKeyDeps],
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
const dataQuery = useQuery({
|
|
615
|
+
queryKey: [queryKeyBase, queryParams, ...queryKeyDeps],
|
|
616
|
+
queryFn: async () => {
|
|
617
|
+
const call = await apiCall<{
|
|
618
|
+
items: any[]
|
|
619
|
+
total: number
|
|
620
|
+
totalPages?: number
|
|
621
|
+
}>(`${source}?${queryParams}`)
|
|
622
|
+
if (!call.ok) throw new Error(`Failed to load ${tableName}`)
|
|
623
|
+
return call.result ?? { items: [], total: 0, totalPages: 1 }
|
|
624
|
+
},
|
|
625
|
+
// See `perspectiveHydrated` above (ledger 4.9). The skeleton stays up for
|
|
626
|
+
// the extra ~50 ms the perspectives request costs; in exchange the first
|
|
627
|
+
// response already carries the view's rollup values instead of a grid full
|
|
628
|
+
// of em-dashes that silently corrects itself a round-trip later.
|
|
629
|
+
enabled: perspectiveHydrated,
|
|
630
|
+
placeholderData: (previousData) => previousData,
|
|
631
|
+
})
|
|
632
|
+
|
|
633
|
+
// ── Dataset-wide aggregate ──
|
|
634
|
+
//
|
|
635
|
+
// A SEPARATE request, deliberately: the rows must never wait on a full-table
|
|
636
|
+
// SUM. The grid shows a page-scoped fold immediately and swaps in the dataset
|
|
637
|
+
// figure when it lands. It reuses `queryParams` (same filters, same search)
|
|
638
|
+
// minus paging, so the total always describes exactly the filtered set the
|
|
639
|
+
// page is a window onto.
|
|
640
|
+
// `field:fn`, or `field:fn:dimension` when the rule partitions its aggregate
|
|
641
|
+
// (per-currency totals). The third segment is optional so every previously
|
|
642
|
+
// published route keeps parsing the specs it already knows.
|
|
643
|
+
const aggregateSpec = useMemo(
|
|
644
|
+
() =>
|
|
645
|
+
aggregations
|
|
646
|
+
.map((r) => (r.dimension ? `${r.field}:${r.fn}:${r.dimension}` : `${r.field}:${r.fn}`))
|
|
647
|
+
.join(','),
|
|
648
|
+
[aggregations],
|
|
649
|
+
)
|
|
650
|
+
|
|
651
|
+
const aggregateParams = useMemo(() => {
|
|
652
|
+
if (!aggregateSpec) return null
|
|
653
|
+
const params = new URLSearchParams(queryParams)
|
|
654
|
+
params.set('aggregate', aggregateSpec)
|
|
655
|
+
// One row is enough to satisfy a route that always paginates; we only read
|
|
656
|
+
// the `aggregate` key off the response.
|
|
657
|
+
params.set('page', '1')
|
|
658
|
+
params.set('limit', '1')
|
|
659
|
+
params.set('pageSize', '1')
|
|
660
|
+
return params.toString()
|
|
661
|
+
}, [queryParams, aggregateSpec])
|
|
662
|
+
|
|
663
|
+
// PROBE ONCE per page. `makeDynamicTableRoute` has no consumers yet, so most
|
|
664
|
+
// list routes simply ignore `?aggregate=` — asking again on every filter
|
|
665
|
+
// keystroke would be one wasted round-trip per keystroke, forever.
|
|
666
|
+
const aggregateUnsupportedRef = useRef(false)
|
|
667
|
+
|
|
668
|
+
const aggregateQuery = useQuery({
|
|
669
|
+
queryKey: [queryKeyBase, 'aggregate', aggregateParams, ...queryKeyDeps],
|
|
670
|
+
enabled: !!aggregateParams && !aggregateUnsupportedRef.current,
|
|
671
|
+
queryFn: async () => {
|
|
672
|
+
const call = await apiCall<{ aggregate?: AggregateResult }>(`${source}?${aggregateParams}`)
|
|
673
|
+
// Degrade, never blank. A route that has not adopted `?aggregate=`
|
|
674
|
+
// (or that rejects the param outright) is not an error condition: the
|
|
675
|
+
// footer already holds a page-scoped fold, and saying "this page only"
|
|
676
|
+
// is truthful. Blanking the totals because a probe failed would be a
|
|
677
|
+
// strictly worse answer than the one we already have.
|
|
678
|
+
if (!call.ok || !call.result?.aggregate) {
|
|
679
|
+
aggregateUnsupportedRef.current = true
|
|
680
|
+
return null
|
|
681
|
+
}
|
|
682
|
+
return call.result.aggregate
|
|
683
|
+
},
|
|
684
|
+
placeholderData: (previousData) => previousData,
|
|
685
|
+
})
|
|
686
|
+
|
|
687
|
+
const tableData = useMemo(() => {
|
|
688
|
+
const items = dataQuery.data?.items ?? []
|
|
689
|
+
if (config.mapApiItem) {
|
|
690
|
+
return items.map(config.mapApiItem).filter(Boolean)
|
|
691
|
+
}
|
|
692
|
+
return items
|
|
693
|
+
}, [dataQuery.data?.items, config.mapApiItem])
|
|
694
|
+
|
|
695
|
+
// ── Whole-table export ──
|
|
696
|
+
// Powers the toolbar Export button: page through `source` collecting every
|
|
697
|
+
// row, ignoring the active perspective (filters + search) and pagination so
|
|
698
|
+
// the export is the whole table. Keeps `extraParams` (tab/category scope) and
|
|
699
|
+
// sort for stable ordering. Mapped through `mapApiItem` like the live table.
|
|
700
|
+
//
|
|
701
|
+
// HEDGE-123 — IT ABORTS, IT DOES NOT SHORTEN.
|
|
702
|
+
//
|
|
703
|
+
// This loop used to end every failure the same way it ended success: `break`,
|
|
704
|
+
// then `return collected`. A page that 500'd, a page that came back `200`
|
|
705
|
+
// with an unparseable body, and a walk that ran into the iteration cap all
|
|
706
|
+
// produced a shorter array and nothing else. The user got a CSV that opened,
|
|
707
|
+
// had headers, had rows, and was missing the rest — with no error anywhere.
|
|
708
|
+
//
|
|
709
|
+
// That is the same server failure as HEDGE-119 wearing different clothes:
|
|
710
|
+
// there a failed fetch rendered as "no invoices", here it writes a short file
|
|
711
|
+
// that reads as "these are all the invoices". The file is the more dangerous
|
|
712
|
+
// of the two, because it looks like an answer and goes into a reconciliation.
|
|
713
|
+
//
|
|
714
|
+
// So: a failed or unparseable page THROWS (the toolbar catches it and says
|
|
715
|
+
// the export failed, and no file is written), and a walk that cannot prove it
|
|
716
|
+
// collected everything returns `complete: false` WITH ITS COUNTS rather than
|
|
717
|
+
// a bare array.
|
|
718
|
+
const exportAllRows = useCallback(async (): Promise<ExportAllResult> => {
|
|
719
|
+
const pageLimit = 100 // upstream list cap
|
|
720
|
+
const collected: any[] = []
|
|
721
|
+
let pageNum = 1
|
|
722
|
+
let expected: number | null = null
|
|
723
|
+
|
|
724
|
+
// Bound the walk by what the SERVER says, not by a constant. The old
|
|
725
|
+
// `guard < 1000` was a silent 100,000-row ceiling: a bigger table exported
|
|
726
|
+
// short for a reason that had nothing to do with failure, and said nothing.
|
|
727
|
+
// Once page 1 reports `totalPages` we know exactly how many iterations are
|
|
728
|
+
// needed; the constant below is only a runaway stop for a server that never
|
|
729
|
+
// reports one, and reaching it is now REPORTED rather than swallowed.
|
|
730
|
+
const RUNAWAY_PAGE_CAP = 10_000
|
|
731
|
+
let maxPages = RUNAWAY_PAGE_CAP
|
|
732
|
+
let incomplete: string | null = null
|
|
733
|
+
|
|
734
|
+
while (pageNum <= maxPages) {
|
|
735
|
+
const params = new URLSearchParams()
|
|
736
|
+
params.set('page', String(pageNum))
|
|
737
|
+
params.set('limit', String(pageLimit))
|
|
738
|
+
params.set('pageSize', String(pageLimit))
|
|
739
|
+
params.set('sortField', sortField)
|
|
740
|
+
params.set('sortDir', sortDir)
|
|
741
|
+
const extra =
|
|
742
|
+
typeof config.extraParams === 'function' ? config.extraParams() : config.extraParams
|
|
743
|
+
if (extra) Object.entries(extra).forEach(([key, value]) => params.set(key, value))
|
|
744
|
+
if (lookupColumns.length) params.set('lookups', lookupColumnsToParam(lookupColumns))
|
|
745
|
+
if (rollupColumns.length) params.set('rollups', rollupColumnsToParam(rollupColumns))
|
|
746
|
+
|
|
747
|
+
const call = await apiCall<{ items: any[]; total: number; totalPages?: number }>(
|
|
748
|
+
`${source}?${params.toString()}`,
|
|
749
|
+
)
|
|
750
|
+
|
|
751
|
+
// The two failures that used to be indistinguishable from "done".
|
|
752
|
+
// `!call.result` is NOT redundant with `!call.ok`: `apiCall` returns
|
|
753
|
+
// `ok: true, result: null` for a 200 whose body will not parse, which is
|
|
754
|
+
// exactly what a route erroring after its status line produces.
|
|
755
|
+
if (!call.ok) {
|
|
756
|
+
throw new Error(
|
|
757
|
+
`Export failed on page ${pageNum} (HTTP ${call.status}) after ${collected.length} row(s)`,
|
|
758
|
+
)
|
|
759
|
+
}
|
|
760
|
+
if (!call.result) {
|
|
761
|
+
throw new Error(
|
|
762
|
+
`Export failed on page ${pageNum}: the server returned no readable body after ${collected.length} row(s)`,
|
|
763
|
+
)
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
const items = call.result.items ?? []
|
|
767
|
+
collected.push(...items)
|
|
768
|
+
|
|
769
|
+
if (typeof call.result.total === 'number') expected = call.result.total
|
|
770
|
+
const total = call.result.total ?? collected.length
|
|
771
|
+
const totalPages = call.result.totalPages ?? Math.ceil(total / pageLimit)
|
|
772
|
+
// Now that the server has declared its own page count, hold the walk to
|
|
773
|
+
// it. `+ 1` of slack absorbs a row appearing mid-walk without turning a
|
|
774
|
+
// benign race into a hard stop.
|
|
775
|
+
if (Number.isFinite(totalPages) && totalPages > 0) {
|
|
776
|
+
maxPages = Math.min(RUNAWAY_PAGE_CAP, totalPages + 1)
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
if (items.length === 0 || pageNum >= totalPages) break
|
|
780
|
+
pageNum++
|
|
781
|
+
|
|
782
|
+
if (pageNum > maxPages) {
|
|
783
|
+
incomplete =
|
|
784
|
+
maxPages >= RUNAWAY_PAGE_CAP
|
|
785
|
+
? `stopped at the ${RUNAWAY_PAGE_CAP}-page safety limit`
|
|
786
|
+
: `the server declared ${totalPages} page(s) but did not finish within them`
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
const rows = config.mapApiItem
|
|
791
|
+
? collected.map(config.mapApiItem).filter(Boolean)
|
|
792
|
+
: collected
|
|
793
|
+
|
|
794
|
+
// A count shortfall is reported, never silently accepted — but it is NOT a
|
|
795
|
+
// throw. Rows deleted by someone else while the walk was in flight make
|
|
796
|
+
// `collected < expected` legitimately, and failing an export over a benign
|
|
797
|
+
// concurrent edit would get the button distrusted. The caller decides; what
|
|
798
|
+
// matters is that it is told, with both numbers.
|
|
799
|
+
if (!incomplete && expected !== null && collected.length < expected) {
|
|
800
|
+
incomplete = `fetched ${collected.length} of ${expected} row(s)`
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
return {
|
|
804
|
+
rows,
|
|
805
|
+
expected,
|
|
806
|
+
complete: incomplete === null,
|
|
807
|
+
...(incomplete ? { reason: incomplete } : {}),
|
|
808
|
+
}
|
|
809
|
+
}, [source, sortField, sortDir, lookupColumns, rollupColumns, config.extraParams, config.mapApiItem])
|
|
810
|
+
|
|
811
|
+
// ── Cell Edit Handler ──
|
|
812
|
+
|
|
813
|
+
const handleCellEditSave = useCallback(
|
|
814
|
+
async (payload: CellEditSaveEvent) => {
|
|
815
|
+
if (config.cellEdit === false) return
|
|
816
|
+
|
|
817
|
+
dispatch(tableRef.current as HTMLElement, TableEvents.CELL_SAVE_START, {
|
|
818
|
+
rowIndex: payload.rowIndex,
|
|
819
|
+
colIndex: payload.colIndex,
|
|
820
|
+
} as CellSaveStartEvent)
|
|
821
|
+
|
|
822
|
+
try {
|
|
823
|
+
const rowData = tableData[payload.rowIndex]
|
|
824
|
+
if (!rowData) throw new Error('Row data not found')
|
|
825
|
+
|
|
826
|
+
const cellEditConfig = typeof config.cellEdit === 'object' ? config.cellEdit : {}
|
|
827
|
+
let hookResult: { url?: string; payload?: Record<string, unknown>; method?: string } | void = undefined
|
|
828
|
+
|
|
829
|
+
if (hooks?.beforeCellEdit) {
|
|
830
|
+
hookResult = hooks.beforeCellEdit(payload, rowData)
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
let apiUrl: string
|
|
834
|
+
if (hookResult?.url) {
|
|
835
|
+
apiUrl = hookResult.url
|
|
836
|
+
} else if (typeof cellEditConfig.url === 'function') {
|
|
837
|
+
apiUrl = cellEditConfig.url(payload, rowData)
|
|
838
|
+
} else if (cellEditConfig.url) {
|
|
839
|
+
apiUrl = `${cellEditConfig.url}/${(rowData as any)[idColumn]}`
|
|
840
|
+
} else {
|
|
841
|
+
apiUrl = `${source}/${(rowData as any)[idColumn]}`
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
let updatePayload: Record<string, unknown>
|
|
845
|
+
if (hookResult?.payload) {
|
|
846
|
+
updatePayload = hookResult.payload
|
|
847
|
+
} else if (cellEditConfig.mapPayload) {
|
|
848
|
+
updatePayload = cellEditConfig.mapPayload(payload, rowData)
|
|
849
|
+
} else {
|
|
850
|
+
updatePayload = { [payload.prop]: payload.newValue }
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
const method = hookResult?.method ?? cellEditConfig.method ?? 'PUT'
|
|
854
|
+
|
|
855
|
+
const response = await apiCall<{ error?: string }>(apiUrl, {
|
|
856
|
+
method,
|
|
857
|
+
headers: { 'Content-Type': 'application/json' },
|
|
858
|
+
body: JSON.stringify(updatePayload),
|
|
859
|
+
})
|
|
860
|
+
|
|
861
|
+
if (response.ok) {
|
|
862
|
+
dispatch(tableRef.current as HTMLElement, TableEvents.CELL_SAVE_SUCCESS, {
|
|
863
|
+
rowIndex: payload.rowIndex,
|
|
864
|
+
colIndex: payload.colIndex,
|
|
865
|
+
} as CellSaveSuccessEvent)
|
|
866
|
+
scheduleListInvalidation()
|
|
867
|
+
if (hooks?.afterMutation) await hooks.afterMutation('cellEdit', { payload, rowData })
|
|
868
|
+
} else {
|
|
869
|
+
const error = response.result?.error || 'Update failed'
|
|
870
|
+
flash(error, 'error')
|
|
871
|
+
dispatch(tableRef.current as HTMLElement, TableEvents.CELL_SAVE_ERROR, {
|
|
872
|
+
rowIndex: payload.rowIndex,
|
|
873
|
+
colIndex: payload.colIndex,
|
|
874
|
+
error,
|
|
875
|
+
} as CellSaveErrorEvent)
|
|
876
|
+
}
|
|
877
|
+
} catch (error) {
|
|
878
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
|
879
|
+
flash(errorMessage, 'error')
|
|
880
|
+
dispatch(tableRef.current as HTMLElement, TableEvents.CELL_SAVE_ERROR, {
|
|
881
|
+
rowIndex: payload.rowIndex,
|
|
882
|
+
colIndex: payload.colIndex,
|
|
883
|
+
error: errorMessage,
|
|
884
|
+
} as CellSaveErrorEvent)
|
|
885
|
+
}
|
|
886
|
+
},
|
|
887
|
+
[tableData, scheduleListInvalidation, config.cellEdit, hooks, source, idColumn, queryKeyBase]
|
|
888
|
+
)
|
|
889
|
+
|
|
890
|
+
// ── New Row Handler ──
|
|
891
|
+
|
|
892
|
+
const handleNewRowSave = useCallback(
|
|
893
|
+
async (payload: NewRowSaveEvent) => {
|
|
894
|
+
if (!config.create) return
|
|
895
|
+
|
|
896
|
+
const createConfig = typeof config.create === 'object' ? config.create : {}
|
|
897
|
+
|
|
898
|
+
// Custom handler — fully replaces built-in flow
|
|
899
|
+
if (createConfig.handler) {
|
|
900
|
+
await createConfig.handler(payload, {
|
|
901
|
+
tableRef: tableRef as React.RefObject<HTMLElement>,
|
|
902
|
+
invalidate: () => invalidateListNow(),
|
|
903
|
+
})
|
|
904
|
+
return
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
try {
|
|
908
|
+
let rowPayload: Record<string, unknown> = { ...payload.rowData }
|
|
909
|
+
|
|
910
|
+
if (hooks?.validateCreate) {
|
|
911
|
+
const validationError = hooks.validateCreate(payload.rowData)
|
|
912
|
+
if (validationError) {
|
|
913
|
+
flash(validationError, 'error')
|
|
914
|
+
dispatch(tableRef.current as HTMLElement, TableEvents.NEW_ROW_SAVE_ERROR, {
|
|
915
|
+
rowIndex: payload.rowIndex,
|
|
916
|
+
error: validationError,
|
|
917
|
+
} as NewRowSaveErrorEvent)
|
|
918
|
+
return
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
if (hooks?.beforeCreate) {
|
|
923
|
+
const mapped = await hooks.beforeCreate(payload.rowData)
|
|
924
|
+
if (mapped) rowPayload = mapped
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
if (createConfig.mapPayload) {
|
|
928
|
+
rowPayload = createConfig.mapPayload(payload.rowData)
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
const createUrl = createConfig.url ?? source
|
|
932
|
+
|
|
933
|
+
const response = await apiCall<{ id: string; error?: string }>(createUrl, {
|
|
934
|
+
method: 'POST',
|
|
935
|
+
headers: { 'Content-Type': 'application/json' },
|
|
936
|
+
body: JSON.stringify(rowPayload),
|
|
937
|
+
})
|
|
938
|
+
|
|
939
|
+
if (response.ok && response.result) {
|
|
940
|
+
dispatch(tableRef.current as HTMLElement, TableEvents.NEW_ROW_SAVE_SUCCESS, {
|
|
941
|
+
rowIndex: payload.rowIndex,
|
|
942
|
+
savedRowData: { ...payload.rowData, id: response.result.id },
|
|
943
|
+
})
|
|
944
|
+
invalidateListNow()
|
|
945
|
+
if (hooks?.afterMutation)
|
|
946
|
+
await hooks.afterMutation('create', { payload, result: response.result })
|
|
947
|
+
} else {
|
|
948
|
+
const error = response.result?.error || `Failed to create ${tableName}`
|
|
949
|
+
flash(error, 'error')
|
|
950
|
+
dispatch(tableRef.current as HTMLElement, TableEvents.NEW_ROW_SAVE_ERROR, {
|
|
951
|
+
rowIndex: payload.rowIndex,
|
|
952
|
+
error,
|
|
953
|
+
} as NewRowSaveErrorEvent)
|
|
954
|
+
}
|
|
955
|
+
} catch (error) {
|
|
956
|
+
const errorMessage = error instanceof Error ? error.message : `Failed to create ${tableName}`
|
|
957
|
+
flash(errorMessage, 'error')
|
|
958
|
+
dispatch(tableRef.current as HTMLElement, TableEvents.NEW_ROW_SAVE_ERROR, {
|
|
959
|
+
rowIndex: payload.rowIndex,
|
|
960
|
+
error: errorMessage,
|
|
961
|
+
} as NewRowSaveErrorEvent)
|
|
962
|
+
}
|
|
963
|
+
},
|
|
964
|
+
[config.create, hooks, source, tableName, queryClient, queryKeyBase]
|
|
965
|
+
)
|
|
966
|
+
|
|
967
|
+
// ── Delete Handler ──
|
|
968
|
+
|
|
969
|
+
const handleConfirmDelete = useCallback(async () => {
|
|
970
|
+
if (!pendingDelete) return
|
|
971
|
+
|
|
972
|
+
if (hooks?.beforeDelete) {
|
|
973
|
+
const proceed = await hooks.beforeDelete(pendingDelete)
|
|
974
|
+
if (!proceed) return
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
setIsDeleting(true)
|
|
978
|
+
try {
|
|
979
|
+
const deleteId = (pendingDelete as any)[idColumn]
|
|
980
|
+
let deleteUrl: string
|
|
981
|
+
if (typeof config.delete === 'string') {
|
|
982
|
+
deleteUrl = `${config.delete}/${deleteId}`
|
|
983
|
+
} else if (typeof config.delete === 'object' && config.delete.url) {
|
|
984
|
+
const urlConfig = config.delete.url
|
|
985
|
+
deleteUrl = typeof urlConfig === 'function'
|
|
986
|
+
? urlConfig(pendingDelete)
|
|
987
|
+
: `${urlConfig}/${deleteId}`
|
|
988
|
+
} else {
|
|
989
|
+
deleteUrl = `${source}/${deleteId}`
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
const response = await apiCall<{ error?: string }>(deleteUrl, { method: 'DELETE' })
|
|
993
|
+
|
|
994
|
+
if (response.ok) {
|
|
995
|
+
flash(`${tableName} deleted`, 'success')
|
|
996
|
+
invalidateListNow()
|
|
997
|
+
setPendingDelete(null)
|
|
998
|
+
if (hooks?.afterMutation) await hooks.afterMutation('delete', { row: pendingDelete })
|
|
999
|
+
} else {
|
|
1000
|
+
flash(response.result?.error || `Failed to delete ${tableName}`, 'error')
|
|
1001
|
+
}
|
|
1002
|
+
} catch (error) {
|
|
1003
|
+
flash(error instanceof Error ? error.message : `Failed to delete ${tableName}`, 'error')
|
|
1004
|
+
} finally {
|
|
1005
|
+
setIsDeleting(false)
|
|
1006
|
+
}
|
|
1007
|
+
}, [pendingDelete, hooks, idColumn, config.delete, source, tableName, queryClient, queryKeyBase])
|
|
1008
|
+
|
|
1009
|
+
// ── Bulk Delete Handler ──
|
|
1010
|
+
// Drives the DynamicTable grouped-actions bar. Deletes each selected row via
|
|
1011
|
+
// the same endpoint convention as the single-row delete, then refreshes the
|
|
1012
|
+
// list. Failures are tallied so a partial success still surfaces clearly.
|
|
1013
|
+
const buildDeleteUrl = useCallback(
|
|
1014
|
+
(id: string) => {
|
|
1015
|
+
if (typeof config.delete === 'string') return `${config.delete}/${id}`
|
|
1016
|
+
if (config.delete && typeof config.delete === 'object' && config.delete.url) {
|
|
1017
|
+
const urlConfig = config.delete.url
|
|
1018
|
+
return typeof urlConfig === 'function'
|
|
1019
|
+
? urlConfig({ [idColumn]: id } as TRow)
|
|
1020
|
+
: `${urlConfig}/${id}`
|
|
1021
|
+
}
|
|
1022
|
+
return `${source}/${id}`
|
|
1023
|
+
},
|
|
1024
|
+
[config.delete, idColumn, source]
|
|
1025
|
+
)
|
|
1026
|
+
|
|
1027
|
+
// onBulkDelete prop for DynamicTable: open the confirmation modal and return a
|
|
1028
|
+
// promise that resolves once the rows are deleted, or rejects if cancelled.
|
|
1029
|
+
const requestBulkDelete = useCallback(
|
|
1030
|
+
(ids: string[]) =>
|
|
1031
|
+
new Promise<void>((resolve, reject) => {
|
|
1032
|
+
if (!ids.length) {
|
|
1033
|
+
resolve()
|
|
1034
|
+
return
|
|
1035
|
+
}
|
|
1036
|
+
bulkResolverRef.current = { resolve, reject }
|
|
1037
|
+
setPendingBulkDelete(ids)
|
|
1038
|
+
}),
|
|
1039
|
+
[]
|
|
1040
|
+
)
|
|
1041
|
+
|
|
1042
|
+
const handleConfirmBulkDelete = useCallback(async () => {
|
|
1043
|
+
const ids = pendingBulkDelete
|
|
1044
|
+
if (!ids || !ids.length) return
|
|
1045
|
+
setIsDeleting(true)
|
|
1046
|
+
try {
|
|
1047
|
+
let deleted = 0
|
|
1048
|
+
let failed = 0
|
|
1049
|
+
for (const id of ids) {
|
|
1050
|
+
try {
|
|
1051
|
+
const response = await apiCall<{ error?: string }>(buildDeleteUrl(id), { method: 'DELETE' })
|
|
1052
|
+
if (response.ok) deleted += 1
|
|
1053
|
+
else failed += 1
|
|
1054
|
+
} catch {
|
|
1055
|
+
failed += 1
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
if (deleted > 0) flash(`${deleted} ${tableName} deleted`, 'success')
|
|
1059
|
+
if (failed > 0) flash(`Failed to delete ${failed} ${tableName}`, 'error')
|
|
1060
|
+
invalidateListNow()
|
|
1061
|
+
if (hooks?.afterMutation) await hooks.afterMutation('delete', { ids })
|
|
1062
|
+
bulkResolverRef.current?.resolve()
|
|
1063
|
+
} finally {
|
|
1064
|
+
bulkResolverRef.current = null
|
|
1065
|
+
setPendingBulkDelete(null)
|
|
1066
|
+
setIsDeleting(false)
|
|
1067
|
+
}
|
|
1068
|
+
}, [pendingBulkDelete, buildDeleteUrl, tableName, queryClient, queryKeyBase, hooks])
|
|
1069
|
+
|
|
1070
|
+
const cancelBulkDelete = useCallback(() => {
|
|
1071
|
+
bulkResolverRef.current?.reject(new Error('cancelled'))
|
|
1072
|
+
bulkResolverRef.current = null
|
|
1073
|
+
setPendingBulkDelete(null)
|
|
1074
|
+
}, [])
|
|
1075
|
+
|
|
1076
|
+
// ── Perspective Handlers ──
|
|
1077
|
+
|
|
1078
|
+
const handlePerspectiveSave = useCallback(
|
|
1079
|
+
async (payload: PerspectiveSaveEvent) => {
|
|
1080
|
+
if (!perspectivesTableId) return
|
|
1081
|
+
const settings = dynamicTableToApi(payload.perspective)
|
|
1082
|
+
// Resolve the row to update BY ID FIRST, name only as a fallback.
|
|
1083
|
+
//
|
|
1084
|
+
// Name-only matching is what made "rename in the Configure View drawer"
|
|
1085
|
+
// create a SECOND view: the panel minted a fresh client-side id, the name
|
|
1086
|
+
// had just changed, nothing matched, and the upsert inserted. An id that
|
|
1087
|
+
// is already in `savedPerspectives` is a server uuid by construction, so
|
|
1088
|
+
// it is safe to send as `perspectiveId`; a client-minted id (a brand-new
|
|
1089
|
+
// view) matches nothing and correctly falls through to insert.
|
|
1090
|
+
const existingPerspective =
|
|
1091
|
+
savedPerspectives.find((p) => p.id === payload.perspective.id) ??
|
|
1092
|
+
savedPerspectives.find((p) => p.name === payload.perspective.name)
|
|
1093
|
+
const response = await apiCall<PerspectiveSaveResponse>(`/api/perspectives/${perspectivesTableId}`, {
|
|
1094
|
+
method: 'POST',
|
|
1095
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1096
|
+
body: JSON.stringify({
|
|
1097
|
+
// The upstream validator reads `perspectiveId`, not `id` — an `id` key
|
|
1098
|
+
// is stripped by zod and the save silently falls back to an upsert by
|
|
1099
|
+
// NAME. Harmless here (same name ⇒ same row) but fatal for rename,
|
|
1100
|
+
// which changes the name and so used to CLONE the view.
|
|
1101
|
+
perspectiveId: existingPerspective?.id,
|
|
1102
|
+
name: payload.perspective.name,
|
|
1103
|
+
settings,
|
|
1104
|
+
}),
|
|
1105
|
+
})
|
|
1106
|
+
if (response.ok) {
|
|
1107
|
+
// `silent` is set for saves the user did not explicitly ask for — the
|
|
1108
|
+
// column order written back after a header drag. The write and the
|
|
1109
|
+
// refetch still happen; only the confirmation toast is dropped.
|
|
1110
|
+
if (!payload.silent) {
|
|
1111
|
+
flash(
|
|
1112
|
+
payload.perspective.isBaseView ? 'Default view saved' : 'Perspective saved',
|
|
1113
|
+
'success',
|
|
1114
|
+
)
|
|
1115
|
+
}
|
|
1116
|
+
queryClient.invalidateQueries({ queryKey: ['perspectives', perspectivesTableId] })
|
|
1117
|
+
const savedId = response.result?.perspective?.id
|
|
1118
|
+
if (savedId) setActivePerspectiveId(savedId)
|
|
1119
|
+
} else {
|
|
1120
|
+
// See `perspectiveWriteError` — the deleted-name collision is the one
|
|
1121
|
+
// failure here a user can act on, and every perspective write can hit it.
|
|
1122
|
+
flash(perspectiveWriteError(response, 'Failed to save perspective'), 'error')
|
|
1123
|
+
}
|
|
1124
|
+
},
|
|
1125
|
+
[perspectivesTableId, savedPerspectives, queryClient]
|
|
1126
|
+
)
|
|
1127
|
+
|
|
1128
|
+
const handlePerspectiveSelect = useCallback(
|
|
1129
|
+
(payload: PerspectiveSelectEvent) => {
|
|
1130
|
+
setActivePerspectiveId(payload.id)
|
|
1131
|
+
if (payload.config) {
|
|
1132
|
+
setFilters(payload.config.filters)
|
|
1133
|
+
setLookupColumns(payload.config.lookupColumns ?? [])
|
|
1134
|
+
setRollupColumns(payload.config.rollupColumns ?? [])
|
|
1135
|
+
setAggregations(payload.config.aggregations ?? [])
|
|
1136
|
+
if (payload.config.sorting.length > 0) {
|
|
1137
|
+
setSortField(payload.config.sorting[0].field)
|
|
1138
|
+
setSortDir(payload.config.sorting[0].direction)
|
|
1139
|
+
}
|
|
1140
|
+
setPage(1)
|
|
1141
|
+
} else {
|
|
1142
|
+
setFilters([])
|
|
1143
|
+
setLookupColumns([])
|
|
1144
|
+
setRollupColumns([])
|
|
1145
|
+
setAggregations([])
|
|
1146
|
+
setSortField(defaultSort?.field ?? 'id')
|
|
1147
|
+
setSortDir(defaultSort?.direction ?? 'asc')
|
|
1148
|
+
setPage(1)
|
|
1149
|
+
}
|
|
1150
|
+
},
|
|
1151
|
+
[defaultSort]
|
|
1152
|
+
)
|
|
1153
|
+
|
|
1154
|
+
const handlePerspectiveRename = useCallback(
|
|
1155
|
+
async (payload: PerspectiveRenameEvent) => {
|
|
1156
|
+
if (!perspectivesTableId) return
|
|
1157
|
+
const perspective = savedPerspectives.find((p) => p.id === payload.id)
|
|
1158
|
+
if (perspective) {
|
|
1159
|
+
// Refuse locally rather than round-trip to a 400 whose body says
|
|
1160
|
+
// nothing. The rename input caps typing at the same limit, so this only
|
|
1161
|
+
// fires for a name assembled in code (a "(copy)" suffix on an already
|
|
1162
|
+
// long name) — and when it does, it names the actual problem instead of
|
|
1163
|
+
// "Failed to rename perspective".
|
|
1164
|
+
if (payload.newName.length > PERSPECTIVE_NAME_MAX_LENGTH) {
|
|
1165
|
+
flash(
|
|
1166
|
+
`A view name can be at most ${PERSPECTIVE_NAME_MAX_LENGTH} characters. This one is ${payload.newName.length}.`,
|
|
1167
|
+
'error',
|
|
1168
|
+
)
|
|
1169
|
+
return
|
|
1170
|
+
}
|
|
1171
|
+
const settings = dynamicTableToApi(perspective)
|
|
1172
|
+
const response = await apiCall(`/api/perspectives/${perspectivesTableId}`, {
|
|
1173
|
+
method: 'POST',
|
|
1174
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1175
|
+
// `perspectiveId`, not `id` — see handlePerspectiveSave. With `id` the
|
|
1176
|
+
// rename created a SECOND view under the new name and left the old one.
|
|
1177
|
+
body: JSON.stringify({ perspectiveId: payload.id, name: payload.newName, settings }),
|
|
1178
|
+
})
|
|
1179
|
+
if (response.ok) {
|
|
1180
|
+
flash('Perspective renamed', 'success')
|
|
1181
|
+
queryClient.invalidateQueries({ queryKey: ['perspectives', perspectivesTableId] })
|
|
1182
|
+
} else {
|
|
1183
|
+
flash(perspectiveWriteError(response, 'Failed to rename perspective'), 'error')
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
},
|
|
1187
|
+
[perspectivesTableId, savedPerspectives, queryClient]
|
|
1188
|
+
)
|
|
1189
|
+
|
|
1190
|
+
/**
|
|
1191
|
+
* A6 — duplicate. A brand-new row (no `perspectiveId`) carrying the source's
|
|
1192
|
+
* settings under a new name, and explicitly NOT the default: a copy must never
|
|
1193
|
+
* hijack which view opens tomorrow.
|
|
1194
|
+
*
|
|
1195
|
+
* `publication` is dropped for the same reason, and it is not cosmetic. It
|
|
1196
|
+
* rides the settings blob as `_published`, so duplicating a view that had been
|
|
1197
|
+
* shared produced a SECOND view that reported itself as a published template —
|
|
1198
|
+
* its `⋯` menu said "Update shared template…" and the tab claimed a shelf
|
|
1199
|
+
* entry that does not exist, because duplicate never calls `applyToRoles` and
|
|
1200
|
+
* so writes no role rows at all. Every view is PRIVATE by default
|
|
1201
|
+
* (personalization model, 2026-08-03); publishing is an explicit act on the
|
|
1202
|
+
* copy, not something inherited from what it was copied from.
|
|
1203
|
+
*
|
|
1204
|
+
* `isBaseView` goes too: the base row owns the reserved `__base__` name and
|
|
1205
|
+
* there can only be one per user, so a copy carrying the flag would render as
|
|
1206
|
+
* a second "Default view".
|
|
1207
|
+
*/
|
|
1208
|
+
const handlePerspectiveDuplicate = useCallback(
|
|
1209
|
+
async (payload: PerspectiveDuplicateEvent) => {
|
|
1210
|
+
if (!perspectivesTableId) return
|
|
1211
|
+
const settings = dynamicTableToApi({
|
|
1212
|
+
...payload.perspective,
|
|
1213
|
+
name: payload.newName,
|
|
1214
|
+
isDefault: false,
|
|
1215
|
+
isBaseView: false,
|
|
1216
|
+
publication: undefined,
|
|
1217
|
+
})
|
|
1218
|
+
const response = await apiCall<PerspectiveSaveResponse>(`/api/perspectives/${perspectivesTableId}`, {
|
|
1219
|
+
method: 'POST',
|
|
1220
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1221
|
+
body: JSON.stringify({ name: payload.newName, settings, isDefault: false }),
|
|
1222
|
+
})
|
|
1223
|
+
if (response.ok) {
|
|
1224
|
+
flash('View duplicated', 'success')
|
|
1225
|
+
queryClient.invalidateQueries({ queryKey: ['perspectives', perspectivesTableId] })
|
|
1226
|
+
const savedId = response.result?.perspective?.id
|
|
1227
|
+
if (savedId) setActivePerspectiveId(savedId)
|
|
1228
|
+
} else {
|
|
1229
|
+
flash(perspectiveWriteError(response, 'Failed to duplicate view'), 'error')
|
|
1230
|
+
}
|
|
1231
|
+
},
|
|
1232
|
+
[perspectivesTableId, queryClient]
|
|
1233
|
+
)
|
|
1234
|
+
|
|
1235
|
+
/**
|
|
1236
|
+
* A4 — make a view this USER's default. `isDefault` is a first-class column
|
|
1237
|
+
* on the upstream entity scoped by `userId`; the service demotes the caller's
|
|
1238
|
+
* other views in one `nativeUpdate` and touches nobody else's rows.
|
|
1239
|
+
*/
|
|
1240
|
+
const handlePerspectiveSetDefault = useCallback(
|
|
1241
|
+
async (payload: PerspectiveSetDefaultEvent) => {
|
|
1242
|
+
if (!perspectivesTableId) return
|
|
1243
|
+
const perspective = savedPerspectives.find((p) => p.id === payload.id)
|
|
1244
|
+
if (!perspective) return
|
|
1245
|
+
const settings = dynamicTableToApi(perspective)
|
|
1246
|
+
const response = await apiCall(`/api/perspectives/${perspectivesTableId}`, {
|
|
1247
|
+
method: 'POST',
|
|
1248
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1249
|
+
body: JSON.stringify({
|
|
1250
|
+
perspectiveId: payload.id,
|
|
1251
|
+
name: perspective.name,
|
|
1252
|
+
settings,
|
|
1253
|
+
isDefault: payload.isDefault,
|
|
1254
|
+
}),
|
|
1255
|
+
})
|
|
1256
|
+
if (response.ok) {
|
|
1257
|
+
flash('Default view updated', 'success')
|
|
1258
|
+
queryClient.invalidateQueries({ queryKey: ['perspectives', perspectivesTableId] })
|
|
1259
|
+
} else {
|
|
1260
|
+
flash('Failed to set the default view', 'error')
|
|
1261
|
+
}
|
|
1262
|
+
},
|
|
1263
|
+
[perspectivesTableId, savedPerspectives, queryClient]
|
|
1264
|
+
)
|
|
1265
|
+
|
|
1266
|
+
/**
|
|
1267
|
+
* PUBLISH — make one of MY views copyable by colleagues.
|
|
1268
|
+
*
|
|
1269
|
+
* One POST does both halves, which is why it is one request and not two:
|
|
1270
|
+
* `applyToRoles` writes the role-scoped template rows, and the same call
|
|
1271
|
+
* re-saves the personal row with `_published` so the tab menu can say
|
|
1272
|
+
* "Update shared template" next time. `setRoleDefault` is pinned to `false`
|
|
1273
|
+
* — a template must never become anybody's default view. Publishing changes
|
|
1274
|
+
* nobody's screen; it only puts the view on the shelf.
|
|
1275
|
+
*
|
|
1276
|
+
* The template necessarily carries the view's own name: the upstream service
|
|
1277
|
+
* uses one `name` for both the personal row and the role rows, so allowing a
|
|
1278
|
+
* different template name here would silently RENAME the publisher's view.
|
|
1279
|
+
*/
|
|
1280
|
+
const handlePerspectivePublish = useCallback(
|
|
1281
|
+
async (payload: PerspectivePublishEvent) => {
|
|
1282
|
+
if (!perspectivesTableId) return
|
|
1283
|
+
const perspective = savedPerspectives.find((p) => p.id === payload.id)
|
|
1284
|
+
if (!perspective || !payload.roleIds.length) return
|
|
1285
|
+
const settings = dynamicTableToApi({
|
|
1286
|
+
...perspective,
|
|
1287
|
+
publication: { roleIds: payload.roleIds, publishedAt: new Date().toISOString() },
|
|
1288
|
+
})
|
|
1289
|
+
const response = await apiCall(`/api/perspectives/${perspectivesTableId}`, {
|
|
1290
|
+
method: 'POST',
|
|
1291
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1292
|
+
body: JSON.stringify({
|
|
1293
|
+
perspectiveId: payload.id,
|
|
1294
|
+
name: perspective.name,
|
|
1295
|
+
settings,
|
|
1296
|
+
applyToRoles: payload.roleIds,
|
|
1297
|
+
setRoleDefault: false,
|
|
1298
|
+
}),
|
|
1299
|
+
})
|
|
1300
|
+
if (response.ok) {
|
|
1301
|
+
flash('View shared as a template', 'success')
|
|
1302
|
+
queryClient.invalidateQueries({ queryKey: ['perspectives', perspectivesTableId] })
|
|
1303
|
+
} else {
|
|
1304
|
+
flash('Failed to share the view', 'error')
|
|
1305
|
+
}
|
|
1306
|
+
},
|
|
1307
|
+
[perspectivesTableId, savedPerspectives, queryClient],
|
|
1308
|
+
)
|
|
1309
|
+
|
|
1310
|
+
/**
|
|
1311
|
+
* COPY A TEMPLATE — the only way a shared view reaches your screen.
|
|
1312
|
+
*
|
|
1313
|
+
* Writes a brand-new PERSONAL row (no `perspectiveId`), never the default,
|
|
1314
|
+
* carrying `_origin` = which template and which version it came from. That
|
|
1315
|
+
* record is what lets a later pass ask "the shared template changed — update
|
|
1316
|
+
* yours?" instead of the two silently diverging.
|
|
1317
|
+
*/
|
|
1318
|
+
const handlePerspectiveTemplateCopy = useCallback(
|
|
1319
|
+
async (payload: PerspectiveTemplateCopyEvent) => {
|
|
1320
|
+
if (!perspectivesTableId) return
|
|
1321
|
+
const { template } = payload
|
|
1322
|
+
const settings = dynamicTableToApi({
|
|
1323
|
+
...template,
|
|
1324
|
+
name: payload.newName,
|
|
1325
|
+
isDefault: false,
|
|
1326
|
+
isBaseView: false,
|
|
1327
|
+
publication: undefined,
|
|
1328
|
+
origin: {
|
|
1329
|
+
templateId: template.id,
|
|
1330
|
+
version: template.version,
|
|
1331
|
+
name: template.name,
|
|
1332
|
+
copiedAt: new Date().toISOString(),
|
|
1333
|
+
},
|
|
1334
|
+
})
|
|
1335
|
+
const response = await apiCall<PerspectiveSaveResponse>(`/api/perspectives/${perspectivesTableId}`, {
|
|
1336
|
+
method: 'POST',
|
|
1337
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1338
|
+
body: JSON.stringify({ name: payload.newName, settings, isDefault: false }),
|
|
1339
|
+
})
|
|
1340
|
+
if (response.ok) {
|
|
1341
|
+
flash('Template copied to your views', 'success')
|
|
1342
|
+
queryClient.invalidateQueries({ queryKey: ['perspectives', perspectivesTableId] })
|
|
1343
|
+
const savedId = response.result?.perspective?.id
|
|
1344
|
+
if (savedId) setActivePerspectiveId(savedId)
|
|
1345
|
+
} else {
|
|
1346
|
+
flash(perspectiveWriteError(response, 'Failed to copy the template'), 'error')
|
|
1347
|
+
}
|
|
1348
|
+
},
|
|
1349
|
+
[perspectivesTableId, queryClient],
|
|
1350
|
+
)
|
|
1351
|
+
|
|
1352
|
+
const handlePerspectiveDelete = useCallback(
|
|
1353
|
+
async (payload: PerspectiveDeleteEvent) => {
|
|
1354
|
+
if (!perspectivesTableId) return
|
|
1355
|
+
const url = payload.hardDelete
|
|
1356
|
+
? `/api/perspectives/${perspectivesTableId}/${payload.id}?hardDelete=true`
|
|
1357
|
+
: `/api/perspectives/${perspectivesTableId}/${payload.id}`
|
|
1358
|
+
const response = await apiCall(url, { method: 'DELETE' })
|
|
1359
|
+
if (response.ok) {
|
|
1360
|
+
flash('Perspective deleted', 'success')
|
|
1361
|
+
queryClient.invalidateQueries({ queryKey: ['perspectives', perspectivesTableId] })
|
|
1362
|
+
if (activePerspectiveId === payload.id) {
|
|
1363
|
+
setActivePerspectiveId(null)
|
|
1364
|
+
setFilters([])
|
|
1365
|
+
setSortField(defaultSort?.field ?? 'id')
|
|
1366
|
+
setSortDir(defaultSort?.direction ?? 'asc')
|
|
1367
|
+
}
|
|
1368
|
+
} else {
|
|
1369
|
+
flash('Failed to delete perspective', 'error')
|
|
1370
|
+
}
|
|
1371
|
+
},
|
|
1372
|
+
[perspectivesTableId, activePerspectiveId, queryClient, defaultSort]
|
|
1373
|
+
)
|
|
1374
|
+
|
|
1375
|
+
const handlePerspectiveChange = useCallback(
|
|
1376
|
+
(payload: PerspectiveChangeEvent) => {
|
|
1377
|
+
if (payload.config.lookupColumns) {
|
|
1378
|
+
setLookupColumns(payload.config.lookupColumns)
|
|
1379
|
+
setPage(1)
|
|
1380
|
+
}
|
|
1381
|
+
// Rollups do NOT reset the page — they add a column to the rows already
|
|
1382
|
+
// on screen, they do not change which rows those are.
|
|
1383
|
+
if (payload.config.rollupColumns) {
|
|
1384
|
+
setRollupColumns(payload.config.rollupColumns)
|
|
1385
|
+
}
|
|
1386
|
+
// Aggregations do NOT reset the page — they change what the footer says,
|
|
1387
|
+
// not which rows are shown.
|
|
1388
|
+
if (payload.config.aggregations) {
|
|
1389
|
+
setAggregations(payload.config.aggregations)
|
|
1390
|
+
}
|
|
1391
|
+
// Grouping and view mode are MIRRORED, never acted on: both are folds
|
|
1392
|
+
// over the rows already fetched, so neither changes the request and
|
|
1393
|
+
// neither may reset the page. Resetting on a grouping change would throw
|
|
1394
|
+
// the user back to page 1 for a purely visual regroup.
|
|
1395
|
+
if (payload.config.grouping) {
|
|
1396
|
+
setGrouping(payload.config.grouping)
|
|
1397
|
+
}
|
|
1398
|
+
if (payload.config.viewMode) {
|
|
1399
|
+
setViewMode(payload.config.viewMode)
|
|
1400
|
+
}
|
|
1401
|
+
if (payload.config.sorting) {
|
|
1402
|
+
if (payload.config.sorting.length > 0) {
|
|
1403
|
+
const firstSort = payload.config.sorting[0]
|
|
1404
|
+
setSortField(firstSort.field)
|
|
1405
|
+
setSortDir(firstSort.direction)
|
|
1406
|
+
} else {
|
|
1407
|
+
setSortField(defaultSort?.field ?? 'id')
|
|
1408
|
+
setSortDir(defaultSort?.direction ?? 'asc')
|
|
1409
|
+
}
|
|
1410
|
+
setPage(1)
|
|
1411
|
+
}
|
|
1412
|
+
},
|
|
1413
|
+
[defaultSort]
|
|
1414
|
+
)
|
|
1415
|
+
|
|
1416
|
+
// ── Register Event Handlers ──
|
|
1417
|
+
|
|
1418
|
+
const eventHandlers = useMemo(() => {
|
|
1419
|
+
const handlers: Record<string, any> = {
|
|
1420
|
+
[TableEvents.COLUMN_SORT]: (payload: { columnName: string; direction: 'asc' | 'desc' | null }) => {
|
|
1421
|
+
setSortField(payload.columnName)
|
|
1422
|
+
setSortDir(payload.direction || 'asc')
|
|
1423
|
+
setPage(1)
|
|
1424
|
+
},
|
|
1425
|
+
[TableEvents.SEARCH]: (payload: { query: string }) => {
|
|
1426
|
+
setSearch(payload.query)
|
|
1427
|
+
setPage(1)
|
|
1428
|
+
},
|
|
1429
|
+
[TableEvents.FILTER_CHANGE]: (payload: { filters: FilterRow[] }) => {
|
|
1430
|
+
setFilters(payload.filters)
|
|
1431
|
+
setPage(1)
|
|
1432
|
+
},
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
if (config.cellEdit !== false) {
|
|
1436
|
+
handlers[TableEvents.CELL_EDIT_SAVE] = handleCellEditSave
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
if (config.create) {
|
|
1440
|
+
handlers[TableEvents.NEW_ROW_SAVE] = handleNewRowSave
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
if (perspectivesTableId) {
|
|
1444
|
+
handlers[TableEvents.PERSPECTIVE_SAVE] = handlePerspectiveSave
|
|
1445
|
+
handlers[TableEvents.PERSPECTIVE_SELECT] = handlePerspectiveSelect
|
|
1446
|
+
handlers[TableEvents.PERSPECTIVE_RENAME] = handlePerspectiveRename
|
|
1447
|
+
handlers[TableEvents.PERSPECTIVE_DELETE] = handlePerspectiveDelete
|
|
1448
|
+
handlers[TableEvents.PERSPECTIVE_CHANGE] = handlePerspectiveChange
|
|
1449
|
+
handlers[TableEvents.PERSPECTIVE_DUPLICATE] = handlePerspectiveDuplicate
|
|
1450
|
+
handlers[TableEvents.PERSPECTIVE_SET_DEFAULT] = handlePerspectiveSetDefault
|
|
1451
|
+
handlers[TableEvents.PERSPECTIVE_PUBLISH] = handlePerspectivePublish
|
|
1452
|
+
handlers[TableEvents.PERSPECTIVE_TEMPLATE_COPY] = handlePerspectiveTemplateCopy
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
return handlers
|
|
1456
|
+
}, [
|
|
1457
|
+
config.cellEdit,
|
|
1458
|
+
config.create,
|
|
1459
|
+
perspectivesTableId,
|
|
1460
|
+
handleCellEditSave,
|
|
1461
|
+
handleNewRowSave,
|
|
1462
|
+
handlePerspectiveSave,
|
|
1463
|
+
handlePerspectiveSelect,
|
|
1464
|
+
handlePerspectiveRename,
|
|
1465
|
+
handlePerspectiveDelete,
|
|
1466
|
+
handlePerspectiveChange,
|
|
1467
|
+
handlePerspectiveDuplicate,
|
|
1468
|
+
handlePerspectiveSetDefault,
|
|
1469
|
+
handlePerspectivePublish,
|
|
1470
|
+
handlePerspectiveTemplateCopy,
|
|
1471
|
+
])
|
|
1472
|
+
|
|
1473
|
+
useEventHandlers(eventHandlers, tableRef as React.RefObject<HTMLElement>)
|
|
1474
|
+
|
|
1475
|
+
// ── Filter Suggestions ──
|
|
1476
|
+
|
|
1477
|
+
const customLoadFilterSuggestions = config.loadFilterSuggestions
|
|
1478
|
+
const loadFilterSuggestions = useMemo(() => {
|
|
1479
|
+
// An explicit loader wins: it is the only way a list can suggest values for
|
|
1480
|
+
// columns that aren't plain fields on its own entity table.
|
|
1481
|
+
if (customLoadFilterSuggestions) return customLoadFilterSuggestions
|
|
1482
|
+
if (!config.filterSuggestions) return undefined
|
|
1483
|
+
const entityType = config.filterSuggestions
|
|
1484
|
+
|
|
1485
|
+
return async (field: string, query: string): Promise<string[]> => {
|
|
1486
|
+
try {
|
|
1487
|
+
const params = new URLSearchParams({ entityId: entityType, field, query: query || '' })
|
|
1488
|
+
const result = await apiCall<{ items: string[] }>(
|
|
1489
|
+
`/api/entities/filter-suggestions?${params.toString()}`,
|
|
1490
|
+
{ credentials: 'include' }
|
|
1491
|
+
)
|
|
1492
|
+
if (!result.ok || !result.result) return []
|
|
1493
|
+
return result.result.items ?? []
|
|
1494
|
+
} catch {
|
|
1495
|
+
return []
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
}, [config.filterSuggestions, customLoadFilterSuggestions])
|
|
1499
|
+
|
|
1500
|
+
// ── Build DynamicTable Props ──
|
|
1501
|
+
|
|
1502
|
+
const pagination: PaginationProps = useMemo(
|
|
1503
|
+
() => ({
|
|
1504
|
+
currentPage: page,
|
|
1505
|
+
totalPages: Math.ceil((dataQuery.data?.total || 0) / limit),
|
|
1506
|
+
limit,
|
|
1507
|
+
total: dataQuery.data?.total ?? 0,
|
|
1508
|
+
limitOptions: [25, 50, 100],
|
|
1509
|
+
onPageChange: setPage,
|
|
1510
|
+
onLimitChange: (l: number) => {
|
|
1511
|
+
setLimit(l)
|
|
1512
|
+
setPage(1)
|
|
1513
|
+
},
|
|
1514
|
+
}),
|
|
1515
|
+
[page, limit, dataQuery.data?.total]
|
|
1516
|
+
)
|
|
1517
|
+
|
|
1518
|
+
const dynamicTableProps: DynamicTableProps = useMemo(() => {
|
|
1519
|
+
const baseProps: DynamicTableProps = {
|
|
1520
|
+
tableRef,
|
|
1521
|
+
data: tableData,
|
|
1522
|
+
columns,
|
|
1523
|
+
tableName,
|
|
1524
|
+
// Stable identity for per-user column-width persistence. Reuse the
|
|
1525
|
+
// perspectives table id when present, else fall back to the entity source.
|
|
1526
|
+
tableId: storageTableId ?? `${storageScope}${source}`,
|
|
1527
|
+
idColumnName: idColumn,
|
|
1528
|
+
colHeaders: true,
|
|
1529
|
+
rowHeaders: true,
|
|
1530
|
+
pagination,
|
|
1531
|
+
// Read-only, for ONE thing: the in-grid finder's scope label says
|
|
1532
|
+
// "…in 100 loaded rows (filtered by \"abc\")". Without it, a finder count
|
|
1533
|
+
// and a SearchBar filter can be conflated while both are active.
|
|
1534
|
+
// The EFFECTIVE needle, so the label names what actually narrowed the
|
|
1535
|
+
// rows — a workspace search when one is driving this pane, this pane's
|
|
1536
|
+
// own box otherwise (identical when no workspace is involved).
|
|
1537
|
+
searchQuery: effectiveSearch,
|
|
1538
|
+
onExportAll: exportAllRows,
|
|
1539
|
+
aggregateResult: aggregateQuery.data ?? null,
|
|
1540
|
+
aggregateLoading: aggregateQuery.isFetching,
|
|
1541
|
+
aggregateError: aggregateQuery.isError,
|
|
1542
|
+
// HEDGE-119 — carry the list query's FAILURE to the grid.
|
|
1543
|
+
//
|
|
1544
|
+
// `tableData` above collapses a rejected query to `[]` (there is no
|
|
1545
|
+
// `dataQuery.data` to read items off), so without this flag the grid
|
|
1546
|
+
// cannot tell "the server refused" from "the server said zero rows" and
|
|
1547
|
+
// paints `EmptyState` for both. That is how a 500 on the invoice queue
|
|
1548
|
+
// reached an accountant as the words "Nothing here yet".
|
|
1549
|
+
//
|
|
1550
|
+
// `placeholderData` keeps the PREVIOUS page's rows visible while a
|
|
1551
|
+
// refetch fails, so this is deliberately not `|| isError` on the whole
|
|
1552
|
+
// grid: rows we still hold are still shown, and the error state only
|
|
1553
|
+
// takes over the zero-row region, which is the case that lies.
|
|
1554
|
+
loadError: dataQuery.isError,
|
|
1555
|
+
onRetryLoad: () => { void dataQuery.refetch() },
|
|
1556
|
+
...(perspectivesTableId
|
|
1557
|
+
? {
|
|
1558
|
+
savedPerspectives,
|
|
1559
|
+
activePerspectiveId,
|
|
1560
|
+
// A4/A6 — which view opens by default FOR THIS USER. Comes straight
|
|
1561
|
+
// off the perspectives index (`isDefault` is scoped by `userId`), so
|
|
1562
|
+
// the star on the tab is a per-user fact, never an org-wide one.
|
|
1563
|
+
defaultPerspectiveId: perspectivesData?.defaultPerspectiveId ?? null,
|
|
1564
|
+
// Personal views + shared templates. `canPublishTemplates` is the
|
|
1565
|
+
// server's own answer (`perspectives.role_defaults`), not a guess:
|
|
1566
|
+
// a user without the feature never sees the publish affordance, and
|
|
1567
|
+
// the endpoint would 403 them anyway.
|
|
1568
|
+
sharedTemplates,
|
|
1569
|
+
canPublishTemplates: perspectivesData?.canApplyToRoles ?? false,
|
|
1570
|
+
publishableRoles: perspectivesData?.roles ?? [],
|
|
1571
|
+
}
|
|
1572
|
+
: {}),
|
|
1573
|
+
...(loadFilterSuggestions ? { loadFilterSuggestions } : {}),
|
|
1574
|
+
...(config.loadLookupSources ? { loadLookupSources: config.loadLookupSources } : {}),
|
|
1575
|
+
...(config.loadRollupSources ? { loadRollupSources: config.loadRollupSources } : {}),
|
|
1576
|
+
...(config.delete ? { onBulkDelete: requestBulkDelete } : {}),
|
|
1577
|
+
...config.tableProps,
|
|
1578
|
+
// LAST, so it cannot be overridden: while a workspace search is driving
|
|
1579
|
+
// this pane, that pane's own search box is ignored (see `sharedSearch`).
|
|
1580
|
+
// Leaving it on screen would let a user type into a box that does
|
|
1581
|
+
// nothing — and the density argument for the bar only holds if the rows
|
|
1582
|
+
// it replaces actually go away. Absent `sharedSearch`, this spreads
|
|
1583
|
+
// nothing and `config.tableProps` still has the final word.
|
|
1584
|
+
...(sharedSearch !== undefined ? { hideSearch: true } : {}),
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
return baseProps
|
|
1588
|
+
}, [
|
|
1589
|
+
tableData,
|
|
1590
|
+
columns,
|
|
1591
|
+
tableName,
|
|
1592
|
+
idColumn,
|
|
1593
|
+
pagination,
|
|
1594
|
+
effectiveSearch,
|
|
1595
|
+
exportAllRows,
|
|
1596
|
+
aggregateQuery.data,
|
|
1597
|
+
aggregateQuery.isFetching,
|
|
1598
|
+
aggregateQuery.isError,
|
|
1599
|
+
dataQuery.isError,
|
|
1600
|
+
dataQuery.refetch,
|
|
1601
|
+
perspectivesTableId,
|
|
1602
|
+
source,
|
|
1603
|
+
savedPerspectives,
|
|
1604
|
+
activePerspectiveId,
|
|
1605
|
+
perspectivesData?.defaultPerspectiveId,
|
|
1606
|
+
perspectivesData?.canApplyToRoles,
|
|
1607
|
+
perspectivesData?.roles,
|
|
1608
|
+
sharedTemplates,
|
|
1609
|
+
loadFilterSuggestions,
|
|
1610
|
+
config.loadLookupSources,
|
|
1611
|
+
config.loadRollupSources,
|
|
1612
|
+
config.delete,
|
|
1613
|
+
requestBulkDelete,
|
|
1614
|
+
config.tableProps,
|
|
1615
|
+
sharedSearch,
|
|
1616
|
+
])
|
|
1617
|
+
|
|
1618
|
+
// ── Delete Dialog Props ──
|
|
1619
|
+
|
|
1620
|
+
const deleteConfig = config.delete
|
|
1621
|
+
const dialogConfig = useMemo(() => {
|
|
1622
|
+
if (!deleteConfig) return null
|
|
1623
|
+
if (typeof deleteConfig === 'object' && typeof deleteConfig !== 'boolean') {
|
|
1624
|
+
return deleteConfig as DynamicTablePageDeleteConfig
|
|
1625
|
+
}
|
|
1626
|
+
return {} as DynamicTablePageDeleteConfig
|
|
1627
|
+
}, [deleteConfig])
|
|
1628
|
+
|
|
1629
|
+
const cancelDelete = useCallback(() => setPendingDelete(null), [])
|
|
1630
|
+
|
|
1631
|
+
// Return JSX element (not a component) so the Dialog's element type is always
|
|
1632
|
+
// TableDeleteDialog — React keeps it mounted across state changes, preserving
|
|
1633
|
+
// animation state. Using a component (function) would cause unmount/remount on
|
|
1634
|
+
// every dep change since the function identity changes.
|
|
1635
|
+
const deleteDialog = dialogConfig ? (
|
|
1636
|
+
<>
|
|
1637
|
+
<TableDeleteDialog
|
|
1638
|
+
row={pendingDelete}
|
|
1639
|
+
isDeleting={isDeleting}
|
|
1640
|
+
onConfirm={handleConfirmDelete}
|
|
1641
|
+
onCancel={cancelDelete}
|
|
1642
|
+
restoreFocusRef={tableRef}
|
|
1643
|
+
title={dialogConfig.title}
|
|
1644
|
+
description={dialogConfig.description}
|
|
1645
|
+
nameColumn={dialogConfig.nameColumn}
|
|
1646
|
+
/>
|
|
1647
|
+
<TableDeleteDialog
|
|
1648
|
+
row={pendingBulkDelete ? ({ count: pendingBulkDelete.length } as TRow) : null}
|
|
1649
|
+
isDeleting={isDeleting}
|
|
1650
|
+
onConfirm={handleConfirmBulkDelete}
|
|
1651
|
+
onCancel={cancelBulkDelete}
|
|
1652
|
+
restoreFocusRef={tableRef}
|
|
1653
|
+
title={(row: any) => `Delete ${row?.count ?? ''} ${tableName}`.replace(/\s+/g, ' ').trim()}
|
|
1654
|
+
description={(row: any) =>
|
|
1655
|
+
`Are you sure you want to delete ${row?.count ?? 'these'} selected items? This action cannot be undone.`}
|
|
1656
|
+
/>
|
|
1657
|
+
</>
|
|
1658
|
+
) : null
|
|
1659
|
+
|
|
1660
|
+
return {
|
|
1661
|
+
props: dynamicTableProps,
|
|
1662
|
+
deleteDialog,
|
|
1663
|
+
setRowToDelete: setPendingDelete,
|
|
1664
|
+
query: dataQuery,
|
|
1665
|
+
aggregateResult: aggregateQuery.data ?? null,
|
|
1666
|
+
aggregateLoading: aggregateQuery.isFetching,
|
|
1667
|
+
aggregateError: aggregateQuery.isError,
|
|
1668
|
+
// HEDGE-119 — hosts that render their own body (split views, card modes)
|
|
1669
|
+
// need the same signal the grid now gets, so they can refuse to draw an
|
|
1670
|
+
// "empty" affordance over a request that never landed.
|
|
1671
|
+
isError: dataQuery.isError,
|
|
1672
|
+
// `!perspectiveHydrated` counts as loading. TanStack reports `isLoading`
|
|
1673
|
+
// false while a query is DISABLED (it is pending but not fetching), so
|
|
1674
|
+
// without this the host would drop its skeleton and paint an empty grid for
|
|
1675
|
+
// the one frame the list is waiting on the perspective.
|
|
1676
|
+
isLoading: (!perspectiveHydrated || dataQuery.isLoading) && !dataQuery.data,
|
|
1677
|
+
refresh: () => invalidateListNow(),
|
|
1678
|
+
state: { page, limit, search, filters, sortField, sortDir, grouping, viewMode },
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
|