indas-ui 0.1.2 → 0.1.4
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/cjs/components/chat/ai-messages.js +3 -2
- package/dist/cjs/components/chat/chat.js +2 -1
- package/dist/cjs/components/dashboard/charts/Area.js +6 -4
- package/dist/cjs/components/dashboard/charts/Bar.js +6 -4
- package/dist/cjs/components/dashboard/charts/FlowMap.js +2 -1
- package/dist/cjs/components/dashboard/charts/Funnel.js +2 -1
- package/dist/cjs/components/dashboard/charts/GeoMap.js +2 -1
- package/dist/cjs/components/dashboard/charts/Line.js +5 -3
- package/dist/cjs/components/dashboard/charts/Pareto.js +2 -1
- package/dist/cjs/components/dashboard/charts/Sankey.js +4 -3
- package/dist/cjs/components/dashboard/charts/Sparkline.js +2 -1
- package/dist/cjs/components/dashboard/charts/Treemap.js +2 -1
- package/dist/cjs/components/dashboard/charts/index.js +7 -1
- package/dist/cjs/components/dashboard/kpi/KpiBreakdown.js +3 -2
- package/dist/cjs/components/dashboard/kpi/KpiComparison.js +2 -1
- package/dist/cjs/components/dashboard/kpi/KpiQuickStats.js +2 -1
- package/dist/cjs/components/dashboard/kpi/KpiRating.js +3 -2
- package/dist/cjs/components/datagrid/DataGrid.js +2 -2
- package/dist/cjs/components/datagrid/cells/CellRenderer.js +4 -4
- package/dist/cjs/components/datagrid/cells/SelectionCell.js +10 -10
- package/dist/cjs/components/datagrid/columns/ColumnContextMenu.js +2 -1
- package/dist/cjs/components/datagrid/controls/PaginationControls.js +3 -3
- package/dist/cjs/components/datagrid/controls/menu/view/CardView.js +8 -7
- package/dist/cjs/components/datagrid/controls/menu/view/ChartView.js +2 -1
- package/dist/cjs/components/datagrid/rows/DraggableRow.js +6 -6
- package/dist/cjs/components/datagrid/search/InlineSearchCell.js +4 -4
- package/dist/cjs/components/datagrid/utils/grid-helpers.js +6 -5
- package/dist/cjs/components/draft/AutoSaveIndicator.js +9 -9
- package/dist/cjs/components/draft/DraftModal.js +6 -6
- package/dist/cjs/components/forms/date-picker/DatePicker.js +7 -13
- package/dist/cjs/components/forms/date-picker/DateTimePicker.js +2 -2
- package/dist/cjs/components/layout/footer.js +2 -1
- package/dist/cjs/components/layout/side-panel.js +4 -1
- package/dist/cjs/components/ui/display/stats-card.js +2 -1
- package/dist/cjs/lib/number-format.js +60 -0
- package/dist/cjs/lib/utils.js +4 -3
- package/dist/cjs/styles/index.css +5 -5
- package/dist/components/chat/ai-messages.js +3 -2
- package/dist/components/chat/chat.js +2 -1
- package/dist/components/dashboard/charts/Area.d.ts +5 -1
- package/dist/components/dashboard/charts/Area.js +6 -4
- package/dist/components/dashboard/charts/Bar.d.ts +5 -1
- package/dist/components/dashboard/charts/Bar.js +6 -4
- package/dist/components/dashboard/charts/FlowMap.js +2 -1
- package/dist/components/dashboard/charts/Funnel.js +2 -1
- package/dist/components/dashboard/charts/GeoMap.js +2 -1
- package/dist/components/dashboard/charts/Line.d.ts +5 -1
- package/dist/components/dashboard/charts/Line.js +5 -3
- package/dist/components/dashboard/charts/Pareto.js +2 -1
- package/dist/components/dashboard/charts/Sankey.js +4 -3
- package/dist/components/dashboard/charts/Sparkline.js +2 -1
- package/dist/components/dashboard/charts/Treemap.js +2 -1
- package/dist/components/dashboard/charts/index.d.ts +1 -0
- package/dist/components/dashboard/charts/index.js +2 -0
- package/dist/components/dashboard/kpi/KpiBreakdown.js +3 -2
- package/dist/components/dashboard/kpi/KpiComparison.js +2 -1
- package/dist/components/dashboard/kpi/KpiQuickStats.js +2 -1
- package/dist/components/dashboard/kpi/KpiRating.js +3 -2
- package/dist/components/datagrid/DataGrid.js +2 -2
- package/dist/components/datagrid/cells/CellRenderer.js +4 -4
- package/dist/components/datagrid/cells/SelectionCell.js +10 -10
- package/dist/components/datagrid/columns/ColumnContextMenu.js +2 -1
- package/dist/components/datagrid/controls/PaginationControls.js +3 -3
- package/dist/components/datagrid/controls/menu/view/CardView.js +8 -7
- package/dist/components/datagrid/controls/menu/view/ChartView.js +2 -1
- package/dist/components/datagrid/rows/DraggableRow.js +6 -6
- package/dist/components/datagrid/search/InlineSearchCell.js +4 -4
- package/dist/components/datagrid/utils/grid-helpers.d.ts +1 -1
- package/dist/components/datagrid/utils/grid-helpers.js +3 -2
- package/dist/components/draft/AutoSaveIndicator.js +9 -9
- package/dist/components/draft/DraftModal.js +6 -6
- package/dist/components/forms/date-picker/DatePicker.js +7 -13
- package/dist/components/forms/date-picker/DateTimePicker.js +2 -2
- package/dist/components/layout/footer.js +2 -1
- package/dist/components/layout/side-panel.js +4 -1
- package/dist/components/ui/display/stats-card.js +2 -1
- package/dist/lib/number-format.d.ts +18 -0
- package/dist/lib/number-format.js +54 -0
- package/dist/lib/utils.js +4 -3
- package/dist/styles/index.css +5 -5
- package/package.json +4 -4
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import ReactECharts from 'echarts-for-react';
|
|
4
4
|
import { cn } from '../../../lib/utils.js';
|
|
5
|
+
import { formatCompactNumber, formatNumber } from '../../../lib/number-format.js';
|
|
5
6
|
// Convert any color (hex, rgb(), rgba(), space-separated rgb()) to rgba() with given alpha (0–1).
|
|
6
7
|
// Hex+alpha-suffix concat (e.g. color + 'cc') breaks for non-hex inputs and crashes canvas gradients.
|
|
7
8
|
function withAlpha(color, alpha) {
|
|
@@ -31,10 +32,11 @@ function withAlpha(color, alpha) {
|
|
|
31
32
|
/**
|
|
32
33
|
* AreaChart - ECharts Area Chart component
|
|
33
34
|
*/
|
|
34
|
-
export function AreaChart({ data, xKey, series, height = 300, showLegend = true, showGrid = true, smooth = true, className }) {
|
|
35
|
+
export function AreaChart({ data, xKey, series, height = 300, showLegend = true, showGrid = true, smooth = true, valueFormatter, axisFormatter, className }) {
|
|
35
36
|
const option = {
|
|
36
37
|
tooltip: {
|
|
37
38
|
trigger: 'axis',
|
|
39
|
+
valueFormatter: (v) => (typeof v === 'number' ? (valueFormatter ?? formatNumber)(v) : String(v ?? '')),
|
|
38
40
|
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
|
39
41
|
borderColor: '#e9ecef',
|
|
40
42
|
borderWidth: 1,
|
|
@@ -70,13 +72,13 @@ export function AreaChart({ data, xKey, series, height = 300, showLegend = true,
|
|
|
70
72
|
},
|
|
71
73
|
yAxis: series.some(s => s.secondaryAxis)
|
|
72
74
|
? [
|
|
73
|
-
{ type: 'value', axisLine: { show: false }, axisLabel: { color: '#6c757d', fontSize: 11 }, splitLine: { show: showGrid, lineStyle: { color: '#f0f0f0', type: 'dashed' } } },
|
|
74
|
-
{ type: 'value', axisLine: { show: false }, axisLabel: { color: '#6c757d', fontSize: 11 }, splitLine: { show: false } },
|
|
75
|
+
{ type: 'value', axisLine: { show: false }, axisLabel: { color: '#6c757d', fontSize: 11, formatter: (v) => (axisFormatter ?? formatCompactNumber)(v) }, splitLine: { show: showGrid, lineStyle: { color: '#f0f0f0', type: 'dashed' } } },
|
|
76
|
+
{ type: 'value', axisLine: { show: false }, axisLabel: { color: '#6c757d', fontSize: 11, formatter: (v) => (axisFormatter ?? formatCompactNumber)(v) }, splitLine: { show: false } },
|
|
75
77
|
]
|
|
76
78
|
: {
|
|
77
79
|
type: 'value',
|
|
78
80
|
axisLine: { show: false },
|
|
79
|
-
axisLabel: { color: '#6c757d', fontSize: 11 },
|
|
81
|
+
axisLabel: { color: '#6c757d', fontSize: 11, formatter: (v) => (axisFormatter ?? formatCompactNumber)(v) },
|
|
80
82
|
splitLine: { show: showGrid, lineStyle: { color: '#f0f0f0', type: 'dashed' } }
|
|
81
83
|
},
|
|
82
84
|
series: series.map((s, index) => {
|
|
@@ -19,9 +19,13 @@ export interface BarChartProps {
|
|
|
19
19
|
* Renders a KPI-vs-target bullet (track + actual fill + target marker + achievement %).
|
|
20
20
|
*/
|
|
21
21
|
targetKey?: string;
|
|
22
|
+
/** How a value reads in the tooltip. Default: the full number, Indian grouping (20,00,000). */
|
|
23
|
+
valueFormatter?: (value: number) => string;
|
|
24
|
+
/** How a value reads on the value axis. Default: compact (12,500 · 20 L · 1.4 Cr). */
|
|
25
|
+
axisFormatter?: (value: number) => string;
|
|
22
26
|
className?: string;
|
|
23
27
|
}
|
|
24
28
|
/**
|
|
25
29
|
* BarChart - ECharts Bar Chart component
|
|
26
30
|
*/
|
|
27
|
-
export declare function BarChart({ data, xKey, series, height, showLegend, showGrid, horizontal, targetKey, className }: BarChartProps): import("react").JSX.Element;
|
|
31
|
+
export declare function BarChart({ data, xKey, series, height, showLegend, showGrid, horizontal, targetKey, valueFormatter, axisFormatter, className }: BarChartProps): import("react").JSX.Element;
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import ReactECharts from 'echarts-for-react';
|
|
4
4
|
import { cn } from '../../../lib/utils.js';
|
|
5
|
+
import { formatCompactNumber, formatNumber } from '../../../lib/number-format.js';
|
|
5
6
|
/**
|
|
6
7
|
* BarChart - ECharts Bar Chart component
|
|
7
8
|
*/
|
|
8
|
-
export function BarChart({ data, xKey, series, height = 300, showLegend = true, showGrid = true, horizontal = false, targetKey, className }) {
|
|
9
|
+
export function BarChart({ data, xKey, series, height = 300, showLegend = true, showGrid = true, horizontal = false, targetKey, valueFormatter, axisFormatter, className }) {
|
|
9
10
|
// Bullet mode: horizontal KPI-vs-target rows (CSS-rendered, not ECharts).
|
|
10
11
|
if (horizontal && targetKey) {
|
|
11
12
|
const actualKey = series[0]?.key;
|
|
@@ -25,13 +26,14 @@ export function BarChart({ data, xKey, series, height = 300, showLegend = true,
|
|
|
25
26
|
: met
|
|
26
27
|
? 'linear-gradient(90deg, #34d399 0%, #10b981 100%)'
|
|
27
28
|
: 'linear-gradient(90deg, #fbbf24 0%, #f59e0b 100%)';
|
|
28
|
-
return (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsxs("div", { className: "flex items-baseline justify-between gap-2", children: [_jsx("span", { className: "text-xs font-medium text-[rgb(var(--fg-default))] truncate", children: name }), _jsxs("span", { className: "text-xs font-semibold tabular-nums shrink-0", style: { color: barColor }, children: [achievement.toFixed(0), "%"] })] }), _jsxs("div", { className: "relative h-2.5 rounded-full bg-[rgb(var(--bg-subtle))] overflow-visible", children: [_jsx("div", { className: "absolute inset-y-0 left-0 rounded-full transition-all duration-300", style: { width: `${actualPct}%`, background: barGradient } }), _jsx("div", { className: "absolute top-1/2 -translate-y-1/2 -translate-x-1/2 w-1 h-4 rounded-full ring-2 ring-[rgb(var(--bg-surface))]", style: { left: `${targetPct}%`, backgroundColor: '#475569' }, title: `Target: ${target
|
|
29
|
+
return (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsxs("div", { className: "flex items-baseline justify-between gap-2", children: [_jsx("span", { className: "text-xs font-medium text-[rgb(var(--fg-default))] truncate", children: name }), _jsxs("span", { className: "text-xs font-semibold tabular-nums shrink-0", style: { color: barColor }, children: [achievement.toFixed(0), "%"] })] }), _jsxs("div", { className: "relative h-2.5 rounded-full bg-[rgb(var(--bg-subtle))] overflow-visible", children: [_jsx("div", { className: "absolute inset-y-0 left-0 rounded-full transition-all duration-300", style: { width: `${actualPct}%`, background: barGradient } }), _jsx("div", { className: "absolute top-1/2 -translate-y-1/2 -translate-x-1/2 w-1 h-4 rounded-full ring-2 ring-[rgb(var(--bg-surface))]", style: { left: `${targetPct}%`, backgroundColor: '#475569' }, title: `Target: ${formatNumber(target)}` })] }), _jsxs("div", { className: "flex items-baseline justify-between gap-2 text-[0.65rem] text-[rgb(var(--fg-muted))] tabular-nums", children: [_jsx("span", { children: formatNumber(actual) }), _jsx("span", { children: `Target ${formatNumber(target)}` })] })] }, `${name}-${idx}`));
|
|
29
30
|
}) }));
|
|
30
31
|
}
|
|
31
32
|
const categoryData = data.map(item => item[xKey]);
|
|
32
33
|
const option = {
|
|
33
34
|
tooltip: {
|
|
34
35
|
trigger: 'item',
|
|
36
|
+
valueFormatter: (v) => (typeof v === 'number' ? (valueFormatter ?? formatNumber)(v) : String(v ?? '')),
|
|
35
37
|
backgroundColor: 'rgba(255, 255, 255, 0.98)',
|
|
36
38
|
borderColor: '#e9ecef',
|
|
37
39
|
borderWidth: 1,
|
|
@@ -64,7 +66,7 @@ export function BarChart({ data, xKey, series, height = 300, showLegend = true,
|
|
|
64
66
|
type: horizontal ? 'value' : 'category',
|
|
65
67
|
data: horizontal ? undefined : categoryData,
|
|
66
68
|
axisLine: { lineStyle: { color: '#dee2e6' } },
|
|
67
|
-
axisLabel: { color: '#6c757d', fontSize: 11 },
|
|
69
|
+
axisLabel: horizontal ? { color: '#6c757d', fontSize: 11, hideOverlap: true, alignMaxLabel: 'right', formatter: (v) => (axisFormatter ?? formatCompactNumber)(v) } : { color: '#6c757d', fontSize: 11 },
|
|
68
70
|
axisTick: { show: false },
|
|
69
71
|
splitLine: horizontal && showGrid ? {
|
|
70
72
|
lineStyle: { color: '#f0f0f0', type: 'dashed' }
|
|
@@ -74,7 +76,7 @@ export function BarChart({ data, xKey, series, height = 300, showLegend = true,
|
|
|
74
76
|
type: horizontal ? 'category' : 'value',
|
|
75
77
|
data: horizontal ? categoryData : undefined,
|
|
76
78
|
axisLine: { show: false },
|
|
77
|
-
axisLabel: { color: '#6c757d', fontSize: 11 },
|
|
79
|
+
axisLabel: horizontal ? { color: '#6c757d', fontSize: 11 } : { color: '#6c757d', fontSize: 11, formatter: (v) => (axisFormatter ?? formatCompactNumber)(v) },
|
|
78
80
|
splitLine: !horizontal && showGrid ? {
|
|
79
81
|
lineStyle: { color: '#f0f0f0', type: 'dashed' }
|
|
80
82
|
} : { show: false }
|
|
@@ -6,6 +6,7 @@ import * as echarts from 'echarts/core';
|
|
|
6
6
|
import { cn } from '../../../lib/utils.js';
|
|
7
7
|
import { INDIA_GEOJSON } from './india-map.js';
|
|
8
8
|
import { getChartTheme } from './theme.js';
|
|
9
|
+
import { formatNumber } from '../../../lib/number-format.js';
|
|
9
10
|
/**
|
|
10
11
|
* FlowMap - Geographic flow/route visualization
|
|
11
12
|
*
|
|
@@ -161,7 +162,7 @@ export function FlowMap({ markers = [], lines = [], height = 400, markerColor =
|
|
|
161
162
|
}
|
|
162
163
|
if (params.seriesType === 'effectScatter' || params.seriesType === 'scatter') {
|
|
163
164
|
const value = params.value?.[2];
|
|
164
|
-
return `<strong>${params.name}</strong>${value ? `<br/>Value: ${value
|
|
165
|
+
return `<strong>${params.name}</strong>${value ? `<br/>Value: ${formatNumber(value)}` : ''}`;
|
|
165
166
|
}
|
|
166
167
|
return `<strong>${params.name}</strong>`;
|
|
167
168
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
import { cn } from '../../../lib/utils.js';
|
|
5
|
+
import { formatNumber } from '../../../lib/number-format.js';
|
|
5
6
|
const FUNNEL_COLORS = ['#5470c6', '#3ba272', '#fac858', '#ee6666', '#73c0de', '#9a60b4', '#fc8452', '#91cc75'];
|
|
6
7
|
/**
|
|
7
8
|
* FunnelChart — centered, tapering stage-bar funnel for conversion/process flows.
|
|
@@ -30,6 +31,6 @@ export function FunnelChart({ data, height = 300, sort = 'descending', className
|
|
|
30
31
|
background: `linear-gradient(90deg, ${color}, ${color}cc)`,
|
|
31
32
|
transform: isHover ? 'translateY(-1px)' : 'none',
|
|
32
33
|
boxShadow: isHover ? `0 4px 14px ${color}55` : `0 1px 2px rgba(0,0,0,0.06)`,
|
|
33
|
-
}, title: `${item.name}: ${item.value
|
|
34
|
+
}, title: `${item.name}: ${formatNumber(item.value)} (${pct.toFixed(1)}%)`, children: [_jsx("span", { className: "text-sm font-semibold text-white truncate", children: item.name }), _jsx("span", { className: "text-xs font-medium text-white/90 tabular-nums shrink-0", children: formatNumber(item.value) })] }) })] }, item.name));
|
|
34
35
|
}) }));
|
|
35
36
|
}
|
|
@@ -5,6 +5,7 @@ import ReactECharts from 'echarts-for-react';
|
|
|
5
5
|
import * as echarts from 'echarts/core';
|
|
6
6
|
import { cn } from '../../../lib/utils.js';
|
|
7
7
|
import { INDIA_GEOJSON } from './india-map.js';
|
|
8
|
+
import { formatNumber } from '../../../lib/number-format.js';
|
|
8
9
|
/**
|
|
9
10
|
* GeoMap - Geographic scatter/bubble map for India
|
|
10
11
|
* Shows location markers with optional size based on values
|
|
@@ -47,7 +48,7 @@ export function GeoMap({ markers = [], height = 400, markerColor = '#5470c6', ma
|
|
|
47
48
|
formatter: (params) => {
|
|
48
49
|
if (params.seriesType === 'effectScatter' || params.seriesType === 'scatter') {
|
|
49
50
|
const value = params.value?.[2];
|
|
50
|
-
return `<strong>${params.name}</strong>${value ? `<br/>Production: ${value
|
|
51
|
+
return `<strong>${params.name}</strong>${value ? `<br/>Production: ${formatNumber(value)} units` : ''}`;
|
|
51
52
|
}
|
|
52
53
|
return `<strong>${params.name}</strong>`;
|
|
53
54
|
}
|
|
@@ -25,9 +25,13 @@ export interface LineChartProps {
|
|
|
25
25
|
yMin?: number;
|
|
26
26
|
/** Y-axis maximum value */
|
|
27
27
|
yMax?: number;
|
|
28
|
+
/** How a value reads in the tooltip. Default: the full number, Indian grouping (20,00,000). */
|
|
29
|
+
valueFormatter?: (value: number) => string;
|
|
30
|
+
/** How a value reads on the value axis. Default: compact (12,500 · 20 L · 1.4 Cr). */
|
|
31
|
+
axisFormatter?: (value: number) => string;
|
|
28
32
|
className?: string;
|
|
29
33
|
}
|
|
30
34
|
/**
|
|
31
35
|
* LineChart - ECharts Line Chart component (no area fill)
|
|
32
36
|
*/
|
|
33
|
-
export declare function LineChart({ data, xKey, series, height, showLegend, showGrid, smooth, showLabels, yMin, yMax, className }: LineChartProps): import("react").JSX.Element;
|
|
37
|
+
export declare function LineChart({ data, xKey, series, height, showLegend, showGrid, smooth, showLabels, yMin, yMax, valueFormatter, axisFormatter, className }: LineChartProps): import("react").JSX.Element;
|
|
@@ -3,16 +3,18 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import ReactECharts from 'echarts-for-react';
|
|
4
4
|
import { cn } from '../../../lib/utils.js';
|
|
5
5
|
import { getChartTheme, getTooltipConfig, getLegendConfig, getAxisConfig, getGridConfig } from './theme.js';
|
|
6
|
+
import { formatCompactNumber, formatNumber } from '../../../lib/number-format.js';
|
|
6
7
|
/**
|
|
7
8
|
* LineChart - ECharts Line Chart component (no area fill)
|
|
8
9
|
*/
|
|
9
|
-
export function LineChart({ data, xKey, series, height = 300, showLegend = true, showGrid = true, smooth = false, showLabels = false, yMin, yMax, className }) {
|
|
10
|
+
export function LineChart({ data, xKey, series, height = 300, showLegend = true, showGrid = true, smooth = false, showLabels = false, yMin, yMax, valueFormatter, axisFormatter, className }) {
|
|
10
11
|
const theme = getChartTheme();
|
|
11
12
|
const axisConfig = getAxisConfig();
|
|
12
13
|
const option = {
|
|
13
14
|
tooltip: {
|
|
14
15
|
trigger: 'axis',
|
|
15
|
-
...getTooltipConfig()
|
|
16
|
+
...getTooltipConfig(),
|
|
17
|
+
valueFormatter: (v) => (typeof v === 'number' ? (valueFormatter ?? formatNumber)(v) : String(v ?? '')),
|
|
16
18
|
},
|
|
17
19
|
legend: showLegend ? getLegendConfig('top') : undefined,
|
|
18
20
|
grid: getGridConfig(showLegend),
|
|
@@ -27,7 +29,7 @@ export function LineChart({ data, xKey, series, height = 300, showLegend = true,
|
|
|
27
29
|
min: yMin,
|
|
28
30
|
max: yMax,
|
|
29
31
|
axisLine: { show: false },
|
|
30
|
-
axisLabel: axisConfig.axisLabel,
|
|
32
|
+
axisLabel: { ...axisConfig.axisLabel, formatter: (v) => (axisFormatter ?? formatCompactNumber)(v) },
|
|
31
33
|
splitLine: {
|
|
32
34
|
show: showGrid,
|
|
33
35
|
...axisConfig.splitLine
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import ReactECharts from 'echarts-for-react';
|
|
4
4
|
import { cn } from '../../../lib/utils.js';
|
|
5
|
+
import { formatNumber } from '../../../lib/number-format.js';
|
|
5
6
|
/**
|
|
6
7
|
* ParetoChart - Combined bar and line chart showing values and cumulative percentage
|
|
7
8
|
* Used for identifying the "vital few" from the "trivial many" (80/20 rule)
|
|
@@ -28,7 +29,7 @@ export function ParetoChart({ data, height = 300, showLegend = true, barColor =
|
|
|
28
29
|
const bar = params.find((p) => p.seriesType === 'bar');
|
|
29
30
|
const line = params.find((p) => p.seriesType === 'line');
|
|
30
31
|
return `${bar?.name}<br/>
|
|
31
|
-
Value: ${bar?.value
|
|
32
|
+
Value: ${formatNumber(bar?.value)}<br/>
|
|
32
33
|
Cumulative: ${line?.value}%`;
|
|
33
34
|
}
|
|
34
35
|
},
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import ReactECharts from 'echarts-for-react';
|
|
4
4
|
import { cn } from '../../../lib/utils.js';
|
|
5
|
+
import { formatNumber } from '../../../lib/number-format.js';
|
|
5
6
|
/**
|
|
6
7
|
* SankeyChart - Flow/relationship visualization
|
|
7
8
|
* Great for: Material flow, process flow, cost flow, conversion funnels
|
|
@@ -35,9 +36,9 @@ export function SankeyChart({ nodes, links, height = 300, orient = 'horizontal',
|
|
|
35
36
|
textStyle: { color: '#344767', fontSize: 12 },
|
|
36
37
|
formatter: (params) => {
|
|
37
38
|
if (params.dataType === 'edge') {
|
|
38
|
-
return `<strong>${params.data.source}</strong> → <strong>${params.data.target}</strong><br/>Value: ${params.data.value
|
|
39
|
+
return `<strong>${params.data.source}</strong> → <strong>${params.data.target}</strong><br/>Value: ${formatNumber(params.data.value)}`;
|
|
39
40
|
}
|
|
40
|
-
return `<strong>${params.name}</strong><br/>Value: ${params.value
|
|
41
|
+
return `<strong>${params.name}</strong><br/>Value: ${formatNumber(params.value) || '-'}`;
|
|
41
42
|
}
|
|
42
43
|
},
|
|
43
44
|
series: [{
|
|
@@ -73,7 +74,7 @@ export function SankeyChart({ nodes, links, height = 300, orient = 'horizontal',
|
|
|
73
74
|
color: '#344767',
|
|
74
75
|
fontSize: 11,
|
|
75
76
|
fontWeight: 500,
|
|
76
|
-
formatter: (p) => `${p.name} (${p.value
|
|
77
|
+
formatter: (p) => `${p.name} (${formatNumber(p.value) || '-'})`
|
|
77
78
|
},
|
|
78
79
|
data: nodesWithColors,
|
|
79
80
|
links: links.map(link => ({
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import ReactECharts from 'echarts-for-react';
|
|
4
4
|
import { cn } from '../../../lib/utils.js';
|
|
5
|
+
import { formatNumber } from '../../../lib/number-format.js';
|
|
5
6
|
// Convert any color (hex, rgb(), rgba(), space-separated rgb()) to rgba() with given alpha (0–1).
|
|
6
7
|
// Hex+alpha-suffix concat (e.g. color + '40') breaks for non-hex inputs and crashes canvas gradients.
|
|
7
8
|
function withAlpha(color, alpha) {
|
|
@@ -97,7 +98,7 @@ export function Sparkline({ data, type = 'line', height = 40, width = '100%', co
|
|
|
97
98
|
formatter: (params) => {
|
|
98
99
|
const p = Array.isArray(params) ? params[0] : params;
|
|
99
100
|
const cat = hasLabels && p.axisValue ? `${p.axisValue}<br/>` : '';
|
|
100
|
-
return `${cat}<strong>${valuePrefix}${Number(p.value)
|
|
101
|
+
return `${cat}<strong>${valuePrefix}${formatNumber(Number(p.value))}${valueSuffix}</strong>`;
|
|
101
102
|
}
|
|
102
103
|
} : undefined,
|
|
103
104
|
grid: {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import ReactECharts from 'echarts-for-react';
|
|
4
4
|
import { cn } from '../../../lib/utils.js';
|
|
5
|
+
import { formatNumber } from '../../../lib/number-format.js';
|
|
5
6
|
/**
|
|
6
7
|
* TreemapChart - Hierarchical data visualization
|
|
7
8
|
* Great for: Cost breakdown, category distribution, budget allocation
|
|
@@ -22,7 +23,7 @@ export function TreemapChart({ data, height = 300, showBreadcrumb = false, color
|
|
|
22
23
|
.map((node) => node.name)
|
|
23
24
|
.filter((name) => name)
|
|
24
25
|
.join(' → ');
|
|
25
|
-
return `<strong>${path || params.name}</strong><br/>Value: ${params.value
|
|
26
|
+
return `<strong>${path || params.name}</strong><br/>Value: ${formatNumber(params.value) || '-'}`;
|
|
26
27
|
}
|
|
27
28
|
},
|
|
28
29
|
series: [{
|
|
@@ -36,3 +36,4 @@ export { FlowMap } from './FlowMap.js';
|
|
|
36
36
|
export type { FlowMapProps, FlowMapLine, FlowMapMarker } from './FlowMap.js';
|
|
37
37
|
export { ChartCard } from './DashboardCard.js';
|
|
38
38
|
export type { ChartCardProps } from './DashboardCard.js';
|
|
39
|
+
export { formatNumber, formatCompactNumber, setNumberLocale, getNumberLocale } from '../../../lib/number-format.js';
|
|
@@ -21,3 +21,5 @@ export { GanttChart } from './Gantt.js';
|
|
|
21
21
|
export { FlowMap } from './FlowMap.js';
|
|
22
22
|
// Chart Wrapper
|
|
23
23
|
export { ChartCard } from './DashboardCard.js';
|
|
24
|
+
// Number formatting: Indian grouping by default; setNumberLocale() switches it for a whole app
|
|
25
|
+
export { formatNumber, formatCompactNumber, setNumberLocale, getNumberLocale } from '../../../lib/number-format.js';
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { cn } from '../../../lib/utils.js';
|
|
4
4
|
import { KpiCardSkeleton } from './kpi-shell.js';
|
|
5
|
+
import { formatNumber } from '../../../lib/number-format.js';
|
|
5
6
|
/**
|
|
6
7
|
* KpiBreakdown - KPI card with list breakdown
|
|
7
8
|
*
|
|
@@ -29,11 +30,11 @@ export function KpiBreakdown({ title, total, items, showPercentage = false, load
|
|
|
29
30
|
};
|
|
30
31
|
const totalValue = typeof total === 'number' ? total :
|
|
31
32
|
items.reduce((sum, item) => sum + (typeof item.value === 'number' ? item.value : 0), 0);
|
|
32
|
-
return (_jsxs("div", { className: cn('bg-[rgb(var(--bg-surface))] rounded-xl border border-[rgb(var(--bd-default))] shadow-sm', compact ? 'p-4' : 'p-5', className), children: [_jsxs("div", { className: cn('flex items-center justify-between', compact ? 'mb-2' : 'mb-3'), children: [_jsx("p", { className: "text-sm text-[rgb(var(--fg-muted))]", children: title }), total !== undefined && (_jsx("p", { className: "text-lg font-bold text-[rgb(var(--fg-default))]", children: typeof total === 'number' ? total
|
|
33
|
+
return (_jsxs("div", { className: cn('bg-[rgb(var(--bg-surface))] rounded-xl border border-[rgb(var(--bd-default))] shadow-sm', compact ? 'p-4' : 'p-5', className), children: [_jsxs("div", { className: cn('flex items-center justify-between', compact ? 'mb-2' : 'mb-3'), children: [_jsx("p", { className: "text-sm text-[rgb(var(--fg-muted))]", children: title }), total !== undefined && (_jsx("p", { className: "text-lg font-bold text-[rgb(var(--fg-default))]", children: typeof total === 'number' ? formatNumber(total) : total }))] }), _jsx("div", { className: compact ? 'space-y-1' : 'space-y-2', children: items.map((item, index) => {
|
|
33
34
|
const colorClass = getColorClass(item.color);
|
|
34
35
|
const isCustomColor = item.color?.startsWith('#') || item.color?.startsWith('rgb');
|
|
35
36
|
const numValue = typeof item.value === 'number' ? item.value : 0;
|
|
36
37
|
const percentage = totalValue > 0 ? Math.round((numValue / totalValue) * 100) : 0;
|
|
37
|
-
return (_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: cn('w-2 h-2 rounded-full', colorClass), style: isCustomColor ? { backgroundColor: item.color } : undefined }), _jsx("span", { className: "text-sm text-[rgb(var(--fg-default))]", children: item.label })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-sm font-medium", children: typeof item.value === 'number' ? item.value
|
|
38
|
+
return (_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: cn('w-2 h-2 rounded-full', colorClass), style: isCustomColor ? { backgroundColor: item.color } : undefined }), _jsx("span", { className: "text-sm text-[rgb(var(--fg-default))]", children: item.label })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-sm font-medium", children: typeof item.value === 'number' ? formatNumber(item.value) : item.value }), showPercentage && (_jsxs("span", { className: "text-xs text-[rgb(var(--fg-muted))]", children: ["(", percentage, "%)"] }))] })] }, index));
|
|
38
39
|
}) })] }));
|
|
39
40
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { cn } from '../../../lib/utils.js';
|
|
4
4
|
import { KpiCardSkeleton } from './kpi-shell.js';
|
|
5
|
+
import { formatNumber } from '../../../lib/number-format.js';
|
|
5
6
|
/**
|
|
6
7
|
* KpiComparison - KPI card with comparison progress bars
|
|
7
8
|
*
|
|
@@ -34,5 +35,5 @@ export function KpiComparison({ title, value, target, status, bars, loading, cla
|
|
|
34
35
|
return 'bg-[rgb(var(--color-error))]/10 text-[rgb(var(--color-error))]';
|
|
35
36
|
}
|
|
36
37
|
};
|
|
37
|
-
return (_jsxs("div", { className: cn('bg-[rgb(var(--bg-surface))] rounded-xl p-5 border border-[rgb(var(--bd-default))] shadow-sm', className), children: [_jsxs("div", { className: "flex items-center justify-between mb-1", children: [_jsx("p", { className: "text-sm text-[rgb(var(--fg-muted))]", children: title }), status && (_jsx("span", { className: cn('text-xs px-2 py-0.5 rounded-full', getStatusStyles(status.type)), children: status.label }))] }), _jsxs("div", { className: "flex items-baseline gap-2", children: [_jsx("p", { className: "text-3xl font-bold text-[rgb(var(--fg-default))]", children: value }), target && (_jsxs("span", { className: "text-sm text-[rgb(var(--fg-muted))]", children: ["/ ", target, " target"] }))] }), _jsx("div", { className: "mt-4 space-y-2", children: bars.map((bar, index) => (_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { className: "text-xs text-[rgb(var(--fg-muted))] w-16", children: bar.label }), _jsx("div", { className: "flex-1 h-3 bg-[rgb(var(--bg-subtle))] rounded", children: _jsx("div", { className: cn('h-full rounded', getBarColor(bar.color)), style: { width: `${Math.min(bar.percentage, 100)}%` } }) }), _jsx("span", { className: "text-xs font-medium w-12 text-right", children: typeof bar.value === 'number' ? bar.value
|
|
38
|
+
return (_jsxs("div", { className: cn('bg-[rgb(var(--bg-surface))] rounded-xl p-5 border border-[rgb(var(--bd-default))] shadow-sm', className), children: [_jsxs("div", { className: "flex items-center justify-between mb-1", children: [_jsx("p", { className: "text-sm text-[rgb(var(--fg-muted))]", children: title }), status && (_jsx("span", { className: cn('text-xs px-2 py-0.5 rounded-full', getStatusStyles(status.type)), children: status.label }))] }), _jsxs("div", { className: "flex items-baseline gap-2", children: [_jsx("p", { className: "text-3xl font-bold text-[rgb(var(--fg-default))]", children: value }), target && (_jsxs("span", { className: "text-sm text-[rgb(var(--fg-muted))]", children: ["/ ", target, " target"] }))] }), _jsx("div", { className: "mt-4 space-y-2", children: bars.map((bar, index) => (_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { className: "text-xs text-[rgb(var(--fg-muted))] w-16", children: bar.label }), _jsx("div", { className: "flex-1 h-3 bg-[rgb(var(--bg-subtle))] rounded", children: _jsx("div", { className: cn('h-full rounded', getBarColor(bar.color)), style: { width: `${Math.min(bar.percentage, 100)}%` } }) }), _jsx("span", { className: "text-xs font-medium w-12 text-right", children: typeof bar.value === 'number' ? formatNumber(bar.value) : bar.value })] }, index))) })] }));
|
|
38
39
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { cn } from '../../../lib/utils.js';
|
|
4
4
|
import { KpiCardSkeleton } from './kpi-shell.js';
|
|
5
|
+
import { formatNumber } from '../../../lib/number-format.js';
|
|
5
6
|
/**
|
|
6
7
|
* KpiQuickStats - KPI card with grid of icon stats
|
|
7
8
|
*
|
|
@@ -33,6 +34,6 @@ export function KpiQuickStats({ title, stats, columns = 2, loading, className })
|
|
|
33
34
|
};
|
|
34
35
|
return (_jsxs("div", { className: cn('bg-[rgb(var(--bg-surface))] rounded-xl p-5 border border-[rgb(var(--bd-default))] shadow-sm', className), children: [_jsx("p", { className: "text-sm text-[rgb(var(--fg-muted))] mb-3", children: title }), _jsx("div", { className: cn('grid gap-x-3 gap-y-2', gridClasses[columns]), children: stats.map((stat, index) => {
|
|
35
36
|
const Icon = stat.icon;
|
|
36
|
-
return (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Icon, { className: cn('w-4 h-4', getIconColorClass(stat.iconColor)) }), _jsxs("div", { children: [_jsx("p", { className: "text-lg font-bold text-[rgb(var(--fg-default))]", children: typeof stat.value === 'number' ? stat.value
|
|
37
|
+
return (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Icon, { className: cn('w-4 h-4', getIconColorClass(stat.iconColor)) }), _jsxs("div", { children: [_jsx("p", { className: "text-lg font-bold text-[rgb(var(--fg-default))]", children: typeof stat.value === 'number' ? formatNumber(stat.value) : stat.value }), _jsx("p", { className: "text-xs text-[rgb(var(--fg-muted))]", children: stat.label })] })] }, index));
|
|
37
38
|
}) })] }));
|
|
38
39
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { TrendingUp, TrendingDown, Info } from 'lucide-react';
|
|
4
4
|
import { cn } from '../../../lib/utils.js';
|
|
5
|
+
import { formatNumber } from '../../../lib/number-format.js';
|
|
5
6
|
/**
|
|
6
7
|
* KpiRating - KPI card with star rating display
|
|
7
8
|
*
|
|
@@ -27,9 +28,9 @@ export function KpiRating({ title, score, maxScore = 5, reviewCount, status, sta
|
|
|
27
28
|
return 'bg-[rgb(var(--color-warning))]';
|
|
28
29
|
return 'bg-[rgb(var(--color-error))]';
|
|
29
30
|
};
|
|
30
|
-
return (_jsxs("div", { className: cn('bg-[rgb(var(--bg-surface))] rounded-xl p-5 border border-[rgb(var(--bd-default))] shadow-sm relative group', className), children: [_jsxs("div", { className: "flex items-center justify-between mb-3", children: [_jsx("p", { className: "text-sm text-[rgb(var(--fg-muted))]", children: title }), tooltip && (_jsxs("div", { className: "relative", children: [_jsx("div", { className: "w-5 h-5 rounded-full bg-[rgb(var(--bg-subtle))] flex items-center justify-center cursor-help hover:bg-[rgb(var(--color-primary))]/10 transition-colors peer", children: _jsx(Info, { className: "w-3 h-3 text-[rgb(var(--fg-muted))]" }) }), _jsxs("div", { className: "absolute right-0 top-7 w-72 p-3 bg-[rgb(var(--bg-surface))] border border-[rgb(var(--bd-default))] rounded-lg shadow-lg opacity-0 invisible peer-hover:opacity-100 peer-hover:visible transition-all z-10", children: [tooltip.formula && (_jsxs(_Fragment, { children: [_jsx("p", { className: "text-xs font-medium text-[rgb(var(--fg-default))] mb-2", children: "Formula:" }), _jsx("div", { className: "bg-[rgb(var(--bg-subtle))] rounded p-2 mb-3", children: _jsx("p", { className: "text-[10px] font-mono text-[rgb(var(--fg-muted))]", children: tooltip.formula }) })] })), tooltip.description && (_jsx("p", { className: "text-[10px] text-[rgb(var(--fg-muted))] mb-3", children: tooltip.description })), tooltip.breakdown && tooltip.breakdown.length > 0 && (_jsxs("div", { className: "border-t border-[rgb(var(--bd-default))] pt-3", children: [_jsx("p", { className: "text-xs font-medium text-[rgb(var(--fg-default))] mb-2", children: "Rating Breakdown:" }), _jsx("div", { className: "space-y-1.5", children: tooltip.breakdown.map((item) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-[10px] text-[rgb(var(--fg-muted))] w-3", children: item.stars }), _jsx("svg", { className: "w-3 h-3 text-yellow-400", fill: "currentColor", viewBox: "0 0 20 20", children: _jsx("path", { d: "M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" }) }), _jsx("div", { className: "flex-1 h-1.5 bg-[rgb(var(--bg-subtle))] rounded-full overflow-hidden", children: _jsx("div", { className: cn('h-full rounded-full', getBreakdownBarColor(item.percentage)), style: { width: `${item.percentage}%` } }) }), _jsx("span", { className: "text-[10px] text-[rgb(var(--fg-muted))] w-10 text-right", children: item.count
|
|
31
|
+
return (_jsxs("div", { className: cn('bg-[rgb(var(--bg-surface))] rounded-xl p-5 border border-[rgb(var(--bd-default))] shadow-sm relative group', className), children: [_jsxs("div", { className: "flex items-center justify-between mb-3", children: [_jsx("p", { className: "text-sm text-[rgb(var(--fg-muted))]", children: title }), tooltip && (_jsxs("div", { className: "relative", children: [_jsx("div", { className: "w-5 h-5 rounded-full bg-[rgb(var(--bg-subtle))] flex items-center justify-center cursor-help hover:bg-[rgb(var(--color-primary))]/10 transition-colors peer", children: _jsx(Info, { className: "w-3 h-3 text-[rgb(var(--fg-muted))]" }) }), _jsxs("div", { className: "absolute right-0 top-7 w-72 p-3 bg-[rgb(var(--bg-surface))] border border-[rgb(var(--bd-default))] rounded-lg shadow-lg opacity-0 invisible peer-hover:opacity-100 peer-hover:visible transition-all z-10", children: [tooltip.formula && (_jsxs(_Fragment, { children: [_jsx("p", { className: "text-xs font-medium text-[rgb(var(--fg-default))] mb-2", children: "Formula:" }), _jsx("div", { className: "bg-[rgb(var(--bg-subtle))] rounded p-2 mb-3", children: _jsx("p", { className: "text-[10px] font-mono text-[rgb(var(--fg-muted))]", children: tooltip.formula }) })] })), tooltip.description && (_jsx("p", { className: "text-[10px] text-[rgb(var(--fg-muted))] mb-3", children: tooltip.description })), tooltip.breakdown && tooltip.breakdown.length > 0 && (_jsxs("div", { className: "border-t border-[rgb(var(--bd-default))] pt-3", children: [_jsx("p", { className: "text-xs font-medium text-[rgb(var(--fg-default))] mb-2", children: "Rating Breakdown:" }), _jsx("div", { className: "space-y-1.5", children: tooltip.breakdown.map((item) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-[10px] text-[rgb(var(--fg-muted))] w-3", children: item.stars }), _jsx("svg", { className: "w-3 h-3 text-yellow-400", fill: "currentColor", viewBox: "0 0 20 20", children: _jsx("path", { d: "M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" }) }), _jsx("div", { className: "flex-1 h-1.5 bg-[rgb(var(--bg-subtle))] rounded-full overflow-hidden", children: _jsx("div", { className: cn('h-full rounded-full', getBreakdownBarColor(item.percentage)), style: { width: `${item.percentage}%` } }) }), _jsx("span", { className: "text-[10px] text-[rgb(var(--fg-muted))] w-10 text-right", children: formatNumber(item.count) }), _jsxs("span", { className: "text-[10px] text-[rgb(var(--fg-muted))] w-6 text-right", children: [item.percentage, "%"] })] }, item.stars))) })] }))] })] }))] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { children: _jsx("p", { className: "text-4xl font-bold text-[rgb(var(--fg-default))]", children: score }) }), _jsxs("div", { children: [_jsx("div", { className: "flex gap-0.5", children: Array.from({ length: maxScore }, (_, i) => {
|
|
31
32
|
const starIndex = i + 1;
|
|
32
33
|
const isFilled = starIndex <= filledStars || (starIndex === filledStars + 1 && hasPartialStar);
|
|
33
34
|
return (_jsx("svg", { className: cn('w-5 h-5 transition-transform hover:scale-110 cursor-pointer', isFilled ? 'text-yellow-400' : 'text-yellow-400/30'), fill: "currentColor", viewBox: "0 0 20 20", children: _jsx("path", { d: "M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" }) }, i));
|
|
34
|
-
}) }), reviewCount !== undefined && (_jsxs("p", { className: "text-xs text-[rgb(var(--fg-muted))] mt-1", children: [reviewCount
|
|
35
|
+
}) }), reviewCount !== undefined && (_jsxs("p", { className: "text-xs text-[rgb(var(--fg-muted))] mt-1", children: [formatNumber(reviewCount), " reviews"] }))] }), status && (_jsx("div", { className: "ml-auto text-right", children: _jsx("span", { className: cn('text-xs px-2 py-0.5 rounded-full', getStatusStyles(statusType)), children: status }) }))] }), (change !== undefined || periodLabel) && (_jsxs("div", { className: "flex items-center justify-between pt-3 mt-3 border-t border-[rgb(var(--bd-default))]", children: [change !== undefined && (_jsxs("div", { className: "flex items-center gap-1", children: [change > 0 ? (_jsx(TrendingUp, { className: "w-3 h-3 text-[rgb(var(--color-success))]" })) : change < 0 ? (_jsx(TrendingDown, { className: "w-3 h-3 text-[rgb(var(--color-error))]" })) : null, _jsxs("span", { className: cn('text-xs', change > 0 ? 'text-[rgb(var(--color-success))]' : change < 0 ? 'text-[rgb(var(--color-error))]' : 'text-[rgb(var(--fg-muted))]'), children: [change > 0 ? '+' : '', change, " ", changeLabel] })] })), periodLabel && (_jsx("span", { className: "text-[10px] text-[rgb(var(--fg-muted))]", children: periodLabel }))] }))] }));
|
|
35
36
|
}
|
|
@@ -489,7 +489,7 @@ enableFilterRow = false, initialColumnFilters, }) {
|
|
|
489
489
|
}
|
|
490
490
|
return rowSelection;
|
|
491
491
|
}, [enableViewToggle, viewMode, selectedRowIds, filteredData, rowSelection, getRowId, data]);
|
|
492
|
-
// Helper function to format dates as "12 Oct
|
|
492
|
+
// Helper function to format dates as "12 Oct 2025"
|
|
493
493
|
const formatDateCell = (dateString) => {
|
|
494
494
|
if (!dateString)
|
|
495
495
|
return '-';
|
|
@@ -500,7 +500,7 @@ enableFilterRow = false, initialColumnFilters, }) {
|
|
|
500
500
|
const day = date.getDate();
|
|
501
501
|
const month = date.toLocaleDateString('en-US', { month: 'short' });
|
|
502
502
|
const year = date.getFullYear();
|
|
503
|
-
return `${day} ${month}
|
|
503
|
+
return `${day} ${month} ${year}`;
|
|
504
504
|
}
|
|
505
505
|
catch {
|
|
506
506
|
return dateString;
|
|
@@ -91,10 +91,10 @@ export function CellRenderer({ cell, cellIndex, row, enableBacchaSearch = false,
|
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
const rowId = row.original.id || row.id;
|
|
94
|
-
return (_jsx("td", { className: `
|
|
95
|
-
px-1 py-1 text-xs text-fg-default align-middle
|
|
96
|
-
${isBadgeColumn || isPlaceholderValue ? 'text-center' : isNumericContent ? 'text-right' : 'text-left'}
|
|
97
|
-
${isCellFrozen || isPinnedRight ? 'sticky z-[5]' : ''}
|
|
94
|
+
return (_jsx("td", { className: `
|
|
95
|
+
px-1 py-1 text-xs text-fg-default align-middle
|
|
96
|
+
${isBadgeColumn || isPlaceholderValue ? 'text-center' : isNumericContent ? 'text-right' : 'text-left'}
|
|
97
|
+
${isCellFrozen || isPinnedRight ? 'sticky z-[5]' : ''}
|
|
98
98
|
`, style: {
|
|
99
99
|
width: `${cell.column.getSize()}px`,
|
|
100
100
|
minWidth: `${cell.column.getSize()}px`,
|
|
@@ -11,13 +11,13 @@ export function SelectionCell({ checked, indeterminate = false, onChange, disabl
|
|
|
11
11
|
};
|
|
12
12
|
// Radio button mode (single selection) - always circular regardless of circularCheckboxes prop
|
|
13
13
|
if (mode === 'radio') {
|
|
14
|
-
return (_jsx("div", { onClick: handleClick, className: `
|
|
15
|
-
relative rounded-full border-2 cursor-pointer transition-all duration-150
|
|
16
|
-
flex items-center justify-center flex-shrink-0
|
|
17
|
-
${disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}
|
|
14
|
+
return (_jsx("div", { onClick: handleClick, className: `
|
|
15
|
+
relative rounded-full border-2 cursor-pointer transition-all duration-150
|
|
16
|
+
flex items-center justify-center flex-shrink-0
|
|
17
|
+
${disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}
|
|
18
18
|
${checked
|
|
19
19
|
? 'border-primary bg-white'
|
|
20
|
-
: 'border-gray-300 bg-white hover:border-gray-400'}
|
|
20
|
+
: 'border-gray-300 bg-white hover:border-gray-400'}
|
|
21
21
|
`, style: {
|
|
22
22
|
boxShadow: checked ? '0 0 0 2px rgba(59, 130, 246, 0.1)' : 'none',
|
|
23
23
|
width: '14px',
|
|
@@ -39,13 +39,13 @@ export function SelectionCell({ checked, indeterminate = false, onChange, disabl
|
|
|
39
39
|
: "border-[rgb(var(--bd-default))] bg-[rgb(var(--bg-surface))] hover:border-[rgb(var(--color-primary))]", disabled && "opacity-50 cursor-not-allowed"), children: [checked && !indeterminate && (_jsx(Check, { className: "w-3 h-3 text-white", strokeWidth: 3 })), indeterminate && (_jsx("div", { className: "w-2 h-0.5 bg-white rounded" }))] })] }));
|
|
40
40
|
}
|
|
41
41
|
// Circular checkbox (AG Grid style, multi-select with circular prop)
|
|
42
|
-
return (_jsxs("div", { onClick: handleClick, className: `
|
|
43
|
-
relative rounded-full border-2 cursor-pointer transition-all duration-150
|
|
44
|
-
flex items-center justify-center flex-shrink-0
|
|
45
|
-
${disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}
|
|
42
|
+
return (_jsxs("div", { onClick: handleClick, className: `
|
|
43
|
+
relative rounded-full border-2 cursor-pointer transition-all duration-150
|
|
44
|
+
flex items-center justify-center flex-shrink-0
|
|
45
|
+
${disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}
|
|
46
46
|
${checked || indeterminate
|
|
47
47
|
? 'border-primary bg-primary'
|
|
48
|
-
: 'border-gray-300 bg-white hover:border-gray-400'}
|
|
48
|
+
: 'border-gray-300 bg-white hover:border-gray-400'}
|
|
49
49
|
`, style: {
|
|
50
50
|
boxShadow: checked || indeterminate ? '0 0 0 2px rgba(59, 130, 246, 0.1)' : 'none',
|
|
51
51
|
width: '14px',
|
|
@@ -8,6 +8,7 @@ import { Input } from '../../ui/index.js';
|
|
|
8
8
|
import { Checkbox } from '../../ui/index.js';
|
|
9
9
|
import { Separator } from '../../ui/index.js';
|
|
10
10
|
import { Badge } from '../../ui/index.js';
|
|
11
|
+
import { formatNumber } from '../../../lib/number-format.js';
|
|
11
12
|
export function ColumnContextMenu({ column, table, data, isVisible, position, onClose, onApplyFilter, }) {
|
|
12
13
|
const [activeSubmenu, setActiveSubmenu] = useState(null);
|
|
13
14
|
const [quickFilterValue, setQuickFilterValue] = useState('');
|
|
@@ -316,7 +317,7 @@ export function ColumnContextMenu({ column, table, data, isVisible, position, on
|
|
|
316
317
|
}, className: "w-full flex items-center gap-2 px-2 py-1 text-left text-xs rounded hover:bg-[rgb(var(--bg-subtle))]", children: [_jsx(Eye, { className: "h-3.5 w-3.5 text-[rgb(var(--fg-muted))] flex-shrink-0" }), _jsx("span", { className: "truncate flex-1", children: col.label })] }, col.id))) })] }) }, item.id));
|
|
317
318
|
}
|
|
318
319
|
if (item.id === 'column-stats' && columnStats) {
|
|
319
|
-
return (_jsxs("div", { className: "p-2 space-y-1.5", children: [_jsx("div", { className: "text-sm font-medium text-[rgb(var(--fg-default))]", children: "Column Statistics" }), _jsxs("div", { className: "grid grid-cols-[auto_1fr] gap-x-4 gap-y-1 text-xs", children: [_jsx("span", { className: "text-[rgb(var(--fg-muted))]", children: "Total:" }), _jsx("span", { className: "font-medium text-right", children: columnStats.totalCount
|
|
320
|
+
return (_jsxs("div", { className: "p-2 space-y-1.5", children: [_jsx("div", { className: "text-sm font-medium text-[rgb(var(--fg-default))]", children: "Column Statistics" }), _jsxs("div", { className: "grid grid-cols-[auto_1fr] gap-x-4 gap-y-1 text-xs", children: [_jsx("span", { className: "text-[rgb(var(--fg-muted))]", children: "Total:" }), _jsx("span", { className: "font-medium text-right", children: formatNumber(columnStats.totalCount) }), _jsx("span", { className: "text-[rgb(var(--fg-muted))]", children: "Unique:" }), _jsx("span", { className: "font-medium text-right", children: formatNumber(columnStats.uniqueCount) }), _jsx("span", { className: "text-[rgb(var(--fg-muted))]", children: "Null:" }), _jsx("span", { className: "font-medium text-right", children: formatNumber(columnStats.nullCount) }), _jsx("span", { className: "text-[rgb(var(--fg-muted))]", children: "Type:" }), _jsx("div", { className: "text-right", children: _jsx(Badge, { variant: "outline", className: "text-xs h-4", children: columnStats.dataType }) }), columnStats.dataType === 'number' && (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-[rgb(var(--fg-muted))]", children: "Min:" }), _jsx("span", { className: "font-medium text-right", children: columnStats.min }), _jsx("span", { className: "text-[rgb(var(--fg-muted))]", children: "Max:" }), _jsx("span", { className: "font-medium text-right", children: columnStats.max }), _jsx("span", { className: "text-[rgb(var(--fg-muted))]", children: "Avg:" }), _jsx("span", { className: "font-medium text-right", children: columnStats.avg?.toFixed(2) }), _jsx("span", { className: "text-[rgb(var(--fg-muted))]", children: "Sum:" }), _jsx("span", { className: "font-medium text-right", children: formatNumber(columnStats.sum) })] }))] })] }, item.id));
|
|
320
321
|
}
|
|
321
322
|
}
|
|
322
323
|
const Icon = item.icon;
|
|
@@ -21,11 +21,11 @@ export function PaginationControls({ pageIndex, pageSize, totalRows, pageSizeOpt
|
|
|
21
21
|
return (_jsx("span", { className: "px-2 text-[rgb(var(--fg-muted))]", children: "..." }, `ellipsis-${index}`));
|
|
22
22
|
}
|
|
23
23
|
const isCurrentPage = page === pageIndex;
|
|
24
|
-
return (_jsx("button", { onClick: () => onPageChange(page), className: `
|
|
25
|
-
min-w-[32px] h-8 px-2 rounded text-sm font-medium transition-colors
|
|
24
|
+
return (_jsx("button", { onClick: () => onPageChange(page), className: `
|
|
25
|
+
min-w-[32px] h-8 px-2 rounded text-sm font-medium transition-colors
|
|
26
26
|
${isCurrentPage
|
|
27
27
|
? 'border border-[rgb(var(--color-primary))] text-[rgb(var(--color-primary))] bg-[rgb(var(--color-primary)/0.08)]'
|
|
28
|
-
: 'text-[rgb(var(--fg-default))] hover:bg-[rgb(var(--bd-default))]'}
|
|
28
|
+
: 'text-[rgb(var(--fg-default))] hover:bg-[rgb(var(--bd-default))]'}
|
|
29
29
|
`, children: page + 1 }, page));
|
|
30
30
|
}) }), _jsx("button", { onClick: () => onPageChange(pageIndex + 1), disabled: !hasNextPage, className: "p-1.5 rounded hover:bg-[rgb(var(--bd-default))] disabled:opacity-40 disabled:cursor-not-allowed transition-colors", "aria-label": "Next Page", children: _jsx(ChevronRight, { className: "h-4 w-4 text-[rgb(var(--fg-muted))]" }) }), _jsx("button", { onClick: () => onPageChange(totalPages - 1), disabled: !hasNextPage, className: "p-1.5 rounded hover:bg-[rgb(var(--bd-default))] disabled:opacity-40 disabled:cursor-not-allowed transition-colors", "aria-label": "Last Page", children: _jsx(ChevronsRight, { className: "h-4 w-4 text-[rgb(var(--fg-muted))]" }) })] }), !isMobile && (_jsxs("div", { className: "flex items-center gap-2 text-sm text-[rgb(var(--fg-default))] flex-shrink-0 order-2 sm:order-3", children: [_jsx("span", { className: "hidden sm:inline", children: "Show:" }), _jsx(Dropdown, { value: String(pageSize), onValueChange: (value) => onPageSizeChange(Number(value)), options: pageSizeOptions.map((size) => ({
|
|
31
31
|
value: String(size),
|
|
@@ -7,6 +7,7 @@ import { Badge } from '../../../../ui/index.js';
|
|
|
7
7
|
import { SelectionCheckbox } from '../../../cells/SelectionCell.js';
|
|
8
8
|
import { Skeleton } from '../../../../ui/feedback/skeleton.js';
|
|
9
9
|
import { useLanguage } from '../../../../../contexts/LanguageContext.js';
|
|
10
|
+
import { formatNumber } from '../../../../../lib/number-format.js';
|
|
10
11
|
const sizeConfig = {
|
|
11
12
|
compact: {
|
|
12
13
|
grid: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
|
|
@@ -128,7 +129,7 @@ export function CardView({ data, columns, onRowClick, selectedRows, onRowSelect,
|
|
|
128
129
|
return value ? (_jsx(Badge, { variant: "default", className: "h-4.5 text-[0.6rem] px-1.5 py-0 bg-[rgb(var(--color-success))]/10 text-[rgb(var(--color-success))] border-0 font-medium", children: t('Yes') })) : (_jsx(Badge, { variant: "secondary", className: "h-4.5 text-[0.6rem] px-1.5 py-0 bg-[rgb(var(--bg-subtle))] text-[rgb(var(--fg-muted))] border-0 font-medium", children: t('No') }));
|
|
129
130
|
}
|
|
130
131
|
if (typeof value === 'number')
|
|
131
|
-
return value
|
|
132
|
+
return formatNumber(value);
|
|
132
133
|
return value?.toString() || '—';
|
|
133
134
|
}, [t]);
|
|
134
135
|
const getFieldLabel = useCallback((column) => {
|
|
@@ -151,14 +152,14 @@ export function CardView({ data, columns, onRowClick, selectedRows, onRowSelect,
|
|
|
151
152
|
return (_jsxs("div", { ref: containerRef, className: "outline-none", tabIndex: 0, onKeyDown: handleKeyDown, children: [_jsx("div", { className: cfg.pad, children: _jsx("div", { className: `grid ${cfg.grid} ${cfg.gap}`, children: _jsx(AnimatePresence, { initial: false, children: data.map((item, index) => {
|
|
152
153
|
const isFocused = focusedIndex === index;
|
|
153
154
|
const itemSelected = isSelected(item);
|
|
154
|
-
return (_jsx(motion.div, { ref: (el) => { cardRefs.current[index] = el; }, initial: { opacity: 0, y: 6 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: -6 }, transition: { duration: 0.12, delay: Math.min(index * 0.015, 0.15) }, className: "group/card outline-none", tabIndex: -1, onClick: () => setFocusedIndex(index), onDoubleClick: () => handleCardClick(item), children: _jsxs("div", { className: `
|
|
155
|
-
relative rounded-lg border bg-[rgb(var(--bg-surface))] overflow-hidden h-full
|
|
156
|
-
transition-all duration-150
|
|
155
|
+
return (_jsx(motion.div, { ref: (el) => { cardRefs.current[index] = el; }, initial: { opacity: 0, y: 6 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: -6 }, transition: { duration: 0.12, delay: Math.min(index * 0.015, 0.15) }, className: "group/card outline-none", tabIndex: -1, onClick: () => setFocusedIndex(index), onDoubleClick: () => handleCardClick(item), children: _jsxs("div", { className: `
|
|
156
|
+
relative rounded-lg border bg-[rgb(var(--bg-surface))] overflow-hidden h-full
|
|
157
|
+
transition-all duration-150
|
|
157
158
|
${itemSelected
|
|
158
159
|
? 'border-[rgb(var(--color-primary))] shadow-sm'
|
|
159
|
-
: 'border-[rgb(var(--bd-default))] hover:border-[rgb(var(--color-primary))]/40 hover:shadow-sm'}
|
|
160
|
-
${isFocused ? 'ring-2 ring-[rgb(var(--color-primary))]/30 ring-offset-1' : ''}
|
|
161
|
-
${getCardClassName?.(item) ?? ''}
|
|
160
|
+
: 'border-[rgb(var(--bd-default))] hover:border-[rgb(var(--color-primary))]/40 hover:shadow-sm'}
|
|
161
|
+
${isFocused ? 'ring-2 ring-[rgb(var(--color-primary))]/30 ring-offset-1' : ''}
|
|
162
|
+
${getCardClassName?.(item) ?? ''}
|
|
162
163
|
`, children: [_jsx("div", { className: `absolute left-0 top-0 bottom-0 w-[2px] transition-colors duration-150 ${itemSelected ? 'bg-[rgb(var(--color-primary))]' : 'bg-transparent group-hover/card:bg-[rgb(var(--color-primary))]/30'}` }), _jsxs("div", { className: "pl-3 pr-2.5 py-2.5", children: [_jsxs("div", { className: "flex items-center gap-2", children: [onRowSelect && (_jsx("div", { className: "flex-shrink-0", onClick: (e) => e.stopPropagation(), children: _jsx(SelectionCheckbox, { checked: itemSelected, onChange: (checked) => handleCheckboxChange(item, checked), circular: circularCheckboxes, mode: "checkbox" }) })), _jsxs("div", { className: "flex-1 min-w-0 flex items-baseline gap-1.5", children: [_jsx("p", { className: `${cfg.titleText} font-semibold text-[rgb(var(--fg-default))] truncate leading-tight`, children: displayColumns.length > 0 && getDisplayValue(item, displayColumns[0].accessorKey) }), displayColumns.length > 1 && (_jsx("p", { className: `${cfg.subtitleText} text-[rgb(var(--color-primary))] font-medium truncate flex-shrink-0`, children: getDisplayValue(item, displayColumns[1].accessorKey) }))] }), _jsx("div", { className: "flex-shrink-0 opacity-0 group-hover/card:opacity-100 transition-opacity duration-150", onClick: (e) => e.stopPropagation(), children: actionsColumn && actionsColumn.cell?.({
|
|
163
164
|
row: { original: item },
|
|
164
165
|
getValue: () => null,
|