@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
|
@@ -1,60 +1,79 @@
|
|
|
1
1
|
import { conditionalStyleToClassName as e, resolveConditionalStyle as t } from "../utils/conditionalFormat.js";
|
|
2
2
|
import { firstObjectRow as n, inferValueField as r, unwrapRows as i } from "../utils/inferFields.js";
|
|
3
|
-
import a from "./
|
|
4
|
-
import o from "
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
3
|
+
import a from "./SparklineChart.js";
|
|
4
|
+
import { clampProgressWidth as o, getTargetProgress as s } from "../utils/targetProgress.js";
|
|
5
|
+
import c from "./TrendIndicator.js";
|
|
6
|
+
import l from "./ComparisonBadge.js";
|
|
7
|
+
import { memo as u, useCallback as d, useMemo as f } from "react";
|
|
8
|
+
import { jsx as p, jsxs as m } from "react/jsx-runtime";
|
|
8
9
|
//#region src/bigconsole/components/widgets/metric-card/MetricCardWidget.tsx
|
|
9
|
-
function
|
|
10
|
+
function h(e) {
|
|
11
|
+
return e !== void 0 && /^[a-z]{3}$/i.test(e) ? e.toUpperCase() : void 0;
|
|
12
|
+
}
|
|
13
|
+
function g(e, t) {
|
|
14
|
+
let n = e?.format, r = n !== void 0 && n !== "auto" ? n === "currency" || n === "percentage" || n === "compact" ? n : "number" : t?.isCurrency === !0 ? "currency" : "number";
|
|
15
|
+
return {
|
|
16
|
+
style: r,
|
|
17
|
+
currency: h(t?.unit) ?? "USD",
|
|
18
|
+
unitSuffix: r === "currency" || e?.suffix ? void 0 : t?.unit
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function _(e, t, n) {
|
|
10
22
|
if (typeof e == "string") return e;
|
|
11
|
-
let {
|
|
12
|
-
switch (
|
|
23
|
+
let { prefix: r, suffix: i, decimalPlaces: a } = t || {}, o = n.style, s = typeof a == "number" && Number.isFinite(a) ? Math.min(Math.max(Math.trunc(a), 0), 20) : void 0, c;
|
|
24
|
+
switch (o) {
|
|
13
25
|
case "currency":
|
|
14
|
-
|
|
26
|
+
c = new Intl.NumberFormat("en-US", {
|
|
15
27
|
style: "currency",
|
|
16
|
-
currency:
|
|
17
|
-
minimumFractionDigits: 0,
|
|
18
|
-
maximumFractionDigits: 2
|
|
28
|
+
currency: n.currency,
|
|
29
|
+
minimumFractionDigits: s ?? 0,
|
|
30
|
+
maximumFractionDigits: s ?? 2
|
|
19
31
|
}).format(e);
|
|
20
32
|
break;
|
|
21
33
|
case "percentage":
|
|
22
|
-
|
|
34
|
+
c = `${(e * 100).toFixed(s ?? 1)}%`;
|
|
23
35
|
break;
|
|
24
36
|
case "compact":
|
|
25
|
-
|
|
37
|
+
c = new Intl.NumberFormat("en-US", {
|
|
26
38
|
notation: "compact",
|
|
27
|
-
|
|
39
|
+
minimumFractionDigits: s,
|
|
40
|
+
maximumFractionDigits: s ?? 1
|
|
28
41
|
}).format(e);
|
|
29
42
|
break;
|
|
30
|
-
default:
|
|
43
|
+
default: c = new Intl.NumberFormat("en-US", {
|
|
44
|
+
minimumFractionDigits: s,
|
|
45
|
+
maximumFractionDigits: s ?? 2
|
|
46
|
+
}).format(e);
|
|
31
47
|
}
|
|
32
|
-
return r && (
|
|
48
|
+
return r && (c = `${r}${c}`), i && (c = `${c}${i}`), n.unitSuffix && !c.endsWith(n.unitSuffix) && (c = `${c} ${n.unitSuffix}`), c;
|
|
33
49
|
}
|
|
34
|
-
function
|
|
50
|
+
function v(e) {
|
|
35
51
|
return e === void 0 || e === 0 ? "flat" : e > 0 ? "up" : "down";
|
|
36
52
|
}
|
|
37
|
-
function
|
|
53
|
+
function y(e, t) {
|
|
38
54
|
return e === "flat" ? "muted" : (t ? e === "up" : e === "down") ? "success" : "error";
|
|
39
55
|
}
|
|
40
|
-
var
|
|
41
|
-
let
|
|
42
|
-
if (!
|
|
56
|
+
var b = u(function({ widget: u, data: h, onClick: b, onDrilldown: x }) {
|
|
57
|
+
let S = u.config, C = f(() => {
|
|
58
|
+
if (!h) return;
|
|
43
59
|
let e = (e, t) => t.split(".").reduce((e, t) => e && typeof e == "object" ? e[t] : void 0, e);
|
|
44
|
-
if (typeof
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
60
|
+
if (typeof h == "number" || typeof h == "string") return { value: h };
|
|
61
|
+
let t = S?.valueField;
|
|
62
|
+
if (t && typeof h == "object") {
|
|
63
|
+
let n = h, r = e(n, t);
|
|
64
|
+
if (r !== void 0) {
|
|
65
|
+
let t = S?.previousValueField, i = t ? e(n, t) : void 0, a = i !== void 0 && i !== 0 && Number.isFinite(i) && typeof r == "number" ? (r - i) / i * 100 : void 0, o = h.changePercent, s = i === void 0 ? {} : {
|
|
66
|
+
previousValue: i,
|
|
67
|
+
changePercent: a ?? (Number.isFinite(o) ? o : void 0)
|
|
68
|
+
};
|
|
51
69
|
return {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
70
|
+
...h,
|
|
71
|
+
value: r,
|
|
72
|
+
...s
|
|
55
73
|
};
|
|
56
74
|
}
|
|
57
75
|
}
|
|
76
|
+
if (typeof h == "object" && "value" in h && h.value !== void 0) return h;
|
|
58
77
|
let a = [
|
|
59
78
|
"value",
|
|
60
79
|
"total",
|
|
@@ -65,12 +84,12 @@ var g = c(function({ widget: c, data: g, onClick: _, onDrilldown: v }) {
|
|
|
65
84
|
"totalRevenue",
|
|
66
85
|
"totalOrders"
|
|
67
86
|
];
|
|
68
|
-
for (let e of a) if (typeof
|
|
69
|
-
let t =
|
|
87
|
+
for (let e of a) if (typeof h == "object" && e in h) {
|
|
88
|
+
let t = h[e];
|
|
70
89
|
if (typeof t == "number" || typeof t == "string") return { value: t };
|
|
71
90
|
}
|
|
72
|
-
if (typeof
|
|
73
|
-
let e =
|
|
91
|
+
if (typeof h == "object" && "summary" in h) {
|
|
92
|
+
let e = h.summary;
|
|
74
93
|
if (e && typeof e == "object") {
|
|
75
94
|
for (let t of a) if (t in e) {
|
|
76
95
|
let n = e[t];
|
|
@@ -78,12 +97,12 @@ var g = c(function({ widget: c, data: g, onClick: _, onDrilldown: v }) {
|
|
|
78
97
|
}
|
|
79
98
|
}
|
|
80
99
|
}
|
|
81
|
-
if (typeof
|
|
82
|
-
let o = n(
|
|
100
|
+
if (typeof h == "number" || typeof h == "string") return { value: h };
|
|
101
|
+
let o = n(h);
|
|
83
102
|
if (o) {
|
|
84
|
-
let t =
|
|
103
|
+
let t = S?.valueField;
|
|
85
104
|
if (t) {
|
|
86
|
-
let n = i(
|
|
105
|
+
let n = i(h) ?? [o];
|
|
87
106
|
for (let r of n) if (r && typeof r == "object") {
|
|
88
107
|
let n = e(r, t);
|
|
89
108
|
if (typeof n == "number" || typeof n == "string") return { value: n };
|
|
@@ -96,90 +115,94 @@ var g = c(function({ widget: c, data: g, onClick: _, onDrilldown: v }) {
|
|
|
96
115
|
if (typeof e == "number" || typeof e == "string") return { value: e };
|
|
97
116
|
}
|
|
98
117
|
}
|
|
99
|
-
}, [
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
118
|
+
}, [h, S]), w = f(() => C?.trend || v(C?.changePercent), [C?.trend, C?.changePercent]), T = C?.trend !== void 0 || C?.changePercent !== void 0, E = S?.positiveIsGood ?? !0, D = f(() => g(S, C), [S, C]), O = f(() => C?.value === void 0 || C?.value === null ? "—" : _(C.value, S, D), [
|
|
119
|
+
C?.value,
|
|
120
|
+
S,
|
|
121
|
+
D
|
|
122
|
+
]), k = f(() => s(C?.value, C?.target), [C?.value, C?.target]), A = f(() => y(w, E), [w, E]), j = f(() => e(t(C?.value, S?.conditionalRules)), [C?.value, S?.conditionalRules]), M = d(() => {
|
|
123
|
+
x ? x({
|
|
124
|
+
value: C?.value,
|
|
125
|
+
changePercent: C?.changePercent,
|
|
126
|
+
trend: w,
|
|
127
|
+
target: C?.target
|
|
128
|
+
}) : b && u.drilldowns?.length && b();
|
|
106
129
|
}, [
|
|
107
|
-
_,
|
|
108
|
-
v,
|
|
109
|
-
c.drilldowns,
|
|
110
130
|
b,
|
|
111
|
-
x
|
|
112
|
-
|
|
113
|
-
|
|
131
|
+
x,
|
|
132
|
+
u.drilldowns,
|
|
133
|
+
C,
|
|
134
|
+
w
|
|
135
|
+
]), N = !!(x || b && u.drilldowns?.length);
|
|
136
|
+
return /* @__PURE__ */ m("div", {
|
|
114
137
|
className: `
|
|
115
138
|
flex flex-col h-full p-4
|
|
116
|
-
${
|
|
139
|
+
${N ? "cursor-pointer hover:bg-bg-sunken/50 transition-colors" : ""}
|
|
117
140
|
`,
|
|
118
|
-
onClick:
|
|
119
|
-
role:
|
|
120
|
-
tabIndex:
|
|
141
|
+
onClick: M,
|
|
142
|
+
role: N ? "button" : void 0,
|
|
143
|
+
tabIndex: N ? 0 : void 0,
|
|
121
144
|
onKeyDown: (e) => {
|
|
122
|
-
|
|
145
|
+
N && (e.key === "Enter" || e.key === " ") && (e.preventDefault(), M());
|
|
123
146
|
},
|
|
124
147
|
children: [
|
|
125
|
-
|
|
148
|
+
S?.subtitle && /* @__PURE__ */ p("p", {
|
|
126
149
|
className: "text-xs text-text-secondary mb-1 truncate",
|
|
127
|
-
children:
|
|
150
|
+
children: S.subtitle
|
|
128
151
|
}),
|
|
129
|
-
/* @__PURE__ */
|
|
152
|
+
/* @__PURE__ */ m("div", {
|
|
130
153
|
className: "flex items-baseline gap-2 mb-2",
|
|
131
|
-
children: [/* @__PURE__ */
|
|
132
|
-
className: `text-3xl font-semibold tracking-tight ${
|
|
133
|
-
title: String(
|
|
134
|
-
children:
|
|
135
|
-
}),
|
|
136
|
-
direction:
|
|
154
|
+
children: [/* @__PURE__ */ p("span", {
|
|
155
|
+
className: `text-3xl font-semibold tracking-tight ${j || "text-text-primary"}`,
|
|
156
|
+
title: String(C?.value),
|
|
157
|
+
children: O
|
|
158
|
+
}), S?.showTrend !== !1 && T && /* @__PURE__ */ p(c, {
|
|
159
|
+
direction: w,
|
|
137
160
|
size: "md",
|
|
138
|
-
positiveIsGood:
|
|
161
|
+
positiveIsGood: E
|
|
139
162
|
})]
|
|
140
163
|
}),
|
|
141
|
-
|
|
164
|
+
S?.showComparison !== !1 && C?.changePercent !== void 0 && /* @__PURE__ */ p("div", {
|
|
142
165
|
className: "mb-3",
|
|
143
|
-
children: /* @__PURE__ */
|
|
144
|
-
value:
|
|
145
|
-
label:
|
|
146
|
-
positiveIsGood:
|
|
166
|
+
children: /* @__PURE__ */ p(l, {
|
|
167
|
+
value: C.changePercent,
|
|
168
|
+
label: S?.comparisonLabel,
|
|
169
|
+
positiveIsGood: E,
|
|
147
170
|
size: "sm"
|
|
148
171
|
})
|
|
149
172
|
}),
|
|
150
|
-
|
|
173
|
+
k !== null && C?.target !== void 0 && /* @__PURE__ */ m("div", {
|
|
151
174
|
className: "mb-3",
|
|
152
|
-
children: [/* @__PURE__ */
|
|
175
|
+
children: [/* @__PURE__ */ m("div", {
|
|
153
176
|
className: "flex justify-between text-xs text-text-secondary mb-1",
|
|
154
|
-
children: [/* @__PURE__ */
|
|
155
|
-
}), /* @__PURE__ */
|
|
177
|
+
children: [/* @__PURE__ */ m("span", { children: ["Target: ", _(C.target, S, D)] }), /* @__PURE__ */ m("span", { children: [k.toFixed(0), "%"] })]
|
|
178
|
+
}), /* @__PURE__ */ p("div", {
|
|
156
179
|
className: "h-1.5 bg-bg-sunken rounded-full overflow-hidden",
|
|
157
|
-
children: /* @__PURE__ */
|
|
158
|
-
className: `h-full rounded-full transition-all ${
|
|
159
|
-
style: { width: `${
|
|
180
|
+
children: /* @__PURE__ */ p("div", {
|
|
181
|
+
className: `h-full rounded-full transition-all ${k >= 100 ? "bg-status-success-bg" : "bg-action-primary-bg"}`,
|
|
182
|
+
style: { width: `${o(k)}%` }
|
|
160
183
|
})
|
|
161
184
|
})]
|
|
162
185
|
}),
|
|
163
|
-
|
|
186
|
+
S?.showSparkline !== !1 && C?.sparklineData?.length ? /* @__PURE__ */ p("div", {
|
|
164
187
|
className: "mt-auto pt-2",
|
|
165
|
-
children: /* @__PURE__ */
|
|
166
|
-
data:
|
|
188
|
+
children: /* @__PURE__ */ p(a, {
|
|
189
|
+
data: C.sparklineData,
|
|
167
190
|
width: 120,
|
|
168
191
|
height: 32,
|
|
169
|
-
color:
|
|
192
|
+
color: A,
|
|
170
193
|
showFill: !0
|
|
171
194
|
})
|
|
172
195
|
}) : null,
|
|
173
|
-
|
|
196
|
+
N && /* @__PURE__ */ p("div", {
|
|
174
197
|
className: "mt-auto pt-2 flex items-center justify-end",
|
|
175
|
-
children: /* @__PURE__ */
|
|
198
|
+
children: /* @__PURE__ */ m("span", {
|
|
176
199
|
className: "text-xs text-text-secondary flex items-center gap-1",
|
|
177
|
-
children: ["View details", /* @__PURE__ */
|
|
200
|
+
children: ["View details", /* @__PURE__ */ p("svg", {
|
|
178
201
|
className: "w-3 h-3",
|
|
179
202
|
fill: "none",
|
|
180
203
|
stroke: "currentColor",
|
|
181
204
|
viewBox: "0 0 24 24",
|
|
182
|
-
children: /* @__PURE__ */
|
|
205
|
+
children: /* @__PURE__ */ p("path", {
|
|
183
206
|
strokeLinecap: "round",
|
|
184
207
|
strokeLinejoin: "round",
|
|
185
208
|
strokeWidth: 2,
|
|
@@ -192,6 +215,6 @@ var g = c(function({ widget: c, data: g, onClick: _, onDrilldown: v }) {
|
|
|
192
215
|
});
|
|
193
216
|
});
|
|
194
217
|
//#endregion
|
|
195
|
-
export {
|
|
218
|
+
export { b as default };
|
|
196
219
|
|
|
197
220
|
//# sourceMappingURL=MetricCardWidget.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MetricCardWidget.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/metric-card/MetricCardWidget.tsx"],"sourcesContent":["/**\n * MetricCardWidget Component\n *\n * Displays a single KPI/metric with trend, comparison, and optional sparkline.\n */\n\nimport { type FC, memo, useMemo, useCallback } from 'react';\nimport type { Widget, MetricCardConfig } from '../../../types';\nimport { TrendIndicator, type TrendDirection } from './TrendIndicator';\nimport { ComparisonBadge } from './ComparisonBadge';\nimport { SparklineChart, type SparklineDataPoint } from './SparklineChart';\nimport { firstObjectRow, inferValueField, unwrapRows } from '../utils/inferFields';\nimport { resolveConditionalStyle, conditionalStyleToClassName } from '../utils/conditionalFormat';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface MetricCardWidgetProps {\n /** Widget configuration */\n widget: Widget;\n /** Fetched data */\n data?: MetricCardData;\n /** Click handler for drilldown */\n onClick?: () => void;\n /** Drilldown handler - receives metric data */\n onDrilldown?: (selectedData: Record<string, unknown>) => void;\n}\n\nexport interface MetricCardData {\n /** Primary value to display */\n value: number | string;\n /** Previous value for comparison */\n previousValue?: number;\n /** Percentage change from previous */\n changePercent?: number;\n /** Trend direction override */\n trend?: TrendDirection;\n /** Sparkline data points */\n sparklineData?: SparklineDataPoint[];\n /** Target value for comparison */\n target?: number;\n /** Unit label (e.g., \"users\", \"$\") */\n unit?: string;\n /** Whether the metric is a currency */\n isCurrency?: boolean;\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Format a number based on configuration\n */\nfunction formatValue(value: number | string, config?: MetricCardConfig): string {\n if (typeof value === 'string') return value;\n\n const { format = 'number', prefix, suffix } = config || {};\n\n let formatted: string;\n\n switch (format) {\n case 'currency':\n formatted = new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD',\n minimumFractionDigits: 0,\n maximumFractionDigits: 2,\n }).format(value);\n break;\n\n case 'percentage':\n formatted = `${(value * 100).toFixed(1)}%`;\n break;\n\n case 'compact':\n formatted = new Intl.NumberFormat('en-US', {\n notation: 'compact',\n maximumFractionDigits: 1,\n }).format(value);\n break;\n\n case 'number':\n default:\n formatted = new Intl.NumberFormat('en-US', {\n maximumFractionDigits: 2,\n }).format(value);\n }\n\n // Apply prefix/suffix if provided\n if (prefix) formatted = `${prefix}${formatted}`;\n if (suffix) formatted = `${formatted}${suffix}`;\n\n return formatted;\n}\n\n/**\n * Calculate trend direction from change percent\n */\nfunction calculateTrend(changePercent?: number): TrendDirection {\n if (changePercent === undefined || changePercent === 0) return 'flat';\n return changePercent > 0 ? 'up' : 'down';\n}\n\n/**\n * Get sparkline color based on trend\n */\nfunction getSparklineColor(trend: TrendDirection, positiveIsGood: boolean): 'success' | 'error' | 'muted' {\n if (trend === 'flat') return 'muted';\n const isGood = positiveIsGood ? trend === 'up' : trend === 'down';\n return isGood ? 'success' : 'error';\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const MetricCardWidget: FC<MetricCardWidgetProps> = memo(function MetricCardWidget({\n widget,\n data: rawData,\n onClick,\n onDrilldown,\n}) {\n const config = widget.config as unknown as MetricCardConfig | undefined;\n\n // Normalize data - extract value from object if valueField is configured\n const data = useMemo((): MetricCardData | undefined => {\n if (!rawData) return undefined;\n\n // Helper to get nested value using dot notation (e.g., \"summary.totalSales\")\n const getNestedValue = (obj: Record<string, unknown>, path: string): unknown => {\n return path.split('.').reduce((current, key) => {\n return current && typeof current === 'object' ? (current as Record<string, unknown>)[key] : undefined;\n }, obj as unknown);\n };\n\n // A parser can return a bare scalar (e.g. `. | length` -> 127). Handle that\n // up front: the `in` operator below throws \"Cannot use 'in' operator to\n // search for 'value' in 127\" when rawData is a primitive, which the widget\n // error boundary surfaces as \"Failed to render\" (BOFF-4110).\n if (typeof rawData === 'number' || typeof rawData === 'string') {\n return { value: rawData };\n }\n\n // If data already has 'value' property, use it directly. Guard the `in`\n // operator with an object check — it only accepts an object on the RHS.\n if (typeof rawData === 'object' && 'value' in rawData && rawData.value !== undefined) {\n return rawData as MetricCardData;\n }\n\n // If valueField is configured, extract the value from that field (supports dot notation)\n const valueField = (config as unknown as Record<string, unknown> | undefined)?.valueField as string | undefined;\n if (valueField && typeof rawData === 'object') {\n const extractedValue = getNestedValue(rawData as unknown as Record<string, unknown>, valueField);\n if (extractedValue !== undefined) {\n // Also try to extract previousValue if previousValueField is configured\n const previousValueField = (config as unknown as Record<string, unknown> | undefined)?.previousValueField as\n string | undefined;\n const previousValue = previousValueField\n ? (getNestedValue(rawData as unknown as Record<string, unknown>, previousValueField) as number | undefined)\n : undefined;\n\n return {\n value: extractedValue as number | string,\n previousValue,\n changePercent:\n previousValue !== undefined && typeof extractedValue === 'number'\n ? ((extractedValue - previousValue) / previousValue) * 100\n : undefined,\n };\n }\n }\n\n // Try common field names at top level\n const commonFields = ['value', 'total', 'amount', 'count', 'sum', 'totalSales', 'totalRevenue', 'totalOrders'];\n for (const field of commonFields) {\n if (typeof rawData === 'object' && field in rawData) {\n const val = (rawData as unknown as Record<string, unknown>)[field];\n if (typeof val === 'number' || typeof val === 'string') {\n return { value: val };\n }\n }\n }\n\n // Try looking in nested 'summary' object (common pattern)\n if (typeof rawData === 'object' && 'summary' in rawData) {\n const summary = (rawData as Record<string, unknown>).summary as Record<string, unknown> | undefined;\n if (summary && typeof summary === 'object') {\n for (const field of commonFields) {\n if (field in summary) {\n const val = summary[field];\n if (typeof val === 'number' || typeof val === 'string') {\n return { value: val };\n }\n }\n }\n }\n }\n\n // If rawData is a number or string directly, use it\n if (typeof rawData === 'number' || typeof rawData === 'string') {\n return { value: rawData };\n }\n\n // BOFF-2246 smart default: if rawData is an array (or envelope around an\n // array) with object rows, use the configured valueField if it resolves\n // on any row (supports dotted paths like \"metrics.revenue\") — otherwise\n // pick the first numeric field from row[0]. This path only fires when\n // the earlier top-level extraction didn't find anything, but an explicit\n // `config.valueField` (flat or dotted) must always win over inference.\n const firstRow = firstObjectRow(rawData);\n if (firstRow) {\n const configuredField = (config as unknown as Record<string, unknown> | undefined)?.valueField as\n string | undefined;\n if (configuredField) {\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\n // from row[0] (that would silently misrepresent the user's mapping).\n const rows = unwrapRows(rawData) ?? [firstRow];\n for (const r of rows) {\n if (r && typeof r === 'object') {\n const resolved = getNestedValue(r as Record<string, unknown>, configuredField);\n if (typeof resolved === 'number' || typeof resolved === 'string') {\n return { value: resolved };\n }\n }\n }\n return undefined;\n }\n const inferredField = inferValueField(firstRow);\n if (inferredField) {\n const val = firstRow[inferredField];\n if (typeof val === 'number' || typeof val === 'string') {\n return { value: val };\n }\n }\n }\n\n return undefined;\n }, [rawData, config]);\n\n // Calculate derived values\n const trend = useMemo(() => {\n return data?.trend || calculateTrend(data?.changePercent);\n }, [data?.trend, data?.changePercent]);\n\n const positiveIsGood = config?.positiveIsGood ?? true;\n\n const formattedValue = useMemo(() => {\n // Treat 0 / \"\" as real values — only a missing value renders the em dash.\n // (`if (!data?.value)` would wrongly blank out a legitimate 0, e.g. an ED\n // boarding-compliance of 0% or an available-bed count of 0.)\n if (data?.value === undefined || data?.value === null) return '—';\n return formatValue(data.value, config);\n }, [data?.value, config]);\n\n const sparklineColor = useMemo(() => {\n return getSparklineColor(trend, positiveIsGood);\n }, [trend, positiveIsGood]);\n\n // Conditional formatting on the metric value (threshold-driven color, not\n // just trend direction). Falls back to the default primary text color.\n const valueConditionalClass = useMemo(() => {\n const style = resolveConditionalStyle(data?.value, config?.conditionalRules);\n return conditionalStyleToClassName(style);\n }, [data?.value, config?.conditionalRules]);\n\n // Handle click\n const handleClick = useCallback(() => {\n // Support both legacy onClick and new onDrilldown\n if (onDrilldown) {\n onDrilldown({\n value: data?.value,\n changePercent: data?.changePercent,\n trend,\n target: data?.target,\n });\n } else if (onClick && widget.drilldowns?.length) {\n onClick();\n }\n }, [onClick, onDrilldown, widget.drilldowns, data, trend]);\n\n // Determine if clickable\n const isClickable = Boolean(onDrilldown || (onClick && widget.drilldowns?.length));\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 {/* Subtitle/Label */}\n {config?.subtitle && <p className=\"text-xs text-text-secondary mb-1 truncate\">{config.subtitle}</p>}\n\n {/* Main Value */}\n <div className=\"flex items-baseline gap-2 mb-2\">\n <span\n className={`text-3xl font-semibold tracking-tight ${valueConditionalClass || 'text-text-primary'}`}\n title={String(data?.value)}\n >\n {formattedValue}\n </span>\n\n {/* Trend Indicator (inline) */}\n {config?.showTrend !== false && data?.changePercent !== undefined && (\n <TrendIndicator direction={trend} size=\"md\" positiveIsGood={positiveIsGood} />\n )}\n </div>\n\n {/* Comparison Badge */}\n {config?.showComparison !== false && data?.changePercent !== undefined && (\n <div className=\"mb-3\">\n <ComparisonBadge\n value={data.changePercent}\n label={config?.comparisonLabel}\n positiveIsGood={positiveIsGood}\n size=\"sm\"\n />\n </div>\n )}\n\n {/* Target Progress */}\n {data?.target !== undefined && typeof data.value === 'number' && (\n <div className=\"mb-3\">\n <div className=\"flex justify-between text-xs text-text-secondary mb-1\">\n <span>Target: {formatValue(data.target, config)}</span>\n <span>{((data.value / data.target) * 100).toFixed(0)}%</span>\n </div>\n <div className=\"h-1.5 bg-bg-sunken rounded-full overflow-hidden\">\n <div\n className={`h-full rounded-full transition-all ${\n data.value >= data.target ? 'bg-status-success-bg' : 'bg-action-primary-bg'\n }`}\n style={{\n width: `${Math.min((data.value / data.target) * 100, 100)}%`,\n }}\n />\n </div>\n </div>\n )}\n\n {/* Sparkline */}\n {config?.showSparkline !== false && data?.sparklineData?.length ? (\n <div className=\"mt-auto pt-2\">\n <SparklineChart data={data.sparklineData} width={120} height={32} color={sparklineColor} showFill />\n </div>\n ) : null}\n\n {/* Drilldown Indicator */}\n {isClickable && (\n <div className=\"mt-auto pt-2 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 MetricCardWidget;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MetricCardWidget.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/metric-card/MetricCardWidget.tsx"],"sourcesContent":["/**\n * MetricCardWidget Component\n *\n * Displays a single KPI/metric with trend, comparison, and optional sparkline.\n */\n\nimport { type FC, memo, useMemo, useCallback } from 'react';\nimport type { Widget, MetricCardConfig } from '../../../types';\nimport { TrendIndicator, type TrendDirection } from './TrendIndicator';\nimport { ComparisonBadge } from './ComparisonBadge';\nimport { SparklineChart, type SparklineDataPoint } from './SparklineChart';\nimport { firstObjectRow, inferValueField, unwrapRows } from '../utils/inferFields';\nimport { resolveConditionalStyle, conditionalStyleToClassName } from '../utils/conditionalFormat';\nimport { getTargetProgress, clampProgressWidth } from '../utils/targetProgress';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface MetricCardWidgetProps {\n /** Widget configuration */\n widget: Widget;\n /** Fetched data */\n data?: MetricCardData;\n /** Click handler for drilldown */\n onClick?: () => void;\n /** Drilldown handler - receives metric data */\n onDrilldown?: (selectedData: Record<string, unknown>) => void;\n}\n\nexport interface MetricCardData {\n /** Primary value to display */\n value: number | string;\n /** Previous value for comparison */\n previousValue?: number;\n /** Percentage change from previous */\n changePercent?: number;\n /** Trend direction override */\n trend?: TrendDirection;\n /** Sparkline data points */\n sparklineData?: SparklineDataPoint[];\n /** Target value for comparison */\n target?: number;\n /** Unit label (e.g., \"users\", \"$\") */\n unit?: string;\n /** Whether the metric is a currency */\n isCurrency?: boolean;\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * ISO-4217 codes are exactly three letters (\"USD\"); anything else is a display\n * label (\"users\", \"ms\").\n *\n * This regex is also the safety guard on untrusted parser data: `Intl.NumberFormat`\n * throws `RangeError` for a *malformed* currency (anything that is not exactly\n * three ASCII letters — e.g. `'ms'`), but accepts any well-formed code and simply\n * renders it verbatim when unregistered (`'RPS'` -> \"RPS 243.00\"). Returning\n * undefined for everything that is not three ASCII letters means a malformed\n * value can never reach `Intl`, so no try/catch is needed downstream. Keep this\n * predicate at least as strict if it is ever changed.\n */\nfunction currencyCodeFrom(unit?: string): string | undefined {\n return unit !== undefined && /^[a-z]{3}$/i.test(unit) ? unit.toUpperCase() : undefined;\n}\n\ninterface ResolvedFormat {\n style: 'currency' | 'percentage' | 'compact' | 'number';\n currency: string;\n /** Payload unit rendered as a trailing label; undefined when the style or config already covers it. */\n unitSuffix?: string;\n}\n\n/**\n * Decide the numeric style, honouring the payload without ever overriding the user.\n *\n * `MetricCardData` documents `unit` and `isCurrency`, but nothing read them — a\n * parser emitting `{value: 482350.75, unit: \"USD\", isCurrency: true}` rendered a\n * bare \"482,350.75\". They cannot simply take precedence though: WidgetRegistry\n * seeds `format` onto every card, so a persisted `'number'` is indistinguishable\n * from a deliberate choice. `'auto'` is the sentinel that defers to the payload.\n */\nfunction resolveFormat(config?: MetricCardConfig, data?: MetricCardData): ResolvedFormat {\n const configured = config?.format;\n const explicit = configured !== undefined && configured !== 'auto';\n\n const style: ResolvedFormat['style'] = explicit\n ? configured === 'currency' || configured === 'percentage' || configured === 'compact'\n ? configured\n : 'number' // 'number' and legacy 'decimal' both render as a plain number.\n : data?.isCurrency === true\n ? 'currency'\n : 'number';\n\n return {\n style,\n currency: currencyCodeFrom(data?.unit) ?? 'USD',\n // A currency render already carries its symbol, and config.suffix is the\n // user's own unit channel — neither should be doubled up.\n unitSuffix: style === 'currency' || config?.suffix ? undefined : data?.unit,\n };\n}\n\n/**\n * Format a number based on configuration\n */\nfunction formatValue(value: number | string, config: MetricCardConfig | undefined, resolved: ResolvedFormat): string {\n if (typeof value === 'string') return value;\n\n const { prefix, suffix, decimalPlaces } = config || {};\n const format = resolved.style;\n\n // `decimalPlaces` is seeded onto every metric card by WidgetRegistry and is\n // set explicitly by templated/seeded dashboards, so it must drive the\n // fraction digits. Clamp to the Intl-legal 0–20 range; when a legacy widget\n // omits the key we keep the previous per-format defaults.\n const fixedDigits =\n typeof decimalPlaces === 'number' && Number.isFinite(decimalPlaces)\n ? Math.min(Math.max(Math.trunc(decimalPlaces), 0), 20)\n : undefined;\n\n let formatted: string;\n\n switch (format) {\n case 'currency':\n formatted = new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: resolved.currency,\n minimumFractionDigits: fixedDigits ?? 0,\n maximumFractionDigits: fixedDigits ?? 2,\n }).format(value);\n break;\n\n case 'percentage':\n formatted = `${(value * 100).toFixed(fixedDigits ?? 1)}%`;\n break;\n\n case 'compact':\n formatted = new Intl.NumberFormat('en-US', {\n notation: 'compact',\n minimumFractionDigits: fixedDigits,\n maximumFractionDigits: fixedDigits ?? 1,\n }).format(value);\n break;\n\n case 'number':\n default:\n formatted = new Intl.NumberFormat('en-US', {\n minimumFractionDigits: fixedDigits,\n maximumFractionDigits: fixedDigits ?? 2,\n }).format(value);\n }\n\n // Apply prefix/suffix if provided\n if (prefix) formatted = `${prefix}${formatted}`;\n if (suffix) formatted = `${formatted}${suffix}`;\n\n // Payload unit (\"users\", \"ms\"). The endsWith guard avoids \"97.3% %\" when the\n // style already emits the symbol.\n if (resolved.unitSuffix && !formatted.endsWith(resolved.unitSuffix)) {\n formatted = `${formatted} ${resolved.unitSuffix}`;\n }\n\n return formatted;\n}\n\n/**\n * Calculate trend direction from change percent\n */\nfunction calculateTrend(changePercent?: number): TrendDirection {\n if (changePercent === undefined || changePercent === 0) return 'flat';\n return changePercent > 0 ? 'up' : 'down';\n}\n\n/**\n * Get sparkline color based on trend\n */\nfunction getSparklineColor(trend: TrendDirection, positiveIsGood: boolean): 'success' | 'error' | 'muted' {\n if (trend === 'flat') return 'muted';\n const isGood = positiveIsGood ? trend === 'up' : trend === 'down';\n return isGood ? 'success' : 'error';\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const MetricCardWidget: FC<MetricCardWidgetProps> = memo(function MetricCardWidget({\n widget,\n data: rawData,\n onClick,\n onDrilldown,\n}) {\n const config = widget.config as unknown as MetricCardConfig | undefined;\n\n // Normalize data - extract value from object if valueField is configured\n const data = useMemo((): MetricCardData | undefined => {\n if (!rawData) return undefined;\n\n // Helper to get nested value using dot notation (e.g., \"summary.totalSales\")\n const getNestedValue = (obj: Record<string, unknown>, path: string): unknown => {\n return path.split('.').reduce((current, key) => {\n return current && typeof current === 'object' ? (current as Record<string, unknown>)[key] : undefined;\n }, obj as unknown);\n };\n\n // A parser can return a bare scalar (e.g. `. | length` -> 127). Handle that\n // up front: the `in` operator below throws \"Cannot use 'in' operator to\n // search for 'value' in 127\" when rawData is a primitive, which the widget\n // error boundary surfaces as \"Failed to render\" (BOFF-4110).\n if (typeof rawData === 'number' || typeof rawData === 'string') {\n return { value: rawData };\n }\n\n // An explicit `config.valueField` mapping is authoritative and MUST be\n // resolved BEFORE the `value` pass-through below. Otherwise a payload that\n // merely happens to carry a top-level `value` silently overrides the field\n // the user picked in the inspector — e.g. `{status, value, p95LatencyMs}`\n // with valueField \"p95LatencyMs\" rendered the health flag, not the latency.\n const valueField = (config as unknown as Record<string, unknown> | undefined)?.valueField as string | undefined;\n if (valueField && typeof rawData === 'object') {\n const envelope = rawData as unknown as Record<string, unknown>;\n const extractedValue = getNestedValue(envelope, valueField);\n if (extractedValue !== undefined) {\n // Also try to extract previousValue if previousValueField is configured\n const previousValueField = (config as unknown as Record<string, unknown> | undefined)?.previousValueField as\n string | undefined;\n const previousValue = previousValueField\n ? (getNestedValue(envelope, previousValueField) as number | undefined)\n : undefined;\n\n // Spread the envelope first so sibling presentation fields the\n // pass-through used to supply (target, unit, trend, sparklineData…)\n // survive when a valueField mapping is configured.\n // A configured previousValueField derives the comparison from the\n // mapped field. Without one, the envelope's own previousValue /\n // changePercent stand — assigning undefined here would spread the\n // envelope and then immediately blank the very fields it supplied.\n // Guard the divisor — a previousValue of 0 would yield Infinity and\n // render \"Infinity%\" in the comparison badge. When we cannot compute a\n // change, keep whatever the envelope already carried rather than\n // blanking it: a source may legitimately pre-compute a change against a\n // zero baseline.\n const computedChange =\n previousValue !== undefined &&\n previousValue !== 0 &&\n Number.isFinite(previousValue) &&\n typeof extractedValue === 'number'\n ? ((extractedValue - previousValue) / previousValue) * 100\n : undefined;\n const envelopeChange = (rawData as MetricCardData).changePercent;\n const derivedComparison =\n previousValue !== undefined\n ? {\n previousValue,\n changePercent: computedChange ?? (Number.isFinite(envelopeChange) ? envelopeChange : undefined),\n }\n : {};\n\n return {\n ...(rawData as MetricCardData),\n value: extractedValue as number | string,\n ...derivedComparison,\n };\n }\n }\n\n // If data already has 'value' property, use it directly. Guard the `in`\n // operator with an object check — it only accepts an object on the RHS.\n if (typeof rawData === 'object' && 'value' in rawData && rawData.value !== undefined) {\n return rawData as MetricCardData;\n }\n\n // Try common field names at top level\n const commonFields = ['value', 'total', 'amount', 'count', 'sum', 'totalSales', 'totalRevenue', 'totalOrders'];\n for (const field of commonFields) {\n if (typeof rawData === 'object' && field in rawData) {\n const val = (rawData as unknown as Record<string, unknown>)[field];\n if (typeof val === 'number' || typeof val === 'string') {\n return { value: val };\n }\n }\n }\n\n // Try looking in nested 'summary' object (common pattern)\n if (typeof rawData === 'object' && 'summary' in rawData) {\n const summary = (rawData as Record<string, unknown>).summary as Record<string, unknown> | undefined;\n if (summary && typeof summary === 'object') {\n for (const field of commonFields) {\n if (field in summary) {\n const val = summary[field];\n if (typeof val === 'number' || typeof val === 'string') {\n return { value: val };\n }\n }\n }\n }\n }\n\n // If rawData is a number or string directly, use it\n if (typeof rawData === 'number' || typeof rawData === 'string') {\n return { value: rawData };\n }\n\n // BOFF-2246 smart default: if rawData is an array (or envelope around an\n // array) with object rows, use the configured valueField if it resolves\n // on any row (supports dotted paths like \"metrics.revenue\") — otherwise\n // pick the first numeric field from row[0]. This path only fires when\n // the earlier top-level extraction didn't find anything, but an explicit\n // `config.valueField` (flat or dotted) must always win over inference.\n const firstRow = firstObjectRow(rawData);\n if (firstRow) {\n const configuredField = (config as unknown as Record<string, unknown> | undefined)?.valueField as\n string | undefined;\n if (configuredField) {\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\n // from row[0] (that would silently misrepresent the user's mapping).\n const rows = unwrapRows(rawData) ?? [firstRow];\n for (const r of rows) {\n if (r && typeof r === 'object') {\n const resolved = getNestedValue(r as Record<string, unknown>, configuredField);\n if (typeof resolved === 'number' || typeof resolved === 'string') {\n return { value: resolved };\n }\n }\n }\n return undefined;\n }\n const inferredField = inferValueField(firstRow);\n if (inferredField) {\n const val = firstRow[inferredField];\n if (typeof val === 'number' || typeof val === 'string') {\n return { value: val };\n }\n }\n }\n\n return undefined;\n }, [rawData, config]);\n\n // Calculate derived values\n const trend = useMemo(() => {\n return data?.trend || calculateTrend(data?.changePercent);\n }, [data?.trend, data?.changePercent]);\n\n // The arrow is renderable when the payload carries an explicit direction\n // override OR a change percentage to derive one from. Gating solely on\n // `changePercent` meant a `trend`-only payload — a documented field of\n // MetricCardData, and one the sparkline colour and drilldown payload already\n // honour — rendered no indicator at all despite `showTrend` being enabled.\n // Without either field `trend` collapses to a meaningless 'flat', so we\n // still render nothing.\n const hasTrendSignal = data?.trend !== undefined || data?.changePercent !== undefined;\n\n const positiveIsGood = config?.positiveIsGood ?? true;\n\n const resolvedFormat = useMemo(() => resolveFormat(config, data), [config, data]);\n\n const formattedValue = useMemo(() => {\n // Treat 0 / \"\" as real values — only a missing value renders the em dash.\n // (`if (!data?.value)` would wrongly blank out a legitimate 0, e.g. an ED\n // boarding-compliance of 0% or an available-bed count of 0.)\n if (data?.value === undefined || data?.value === null) return '—';\n return formatValue(data.value, config, resolvedFormat);\n }, [data?.value, config, resolvedFormat]);\n\n // Null whenever no meaningful ratio exists, which also gates the whole\n // target block. Resolving `valueField` before the `value` pass-through made\n // a numeric value reachable alongside a `target: 0` from the same envelope,\n // so the previously-unreachable division now needs a real guard.\n const targetProgress = useMemo(() => getTargetProgress(data?.value, data?.target), [data?.value, data?.target]);\n\n const sparklineColor = useMemo(() => {\n return getSparklineColor(trend, positiveIsGood);\n }, [trend, positiveIsGood]);\n\n // Conditional formatting on the metric value (threshold-driven color, not\n // just trend direction). Falls back to the default primary text color.\n const valueConditionalClass = useMemo(() => {\n const style = resolveConditionalStyle(data?.value, config?.conditionalRules);\n return conditionalStyleToClassName(style);\n }, [data?.value, config?.conditionalRules]);\n\n // Handle click\n const handleClick = useCallback(() => {\n // Support both legacy onClick and new onDrilldown\n if (onDrilldown) {\n onDrilldown({\n value: data?.value,\n changePercent: data?.changePercent,\n trend,\n target: data?.target,\n });\n } else if (onClick && widget.drilldowns?.length) {\n onClick();\n }\n }, [onClick, onDrilldown, widget.drilldowns, data, trend]);\n\n // Determine if clickable\n const isClickable = Boolean(onDrilldown || (onClick && widget.drilldowns?.length));\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 {/* Subtitle/Label */}\n {config?.subtitle && <p className=\"text-xs text-text-secondary mb-1 truncate\">{config.subtitle}</p>}\n\n {/* Main Value */}\n <div className=\"flex items-baseline gap-2 mb-2\">\n <span\n className={`text-3xl font-semibold tracking-tight ${valueConditionalClass || 'text-text-primary'}`}\n title={String(data?.value)}\n >\n {formattedValue}\n </span>\n\n {/* Trend Indicator (inline) */}\n {config?.showTrend !== false && hasTrendSignal && (\n <TrendIndicator direction={trend} size=\"md\" positiveIsGood={positiveIsGood} />\n )}\n </div>\n\n {/* Comparison Badge */}\n {config?.showComparison !== false && data?.changePercent !== undefined && (\n <div className=\"mb-3\">\n <ComparisonBadge\n value={data.changePercent}\n label={config?.comparisonLabel}\n positiveIsGood={positiveIsGood}\n size=\"sm\"\n />\n </div>\n )}\n\n {/* Target Progress */}\n {targetProgress !== null && data?.target !== undefined && (\n <div className=\"mb-3\">\n <div className=\"flex justify-between text-xs text-text-secondary mb-1\">\n <span>Target: {formatValue(data.target, config, resolvedFormat)}</span>\n <span>{targetProgress.toFixed(0)}%</span>\n </div>\n <div className=\"h-1.5 bg-bg-sunken rounded-full overflow-hidden\">\n <div\n className={`h-full rounded-full transition-all ${\n targetProgress >= 100 ? 'bg-status-success-bg' : 'bg-action-primary-bg'\n }`}\n style={{\n // Clamp both ends — a negative value would otherwise emit a\n // negative width, which the browser drops entirely.\n width: `${clampProgressWidth(targetProgress)}%`,\n }}\n />\n </div>\n </div>\n )}\n\n {/* Sparkline */}\n {config?.showSparkline !== false && data?.sparklineData?.length ? (\n <div className=\"mt-auto pt-2\">\n <SparklineChart data={data.sparklineData} width={120} height={32} color={sparklineColor} showFill />\n </div>\n ) : null}\n\n {/* Drilldown Indicator */}\n {isClickable && (\n <div className=\"mt-auto pt-2 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 MetricCardWidget;\n"],"mappings":"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"}
|
|
@@ -27,20 +27,28 @@ var a = {
|
|
|
27
27
|
let { t: d } = n(), f = t(() => {
|
|
28
28
|
if (!e.length) return {
|
|
29
29
|
line: "",
|
|
30
|
-
area: ""
|
|
30
|
+
area: "",
|
|
31
|
+
end: null
|
|
31
32
|
};
|
|
32
|
-
let t = e.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
let t = e.reduce((e, t) => (typeof t.value == "number" && Number.isFinite(t.value) && e.push(t.value), e), []);
|
|
34
|
+
if (!t.length) return {
|
|
35
|
+
line: "",
|
|
36
|
+
area: "",
|
|
37
|
+
end: null
|
|
38
|
+
};
|
|
39
|
+
let n = t.reduce((e, t) => Math.min(e, t), Infinity), r = t.reduce((e, t) => Math.max(e, t), -Infinity) - n || 1, i = u, a = o - i * 2, c = s - i * 2, l = t.length - 1, d = t.map((e, t) => ({
|
|
40
|
+
x: i + (l === 0 ? a / 2 : t / l * a),
|
|
41
|
+
y: i + c - (e - n) / r * c
|
|
42
|
+
})), f = d.map((e, t) => `${t === 0 ? "M" : "L"} ${e.x} ${e.y}`).join(" "), p = d[d.length - 1];
|
|
36
43
|
return {
|
|
37
|
-
line:
|
|
44
|
+
line: f,
|
|
38
45
|
area: `
|
|
39
|
-
${
|
|
40
|
-
L ${
|
|
46
|
+
${f}
|
|
47
|
+
L ${p.x} ${s - i}
|
|
41
48
|
L ${i} ${s - i}
|
|
42
49
|
Z
|
|
43
|
-
|
|
50
|
+
`,
|
|
51
|
+
end: p
|
|
44
52
|
};
|
|
45
53
|
}, [
|
|
46
54
|
e,
|
|
@@ -48,7 +56,7 @@ var a = {
|
|
|
48
56
|
s,
|
|
49
57
|
u
|
|
50
58
|
]);
|
|
51
|
-
if (!
|
|
59
|
+
if (!f.end) return /* @__PURE__ */ r("div", {
|
|
52
60
|
className: "flex items-center justify-center text-text-secondary",
|
|
53
61
|
style: {
|
|
54
62
|
width: o,
|
|
@@ -80,9 +88,9 @@ var a = {
|
|
|
80
88
|
strokeLinecap: "round",
|
|
81
89
|
strokeLinejoin: "round"
|
|
82
90
|
}),
|
|
83
|
-
|
|
84
|
-
cx:
|
|
85
|
-
cy:
|
|
91
|
+
/* @__PURE__ */ r("circle", {
|
|
92
|
+
cx: f.end.x,
|
|
93
|
+
cy: f.end.y,
|
|
86
94
|
r: u + 1,
|
|
87
95
|
className: `${p.stroke.replace("stroke-", "fill-")}`
|
|
88
96
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SparklineChart.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/metric-card/SparklineChart.tsx"],"sourcesContent":["/**\n * SparklineChart Component\n *\n * Mini trend chart for metric cards.\n */\n\nimport { type FC, memo, useMemo } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nexport interface SparklineDataPoint {\n value: number;\n timestamp?: string;\n}\n\nexport interface SparklineChartProps {\n /** Data points for the sparkline */\n data: SparklineDataPoint[];\n /** Width of the chart */\n width?: number;\n /** Height of the chart */\n height?: number;\n /** Line color (uses semantic token) */\n color?: 'primary' | 'success' | 'error' | 'warning' | 'muted';\n /** Whether to show the fill area */\n showFill?: boolean;\n /** Stroke width */\n strokeWidth?: number;\n}\n\nconst COLOR_CLASSES = {\n primary: {\n stroke: 'stroke-action-primary-bg',\n fill: 'fill-action-primary-bg/20',\n },\n success: {\n stroke: 'stroke-status-success-bg',\n fill: 'fill-status-success-bg/20',\n },\n error: {\n stroke: 'stroke-status-error-bg',\n fill: 'fill-status-error-bg/20',\n },\n warning: {\n stroke: 'stroke-status-warning-bg',\n fill: 'fill-status-warning-bg/20',\n },\n muted: {\n stroke: 'stroke-text-secondary',\n fill: 'fill-text-secondary/10',\n },\n};\n\nexport const SparklineChart: FC<SparklineChartProps> = memo(function SparklineChart({\n data,\n width = 100,\n height = 32,\n color = 'primary',\n showFill = true,\n strokeWidth = 2,\n}) {\n const { t } = useI18n();\n const pathData = useMemo(() => {\n if (!data.length) return { line: '', area: '' };\n\n const values = data.
|
|
1
|
+
{"version":3,"file":"SparklineChart.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/metric-card/SparklineChart.tsx"],"sourcesContent":["/**\n * SparklineChart Component\n *\n * Mini trend chart for metric cards.\n */\n\nimport { type FC, memo, useMemo } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nexport interface SparklineDataPoint {\n value: number;\n timestamp?: string;\n}\n\nexport interface SparklineChartProps {\n /** Data points for the sparkline */\n data: SparklineDataPoint[];\n /** Width of the chart */\n width?: number;\n /** Height of the chart */\n height?: number;\n /** Line color (uses semantic token) */\n color?: 'primary' | 'success' | 'error' | 'warning' | 'muted';\n /** Whether to show the fill area */\n showFill?: boolean;\n /** Stroke width */\n strokeWidth?: number;\n}\n\nconst COLOR_CLASSES = {\n primary: {\n stroke: 'stroke-action-primary-bg',\n fill: 'fill-action-primary-bg/20',\n },\n success: {\n stroke: 'stroke-status-success-bg',\n fill: 'fill-status-success-bg/20',\n },\n error: {\n stroke: 'stroke-status-error-bg',\n fill: 'fill-status-error-bg/20',\n },\n warning: {\n stroke: 'stroke-status-warning-bg',\n fill: 'fill-status-warning-bg/20',\n },\n muted: {\n stroke: 'stroke-text-secondary',\n fill: 'fill-text-secondary/10',\n },\n};\n\nexport const SparklineChart: FC<SparklineChartProps> = memo(function SparklineChart({\n data,\n width = 100,\n height = 32,\n color = 'primary',\n showFill = true,\n strokeWidth = 2,\n}) {\n const { t } = useI18n();\n const pathData = useMemo(() => {\n if (!data.length) return { line: '', area: '', end: null };\n\n // Only finite numbers can be plotted. A non-numeric point (a string,\n // null or NaN coming out of a parser) would NaN-poison min/max — and\n // `NaN - NaN || 1` silently collapses the range to 1 — blanking the whole\n // series. Drop such points instead.\n // reduce, not spread: a long series would overflow the call stack.\n const values = data.reduce<number[]>((acc, d) => {\n if (typeof d.value === 'number' && Number.isFinite(d.value)) acc.push(d.value);\n return acc;\n }, []);\n if (!values.length) return { line: '', area: '', end: null };\n\n const min = values.reduce((a, b) => Math.min(a, b), Infinity);\n const max = values.reduce((a, b) => Math.max(a, b), -Infinity);\n const range = max - min || 1;\n\n const padding = strokeWidth;\n const chartWidth = width - padding * 2;\n const chartHeight = height - padding * 2;\n\n // A single point has no span to divide by — `i / 0` is NaN, which makes\n // every coordinate invalid. Draw it centred instead.\n const lastIndex = values.length - 1;\n const points = values.map((value, i) => {\n const x = padding + (lastIndex === 0 ? chartWidth / 2 : (i / lastIndex) * chartWidth);\n const y = padding + chartHeight - ((value - min) / range) * chartHeight;\n return { x, y };\n });\n\n // Create line path\n const linePath = points.map((p, i) => `${i === 0 ? 'M' : 'L'} ${p.x} ${p.y}`).join(' ');\n\n const end = points[points.length - 1];\n\n // Create area path (closed shape for fill)\n const areaPath = `\n ${linePath}\n L ${end.x} ${height - padding}\n L ${padding} ${height - padding}\n Z\n `;\n\n return { line: linePath, area: areaPath, end };\n }, [data, width, height, strokeWidth]);\n\n // `end` is null exactly when there is nothing plottable — either an empty\n // `data` array, or one whose every point was rejected as non-finite. Gating\n // on `data.length` alone would let the latter through and render a silent\n // blank SVG instead of the \"No data\" fallback.\n if (!pathData.end) {\n return (\n <div className=\"flex items-center justify-center text-text-secondary\" style={{ width, height }}>\n <span className=\"text-xs\">{t('bigconsole.widget.sparkline.noData', 'No data')}</span>\n </div>\n );\n }\n\n const colors = COLOR_CLASSES[color];\n\n return (\n <svg\n width={width}\n height={height}\n className=\"overflow-visible\"\n role=\"img\"\n aria-label={t('bigconsole.widget.sparkline.ariaLabel', 'Sparkline chart')}\n >\n {/* Gradient fill */}\n {showFill && <path d={pathData.area} className={colors.fill} strokeWidth={0} />}\n\n {/* Line */}\n <path\n d={pathData.line}\n className={colors.stroke}\n fill=\"none\"\n strokeWidth={strokeWidth}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n\n {/* End dot — reuses the already-validated last plotted point rather than\n recomputing min/max from raw (possibly non-numeric) input. */}\n <circle\n cx={pathData.end.x}\n cy={pathData.end.y}\n r={strokeWidth + 1}\n className={`${colors.stroke.replace('stroke-', 'fill-')}`}\n />\n </svg>\n );\n});\n\nexport default SparklineChart;\n"],"mappings":";;;;AA6BA,IAAM,IAAgB;CACpB,SAAS;EACP,QAAQ;EACR,MAAM;EACP;CACD,SAAS;EACP,QAAQ;EACR,MAAM;EACP;CACD,OAAO;EACL,QAAQ;EACR,MAAM;EACP;CACD,SAAS;EACP,QAAQ;EACR,MAAM;EACP;CACD,OAAO;EACL,QAAQ;EACR,MAAM;EACP;CACF,EAEY,IAA0C,EAAK,SAAwB,EAClF,SACA,WAAQ,KACR,YAAS,IACT,WAAQ,WACR,cAAW,IACX,iBAAc,KACb;CACD,IAAM,EAAE,SAAM,GAAS,EACjB,IAAW,QAAc;AAC7B,MAAI,CAAC,EAAK,OAAQ,QAAO;GAAE,MAAM;GAAI,MAAM;GAAI,KAAK;GAAM;EAO1D,IAAM,IAAS,EAAK,QAAkB,GAAK,OACrC,OAAO,EAAE,SAAU,YAAY,OAAO,SAAS,EAAE,MAAM,IAAE,EAAI,KAAK,EAAE,MAAM,EACvE,IACN,EAAE,CAAC;AACN,MAAI,CAAC,EAAO,OAAQ,QAAO;GAAE,MAAM;GAAI,MAAM;GAAI,KAAK;GAAM;EAE5D,IAAM,IAAM,EAAO,QAAQ,GAAG,MAAM,KAAK,IAAI,GAAG,EAAE,EAAE,SAAS,EAEvD,IADM,EAAO,QAAQ,GAAG,MAAM,KAAK,IAAI,GAAG,EAAE,EAAE,UAAU,GAC1C,KAAO,GAErB,IAAU,GACV,IAAa,IAAQ,IAAU,GAC/B,IAAc,IAAS,IAAU,GAIjC,IAAY,EAAO,SAAS,GAC5B,IAAS,EAAO,KAAK,GAAO,OAGzB;GAAE,GAFC,KAAW,MAAc,IAAI,IAAa,IAAK,IAAI,IAAa;GAE9D,GADF,IAAU,KAAgB,IAAQ,KAAO,IAAS;GAC7C,EACf,EAGI,IAAW,EAAO,KAAK,GAAG,MAAM,GAAG,MAAM,IAAI,MAAM,IAAI,GAAG,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,KAAK,IAAI,EAEjF,IAAM,EAAO,EAAO,SAAS;AAUnC,SAAO;GAAE,MAAM;GAAU,MAPR;UACX,EAAS;YACP,EAAI,EAAE,GAAG,IAAS,EAAQ;YAC1B,EAAQ,GAAG,IAAS,EAAQ;;;GAIK;GAAK;IAC7C;EAAC;EAAM;EAAO;EAAQ;EAAY,CAAC;AAMtC,KAAI,CAAC,EAAS,IACZ,QACE,kBAAC,OAAD;EAAK,WAAU;EAAuD,OAAO;GAAE;GAAO;GAAQ;YAC5F,kBAAC,QAAD;GAAM,WAAU;aAAW,EAAE,sCAAsC,UAAU;GAAQ,CAAA;EACjF,CAAA;CAIV,IAAM,IAAS,EAAc;AAE7B,QACE,kBAAC,OAAD;EACS;EACC;EACR,WAAU;EACV,MAAK;EACL,cAAY,EAAE,yCAAyC,kBAAkB;YAL3E;GAQG,KAAY,kBAAC,QAAD;IAAM,GAAG,EAAS;IAAM,WAAW,EAAO;IAAM,aAAa;IAAK,CAAA;GAG/E,kBAAC,QAAD;IACE,GAAG,EAAS;IACZ,WAAW,EAAO;IAClB,MAAK;IACQ;IACb,eAAc;IACd,gBAAe;IACf,CAAA;GAIF,kBAAC,UAAD;IACE,IAAI,EAAS,IAAI;IACjB,IAAI,EAAS,IAAI;IACjB,GAAG,IAAc;IACjB,WAAW,GAAG,EAAO,OAAO,QAAQ,WAAW,QAAQ;IACvD,CAAA;GACE;;EAER"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import e from "./
|
|
2
|
-
import t from "./
|
|
3
|
-
import n from "./
|
|
1
|
+
import e from "./SparklineChart.js";
|
|
2
|
+
import t from "./TrendIndicator.js";
|
|
3
|
+
import n from "./ComparisonBadge.js";
|
|
4
4
|
import r from "./MetricCardWidget.js";
|
|
5
5
|
import i from "./MetricCardConfig.js";
|
|
6
|
-
export {
|
|
6
|
+
export { n as ComparisonBadge, i as MetricCardConfig, r as MetricCardWidget, e as SparklineChart, t as TrendIndicator };
|
|
@@ -393,7 +393,7 @@ var v = n(function({ widget: n, data: c = [], onDrilldown: p }) {
|
|
|
393
393
|
className: "inline-flex items-center gap-1 px-2 py-1 rounded-md bg-bg-surface border border-border-default text-xs text-text-primary cursor-grab",
|
|
394
394
|
children: [
|
|
395
395
|
/* @__PURE__ */ d(c, {
|
|
396
|
-
className: "w-3 h-3 text-text-
|
|
396
|
+
className: "w-3 h-3 text-text-muted",
|
|
397
397
|
"aria-hidden": "true"
|
|
398
398
|
}),
|
|
399
399
|
/* @__PURE__ */ d("span", { children: (n?.label || e).replace(/_/g, " ") }),
|
|
@@ -417,7 +417,7 @@ var v = n(function({ widget: n, data: c = [], onDrilldown: p }) {
|
|
|
417
417
|
onDrop: h(t),
|
|
418
418
|
className: `flex-1 min-w-[8rem] rounded-md border p-2 transition-colors ${l === t ? "border-action-primary-bg bg-action-primary-bg/5" : "border-border-default bg-bg-sunken/30"}`,
|
|
419
419
|
children: [/* @__PURE__ */ d("div", {
|
|
420
|
-
className: "text-[10px] font-semibold uppercase tracking-wider text-text-
|
|
420
|
+
className: "text-[10px] font-semibold uppercase tracking-wider text-text-muted mb-1.5",
|
|
421
421
|
children: e
|
|
422
422
|
}), /* @__PURE__ */ d("div", {
|
|
423
423
|
className: "flex flex-wrap gap-1.5 min-h-[1.75rem]",
|
|
@@ -431,7 +431,7 @@ var v = n(function({ widget: n, data: c = [], onDrilldown: p }) {
|
|
|
431
431
|
_(s("bigconsole.widget.pivotTable.zoneColumns", "Columns"), "columns", t.map((e) => g(e, "columns"))),
|
|
432
432
|
_(s("bigconsole.widget.pivotTable.zoneValues", "Values"), "values", n.map((e) => g(e.field, "values", e))),
|
|
433
433
|
_(s("bigconsole.widget.pivotTable.zoneAvailable", "Available"), "available", r.length > 0 ? r.map((e) => g(e, "available")) : /* @__PURE__ */ d("span", {
|
|
434
|
-
className: "text-[11px] text-text-
|
|
434
|
+
className: "text-[11px] text-text-muted italic",
|
|
435
435
|
children: s("bigconsole.widget.pivotTable.allFieldsInUse", "All fields in use")
|
|
436
436
|
}))
|
|
437
437
|
]
|