indas-ui 0.1.3 → 0.1.5
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/auth.d.ts +1 -0
- package/dist/auth.js +5 -0
- package/dist/cjs/auth.js +10 -0
- package/dist/cjs/components/chat/ai-messages.js +3 -2
- package/dist/cjs/components/chat/chat.js +4 -3
- package/dist/cjs/components/dashboard/FilterBar.js +31 -8
- 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/dashboard/time-range.js +71 -27
- package/dist/cjs/components/datagrid/columns/ColumnContextMenu.js +2 -1
- package/dist/cjs/components/datagrid/controls/menu/view/CardView.js +2 -1
- package/dist/cjs/components/datagrid/controls/menu/view/ChartView.js +2 -1
- package/dist/cjs/components/datagrid/utils/grid-helpers.js +6 -5
- package/dist/cjs/components/layout/footer.js +2 -1
- package/dist/cjs/components/layout/side-panel.js +4 -1
- package/dist/cjs/components/layout/sidebar.js +17 -2
- package/dist/cjs/components/messaging/messaging-panel-content.js +2 -2
- package/dist/cjs/components/ui/display/stats-card.js +2 -1
- package/dist/cjs/components/ui/navigation/tabs.js +4 -1
- package/dist/cjs/contexts/EmailContext.js +3 -2
- package/dist/cjs/contexts/MessagingContext.js +2 -2
- package/dist/cjs/contexts/NavigationContext.js +17 -2
- package/dist/cjs/contexts/NotificationContext.js +2 -2
- package/dist/cjs/contexts/SessionAdapterContext.js +8 -0
- package/dist/cjs/hooks/useApproval.js +2 -2
- package/dist/cjs/hooks/useBusinessAudit.js +2 -2
- package/dist/cjs/hooks/useDraft.js +2 -2
- package/dist/cjs/lib/api/utility/menu.js +8 -7
- package/dist/cjs/lib/number-format.js +60 -0
- package/dist/cjs/lib/utils.js +4 -3
- package/dist/cjs/providers.js +3 -3
- package/dist/components/auth/index.js +0 -1
- package/dist/components/chat/ai-messages.js +3 -2
- package/dist/components/chat/chat.js +4 -3
- package/dist/components/chat/index.js +0 -1
- package/dist/components/dashboard/FilterBar.d.ts +5 -1
- package/dist/components/dashboard/FilterBar.js +31 -8
- 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 -1
- package/dist/components/dashboard/index.js +0 -1
- 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/dashboard/kpi/index.js +0 -1
- package/dist/components/dashboard/time-range.d.ts +12 -1
- package/dist/components/dashboard/time-range.js +71 -27
- package/dist/components/datagrid/cells/index.js +0 -1
- package/dist/components/datagrid/columns/ColumnContextMenu.js +2 -1
- package/dist/components/datagrid/controls/menu/grouping/index.js +0 -1
- package/dist/components/datagrid/controls/menu/index.js +0 -1
- package/dist/components/datagrid/controls/menu/view/CardView.js +2 -1
- package/dist/components/datagrid/controls/menu/view/ChartView.js +2 -1
- package/dist/components/datagrid/index.js +0 -1
- package/dist/components/datagrid/utils/grid-helpers.d.ts +1 -1
- package/dist/components/datagrid/utils/grid-helpers.js +3 -2
- package/dist/components/forms/date-picker/index.js +0 -1
- package/dist/components/forms/dropdown/index.js +0 -1
- package/dist/components/forms/file-attachment/index.js +0 -1
- package/dist/components/forms/index.js +0 -1
- package/dist/components/forms/remark-input/index.js +0 -1
- package/dist/components/index.js +0 -1
- package/dist/components/layout/footer.js +2 -1
- package/dist/components/layout/index.js +0 -1
- package/dist/components/layout/side-panel.js +4 -1
- package/dist/components/layout/sidebar.js +17 -2
- package/dist/components/master/editable-cells/index.js +0 -1
- package/dist/components/master/index.js +0 -1
- package/dist/components/messaging/messaging-panel-content.js +2 -2
- package/dist/components/modals/index.js +0 -1
- package/dist/components/ui/display/stats-card.js +2 -1
- package/dist/components/ui/feedback/badge.d.ts +1 -1
- package/dist/components/ui/index.js +0 -1
- package/dist/components/ui/navigation/tabs.js +4 -1
- package/dist/contexts/EmailContext.js +3 -2
- package/dist/contexts/MessagingContext.js +2 -2
- package/dist/contexts/NavigationContext.js +17 -2
- package/dist/contexts/NotificationContext.js +2 -2
- package/dist/contexts/SessionAdapterContext.d.ts +5 -0
- package/dist/contexts/SessionAdapterContext.js +7 -0
- package/dist/hooks/index.js +0 -1
- package/dist/hooks/useApproval.js +2 -2
- package/dist/hooks/useBusinessAudit.js +2 -2
- package/dist/hooks/useDraft.js +2 -2
- package/dist/index.js +0 -1
- package/dist/lib/api/activity/audit/index.js +0 -1
- package/dist/lib/api/activity/email/index.js +0 -1
- package/dist/lib/api/activity/index.js +0 -1
- package/dist/lib/api/activity/notifications/index.js +0 -1
- package/dist/lib/api/activity/remarks/index.js +0 -1
- package/dist/lib/api/approval/index.js +0 -1
- package/dist/lib/api/auth/index.js +0 -1
- package/dist/lib/api/core/index.js +0 -1
- package/dist/lib/api/draft/index.js +0 -1
- package/dist/lib/api/enquiry/index.js +0 -1
- package/dist/lib/api/estimation/index.js +0 -1
- package/dist/lib/api/index.js +0 -1
- package/dist/lib/api/master/index.js +0 -1
- package/dist/lib/api/utility/index.js +0 -1
- package/dist/lib/api/utility/menu.d.ts +7 -3
- package/dist/lib/api/utility/menu.js +8 -7
- 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/providers.d.ts +0 -1
- package/dist/providers.js +2 -2
- package/package.json +23 -8
|
@@ -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';
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
'use client';
|
|
2
1
|
// ECharts Components
|
|
3
2
|
export { AreaChart } from './Area.js';
|
|
4
3
|
export { BarChart } from './Bar.js';
|
|
@@ -21,3 +20,5 @@ export { GanttChart } from './Gantt.js';
|
|
|
21
20
|
export { FlowMap } from './FlowMap.js';
|
|
22
21
|
// Chart Wrapper
|
|
23
22
|
export { ChartCard } from './DashboardCard.js';
|
|
23
|
+
// Number formatting: Indian grouping by default; setNumberLocale() switches it for a whole app
|
|
24
|
+
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
|
}
|
|
@@ -5,7 +5,18 @@ export declare const fiscalWeekRange: (fyStart: number, wk: number) => {
|
|
|
5
5
|
to: Date;
|
|
6
6
|
} | null;
|
|
7
7
|
export declare const fiscalWeekCount: (fyStart: number) => number;
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* The dates a selection covers.
|
|
10
|
+
*
|
|
11
|
+
* Quick picks (This / Last …) count from `today`: "This week" is the week holding today, "Last
|
|
12
|
+
* month" the calendar month before this one, "This quarter" the fiscal quarter holding today.
|
|
13
|
+
* They used to anchor to the financial year's first period, so "This week" meant Week 1 in April.
|
|
14
|
+
* A "This …" range runs to today, since nothing after today has happened yet.
|
|
15
|
+
*
|
|
16
|
+
* A numbered pick (W27, month "9", Q3, a year "2025") is that period of the financial year
|
|
17
|
+
* starting `fyStart`. Year "all" is all time, from the earliest date a book could hold to today.
|
|
18
|
+
*/
|
|
19
|
+
export declare const rangeFromSelection: (sel: TimeFilterSelection | undefined, fyStart: number, today?: Date) => {
|
|
9
20
|
from: Date;
|
|
10
21
|
to: Date;
|
|
11
22
|
};
|
|
@@ -37,44 +37,77 @@ export const fiscalWeekCount = (fyStart) => {
|
|
|
37
37
|
wk++;
|
|
38
38
|
return wk;
|
|
39
39
|
};
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
const startOfDay = (d) => new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
|
41
|
+
const addDays = (d, n) => new Date(d.getFullYear(), d.getMonth(), d.getDate() + n);
|
|
42
|
+
// Monday of the week holding `d` (weeks run Monday to Sunday, as the fiscal weeks do).
|
|
43
|
+
const mondayOf = (d) => addDays(d, -((d.getDay() + 6) % 7));
|
|
44
|
+
/**
|
|
45
|
+
* The dates a selection covers.
|
|
46
|
+
*
|
|
47
|
+
* Quick picks (This / Last …) count from `today`: "This week" is the week holding today, "Last
|
|
48
|
+
* month" the calendar month before this one, "This quarter" the fiscal quarter holding today.
|
|
49
|
+
* They used to anchor to the financial year's first period, so "This week" meant Week 1 in April.
|
|
50
|
+
* A "This …" range runs to today, since nothing after today has happened yet.
|
|
51
|
+
*
|
|
52
|
+
* A numbered pick (W27, month "9", Q3, a year "2025") is that period of the financial year
|
|
53
|
+
* starting `fyStart`. Year "all" is all time, from the earliest date a book could hold to today.
|
|
54
|
+
*/
|
|
55
|
+
export const rangeFromSelection = (sel, fyStart, today = new Date()) => {
|
|
56
|
+
const t = startOfDay(today);
|
|
42
57
|
const fyOpen = new Date(fyStart, 3, 1);
|
|
43
58
|
const fyClose = new Date(fyStart + 1, 2, 31);
|
|
44
59
|
const yearOf = (mo) => (mo >= 3 ? fyStart : fyStart + 1);
|
|
45
60
|
const monthRange = (mo) => ({ from: new Date(yearOf(mo), mo, 1), to: new Date(yearOf(mo), mo + 1, 0) });
|
|
46
61
|
const type = sel?.type || 'Month';
|
|
47
62
|
if (type === 'Day') {
|
|
48
|
-
const now = new Date();
|
|
49
|
-
const y = now.getFullYear(), m = now.getMonth(), d = now.getDate();
|
|
50
63
|
const pick = sel?.days?.[0] || 'today';
|
|
51
|
-
if (pick === 'yesterday')
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
if (pick === 'last7')
|
|
56
|
-
return { from: new Date(y, m, d - 6), to: new Date(y, m, d) };
|
|
57
|
-
if (pick === 'last14')
|
|
58
|
-
return { from: new Date(y, m, d - 13), to: new Date(y, m, d) };
|
|
59
|
-
if (pick === 'last30')
|
|
60
|
-
return { from: new Date(y, m, d - 29), to: new Date(y, m, d) };
|
|
61
|
-
return { from: new Date(y, m, d), to: new Date(y, m, d) };
|
|
64
|
+
if (pick === 'yesterday')
|
|
65
|
+
return { from: addDays(t, -1), to: addDays(t, -1) };
|
|
66
|
+
const span = { last7: 7, last14: 14, last30: 30 }[pick];
|
|
67
|
+
return span ? { from: addDays(t, -(span - 1)), to: t } : { from: t, to: t };
|
|
62
68
|
}
|
|
63
69
|
if (type === 'Week') {
|
|
64
|
-
const pick = sel?.weeks?.[0] || '
|
|
70
|
+
const pick = sel?.weeks?.[0] || 'this';
|
|
71
|
+
const monday = mondayOf(t);
|
|
72
|
+
if (pick === 'this')
|
|
73
|
+
return { from: monday, to: t };
|
|
74
|
+
if (pick === 'last')
|
|
75
|
+
return { from: addDays(monday, -7), to: addDays(monday, -1) };
|
|
76
|
+
// "Last 2 weeks" is this week and the one before it, to today.
|
|
77
|
+
const weeks = { last2: 2, last4: 4 }[pick];
|
|
78
|
+
if (weeks)
|
|
79
|
+
return { from: addDays(monday, -7 * (weeks - 1)), to: t };
|
|
65
80
|
const wk = /^W\d+$/i.test(pick) ? parseInt(pick.slice(1)) : 1;
|
|
66
81
|
return fiscalWeekRange(fyStart, wk) || { from: fyOpen, to: fyClose };
|
|
67
82
|
}
|
|
68
83
|
if (type === 'Quarter') {
|
|
69
|
-
const pick = sel?.quarters?.[0];
|
|
70
|
-
|
|
84
|
+
const pick = sel?.quarters?.[0] || 'this';
|
|
85
|
+
// The fiscal quarter holding today: Apr–Jun is Q1.
|
|
86
|
+
const qStart = new Date(t.getFullYear(), t.getMonth() - (((t.getMonth() - 3 + 12) % 12) % 3), 1);
|
|
87
|
+
if (pick === 'this')
|
|
88
|
+
return { from: qStart, to: t };
|
|
89
|
+
if (pick === 'last')
|
|
90
|
+
return { from: new Date(qStart.getFullYear(), qStart.getMonth() - 3, 1), to: addDays(qStart, -1) };
|
|
91
|
+
const qNum = /^q[1-4]$/i.test(pick) ? parseInt(pick.slice(1)) - 1 : 0;
|
|
71
92
|
const startMonth = (3 + qNum * 3) % 12;
|
|
72
93
|
const sy = yearOf(startMonth);
|
|
73
94
|
return { from: new Date(sy, startMonth, 1), to: new Date(sy, startMonth + 3, 0) };
|
|
74
95
|
}
|
|
75
|
-
if (type === 'Year')
|
|
76
|
-
|
|
77
|
-
|
|
96
|
+
if (type === 'Year') {
|
|
97
|
+
const pick = sel?.years?.[0] || 'this';
|
|
98
|
+
if (pick === 'all')
|
|
99
|
+
return { from: new Date(1970, 0, 1), to: t };
|
|
100
|
+
if (pick === 'last')
|
|
101
|
+
return { from: new Date(fyStart - 1, 3, 1), to: new Date(fyStart, 2, 31) };
|
|
102
|
+
const fy = /^\d{4}$/.test(pick) ? parseInt(pick) : fyStart;
|
|
103
|
+
return { from: new Date(fy, 3, 1), to: new Date(fy + 1, 2, 31) };
|
|
104
|
+
}
|
|
105
|
+
const months = sel?.months || [];
|
|
106
|
+
if (months.includes('this'))
|
|
107
|
+
return { from: new Date(t.getFullYear(), t.getMonth(), 1), to: t };
|
|
108
|
+
if (months.includes('last'))
|
|
109
|
+
return { from: new Date(t.getFullYear(), t.getMonth() - 1, 1), to: new Date(t.getFullYear(), t.getMonth(), 0) };
|
|
110
|
+
const picks = months.filter(s => /^\d{1,2}$/.test(s)).map(s => parseInt(s));
|
|
78
111
|
if (picks.length > 0) {
|
|
79
112
|
const ranges = picks.map(monthRange);
|
|
80
113
|
return {
|
|
@@ -82,7 +115,7 @@ export const rangeFromSelection = (sel, fyStart) => {
|
|
|
82
115
|
to: ranges.reduce((a, b) => (b.to > a.to ? b : a)).to,
|
|
83
116
|
};
|
|
84
117
|
}
|
|
85
|
-
return
|
|
118
|
+
return { from: new Date(t.getFullYear(), t.getMonth(), 1), to: t };
|
|
86
119
|
};
|
|
87
120
|
// Header subtitle for the selected period. Week/Day show their day span in brackets;
|
|
88
121
|
// Quarter/Year show fiscal labels. `t` translates the period words.
|
|
@@ -99,17 +132,28 @@ export const buildContextLabel = (sel, fyStart, dateFrom, dateTo, t) => {
|
|
|
99
132
|
return `${pick} (${dayRange})`;
|
|
100
133
|
}
|
|
101
134
|
if (!dateFrom && !dateTo && sel?.type === 'Week') {
|
|
102
|
-
const w = sel.weeks?.[0];
|
|
103
|
-
const
|
|
135
|
+
const w = sel.weeks?.[0] || 'this';
|
|
136
|
+
const quick = { this: 'This Week', last: 'Last Week', last2: 'Last 2 Weeks', last4: 'Last 4 Weeks' }[w];
|
|
137
|
+
if (quick)
|
|
138
|
+
return `${t(quick)} (${dayRange})`;
|
|
139
|
+
const wk = /^W\d+$/i.test(w) ? w.slice(1) : '1';
|
|
104
140
|
return `${t('Week')} ${wk} (${dayRange})`;
|
|
105
141
|
}
|
|
106
142
|
if (!dateFrom && !dateTo && sel?.type === 'Quarter') {
|
|
107
|
-
const q = sel.quarters?.[0];
|
|
108
|
-
const label = q
|
|
143
|
+
const q = sel.quarters?.[0] || 'this';
|
|
144
|
+
const label = q === 'this' ? t('This Quarter') : q === 'last' ? t('Last Quarter') : /^q[1-4]$/i.test(q) ? `Q${q.slice(1)}` : 'Q1';
|
|
109
145
|
return `${label} (${fmtMon(from)} – ${fmtMon(to)})`;
|
|
110
146
|
}
|
|
111
147
|
if (!dateFrom && !dateTo && sel?.type === 'Year') {
|
|
112
|
-
|
|
148
|
+
const y = sel.years?.[0] || 'this';
|
|
149
|
+
if (y === 'all')
|
|
150
|
+
return t('All Time');
|
|
151
|
+
const fy = y === 'last' ? fyStart - 1 : /^\d{4}$/.test(y) ? parseInt(y) : fyStart;
|
|
152
|
+
return `${t('FY')} ${fy}-${fy + 1}`;
|
|
153
|
+
}
|
|
154
|
+
const m0 = sel?.months?.[0];
|
|
155
|
+
if (!dateFrom && !dateTo && sel?.type === 'Month' && (m0 === 'this' || m0 === 'last')) {
|
|
156
|
+
return `${t(m0 === 'this' ? 'This Month' : 'Last Month')} (${dayRange})`;
|
|
113
157
|
}
|
|
114
158
|
const sameMonth = from.getFullYear() === to.getFullYear() && from.getMonth() === to.getMonth();
|
|
115
159
|
if (sameMonth)
|
|
@@ -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;
|
|
@@ -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) => {
|
|
@@ -8,6 +8,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '../../../../ui/index.j
|
|
|
8
8
|
import { Button } from '../../../../ui/index.js';
|
|
9
9
|
import { Dropdown } from '../../../../index.js';
|
|
10
10
|
import { Badge } from '../../../../ui/index.js';
|
|
11
|
+
import { formatNumber } from '../../../../../lib/number-format.js';
|
|
11
12
|
const CHART_COLORS = [
|
|
12
13
|
'#3b82f6', // Blue
|
|
13
14
|
'#ef4444', // Red
|
|
@@ -341,7 +342,7 @@ export function DataVisualization({ data, columns, title = "Data Insights" }) {
|
|
|
341
342
|
if (data.length === 0) {
|
|
342
343
|
return (_jsx(Card, { className: "p-8", children: _jsxs("div", { className: "text-center text-gray-500", children: [_jsx(BarChart3, { className: "h-12 w-12 mx-auto mb-4 opacity-50" }), _jsx("h3", { className: "text-lg font-medium mb-2", children: "No Data Available" }), _jsx("p", { className: "text-sm", children: "Add some data to view visualizations" })] }) }));
|
|
343
344
|
}
|
|
344
|
-
return (_jsxs("div", { className: "space-y-6 p-6", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-2xl font-bold tracking-tight", children: title }), _jsxs("p", { className: "text-sm text-gray-600 mt-1", children: ["Visual analysis of ", data.length, " records"] })] }), _jsxs("div", { className: "flex items-center space-x-2", children: [_jsxs(Button, { variant: selectedChart === 'bar' ? 'primary' : 'outline', size: "sm", onClick: () => setSelectedChart('bar'), children: [_jsx(BarChart3, { className: "h-4 w-4 mr-2" }), "Bar"] }), _jsxs(Button, { variant: selectedChart === 'line' ? 'primary' : 'outline', size: "sm", onClick: () => setSelectedChart('line'), children: [_jsx(LineChartIcon, { className: "h-4 w-4 mr-2" }), "Line"] }), _jsxs(Button, { variant: selectedChart === 'area' ? 'primary' : 'outline', size: "sm", onClick: () => setSelectedChart('area'), children: [_jsx(Activity, { className: "h-4 w-4 mr-2" }), "Area"] }), _jsxs(Button, { variant: selectedChart === 'pie' ? 'primary' : 'outline', size: "sm", onClick: () => setSelectedChart('pie'), children: [_jsx(PieChartIcon, { className: "h-4 w-4 mr-2" }), "Pie"] })] })] }), summaryStats && (_jsxs("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-4", children: [_jsx(Card, { children: _jsxs(CardContent, { className: "p-4", children: [_jsx("div", { className: "text-2xl font-bold text-blue-600", children: summaryStats.sum
|
|
345
|
+
return (_jsxs("div", { className: "space-y-6 p-6", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-2xl font-bold tracking-tight", children: title }), _jsxs("p", { className: "text-sm text-gray-600 mt-1", children: ["Visual analysis of ", data.length, " records"] })] }), _jsxs("div", { className: "flex items-center space-x-2", children: [_jsxs(Button, { variant: selectedChart === 'bar' ? 'primary' : 'outline', size: "sm", onClick: () => setSelectedChart('bar'), children: [_jsx(BarChart3, { className: "h-4 w-4 mr-2" }), "Bar"] }), _jsxs(Button, { variant: selectedChart === 'line' ? 'primary' : 'outline', size: "sm", onClick: () => setSelectedChart('line'), children: [_jsx(LineChartIcon, { className: "h-4 w-4 mr-2" }), "Line"] }), _jsxs(Button, { variant: selectedChart === 'area' ? 'primary' : 'outline', size: "sm", onClick: () => setSelectedChart('area'), children: [_jsx(Activity, { className: "h-4 w-4 mr-2" }), "Area"] }), _jsxs(Button, { variant: selectedChart === 'pie' ? 'primary' : 'outline', size: "sm", onClick: () => setSelectedChart('pie'), children: [_jsx(PieChartIcon, { className: "h-4 w-4 mr-2" }), "Pie"] })] })] }), summaryStats && (_jsxs("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-4", children: [_jsx(Card, { children: _jsxs(CardContent, { className: "p-4", children: [_jsx("div", { className: "text-2xl font-bold text-blue-600", children: formatNumber(summaryStats.sum) }), _jsx("div", { className: "text-xs text-gray-600", children: "Total" })] }) }), _jsx(Card, { children: _jsxs(CardContent, { className: "p-4", children: [_jsx("div", { className: "text-2xl font-bold text-green-600", children: summaryStats.avg.toFixed(1) }), _jsx("div", { className: "text-xs text-gray-600", children: "Average" })] }) }), _jsx(Card, { children: _jsxs(CardContent, { className: "p-4", children: [_jsx("div", { className: "text-2xl font-bold text-orange-600", children: formatNumber(summaryStats.min) }), _jsx("div", { className: "text-xs text-gray-600", children: "Minimum" })] }) }), _jsx(Card, { children: _jsxs(CardContent, { className: "p-4", children: [_jsx("div", { className: "text-2xl font-bold text-purple-600", children: formatNumber(summaryStats.max) }), _jsx("div", { className: "text-xs text-gray-600", children: "Maximum" })] }) })] })), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-lg", children: "Chart Configuration" }) }), _jsx(CardContent, { children: _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-4", children: [_jsxs("div", { children: [_jsxs("label", { className: "text-sm font-medium mb-2 block", children: ["X-Axis (", selectedChart === 'pie' ? 'Category' : 'Categories', ")"] }), _jsx(Dropdown, { options: categoricalColumns.map((col) => ({
|
|
345
346
|
value: col.key,
|
|
346
347
|
label: col.label,
|
|
347
348
|
})), value: xAxisColumn, onValueChange: (value) => setXAxisColumn(typeof value === 'string' ? value : value[0] || ''), placeholder: "Select column" })] }), selectedChart !== 'pie' && (_jsxs("div", { children: [_jsx("label", { className: "text-sm font-medium mb-2 block", children: "Y-Axis (Values)" }), _jsx(Dropdown, { options: numericColumns.map((col) => ({
|
|
@@ -25,7 +25,7 @@ export declare function debounce<T extends (...args: any[]) => any>(func: T, del
|
|
|
25
25
|
*/
|
|
26
26
|
export declare function getRowId<TData>(row: TData, index: number, getRowIdFn?: (row: TData) => string): string;
|
|
27
27
|
/**
|
|
28
|
-
* Format large numbers with
|
|
28
|
+
* Format large numbers with grouping, in the library's number locale (Indian by default)
|
|
29
29
|
*/
|
|
30
30
|
export declare function formatNumber(num: number): string;
|
|
31
31
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
+
import { formatNumber as formatGrouped } from '../../../lib/number-format.js';
|
|
2
3
|
/**
|
|
3
4
|
* Grid utility functions and helpers
|
|
4
5
|
*/
|
|
@@ -75,10 +76,10 @@ export function getRowId(row, index, getRowIdFn) {
|
|
|
75
76
|
return String(index);
|
|
76
77
|
}
|
|
77
78
|
/**
|
|
78
|
-
* Format large numbers with
|
|
79
|
+
* Format large numbers with grouping, in the library's number locale (Indian by default)
|
|
79
80
|
*/
|
|
80
81
|
export function formatNumber(num) {
|
|
81
|
-
return num
|
|
82
|
+
return formatGrouped(num);
|
|
82
83
|
}
|
|
83
84
|
/**
|
|
84
85
|
* Check if value matches search term
|