@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.
Files changed (300) hide show
  1. package/LICENSE +21 -6
  2. package/README.md +115 -0
  3. package/dist/{plugin-insights/src/components → components}/CollectionInsightsInline.d.ts +5 -3
  4. package/dist/{plugin-insights/src/components → components}/HomeCardInsightSlot.d.ts +3 -2
  5. package/dist/{plugin-insights/src/components → components}/HomeInsightsSlot.d.ts +3 -2
  6. package/dist/{plugin-insights/src/components → components}/InsightWidget.d.ts +8 -4
  7. package/dist/{plugin-insights/src/components → components}/InsightWidgetSkeleton.d.ts +11 -2
  8. package/dist/{plugin-insights/src/components → components}/InsightsScorecardView.d.ts +6 -3
  9. package/dist/{plugin-insights/src/engine → engine}/InsightsProvider.d.ts +2 -2
  10. package/dist/{plugin-insights/src/engine → engine}/useInsightsData.d.ts +2 -2
  11. package/dist/{plugin-insights/src/index.d.ts → index.d.ts} +1 -1
  12. package/dist/index.es.js +587 -923
  13. package/dist/index.es.js.map +1 -1
  14. package/dist/index.umd.js +645 -943
  15. package/dist/index.umd.js.map +1 -1
  16. package/dist/{plugin-insights/src/types → types}/engine.d.ts +10 -1
  17. package/package.json +27 -18
  18. package/src/components/CollectionInsightsInline.tsx +10 -2
  19. package/src/components/HomeCardInsightSlot.tsx +6 -2
  20. package/src/components/HomeInsightsSlot.tsx +1 -1
  21. package/src/components/InsightWidget.tsx +41 -6
  22. package/src/components/InsightWidgetSkeleton.tsx +134 -38
  23. package/src/components/InsightsScorecardView.tsx +7 -4
  24. package/src/engine/InsightsCache.test.ts +58 -0
  25. package/src/engine/InsightsCache.ts +3 -2
  26. package/src/engine/useInsightsData.ts +10 -7
  27. package/src/index.ts +1 -1
  28. package/src/types/engine.ts +11 -1
  29. package/src/useInsightsPlugin.tsx +68 -65
  30. package/dist/common/src/collections/CollectionRegistry.d.ts +0 -56
  31. package/dist/common/src/collections/index.d.ts +0 -1
  32. package/dist/common/src/data/buildRebaseData.d.ts +0 -14
  33. package/dist/common/src/index.d.ts +0 -3
  34. package/dist/common/src/util/builders.d.ts +0 -57
  35. package/dist/common/src/util/callbacks.d.ts +0 -6
  36. package/dist/common/src/util/collections.d.ts +0 -11
  37. package/dist/common/src/util/common.d.ts +0 -2
  38. package/dist/common/src/util/conditions.d.ts +0 -26
  39. package/dist/common/src/util/entities.d.ts +0 -58
  40. package/dist/common/src/util/enums.d.ts +0 -3
  41. package/dist/common/src/util/index.d.ts +0 -16
  42. package/dist/common/src/util/navigation_from_path.d.ts +0 -34
  43. package/dist/common/src/util/navigation_utils.d.ts +0 -20
  44. package/dist/common/src/util/parent_references_from_path.d.ts +0 -6
  45. package/dist/common/src/util/paths.d.ts +0 -14
  46. package/dist/common/src/util/permissions.d.ts +0 -5
  47. package/dist/common/src/util/references.d.ts +0 -2
  48. package/dist/common/src/util/relations.d.ts +0 -22
  49. package/dist/common/src/util/resolutions.d.ts +0 -72
  50. package/dist/common/src/util/storage.d.ts +0 -24
  51. package/dist/core/src/components/AIIcon.d.ts +0 -16
  52. package/dist/core/src/components/ConfirmationDialog.d.ts +0 -9
  53. package/dist/core/src/components/Debug/UIReferenceView.d.ts +0 -1
  54. package/dist/core/src/components/Debug/UIStyleGuide.d.ts +0 -1
  55. package/dist/core/src/components/ErrorTooltip.d.ts +0 -2
  56. package/dist/core/src/components/ErrorView.d.ts +0 -21
  57. package/dist/core/src/components/LanguageToggle.d.ts +0 -1
  58. package/dist/core/src/components/LoginView/LoginView.d.ts +0 -68
  59. package/dist/core/src/components/LoginView/index.d.ts +0 -2
  60. package/dist/core/src/components/NotFoundPage.d.ts +0 -1
  61. package/dist/core/src/components/RebaseAuth.d.ts +0 -10
  62. package/dist/core/src/components/RebaseLogo.d.ts +0 -7
  63. package/dist/core/src/components/UnsavedChangesDialog.d.ts +0 -9
  64. package/dist/core/src/components/UserDisplay.d.ts +0 -7
  65. package/dist/core/src/components/UserSelectPopover.d.ts +0 -62
  66. package/dist/core/src/components/UserSettingsView.d.ts +0 -1
  67. package/dist/core/src/components/common/index.d.ts +0 -6
  68. package/dist/core/src/components/common/table_height.d.ts +0 -5
  69. package/dist/core/src/components/common/types.d.ts +0 -63
  70. package/dist/core/src/components/common/useColumnsIds.d.ts +0 -9
  71. package/dist/core/src/components/common/useDataTableController.d.ts +0 -45
  72. package/dist/core/src/components/common/useDebouncedData.d.ts +0 -9
  73. package/dist/core/src/components/common/useScrollRestoration.d.ts +0 -14
  74. package/dist/core/src/components/index.d.ts +0 -16
  75. package/dist/core/src/contexts/AdminModeController.d.ts +0 -4
  76. package/dist/core/src/contexts/AnalyticsContext.d.ts +0 -3
  77. package/dist/core/src/contexts/AuthControllerContext.d.ts +0 -3
  78. package/dist/core/src/contexts/CustomizationControllerContext.d.ts +0 -3
  79. package/dist/core/src/contexts/DataDriverContext.d.ts +0 -3
  80. package/dist/core/src/contexts/DatabaseAdminContext.d.ts +0 -3
  81. package/dist/core/src/contexts/DialogsProvider.d.ts +0 -4
  82. package/dist/core/src/contexts/EffectiveRoleController.d.ts +0 -4
  83. package/dist/core/src/contexts/InternalUserManagementContext.d.ts +0 -3
  84. package/dist/core/src/contexts/ModeController.d.ts +0 -4
  85. package/dist/core/src/contexts/RebaseClientInstanceContext.d.ts +0 -6
  86. package/dist/core/src/contexts/RebaseDataContext.d.ts +0 -3
  87. package/dist/core/src/contexts/SnackbarProvider.d.ts +0 -2
  88. package/dist/core/src/contexts/StorageSourceContext.d.ts +0 -3
  89. package/dist/core/src/contexts/UserConfigurationPersistenceContext.d.ts +0 -3
  90. package/dist/core/src/contexts/index.d.ts +0 -13
  91. package/dist/core/src/core/PluginLifecycleManager.d.ts +0 -17
  92. package/dist/core/src/core/PluginProviderStack.d.ts +0 -21
  93. package/dist/core/src/core/Rebase.d.ts +0 -14
  94. package/dist/core/src/core/RebaseProps.d.ts +0 -136
  95. package/dist/core/src/core/RebaseRouter.d.ts +0 -4
  96. package/dist/core/src/core/RebaseRoutes.d.ts +0 -17
  97. package/dist/core/src/core/index.d.ts +0 -4
  98. package/dist/core/src/hooks/ApiConfigContext.d.ts +0 -24
  99. package/dist/core/src/hooks/data/delete.d.ts +0 -31
  100. package/dist/core/src/hooks/data/save.d.ts +0 -34
  101. package/dist/core/src/hooks/data/useCollectionFetch.d.ts +0 -51
  102. package/dist/core/src/hooks/data/useData.d.ts +0 -13
  103. package/dist/core/src/hooks/data/useDataOrder.d.ts +0 -12
  104. package/dist/core/src/hooks/data/useEntityFetch.d.ts +0 -38
  105. package/dist/core/src/hooks/data/useRelationSelector.d.ts +0 -52
  106. package/dist/core/src/hooks/data/useUserSelector.d.ts +0 -31
  107. package/dist/core/src/hooks/index.d.ts +0 -37
  108. package/dist/core/src/hooks/useAdminModeController.d.ts +0 -19
  109. package/dist/core/src/hooks/useAnalyticsController.d.ts +0 -5
  110. package/dist/core/src/hooks/useAuthController.d.ts +0 -11
  111. package/dist/core/src/hooks/useAuthSubscription.d.ts +0 -2
  112. package/dist/core/src/hooks/useBackendStorageSource.d.ts +0 -30
  113. package/dist/core/src/hooks/useBridgeRegistration.d.ts +0 -18
  114. package/dist/core/src/hooks/useBrowserTitleAndIcon.d.ts +0 -6
  115. package/dist/core/src/hooks/useBuildAdminModeController.d.ts +0 -6
  116. package/dist/core/src/hooks/useBuildEffectiveRoleController.d.ts +0 -8
  117. package/dist/core/src/hooks/useBuildLocalConfigurationPersistence.d.ts +0 -2
  118. package/dist/core/src/hooks/useBuildModeController.d.ts +0 -6
  119. package/dist/core/src/hooks/useClipboard.d.ts +0 -57
  120. package/dist/core/src/hooks/useCollapsedGroups.d.ts +0 -12
  121. package/dist/core/src/hooks/useCustomizationController.d.ts +0 -11
  122. package/dist/core/src/hooks/useDialogsController.d.ts +0 -11
  123. package/dist/core/src/hooks/useEffectiveRoleController.d.ts +0 -7
  124. package/dist/core/src/hooks/useInternalUserManagementController.d.ts +0 -12
  125. package/dist/core/src/hooks/useLargeLayout.d.ts +0 -1
  126. package/dist/core/src/hooks/useModeController.d.ts +0 -19
  127. package/dist/core/src/hooks/usePermissions.d.ts +0 -12
  128. package/dist/core/src/hooks/useRebaseClient.d.ts +0 -5
  129. package/dist/core/src/hooks/useRebaseContext.d.ts +0 -11
  130. package/dist/core/src/hooks/useRebaseRegistry.d.ts +0 -34
  131. package/dist/core/src/hooks/useSlot.d.ts +0 -18
  132. package/dist/core/src/hooks/useSnackbarController.d.ts +0 -20
  133. package/dist/core/src/hooks/useStorageSource.d.ts +0 -7
  134. package/dist/core/src/hooks/useStudioBridge.d.ts +0 -91
  135. package/dist/core/src/hooks/useTranslation.d.ts +0 -17
  136. package/dist/core/src/hooks/useUnsavedChangesDialog.d.ts +0 -12
  137. package/dist/core/src/hooks/useUserConfigurationPersistence.d.ts +0 -8
  138. package/dist/core/src/hooks/useValidateAuthenticator.d.ts +0 -21
  139. package/dist/core/src/i18n/RebaseI18nProvider.d.ts +0 -33
  140. package/dist/core/src/index.d.ts +0 -15
  141. package/dist/core/src/internal/common.d.ts +0 -3
  142. package/dist/core/src/internal/useRestoreScroll.d.ts +0 -6
  143. package/dist/core/src/locales/de.d.ts +0 -2
  144. package/dist/core/src/locales/en.d.ts +0 -10
  145. package/dist/core/src/locales/es.d.ts +0 -10
  146. package/dist/core/src/locales/fr.d.ts +0 -2
  147. package/dist/core/src/locales/hi.d.ts +0 -2
  148. package/dist/core/src/locales/it.d.ts +0 -2
  149. package/dist/core/src/locales/pt.d.ts +0 -7
  150. package/dist/core/src/util/constants.d.ts +0 -1
  151. package/dist/core/src/util/createFormexStub.d.ts +0 -2
  152. package/dist/core/src/util/entity_cache.d.ts +0 -27
  153. package/dist/core/src/util/enums.d.ts +0 -5
  154. package/dist/core/src/util/icon_list.d.ts +0 -5
  155. package/dist/core/src/util/icon_synonyms.d.ts +0 -1
  156. package/dist/core/src/util/icons.d.ts +0 -20
  157. package/dist/core/src/util/index.d.ts +0 -10
  158. package/dist/core/src/util/previews.d.ts +0 -4
  159. package/dist/core/src/util/useStorageUploadController.d.ts +0 -38
  160. package/dist/core/src/util/useTraceUpdate.d.ts +0 -2
  161. package/dist/formex/src/Field.d.ts +0 -52
  162. package/dist/formex/src/Formex.d.ts +0 -7
  163. package/dist/formex/src/index.d.ts +0 -5
  164. package/dist/formex/src/types.d.ts +0 -40
  165. package/dist/formex/src/useCreateFormex.d.ts +0 -14
  166. package/dist/formex/src/utils.d.ts +0 -16
  167. package/dist/types/src/controllers/analytics_controller.d.ts +0 -7
  168. package/dist/types/src/controllers/auth.d.ts +0 -119
  169. package/dist/types/src/controllers/client.d.ts +0 -170
  170. package/dist/types/src/controllers/collection_registry.d.ts +0 -45
  171. package/dist/types/src/controllers/customization_controller.d.ts +0 -60
  172. package/dist/types/src/controllers/data.d.ts +0 -168
  173. package/dist/types/src/controllers/data_driver.d.ts +0 -160
  174. package/dist/types/src/controllers/database_admin.d.ts +0 -11
  175. package/dist/types/src/controllers/dialogs_controller.d.ts +0 -36
  176. package/dist/types/src/controllers/effective_role.d.ts +0 -4
  177. package/dist/types/src/controllers/email.d.ts +0 -34
  178. package/dist/types/src/controllers/index.d.ts +0 -18
  179. package/dist/types/src/controllers/local_config_persistence.d.ts +0 -20
  180. package/dist/types/src/controllers/navigation.d.ts +0 -213
  181. package/dist/types/src/controllers/registry.d.ts +0 -54
  182. package/dist/types/src/controllers/side_dialogs_controller.d.ts +0 -67
  183. package/dist/types/src/controllers/side_entity_controller.d.ts +0 -90
  184. package/dist/types/src/controllers/snackbar.d.ts +0 -24
  185. package/dist/types/src/controllers/storage.d.ts +0 -171
  186. package/dist/types/src/index.d.ts +0 -4
  187. package/dist/types/src/rebase_context.d.ts +0 -105
  188. package/dist/types/src/types/backend.d.ts +0 -536
  189. package/dist/types/src/types/builders.d.ts +0 -15
  190. package/dist/types/src/types/chips.d.ts +0 -5
  191. package/dist/types/src/types/collections.d.ts +0 -856
  192. package/dist/types/src/types/cron.d.ts +0 -102
  193. package/dist/types/src/types/data_source.d.ts +0 -64
  194. package/dist/types/src/types/entities.d.ts +0 -145
  195. package/dist/types/src/types/entity_actions.d.ts +0 -98
  196. package/dist/types/src/types/entity_callbacks.d.ts +0 -173
  197. package/dist/types/src/types/entity_link_builder.d.ts +0 -7
  198. package/dist/types/src/types/entity_overrides.d.ts +0 -10
  199. package/dist/types/src/types/entity_views.d.ts +0 -61
  200. package/dist/types/src/types/export_import.d.ts +0 -21
  201. package/dist/types/src/types/index.d.ts +0 -23
  202. package/dist/types/src/types/locales.d.ts +0 -4
  203. package/dist/types/src/types/modify_collections.d.ts +0 -5
  204. package/dist/types/src/types/plugins.d.ts +0 -279
  205. package/dist/types/src/types/properties.d.ts +0 -1176
  206. package/dist/types/src/types/property_config.d.ts +0 -70
  207. package/dist/types/src/types/relations.d.ts +0 -336
  208. package/dist/types/src/types/slots.d.ts +0 -252
  209. package/dist/types/src/types/translations.d.ts +0 -870
  210. package/dist/types/src/types/user_management_delegate.d.ts +0 -121
  211. package/dist/types/src/types/websockets.d.ts +0 -78
  212. package/dist/types/src/users/index.d.ts +0 -2
  213. package/dist/types/src/users/roles.d.ts +0 -22
  214. package/dist/types/src/users/user.d.ts +0 -46
  215. package/dist/ui/src/components/Alert.d.ts +0 -12
  216. package/dist/ui/src/components/Autocomplete.d.ts +0 -21
  217. package/dist/ui/src/components/Avatar.d.ts +0 -11
  218. package/dist/ui/src/components/Badge.d.ts +0 -8
  219. package/dist/ui/src/components/BooleanSwitch.d.ts +0 -14
  220. package/dist/ui/src/components/BooleanSwitchWithLabel.d.ts +0 -17
  221. package/dist/ui/src/components/Button.d.ts +0 -14
  222. package/dist/ui/src/components/Card.d.ts +0 -9
  223. package/dist/ui/src/components/CenteredView.d.ts +0 -9
  224. package/dist/ui/src/components/Checkbox.d.ts +0 -13
  225. package/dist/ui/src/components/Chip.d.ts +0 -26
  226. package/dist/ui/src/components/CircularProgress.d.ts +0 -5
  227. package/dist/ui/src/components/CircularProgressCenter.d.ts +0 -11
  228. package/dist/ui/src/components/Collapse.d.ts +0 -9
  229. package/dist/ui/src/components/ColorPicker.d.ts +0 -30
  230. package/dist/ui/src/components/Container.d.ts +0 -8
  231. package/dist/ui/src/components/DateTimeField.d.ts +0 -24
  232. package/dist/ui/src/components/DebouncedTextField.d.ts +0 -2
  233. package/dist/ui/src/components/Dialog.d.ts +0 -39
  234. package/dist/ui/src/components/DialogActions.d.ts +0 -7
  235. package/dist/ui/src/components/DialogContent.d.ts +0 -7
  236. package/dist/ui/src/components/DialogTitle.d.ts +0 -10
  237. package/dist/ui/src/components/ErrorBoundary.d.ts +0 -11
  238. package/dist/ui/src/components/ExpandablePanel.d.ts +0 -12
  239. package/dist/ui/src/components/FileUpload.d.ts +0 -23
  240. package/dist/ui/src/components/IconButton.d.ts +0 -12
  241. package/dist/ui/src/components/InfoLabel.d.ts +0 -5
  242. package/dist/ui/src/components/InputLabel.d.ts +0 -11
  243. package/dist/ui/src/components/Label.d.ts +0 -7
  244. package/dist/ui/src/components/LoadingButton.d.ts +0 -7
  245. package/dist/ui/src/components/Markdown.d.ts +0 -10
  246. package/dist/ui/src/components/Menu.d.ts +0 -23
  247. package/dist/ui/src/components/Menubar.d.ts +0 -80
  248. package/dist/ui/src/components/MultiSelect.d.ts +0 -48
  249. package/dist/ui/src/components/Paper.d.ts +0 -6
  250. package/dist/ui/src/components/Popover.d.ts +0 -24
  251. package/dist/ui/src/components/RadioGroup.d.ts +0 -28
  252. package/dist/ui/src/components/ResizablePanels.d.ts +0 -18
  253. package/dist/ui/src/components/SearchBar.d.ts +0 -22
  254. package/dist/ui/src/components/Select.d.ts +0 -43
  255. package/dist/ui/src/components/Separator.d.ts +0 -5
  256. package/dist/ui/src/components/Sheet.d.ts +0 -22
  257. package/dist/ui/src/components/Skeleton.d.ts +0 -6
  258. package/dist/ui/src/components/Slider.d.ts +0 -21
  259. package/dist/ui/src/components/Table.d.ts +0 -34
  260. package/dist/ui/src/components/Tabs.d.ts +0 -19
  261. package/dist/ui/src/components/TextField.d.ts +0 -58
  262. package/dist/ui/src/components/TextareaAutosize.d.ts +0 -43
  263. package/dist/ui/src/components/ToggleButtonGroup.d.ts +0 -30
  264. package/dist/ui/src/components/Tooltip.d.ts +0 -19
  265. package/dist/ui/src/components/Typography.d.ts +0 -36
  266. package/dist/ui/src/components/VirtualTable/VirtualTable.d.ts +0 -11
  267. package/dist/ui/src/components/VirtualTable/VirtualTableCell.d.ts +0 -21
  268. package/dist/ui/src/components/VirtualTable/VirtualTableHeader.d.ts +0 -29
  269. package/dist/ui/src/components/VirtualTable/VirtualTableHeaderRow.d.ts +0 -2
  270. package/dist/ui/src/components/VirtualTable/VirtualTableProps.d.ts +0 -243
  271. package/dist/ui/src/components/VirtualTable/VirtualTableRow.d.ts +0 -3
  272. package/dist/ui/src/components/VirtualTable/index.d.ts +0 -3
  273. package/dist/ui/src/components/VirtualTable/types.d.ts +0 -38
  274. package/dist/ui/src/components/common/SelectInputLabel.d.ts +0 -5
  275. package/dist/ui/src/components/index.d.ts +0 -53
  276. package/dist/ui/src/hooks/PortalContainerContext.d.ts +0 -31
  277. package/dist/ui/src/hooks/index.d.ts +0 -6
  278. package/dist/ui/src/hooks/useDebounceCallback.d.ts +0 -1
  279. package/dist/ui/src/hooks/useDebounceValue.d.ts +0 -1
  280. package/dist/ui/src/hooks/useDebouncedCallback.d.ts +0 -1
  281. package/dist/ui/src/hooks/useInjectStyles.d.ts +0 -7
  282. package/dist/ui/src/hooks/useOutsideAlerter.d.ts +0 -5
  283. package/dist/ui/src/icons/GitHubIcon.d.ts +0 -2
  284. package/dist/ui/src/icons/HandleIcon.d.ts +0 -1
  285. package/dist/ui/src/icons/Icon.d.ts +0 -20
  286. package/dist/ui/src/icons/cool_icon_keys.d.ts +0 -1
  287. package/dist/ui/src/icons/icon_keys.d.ts +0 -1
  288. package/dist/ui/src/icons/index.d.ts +0 -6
  289. package/dist/ui/src/index.d.ts +0 -5
  290. package/dist/ui/src/styles.d.ts +0 -12
  291. package/dist/ui/src/util/chip_colors.d.ts +0 -4
  292. package/dist/ui/src/util/cls.d.ts +0 -2
  293. package/dist/ui/src/util/debounce.d.ts +0 -10
  294. package/dist/ui/src/util/hash.d.ts +0 -1
  295. package/dist/ui/src/util/index.d.ts +0 -4
  296. package/dist/ui/src/util/key_to_icon_component.d.ts +0 -1
  297. /package/dist/{plugin-insights/src/engine → engine}/InsightsCache.d.ts +0 -0
  298. /package/dist/{plugin-insights/src/types → types}/index.d.ts +0 -0
  299. /package/dist/{plugin-insights/src/types → types}/widgets.d.ts +0 -0
  300. /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 { c } from "react-compiler-runtime";
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
- class InsightsCache {
7
- constructor(ttl = 6e4) {
8
- this.ttl = ttl;
9
- }
10
- cache = /* @__PURE__ */ new Map();
11
- inflight = /* @__PURE__ */ new Map();
12
- get(key) {
13
- const entry = this.cache.get(key);
14
- if (!entry) return null;
15
- if (Date.now() - entry.timestamp > this.ttl) {
16
- this.cache.delete(key);
17
- return null;
18
- }
19
- return entry.data;
20
- }
21
- set(key, data) {
22
- this.cache.set(key, {
23
- data,
24
- timestamp: Date.now()
25
- });
26
- this.inflight.delete(key);
27
- }
28
- getInflight(key) {
29
- return this.inflight.get(key) ?? null;
30
- }
31
- setInflight(key, promise) {
32
- this.inflight.set(key, promise);
33
- }
34
- invalidate(key) {
35
- if (key) {
36
- this.cache.delete(key);
37
- this.inflight.delete(key);
38
- } else {
39
- this.cache.clear();
40
- this.inflight.clear();
41
- }
42
- }
43
- }
44
- const InsightsContext = createContext(null);
45
- function InsightsProvider(t0) {
46
- const $ = c(7);
47
- const {
48
- cacheTTL,
49
- children
50
- } = t0;
51
- let t1;
52
- let t2;
53
- if ($[0] !== cacheTTL) {
54
- t2 = new InsightsCache(cacheTTL);
55
- $[0] = cacheTTL;
56
- $[1] = t2;
57
- } else {
58
- t2 = $[1];
59
- }
60
- t1 = t2;
61
- const cache = t1;
62
- let t3;
63
- let t4;
64
- if ($[2] !== cache) {
65
- t4 = {
66
- cache
67
- };
68
- $[2] = cache;
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
- return useContext(InsightsContext);
75
+ return useContext(InsightsContext);
88
76
  }
89
- function useInsightsData(definition, collectionSlug) {
90
- const $ = c(16);
91
- const engine = useInsightsEngine();
92
- const cache = engine?.cache ?? null;
93
- const {
94
- initialLoading,
95
- authLoading,
96
- user,
97
- loginSkipped
98
- } = useAuthController();
99
- const authReady = !initialLoading && !authLoading && (Boolean(user) || loginSkipped);
100
- const [data, setData] = useState(null);
101
- const [loading, setLoading] = useState(true);
102
- const [error, setError] = useState(null);
103
- const cacheKey = `${definition.id}:${collectionSlug ?? "global"}`;
104
- let t0;
105
- if ($[0] !== authReady || $[1] !== cache || $[2] !== cacheKey || $[3] !== definition) {
106
- t0 = () => {
107
- if (!authReady || !cache) {
108
- return;
109
- }
110
- let cancelled;
111
- cancelled = false;
112
- const cached = cache.get(cacheKey);
113
- if (cached) {
114
- setData(cached);
115
- setLoading(false);
116
- return;
117
- }
118
- const inflight = cache.getInflight(cacheKey);
119
- if (inflight) {
120
- setLoading(true);
121
- inflight.then((result) => {
122
- if (!cancelled) {
123
- setData(result);
124
- }
125
- }).catch((err) => {
126
- if (!cancelled) {
127
- setError(err instanceof Error ? err : new Error(String(err)));
128
- }
129
- }).finally(() => {
130
- if (!cancelled) {
131
- setLoading(false);
132
- }
133
- });
134
- return;
135
- }
136
- setLoading(true);
137
- setError(null);
138
- const promise = definition.data();
139
- cache.setInflight(cacheKey, promise);
140
- promise.then((result_0) => {
141
- cache.set(cacheKey, result_0);
142
- if (!cancelled) {
143
- setData(result_0);
144
- }
145
- }).catch((err_0) => {
146
- if (!cancelled) {
147
- setError(err_0 instanceof Error ? err_0 : new Error(String(err_0)));
148
- }
149
- }).finally(() => {
150
- if (!cancelled) {
151
- setLoading(false);
152
- }
153
- });
154
- return () => {
155
- cancelled = true;
156
- };
157
- };
158
- $[0] = authReady;
159
- $[1] = cache;
160
- $[2] = cacheKey;
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
- if (value === null || value === void 0) return "N/A";
198
- const options = {
199
- style: format?.style ?? "decimal",
200
- notation: format?.notation ?? "standard",
201
- maximumFractionDigits: format?.decimals ?? 1,
202
- minimumFractionDigits: format?.decimals ?? 1
203
- };
204
- if (format?.style === "currency") {
205
- options.currency = format.currency ?? "USD";
206
- }
207
- let formatted = new Intl.NumberFormat("en-US", options).format(value);
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
- function InsightsScorecardView(t0) {
214
- const $ = c(58);
215
- const {
216
- config,
217
- data,
218
- title,
219
- compact: t1,
220
- embedded: t2
221
- } = t0;
222
- const compact = t1 === void 0 ? false : t1;
223
- const embedded = t2 === void 0 ? false : t2;
224
- const containerRef = useRef(null);
225
- const [containerWidth, setContainerWidth] = useState(null);
226
- let t3;
227
- let t4;
228
- if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
229
- t3 = () => {
230
- if (!containerRef.current) {
231
- return;
232
- }
233
- setContainerWidth(containerRef.current.offsetWidth);
234
- const observer = new ResizeObserver((entries) => {
235
- for (const entry of entries) {
236
- setContainerWidth(entry.contentRect.width);
237
- }
238
- });
239
- observer.observe(containerRef.current);
240
- return () => observer.disconnect();
241
- };
242
- t4 = [];
243
- $[0] = t3;
244
- $[1] = t4;
245
- } else {
246
- t3 = $[0];
247
- t4 = $[1];
248
- }
249
- React.useLayoutEffect(t3, t4);
250
- const mainValue = data[config.value.field];
251
- let t5;
252
- if ($[2] !== config.value.format || $[3] !== mainValue) {
253
- t5 = typeof mainValue === "number" ? formatNumber(mainValue, config.value.format) : String(mainValue ?? "N/A");
254
- $[2] = config.value.format;
255
- $[3] = mainValue;
256
- $[4] = t5;
257
- } else {
258
- t5 = $[4];
259
- }
260
- const formattedValue = t5;
261
- let comparisonElement = null;
262
- if (config.comparison) {
263
- const comparisonValue = data[config.comparison.field];
264
- if (typeof comparisonValue === "number") {
265
- let t62;
266
- if ($[5] !== comparisonValue || $[6] !== config.comparison.format) {
267
- t62 = formatNumber(comparisonValue, config.comparison.format);
268
- $[5] = comparisonValue;
269
- $[6] = config.comparison.format;
270
- $[7] = t62;
271
- } else {
272
- t62 = $[7];
273
- }
274
- const formattedComparison = t62;
275
- const isPositive = comparisonValue > 0;
276
- const isNegative = comparisonValue < 0;
277
- let colorClass = "text-surface-500 dark:text-surface-400";
278
- if (config.comparison.intent === "increase_is_good") {
279
- if (isPositive) {
280
- colorClass = "text-emerald-500";
281
- }
282
- if (isNegative) {
283
- colorClass = "text-red-500";
284
- }
285
- } else {
286
- if (config.comparison.intent === "decrease_is_good") {
287
- if (isPositive) {
288
- colorClass = "text-red-500";
289
- }
290
- if (isNegative) {
291
- colorClass = "text-emerald-500";
292
- }
293
- }
294
- }
295
- const t72 = `font-medium ${compact ? "text-[10px]" : "text-xs"} ${colorClass}`;
296
- let t82;
297
- if ($[8] !== formattedComparison || $[9] !== t72) {
298
- t82 = /* @__PURE__ */ jsx("span", { className: t72, children: formattedComparison });
299
- $[8] = formattedComparison;
300
- $[9] = t72;
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
- function InsightWidgetSkeleton(t0) {
482
- const $ = c(35);
483
- const {
484
- config,
485
- compact: t1,
486
- embedded: t2
487
- } = t0;
488
- const compact = t1 === void 0 ? false : t1;
489
- const embedded = t2 === void 0 ? false : t2;
490
- const hasComparison = Boolean(config.comparison);
491
- const hasIcon = Boolean(config.icon);
492
- const hasDateRange = Boolean(config.dateRange);
493
- if (compact) {
494
- const t32 = 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";
495
- const t42 = !embedded && defaultBorderMixin;
496
- let t52;
497
- if ($[0] !== t32 || $[1] !== t42) {
498
- t52 = cls("animate-pulse", t32, t42);
499
- $[0] = t32;
500
- $[1] = t42;
501
- $[2] = t52;
502
- } else {
503
- t52 = $[2];
504
- }
505
- let t62;
506
- if ($[3] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
507
- t62 = /* @__PURE__ */ jsx("div", { className: "bg-surface-200 dark:bg-surface-700 rounded-sm", style: {
508
- height: 14,
509
- width: 48
510
- } });
511
- $[3] = t62;
512
- } else {
513
- t62 = $[3];
514
- }
515
- let t72;
516
- if ($[4] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
517
- t72 = /* @__PURE__ */ jsx("div", { className: "bg-surface-200 dark:bg-surface-700 rounded-sm", style: {
518
- height: 20,
519
- width: 40
520
- } });
521
- $[4] = t72;
522
- } else {
523
- t72 = $[4];
524
- }
525
- let t82;
526
- if ($[5] !== hasComparison) {
527
- t82 = hasComparison && /* @__PURE__ */ jsx("div", { className: "bg-surface-200/60 dark:bg-surface-700/60 rounded-sm", style: {
528
- height: 14,
529
- width: 28
530
- } });
531
- $[5] = hasComparison;
532
- $[6] = t82;
533
- } else {
534
- t82 = $[6];
535
- }
536
- let t92;
537
- if ($[7] !== t82) {
538
- t92 = /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-1.5", children: [
539
- t72,
540
- t82
541
- ] });
542
- $[7] = t82;
543
- $[8] = t92;
544
- } else {
545
- t92 = $[8];
546
- }
547
- let t102;
548
- if ($[9] !== t52 || $[10] !== t92) {
549
- t102 = /* @__PURE__ */ jsxs("div", { className: t52, children: [
550
- t62,
551
- t92
552
- ] });
553
- $[9] = t52;
554
- $[10] = t92;
555
- $[11] = t102;
556
- } else {
557
- t102 = $[11];
558
- }
559
- return t102;
560
- }
561
- const t3 = embedded ? "h-full px-5 py-4" : "rounded-lg bg-transparent border px-5 py-4";
562
- const t4 = !embedded && defaultBorderMixin;
563
- let t5;
564
- if ($[12] !== t3 || $[13] !== t4) {
565
- t5 = cls("animate-pulse", t3, t4);
566
- $[12] = t3;
567
- $[13] = t4;
568
- $[14] = t5;
569
- } else {
570
- t5 = $[14];
571
- }
572
- let t6;
573
- if ($[15] !== embedded) {
574
- t6 = embedded ? void 0 : {
575
- minHeight: 92
576
- };
577
- $[15] = embedded;
578
- $[16] = t6;
579
- } else {
580
- t6 = $[16];
581
- }
582
- let t7;
583
- if ($[17] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
584
- t7 = /* @__PURE__ */ jsx("div", { className: "bg-surface-200 dark:bg-surface-700 rounded", style: {
585
- height: 16,
586
- width: "60%"
587
- } });
588
- $[17] = t7;
589
- } else {
590
- t7 = $[17];
591
- }
592
- let t8;
593
- if ($[18] !== hasDateRange) {
594
- t8 = hasDateRange && /* @__PURE__ */ jsx("div", { className: "bg-surface-200/60 dark:bg-surface-700/60 rounded mt-0.5", style: {
595
- height: 14,
596
- width: "40%"
597
- } });
598
- $[18] = hasDateRange;
599
- $[19] = t8;
600
- } else {
601
- t8 = $[19];
602
- }
603
- let t9;
604
- if ($[20] !== t8) {
605
- t9 = /* @__PURE__ */ jsxs("div", { className: "flex flex-col min-w-0", children: [
606
- t7,
607
- t8
608
- ] });
609
- $[20] = t8;
610
- $[21] = t9;
611
- } else {
612
- t9 = $[21];
613
- }
614
- let t10;
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
- function InsightWidget(t0) {
677
- const $ = c(21);
678
- const {
679
- definition,
680
- collectionSlug,
681
- compact: t1,
682
- embedded: t2
683
- } = t0;
684
- const compact = t1 === void 0 ? false : t1;
685
- const embedded = t2 === void 0 ? false : t2;
686
- const {
687
- data,
688
- loading,
689
- error
690
- } = useInsightsData(definition, collectionSlug);
691
- if (loading) {
692
- let t32;
693
- if ($[0] !== compact || $[1] !== definition.scorecard || $[2] !== embedded) {
694
- t32 = /* @__PURE__ */ jsx(InsightWidgetSkeleton, { config: definition.scorecard, compact, embedded });
695
- $[0] = compact;
696
- $[1] = definition.scorecard;
697
- $[2] = embedded;
698
- $[3] = t32;
699
- } else {
700
- t32 = $[3];
701
- }
702
- return t32;
703
- }
704
- if (error) {
705
- const t32 = `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"}`}`;
706
- let t42;
707
- if ($[4] !== definition.title) {
708
- t42 = /* @__PURE__ */ jsx("div", { className: "font-semibold mb-1", children: definition.title });
709
- $[4] = definition.title;
710
- $[5] = t42;
711
- } else {
712
- t42 = $[5];
713
- }
714
- let t5;
715
- if ($[6] !== error.message) {
716
- t5 = /* @__PURE__ */ jsx("div", { children: error.message });
717
- $[6] = error.message;
718
- $[7] = t5;
719
- } else {
720
- t5 = $[7];
721
- }
722
- let t6;
723
- if ($[8] !== t32 || $[9] !== t42 || $[10] !== t5) {
724
- t6 = /* @__PURE__ */ jsxs("div", { className: t32, children: [
725
- t42,
726
- t5
727
- ] });
728
- $[8] = t32;
729
- $[9] = t42;
730
- $[10] = t5;
731
- $[11] = t6;
732
- } else {
733
- t6 = $[11];
734
- }
735
- return t6;
736
- }
737
- if (!data || data.rows.length === 0) {
738
- const t32 = `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"}`}`;
739
- let t42;
740
- if ($[12] !== definition.title || $[13] !== t32) {
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
- function HomeCardInsightSlot(t0) {
770
- const $ = c(8);
771
- const {
772
- slug,
773
- insights
774
- } = t0;
775
- if (!insights || insights.length === 0) {
776
- return null;
777
- }
778
- let t1;
779
- if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
780
- t1 = {
781
- minHeight: 46
782
- };
783
- $[0] = t1;
784
- } else {
785
- t1 = $[0];
786
- }
787
- let t2;
788
- if ($[1] !== insights || $[2] !== slug) {
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
- function HomeInsightsSlot(t0) {
816
- const $ = c(5);
817
- const {
818
- insights
819
- } = t0;
820
- if (!insights || insights.length === 0) {
821
- return null;
822
- }
823
- let t1;
824
- if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
825
- t1 = {
826
- minHeight: 92
827
- };
828
- $[0] = t1;
829
- } else {
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
- function CollectionInsightsInline(t0) {
855
- const $ = c(4);
856
- const {
857
- insights
858
- } = t0;
859
- if (!insights || insights.length === 0) {
860
- return null;
861
- }
862
- let t1;
863
- if ($[0] !== insights) {
864
- t1 = insights.map(_temp);
865
- $[0] = insights;
866
- $[1] = t1;
867
- } else {
868
- t1 = $[1];
869
- }
870
- let t2;
871
- if ($[2] !== t1) {
872
- t2 = /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-3 pb-4", children: t1 });
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
- const {
886
- insights,
887
- cacheTTL
888
- } = config;
889
- const slots = [];
890
- if (insights.home && insights.home.length > 0) {
891
- const homeInsights = insights.home;
892
- slots.push({
893
- slot: "home.children.start",
894
- Component: (props) => /* @__PURE__ */ jsx(HomeInsightsSlot, { ...props, insights: homeInsights }),
895
- order: 10
896
- });
897
- }
898
- if (insights.collections) {
899
- for (const [slug, defs] of Object.entries(insights.collections)) {
900
- if (defs.length === 0) continue;
901
- const collectionInsights = defs;
902
- slots.push({
903
- slot: "collection.insights",
904
- Component: (props) => {
905
- const path = props.path;
906
- const collectionSlug = path?.split("/").filter(Boolean).pop() ?? "";
907
- if (collectionSlug !== slug) return null;
908
- return /* @__PURE__ */ jsx(CollectionInsightsInline, { ...props, insights: collectionInsights });
909
- },
910
- order: 10
911
- });
912
- slots.push({
913
- slot: "home.card.insight",
914
- Component: (props) => {
915
- const cardSlug = props.slug;
916
- if (cardSlug !== slug) return null;
917
- return /* @__PURE__ */ jsx(HomeCardInsightSlot, { ...props, insights: collectionInsights });
918
- },
919
- order: 10
920
- });
921
- }
922
- }
923
- return {
924
- key: "plugin-insights",
925
- slots,
926
- providers: [{
927
- scope: "root",
928
- Component: InsightsProvider,
929
- props: {
930
- cacheTTL
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
- export {
936
- InsightWidget,
937
- InsightWidgetSkeleton,
938
- InsightsCache,
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