@burdenoff/microfe-bigconsole 2026.805.3 → 2026.812.2
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/dist/bigconsole/components/DrilldownDashboardRenderer.js +118 -106
- package/dist/bigconsole/components/DrilldownDashboardRenderer.js.map +1 -1
- package/dist/bigconsole/components/DrilldownModal.js +54 -50
- package/dist/bigconsole/components/DrilldownModal.js.map +1 -1
- package/dist/bigconsole/components/builder/BuilderCanvas.js +2 -2
- package/dist/bigconsole/components/builder/BuilderCanvas.js.map +1 -1
- package/dist/bigconsole/components/builder/BuilderTopbar.js +3 -3
- package/dist/bigconsole/components/builder/BuilderTopbar.js.map +1 -1
- package/dist/bigconsole/components/builder/inspector/AdvancedSection.js +24 -25
- package/dist/bigconsole/components/builder/inspector/AdvancedSection.js.map +1 -1
- package/dist/bigconsole/components/builder/inspector/AppearanceSection.js +40 -29
- package/dist/bigconsole/components/builder/inspector/AppearanceSection.js.map +1 -1
- package/dist/bigconsole/components/builder/inspector/DataBindingSection.js +135 -97
- package/dist/bigconsole/components/builder/inspector/DataBindingSection.js.map +1 -1
- package/dist/bigconsole/components/builder/inspector/GeneralSection.js +58 -44
- package/dist/bigconsole/components/builder/inspector/GeneralSection.js.map +1 -1
- package/dist/bigconsole/components/common/Section.js +70 -0
- package/dist/bigconsole/components/common/Section.js.map +1 -0
- package/dist/bigconsole/components/common/index.js +2 -0
- package/dist/bigconsole/components/common/sectionStyles.js +42 -0
- package/dist/bigconsole/components/common/sectionStyles.js.map +1 -0
- package/dist/bigconsole/components/dashboard/CreateDashboardDialog.js +209 -193
- package/dist/bigconsole/components/dashboard/CreateDashboardDialog.js.map +1 -1
- package/dist/bigconsole/components/dashboard/DashboardCanvas.js +213 -231
- package/dist/bigconsole/components/dashboard/DashboardCanvas.js.map +1 -1
- package/dist/bigconsole/components/dashboard/DashboardGrid.js +12 -18
- package/dist/bigconsole/components/dashboard/DashboardGrid.js.map +1 -1
- package/dist/bigconsole/components/dashboard/DashboardToolbar.js +109 -102
- package/dist/bigconsole/components/dashboard/DashboardToolbar.js.map +1 -1
- package/dist/bigconsole/components/dashboard/DrilldownBreadcrumb.js +81 -70
- package/dist/bigconsole/components/dashboard/DrilldownBreadcrumb.js.map +1 -1
- package/dist/bigconsole/components/dashboard/ExportDashboardDialog.js +125 -159
- package/dist/bigconsole/components/dashboard/ExportDashboardDialog.js.map +1 -1
- package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterChip.js +25 -23
- package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterChip.js.map +1 -1
- package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterEditor.js +3 -3
- package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterEditor.js.map +1 -1
- package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterPresetSelector.js +5 -3
- package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterPresetSelector.js.map +1 -1
- package/dist/bigconsole/components/dashboard/GlobalFiltersBar/GlobalFiltersBar.js +170 -284
- package/dist/bigconsole/components/dashboard/GlobalFiltersBar/GlobalFiltersBar.js.map +1 -1
- package/dist/bigconsole/components/dashboard/ImportDashboardDialog.js +147 -151
- package/dist/bigconsole/components/dashboard/ImportDashboardDialog.js.map +1 -1
- package/dist/bigconsole/components/dashboard/LiveBoardBanner.js +45 -53
- package/dist/bigconsole/components/dashboard/LiveBoardBanner.js.map +1 -1
- package/dist/bigconsole/components/dashboard/PageTabsManager/PageTabsManager.js +157 -148
- package/dist/bigconsole/components/dashboard/PageTabsManager/PageTabsManager.js.map +1 -1
- package/dist/bigconsole/components/dashboard/SavedViewsDropdown/SavedViewsDropdown.js +17 -8
- package/dist/bigconsole/components/dashboard/SavedViewsDropdown/SavedViewsDropdown.js.map +1 -1
- package/dist/bigconsole/components/dashboard/ShareDialog/ShareDialog.js +101 -95
- package/dist/bigconsole/components/dashboard/ShareDialog/ShareDialog.js.map +1 -1
- package/dist/bigconsole/components/dashboard/StoreTemplatePicker.js +20 -18
- package/dist/bigconsole/components/dashboard/StoreTemplatePicker.js.map +1 -1
- package/dist/bigconsole/components/dashboard/TemplateSetupBanner.js +26 -58
- package/dist/bigconsole/components/dashboard/TemplateSetupBanner.js.map +1 -1
- package/dist/bigconsole/components/dashboard/WidgetPalette.js +307 -231
- package/dist/bigconsole/components/dashboard/WidgetPalette.js.map +1 -1
- package/dist/bigconsole/components/datasink/CacheTTLSelector.js +29 -29
- package/dist/bigconsole/components/datasink/CacheTTLSelector.js.map +1 -1
- package/dist/bigconsole/components/datasink/DataSinkCard.js +78 -79
- package/dist/bigconsole/components/datasink/DataSinkCard.js.map +1 -1
- package/dist/bigconsole/components/datasink/DataSinkDataViewer.js +33 -34
- package/dist/bigconsole/components/datasink/DataSinkDataViewer.js.map +1 -1
- package/dist/bigconsole/components/datasink/DataSinkDeleteDialog.js +79 -56
- package/dist/bigconsole/components/datasink/DataSinkDeleteDialog.js.map +1 -1
- package/dist/bigconsole/components/datasink/DataSinkRefreshDialog.js +75 -65
- package/dist/bigconsole/components/datasink/DataSinkRefreshDialog.js.map +1 -1
- package/dist/bigconsole/components/datasink/DataSinkStatusBadge.js +1 -1
- package/dist/bigconsole/components/datasink/DataSinkStatusBadge.js.map +1 -1
- package/dist/bigconsole/components/datasink/DataSinkTableViewer.js +20 -19
- package/dist/bigconsole/components/datasink/DataSinkTableViewer.js.map +1 -1
- package/dist/bigconsole/components/datasink/ExportDataSinkDialog.js +127 -171
- package/dist/bigconsole/components/datasink/ExportDataSinkDialog.js.map +1 -1
- package/dist/bigconsole/components/datasink/ImportDataSinkDialog.js +263 -243
- package/dist/bigconsole/components/datasink/ImportDataSinkDialog.js.map +1 -1
- package/dist/bigconsole/components/datasink/LinkedParsersPanel.js +65 -66
- package/dist/bigconsole/components/datasink/LinkedParsersPanel.js.map +1 -1
- package/dist/bigconsole/components/datasink/LinkedWorkflowsPanel.js +121 -127
- package/dist/bigconsole/components/datasink/LinkedWorkflowsPanel.js.map +1 -1
- package/dist/bigconsole/components/datasink/RefreshTypeSelector.js +33 -26
- package/dist/bigconsole/components/datasink/RefreshTypeSelector.js.map +1 -1
- package/dist/bigconsole/components/datasink/SetupStateBadge.js +3 -3
- package/dist/bigconsole/components/datasink/SetupStateBadge.js.map +1 -1
- package/dist/bigconsole/components/datasink/WorkflowStatusPanel.js +95 -95
- package/dist/bigconsole/components/datasink/WorkflowStatusPanel.js.map +1 -1
- package/dist/bigconsole/components/parser/DataSinkSelector.js +154 -123
- package/dist/bigconsole/components/parser/DataSinkSelector.js.map +1 -1
- package/dist/bigconsole/components/parser/ExpressionEditor.js +3 -3
- package/dist/bigconsole/components/parser/ExpressionEditor.js.map +1 -1
- package/dist/bigconsole/components/parser/ImportParserDialog.js +235 -275
- package/dist/bigconsole/components/parser/ImportParserDialog.js.map +1 -1
- package/dist/bigconsole/components/parser/ParserCard.js +133 -114
- package/dist/bigconsole/components/parser/ParserCard.js.map +1 -1
- package/dist/bigconsole/components/parser/ParserSelector.js +191 -174
- package/dist/bigconsole/components/parser/ParserSelector.js.map +1 -1
- package/dist/bigconsole/components/parser/ParserStatusBadge.js +2 -2
- package/dist/bigconsole/components/parser/ParserStatusBadge.js.map +1 -1
- package/dist/bigconsole/components/parser/ParserTypeBadge.js +4 -4
- package/dist/bigconsole/components/parser/ParserTypeBadge.js.map +1 -1
- package/dist/bigconsole/components/pipeline/PipelineCard.js +134 -89
- package/dist/bigconsole/components/pipeline/PipelineCard.js.map +1 -1
- package/dist/bigconsole/components/pipeline/PipelineDeleteDialog.js +77 -64
- package/dist/bigconsole/components/pipeline/PipelineDeleteDialog.js.map +1 -1
- package/dist/bigconsole/components/pipeline/PipelineStatusBadge.js +7 -10
- package/dist/bigconsole/components/pipeline/PipelineStatusBadge.js.map +1 -1
- package/dist/bigconsole/components/preview/AiPreviewContent.js +31 -32
- package/dist/bigconsole/components/preview/AiPreviewContent.js.map +1 -1
- package/dist/bigconsole/components/preview/AiPreviewMiniPanel.js +152 -106
- package/dist/bigconsole/components/preview/AiPreviewMiniPanel.js.map +1 -1
- package/dist/bigconsole/components/preview/AiPreviewPanel.js +193 -152
- package/dist/bigconsole/components/preview/AiPreviewPanel.js.map +1 -1
- package/dist/bigconsole/components/preview/previewShared.js +41 -23
- package/dist/bigconsole/components/preview/previewShared.js.map +1 -1
- package/dist/bigconsole/components/widgets/ActionConfigDialog.js +342 -308
- package/dist/bigconsole/components/widgets/ActionConfigDialog.js.map +1 -1
- package/dist/bigconsole/components/widgets/DrilldownConfigPanel.js +262 -238
- package/dist/bigconsole/components/widgets/DrilldownConfigPanel.js.map +1 -1
- package/dist/bigconsole/components/widgets/PropertiesPanel.js +783 -867
- package/dist/bigconsole/components/widgets/PropertiesPanel.js.map +1 -1
- package/dist/bigconsole/components/widgets/WidgetErrorBoundary.js +7 -5
- package/dist/bigconsole/components/widgets/WidgetErrorBoundary.js.map +1 -1
- package/dist/bigconsole/components/widgets/WidgetHeader.js +7 -8
- package/dist/bigconsole/components/widgets/WidgetHeader.js.map +1 -1
- package/dist/bigconsole/components/widgets/WidgetRegistry.js +4 -4
- package/dist/bigconsole/components/widgets/WidgetRegistry.js.map +1 -1
- package/dist/bigconsole/components/widgets/WidgetWrapper.js +8 -7
- package/dist/bigconsole/components/widgets/WidgetWrapper.js.map +1 -1
- package/dist/bigconsole/components/widgets/adaptive-card/AdaptiveCardWidget.js +1 -1
- package/dist/bigconsole/components/widgets/adaptive-card/AdaptiveCardWidget.js.map +1 -1
- package/dist/bigconsole/components/widgets/calendar/CalendarWidget.js +2 -2
- package/dist/bigconsole/components/widgets/calendar/CalendarWidget.js.map +1 -1
- package/dist/bigconsole/components/widgets/chart/ChartConfig.js +11 -5
- package/dist/bigconsole/components/widgets/chart/ChartConfig.js.map +1 -1
- package/dist/bigconsole/components/widgets/comments/WidgetComments.js +19 -15
- package/dist/bigconsole/components/widgets/comments/WidgetComments.js.map +1 -1
- package/dist/bigconsole/components/widgets/event-logs/EventLogViewer.js +154 -102
- package/dist/bigconsole/components/widgets/event-logs/EventLogViewer.js.map +1 -1
- package/dist/bigconsole/components/widgets/gauge/CircularGauge.js +36 -32
- package/dist/bigconsole/components/widgets/gauge/CircularGauge.js.map +1 -1
- package/dist/bigconsole/components/widgets/gauge/GaugeConfig.js +85 -60
- package/dist/bigconsole/components/widgets/gauge/GaugeConfig.js.map +1 -1
- package/dist/bigconsole/components/widgets/gauge/GaugeWidget.js +64 -35
- package/dist/bigconsole/components/widgets/gauge/GaugeWidget.js.map +1 -1
- package/dist/bigconsole/components/widgets/gauge/LinearGauge.js +46 -45
- package/dist/bigconsole/components/widgets/gauge/LinearGauge.js.map +1 -1
- package/dist/bigconsole/components/widgets/heatmap/HeatmapConfig.js +35 -24
- package/dist/bigconsole/components/widgets/heatmap/HeatmapConfig.js.map +1 -1
- package/dist/bigconsole/components/widgets/iframe-widget/IframeWidget.js +2 -2
- package/dist/bigconsole/components/widgets/iframe-widget/IframeWidget.js.map +1 -1
- package/dist/bigconsole/components/widgets/kanban/KanbanWidget.js +4 -4
- package/dist/bigconsole/components/widgets/kanban/KanbanWidget.js.map +1 -1
- package/dist/bigconsole/components/widgets/kpi-comparison/KPIComparisonWidget.js +153 -90
- package/dist/bigconsole/components/widgets/kpi-comparison/KPIComparisonWidget.js.map +1 -1
- package/dist/bigconsole/components/widgets/list/ListConfig.js +8 -2
- package/dist/bigconsole/components/widgets/list/ListConfig.js.map +1 -1
- package/dist/bigconsole/components/widgets/list/ListItem.js +1 -1
- package/dist/bigconsole/components/widgets/list/ListItem.js.map +1 -1
- package/dist/bigconsole/components/widgets/map-widget/MapWidget.js +1 -1
- package/dist/bigconsole/components/widgets/map-widget/MapWidget.js.map +1 -1
- package/dist/bigconsole/components/widgets/metric-card/MetricCardConfig.js +14 -4
- package/dist/bigconsole/components/widgets/metric-card/MetricCardConfig.js.map +1 -1
- package/dist/bigconsole/components/widgets/metric-card/MetricCardWidget.js +112 -89
- package/dist/bigconsole/components/widgets/metric-card/MetricCardWidget.js.map +1 -1
- package/dist/bigconsole/components/widgets/metric-card/SparklineChart.js +21 -13
- package/dist/bigconsole/components/widgets/metric-card/SparklineChart.js.map +1 -1
- package/dist/bigconsole/components/widgets/metric-card/index.js +4 -4
- package/dist/bigconsole/components/widgets/pivot-table/PivotTableWidget.js +3 -3
- package/dist/bigconsole/components/widgets/pivot-table/PivotTableWidget.js.map +1 -1
- package/dist/bigconsole/components/widgets/progress/ProgressConfig.js +34 -25
- package/dist/bigconsole/components/widgets/progress/ProgressConfig.js.map +1 -1
- package/dist/bigconsole/components/widgets/progress/ProgressWidget.js +129 -114
- package/dist/bigconsole/components/widgets/progress/ProgressWidget.js.map +1 -1
- package/dist/bigconsole/components/widgets/retention/RetentionWidget.js +3 -3
- package/dist/bigconsole/components/widgets/retention/RetentionWidget.js.map +1 -1
- package/dist/bigconsole/components/widgets/states/EmptyState.js +6 -4
- package/dist/bigconsole/components/widgets/states/EmptyState.js.map +1 -1
- package/dist/bigconsole/components/widgets/states/ErrorState.js +7 -5
- package/dist/bigconsole/components/widgets/states/ErrorState.js.map +1 -1
- package/dist/bigconsole/components/widgets/states/LoadingState.js +5 -4
- package/dist/bigconsole/components/widgets/states/LoadingState.js.map +1 -1
- package/dist/bigconsole/components/widgets/table/TableConfig.js +10 -4
- package/dist/bigconsole/components/widgets/table/TableConfig.js.map +1 -1
- package/dist/bigconsole/components/widgets/table/TableHeader.js +1 -1
- package/dist/bigconsole/components/widgets/table/TableHeader.js.map +1 -1
- package/dist/bigconsole/components/widgets/table/TableWidget.js +1 -1
- package/dist/bigconsole/components/widgets/table/TableWidget.js.map +1 -1
- package/dist/bigconsole/components/widgets/text-widget/TextWidget.js +1 -1
- package/dist/bigconsole/components/widgets/text-widget/TextWidget.js.map +1 -1
- package/dist/bigconsole/components/widgets/timeline/TimelineWidget.js +3 -3
- package/dist/bigconsole/components/widgets/timeline/TimelineWidget.js.map +1 -1
- package/dist/bigconsole/components/widgets/utils/conditionalFormat.js +2 -2
- package/dist/bigconsole/components/widgets/utils/conditionalFormat.js.map +1 -1
- package/dist/bigconsole/components/widgets/utils/targetProgress.js +13 -0
- package/dist/bigconsole/components/widgets/utils/targetProgress.js.map +1 -0
- package/dist/bigconsole/components/widgets/webhook-config/WebhookConfigPanel.js +112 -85
- package/dist/bigconsole/components/widgets/webhook-config/WebhookConfigPanel.js.map +1 -1
- package/dist/bigconsole/components/widgets/widget-actions/WidgetActions.js +28 -20
- package/dist/bigconsole/components/widgets/widget-actions/WidgetActions.js.map +1 -1
- package/dist/bigconsole/components/workflow/WorkflowCard.js +92 -77
- package/dist/bigconsole/components/workflow/WorkflowCard.js.map +1 -1
- package/dist/bigconsole/components/workflow/WorkflowDeleteDialog.js +70 -51
- package/dist/bigconsole/components/workflow/WorkflowDeleteDialog.js.map +1 -1
- package/dist/bigconsole/components/workflow/WorkflowStatusBadge.js +16 -17
- package/dist/bigconsole/components/workflow/WorkflowStatusBadge.js.map +1 -1
- package/dist/bigconsole/customWidget/CustomWidgetBuilderSheet.js +6 -4
- package/dist/bigconsole/customWidget/CustomWidgetBuilderSheet.js.map +1 -1
- package/dist/bigconsole/customWidget/CustomWidgetProposalPanel.js +360 -338
- package/dist/bigconsole/customWidget/CustomWidgetProposalPanel.js.map +1 -1
- package/dist/bigconsole/hooks/useModalFocusTrap.js +53 -0
- package/dist/bigconsole/hooks/useModalFocusTrap.js.map +1 -0
- package/dist/bigconsole/pages/DashboardBuilderPage.js +209 -203
- package/dist/bigconsole/pages/DashboardBuilderPage.js.map +1 -1
- package/dist/bigconsole/pages/DashboardViewPage.js +126 -120
- package/dist/bigconsole/pages/DashboardViewPage.js.map +1 -1
- package/dist/bigconsole/pages/DashboardsPage.js +838 -800
- package/dist/bigconsole/pages/DashboardsPage.js.map +1 -1
- package/dist/bigconsole/pages/DataParsersPage.js +263 -315
- package/dist/bigconsole/pages/DataParsersPage.js.map +1 -1
- package/dist/bigconsole/pages/DataSinkBuilderPage.js +301 -341
- package/dist/bigconsole/pages/DataSinkBuilderPage.js.map +1 -1
- package/dist/bigconsole/pages/DataSinkViewPage.js +346 -339
- package/dist/bigconsole/pages/DataSinkViewPage.js.map +1 -1
- package/dist/bigconsole/pages/DataSinksPage.js +356 -458
- package/dist/bigconsole/pages/DataSinksPage.js.map +1 -1
- package/dist/bigconsole/pages/DataSourcesPage.js +7 -2
- package/dist/bigconsole/pages/DataSourcesPage.js.map +1 -1
- package/dist/bigconsole/pages/HomePage.js +186 -193
- package/dist/bigconsole/pages/HomePage.js.map +1 -1
- package/dist/bigconsole/pages/InsightsPage.js +887 -740
- package/dist/bigconsole/pages/InsightsPage.js.map +1 -1
- package/dist/bigconsole/pages/ParserBuilderPage.js +679 -637
- package/dist/bigconsole/pages/ParserBuilderPage.js.map +1 -1
- package/dist/bigconsole/pages/ParserViewPage.js +368 -342
- package/dist/bigconsole/pages/ParserViewPage.js.map +1 -1
- package/dist/bigconsole/pages/PipelineBuilderPage.js +537 -444
- package/dist/bigconsole/pages/PipelineBuilderPage.js.map +1 -1
- package/dist/bigconsole/pages/PipelineViewPage.js +412 -377
- package/dist/bigconsole/pages/PipelineViewPage.js.map +1 -1
- package/dist/bigconsole/pages/PipelinesPage.js +248 -298
- package/dist/bigconsole/pages/PipelinesPage.js.map +1 -1
- package/dist/bigconsole/pages/WidgetDemoPage.js +251 -153
- package/dist/bigconsole/pages/WidgetDemoPage.js.map +1 -1
- package/dist/bigconsole/pages/WorkflowViewPage.js +381 -371
- package/dist/bigconsole/pages/WorkflowViewPage.js.map +1 -1
- package/dist/bigconsole/pages/WorkflowsPage.js +171 -286
- package/dist/bigconsole/pages/WorkflowsPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -3,155 +3,154 @@ import i from "./ProgressBar.js";
|
|
|
3
3
|
import { memo as a, useCallback as o, useMemo as s } from "react";
|
|
4
4
|
import { jsx as c, jsxs as l } from "react/jsx-runtime";
|
|
5
5
|
//#region src/bigconsole/components/widgets/progress/ProgressWidget.tsx
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
6
|
+
function u(e) {
|
|
7
|
+
if (typeof e == "number" && Number.isFinite(e)) return e;
|
|
8
|
+
if (typeof e == "string") {
|
|
9
|
+
let t = e.trim();
|
|
10
|
+
if (t === "") return null;
|
|
11
|
+
let n = Number(t);
|
|
12
|
+
if (Number.isFinite(n)) return n;
|
|
13
|
+
}
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
function d(e, t) {
|
|
17
|
+
for (let n of [e, t]) if (typeof n == "number" && Number.isFinite(n) && n > 0) return n;
|
|
18
|
+
return 100;
|
|
19
|
+
}
|
|
20
|
+
function f(e, t) {
|
|
21
|
+
switch (t) {
|
|
22
|
+
case "currency": return new Intl.NumberFormat("en-US", {
|
|
23
|
+
style: "currency",
|
|
24
|
+
currency: "USD",
|
|
25
|
+
maximumFractionDigits: 0
|
|
26
|
+
}).format(e);
|
|
27
|
+
case "compact": return new Intl.NumberFormat("en-US", {
|
|
28
|
+
notation: "compact",
|
|
29
|
+
maximumFractionDigits: 1
|
|
30
|
+
}).format(e);
|
|
31
|
+
default: return new Intl.NumberFormat("en-US").format(e);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function p(e) {
|
|
35
|
+
let t = u(e.target);
|
|
36
|
+
return {
|
|
37
|
+
...t === null ? {} : { target: t },
|
|
38
|
+
...typeof e.description == "string" ? { description: e.description } : {}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
var m = a(function({ widget: a, data: m, onClick: h, onDrilldown: g }) {
|
|
42
|
+
let _ = a.config, v = s(() => {
|
|
43
|
+
if (!m) return;
|
|
44
|
+
if (typeof m == "object" && !Array.isArray(m) && "value" in m) {
|
|
45
|
+
let e = m, t = u(e.value);
|
|
46
|
+
if (t !== null) {
|
|
47
|
+
let n = u(e.max), r = u(e.target);
|
|
48
|
+
return {
|
|
49
|
+
...m,
|
|
50
|
+
value: t,
|
|
51
|
+
max: n ?? void 0,
|
|
52
|
+
target: r ?? void 0
|
|
17
53
|
};
|
|
18
54
|
}
|
|
19
55
|
}
|
|
20
|
-
let i = e(
|
|
56
|
+
let i = e(m);
|
|
21
57
|
if (!i) return;
|
|
22
|
-
let a =
|
|
23
|
-
if (typeof e == "number" && Number.isFinite(e)) return e;
|
|
24
|
-
if (typeof e == "string") {
|
|
25
|
-
let t = Number(e);
|
|
26
|
-
if (Number.isFinite(t)) return t;
|
|
27
|
-
}
|
|
28
|
-
return null;
|
|
29
|
-
}, c = typeof i.label == "string" ? i.label : typeof i.name == "string" ? i.name : void 0;
|
|
58
|
+
let a = _?.valueField, o = (e, t) => t.split(".").reduce((e, t) => e && typeof e == "object" ? e[t] : void 0, e), s = typeof i.label == "string" ? i.label : typeof i.name == "string" ? i.name : void 0;
|
|
30
59
|
if (a) {
|
|
31
|
-
let e = r(
|
|
60
|
+
let e = r(m) ?? [i];
|
|
32
61
|
for (let n of e) if (n && typeof n == "object") {
|
|
33
|
-
let e = n, r =
|
|
62
|
+
let e = n, r = u(o(e, a));
|
|
34
63
|
if (r !== null) {
|
|
35
|
-
let n = typeof e.label == "string" ? e.label : typeof e.name == "string" ? e.name :
|
|
64
|
+
let n = typeof e.label == "string" ? e.label : typeof e.name == "string" ? e.name : s;
|
|
36
65
|
return {
|
|
37
66
|
value: r,
|
|
38
|
-
max:
|
|
39
|
-
label: n
|
|
67
|
+
max: _?.max ?? u(e.max) ?? t(m, a) ?? void 0,
|
|
68
|
+
label: n,
|
|
69
|
+
...p(e)
|
|
40
70
|
};
|
|
41
71
|
}
|
|
42
72
|
}
|
|
43
73
|
return;
|
|
44
74
|
}
|
|
45
|
-
let
|
|
46
|
-
if (!
|
|
47
|
-
let
|
|
48
|
-
if (
|
|
49
|
-
value:
|
|
50
|
-
max:
|
|
51
|
-
label:
|
|
75
|
+
let c = n(i);
|
|
76
|
+
if (!c) return;
|
|
77
|
+
let l = u(i[c]);
|
|
78
|
+
if (l !== null) return {
|
|
79
|
+
value: l,
|
|
80
|
+
max: _?.max ?? u(i.max) ?? t(m, c) ?? void 0,
|
|
81
|
+
label: s,
|
|
82
|
+
...p(i)
|
|
52
83
|
};
|
|
53
|
-
}, [
|
|
54
|
-
if (
|
|
55
|
-
|
|
56
|
-
return m.value / e * 100;
|
|
57
|
-
}, [m, p?.max]), g = s(() => {
|
|
58
|
-
if (!m?.target) return;
|
|
59
|
-
let e = m.max || p?.max || 100;
|
|
60
|
-
return m.target / e * 100;
|
|
61
|
-
}, [m, p?.max]), _ = s(() => p?.milestones?.length ? p.milestones.map((e) => ({
|
|
84
|
+
}, [m, _]), y = s(() => v ? v.value / d(v.max, _?.max) * 100 : 0, [v, _?.max]), b = s(() => {
|
|
85
|
+
if (v?.target !== void 0) return v.target / d(v.max, _?.max) * 100;
|
|
86
|
+
}, [v, _?.max]), x = s(() => _?.milestones?.length ? _.milestones.map((e) => ({
|
|
62
87
|
position: e.position ?? e.value ?? 0,
|
|
63
88
|
label: e.label,
|
|
64
|
-
reached:
|
|
65
|
-
})) : [], [
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
}, [m, p?.format]), b = s(() => {
|
|
80
|
-
let e = m?.max || p?.max || 100;
|
|
81
|
-
switch (p?.format || "number") {
|
|
82
|
-
case "currency": return new Intl.NumberFormat("en-US", {
|
|
83
|
-
style: "currency",
|
|
84
|
-
currency: "USD",
|
|
85
|
-
maximumFractionDigits: 0
|
|
86
|
-
}).format(e);
|
|
87
|
-
case "compact": return new Intl.NumberFormat("en-US", {
|
|
88
|
-
notation: "compact",
|
|
89
|
-
maximumFractionDigits: 1
|
|
90
|
-
}).format(e);
|
|
91
|
-
default: return new Intl.NumberFormat("en-US").format(e);
|
|
92
|
-
}
|
|
89
|
+
reached: y >= (e.position ?? e.value ?? 0)
|
|
90
|
+
})) : [], [_?.milestones, y]), S = s(() => _?.colorMode === "dynamic" ? "dynamic" : _?.colorMode === "success" ? "success" : _?.colorMode === "warning" ? "warning" : _?.colorMode === "error" ? "error" : "default", [_?.colorMode]), C = s(() => v ? f(v.value, _?.format || "number") : "0", [v, _?.format]), w = s(() => f(d(v?.max, _?.max), _?.format || "number"), [
|
|
91
|
+
v?.max,
|
|
92
|
+
_?.max,
|
|
93
|
+
_?.format
|
|
94
|
+
]), T = s(() => {
|
|
95
|
+
if (v?.target !== void 0) return f(v.target, _?.format || "number");
|
|
96
|
+
}, [v?.target, _?.format]), E = o(() => {
|
|
97
|
+
g && v ? g({
|
|
98
|
+
value: v.value,
|
|
99
|
+
max: v.max,
|
|
100
|
+
target: v.target,
|
|
101
|
+
label: v.label,
|
|
102
|
+
percentage: y
|
|
103
|
+
}) : h && a.drilldowns?.length && h();
|
|
93
104
|
}, [
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
p?.format
|
|
97
|
-
]), x = o(() => {
|
|
98
|
-
f && m ? f({
|
|
99
|
-
value: m.value,
|
|
100
|
-
max: m.max,
|
|
101
|
-
target: m.target,
|
|
102
|
-
label: m.label,
|
|
103
|
-
percentage: h
|
|
104
|
-
}) : d && a.drilldowns?.length && d();
|
|
105
|
-
}, [
|
|
106
|
-
d,
|
|
107
|
-
f,
|
|
105
|
+
h,
|
|
106
|
+
g,
|
|
108
107
|
a.drilldowns,
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
]),
|
|
112
|
-
return
|
|
108
|
+
v,
|
|
109
|
+
y
|
|
110
|
+
]), D = !!(a.drilldown?.enabled || h && a.drilldowns?.length);
|
|
111
|
+
return v ? /* @__PURE__ */ l("div", {
|
|
113
112
|
className: `
|
|
114
113
|
flex flex-col h-full p-4
|
|
115
|
-
${
|
|
114
|
+
${D ? "cursor-pointer hover:bg-bg-sunken/50 transition-colors" : ""}
|
|
116
115
|
`,
|
|
117
|
-
onClick:
|
|
118
|
-
role:
|
|
119
|
-
tabIndex:
|
|
116
|
+
onClick: E,
|
|
117
|
+
role: D ? "button" : void 0,
|
|
118
|
+
tabIndex: D ? 0 : void 0,
|
|
120
119
|
onKeyDown: (e) => {
|
|
121
|
-
|
|
120
|
+
D && (e.key === "Enter" || e.key === " ") && (e.preventDefault(), E());
|
|
122
121
|
},
|
|
123
122
|
children: [
|
|
124
123
|
/* @__PURE__ */ l("div", {
|
|
125
124
|
className: "flex items-start justify-between mb-4",
|
|
126
125
|
children: [/* @__PURE__ */ l("div", {
|
|
127
126
|
className: "flex-1",
|
|
128
|
-
children: [
|
|
127
|
+
children: [v.label && /* @__PURE__ */ c("h4", {
|
|
129
128
|
className: "text-sm font-medium text-text-primary",
|
|
130
|
-
children:
|
|
131
|
-
}),
|
|
129
|
+
children: v.label
|
|
130
|
+
}), v.description && /* @__PURE__ */ c("p", {
|
|
132
131
|
className: "text-xs text-text-secondary mt-0.5",
|
|
133
|
-
children:
|
|
132
|
+
children: v.description
|
|
134
133
|
})]
|
|
135
134
|
}), /* @__PURE__ */ l("div", {
|
|
136
135
|
className: "text-right",
|
|
137
136
|
children: [/* @__PURE__ */ c("span", {
|
|
138
137
|
className: "text-2xl font-bold text-text-primary",
|
|
139
|
-
children:
|
|
140
|
-
}),
|
|
138
|
+
children: C
|
|
139
|
+
}), _?.showMax !== !1 && /* @__PURE__ */ l("span", {
|
|
141
140
|
className: "text-sm text-text-secondary",
|
|
142
|
-
children: [" / ",
|
|
141
|
+
children: [" / ", w]
|
|
143
142
|
})]
|
|
144
143
|
})]
|
|
145
144
|
}),
|
|
146
145
|
/* @__PURE__ */ c(i, {
|
|
147
|
-
value:
|
|
148
|
-
target:
|
|
149
|
-
milestones:
|
|
150
|
-
height:
|
|
151
|
-
color:
|
|
152
|
-
showPercentage:
|
|
146
|
+
value: y,
|
|
147
|
+
target: b,
|
|
148
|
+
milestones: x,
|
|
149
|
+
height: _?.barHeight || 12,
|
|
150
|
+
color: S,
|
|
151
|
+
showPercentage: _?.showPercentage !== !1
|
|
153
152
|
}),
|
|
154
|
-
|
|
153
|
+
v.target !== void 0 && /* @__PURE__ */ l("div", {
|
|
155
154
|
className: "mt-3 flex items-center gap-2 text-xs",
|
|
156
155
|
children: [/* @__PURE__ */ c("svg", {
|
|
157
156
|
className: "w-4 h-4 text-text-secondary",
|
|
@@ -161,20 +160,19 @@ var u = a(function({ widget: a, data: u, onClick: d, onDrilldown: f }) {
|
|
|
161
160
|
}), /* @__PURE__ */ l("span", {
|
|
162
161
|
className: "text-text-secondary",
|
|
163
162
|
children: [
|
|
164
|
-
"Target:",
|
|
165
|
-
" ",
|
|
163
|
+
"Target: ",
|
|
166
164
|
/* @__PURE__ */ c("span", {
|
|
167
165
|
className: "font-medium text-text-primary",
|
|
168
|
-
children:
|
|
166
|
+
children: T
|
|
169
167
|
}),
|
|
170
|
-
|
|
168
|
+
y >= (b || 0) && /* @__PURE__ */ c("span", {
|
|
171
169
|
className: "text-status-success-text ml-1",
|
|
172
170
|
children: "Reached!"
|
|
173
171
|
})
|
|
174
172
|
]
|
|
175
173
|
})]
|
|
176
174
|
}),
|
|
177
|
-
|
|
175
|
+
D && /* @__PURE__ */ c("div", {
|
|
178
176
|
className: "mt-auto pt-3 flex items-center justify-end",
|
|
179
177
|
children: /* @__PURE__ */ l("span", {
|
|
180
178
|
className: "text-xs text-text-secondary flex items-center gap-1",
|
|
@@ -193,9 +191,26 @@ var u = a(function({ widget: a, data: u, onClick: d, onDrilldown: f }) {
|
|
|
193
191
|
})
|
|
194
192
|
})
|
|
195
193
|
]
|
|
196
|
-
}) :
|
|
194
|
+
}) : /* @__PURE__ */ c("div", {
|
|
195
|
+
className: "flex items-center justify-center h-full text-text-secondary text-sm",
|
|
196
|
+
children: /* @__PURE__ */ l("div", {
|
|
197
|
+
className: "text-center",
|
|
198
|
+
children: [/* @__PURE__ */ c("svg", {
|
|
199
|
+
className: "w-12 h-12 mx-auto mb-2 text-text-tertiary",
|
|
200
|
+
fill: "none",
|
|
201
|
+
stroke: "currentColor",
|
|
202
|
+
viewBox: "0 0 24 24",
|
|
203
|
+
children: /* @__PURE__ */ c("path", {
|
|
204
|
+
strokeLinecap: "round",
|
|
205
|
+
strokeLinejoin: "round",
|
|
206
|
+
strokeWidth: 1.5,
|
|
207
|
+
d: "M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"
|
|
208
|
+
})
|
|
209
|
+
}), /* @__PURE__ */ c("p", { children: "No progress data" })]
|
|
210
|
+
})
|
|
211
|
+
});
|
|
197
212
|
});
|
|
198
213
|
//#endregion
|
|
199
|
-
export {
|
|
214
|
+
export { m as default };
|
|
200
215
|
|
|
201
216
|
//# sourceMappingURL=ProgressWidget.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProgressWidget.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/progress/ProgressWidget.tsx"],"sourcesContent":["/**\n * ProgressWidget Component\n *\n * Progress bar widget with target, milestones, and color stages.\n */\n\nimport { type FC, memo, useMemo, useCallback } from 'react';\nimport type { Widget, ProgressConfig } from '../../../types';\nimport { ProgressBar, type Milestone } from './ProgressBar';\nimport { firstObjectRow, inferValueField, inferMaxValue, unwrapRows } from '../utils/inferFields';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ProgressWidgetProps {\n /** Widget configuration */\n widget: Widget;\n /** Progress data */\n data?: ProgressData;\n /** Click handler for drilldown */\n onClick?: () => void;\n /** Drilldown handler - receives progress data */\n onDrilldown?: (selectedData: Record<string, unknown>) => void;\n}\n\nexport interface ProgressData {\n /** Current value */\n value: number;\n /** Maximum value (for calculating percentage) */\n max?: number;\n /** Target value */\n target?: number;\n /** Label for the progress */\n label?: string;\n /** Description text */\n description?: string;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const ProgressWidget: FC<ProgressWidgetProps> = memo(function ProgressWidget({\n widget,\n data: rawData,\n onClick,\n onDrilldown,\n}) {\n const config = widget.config as unknown as ProgressConfig | undefined;\n\n // BOFF-2246 smart default: if rawData is an array (or envelope) rather than\n // the expected `ProgressData` single object, pull the configured valueField\n // (flat OR dotted, e.g. `metrics.revenue`) or fall back to the first\n // numeric field. Auto-compute a sensible max from the dataset.\n const normalizedData = useMemo((): ProgressData | undefined => {\n if (!rawData) return undefined;\n // Already the expected shape — pass through. Accept both number and\n // numeric-string `value`, since parsers sometimes serialize metrics as\n // strings and the pre-BOFF-2246 code path rendered those via implicit\n // JS coercion in percentage math.\n if (typeof rawData === 'object' && !Array.isArray(rawData) && 'value' in rawData) {\n const rawValue = (rawData as { value: unknown }).value;\n if (typeof rawValue === 'number') {\n return rawData as ProgressData;\n }\n if (typeof rawValue === 'string') {\n const parsed = Number(rawValue);\n if (Number.isFinite(parsed)) {\n // Coerce to number so downstream math is deterministic.\n return { ...(rawData as ProgressData), value: parsed };\n }\n }\n }\n const row = firstObjectRow(rawData);\n if (!row) return undefined;\n const configuredField = (config as Record<string, unknown> | undefined)?.valueField as string | undefined;\n\n // Supports dotted paths like `metrics.revenue` so nested valueFields\n // configured in widget config resolve on array/envelope payloads the\n // same way they do on single-object payloads.\n const getNestedValue = (obj: Record<string, unknown>, path: string): unknown =>\n path.split('.').reduce<unknown>((current, key) => {\n return current && typeof current === 'object' ? (current as Record<string, unknown>)[key] : undefined;\n }, obj);\n\n // Coerce `number` or finite numeric string to a number. Mirrors the\n // pass-through branch above so the row path doesn't silently fall back\n // to inference when a parser serializes metrics as strings.\n const toNumber = (v: unknown): number | null => {\n if (typeof v === 'number' && Number.isFinite(v)) return v;\n if (typeof v === 'string') {\n const n = Number(v);\n if (Number.isFinite(n)) return n;\n }\n return null;\n };\n\n const labelFromRow =\n typeof row.label === 'string' ? row.label : typeof row.name === 'string' ? row.name : undefined;\n\n // Configured field is authoritative — scan every row for it so sparse\n // envelopes like `{data:[{id:1},{revenue:120}]}` with\n // `valueField:\"revenue\"` still resolve. If no row carries it, return\n // undefined rather than substituting an unrelated numeric column from\n // row[0] via inference (that would silently misrepresent user config).\n if (configuredField) {\n const rows = unwrapRows(rawData) ?? [row];\n for (const r of rows) {\n if (r && typeof r === 'object') {\n const resolvedRow = r as Record<string, unknown>;\n const resolved = toNumber(getNestedValue(resolvedRow, configuredField));\n if (resolved !== null) {\n const rowLabel =\n typeof resolvedRow.label === 'string'\n ? resolvedRow.label\n : typeof resolvedRow.name === 'string'\n ? resolvedRow.name\n : labelFromRow;\n return {\n value: resolved,\n max: config?.max ?? inferMaxValue(rawData, configuredField) ?? undefined,\n label: rowLabel,\n };\n }\n }\n }\n return undefined;\n }\n\n // Fall back to inference (only when no configured field). `inferValueField`\n // already filters to numeric fields, but we still coerce here for symmetry\n // with the configured branch above.\n const inferred = inferValueField(row);\n if (!inferred) return undefined;\n const inferredValue = toNumber(row[inferred]);\n if (inferredValue === null) return undefined;\n return {\n value: inferredValue,\n max: config?.max ?? inferMaxValue(rawData, inferred) ?? undefined,\n label: labelFromRow,\n };\n }, [rawData, config]);\n\n // Downstream render uses the normalized shape. Name kept as `data` so the\n // rest of the component body (formattedValue, handleClick, header, etc.)\n // stays unchanged.\n const data = normalizedData;\n\n // Calculate percentage\n const percentage = useMemo(() => {\n if (!data) return 0;\n const max = data.max || config?.max || 100;\n return (data.value / max) * 100;\n }, [data, config?.max]);\n\n // Calculate target percentage\n const targetPercentage = useMemo(() => {\n if (!data?.target) return undefined;\n const max = data.max || config?.max || 100;\n return (data.target / max) * 100;\n }, [data, config?.max]);\n\n // Build milestones from config\n const milestones: Milestone[] = useMemo(() => {\n if (!config?.milestones?.length) return [];\n return config.milestones.map((m) => ({\n position: m.position ?? m.value ?? 0,\n label: m.label,\n reached: percentage >= (m.position ?? m.value ?? 0),\n }));\n }, [config?.milestones, percentage]);\n\n // Determine color mode\n const colorMode = useMemo(() => {\n if (config?.colorMode === 'dynamic') return 'dynamic';\n if (config?.colorMode === 'success') return 'success';\n if (config?.colorMode === 'warning') return 'warning';\n if (config?.colorMode === 'error') return 'error';\n return 'default';\n }, [config?.colorMode]);\n\n // Format value\n const formattedValue = useMemo(() => {\n if (!data) return '0';\n const format = config?.format || 'number';\n\n switch (format) {\n case 'currency':\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD',\n maximumFractionDigits: 0,\n }).format(data.value);\n case 'compact':\n return new Intl.NumberFormat('en-US', {\n notation: 'compact',\n maximumFractionDigits: 1,\n }).format(data.value);\n case 'number':\n default:\n return new Intl.NumberFormat('en-US').format(data.value);\n }\n }, [data, config?.format]);\n\n const formattedMax = useMemo(() => {\n const max = data?.max || config?.max || 100;\n const format = config?.format || 'number';\n\n switch (format) {\n case 'currency':\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD',\n maximumFractionDigits: 0,\n }).format(max);\n case 'compact':\n return new Intl.NumberFormat('en-US', {\n notation: 'compact',\n maximumFractionDigits: 1,\n }).format(max);\n case 'number':\n default:\n return new Intl.NumberFormat('en-US').format(max);\n }\n }, [data?.max, config?.max, config?.format]);\n\n const handleClick = useCallback(() => {\n // Support both legacy onClick and new onDrilldown\n if (onDrilldown && data) {\n onDrilldown({\n value: data.value,\n max: data.max,\n target: data.target,\n label: data.label,\n percentage,\n });\n } else if (onClick && widget.drilldowns?.length) {\n onClick();\n }\n }, [onClick, onDrilldown, widget.drilldowns, data, percentage]);\n\n const hasDrilldown = !!widget.drilldown?.enabled;\n const isClickable = Boolean(hasDrilldown || (onClick && widget.drilldowns?.length));\n\n if (!data) {\n return null;\n }\n\n return (\n <div\n className={`\n flex flex-col h-full p-4\n ${isClickable ? 'cursor-pointer hover:bg-bg-sunken/50 transition-colors' : ''}\n `}\n onClick={handleClick}\n role={isClickable ? 'button' : undefined}\n tabIndex={isClickable ? 0 : undefined}\n onKeyDown={(e) => {\n if (isClickable && (e.key === 'Enter' || e.key === ' ')) {\n e.preventDefault();\n handleClick();\n }\n }}\n >\n {/* Header */}\n <div className=\"flex items-start justify-between mb-4\">\n <div className=\"flex-1\">\n {data.label && <h4 className=\"text-sm font-medium text-text-primary\">{data.label}</h4>}\n {data.description && <p className=\"text-xs text-text-secondary mt-0.5\">{data.description}</p>}\n </div>\n\n {/* Value display */}\n <div className=\"text-right\">\n <span className=\"text-2xl font-bold text-text-primary\">{formattedValue}</span>\n {config?.showMax !== false && (\n <span className=\"text-sm text-text-secondary\">\n {' / '}\n {formattedMax}\n </span>\n )}\n </div>\n </div>\n\n {/* Progress bar */}\n <ProgressBar\n value={percentage}\n target={targetPercentage}\n milestones={milestones}\n height={config?.barHeight || 12}\n color={colorMode}\n showPercentage={config?.showPercentage !== false}\n />\n\n {/* Target info */}\n {data.target !== undefined && (\n <div className=\"mt-3 flex items-center gap-2 text-xs\">\n <svg className=\"w-4 h-4 text-text-secondary\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M5 21V3h14l-3 9 3 9H5z\" />\n </svg>\n <span className=\"text-text-secondary\">\n Target:{' '}\n <span className=\"font-medium text-text-primary\">{new Intl.NumberFormat('en-US').format(data.target)}</span>\n {percentage >= (targetPercentage || 0) && <span className=\"text-status-success-text ml-1\">Reached!</span>}\n </span>\n </div>\n )}\n\n {/* Drilldown indicator */}\n {isClickable && (\n <div className=\"mt-auto pt-3 flex items-center justify-end\">\n <span className=\"text-xs text-text-secondary flex items-center gap-1\">\n View details\n <svg className=\"w-3 h-3\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M9 5l7 7-7 7\" />\n </svg>\n </span>\n </div>\n )}\n </div>\n );\n});\n\nexport default ProgressWidget;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ProgressWidget.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/progress/ProgressWidget.tsx"],"sourcesContent":["/**\n * ProgressWidget Component\n *\n * Progress bar widget with target, milestones, and color stages.\n */\n\nimport { type FC, memo, useMemo, useCallback } from 'react';\nimport type { Widget, ProgressConfig } from '../../../types';\nimport { ProgressBar, type Milestone } from './ProgressBar';\nimport { firstObjectRow, inferValueField, inferMaxValue, unwrapRows } from '../utils/inferFields';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ProgressWidgetProps {\n /** Widget configuration */\n widget: Widget;\n /** Progress data */\n data?: ProgressData;\n /** Click handler for drilldown */\n onClick?: () => void;\n /** Drilldown handler - receives progress data */\n onDrilldown?: (selectedData: Record<string, unknown>) => void;\n}\n\nexport interface ProgressData {\n /** Current value */\n value: number;\n /** Maximum value (for calculating percentage) */\n max?: number;\n /** Target value */\n target?: number;\n /** Label for the progress */\n label?: string;\n /** Description text */\n description?: string;\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Coerce a `number` or finite numeric string to a number, else null.\n *\n * Parser output is untrusted JSON and routinely serializes metrics as strings,\n * so every numeric field (`value`, `max`, `target`) goes through this rather\n * than relying on implicit coercion or truthiness.\n */\nfunction toNumber(v: unknown): number | null {\n if (typeof v === 'number' && Number.isFinite(v)) return v;\n if (typeof v === 'string') {\n // `Number('')` and `Number(' ')` are 0, not NaN. An absent/blank field\n // must stay null — otherwise a parser emitting `{target: \"\"}` would render\n // a literal \"Target: 0\" with a marker pinned at 0%.\n const trimmed = v.trim();\n if (trimmed === '') return null;\n const n = Number(trimmed);\n if (Number.isFinite(n)) return n;\n }\n return null;\n}\n\n/**\n * Resolve the denominator for percentage maths.\n *\n * Picks the first candidate that is a usable positive number, so a max of `0`\n * (or a non-finite one) can never produce an Infinity/NaN percentage. The\n * previous `data.max || config?.max || 100` chain also treated a legitimate\n * max of 0 and a *string* max inconsistently.\n */\nfunction resolveMax(dataMax: number | undefined, configMax: number | undefined): number {\n for (const candidate of [dataMax, configMax]) {\n if (typeof candidate === 'number' && Number.isFinite(candidate) && candidate > 0) return candidate;\n }\n return 100;\n}\n\ntype ProgressValueFormat = NonNullable<ProgressConfig['format']>;\n\n/**\n * Format a progress number according to the widget's configured format.\n *\n * Shared by value, max AND target so the three figures shown together stay\n * mutually consistent — the target used to be formatted with a bare\n * `Intl.NumberFormat`, rendering e.g. \"$4,823,500 / $5,000,000\" next to a\n * target of \"5,000,000\".\n */\nfunction formatProgressNumber(value: number, format: ProgressValueFormat): string {\n switch (format) {\n case 'currency':\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD',\n maximumFractionDigits: 0,\n }).format(value);\n case 'compact':\n return new Intl.NumberFormat('en-US', {\n notation: 'compact',\n maximumFractionDigits: 1,\n }).format(value);\n // 'percentage' deliberately shares the plain-number branch so value, max\n // and target remain consistent (matches the pre-existing value/max behaviour).\n case 'percentage':\n case 'number':\n default:\n return new Intl.NumberFormat('en-US').format(value);\n }\n}\n\n/**\n * Optional presentation fields a DataSink row carried.\n *\n * The array/envelope branches below rebuild a fresh object, so without this\n * they silently dropped `target` and `description` — an array payload rendered\n * with no target marker, no target footer and no description while the\n * identical single-object payload rendered all three.\n */\nfunction rowExtras(row: Record<string, unknown>): Pick<ProgressData, 'target' | 'description'> {\n const target = toNumber(row.target);\n return {\n ...(target !== null ? { target } : {}),\n ...(typeof row.description === 'string' ? { description: row.description } : {}),\n };\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const ProgressWidget: FC<ProgressWidgetProps> = memo(function ProgressWidget({\n widget,\n data: rawData,\n onClick,\n onDrilldown,\n}) {\n const config = widget.config as unknown as ProgressConfig | undefined;\n\n // BOFF-2246 smart default: if rawData is an array (or envelope) rather than\n // the expected `ProgressData` single object, pull the configured valueField\n // (flat OR dotted, e.g. `metrics.revenue`) or fall back to the first\n // numeric field. Auto-compute a sensible max from the dataset.\n const normalizedData = useMemo((): ProgressData | undefined => {\n if (!rawData) return undefined;\n // Already the expected shape — pass through. Accept both number and\n // numeric-string `value`, since parsers sometimes serialize metrics as\n // strings and the pre-BOFF-2246 code path rendered those via implicit\n // JS coercion in percentage math.\n if (typeof rawData === 'object' && !Array.isArray(rawData) && 'value' in rawData) {\n const source = rawData as unknown as Record<string, unknown>;\n const numericValue = toNumber(source.value);\n if (numericValue !== null) {\n // Coerce `max`/`target` with the same rule as `value`. Parser payloads\n // are untrusted JSON: a string max escapes the numeric guards below\n // (e.g. \"0\" is truthy, so `data.max || …` would select it and yield an\n // Infinity percentage), and a string target breaks the marker maths.\n const numericMax = toNumber(source.max);\n const numericTarget = toNumber(source.target);\n return {\n ...(rawData as ProgressData),\n value: numericValue,\n max: numericMax ?? undefined,\n target: numericTarget ?? undefined,\n };\n }\n }\n const row = firstObjectRow(rawData);\n if (!row) return undefined;\n const configuredField = (config as Record<string, unknown> | undefined)?.valueField as string | undefined;\n\n // Supports dotted paths like `metrics.revenue` so nested valueFields\n // configured in widget config resolve on array/envelope payloads the\n // same way they do on single-object payloads.\n const getNestedValue = (obj: Record<string, unknown>, path: string): unknown =>\n path.split('.').reduce<unknown>((current, key) => {\n return current && typeof current === 'object' ? (current as Record<string, unknown>)[key] : undefined;\n }, obj);\n\n const labelFromRow =\n typeof row.label === 'string' ? row.label : typeof row.name === 'string' ? row.name : undefined;\n\n // Configured field is authoritative — scan every row for it so sparse\n // envelopes like `{data:[{id:1},{revenue:120}]}` with\n // `valueField:\"revenue\"` still resolve. If no row carries it, return\n // undefined rather than substituting an unrelated numeric column from\n // row[0] via inference (that would silently misrepresent user config).\n if (configuredField) {\n const rows = unwrapRows(rawData) ?? [row];\n for (const r of rows) {\n if (r && typeof r === 'object') {\n const resolvedRow = r as Record<string, unknown>;\n const resolved = toNumber(getNestedValue(resolvedRow, configuredField));\n if (resolved !== null) {\n const rowLabel =\n typeof resolvedRow.label === 'string'\n ? resolvedRow.label\n : typeof resolvedRow.name === 'string'\n ? resolvedRow.name\n : labelFromRow;\n return {\n value: resolved,\n // Explicit config wins, then the row's own max (authoritative for\n // that row), then a dataset-derived guess.\n max: config?.max ?? toNumber(resolvedRow.max) ?? inferMaxValue(rawData, configuredField) ?? undefined,\n label: rowLabel,\n ...rowExtras(resolvedRow),\n };\n }\n }\n }\n return undefined;\n }\n\n // Fall back to inference (only when no configured field). `inferValueField`\n // already filters to numeric fields, but we still coerce here for symmetry\n // with the configured branch above.\n const inferred = inferValueField(row);\n if (!inferred) return undefined;\n const inferredValue = toNumber(row[inferred]);\n if (inferredValue === null) return undefined;\n return {\n value: inferredValue,\n max: config?.max ?? toNumber(row.max) ?? inferMaxValue(rawData, inferred) ?? undefined,\n label: labelFromRow,\n ...rowExtras(row),\n };\n }, [rawData, config]);\n\n // Downstream render uses the normalized shape. Name kept as `data` so the\n // rest of the component body (formattedValue, handleClick, header, etc.)\n // stays unchanged.\n const data = normalizedData;\n\n // Calculate percentage\n const percentage = useMemo(() => {\n if (!data) return 0;\n return (data.value / resolveMax(data.max, config?.max)) * 100;\n }, [data, config?.max]);\n\n // Calculate target percentage. `=== undefined` rather than a truthiness\n // check so a legitimate target of 0 still renders its marker.\n const targetPercentage = useMemo(() => {\n if (data?.target === undefined) return undefined;\n return (data.target / resolveMax(data.max, config?.max)) * 100;\n }, [data, config?.max]);\n\n // Build milestones from config\n const milestones: Milestone[] = useMemo(() => {\n if (!config?.milestones?.length) return [];\n return config.milestones.map((m) => ({\n position: m.position ?? m.value ?? 0,\n label: m.label,\n reached: percentage >= (m.position ?? m.value ?? 0),\n }));\n }, [config?.milestones, percentage]);\n\n // Determine color mode\n const colorMode = useMemo(() => {\n if (config?.colorMode === 'dynamic') return 'dynamic';\n if (config?.colorMode === 'success') return 'success';\n if (config?.colorMode === 'warning') return 'warning';\n if (config?.colorMode === 'error') return 'error';\n return 'default';\n }, [config?.colorMode]);\n\n // Format value\n const formattedValue = useMemo(() => {\n if (!data) return '0';\n return formatProgressNumber(data.value, config?.format || 'number');\n }, [data, config?.format]);\n\n const formattedMax = useMemo(() => {\n return formatProgressNumber(resolveMax(data?.max, config?.max), config?.format || 'number');\n }, [data?.max, config?.max, config?.format]);\n\n const formattedTarget = useMemo(() => {\n if (data?.target === undefined) return undefined;\n return formatProgressNumber(data.target, config?.format || 'number');\n }, [data?.target, config?.format]);\n\n const handleClick = useCallback(() => {\n // Support both legacy onClick and new onDrilldown\n if (onDrilldown && data) {\n onDrilldown({\n value: data.value,\n max: data.max,\n target: data.target,\n label: data.label,\n percentage,\n });\n } else if (onClick && widget.drilldowns?.length) {\n onClick();\n }\n }, [onClick, onDrilldown, widget.drilldowns, data, percentage]);\n\n const hasDrilldown = !!widget.drilldown?.enabled;\n const isClickable = Boolean(hasDrilldown || (onClick && widget.drilldowns?.length));\n\n if (!data) {\n // WidgetWrapper's `isUsableData` admits payloads this widget cannot turn\n // into a number (e.g. `{\"value\":\"n/a\"}` or `[{\"name\":\"a\",\"status\":\"ok\"}]`),\n // so its own empty state never shows. Returning bare null left an\n // unexplained blank card; render the same empty state the sibling widgets\n // use instead.\n return (\n <div className=\"flex items-center justify-center h-full text-text-secondary text-sm\">\n <div className=\"text-center\">\n <svg\n className=\"w-12 h-12 mx-auto mb-2 text-text-tertiary\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={1.5}\n d=\"M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z\"\n />\n </svg>\n <p>No progress data</p>\n </div>\n </div>\n );\n }\n\n return (\n <div\n className={`\n flex flex-col h-full p-4\n ${isClickable ? 'cursor-pointer hover:bg-bg-sunken/50 transition-colors' : ''}\n `}\n onClick={handleClick}\n role={isClickable ? 'button' : undefined}\n tabIndex={isClickable ? 0 : undefined}\n onKeyDown={(e) => {\n if (isClickable && (e.key === 'Enter' || e.key === ' ')) {\n e.preventDefault();\n handleClick();\n }\n }}\n >\n {/* Header */}\n <div className=\"flex items-start justify-between mb-4\">\n <div className=\"flex-1\">\n {data.label && <h4 className=\"text-sm font-medium text-text-primary\">{data.label}</h4>}\n {data.description && <p className=\"text-xs text-text-secondary mt-0.5\">{data.description}</p>}\n </div>\n\n {/* Value display */}\n <div className=\"text-right\">\n <span className=\"text-2xl font-bold text-text-primary\">{formattedValue}</span>\n {config?.showMax !== false && (\n <span className=\"text-sm text-text-secondary\">\n {' / '}\n {formattedMax}\n </span>\n )}\n </div>\n </div>\n\n {/* Progress bar */}\n <ProgressBar\n value={percentage}\n target={targetPercentage}\n milestones={milestones}\n height={config?.barHeight || 12}\n color={colorMode}\n showPercentage={config?.showPercentage !== false}\n />\n\n {/* Target info */}\n {data.target !== undefined && (\n <div className=\"mt-3 flex items-center gap-2 text-xs\">\n <svg className=\"w-4 h-4 text-text-secondary\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M5 21V3h14l-3 9 3 9H5z\" />\n </svg>\n <span className=\"text-text-secondary\">\n Target: <span className=\"font-medium text-text-primary\">{formattedTarget}</span>\n {percentage >= (targetPercentage || 0) && <span className=\"text-status-success-text ml-1\">Reached!</span>}\n </span>\n </div>\n )}\n\n {/* Drilldown indicator */}\n {isClickable && (\n <div className=\"mt-auto pt-3 flex items-center justify-end\">\n <span className=\"text-xs text-text-secondary flex items-center gap-1\">\n View details\n <svg className=\"w-3 h-3\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M9 5l7 7-7 7\" />\n </svg>\n </span>\n </div>\n )}\n </div>\n );\n});\n\nexport default ProgressWidget;\n"],"mappings":"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"}
|
|
@@ -17,7 +17,7 @@ var o = e(function({ widget: e, data: o, onClick: s }) {
|
|
|
17
17
|
className: "text-center",
|
|
18
18
|
children: [
|
|
19
19
|
/* @__PURE__ */ r("svg", {
|
|
20
|
-
className: "w-12 h-12 mx-auto mb-2 text-text-
|
|
20
|
+
className: "w-12 h-12 mx-auto mb-2 text-text-muted",
|
|
21
21
|
fill: "none",
|
|
22
22
|
stroke: "currentColor",
|
|
23
23
|
viewBox: "0 0 24 24",
|
|
@@ -33,7 +33,7 @@ var o = e(function({ widget: e, data: o, onClick: s }) {
|
|
|
33
33
|
children: "No retention cohort data"
|
|
34
34
|
}),
|
|
35
35
|
/* @__PURE__ */ i("p", {
|
|
36
|
-
className: "text-xs text-text-
|
|
36
|
+
className: "text-xs text-text-muted mt-1",
|
|
37
37
|
children: [
|
|
38
38
|
"Connect a data source that returns ",
|
|
39
39
|
/* @__PURE__ */ r("span", {
|
|
@@ -50,7 +50,7 @@ var o = e(function({ widget: e, data: o, onClick: s }) {
|
|
|
50
50
|
]
|
|
51
51
|
}),
|
|
52
52
|
/* @__PURE__ */ i("p", {
|
|
53
|
-
className: "text-xs text-text-
|
|
53
|
+
className: "text-xs text-text-muted mt-2",
|
|
54
54
|
children: [
|
|
55
55
|
"Expected shape: ",
|
|
56
56
|
/* @__PURE__ */ r("span", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RetentionWidget.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/retention/RetentionWidget.tsx"],"sourcesContent":["/**\n * RetentionWidget Component\n *\n * Retention cohort chart showing user retention over time.\n */\n\nimport { type FC, memo, useMemo, useCallback } from 'react';\nimport type { Widget, RetentionConfig } from '../../../types';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface RetentionWidgetProps {\n widget: Widget;\n data?: RetentionData;\n onClick?: (cohort: string, period: number, value: number) => void;\n}\n\nexport interface RetentionCohort {\n /** Cohort identifier (e.g., \"Week 1\", \"Jan 2024\") */\n cohort: string;\n /** Initial cohort size */\n initialSize: number;\n /** Retention values for each period (percentages) */\n values: number[];\n}\n\nexport interface RetentionData {\n /** Period labels (e.g., [\"Day 0\", \"Day 1\", ...]) */\n periods: string[];\n /** Cohort data */\n cohorts: RetentionCohort[];\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction getRetentionColor(value: number): string {\n // Green gradient based on retention percentage\n if (value >= 80) return 'bg-status-success-bg/90 text-white';\n if (value >= 60) return 'bg-status-success-bg/70 text-white';\n if (value >= 40) return 'bg-status-success-bg/50 text-text-primary';\n if (value >= 20) return 'bg-status-success-bg/30 text-text-primary';\n if (value > 0) return 'bg-status-success-bg/10 text-text-secondary';\n return 'bg-bg-sunken text-text-secondary';\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const RetentionWidget: FC<RetentionWidgetProps> = memo(function RetentionWidget({ widget, data, onClick }) {\n const config = widget.config as unknown as RetentionConfig | undefined;\n\n const handleCellClick = useCallback(\n (cohort: string, periodIndex: number, value: number) => {\n if (onClick && widget.drilldowns?.length) {\n onClick(cohort, periodIndex, value);\n }\n },\n [onClick, widget.drilldowns]\n );\n\n const isClickable = Boolean(onClick && widget.drilldowns?.length);\n\n // Calculate average retention per period\n const averages = useMemo(() => {\n if (!data?.cohorts?.length || !data?.periods?.length) return [];\n\n return data.periods.map((_, periodIndex) => {\n const values = data.cohorts\n .map((c) => c.values?.[periodIndex])\n .filter((v) => v !== undefined && v !== null) as number[];\n if (values.length === 0) return 0;\n return values.reduce((a, b) => a + b, 0) / values.length;\n });\n }, [data]);\n\n // Show retention-specific empty state when cohort data is missing.\n if (!data?.cohorts?.length || !data?.periods?.length) {\n return (\n <div className=\"flex items-center justify-center h-full text-text-secondary text-sm\">\n <div className=\"text-center\">\n <svg\n className=\"w-12 h-12 mx-auto mb-2 text-text-tertiary\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={1.5}\n d=\"M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2\"\n />\n </svg>\n <p className=\"font-medium text-text-primary\">No retention cohort data</p>\n <p className=\"text-xs text-text-tertiary mt-1\">\n Connect a data source that returns <span className=\"font-mono\">periods</span> and{' '}\n <span className=\"font-mono\">cohorts</span>.\n </p>\n <p className=\"text-xs text-text-tertiary mt-2\">\n Expected shape: <span className=\"font-mono\">RetentionData</span>.\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"flex flex-col h-full p-4 overflow-auto\">\n <table className=\"w-full border-collapse\">\n <thead>\n <tr>\n <th className=\"p-2 text-xs font-medium text-text-secondary text-left sticky left-0 bg-bg-surface\">\n Cohort\n </th>\n <th className=\"p-2 text-xs font-medium text-text-secondary text-right\">Users</th>\n {data.periods.map((period, i) => (\n <th key={i} className=\"p-2 text-xs font-medium text-text-secondary text-center min-w-[60px]\">\n {period}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {data.cohorts.map((cohort, _cohortIndex) => (\n <tr key={cohort.cohort}>\n <td className=\"p-2 text-sm font-medium text-text-primary text-left sticky left-0 bg-bg-surface\">\n {cohort.cohort}\n </td>\n <td className=\"p-2 text-sm text-text-secondary text-right\">\n {new Intl.NumberFormat('en-US').format(cohort.initialSize)}\n </td>\n {cohort.values.map((value, periodIndex) => (\n <td\n key={periodIndex}\n className={`p-0 ${isClickable ? 'cursor-pointer' : ''}`}\n onClick={() => handleCellClick(cohort.cohort, periodIndex, value)}\n >\n <div\n className={`\n m-0.5 p-2 rounded text-center\n ${getRetentionColor(value)}\n ${isClickable ? 'hover:ring-2 hover:ring-action-primary-bg' : ''}\n transition-all\n `}\n title={`${cohort.cohort} - ${data.periods[periodIndex]}: ${value.toFixed(1)}%`}\n >\n <span className=\"text-xs font-medium\">{value.toFixed(config?.decimals ?? 0)}%</span>\n </div>\n </td>\n ))}\n </tr>\n ))}\n\n {/* Average row */}\n {config?.showAverages !== false && (\n <tr className=\"border-t-2 border-border-default\">\n <td className=\"p-2 text-sm font-medium text-text-primary sticky left-0 bg-bg-surface\">Average</td>\n <td className=\"p-2 text-sm text-text-secondary text-right\">—</td>\n {averages.map((avg, i) => (\n <td key={i} className=\"p-0\">\n <div className=\"m-0.5 p-2 rounded bg-bg-sunken text-center\">\n <span className=\"text-xs font-semibold text-text-primary\">\n {avg.toFixed(config?.decimals ?? 0)}%\n </span>\n </div>\n </td>\n ))}\n </tr>\n )}\n </tbody>\n </table>\n\n {/* Legend */}\n {config?.showLegend !== false && (\n <div className=\"flex items-center justify-center gap-2 mt-4\">\n <span className=\"text-xs text-text-secondary\">0%</span>\n <div className=\"flex gap-0.5\">\n {[10, 30, 50, 70, 90].map((opacity) => (\n <div key={opacity} className={`w-6 h-4 rounded bg-status-success-bg/${opacity}`} />\n ))}\n </div>\n <span className=\"text-xs text-text-secondary\">100%</span>\n </div>\n )}\n </div>\n );\n});\n\nexport default RetentionWidget;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"RetentionWidget.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/retention/RetentionWidget.tsx"],"sourcesContent":["/**\n * RetentionWidget Component\n *\n * Retention cohort chart showing user retention over time.\n */\n\nimport { type FC, memo, useMemo, useCallback } from 'react';\nimport type { Widget, RetentionConfig } from '../../../types';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface RetentionWidgetProps {\n widget: Widget;\n data?: RetentionData;\n onClick?: (cohort: string, period: number, value: number) => void;\n}\n\nexport interface RetentionCohort {\n /** Cohort identifier (e.g., \"Week 1\", \"Jan 2024\") */\n cohort: string;\n /** Initial cohort size */\n initialSize: number;\n /** Retention values for each period (percentages) */\n values: number[];\n}\n\nexport interface RetentionData {\n /** Period labels (e.g., [\"Day 0\", \"Day 1\", ...]) */\n periods: string[];\n /** Cohort data */\n cohorts: RetentionCohort[];\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction getRetentionColor(value: number): string {\n // Green gradient based on retention percentage\n if (value >= 80) return 'bg-status-success-bg/90 text-white';\n if (value >= 60) return 'bg-status-success-bg/70 text-white';\n if (value >= 40) return 'bg-status-success-bg/50 text-text-primary';\n if (value >= 20) return 'bg-status-success-bg/30 text-text-primary';\n if (value > 0) return 'bg-status-success-bg/10 text-text-secondary';\n return 'bg-bg-sunken text-text-secondary';\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const RetentionWidget: FC<RetentionWidgetProps> = memo(function RetentionWidget({ widget, data, onClick }) {\n const config = widget.config as unknown as RetentionConfig | undefined;\n\n const handleCellClick = useCallback(\n (cohort: string, periodIndex: number, value: number) => {\n if (onClick && widget.drilldowns?.length) {\n onClick(cohort, periodIndex, value);\n }\n },\n [onClick, widget.drilldowns]\n );\n\n const isClickable = Boolean(onClick && widget.drilldowns?.length);\n\n // Calculate average retention per period\n const averages = useMemo(() => {\n if (!data?.cohorts?.length || !data?.periods?.length) return [];\n\n return data.periods.map((_, periodIndex) => {\n const values = data.cohorts\n .map((c) => c.values?.[periodIndex])\n .filter((v) => v !== undefined && v !== null) as number[];\n if (values.length === 0) return 0;\n return values.reduce((a, b) => a + b, 0) / values.length;\n });\n }, [data]);\n\n // Show retention-specific empty state when cohort data is missing.\n if (!data?.cohorts?.length || !data?.periods?.length) {\n return (\n <div className=\"flex items-center justify-center h-full text-text-secondary text-sm\">\n <div className=\"text-center\">\n <svg className=\"w-12 h-12 mx-auto mb-2 text-text-muted\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={1.5}\n d=\"M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2\"\n />\n </svg>\n <p className=\"font-medium text-text-primary\">No retention cohort data</p>\n <p className=\"text-xs text-text-muted mt-1\">\n Connect a data source that returns <span className=\"font-mono\">periods</span> and{' '}\n <span className=\"font-mono\">cohorts</span>.\n </p>\n <p className=\"text-xs text-text-muted mt-2\">\n Expected shape: <span className=\"font-mono\">RetentionData</span>.\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"flex flex-col h-full p-4 overflow-auto\">\n <table className=\"w-full border-collapse\">\n <thead>\n <tr>\n <th className=\"p-2 text-xs font-medium text-text-secondary text-left sticky left-0 bg-bg-surface\">\n Cohort\n </th>\n <th className=\"p-2 text-xs font-medium text-text-secondary text-right\">Users</th>\n {data.periods.map((period, i) => (\n <th key={i} className=\"p-2 text-xs font-medium text-text-secondary text-center min-w-[60px]\">\n {period}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {data.cohorts.map((cohort, _cohortIndex) => (\n <tr key={cohort.cohort}>\n <td className=\"p-2 text-sm font-medium text-text-primary text-left sticky left-0 bg-bg-surface\">\n {cohort.cohort}\n </td>\n <td className=\"p-2 text-sm text-text-secondary text-right\">\n {new Intl.NumberFormat('en-US').format(cohort.initialSize)}\n </td>\n {cohort.values.map((value, periodIndex) => (\n <td\n key={periodIndex}\n className={`p-0 ${isClickable ? 'cursor-pointer' : ''}`}\n onClick={() => handleCellClick(cohort.cohort, periodIndex, value)}\n >\n <div\n className={`\n m-0.5 p-2 rounded text-center\n ${getRetentionColor(value)}\n ${isClickable ? 'hover:ring-2 hover:ring-action-primary-bg' : ''}\n transition-all\n `}\n title={`${cohort.cohort} - ${data.periods[periodIndex]}: ${value.toFixed(1)}%`}\n >\n <span className=\"text-xs font-medium\">{value.toFixed(config?.decimals ?? 0)}%</span>\n </div>\n </td>\n ))}\n </tr>\n ))}\n\n {/* Average row */}\n {config?.showAverages !== false && (\n <tr className=\"border-t-2 border-border-default\">\n <td className=\"p-2 text-sm font-medium text-text-primary sticky left-0 bg-bg-surface\">Average</td>\n <td className=\"p-2 text-sm text-text-secondary text-right\">—</td>\n {averages.map((avg, i) => (\n <td key={i} className=\"p-0\">\n <div className=\"m-0.5 p-2 rounded bg-bg-sunken text-center\">\n <span className=\"text-xs font-semibold text-text-primary\">\n {avg.toFixed(config?.decimals ?? 0)}%\n </span>\n </div>\n </td>\n ))}\n </tr>\n )}\n </tbody>\n </table>\n\n {/* Legend */}\n {config?.showLegend !== false && (\n <div className=\"flex items-center justify-center gap-2 mt-4\">\n <span className=\"text-xs text-text-secondary\">0%</span>\n <div className=\"flex gap-0.5\">\n {[10, 30, 50, 70, 90].map((opacity) => (\n <div key={opacity} className={`w-6 h-4 rounded bg-status-success-bg/${opacity}`} />\n ))}\n </div>\n <span className=\"text-xs text-text-secondary\">100%</span>\n </div>\n )}\n </div>\n );\n});\n\nexport default RetentionWidget;\n"],"mappings":"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"}
|
|
@@ -4,11 +4,13 @@ import { jsx as t, jsxs as n } from "react/jsx-runtime";
|
|
|
4
4
|
var r = e(function({ widgetType: e, hasDataSource: r = !1, message: a, templateSlot: o }) {
|
|
5
5
|
let s = a || (!r && o ? `Connect the "${o}" data source to finish setting up this widget` : r ? i(e) : "Connect a data source to display data");
|
|
6
6
|
return /* @__PURE__ */ n("div", {
|
|
7
|
-
className: "\n flex flex-col items-center justify-center\n h-full w-full min-h-[120px]\n text-center\n px-
|
|
7
|
+
className: "\n flex flex-col items-center justify-center\n h-full w-full min-w-0 min-h-[120px]\n text-center\n px-3\n ",
|
|
8
|
+
role: "status",
|
|
8
9
|
children: [
|
|
9
10
|
/* @__PURE__ */ t("div", {
|
|
10
|
-
className: "\n w-12 h-12 mb-3\n flex items-center justify-center\n rounded-full\n bg-bg-sunken\n text-text-secondary\n ",
|
|
11
|
+
className: "\n w-12 h-12 mb-3 shrink-0\n flex items-center justify-center\n rounded-full\n bg-bg-sunken\n text-text-secondary\n ",
|
|
11
12
|
children: /* @__PURE__ */ t("svg", {
|
|
13
|
+
"aria-hidden": "true",
|
|
12
14
|
className: "w-6 h-6",
|
|
13
15
|
fill: "none",
|
|
14
16
|
stroke: "currentColor",
|
|
@@ -22,11 +24,11 @@ var r = e(function({ widgetType: e, hasDataSource: r = !1, message: a, templateS
|
|
|
22
24
|
})
|
|
23
25
|
}),
|
|
24
26
|
/* @__PURE__ */ t("p", {
|
|
25
|
-
className: "text-sm text-text-
|
|
27
|
+
className: "max-w-[32ch] text-sm font-medium text-text-primary",
|
|
26
28
|
children: s
|
|
27
29
|
}),
|
|
28
30
|
!r && /* @__PURE__ */ t("p", {
|
|
29
|
-
className: "
|
|
31
|
+
className: "mt-2 max-w-[32ch] text-xs text-text-secondary",
|
|
30
32
|
children: "Click Edit to configure this widget"
|
|
31
33
|
})
|
|
32
34
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EmptyState.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/states/EmptyState.tsx"],"sourcesContent":["/**\n * WidgetEmptyState Component\n *\n * Empty state displayed when widget has no data.\n */\n\nimport { type FC, memo } from 'react';\nimport type { WidgetType } from '../../../types';\n\nexport interface WidgetEmptyStateProps {\n /** Widget type for context-specific messaging */\n widgetType?: WidgetType;\n /** Whether a data source is configured */\n hasDataSource?: boolean;\n /** Custom message */\n message?: string;\n /**\n * Slot name when this widget came from a store template and its data source\n * has not been connected yet. Turns the generic empty state into an\n * explicitly unfinished-setup state, so it does not read as a broken widget.\n */\n templateSlot?: string | null;\n}\n\nexport const WidgetEmptyState: FC<WidgetEmptyStateProps> = memo(function WidgetEmptyState({\n widgetType,\n hasDataSource = false,\n message,\n templateSlot,\n}) {\n // A template widget awaiting its data source is a designed setup state, not\n // an absence of results — say so explicitly and name the slot to connect.\n const awaitingTemplateBinding = !hasDataSource && !!templateSlot;\n\n // Determine appropriate message\n const displayMessage =\n message ||\n (awaitingTemplateBinding\n ? `Connect the \"${templateSlot}\" data source to finish setting up this widget`\n : !hasDataSource\n ? 'Connect a data source to display data'\n : getEmptyMessageForType(widgetType));\n\n return (\n <div\n className=\"\n flex flex-col items-center justify-center\n h-full w-full min-h-[120px]\n text-center\n px-
|
|
1
|
+
{"version":3,"file":"EmptyState.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/states/EmptyState.tsx"],"sourcesContent":["/**\n * WidgetEmptyState Component\n *\n * Empty state displayed when widget has no data.\n */\n\nimport { type FC, memo } from 'react';\nimport type { WidgetType } from '../../../types';\n\nexport interface WidgetEmptyStateProps {\n /** Widget type for context-specific messaging */\n widgetType?: WidgetType;\n /** Whether a data source is configured */\n hasDataSource?: boolean;\n /** Custom message */\n message?: string;\n /**\n * Slot name when this widget came from a store template and its data source\n * has not been connected yet. Turns the generic empty state into an\n * explicitly unfinished-setup state, so it does not read as a broken widget.\n */\n templateSlot?: string | null;\n}\n\nexport const WidgetEmptyState: FC<WidgetEmptyStateProps> = memo(function WidgetEmptyState({\n widgetType,\n hasDataSource = false,\n message,\n templateSlot,\n}) {\n // A template widget awaiting its data source is a designed setup state, not\n // an absence of results — say so explicitly and name the slot to connect.\n const awaitingTemplateBinding = !hasDataSource && !!templateSlot;\n\n // Determine appropriate message\n const displayMessage =\n message ||\n (awaitingTemplateBinding\n ? `Connect the \"${templateSlot}\" data source to finish setting up this widget`\n : !hasDataSource\n ? 'Connect a data source to display data'\n : getEmptyMessageForType(widgetType));\n\n return (\n /*\n One shared recipe across the four widget states (empty / error / loading /\n crash): same icon chip, same copy width, same padding. Copy is capped in\n `ch` rather than a fixed 200px so it never overflows a narrow card\n (BOFF-5636).\n */\n <div\n className=\"\n flex flex-col items-center justify-center\n h-full w-full min-w-0 min-h-[120px]\n text-center\n px-3\n \"\n role=\"status\"\n >\n {/* Empty Icon */}\n <div\n className=\"\n w-12 h-12 mb-3 shrink-0\n flex items-center justify-center\n rounded-full\n bg-bg-sunken\n text-text-secondary\n \"\n >\n <svg aria-hidden=\"true\" className=\"w-6 h-6\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4\"\n />\n </svg>\n </div>\n\n {/* Message */}\n <p className=\"max-w-[32ch] text-sm font-medium text-text-primary\">{displayMessage}</p>\n\n {/* Data source hint */}\n {!hasDataSource && (\n <p className=\"mt-2 max-w-[32ch] text-xs text-text-secondary\">Click Edit to configure this widget</p>\n )}\n </div>\n );\n});\n\n/**\n * Get context-specific empty message for widget type (v1.0 snake_case)\n */\nfunction getEmptyMessageForType(type?: WidgetType): string {\n switch (type) {\n case 'metric_card':\n case 'kpi_card_comparison':\n return 'No metrics available';\n case 'chart':\n case 'funnel_chart':\n return 'No chart data available';\n case 'table':\n case 'pivot_table':\n return 'No rows to display';\n case 'gauge':\n case 'progress':\n return 'No value available';\n case 'list':\n return 'No items to display';\n case 'heatmap':\n return 'No heatmap data available';\n case 'calendar':\n case 'timeline':\n case 'kanban':\n return 'No schedule data available';\n case 'map':\n return 'No location data available';\n case 'form':\n return 'Form not configured';\n case 'text':\n case 'iframe':\n return 'No content configured';\n case 'custom':\n return 'Custom widget not configured';\n default:\n return 'No data available';\n }\n}\n\nexport default WidgetEmptyState;\n"],"mappings":";;;AAwBA,IAAa,IAA8C,EAAK,SAA0B,EACxF,eACA,mBAAgB,IAChB,YACA,mBACC;CAMD,IAAM,IACJ,MAJ8B,CAAC,KAAmB,IAM9C,gBAAgB,EAAa,kDAC5B,IAEC,EAAuB,EAAW,GADlC;AAGR,QAOE,kBAAC,OAAD;EACE,WAAU;EAMV,MAAK;YAPP;GAUE,kBAAC,OAAD;IACE,WAAU;cAQV,kBAAC,OAAD;KAAK,eAAY;KAAO,WAAU;KAAU,MAAK;KAAO,QAAO;KAAe,SAAQ;eACpF,kBAAC,QAAD;MACE,eAAc;MACd,gBAAe;MACf,aAAa;MACb,GAAE;MACF,CAAA;KACE,CAAA;IACF,CAAA;GAGN,kBAAC,KAAD;IAAG,WAAU;cAAsD;IAAmB,CAAA;GAGrF,CAAC,KACA,kBAAC,KAAD;IAAG,WAAU;cAAgD;IAAuC,CAAA;GAElG;;EAER;AAKF,SAAS,EAAuB,GAA2B;AACzD,SAAQ,GAAR;EACE,KAAK;EACL,KAAK,sBACH,QAAO;EACT,KAAK;EACL,KAAK,eACH,QAAO;EACT,KAAK;EACL,KAAK,cACH,QAAO;EACT,KAAK;EACL,KAAK,WACH,QAAO;EACT,KAAK,OACH,QAAO;EACT,KAAK,UACH,QAAO;EACT,KAAK;EACL,KAAK;EACL,KAAK,SACH,QAAO;EACT,KAAK,MACH,QAAO;EACT,KAAK,OACH,QAAO;EACT,KAAK;EACL,KAAK,SACH,QAAO;EACT,KAAK,SACH,QAAO;EACT,QACE,QAAO"}
|
|
@@ -3,12 +3,13 @@ import { jsx as t, jsxs as n } from "react/jsx-runtime";
|
|
|
3
3
|
//#region src/bigconsole/components/widgets/states/ErrorState.tsx
|
|
4
4
|
var r = e(function({ error: e, onRetry: r }) {
|
|
5
5
|
return /* @__PURE__ */ n("div", {
|
|
6
|
-
className: "\n flex flex-col items-center justify-center\n h-full w-full min-h-[120px]\n text-center\n px-
|
|
6
|
+
className: "\n flex flex-col items-center justify-center\n h-full w-full min-w-0 min-h-[120px]\n text-center\n px-3\n ",
|
|
7
7
|
role: "alert",
|
|
8
8
|
children: [
|
|
9
9
|
/* @__PURE__ */ t("div", {
|
|
10
|
-
className: "\n w-12 h-12 mb-3\n flex items-center justify-center\n rounded-full\n bg-status-error-bg
|
|
10
|
+
className: "\n w-12 h-12 mb-3 shrink-0\n flex items-center justify-center\n rounded-full\n bg-status-error-bg-subtle\n text-[var(--color-status-error-text)]\n ",
|
|
11
11
|
children: /* @__PURE__ */ t("svg", {
|
|
12
|
+
"aria-hidden": "true",
|
|
12
13
|
className: "w-6 h-6",
|
|
13
14
|
fill: "none",
|
|
14
15
|
stroke: "currentColor",
|
|
@@ -22,19 +23,20 @@ var r = e(function({ error: e, onRetry: r }) {
|
|
|
22
23
|
})
|
|
23
24
|
}),
|
|
24
25
|
/* @__PURE__ */ t("p", {
|
|
25
|
-
className: "text-sm font-
|
|
26
|
+
className: "mb-1 text-sm font-semibold text-[var(--color-status-error-text)]",
|
|
26
27
|
children: "Failed to load data"
|
|
27
28
|
}),
|
|
28
29
|
/* @__PURE__ */ t("p", {
|
|
29
|
-
className: "
|
|
30
|
+
className: "mb-4 max-w-[32ch] break-words text-xs text-text-secondary",
|
|
30
31
|
title: e,
|
|
31
32
|
children: e.length > 100 ? `${e.substring(0, 100)}...` : e
|
|
32
33
|
}),
|
|
33
34
|
r && /* @__PURE__ */ n("button", {
|
|
34
35
|
type: "button",
|
|
35
36
|
onClick: r,
|
|
36
|
-
className: "\n inline-flex items-center gap-1.5\n px-3
|
|
37
|
+
className: "\n inline-flex items-center gap-1.5\n min-h-control-md px-3\n text-sm font-medium\n text-action-primary-text\n bg-action-primary-bg\n hover:bg-action-primary-bgHover\n rounded-button\n motion-safe:transition-colors\n focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-bg-surface\n ",
|
|
37
38
|
children: [/* @__PURE__ */ t("svg", {
|
|
39
|
+
"aria-hidden": "true",
|
|
38
40
|
className: "w-4 h-4",
|
|
39
41
|
fill: "none",
|
|
40
42
|
stroke: "currentColor",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ErrorState.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/states/ErrorState.tsx"],"sourcesContent":["/**\n * WidgetErrorState Component\n *\n * Error state displayed when widget data fetch fails.\n */\n\nimport { type FC, memo } from 'react';\n\nexport interface WidgetErrorStateProps {\n /** Error message */\n error: string;\n /** Callback to retry loading */\n onRetry?: () => void;\n}\n\nexport const WidgetErrorState: FC<WidgetErrorStateProps> = memo(function WidgetErrorState({ error, onRetry }) {\n return (\n <div\n className=\"\n flex flex-col items-center justify-center\n h-full w-full min-h-[120px]\n text-center\n px-
|
|
1
|
+
{"version":3,"file":"ErrorState.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/states/ErrorState.tsx"],"sourcesContent":["/**\n * WidgetErrorState Component\n *\n * Error state displayed when widget data fetch fails.\n */\n\nimport { type FC, memo } from 'react';\n\nexport interface WidgetErrorStateProps {\n /** Error message */\n error: string;\n /** Callback to retry loading */\n onRetry?: () => void;\n}\n\nexport const WidgetErrorState: FC<WidgetErrorStateProps> = memo(function WidgetErrorState({ error, onRetry }) {\n return (\n /* Shares the empty-state recipe: same chip, same copy width, same padding. */\n <div\n className=\"\n flex flex-col items-center justify-center\n h-full w-full min-w-0 min-h-[120px]\n text-center\n px-3\n \"\n role=\"alert\"\n >\n {/* Error Icon */}\n <div\n className=\"\n w-12 h-12 mb-3 shrink-0\n flex items-center justify-center\n rounded-full\n bg-status-error-bg-subtle\n text-[var(--color-status-error-text)]\n \"\n >\n <svg aria-hidden=\"true\" className=\"w-6 h-6\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n </div>\n\n {/* Error Message */}\n <p className=\"mb-1 text-sm font-semibold text-[var(--color-status-error-text)]\">Failed to load data</p>\n <p className=\"mb-4 max-w-[32ch] break-words text-xs text-text-secondary\" title={error}>\n {error.length > 100 ? `${error.substring(0, 100)}...` : error}\n </p>\n\n {/* Retry Button */}\n {onRetry && (\n <button\n type=\"button\"\n onClick={onRetry}\n className=\"\n inline-flex items-center gap-1.5\n min-h-control-md px-3\n text-sm font-medium\n text-action-primary-text\n bg-action-primary-bg\n hover:bg-action-primary-bgHover\n rounded-button\n motion-safe:transition-colors\n focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-bg-surface\n \"\n >\n <svg aria-hidden=\"true\" className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15\"\n />\n </svg>\n Retry\n </button>\n )}\n </div>\n );\n});\n\nexport default WidgetErrorState;\n"],"mappings":";;;AAeA,IAAa,IAA8C,EAAK,SAA0B,EAAE,UAAO,cAAW;AAC5G,QAEE,kBAAC,OAAD;EACE,WAAU;EAMV,MAAK;YAPP;GAUE,kBAAC,OAAD;IACE,WAAU;cAQV,kBAAC,OAAD;KAAK,eAAY;KAAO,WAAU;KAAU,MAAK;KAAO,QAAO;KAAe,SAAQ;eACpF,kBAAC,QAAD;MACE,eAAc;MACd,gBAAe;MACf,aAAa;MACb,GAAE;MACF,CAAA;KACE,CAAA;IACF,CAAA;GAGN,kBAAC,KAAD;IAAG,WAAU;cAAmE;IAAuB,CAAA;GACvG,kBAAC,KAAD;IAAG,WAAU;IAA4D,OAAO;cAC7E,EAAM,SAAS,MAAM,GAAG,EAAM,UAAU,GAAG,IAAI,CAAC,OAAO;IACtD,CAAA;GAGH,KACC,kBAAC,UAAD;IACE,MAAK;IACL,SAAS;IACT,WAAU;cAHZ,CAeE,kBAAC,OAAD;KAAK,eAAY;KAAO,WAAU;KAAU,MAAK;KAAO,QAAO;KAAe,SAAQ;eACpF,kBAAC,QAAD;MACE,eAAc;MACd,gBAAe;MACf,aAAa;MACb,GAAE;MACF,CAAA;KACE,CAAA,EAAA,QAEC;;GAEP;;EAER"}
|