@rebasepro/plugin-insights 0.0.1-canary.eae7889 → 0.0.1-canary.eb08332
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 -6
- package/README.md +115 -0
- package/dist/{plugin-insights/src/components → components}/CollectionInsightsInline.d.ts +5 -3
- package/dist/{plugin-insights/src/components → components}/HomeCardInsightSlot.d.ts +3 -2
- package/dist/{plugin-insights/src/components → components}/HomeInsightsSlot.d.ts +3 -2
- package/dist/{plugin-insights/src/components → components}/InsightWidget.d.ts +8 -4
- package/dist/{plugin-insights/src/components → components}/InsightWidgetSkeleton.d.ts +11 -2
- package/dist/{plugin-insights/src/components → components}/InsightsScorecardView.d.ts +6 -3
- package/dist/{plugin-insights/src/engine → engine}/InsightsProvider.d.ts +2 -2
- package/dist/{plugin-insights/src/engine → engine}/useInsightsData.d.ts +2 -2
- package/dist/{plugin-insights/src/index.d.ts → index.d.ts} +1 -1
- package/dist/index.es.js +587 -923
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +645 -943
- package/dist/index.umd.js.map +1 -1
- package/dist/{plugin-insights/src/types → types}/engine.d.ts +10 -1
- package/package.json +27 -18
- package/src/components/CollectionInsightsInline.tsx +10 -2
- package/src/components/HomeCardInsightSlot.tsx +6 -2
- package/src/components/HomeInsightsSlot.tsx +1 -1
- package/src/components/InsightWidget.tsx +41 -6
- package/src/components/InsightWidgetSkeleton.tsx +134 -38
- package/src/components/InsightsScorecardView.tsx +7 -4
- package/src/engine/InsightsCache.test.ts +58 -0
- package/src/engine/InsightsCache.ts +3 -2
- package/src/engine/useInsightsData.ts +10 -7
- package/src/index.ts +1 -1
- package/src/types/engine.ts +11 -1
- package/src/useInsightsPlugin.tsx +68 -65
- package/dist/common/src/collections/CollectionRegistry.d.ts +0 -56
- package/dist/common/src/collections/index.d.ts +0 -1
- package/dist/common/src/data/buildRebaseData.d.ts +0 -14
- package/dist/common/src/index.d.ts +0 -3
- package/dist/common/src/util/builders.d.ts +0 -57
- package/dist/common/src/util/callbacks.d.ts +0 -6
- package/dist/common/src/util/collections.d.ts +0 -11
- package/dist/common/src/util/common.d.ts +0 -2
- package/dist/common/src/util/conditions.d.ts +0 -26
- package/dist/common/src/util/entities.d.ts +0 -58
- package/dist/common/src/util/enums.d.ts +0 -3
- package/dist/common/src/util/index.d.ts +0 -16
- package/dist/common/src/util/navigation_from_path.d.ts +0 -34
- package/dist/common/src/util/navigation_utils.d.ts +0 -20
- package/dist/common/src/util/parent_references_from_path.d.ts +0 -6
- package/dist/common/src/util/paths.d.ts +0 -14
- package/dist/common/src/util/permissions.d.ts +0 -5
- package/dist/common/src/util/references.d.ts +0 -2
- package/dist/common/src/util/relations.d.ts +0 -22
- package/dist/common/src/util/resolutions.d.ts +0 -72
- package/dist/common/src/util/storage.d.ts +0 -24
- package/dist/core/src/components/AIIcon.d.ts +0 -16
- package/dist/core/src/components/ConfirmationDialog.d.ts +0 -9
- package/dist/core/src/components/Debug/UIReferenceView.d.ts +0 -1
- package/dist/core/src/components/Debug/UIStyleGuide.d.ts +0 -1
- package/dist/core/src/components/ErrorTooltip.d.ts +0 -2
- package/dist/core/src/components/ErrorView.d.ts +0 -21
- package/dist/core/src/components/LanguageToggle.d.ts +0 -1
- package/dist/core/src/components/LoginView/LoginView.d.ts +0 -68
- package/dist/core/src/components/LoginView/index.d.ts +0 -2
- package/dist/core/src/components/NotFoundPage.d.ts +0 -1
- package/dist/core/src/components/RebaseAuth.d.ts +0 -10
- package/dist/core/src/components/RebaseLogo.d.ts +0 -7
- package/dist/core/src/components/UnsavedChangesDialog.d.ts +0 -9
- package/dist/core/src/components/UserDisplay.d.ts +0 -7
- package/dist/core/src/components/UserSelectPopover.d.ts +0 -62
- package/dist/core/src/components/UserSettingsView.d.ts +0 -1
- package/dist/core/src/components/common/index.d.ts +0 -6
- package/dist/core/src/components/common/table_height.d.ts +0 -5
- package/dist/core/src/components/common/types.d.ts +0 -63
- package/dist/core/src/components/common/useColumnsIds.d.ts +0 -9
- package/dist/core/src/components/common/useDataTableController.d.ts +0 -45
- package/dist/core/src/components/common/useDebouncedData.d.ts +0 -9
- package/dist/core/src/components/common/useScrollRestoration.d.ts +0 -14
- package/dist/core/src/components/index.d.ts +0 -16
- package/dist/core/src/contexts/AdminModeController.d.ts +0 -4
- package/dist/core/src/contexts/AnalyticsContext.d.ts +0 -3
- package/dist/core/src/contexts/AuthControllerContext.d.ts +0 -3
- package/dist/core/src/contexts/CustomizationControllerContext.d.ts +0 -3
- package/dist/core/src/contexts/DataDriverContext.d.ts +0 -3
- package/dist/core/src/contexts/DatabaseAdminContext.d.ts +0 -3
- package/dist/core/src/contexts/DialogsProvider.d.ts +0 -4
- package/dist/core/src/contexts/EffectiveRoleController.d.ts +0 -4
- package/dist/core/src/contexts/InternalUserManagementContext.d.ts +0 -3
- package/dist/core/src/contexts/ModeController.d.ts +0 -4
- package/dist/core/src/contexts/RebaseClientInstanceContext.d.ts +0 -6
- package/dist/core/src/contexts/RebaseDataContext.d.ts +0 -3
- package/dist/core/src/contexts/SnackbarProvider.d.ts +0 -2
- package/dist/core/src/contexts/StorageSourceContext.d.ts +0 -3
- package/dist/core/src/contexts/UserConfigurationPersistenceContext.d.ts +0 -3
- package/dist/core/src/contexts/index.d.ts +0 -13
- package/dist/core/src/core/PluginLifecycleManager.d.ts +0 -17
- package/dist/core/src/core/PluginProviderStack.d.ts +0 -21
- package/dist/core/src/core/Rebase.d.ts +0 -14
- package/dist/core/src/core/RebaseProps.d.ts +0 -136
- package/dist/core/src/core/RebaseRouter.d.ts +0 -4
- package/dist/core/src/core/RebaseRoutes.d.ts +0 -17
- package/dist/core/src/core/index.d.ts +0 -4
- package/dist/core/src/hooks/ApiConfigContext.d.ts +0 -24
- package/dist/core/src/hooks/data/delete.d.ts +0 -31
- package/dist/core/src/hooks/data/save.d.ts +0 -34
- package/dist/core/src/hooks/data/useCollectionFetch.d.ts +0 -51
- package/dist/core/src/hooks/data/useData.d.ts +0 -13
- package/dist/core/src/hooks/data/useDataOrder.d.ts +0 -12
- package/dist/core/src/hooks/data/useEntityFetch.d.ts +0 -38
- package/dist/core/src/hooks/data/useRelationSelector.d.ts +0 -52
- package/dist/core/src/hooks/data/useUserSelector.d.ts +0 -31
- package/dist/core/src/hooks/index.d.ts +0 -37
- package/dist/core/src/hooks/useAdminModeController.d.ts +0 -19
- package/dist/core/src/hooks/useAnalyticsController.d.ts +0 -5
- package/dist/core/src/hooks/useAuthController.d.ts +0 -11
- package/dist/core/src/hooks/useAuthSubscription.d.ts +0 -2
- package/dist/core/src/hooks/useBackendStorageSource.d.ts +0 -30
- package/dist/core/src/hooks/useBridgeRegistration.d.ts +0 -18
- package/dist/core/src/hooks/useBrowserTitleAndIcon.d.ts +0 -6
- package/dist/core/src/hooks/useBuildAdminModeController.d.ts +0 -6
- package/dist/core/src/hooks/useBuildEffectiveRoleController.d.ts +0 -8
- package/dist/core/src/hooks/useBuildLocalConfigurationPersistence.d.ts +0 -2
- package/dist/core/src/hooks/useBuildModeController.d.ts +0 -6
- package/dist/core/src/hooks/useClipboard.d.ts +0 -57
- package/dist/core/src/hooks/useCollapsedGroups.d.ts +0 -12
- package/dist/core/src/hooks/useCustomizationController.d.ts +0 -11
- package/dist/core/src/hooks/useDialogsController.d.ts +0 -11
- package/dist/core/src/hooks/useEffectiveRoleController.d.ts +0 -7
- package/dist/core/src/hooks/useInternalUserManagementController.d.ts +0 -12
- package/dist/core/src/hooks/useLargeLayout.d.ts +0 -1
- package/dist/core/src/hooks/useModeController.d.ts +0 -19
- package/dist/core/src/hooks/usePermissions.d.ts +0 -12
- package/dist/core/src/hooks/useRebaseClient.d.ts +0 -5
- package/dist/core/src/hooks/useRebaseContext.d.ts +0 -11
- package/dist/core/src/hooks/useRebaseRegistry.d.ts +0 -34
- package/dist/core/src/hooks/useSlot.d.ts +0 -18
- package/dist/core/src/hooks/useSnackbarController.d.ts +0 -20
- package/dist/core/src/hooks/useStorageSource.d.ts +0 -7
- package/dist/core/src/hooks/useStudioBridge.d.ts +0 -91
- package/dist/core/src/hooks/useTranslation.d.ts +0 -17
- package/dist/core/src/hooks/useUnsavedChangesDialog.d.ts +0 -12
- package/dist/core/src/hooks/useUserConfigurationPersistence.d.ts +0 -8
- package/dist/core/src/hooks/useValidateAuthenticator.d.ts +0 -21
- package/dist/core/src/i18n/RebaseI18nProvider.d.ts +0 -33
- package/dist/core/src/index.d.ts +0 -15
- package/dist/core/src/internal/common.d.ts +0 -3
- package/dist/core/src/internal/useRestoreScroll.d.ts +0 -6
- package/dist/core/src/locales/de.d.ts +0 -2
- package/dist/core/src/locales/en.d.ts +0 -10
- package/dist/core/src/locales/es.d.ts +0 -10
- package/dist/core/src/locales/fr.d.ts +0 -2
- package/dist/core/src/locales/hi.d.ts +0 -2
- package/dist/core/src/locales/it.d.ts +0 -2
- package/dist/core/src/locales/pt.d.ts +0 -7
- package/dist/core/src/util/constants.d.ts +0 -1
- package/dist/core/src/util/createFormexStub.d.ts +0 -2
- package/dist/core/src/util/entity_cache.d.ts +0 -27
- package/dist/core/src/util/enums.d.ts +0 -5
- package/dist/core/src/util/icon_list.d.ts +0 -5
- package/dist/core/src/util/icon_synonyms.d.ts +0 -1
- package/dist/core/src/util/icons.d.ts +0 -20
- package/dist/core/src/util/index.d.ts +0 -10
- package/dist/core/src/util/previews.d.ts +0 -4
- package/dist/core/src/util/useStorageUploadController.d.ts +0 -38
- package/dist/core/src/util/useTraceUpdate.d.ts +0 -2
- package/dist/formex/src/Field.d.ts +0 -52
- package/dist/formex/src/Formex.d.ts +0 -7
- package/dist/formex/src/index.d.ts +0 -5
- package/dist/formex/src/types.d.ts +0 -40
- package/dist/formex/src/useCreateFormex.d.ts +0 -14
- package/dist/formex/src/utils.d.ts +0 -16
- package/dist/types/src/controllers/analytics_controller.d.ts +0 -7
- package/dist/types/src/controllers/auth.d.ts +0 -119
- package/dist/types/src/controllers/client.d.ts +0 -170
- package/dist/types/src/controllers/collection_registry.d.ts +0 -45
- package/dist/types/src/controllers/customization_controller.d.ts +0 -60
- package/dist/types/src/controllers/data.d.ts +0 -168
- package/dist/types/src/controllers/data_driver.d.ts +0 -160
- package/dist/types/src/controllers/database_admin.d.ts +0 -11
- package/dist/types/src/controllers/dialogs_controller.d.ts +0 -36
- package/dist/types/src/controllers/effective_role.d.ts +0 -4
- package/dist/types/src/controllers/email.d.ts +0 -34
- package/dist/types/src/controllers/index.d.ts +0 -18
- package/dist/types/src/controllers/local_config_persistence.d.ts +0 -20
- package/dist/types/src/controllers/navigation.d.ts +0 -213
- package/dist/types/src/controllers/registry.d.ts +0 -54
- package/dist/types/src/controllers/side_dialogs_controller.d.ts +0 -67
- package/dist/types/src/controllers/side_entity_controller.d.ts +0 -90
- package/dist/types/src/controllers/snackbar.d.ts +0 -24
- package/dist/types/src/controllers/storage.d.ts +0 -171
- package/dist/types/src/index.d.ts +0 -4
- package/dist/types/src/rebase_context.d.ts +0 -105
- package/dist/types/src/types/backend.d.ts +0 -536
- package/dist/types/src/types/builders.d.ts +0 -15
- package/dist/types/src/types/chips.d.ts +0 -5
- package/dist/types/src/types/collections.d.ts +0 -856
- package/dist/types/src/types/cron.d.ts +0 -102
- package/dist/types/src/types/data_source.d.ts +0 -64
- package/dist/types/src/types/entities.d.ts +0 -145
- package/dist/types/src/types/entity_actions.d.ts +0 -98
- package/dist/types/src/types/entity_callbacks.d.ts +0 -173
- package/dist/types/src/types/entity_link_builder.d.ts +0 -7
- package/dist/types/src/types/entity_overrides.d.ts +0 -10
- package/dist/types/src/types/entity_views.d.ts +0 -61
- package/dist/types/src/types/export_import.d.ts +0 -21
- package/dist/types/src/types/index.d.ts +0 -23
- package/dist/types/src/types/locales.d.ts +0 -4
- package/dist/types/src/types/modify_collections.d.ts +0 -5
- package/dist/types/src/types/plugins.d.ts +0 -279
- package/dist/types/src/types/properties.d.ts +0 -1176
- package/dist/types/src/types/property_config.d.ts +0 -70
- package/dist/types/src/types/relations.d.ts +0 -336
- package/dist/types/src/types/slots.d.ts +0 -252
- package/dist/types/src/types/translations.d.ts +0 -870
- package/dist/types/src/types/user_management_delegate.d.ts +0 -121
- package/dist/types/src/types/websockets.d.ts +0 -78
- package/dist/types/src/users/index.d.ts +0 -2
- package/dist/types/src/users/roles.d.ts +0 -22
- package/dist/types/src/users/user.d.ts +0 -46
- package/dist/ui/src/components/Alert.d.ts +0 -12
- package/dist/ui/src/components/Autocomplete.d.ts +0 -21
- package/dist/ui/src/components/Avatar.d.ts +0 -11
- package/dist/ui/src/components/Badge.d.ts +0 -8
- package/dist/ui/src/components/BooleanSwitch.d.ts +0 -14
- package/dist/ui/src/components/BooleanSwitchWithLabel.d.ts +0 -17
- package/dist/ui/src/components/Button.d.ts +0 -14
- package/dist/ui/src/components/Card.d.ts +0 -9
- package/dist/ui/src/components/CenteredView.d.ts +0 -9
- package/dist/ui/src/components/Checkbox.d.ts +0 -13
- package/dist/ui/src/components/Chip.d.ts +0 -26
- package/dist/ui/src/components/CircularProgress.d.ts +0 -5
- package/dist/ui/src/components/CircularProgressCenter.d.ts +0 -11
- package/dist/ui/src/components/Collapse.d.ts +0 -9
- package/dist/ui/src/components/ColorPicker.d.ts +0 -30
- package/dist/ui/src/components/Container.d.ts +0 -8
- package/dist/ui/src/components/DateTimeField.d.ts +0 -24
- package/dist/ui/src/components/DebouncedTextField.d.ts +0 -2
- package/dist/ui/src/components/Dialog.d.ts +0 -39
- package/dist/ui/src/components/DialogActions.d.ts +0 -7
- package/dist/ui/src/components/DialogContent.d.ts +0 -7
- package/dist/ui/src/components/DialogTitle.d.ts +0 -10
- package/dist/ui/src/components/ErrorBoundary.d.ts +0 -11
- package/dist/ui/src/components/ExpandablePanel.d.ts +0 -12
- package/dist/ui/src/components/FileUpload.d.ts +0 -23
- package/dist/ui/src/components/IconButton.d.ts +0 -12
- package/dist/ui/src/components/InfoLabel.d.ts +0 -5
- package/dist/ui/src/components/InputLabel.d.ts +0 -11
- package/dist/ui/src/components/Label.d.ts +0 -7
- package/dist/ui/src/components/LoadingButton.d.ts +0 -7
- package/dist/ui/src/components/Markdown.d.ts +0 -10
- package/dist/ui/src/components/Menu.d.ts +0 -23
- package/dist/ui/src/components/Menubar.d.ts +0 -80
- package/dist/ui/src/components/MultiSelect.d.ts +0 -48
- package/dist/ui/src/components/Paper.d.ts +0 -6
- package/dist/ui/src/components/Popover.d.ts +0 -24
- package/dist/ui/src/components/RadioGroup.d.ts +0 -28
- package/dist/ui/src/components/ResizablePanels.d.ts +0 -18
- package/dist/ui/src/components/SearchBar.d.ts +0 -22
- package/dist/ui/src/components/Select.d.ts +0 -43
- package/dist/ui/src/components/Separator.d.ts +0 -5
- package/dist/ui/src/components/Sheet.d.ts +0 -22
- package/dist/ui/src/components/Skeleton.d.ts +0 -6
- package/dist/ui/src/components/Slider.d.ts +0 -21
- package/dist/ui/src/components/Table.d.ts +0 -34
- package/dist/ui/src/components/Tabs.d.ts +0 -19
- package/dist/ui/src/components/TextField.d.ts +0 -58
- package/dist/ui/src/components/TextareaAutosize.d.ts +0 -43
- package/dist/ui/src/components/ToggleButtonGroup.d.ts +0 -30
- package/dist/ui/src/components/Tooltip.d.ts +0 -19
- package/dist/ui/src/components/Typography.d.ts +0 -36
- package/dist/ui/src/components/VirtualTable/VirtualTable.d.ts +0 -11
- package/dist/ui/src/components/VirtualTable/VirtualTableCell.d.ts +0 -21
- package/dist/ui/src/components/VirtualTable/VirtualTableHeader.d.ts +0 -29
- package/dist/ui/src/components/VirtualTable/VirtualTableHeaderRow.d.ts +0 -2
- package/dist/ui/src/components/VirtualTable/VirtualTableProps.d.ts +0 -243
- package/dist/ui/src/components/VirtualTable/VirtualTableRow.d.ts +0 -3
- package/dist/ui/src/components/VirtualTable/index.d.ts +0 -3
- package/dist/ui/src/components/VirtualTable/types.d.ts +0 -38
- package/dist/ui/src/components/common/SelectInputLabel.d.ts +0 -5
- package/dist/ui/src/components/index.d.ts +0 -53
- package/dist/ui/src/hooks/PortalContainerContext.d.ts +0 -31
- package/dist/ui/src/hooks/index.d.ts +0 -6
- package/dist/ui/src/hooks/useDebounceCallback.d.ts +0 -1
- package/dist/ui/src/hooks/useDebounceValue.d.ts +0 -1
- package/dist/ui/src/hooks/useDebouncedCallback.d.ts +0 -1
- package/dist/ui/src/hooks/useInjectStyles.d.ts +0 -7
- package/dist/ui/src/hooks/useOutsideAlerter.d.ts +0 -5
- package/dist/ui/src/icons/GitHubIcon.d.ts +0 -2
- package/dist/ui/src/icons/HandleIcon.d.ts +0 -1
- package/dist/ui/src/icons/Icon.d.ts +0 -20
- package/dist/ui/src/icons/cool_icon_keys.d.ts +0 -1
- package/dist/ui/src/icons/icon_keys.d.ts +0 -1
- package/dist/ui/src/icons/index.d.ts +0 -6
- package/dist/ui/src/index.d.ts +0 -5
- package/dist/ui/src/styles.d.ts +0 -12
- package/dist/ui/src/util/chip_colors.d.ts +0 -4
- package/dist/ui/src/util/cls.d.ts +0 -2
- package/dist/ui/src/util/debounce.d.ts +0 -10
- package/dist/ui/src/util/hash.d.ts +0 -1
- package/dist/ui/src/util/index.d.ts +0 -4
- package/dist/ui/src/util/key_to_icon_component.d.ts +0 -1
- /package/dist/{plugin-insights/src/engine → engine}/InsightsCache.d.ts +0 -0
- /package/dist/{plugin-insights/src/types → types}/index.d.ts +0 -0
- /package/dist/{plugin-insights/src/types → types}/widgets.d.ts +0 -0
- /package/dist/{plugin-insights/src/useInsightsPlugin.d.ts → useInsightsPlugin.d.ts} +0 -0
package/dist/index.es.js
CHANGED
|
@@ -1,945 +1,609 @@
|
|
|
1
|
+
import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
1
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
import React, { createContext, useContext, useState, useEffect, useRef } from "react";
|
|
4
|
-
import { useAuthController, getIcon } from "@rebasepro/core";
|
|
3
|
+
import { getIcon, useAuthController } from "@rebasepro/app";
|
|
5
4
|
import { cls, defaultBorderMixin } from "@rebasepro/ui";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
$[3] = t4;
|
|
70
|
-
} else {
|
|
71
|
-
t4 = $[3];
|
|
72
|
-
}
|
|
73
|
-
t3 = t4;
|
|
74
|
-
const value = t3;
|
|
75
|
-
let t5;
|
|
76
|
-
if ($[4] !== children || $[5] !== value) {
|
|
77
|
-
t5 = /* @__PURE__ */ jsx(InsightsContext.Provider, { value, children });
|
|
78
|
-
$[4] = children;
|
|
79
|
-
$[5] = value;
|
|
80
|
-
$[6] = t5;
|
|
81
|
-
} else {
|
|
82
|
-
t5 = $[6];
|
|
83
|
-
}
|
|
84
|
-
return t5;
|
|
5
|
+
//#region src/engine/InsightsCache.ts
|
|
6
|
+
/**
|
|
7
|
+
* In-memory cache for insight query results.
|
|
8
|
+
* Supports TTL-based expiry and inflight request deduplication
|
|
9
|
+
* to prevent redundant network requests when multiple widgets
|
|
10
|
+
* share the same query.
|
|
11
|
+
*/
|
|
12
|
+
var InsightsCache = class {
|
|
13
|
+
ttl;
|
|
14
|
+
cache = /* @__PURE__ */ new Map();
|
|
15
|
+
inflight = /* @__PURE__ */ new Map();
|
|
16
|
+
constructor(ttl = 6e4) {
|
|
17
|
+
this.ttl = ttl;
|
|
18
|
+
}
|
|
19
|
+
get(key) {
|
|
20
|
+
const entry = this.cache.get(key);
|
|
21
|
+
if (!entry) return null;
|
|
22
|
+
if (Date.now() - entry.timestamp > this.ttl) {
|
|
23
|
+
this.cache.delete(key);
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
return entry.data;
|
|
27
|
+
}
|
|
28
|
+
set(key, data) {
|
|
29
|
+
this.cache.set(key, {
|
|
30
|
+
data,
|
|
31
|
+
timestamp: Date.now()
|
|
32
|
+
});
|
|
33
|
+
this.inflight.delete(key);
|
|
34
|
+
}
|
|
35
|
+
getInflight(key) {
|
|
36
|
+
return this.inflight.get(key) ?? null;
|
|
37
|
+
}
|
|
38
|
+
setInflight(key, promise) {
|
|
39
|
+
this.inflight.set(key, promise);
|
|
40
|
+
}
|
|
41
|
+
invalidate(key) {
|
|
42
|
+
if (key) {
|
|
43
|
+
this.cache.delete(key);
|
|
44
|
+
this.inflight.delete(key);
|
|
45
|
+
} else {
|
|
46
|
+
this.cache.clear();
|
|
47
|
+
this.inflight.clear();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region src/engine/InsightsProvider.tsx
|
|
53
|
+
var InsightsContext = createContext(null);
|
|
54
|
+
/**
|
|
55
|
+
* Root-level provider for the insights data engine.
|
|
56
|
+
* Injected automatically by the plugin via `providers: [{ scope: "root" }]`.
|
|
57
|
+
*
|
|
58
|
+
* Manages a single `InsightsCache` instance shared by all insight widgets
|
|
59
|
+
* for TTL-based caching and inflight request deduplication.
|
|
60
|
+
*/
|
|
61
|
+
function InsightsProvider({ cacheTTL, children }) {
|
|
62
|
+
const cache = useMemo(() => new InsightsCache(cacheTTL), [cacheTTL]);
|
|
63
|
+
const value = useMemo(() => ({ cache }), [cache]);
|
|
64
|
+
return /* @__PURE__ */ jsx(InsightsContext.Provider, {
|
|
65
|
+
value,
|
|
66
|
+
children
|
|
67
|
+
});
|
|
85
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Access the insights cache (for advanced usage).
|
|
71
|
+
* Returns null when called outside of an `InsightsProvider`
|
|
72
|
+
* (e.g. during auth-loading phase before plugin providers mount).
|
|
73
|
+
*/
|
|
86
74
|
function useInsightsEngine() {
|
|
87
|
-
|
|
75
|
+
return useContext(InsightsContext);
|
|
88
76
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
$[3] = definition;
|
|
162
|
-
$[4] = t0;
|
|
163
|
-
} else {
|
|
164
|
-
t0 = $[4];
|
|
165
|
-
}
|
|
166
|
-
let t1;
|
|
167
|
-
if ($[5] !== authReady || $[6] !== cache || $[7] !== cacheKey || $[8] !== collectionSlug || $[9] !== definition.data || $[10] !== definition.id) {
|
|
168
|
-
t1 = [definition.id, definition.data, collectionSlug, cacheKey, cache, authReady];
|
|
169
|
-
$[5] = authReady;
|
|
170
|
-
$[6] = cache;
|
|
171
|
-
$[7] = cacheKey;
|
|
172
|
-
$[8] = collectionSlug;
|
|
173
|
-
$[9] = definition.data;
|
|
174
|
-
$[10] = definition.id;
|
|
175
|
-
$[11] = t1;
|
|
176
|
-
} else {
|
|
177
|
-
t1 = $[11];
|
|
178
|
-
}
|
|
179
|
-
useEffect(t0, t1);
|
|
180
|
-
let t2;
|
|
181
|
-
if ($[12] !== data || $[13] !== error || $[14] !== loading) {
|
|
182
|
-
t2 = {
|
|
183
|
-
data,
|
|
184
|
-
loading,
|
|
185
|
-
error
|
|
186
|
-
};
|
|
187
|
-
$[12] = data;
|
|
188
|
-
$[13] = error;
|
|
189
|
-
$[14] = loading;
|
|
190
|
-
$[15] = t2;
|
|
191
|
-
} else {
|
|
192
|
-
t2 = $[15];
|
|
193
|
-
}
|
|
194
|
-
return t2;
|
|
77
|
+
//#endregion
|
|
78
|
+
//#region src/engine/useInsightsData.ts
|
|
79
|
+
/**
|
|
80
|
+
* Hook that fetches and caches data for a single insight definition.
|
|
81
|
+
*
|
|
82
|
+
* Calls the definition's own `data()` callback and manages:
|
|
83
|
+
* - TTL-based caching via InsightsCache
|
|
84
|
+
* - Inflight request deduplication (multiple mounts of the same widget)
|
|
85
|
+
* - Loading and error state management
|
|
86
|
+
*
|
|
87
|
+
* @param definition - The insight to fetch data for
|
|
88
|
+
* @param collectionSlug - Optional collection context for cache key scoping
|
|
89
|
+
*/
|
|
90
|
+
function useInsightsData(definition, context) {
|
|
91
|
+
const cache = useInsightsEngine()?.cache ?? null;
|
|
92
|
+
const { initialLoading, authLoading, user, loginSkipped } = useAuthController();
|
|
93
|
+
const authReady = !initialLoading && !authLoading && (Boolean(user) || loginSkipped);
|
|
94
|
+
const [data, setData] = useState(null);
|
|
95
|
+
const [loading, setLoading] = useState(true);
|
|
96
|
+
const [error, setError] = useState(null);
|
|
97
|
+
const cacheKey = `${definition.id}:${context.path ?? context.collectionSlug ?? "global"}`;
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
if (!authReady || !cache) return;
|
|
100
|
+
let cancelled = false;
|
|
101
|
+
const cached = cache.get(cacheKey);
|
|
102
|
+
if (cached) {
|
|
103
|
+
setData(cached);
|
|
104
|
+
setLoading(false);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const inflight = cache.getInflight(cacheKey);
|
|
108
|
+
if (inflight) {
|
|
109
|
+
setLoading(true);
|
|
110
|
+
inflight.then((result) => {
|
|
111
|
+
if (!cancelled) setData(result);
|
|
112
|
+
}).catch((err) => {
|
|
113
|
+
if (!cancelled) setError(err instanceof Error ? err : new Error(String(err)));
|
|
114
|
+
}).finally(() => {
|
|
115
|
+
if (!cancelled) setLoading(false);
|
|
116
|
+
});
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
setLoading(true);
|
|
120
|
+
setError(null);
|
|
121
|
+
const promise = definition.data(context);
|
|
122
|
+
cache.setInflight(cacheKey, promise);
|
|
123
|
+
promise.then((result) => {
|
|
124
|
+
cache.set(cacheKey, result);
|
|
125
|
+
if (!cancelled) setData(result);
|
|
126
|
+
}).catch((err) => {
|
|
127
|
+
cache.invalidate(cacheKey);
|
|
128
|
+
if (!cancelled) setError(err instanceof Error ? err : new Error(String(err)));
|
|
129
|
+
}).finally(() => {
|
|
130
|
+
if (!cancelled) setLoading(false);
|
|
131
|
+
});
|
|
132
|
+
return () => {
|
|
133
|
+
cancelled = true;
|
|
134
|
+
};
|
|
135
|
+
}, [
|
|
136
|
+
definition.id,
|
|
137
|
+
definition.data,
|
|
138
|
+
context.path,
|
|
139
|
+
context.collectionSlug,
|
|
140
|
+
cacheKey,
|
|
141
|
+
cache,
|
|
142
|
+
authReady
|
|
143
|
+
]);
|
|
144
|
+
return {
|
|
145
|
+
data,
|
|
146
|
+
loading,
|
|
147
|
+
error
|
|
148
|
+
};
|
|
195
149
|
}
|
|
150
|
+
//#endregion
|
|
151
|
+
//#region src/components/InsightsScorecardView.tsx
|
|
196
152
|
function formatNumber(value, format) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
if (format?.showSign && value > 0) {
|
|
209
|
-
formatted = "+" + formatted;
|
|
210
|
-
}
|
|
211
|
-
return formatted;
|
|
153
|
+
if (value === null || value === void 0) return "N/A";
|
|
154
|
+
const options = {
|
|
155
|
+
style: format?.style ?? "decimal",
|
|
156
|
+
notation: format?.notation ?? "standard",
|
|
157
|
+
maximumFractionDigits: format?.decimals ?? 1,
|
|
158
|
+
minimumFractionDigits: format?.decimals ?? 1
|
|
159
|
+
};
|
|
160
|
+
if (format?.style === "currency") options.currency = format.currency ?? "USD";
|
|
161
|
+
let formatted = new Intl.NumberFormat("en-US", options).format(value);
|
|
162
|
+
if (format?.showSign && value > 0) formatted = "+" + formatted;
|
|
163
|
+
return formatted;
|
|
212
164
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
$[10] = t82;
|
|
302
|
-
} else {
|
|
303
|
-
t82 = $[10];
|
|
304
|
-
}
|
|
305
|
-
comparisonElement = t82;
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
const isSmall = compact || containerWidth !== null && containerWidth < 200;
|
|
309
|
-
let t6;
|
|
310
|
-
if ($[11] !== config.icon || $[12] !== isSmall) {
|
|
311
|
-
t6 = config.icon ? getIcon(config.icon, "text-surface-400 dark:text-surface-500", void 0, isSmall ? 14 : 18) : null;
|
|
312
|
-
$[11] = config.icon;
|
|
313
|
-
$[12] = isSmall;
|
|
314
|
-
$[13] = t6;
|
|
315
|
-
} else {
|
|
316
|
-
t6 = $[13];
|
|
317
|
-
}
|
|
318
|
-
const iconElement = t6;
|
|
319
|
-
if (compact) {
|
|
320
|
-
let t72;
|
|
321
|
-
if ($[14] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
322
|
-
t72 = cls("flex flex-col gap-0.5 px-2.5 py-2 rounded-md bg-transparent border min-w-0", defaultBorderMixin);
|
|
323
|
-
$[14] = t72;
|
|
324
|
-
} else {
|
|
325
|
-
t72 = $[14];
|
|
326
|
-
}
|
|
327
|
-
let t82;
|
|
328
|
-
if ($[15] !== title) {
|
|
329
|
-
t82 = /* @__PURE__ */ jsx("span", { className: "text-[10px] uppercase tracking-wider text-surface-400 dark:text-surface-500 truncate", children: title });
|
|
330
|
-
$[15] = title;
|
|
331
|
-
$[16] = t82;
|
|
332
|
-
} else {
|
|
333
|
-
t82 = $[16];
|
|
334
|
-
}
|
|
335
|
-
let t92;
|
|
336
|
-
if ($[17] !== formattedValue) {
|
|
337
|
-
t92 = /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold tabular-nums text-surface-800 dark:text-surface-100", children: formattedValue });
|
|
338
|
-
$[17] = formattedValue;
|
|
339
|
-
$[18] = t92;
|
|
340
|
-
} else {
|
|
341
|
-
t92 = $[18];
|
|
342
|
-
}
|
|
343
|
-
let t102;
|
|
344
|
-
if ($[19] !== comparisonElement || $[20] !== t92) {
|
|
345
|
-
t102 = /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-1.5", children: [
|
|
346
|
-
t92,
|
|
347
|
-
comparisonElement
|
|
348
|
-
] });
|
|
349
|
-
$[19] = comparisonElement;
|
|
350
|
-
$[20] = t92;
|
|
351
|
-
$[21] = t102;
|
|
352
|
-
} else {
|
|
353
|
-
t102 = $[21];
|
|
354
|
-
}
|
|
355
|
-
let t112;
|
|
356
|
-
if ($[22] !== t102 || $[23] !== t82) {
|
|
357
|
-
t112 = /* @__PURE__ */ jsxs("div", { className: t72, children: [
|
|
358
|
-
t82,
|
|
359
|
-
t102
|
|
360
|
-
] });
|
|
361
|
-
$[22] = t102;
|
|
362
|
-
$[23] = t82;
|
|
363
|
-
$[24] = t112;
|
|
364
|
-
} else {
|
|
365
|
-
t112 = $[24];
|
|
366
|
-
}
|
|
367
|
-
return t112;
|
|
368
|
-
}
|
|
369
|
-
let t7;
|
|
370
|
-
if ($[25] !== embedded || $[26] !== isSmall) {
|
|
371
|
-
t7 = embedded ? `flex flex-col min-w-0 h-full ${isSmall ? "px-3.5 py-3" : "px-5 py-4"}` : cls("rounded-lg flex flex-col min-w-0 bg-transparent border", defaultBorderMixin, isSmall ? "px-3.5 py-3" : "px-5 py-4");
|
|
372
|
-
$[25] = embedded;
|
|
373
|
-
$[26] = isSmall;
|
|
374
|
-
$[27] = t7;
|
|
375
|
-
} else {
|
|
376
|
-
t7 = $[27];
|
|
377
|
-
}
|
|
378
|
-
const baseClass = t7;
|
|
379
|
-
let t8;
|
|
380
|
-
if ($[28] !== embedded || $[29] !== isSmall) {
|
|
381
|
-
t8 = embedded ? void 0 : {
|
|
382
|
-
minHeight: isSmall ? 68 : 92
|
|
383
|
-
};
|
|
384
|
-
$[28] = embedded;
|
|
385
|
-
$[29] = isSmall;
|
|
386
|
-
$[30] = t8;
|
|
387
|
-
} else {
|
|
388
|
-
t8 = $[30];
|
|
389
|
-
}
|
|
390
|
-
const t9 = `flex items-center justify-between ${isSmall ? "mb-1" : "mb-2"}`;
|
|
391
|
-
const t10 = `font-medium leading-snug truncate text-surface-500 dark:text-surface-400 ${isSmall ? "text-[11px]" : "text-xs"}`;
|
|
392
|
-
let t11;
|
|
393
|
-
if ($[31] !== t10 || $[32] !== title) {
|
|
394
|
-
t11 = /* @__PURE__ */ jsx("span", { className: t10, children: title });
|
|
395
|
-
$[31] = t10;
|
|
396
|
-
$[32] = title;
|
|
397
|
-
$[33] = t11;
|
|
398
|
-
} else {
|
|
399
|
-
t11 = $[33];
|
|
400
|
-
}
|
|
401
|
-
let t12;
|
|
402
|
-
if ($[34] !== config.dateRange || $[35] !== isSmall) {
|
|
403
|
-
t12 = config.dateRange && !isSmall && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-surface-400 dark:text-surface-500 truncate mt-0.5", children: config.dateRange });
|
|
404
|
-
$[34] = config.dateRange;
|
|
405
|
-
$[35] = isSmall;
|
|
406
|
-
$[36] = t12;
|
|
407
|
-
} else {
|
|
408
|
-
t12 = $[36];
|
|
409
|
-
}
|
|
410
|
-
let t13;
|
|
411
|
-
if ($[37] !== t11 || $[38] !== t12) {
|
|
412
|
-
t13 = /* @__PURE__ */ jsxs("div", { className: "flex flex-col min-w-0", children: [
|
|
413
|
-
t11,
|
|
414
|
-
t12
|
|
415
|
-
] });
|
|
416
|
-
$[37] = t11;
|
|
417
|
-
$[38] = t12;
|
|
418
|
-
$[39] = t13;
|
|
419
|
-
} else {
|
|
420
|
-
t13 = $[39];
|
|
421
|
-
}
|
|
422
|
-
let t14;
|
|
423
|
-
if ($[40] !== iconElement) {
|
|
424
|
-
t14 = iconElement && /* @__PURE__ */ jsx("span", { className: "ml-2 shrink-0", children: iconElement });
|
|
425
|
-
$[40] = iconElement;
|
|
426
|
-
$[41] = t14;
|
|
427
|
-
} else {
|
|
428
|
-
t14 = $[41];
|
|
429
|
-
}
|
|
430
|
-
let t15;
|
|
431
|
-
if ($[42] !== t13 || $[43] !== t14 || $[44] !== t9) {
|
|
432
|
-
t15 = /* @__PURE__ */ jsxs("div", { className: t9, children: [
|
|
433
|
-
t13,
|
|
434
|
-
t14
|
|
435
|
-
] });
|
|
436
|
-
$[42] = t13;
|
|
437
|
-
$[43] = t14;
|
|
438
|
-
$[44] = t9;
|
|
439
|
-
$[45] = t15;
|
|
440
|
-
} else {
|
|
441
|
-
t15 = $[45];
|
|
442
|
-
}
|
|
443
|
-
const t16 = `font-semibold leading-tight tracking-tight break-all text-surface-800 dark:text-surface-100 ${isSmall ? "text-lg" : containerWidth !== null && containerWidth < 300 ? "text-xl" : "text-2xl"}`;
|
|
444
|
-
let t17;
|
|
445
|
-
if ($[46] !== formattedValue || $[47] !== t16) {
|
|
446
|
-
t17 = /* @__PURE__ */ jsx("div", { className: t16, children: formattedValue });
|
|
447
|
-
$[46] = formattedValue;
|
|
448
|
-
$[47] = t16;
|
|
449
|
-
$[48] = t17;
|
|
450
|
-
} else {
|
|
451
|
-
t17 = $[48];
|
|
452
|
-
}
|
|
453
|
-
let t18;
|
|
454
|
-
if ($[49] !== comparisonElement || $[50] !== isSmall) {
|
|
455
|
-
t18 = comparisonElement && /* @__PURE__ */ jsx("div", { className: isSmall ? "mt-0.5" : "mt-1", children: comparisonElement });
|
|
456
|
-
$[49] = comparisonElement;
|
|
457
|
-
$[50] = isSmall;
|
|
458
|
-
$[51] = t18;
|
|
459
|
-
} else {
|
|
460
|
-
t18 = $[51];
|
|
461
|
-
}
|
|
462
|
-
let t19;
|
|
463
|
-
if ($[52] !== baseClass || $[53] !== t15 || $[54] !== t17 || $[55] !== t18 || $[56] !== t8) {
|
|
464
|
-
t19 = /* @__PURE__ */ jsxs("div", { ref: containerRef, className: baseClass, style: t8, children: [
|
|
465
|
-
t15,
|
|
466
|
-
t17,
|
|
467
|
-
t18
|
|
468
|
-
] });
|
|
469
|
-
$[52] = baseClass;
|
|
470
|
-
$[53] = t15;
|
|
471
|
-
$[54] = t17;
|
|
472
|
-
$[55] = t18;
|
|
473
|
-
$[56] = t8;
|
|
474
|
-
$[57] = t19;
|
|
475
|
-
} else {
|
|
476
|
-
t19 = $[57];
|
|
477
|
-
}
|
|
478
|
-
return t19;
|
|
165
|
+
/**
|
|
166
|
+
* Scorecard widget for the Rebase design system.
|
|
167
|
+
*
|
|
168
|
+
* Renders a single KPI metric with optional comparison value and icon.
|
|
169
|
+
* Uses Tailwind `dark:` classes — no JS dark mode detection.
|
|
170
|
+
* Icons are resolved via `getIcon` from `@rebasepro/app`.
|
|
171
|
+
*/
|
|
172
|
+
function InsightsScorecardView({ config, data, title, compact = false, embedded = false, fixedHeight }) {
|
|
173
|
+
const containerRef = useRef(null);
|
|
174
|
+
const [containerWidth, setContainerWidth] = useState(null);
|
|
175
|
+
React.useLayoutEffect(() => {
|
|
176
|
+
if (!containerRef.current) return;
|
|
177
|
+
setContainerWidth(containerRef.current.offsetWidth);
|
|
178
|
+
const observer = new ResizeObserver((entries) => {
|
|
179
|
+
for (const entry of entries) setContainerWidth(entry.contentRect.width);
|
|
180
|
+
});
|
|
181
|
+
observer.observe(containerRef.current);
|
|
182
|
+
return () => observer.disconnect();
|
|
183
|
+
}, []);
|
|
184
|
+
const mainValue = data[config.value.field];
|
|
185
|
+
const formattedValue = typeof mainValue === "number" ? formatNumber(mainValue, config.value.format) : String(mainValue ?? "N/A");
|
|
186
|
+
let comparisonElement = null;
|
|
187
|
+
if (config.comparison) {
|
|
188
|
+
const comparisonValue = data[config.comparison.field];
|
|
189
|
+
if (typeof comparisonValue === "number") {
|
|
190
|
+
const formattedComparison = formatNumber(comparisonValue, config.comparison.format);
|
|
191
|
+
const isPositive = comparisonValue > 0;
|
|
192
|
+
const isNegative = comparisonValue < 0;
|
|
193
|
+
let colorClass = "text-surface-500 dark:text-surface-400";
|
|
194
|
+
if (config.comparison.intent === "increase_is_good") {
|
|
195
|
+
if (isPositive) colorClass = "text-emerald-500";
|
|
196
|
+
if (isNegative) colorClass = "text-red-500";
|
|
197
|
+
} else if (config.comparison.intent === "decrease_is_good") {
|
|
198
|
+
if (isPositive) colorClass = "text-red-500";
|
|
199
|
+
if (isNegative) colorClass = "text-emerald-500";
|
|
200
|
+
}
|
|
201
|
+
comparisonElement = /* @__PURE__ */ jsx("span", {
|
|
202
|
+
className: `font-medium ${compact ? "text-[10px]" : "text-xs"} ${colorClass}`,
|
|
203
|
+
children: formattedComparison
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const isSmall = compact || containerWidth !== null && containerWidth < 200;
|
|
208
|
+
const iconElement = config.icon ? getIcon(config.icon, "text-surface-400 dark:text-surface-500", void 0, isSmall ? 14 : 18) : null;
|
|
209
|
+
if (compact) return /* @__PURE__ */ jsxs("div", {
|
|
210
|
+
className: cls("flex flex-col gap-0.5 px-2.5 py-2 rounded-md bg-transparent border min-w-0", defaultBorderMixin),
|
|
211
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
212
|
+
className: "text-[10px] uppercase tracking-wider text-surface-400 dark:text-surface-500 truncate",
|
|
213
|
+
children: title
|
|
214
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
215
|
+
className: "flex items-baseline gap-1.5",
|
|
216
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
217
|
+
className: "text-sm font-semibold tabular-nums text-surface-800 dark:text-surface-100",
|
|
218
|
+
children: formattedValue
|
|
219
|
+
}), comparisonElement]
|
|
220
|
+
})]
|
|
221
|
+
});
|
|
222
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
223
|
+
ref: containerRef,
|
|
224
|
+
className: embedded ? `flex flex-col min-w-0 h-full ${isSmall ? "px-3.5 py-3" : "px-5 py-4"}` : cls("rounded-lg flex flex-col min-w-0 bg-transparent border", defaultBorderMixin, isSmall ? "px-3.5 py-3" : "px-5 py-4"),
|
|
225
|
+
style: embedded ? void 0 : fixedHeight ? { height: fixedHeight } : { minHeight: isSmall ? 68 : 92 },
|
|
226
|
+
children: [
|
|
227
|
+
/* @__PURE__ */ jsxs("div", {
|
|
228
|
+
className: `flex items-center justify-between ${isSmall ? "mb-1" : "mb-2"}`,
|
|
229
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
230
|
+
className: "flex flex-col min-w-0",
|
|
231
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
232
|
+
className: `font-medium leading-snug truncate text-surface-500 dark:text-surface-400 ${isSmall ? "text-[11px]" : "text-xs"}`,
|
|
233
|
+
children: title
|
|
234
|
+
}), config.dateRange && !isSmall && /* @__PURE__ */ jsx("span", {
|
|
235
|
+
className: "text-[10px] text-surface-400 dark:text-surface-500 truncate mt-0.5",
|
|
236
|
+
children: config.dateRange
|
|
237
|
+
})]
|
|
238
|
+
}), iconElement && /* @__PURE__ */ jsx("span", {
|
|
239
|
+
className: "ml-2 shrink-0",
|
|
240
|
+
children: iconElement
|
|
241
|
+
})]
|
|
242
|
+
}),
|
|
243
|
+
/* @__PURE__ */ jsx("div", {
|
|
244
|
+
className: `font-semibold leading-tight tracking-tight break-all text-surface-800 dark:text-surface-100 ${isSmall ? "text-lg" : containerWidth !== null && containerWidth < 300 ? "text-xl" : "text-2xl"}`,
|
|
245
|
+
children: formattedValue
|
|
246
|
+
}),
|
|
247
|
+
comparisonElement && /* @__PURE__ */ jsx("div", {
|
|
248
|
+
className: isSmall ? "mt-0.5" : "mt-1",
|
|
249
|
+
children: comparisonElement
|
|
250
|
+
})
|
|
251
|
+
]
|
|
252
|
+
});
|
|
479
253
|
}
|
|
480
254
|
InsightsScorecardView.displayName = "InsightsScorecardView";
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
if ($[22] !== hasIcon) {
|
|
616
|
-
t10 = hasIcon && /* @__PURE__ */ jsx("div", { className: "bg-surface-200 dark:bg-surface-700 rounded ml-2 shrink-0", style: {
|
|
617
|
-
height: 18,
|
|
618
|
-
width: 18
|
|
619
|
-
} });
|
|
620
|
-
$[22] = hasIcon;
|
|
621
|
-
$[23] = t10;
|
|
622
|
-
} else {
|
|
623
|
-
t10 = $[23];
|
|
624
|
-
}
|
|
625
|
-
let t11;
|
|
626
|
-
if ($[24] !== t10 || $[25] !== t9) {
|
|
627
|
-
t11 = /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
|
|
628
|
-
t9,
|
|
629
|
-
t10
|
|
630
|
-
] });
|
|
631
|
-
$[24] = t10;
|
|
632
|
-
$[25] = t9;
|
|
633
|
-
$[26] = t11;
|
|
634
|
-
} else {
|
|
635
|
-
t11 = $[26];
|
|
636
|
-
}
|
|
637
|
-
let t12;
|
|
638
|
-
if ($[27] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
|
|
639
|
-
t12 = /* @__PURE__ */ jsx("div", { className: "bg-surface-200 dark:bg-surface-700 rounded", style: {
|
|
640
|
-
height: 32,
|
|
641
|
-
width: "40%"
|
|
642
|
-
} });
|
|
643
|
-
$[27] = t12;
|
|
644
|
-
} else {
|
|
645
|
-
t12 = $[27];
|
|
646
|
-
}
|
|
647
|
-
let t13;
|
|
648
|
-
if ($[28] !== hasComparison) {
|
|
649
|
-
t13 = hasComparison && /* @__PURE__ */ jsx("div", { className: "bg-surface-200/60 dark:bg-surface-700/60 rounded mt-1", style: {
|
|
650
|
-
height: 16,
|
|
651
|
-
width: "25%"
|
|
652
|
-
} });
|
|
653
|
-
$[28] = hasComparison;
|
|
654
|
-
$[29] = t13;
|
|
655
|
-
} else {
|
|
656
|
-
t13 = $[29];
|
|
657
|
-
}
|
|
658
|
-
let t14;
|
|
659
|
-
if ($[30] !== t11 || $[31] !== t13 || $[32] !== t5 || $[33] !== t6) {
|
|
660
|
-
t14 = /* @__PURE__ */ jsxs("div", { className: t5, style: t6, children: [
|
|
661
|
-
t11,
|
|
662
|
-
t12,
|
|
663
|
-
t13
|
|
664
|
-
] });
|
|
665
|
-
$[30] = t11;
|
|
666
|
-
$[31] = t13;
|
|
667
|
-
$[32] = t5;
|
|
668
|
-
$[33] = t6;
|
|
669
|
-
$[34] = t14;
|
|
670
|
-
} else {
|
|
671
|
-
t14 = $[34];
|
|
672
|
-
}
|
|
673
|
-
return t14;
|
|
255
|
+
//#endregion
|
|
256
|
+
//#region src/components/InsightWidgetSkeleton.tsx
|
|
257
|
+
/**
|
|
258
|
+
* Skeleton loader for scorecard insight widgets — displays animated
|
|
259
|
+
* shimmer placeholders that exactly match the final rendered layout
|
|
260
|
+
* of InsightsScorecardView for a given config, preventing layout shift.
|
|
261
|
+
*
|
|
262
|
+
* The skeleton receives the scorecard config so it can conditionally
|
|
263
|
+
* render placeholder lines for comparison, dateRange, and icon —
|
|
264
|
+
* only when the loaded view will also render them.
|
|
265
|
+
*
|
|
266
|
+
* The standard skeleton mirrors InsightsScorecardView's responsive
|
|
267
|
+
* container-width breakpoints (ResizeObserver → isSmall / isMedium)
|
|
268
|
+
* and uses placeholder heights that exactly match the **computed**
|
|
269
|
+
* Tailwind line-heights (accounting for `leading-*` overrides).
|
|
270
|
+
* This guarantees a pixel-perfect skeleton → loaded transition.
|
|
271
|
+
*/
|
|
272
|
+
function InsightWidgetSkeleton({ config, compact = false, embedded = false, fixedHeight }) {
|
|
273
|
+
const hasComparison = Boolean(config.comparison);
|
|
274
|
+
const hasIcon = Boolean(config.icon);
|
|
275
|
+
const hasDateRange = Boolean(config.dateRange);
|
|
276
|
+
if (compact) return /* @__PURE__ */ jsxs("div", {
|
|
277
|
+
className: cls("animate-pulse", embedded ? "h-full px-2.5 py-2" : "flex flex-col gap-0.5 rounded-md bg-transparent border min-w-0 px-2.5 py-2", !embedded && defaultBorderMixin),
|
|
278
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
279
|
+
className: "bg-surface-200 dark:bg-surface-700 rounded-sm",
|
|
280
|
+
style: {
|
|
281
|
+
height: 14,
|
|
282
|
+
width: 48
|
|
283
|
+
}
|
|
284
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
285
|
+
className: "flex items-baseline gap-1.5",
|
|
286
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
287
|
+
className: "bg-surface-200 dark:bg-surface-700 rounded-sm",
|
|
288
|
+
style: {
|
|
289
|
+
height: 20,
|
|
290
|
+
width: 40
|
|
291
|
+
}
|
|
292
|
+
}), hasComparison && /* @__PURE__ */ jsx("div", {
|
|
293
|
+
className: "bg-surface-200/60 dark:bg-surface-700/60 rounded-sm",
|
|
294
|
+
style: {
|
|
295
|
+
height: 14,
|
|
296
|
+
width: 28
|
|
297
|
+
}
|
|
298
|
+
})]
|
|
299
|
+
})]
|
|
300
|
+
});
|
|
301
|
+
return /* @__PURE__ */ jsx(StandardSkeleton, {
|
|
302
|
+
hasComparison,
|
|
303
|
+
hasIcon,
|
|
304
|
+
hasDateRange,
|
|
305
|
+
embedded,
|
|
306
|
+
fixedHeight
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Inner component for the standard scorecard skeleton.
|
|
311
|
+
*
|
|
312
|
+
* Mirrors InsightsScorecardView's layout by:
|
|
313
|
+
* 1. Using the same ResizeObserver + containerWidth pattern for
|
|
314
|
+
* responsive breakpoints (isSmall < 200px, isMedium < 300px).
|
|
315
|
+
* 2. Using placeholder heights derived from the exact computed
|
|
316
|
+
* Tailwind line-heights that InsightsScorecardView renders.
|
|
317
|
+
* 3. Matching all container classes, margins, paddings, and flex
|
|
318
|
+
* layout properties identically.
|
|
319
|
+
*/
|
|
320
|
+
function StandardSkeleton({ hasComparison, hasIcon, hasDateRange, embedded, fixedHeight }) {
|
|
321
|
+
const containerRef = useRef(null);
|
|
322
|
+
const [containerWidth, setContainerWidth] = useState(null);
|
|
323
|
+
React.useLayoutEffect(() => {
|
|
324
|
+
if (!containerRef.current) return;
|
|
325
|
+
setContainerWidth(containerRef.current.offsetWidth);
|
|
326
|
+
const observer = new ResizeObserver((entries) => {
|
|
327
|
+
for (const entry of entries) setContainerWidth(entry.contentRect.width);
|
|
328
|
+
});
|
|
329
|
+
observer.observe(containerRef.current);
|
|
330
|
+
return () => observer.disconnect();
|
|
331
|
+
}, []);
|
|
332
|
+
const isSmall = containerWidth !== null && containerWidth < 200;
|
|
333
|
+
const titleHeight = isSmall ? 15 : 16.5;
|
|
334
|
+
const valueHeight = isSmall ? 22.5 : containerWidth !== null && containerWidth < 300 ? 25 : 30;
|
|
335
|
+
const comparisonHeight = 16;
|
|
336
|
+
const iconSize = isSmall ? 14 : 18;
|
|
337
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
338
|
+
ref: containerRef,
|
|
339
|
+
className: cls("animate-pulse", embedded ? `flex flex-col min-w-0 h-full ${isSmall ? "px-3.5 py-3" : "px-5 py-4"}` : cls("rounded-lg flex flex-col min-w-0 bg-transparent border", defaultBorderMixin, isSmall ? "px-3.5 py-3" : "px-5 py-4")),
|
|
340
|
+
style: embedded ? void 0 : fixedHeight ? { height: fixedHeight } : { minHeight: isSmall ? 68 : 92 },
|
|
341
|
+
children: [
|
|
342
|
+
/* @__PURE__ */ jsxs("div", {
|
|
343
|
+
className: `flex items-center justify-between ${isSmall ? "mb-1" : "mb-2"}`,
|
|
344
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
345
|
+
className: "flex flex-col min-w-0",
|
|
346
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
347
|
+
className: "bg-surface-200 dark:bg-surface-700 rounded",
|
|
348
|
+
style: {
|
|
349
|
+
height: titleHeight,
|
|
350
|
+
width: "60%"
|
|
351
|
+
}
|
|
352
|
+
}), hasDateRange && !isSmall && /* @__PURE__ */ jsx("div", {
|
|
353
|
+
className: "bg-surface-200/60 dark:bg-surface-700/60 rounded mt-0.5",
|
|
354
|
+
style: {
|
|
355
|
+
height: 14,
|
|
356
|
+
width: "40%"
|
|
357
|
+
}
|
|
358
|
+
})]
|
|
359
|
+
}), hasIcon && /* @__PURE__ */ jsx("span", {
|
|
360
|
+
className: "ml-2 shrink-0",
|
|
361
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
362
|
+
className: "bg-surface-200 dark:bg-surface-700 rounded",
|
|
363
|
+
style: {
|
|
364
|
+
height: iconSize,
|
|
365
|
+
width: iconSize
|
|
366
|
+
}
|
|
367
|
+
})
|
|
368
|
+
})]
|
|
369
|
+
}),
|
|
370
|
+
/* @__PURE__ */ jsx("div", {
|
|
371
|
+
className: "bg-surface-200 dark:bg-surface-700 rounded",
|
|
372
|
+
style: {
|
|
373
|
+
height: valueHeight,
|
|
374
|
+
width: "40%"
|
|
375
|
+
}
|
|
376
|
+
}),
|
|
377
|
+
hasComparison && /* @__PURE__ */ jsx("div", {
|
|
378
|
+
className: isSmall ? "mt-0.5" : "mt-1",
|
|
379
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
380
|
+
className: "bg-surface-200/60 dark:bg-surface-700/60 rounded",
|
|
381
|
+
style: {
|
|
382
|
+
height: comparisonHeight,
|
|
383
|
+
width: "25%"
|
|
384
|
+
}
|
|
385
|
+
})
|
|
386
|
+
})
|
|
387
|
+
]
|
|
388
|
+
});
|
|
674
389
|
}
|
|
675
390
|
InsightWidgetSkeleton.displayName = "InsightWidgetSkeleton";
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
t42 = /* @__PURE__ */ jsxs("div", { className: t32, children: [
|
|
742
|
-
definition.title,
|
|
743
|
-
" — No data"
|
|
744
|
-
] });
|
|
745
|
-
$[12] = definition.title;
|
|
746
|
-
$[13] = t32;
|
|
747
|
-
$[14] = t42;
|
|
748
|
-
} else {
|
|
749
|
-
t42 = $[14];
|
|
750
|
-
}
|
|
751
|
-
return t42;
|
|
752
|
-
}
|
|
753
|
-
const t3 = data.rows[0];
|
|
754
|
-
let t4;
|
|
755
|
-
if ($[15] !== compact || $[16] !== definition.scorecard || $[17] !== definition.title || $[18] !== embedded || $[19] !== t3) {
|
|
756
|
-
t4 = /* @__PURE__ */ jsx(InsightsScorecardView, { config: definition.scorecard, data: t3, title: definition.title, compact, embedded });
|
|
757
|
-
$[15] = compact;
|
|
758
|
-
$[16] = definition.scorecard;
|
|
759
|
-
$[17] = definition.title;
|
|
760
|
-
$[18] = embedded;
|
|
761
|
-
$[19] = t3;
|
|
762
|
-
$[20] = t4;
|
|
763
|
-
} else {
|
|
764
|
-
t4 = $[20];
|
|
765
|
-
}
|
|
766
|
-
return t4;
|
|
391
|
+
//#endregion
|
|
392
|
+
//#region src/components/InsightWidget.tsx
|
|
393
|
+
/**
|
|
394
|
+
* Compute a deterministic fixed height for a standard scorecard based
|
|
395
|
+
* on which optional elements the config declares. This eliminates
|
|
396
|
+
* layout shift between skeleton and loaded states.
|
|
397
|
+
*
|
|
398
|
+
* Breakdown (non-compact, non-small):
|
|
399
|
+
* py-4 padding: 16 + 16 = 32
|
|
400
|
+
* title row: 16.5 (text-xs leading-snug)
|
|
401
|
+
* mb-2 margin: 8
|
|
402
|
+
* value: 30 (text-2xl leading-tight)
|
|
403
|
+
* ---
|
|
404
|
+
* base: 86.5
|
|
405
|
+
* + dateRange: +16 (14px text + 2px mt-0.5)
|
|
406
|
+
* + comparison: +20 (16px text + 4px mt-1)
|
|
407
|
+
*/
|
|
408
|
+
function computeFixedHeight(config) {
|
|
409
|
+
let h = 86.5;
|
|
410
|
+
if (config.dateRange) h += 16;
|
|
411
|
+
if (config.comparison) h += 20;
|
|
412
|
+
return Math.ceil(h);
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Single insight widget orchestrator.
|
|
416
|
+
*
|
|
417
|
+
* Wraps skeleton and loaded states in a fixed-height container
|
|
418
|
+
* (computed from the scorecard config) to prevent layout shift.
|
|
419
|
+
*
|
|
420
|
+
* All theme-awareness is handled via Tailwind `dark:` classes.
|
|
421
|
+
*/
|
|
422
|
+
function InsightWidget({ definition, collectionSlug, path, parentCollectionSlugs, parentEntityIds, compact = false, embedded = false }) {
|
|
423
|
+
const { data, loading, error } = useInsightsData(definition, {
|
|
424
|
+
path,
|
|
425
|
+
collectionSlug,
|
|
426
|
+
parentCollectionSlugs
|
|
427
|
+
});
|
|
428
|
+
const fixedHeight = !compact && !embedded ? computeFixedHeight(definition.scorecard) : void 0;
|
|
429
|
+
if (loading) return /* @__PURE__ */ jsx(InsightWidgetSkeleton, {
|
|
430
|
+
config: definition.scorecard,
|
|
431
|
+
compact,
|
|
432
|
+
embedded,
|
|
433
|
+
fixedHeight
|
|
434
|
+
});
|
|
435
|
+
if (error) return /* @__PURE__ */ jsxs("div", {
|
|
436
|
+
className: `text-red-500/70 dark:text-red-400/70 text-[0.8125rem] ${embedded ? "px-5 py-4 h-full" : `rounded-lg bg-red-500/5 dark:bg-red-400/5 border border-red-500/10 dark:border-red-400/10 ${compact ? "px-3.5 py-3" : "px-5 py-4"}`}`,
|
|
437
|
+
style: fixedHeight ? { height: fixedHeight } : void 0,
|
|
438
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
439
|
+
className: "font-semibold mb-1",
|
|
440
|
+
children: definition.title
|
|
441
|
+
}), /* @__PURE__ */ jsx("div", { children: error.message })]
|
|
442
|
+
});
|
|
443
|
+
if (!data || data.rows.length === 0) return /* @__PURE__ */ jsxs("div", {
|
|
444
|
+
className: `text-surface-400 dark:text-surface-500 text-[0.8125rem] ${embedded ? "px-5 py-4 h-full" : `rounded-lg bg-surface-100 dark:bg-surface-800 border border-surface-200 dark:border-surface-700 ${compact ? "px-3.5 py-3" : "px-5 py-4"}`}`,
|
|
445
|
+
style: fixedHeight ? { height: fixedHeight } : void 0,
|
|
446
|
+
children: [definition.title, " — No data"]
|
|
447
|
+
});
|
|
448
|
+
return /* @__PURE__ */ jsx(InsightsScorecardView, {
|
|
449
|
+
config: definition.scorecard,
|
|
450
|
+
data: data.rows[0],
|
|
451
|
+
title: definition.title,
|
|
452
|
+
compact,
|
|
453
|
+
embedded,
|
|
454
|
+
fixedHeight
|
|
455
|
+
});
|
|
767
456
|
}
|
|
768
457
|
InsightWidget.displayName = "InsightWidget";
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
let t32;
|
|
790
|
-
if ($[4] !== slug) {
|
|
791
|
-
t32 = (def) => /* @__PURE__ */ jsx(InsightWidget, { definition: def, collectionSlug: slug, compact: true }, def.id);
|
|
792
|
-
$[4] = slug;
|
|
793
|
-
$[5] = t32;
|
|
794
|
-
} else {
|
|
795
|
-
t32 = $[5];
|
|
796
|
-
}
|
|
797
|
-
t2 = insights.map(t32);
|
|
798
|
-
$[1] = insights;
|
|
799
|
-
$[2] = slug;
|
|
800
|
-
$[3] = t2;
|
|
801
|
-
} else {
|
|
802
|
-
t2 = $[3];
|
|
803
|
-
}
|
|
804
|
-
let t3;
|
|
805
|
-
if ($[6] !== t2) {
|
|
806
|
-
t3 = /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-1.5 mt-2", style: t1, children: t2 });
|
|
807
|
-
$[6] = t2;
|
|
808
|
-
$[7] = t3;
|
|
809
|
-
} else {
|
|
810
|
-
t3 = $[7];
|
|
811
|
-
}
|
|
812
|
-
return t3;
|
|
458
|
+
//#endregion
|
|
459
|
+
//#region src/components/HomeCardInsightSlot.tsx
|
|
460
|
+
/**
|
|
461
|
+
* Renders compact insight widgets inline within a home page collection card.
|
|
462
|
+
* Injected via the `home.card.insight` slot.
|
|
463
|
+
*
|
|
464
|
+
* Uses a horizontal flex layout so multiple cards sit side by side.
|
|
465
|
+
*/
|
|
466
|
+
function HomeCardInsightSlot({ slug, insights }) {
|
|
467
|
+
if (!insights || insights.length === 0) return null;
|
|
468
|
+
const estimatedRows = Math.ceil(insights.length / 2);
|
|
469
|
+
return /* @__PURE__ */ jsx("div", {
|
|
470
|
+
className: "flex flex-wrap items-center gap-1.5 mt-2",
|
|
471
|
+
style: { minHeight: estimatedRows * 42 + (estimatedRows - 1) * 6 },
|
|
472
|
+
children: insights.map((def) => /* @__PURE__ */ jsx(InsightWidget, {
|
|
473
|
+
definition: def,
|
|
474
|
+
collectionSlug: slug,
|
|
475
|
+
compact: true
|
|
476
|
+
}, def.id))
|
|
477
|
+
});
|
|
813
478
|
}
|
|
814
479
|
HomeCardInsightSlot.displayName = "HomeCardInsightSlot";
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
t1 = $[0];
|
|
831
|
-
}
|
|
832
|
-
let t2;
|
|
833
|
-
if ($[1] !== insights) {
|
|
834
|
-
t2 = insights.map(_temp$1);
|
|
835
|
-
$[1] = insights;
|
|
836
|
-
$[2] = t2;
|
|
837
|
-
} else {
|
|
838
|
-
t2 = $[2];
|
|
839
|
-
}
|
|
840
|
-
let t3;
|
|
841
|
-
if ($[3] !== t2) {
|
|
842
|
-
t3 = /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-3 pb-6", style: t1, children: t2 });
|
|
843
|
-
$[3] = t2;
|
|
844
|
-
$[4] = t3;
|
|
845
|
-
} else {
|
|
846
|
-
t3 = $[4];
|
|
847
|
-
}
|
|
848
|
-
return t3;
|
|
849
|
-
}
|
|
850
|
-
function _temp$1(def) {
|
|
851
|
-
return /* @__PURE__ */ jsx(InsightWidget, { definition: def }, def.id);
|
|
480
|
+
//#endregion
|
|
481
|
+
//#region src/components/HomeInsightsSlot.tsx
|
|
482
|
+
/**
|
|
483
|
+
* Scorecard insights panel rendered at the top of the home page.
|
|
484
|
+
* Injected via the `home.children.start` slot.
|
|
485
|
+
*
|
|
486
|
+
* Renders scorecards in a responsive grid (up to 4 columns).
|
|
487
|
+
*/
|
|
488
|
+
function HomeInsightsSlot({ insights }) {
|
|
489
|
+
if (!insights || insights.length === 0) return null;
|
|
490
|
+
return /* @__PURE__ */ jsx("div", {
|
|
491
|
+
className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-3 pb-6",
|
|
492
|
+
style: { minHeight: 92 },
|
|
493
|
+
children: insights.map((def) => /* @__PURE__ */ jsx(InsightWidget, { definition: def }, def.id))
|
|
494
|
+
});
|
|
852
495
|
}
|
|
853
496
|
HomeInsightsSlot.displayName = "HomeInsightsSlot";
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
$[2] = t1;
|
|
874
|
-
$[3] = t2;
|
|
875
|
-
} else {
|
|
876
|
-
t2 = $[3];
|
|
877
|
-
}
|
|
878
|
-
return t2;
|
|
879
|
-
}
|
|
880
|
-
function _temp(def) {
|
|
881
|
-
return /* @__PURE__ */ jsx(InsightWidget, { definition: def }, def.id);
|
|
497
|
+
//#endregion
|
|
498
|
+
//#region src/components/CollectionInsightsInline.tsx
|
|
499
|
+
/**
|
|
500
|
+
* Renders scorecard insight widgets inline within a collection's list view,
|
|
501
|
+
* positioned below the title and above the main data list.
|
|
502
|
+
*
|
|
503
|
+
* Injected via the `collection.insights` slot.
|
|
504
|
+
*/
|
|
505
|
+
function CollectionInsightsInline({ insights, path, parentCollectionSlugs, parentEntityIds }) {
|
|
506
|
+
if (!insights || insights.length === 0) return null;
|
|
507
|
+
return /* @__PURE__ */ jsx("div", {
|
|
508
|
+
className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-3 pb-4",
|
|
509
|
+
children: insights.map((def) => /* @__PURE__ */ jsx(InsightWidget, {
|
|
510
|
+
definition: def,
|
|
511
|
+
path,
|
|
512
|
+
parentCollectionSlugs,
|
|
513
|
+
parentEntityIds
|
|
514
|
+
}, def.id))
|
|
515
|
+
});
|
|
882
516
|
}
|
|
883
517
|
CollectionInsightsInline.displayName = "CollectionInsightsInline";
|
|
518
|
+
//#endregion
|
|
519
|
+
//#region src/useInsightsPlugin.tsx
|
|
520
|
+
/**
|
|
521
|
+
* Creates the Insights plugin for Rebase.
|
|
522
|
+
*
|
|
523
|
+
* This plugin injects scorecard widgets into key UI locations:
|
|
524
|
+
* - **Home page header**: KPI overview via `home.children.start` slot
|
|
525
|
+
* - **Collection list view**: Scorecards inline (below title, above list) via `collection.insights` slot
|
|
526
|
+
* - **Home page cards**: Compact scorecard metrics auto-extracted from collection insights via `home.card.insight` slot
|
|
527
|
+
*
|
|
528
|
+
* Collection-level insights (`collections.<slug>`) are the single source of truth:
|
|
529
|
+
* scorecards render in the collection list view and are automatically extracted
|
|
530
|
+
* to show as compact widgets on the corresponding home page card.
|
|
531
|
+
*
|
|
532
|
+
* Each insight owns its own `data()` callback — use the Rebase client SDK,
|
|
533
|
+
* call a custom function, or hit any external API. Full flexibility, zero new endpoints.
|
|
534
|
+
*
|
|
535
|
+
* @example
|
|
536
|
+
* ```typescript
|
|
537
|
+
* import { useInsightsPlugin } from "@rebasepro/plugin-insights";
|
|
538
|
+
*
|
|
539
|
+
* const insightsPlugin = useInsightsPlugin({
|
|
540
|
+
* cacheTTL: 120_000,
|
|
541
|
+
* insights: {
|
|
542
|
+
* home: [
|
|
543
|
+
* { id: "revenue", title: "Revenue", data: async () => ..., scorecard: { ... } },
|
|
544
|
+
* ],
|
|
545
|
+
* collections: {
|
|
546
|
+
* orders: [
|
|
547
|
+
* { id: "total", title: "Total Orders", data: async () => ..., scorecard: { ... } },
|
|
548
|
+
* ],
|
|
549
|
+
* },
|
|
550
|
+
* },
|
|
551
|
+
* });
|
|
552
|
+
* ```
|
|
553
|
+
*/
|
|
884
554
|
function useInsightsPlugin(config) {
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
555
|
+
const { insights, cacheTTL } = config;
|
|
556
|
+
return React.useMemo(() => {
|
|
557
|
+
const slots = [];
|
|
558
|
+
if (insights.home && insights.home.length > 0) {
|
|
559
|
+
const homeInsights = insights.home;
|
|
560
|
+
slots.push({
|
|
561
|
+
slot: "home.children.start",
|
|
562
|
+
Component: (props) => /* @__PURE__ */ jsx(HomeInsightsSlot, {
|
|
563
|
+
...props,
|
|
564
|
+
insights: homeInsights
|
|
565
|
+
}),
|
|
566
|
+
order: 10
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
if (insights.collections) for (const [slug, defs] of Object.entries(insights.collections)) {
|
|
570
|
+
if (defs.length === 0) continue;
|
|
571
|
+
const collectionInsights = defs;
|
|
572
|
+
slots.push({
|
|
573
|
+
slot: "collection.insights",
|
|
574
|
+
Component: (props) => {
|
|
575
|
+
if ((props.path?.split("/").filter(Boolean).pop() ?? "") !== slug) return null;
|
|
576
|
+
return /* @__PURE__ */ jsx(CollectionInsightsInline, {
|
|
577
|
+
...props,
|
|
578
|
+
insights: collectionInsights
|
|
579
|
+
});
|
|
580
|
+
},
|
|
581
|
+
order: 10
|
|
582
|
+
});
|
|
583
|
+
slots.push({
|
|
584
|
+
slot: "home.card.insight",
|
|
585
|
+
Component: (props) => {
|
|
586
|
+
if (props.slug !== slug) return null;
|
|
587
|
+
return /* @__PURE__ */ jsx(HomeCardInsightSlot, {
|
|
588
|
+
...props,
|
|
589
|
+
insights: collectionInsights
|
|
590
|
+
});
|
|
591
|
+
},
|
|
592
|
+
order: 10
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
return {
|
|
596
|
+
key: "plugin-insights",
|
|
597
|
+
slots,
|
|
598
|
+
providers: [{
|
|
599
|
+
scope: "root",
|
|
600
|
+
Component: InsightsProvider,
|
|
601
|
+
props: { cacheTTL }
|
|
602
|
+
}]
|
|
603
|
+
};
|
|
604
|
+
}, [insights, cacheTTL]);
|
|
934
605
|
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
InsightsProvider,
|
|
940
|
-
InsightsScorecardView,
|
|
941
|
-
useInsightsData,
|
|
942
|
-
useInsightsEngine,
|
|
943
|
-
useInsightsPlugin
|
|
944
|
-
};
|
|
945
|
-
//# sourceMappingURL=index.es.js.map
|
|
606
|
+
//#endregion
|
|
607
|
+
export { InsightWidget, InsightWidgetSkeleton, InsightsCache, InsightsProvider, InsightsScorecardView, useInsightsData, useInsightsEngine, useInsightsPlugin };
|
|
608
|
+
|
|
609
|
+
//# sourceMappingURL=index.es.js.map
|