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
|
@@ -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) => ({
|
|
@@ -17,15 +17,15 @@ navigationState = {}, columnSearches = {}, isColumnSearchActive = {}, onColumnSe
|
|
|
17
17
|
transition,
|
|
18
18
|
opacity: isDragging ? 0.5 : 1,
|
|
19
19
|
};
|
|
20
|
-
return (_jsxs("tr", { ref: setNodeRef, style: style, className: `
|
|
21
|
-
cursor-pointer group
|
|
22
|
-
transition-colors duration-150 ease-out
|
|
23
|
-
${isDragging ? 'shadow-lg z-50' : ''}
|
|
20
|
+
return (_jsxs("tr", { ref: setNodeRef, style: style, className: `
|
|
21
|
+
cursor-pointer group
|
|
22
|
+
transition-colors duration-150 ease-out
|
|
23
|
+
${isDragging ? 'shadow-lg z-50' : ''}
|
|
24
24
|
${isHighlighted
|
|
25
25
|
? 'border-2 border-[rgb(var(--color-primary))] bg-gradient-to-r from-[rgb(var(--color-primary))]/10 to-[rgb(var(--color-primary))]/20 shadow-lg relative'
|
|
26
26
|
: isSelected
|
|
27
27
|
? 'bg-[color-mix(in_srgb,rgb(var(--color-primary))_8%,rgb(var(--bg-surface)))] shadow-sm hover:bg-[color-mix(in_srgb,rgb(var(--color-primary))_15%,rgb(var(--bg-surface)))]'
|
|
28
|
-
: 'hover:bg-[color-mix(in_srgb,rgb(var(--color-primary))_5%,rgb(var(--bg-surface)))]'}
|
|
29
|
-
${className ?? ''}
|
|
28
|
+
: 'hover:bg-[color-mix(in_srgb,rgb(var(--color-primary))_5%,rgb(var(--bg-surface)))]'}
|
|
29
|
+
${className ?? ''}
|
|
30
30
|
`, onClick: onRowClick, onDoubleClick: onRowDoubleClick, onContextMenu: onRowContextMenu, children: [showDragHandle && (_jsx("td", { className: "text-center align-middle cursor-grab active:cursor-grabbing", style: { width: '1.875rem', minWidth: '1.875rem', maxWidth: '1.875rem' }, ...attributes, ...listeners, children: _jsx("div", { className: "w-full h-full flex items-center justify-center", children: _jsx(GripVertical, { className: "h-4 w-4 text-gray-400" }) }) })), row.getVisibleCells().map((cell, cellIndex) => (_jsx(CellRenderer, { cell: cell, cellIndex: cellIndex, row: row, enableBacchaSearch: enableBacchaSearch, mainColumnsArray: mainColumnsArray, frozenColumnsState: frozenColumnsState, navigationState: navigationState, columnSearches: columnSearches, isColumnSearchActive: isColumnSearchActive, onColumnSearch: onColumnSearch, onClearColumnSearch: onClearColumnSearch, SearchHighlighter: SearchHighlighter, InlineSearchCell: InlineSearchCell, isSelected: isSelected }, cell.id)))] }));
|
|
31
31
|
}
|
|
@@ -86,10 +86,10 @@ export function InlineSearchCell({ value, columnId, rowId, onSearch, onClearSear
|
|
|
86
86
|
return (_jsx(_Fragment, { children: parts.map((part, index) => part.toLowerCase() === highlight.toLowerCase() ? (_jsx("mark", { className: "bg-yellow-200 text-yellow-900 px-0.5 rounded", children: part }, index)) : (_jsx("span", { children: part }, index))) }));
|
|
87
87
|
};
|
|
88
88
|
const displayValue = value?.toString() || '';
|
|
89
|
-
return (_jsxs("div", { ref: cellRef, tabIndex: 0, className: `bacchasearch relative group cursor-pointer outline-none inline-flex items-center w-full ${className}`, onClick: handleCellClick, children: [_jsx("span", { className: `
|
|
90
|
-
text-xs flex-1 rounded transition-colors
|
|
91
|
-
${isSearchActive ? 'bg-[rgb(var(--color-primary)/0.1)]' : 'hover:bg-[rgb(var(--color-primary)/0.05)]'}
|
|
92
|
-
${isTyping ? 'ring-1 ring-[rgb(var(--color-primary)/0.3)] bg-[rgb(var(--color-primary)/0.15)]' : ''}
|
|
89
|
+
return (_jsxs("div", { ref: cellRef, tabIndex: 0, className: `bacchasearch relative group cursor-pointer outline-none inline-flex items-center w-full ${className}`, onClick: handleCellClick, children: [_jsx("span", { className: `
|
|
90
|
+
text-xs flex-1 rounded transition-colors
|
|
91
|
+
${isSearchActive ? 'bg-[rgb(var(--color-primary)/0.1)]' : 'hover:bg-[rgb(var(--color-primary)/0.05)]'}
|
|
92
|
+
${isTyping ? 'ring-1 ring-[rgb(var(--color-primary)/0.3)] bg-[rgb(var(--color-primary)/0.15)]' : ''}
|
|
93
93
|
`, children: isSearchActive && searchTerm ?
|
|
94
94
|
renderHighlightedText(displayValue, searchTerm) :
|
|
95
95
|
displayValue }), _jsx(Search, { className: `h-3 w-3 text-[rgb(var(--fg-muted))] ml-0.5 flex-shrink-0 transition-opacity ${isTyping ? 'opacity-100' : 'opacity-0 group-hover:opacity-100'}` }), (isTyping || isSearchActive) && (_jsx("div", { className: "absolute -top-0.5 -right-0.5", children: _jsx("div", { className: `h-1.5 w-1.5 rounded-full ${isTyping ? 'bg-[rgb(var(--color-success))]' : 'bg-[rgb(var(--color-primary))]'}` }) })), isTyping && localSearchTerm && (_jsxs("div", { className: "absolute -bottom-5 left-0 bg-[rgb(var(--bg-surface))] text-[rgb(var(--fg-default))] text-xs px-1.5 py-0.5 rounded shadow-lg border border-[rgb(var(--bd-default))] z-10 whitespace-nowrap", children: ["\"", localSearchTerm, "\""] }))] }));
|
|
@@ -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
|
|
@@ -54,14 +54,14 @@ export function AutoSaveIndicator({ enabled, lastSaved, isDirty, isSaving, onOpe
|
|
|
54
54
|
};
|
|
55
55
|
};
|
|
56
56
|
const status = getStatus();
|
|
57
|
-
return (_jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-xs text-[rgb(var(--fg-muted))]", children: "Auto-save:" }), _jsx("button", { onClick: () => onToggleAutoSave?.(!enabled), className: `
|
|
58
|
-
relative inline-flex h-5 w-9 items-center rounded-full transition-colors
|
|
59
|
-
${enabled ? 'bg-[rgb(var(--color-primary))]' : 'bg-[rgb(var(--bg-muted))]'}
|
|
60
|
-
`, title: enabled ? 'Disable auto-save' : 'Enable auto-save', children: _jsx("span", { className: `
|
|
61
|
-
inline-block h-3.5 w-3.5 transform rounded-full bg-white transition-transform
|
|
62
|
-
${enabled ? 'translate-x-5' : 'translate-x-1'}
|
|
63
|
-
` }) }), _jsx("span", { className: "text-xs font-medium text-[rgb(var(--fg-default))]", children: enabled ? 'ON' : 'OFF' })] }), _jsx("div", { className: "h-4 w-px bg-[rgb(var(--bd-default))]" }), _jsxs("button", { onClick: onOpenDraftManager, className: `
|
|
64
|
-
flex items-center gap-2 px-3 py-1.5 rounded-md transition-colors
|
|
65
|
-
${status.bg} ${status.color} hover:opacity-80
|
|
57
|
+
return (_jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-xs text-[rgb(var(--fg-muted))]", children: "Auto-save:" }), _jsx("button", { onClick: () => onToggleAutoSave?.(!enabled), className: `
|
|
58
|
+
relative inline-flex h-5 w-9 items-center rounded-full transition-colors
|
|
59
|
+
${enabled ? 'bg-[rgb(var(--color-primary))]' : 'bg-[rgb(var(--bg-muted))]'}
|
|
60
|
+
`, title: enabled ? 'Disable auto-save' : 'Enable auto-save', children: _jsx("span", { className: `
|
|
61
|
+
inline-block h-3.5 w-3.5 transform rounded-full bg-white transition-transform
|
|
62
|
+
${enabled ? 'translate-x-5' : 'translate-x-1'}
|
|
63
|
+
` }) }), _jsx("span", { className: "text-xs font-medium text-[rgb(var(--fg-default))]", children: enabled ? 'ON' : 'OFF' })] }), _jsx("div", { className: "h-4 w-px bg-[rgb(var(--bd-default))]" }), _jsxs("button", { onClick: onOpenDraftManager, className: `
|
|
64
|
+
flex items-center gap-2 px-3 py-1.5 rounded-md transition-colors
|
|
65
|
+
${status.bg} ${status.color} hover:opacity-80
|
|
66
66
|
`, title: "Click to manage drafts", children: [status.icon, _jsx("span", { className: "text-xs font-medium", children: status.text })] })] }));
|
|
67
67
|
}
|
|
@@ -156,10 +156,10 @@ export function DraftModal({ open, onClose, drafts, onLoad, onDelete, isLoading
|
|
|
156
156
|
// Early return after all hooks to comply with Rules of Hooks
|
|
157
157
|
if (!open)
|
|
158
158
|
return null;
|
|
159
|
-
return (_jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm", children: _jsxs("div", { className: "bg-[rgb(var(--bg-surface))] rounded-lg shadow-2xl w-full max-w-4xl max-h-[85vh] flex flex-col", children: [_jsxs("div", { className: "px-6 py-3 border-b border-[rgb(var(--bd-default))] flex items-center gap-4", children: [_jsxs("div", { className: "flex items-center gap-2 flex-shrink-0", children: [_jsx(FileText, { className: "w-5 h-5 text-[rgb(var(--color-primary))]" }), _jsx("h2", { className: "text-base font-semibold text-[rgb(var(--fg-default))]", children: "Saved Drafts" }), _jsxs("span", { className: "text-xs text-[rgb(var(--fg-muted))]", children: ["(", drafts.length, ")"] })] }), _jsxs("div", { className: "relative flex-1", children: [_jsx(Search, { className: "absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-[rgb(var(--fg-muted))]" }), _jsx("input", { type: "text", placeholder: "Search...", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), className: "\n w-full pl-9 pr-3 py-1.5 rounded-lg text-sm\n bg-[rgb(var(--bg-default))]\n border border-[rgb(var(--bd-default))]\n text-[rgb(var(--fg-default))]\n placeholder:text-[rgb(var(--fg-muted))]\n focus:outline-none focus:ring-2 focus:ring-[rgb(var(--color-primary))]\n " })] }), _jsx("div", { className: "flex-shrink-0 w-36", children: _jsx(Dropdown, { options: SORT_OPTIONS, value: sortBy, onValueChange: (val) => setSortBy(val), size: "sm", autoWidth: true }) }), _jsx("button", { onClick: onClose, className: "p-1.5 rounded-lg hover:bg-[rgb(var(--bg-muted))] text-[rgb(var(--fg-muted))] hover:text-[rgb(var(--fg-default))] transition-colors flex-shrink-0", children: _jsx(X, { className: "w-5 h-5" }) })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-6 py-4", children: isLoading ? (_jsx("div", { className: "flex items-center justify-center py-16", children: _jsxs("div", { className: "text-center", children: [_jsx("div", { className: "w-12 h-12 rounded-full border-4 border-[rgb(var(--color-primary)_/_0.2)] border-t-[rgb(var(--color-primary))] animate-spin mx-auto mb-3" }), _jsx("p", { className: "text-[rgb(var(--fg-muted))]", children: "Loading drafts..." })] }) })) : filteredAndSortedDrafts.length === 0 ? (_jsxs("div", { className: "flex flex-col items-center justify-center py-16 text-center", children: [_jsx(FileText, { className: "w-16 h-16 text-[rgb(var(--fg-muted))] mb-4 opacity-50" }), _jsx("p", { className: "text-lg font-medium text-[rgb(var(--fg-default))] mb-1", children: searchQuery ? 'No matching drafts' : 'No drafts saved yet' }), _jsx("p", { className: "text-sm text-[rgb(var(--fg-muted))]", children: searchQuery ? 'Try a different search term' : 'Start by clicking "Save Draft" button' })] })) : (_jsx("div", { className: "space-y-3", children: paginatedDrafts.map((draft) => {
|
|
159
|
+
return (_jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm", children: _jsxs("div", { className: "bg-[rgb(var(--bg-surface))] rounded-lg shadow-2xl w-full max-w-4xl max-h-[85vh] flex flex-col", children: [_jsxs("div", { className: "px-6 py-3 border-b border-[rgb(var(--bd-default))] flex items-center gap-4", children: [_jsxs("div", { className: "flex items-center gap-2 flex-shrink-0", children: [_jsx(FileText, { className: "w-5 h-5 text-[rgb(var(--color-primary))]" }), _jsx("h2", { className: "text-base font-semibold text-[rgb(var(--fg-default))]", children: "Saved Drafts" }), _jsxs("span", { className: "text-xs text-[rgb(var(--fg-muted))]", children: ["(", drafts.length, ")"] })] }), _jsxs("div", { className: "relative flex-1", children: [_jsx(Search, { className: "absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-[rgb(var(--fg-muted))]" }), _jsx("input", { type: "text", placeholder: "Search...", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), className: "\r\n w-full pl-9 pr-3 py-1.5 rounded-lg text-sm\r\n bg-[rgb(var(--bg-default))]\r\n border border-[rgb(var(--bd-default))]\r\n text-[rgb(var(--fg-default))]\r\n placeholder:text-[rgb(var(--fg-muted))]\r\n focus:outline-none focus:ring-2 focus:ring-[rgb(var(--color-primary))]\r\n " })] }), _jsx("div", { className: "flex-shrink-0 w-36", children: _jsx(Dropdown, { options: SORT_OPTIONS, value: sortBy, onValueChange: (val) => setSortBy(val), size: "sm", autoWidth: true }) }), _jsx("button", { onClick: onClose, className: "p-1.5 rounded-lg hover:bg-[rgb(var(--bg-muted))] text-[rgb(var(--fg-muted))] hover:text-[rgb(var(--fg-default))] transition-colors flex-shrink-0", children: _jsx(X, { className: "w-5 h-5" }) })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-6 py-4", children: isLoading ? (_jsx("div", { className: "flex items-center justify-center py-16", children: _jsxs("div", { className: "text-center", children: [_jsx("div", { className: "w-12 h-12 rounded-full border-4 border-[rgb(var(--color-primary)_/_0.2)] border-t-[rgb(var(--color-primary))] animate-spin mx-auto mb-3" }), _jsx("p", { className: "text-[rgb(var(--fg-muted))]", children: "Loading drafts..." })] }) })) : filteredAndSortedDrafts.length === 0 ? (_jsxs("div", { className: "flex flex-col items-center justify-center py-16 text-center", children: [_jsx(FileText, { className: "w-16 h-16 text-[rgb(var(--fg-muted))] mb-4 opacity-50" }), _jsx("p", { className: "text-lg font-medium text-[rgb(var(--fg-default))] mb-1", children: searchQuery ? 'No matching drafts' : 'No drafts saved yet' }), _jsx("p", { className: "text-sm text-[rgb(var(--fg-muted))]", children: searchQuery ? 'Try a different search term' : 'Start by clicking "Save Draft" button' })] })) : (_jsx("div", { className: "space-y-3", children: paginatedDrafts.map((draft) => {
|
|
160
160
|
const progress = calculateProgress(draft);
|
|
161
161
|
const preview = draft.previewData || {};
|
|
162
|
-
return (_jsx("div", { className: "\n group relative p-3 rounded-lg\n bg-[rgb(var(--bg-default))]\n border border-[rgb(var(--bd-default))]\n hover:border-[rgb(var(--color-primary))]\n hover:shadow-lg\n transition-all duration-200\n ", children: _jsx("div", { className: "flex items-start gap-4", children: _jsxs("div", { className: "flex-1 min-w-0", children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("h3", { className: "font-semibold text-[rgb(var(--fg-default))] truncate text-base", children: draft.draftName || 'Untitled Draft' }), _jsxs("div", { className: "flex items-center gap-2 mt-1 text-xs text-[rgb(var(--fg-muted))]", children: [_jsx("span", { children: formatRelativeTime(draft.lastSaved) }), _jsxs("span", { className: "hidden group-hover:inline", children: ["\u2022 ", formatFullDate(draft.lastSaved)] }), draft.isAutoSave && (_jsx("span", { className: "text-[10px] px-2 py-0.5 rounded-full bg-[rgb(var(--color-primary)_/_0.1)] text-[rgb(var(--color-primary))] font-medium", children: "AUTO-SAVE" }))] })] }), _jsxs("div", { className: "flex items-center gap-2 opacity-0 group-hover:opacity-100 transition-opacity", children: [_jsxs("button", { onClick: () => handleLoad(draft.draftId), className: "\n px-4 py-2 rounded-lg text-sm font-medium\n bg-[rgb(var(--color-primary))]\n text-white\n hover:opacity-90\n transition-opacity\n flex items-center gap-2\n ", children: [_jsx(Download, { className: "w-4 h-4" }), "Load"] }), _jsx("button", { onClick: () => handleDelete(draft.draftId), disabled: deletingId === draft.draftId, className: "\n p-2 rounded-lg\n text-[rgb(var(--fg-muted))]\n hover:text-red-600\n hover:bg-red-50\n transition-colors\n disabled:opacity-50\n ", title: "Delete draft", children: deletingId === draft.draftId ? (_jsx("div", { className: "w-4 h-4 rounded-full border-2 border-red-200 border-t-red-600 animate-spin" })) : (_jsx(Trash2, { className: "w-4 h-4" })) })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-x-6 gap-y-1 mt-2 text-sm", children: [preview.jobName && (_jsx("div", { className: "text-[rgb(var(--fg-muted))] truncate", children: preview.jobName }, "job")), preview.clientName && (_jsx("div", { className: "text-[rgb(var(--fg-muted))] truncate", children: preview.clientName }, "client")), preview.category && (_jsx("div", { className: "text-[rgb(var(--fg-muted))] truncate", children: preview.category }, "category")), preview.totalCost !== undefined && preview.totalCost > 0 && (_jsxs("div", { className: "font-medium text-[rgb(var(--color-primary))]", children: [currencySymbol, preview.totalCost.toLocaleString('en-IN')] }, "cost"))] }), progress > 0 && (_jsxs("div", { className: "mt-2", children: [_jsxs("div", { className: "flex items-center justify-between text-xs text-[rgb(var(--fg-muted))] mb-1", children: [_jsx("span", { children: "Progress" }), _jsxs("span", { className: "font-medium", children: [progress, "%"] })] }), _jsx("div", { className: "h-1.5 bg-[rgb(var(--bg-muted))] rounded-full overflow-hidden", children: _jsx("div", { className: "h-full bg-[rgb(var(--color-primary))] transition-all duration-500 rounded-full", style: { width: `${progress}%` } }) })] }))] }) }) }, draft.draftId));
|
|
162
|
+
return (_jsx("div", { className: "\r\n group relative p-3 rounded-lg\r\n bg-[rgb(var(--bg-default))]\r\n border border-[rgb(var(--bd-default))]\r\n hover:border-[rgb(var(--color-primary))]\r\n hover:shadow-lg\r\n transition-all duration-200\r\n ", children: _jsx("div", { className: "flex items-start gap-4", children: _jsxs("div", { className: "flex-1 min-w-0", children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("h3", { className: "font-semibold text-[rgb(var(--fg-default))] truncate text-base", children: draft.draftName || 'Untitled Draft' }), _jsxs("div", { className: "flex items-center gap-2 mt-1 text-xs text-[rgb(var(--fg-muted))]", children: [_jsx("span", { children: formatRelativeTime(draft.lastSaved) }), _jsxs("span", { className: "hidden group-hover:inline", children: ["\u2022 ", formatFullDate(draft.lastSaved)] }), draft.isAutoSave && (_jsx("span", { className: "text-[10px] px-2 py-0.5 rounded-full bg-[rgb(var(--color-primary)_/_0.1)] text-[rgb(var(--color-primary))] font-medium", children: "AUTO-SAVE" }))] })] }), _jsxs("div", { className: "flex items-center gap-2 opacity-0 group-hover:opacity-100 transition-opacity", children: [_jsxs("button", { onClick: () => handleLoad(draft.draftId), className: "\r\n px-4 py-2 rounded-lg text-sm font-medium\r\n bg-[rgb(var(--color-primary))]\r\n text-white\r\n hover:opacity-90\r\n transition-opacity\r\n flex items-center gap-2\r\n ", children: [_jsx(Download, { className: "w-4 h-4" }), "Load"] }), _jsx("button", { onClick: () => handleDelete(draft.draftId), disabled: deletingId === draft.draftId, className: "\r\n p-2 rounded-lg\r\n text-[rgb(var(--fg-muted))]\r\n hover:text-red-600\r\n hover:bg-red-50\r\n transition-colors\r\n disabled:opacity-50\r\n ", title: "Delete draft", children: deletingId === draft.draftId ? (_jsx("div", { className: "w-4 h-4 rounded-full border-2 border-red-200 border-t-red-600 animate-spin" })) : (_jsx(Trash2, { className: "w-4 h-4" })) })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-x-6 gap-y-1 mt-2 text-sm", children: [preview.jobName && (_jsx("div", { className: "text-[rgb(var(--fg-muted))] truncate", children: preview.jobName }, "job")), preview.clientName && (_jsx("div", { className: "text-[rgb(var(--fg-muted))] truncate", children: preview.clientName }, "client")), preview.category && (_jsx("div", { className: "text-[rgb(var(--fg-muted))] truncate", children: preview.category }, "category")), preview.totalCost !== undefined && preview.totalCost > 0 && (_jsxs("div", { className: "font-medium text-[rgb(var(--color-primary))]", children: [currencySymbol, preview.totalCost.toLocaleString('en-IN')] }, "cost"))] }), progress > 0 && (_jsxs("div", { className: "mt-2", children: [_jsxs("div", { className: "flex items-center justify-between text-xs text-[rgb(var(--fg-muted))] mb-1", children: [_jsx("span", { children: "Progress" }), _jsxs("span", { className: "font-medium", children: [progress, "%"] })] }), _jsx("div", { className: "h-1.5 bg-[rgb(var(--bg-muted))] rounded-full overflow-hidden", children: _jsx("div", { className: "h-full bg-[rgb(var(--color-primary))] transition-all duration-500 rounded-full", style: { width: `${progress}%` } }) })] }))] }) }) }, draft.draftId));
|
|
163
163
|
}) })) }), _jsxs("div", { className: "px-6 py-4 border-t border-[rgb(var(--bd-default))] flex justify-between items-center bg-[rgb(var(--bg-muted)_/_0.3)]", children: [_jsx("div", { className: "text-sm text-[rgb(var(--fg-muted))]", children: filteredAndSortedDrafts.length > 0 ? (_jsxs(_Fragment, { children: ["Showing ", ((currentPage - 1) * PAGE_SIZE) + 1, "-", Math.min(currentPage * PAGE_SIZE, filteredAndSortedDrafts.length), " of ", filteredAndSortedDrafts.length, filteredAndSortedDrafts.length !== drafts.length && ` (filtered from ${drafts.length})`] })) : (`0 of ${drafts.length} drafts`) }), totalPages > 1 && (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("button", { onClick: () => setCurrentPage(p => Math.max(1, p - 1)), disabled: currentPage === 1, className: "p-1.5 rounded text-[rgb(var(--fg-muted))] hover:bg-[rgb(var(--bg-default))] disabled:opacity-30 disabled:cursor-not-allowed transition-colors", children: _jsx(ChevronLeft, { className: "w-4 h-4" }) }), _jsx("div", { className: "flex items-center gap-1", children: Array.from({ length: Math.min(5, totalPages) }, (_, i) => {
|
|
164
164
|
let pageNum;
|
|
165
165
|
if (totalPages <= 5) {
|
|
@@ -174,11 +174,11 @@ export function DraftModal({ open, onClose, drafts, onLoad, onDelete, isLoading
|
|
|
174
174
|
else {
|
|
175
175
|
pageNum = currentPage - 2 + i;
|
|
176
176
|
}
|
|
177
|
-
return (_jsx("button", { onClick: () => setCurrentPage(pageNum), className: `
|
|
178
|
-
w-8 h-8 rounded text-sm font-medium transition-colors
|
|
177
|
+
return (_jsx("button", { onClick: () => setCurrentPage(pageNum), className: `
|
|
178
|
+
w-8 h-8 rounded text-sm font-medium transition-colors
|
|
179
179
|
${currentPage === pageNum
|
|
180
180
|
? 'bg-[rgb(var(--color-primary))] text-white'
|
|
181
|
-
: 'text-[rgb(var(--fg-muted))] hover:bg-[rgb(var(--bg-default))]'}
|
|
181
|
+
: 'text-[rgb(var(--fg-muted))] hover:bg-[rgb(var(--bg-default))]'}
|
|
182
182
|
`, children: pageNum }, pageNum));
|
|
183
|
-
}) }), _jsx("button", { onClick: () => setCurrentPage(p => Math.min(totalPages, p + 1)), disabled: currentPage === totalPages, className: "p-1.5 rounded text-[rgb(var(--fg-muted))] hover:bg-[rgb(var(--bg-default))] disabled:opacity-30 disabled:cursor-not-allowed transition-colors", children: _jsx(ChevronRight, { className: "w-4 h-4" }) })] })), _jsx("button", { onClick: onClose, className: "\n px-5 py-2 rounded-lg text-sm font-medium\n bg-[rgb(var(--bg-default))]\n text-[rgb(var(--fg-default))]\n border border-[rgb(var(--bd-default))]\n hover:bg-[rgb(var(--bg-muted))]\n transition-colors\n ", children: "Close" })] })] }) }));
|
|
183
|
+
}) }), _jsx("button", { onClick: () => setCurrentPage(p => Math.min(totalPages, p + 1)), disabled: currentPage === totalPages, className: "p-1.5 rounded text-[rgb(var(--fg-muted))] hover:bg-[rgb(var(--bg-default))] disabled:opacity-30 disabled:cursor-not-allowed transition-colors", children: _jsx(ChevronRight, { className: "w-4 h-4" }) })] })), _jsx("button", { onClick: onClose, className: "\r\n px-5 py-2 rounded-lg text-sm font-medium\r\n bg-[rgb(var(--bg-default))]\r\n text-[rgb(var(--fg-default))]\r\n border border-[rgb(var(--bd-default))]\r\n hover:bg-[rgb(var(--bg-muted))]\r\n transition-colors\r\n ", children: "Close" })] })] }) }));
|
|
184
184
|
}
|
|
@@ -93,20 +93,14 @@ export function DatePicker({ id, 'aria-label': ariaLabel, value, onChange, mode
|
|
|
93
93
|
const normalizeDateToMidnight = (date) => {
|
|
94
94
|
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0, 0);
|
|
95
95
|
};
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return `${day} ${month}, ${year}`;
|
|
101
|
-
};
|
|
96
|
+
// "1 Sep 2026": built by hand rather than with toLocaleDateString, whose en-GB and en-IN
|
|
97
|
+
// locales spell September "Sept". parseDate reads it back with or without a comma.
|
|
98
|
+
const formatDateNoYear = (date) => `${date.getDate()} ${MONTHS[date.getMonth()].slice(0, 3)}`;
|
|
99
|
+
const formatDate = (date) => `${formatDateNoYear(date)} ${date.getFullYear()}`;
|
|
102
100
|
const formatDateShort = (date) => {
|
|
103
101
|
if (!date)
|
|
104
102
|
return '--';
|
|
105
|
-
return date
|
|
106
|
-
day: '2-digit',
|
|
107
|
-
month: 'short',
|
|
108
|
-
year: 'numeric'
|
|
109
|
-
});
|
|
103
|
+
return formatDate(date);
|
|
110
104
|
};
|
|
111
105
|
const formatDisplayValue = () => {
|
|
112
106
|
if (!value)
|
|
@@ -664,9 +658,9 @@ export function DatePicker({ id, 'aria-label': ariaLabel, value, onChange, mode
|
|
|
664
658
|
}
|
|
665
659
|
// Default popover mode
|
|
666
660
|
return (_jsxs("div", { className: cn('relative w-full', className), ref: containerRef, children: [label && (_jsx("label", { className: "block text-sm font-medium text-[rgb(var(--fg-muted))] mb-2", children: label })), _jsxs(Popover.Root, { open: !disabled && isOpen, onOpenChange: (open) => !disabled && setIsOpen(open), children: [_jsx(Popover.Trigger, { asChild: true, disabled: disabled, children: iconOnly ? (_jsxs("button", { type: "button", className: cn('relative flex items-center gap-1 text-[rgb(var(--fg-muted))] hover:text-[rgb(var(--fg-default))] transition-colors p-0.5 flex-shrink-0', (selectedRange.from || selectedRange.to) && 'text-[rgb(var(--color-primary))]', className), title: selectedRange.from || selectedRange.to
|
|
667
|
-
? `Date filter: ${selectedRange.from ? new Date(selectedRange.from)
|
|
661
|
+
? `Date filter: ${selectedRange.from ? formatDate(new Date(selectedRange.from)) : ''}${selectedRange.to ? ` – ${formatDate(new Date(selectedRange.to))}` : ''}`
|
|
668
662
|
: 'Select date range', children: [_jsx(CalendarDays, { className: "h-3.5 w-3.5 flex-shrink-0" }), _jsx("span", { className: "hidden sm:inline text-xs", children: selectedRange.from || selectedRange.to
|
|
669
|
-
? `${selectedRange.from ? new Date(selectedRange.from)
|
|
663
|
+
? `${selectedRange.from ? formatDateNoYear(new Date(selectedRange.from)) : ''}${selectedRange.to ? ` – ${formatDateNoYear(new Date(selectedRange.to))}` : ''}`
|
|
670
664
|
: 'Date' }), (selectedRange.from || selectedRange.to) && (_jsx("span", { className: "absolute -top-0.5 -right-0.5 h-1.5 w-1.5 rounded-full bg-[rgb(var(--color-primary))] ring-1 ring-[rgb(var(--bg-surface))]" }))] })) : (_jsx("div", { className: "relative", ref: inputWrapperRef, onClick: (e) => e.preventDefault(), children: _jsx(Input, { id: id, "aria-label": ariaLabel, value: inputValue, onChange: handleInputChange, onBlur: handleInputBlur, onKeyDown: handleInputKeyDown, placeholder: getPlaceholder(), disabled: disabled, error: error, className: cn('pl-2 pr-9 text-xs w-full', error && 'border-red-300'), onFocus: () => !disabled && setIsOpen(true), rightIcon: Calendar, onRightIconClick: () => !disabled && setIsOpen(!isOpen) }) })) }), _jsx(Popover.Portal, { container: typeof document !== 'undefined' ? document.body : undefined, children: _jsxs(Popover.Content, { ref: calendarRef, className: cn("z-[300] bg-[rgb(var(--bg-surface))] rounded-xl shadow-2xl border border-[rgb(var(--bd-default))] backdrop-blur-sm", "data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95", "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95", "data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2", mode === 'range' ? 'w-[95vw] sm:w-auto sm:min-w-[580px] p-2' : 'w-[95vw] sm:w-auto sm:min-w-[280px] p-3', 'max-w-[calc(100vw-2rem)]'), side: "bottom", align: popoverAlign ?? (mode === 'range' ? 'end' : 'start'), sideOffset: 8, avoidCollisions: true, collisionPadding: 16, sticky: "always", onOpenAutoFocus: (e) => e.preventDefault(), onKeyDown: handleCalendarKeyDown, tabIndex: -1, onInteractOutside: (e) => {
|
|
671
665
|
// Prevent closing when interacting with nested Radix portals (like Dropdowns)
|
|
672
666
|
const target = e.target;
|
|
@@ -40,7 +40,7 @@ export function DateTimePicker({ value, onChange, placeholder = 'Select date & t
|
|
|
40
40
|
return returnFormat === 'string' ? date.toISOString() : date;
|
|
41
41
|
};
|
|
42
42
|
const formatDisplayValue = (date) => {
|
|
43
|
-
const day = date.getDate()
|
|
43
|
+
const day = date.getDate();
|
|
44
44
|
const month = MONTHS[date.getMonth()].slice(0, 3);
|
|
45
45
|
const year = date.getFullYear();
|
|
46
46
|
let timeStr = '';
|
|
@@ -54,7 +54,7 @@ export function DateTimePicker({ value, onChange, placeholder = 'Select date & t
|
|
|
54
54
|
else {
|
|
55
55
|
timeStr = `${hrs.toString().padStart(2, '0')}:${mins.toString().padStart(2, '0')}`;
|
|
56
56
|
}
|
|
57
|
-
return `${day} ${month}
|
|
57
|
+
return `${day} ${month} ${year} ${timeStr}`;
|
|
58
58
|
};
|
|
59
59
|
const handleDateClick = (date) => {
|
|
60
60
|
setSelectedDate(date);
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { cn } from "../../lib/utils.js";
|
|
4
4
|
import { useTranslation } from "../../hooks/useTranslation.js";
|
|
5
|
+
import { formatNumber } from '../../lib/number-format.js';
|
|
5
6
|
export function Footer({ kpis, actions, iconButtons, leftContent, className, sticky = false, compact = false, padding, variant = 'page', gradient = false }) {
|
|
6
7
|
if (!kpis && !actions && !leftContent && !iconButtons) {
|
|
7
8
|
return null;
|
|
@@ -41,7 +42,7 @@ export function Footer({ kpis, actions, iconButtons, leftContent, className, sti
|
|
|
41
42
|
padding === 'normal' ? "py-3 sm:py-4" :
|
|
42
43
|
compact ? "py-1" :
|
|
43
44
|
isModal ? "py-1.5 sm:py-2" : "py-3 sm:py-4"), children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2 sm:gap-4 w-full sm:w-auto", children: [leftContent, kpis && kpis.length > 0 && (_jsx("div", { className: "flex flex-wrap items-center gap-3 sm:gap-6 md:gap-8", children: kpis.map((kpi) => (_jsxs("div", { className: cn("flex items-center gap-1.5 sm:gap-2 text-xs sm:text-sm", kpi.onClick && "cursor-pointer hover:text-[rgb(var(--color-primary-hover))] transition-colors"), onClick: kpi.onClick, children: [_jsxs("span", { className: "text-[rgb(var(--fg-muted))] whitespace-nowrap", children: [kpi.label, ":"] }), _jsx("span", { className: "font-medium text-[rgb(var(--fg-default))]", children: typeof kpi.value === 'number'
|
|
44
|
-
? kpi.value
|
|
45
|
+
? formatNumber(kpi.value)
|
|
45
46
|
: kpi.value }), kpi.trend && (_jsxs("span", { className: cn("text-xs font-medium", kpi.trend.direction === 'up'
|
|
46
47
|
? "text-[rgb(var(--color-success))]"
|
|
47
48
|
: "text-[rgb(var(--color-error))]"), children: [kpi.trend.direction === 'up' ? '↑' : '↓', Math.abs(kpi.trend.value), "%"] }))] }, kpi.id))) }))] }), iconButtons && iconButtons.length > 0 && (_jsx("div", { className: "flex flex-wrap items-center gap-1.5 sm:gap-2", children: iconButtons.map((button) => {
|
|
@@ -107,7 +107,10 @@ export function SidePanel({ title, icon: Icon, categories, activeItemId, onItemS
|
|
|
107
107
|
return _jsx(ItemIcon, { className: "w-4 h-4 shrink-0 opacity-50" });
|
|
108
108
|
})(), _jsx("span", { className: "truncate flex-1", children: item.label }), item.badge !== undefined && item.badge !== 0 && (_jsx("span", { className: cn("text-xs px-1.5 py-0.5 rounded-full shrink-0 tabular-nums", activeItemId === item.id
|
|
109
109
|
? "bg-[rgb(var(--color-primary))] text-white"
|
|
110
|
-
: "bg-[rgb(var(--bg-subtle))] text-[rgb(var(--fg-muted))]"), children: item.badge }))] }), itemContextMenu && (
|
|
110
|
+
: "bg-[rgb(var(--bg-subtle))] text-[rgb(var(--fg-muted))]"), children: item.badge }))] }), itemContextMenu && (
|
|
111
|
+
// Named for screen readers, and shown on keyboard focus as well as hover:
|
|
112
|
+
// it was an unlabelled button nobody tabbing could see.
|
|
113
|
+
_jsx("button", { type: "button", onClick: (e) => handleMenuToggle(e, item.id), "aria-label": `More actions for ${item.label}`, "aria-haspopup": "menu", "aria-expanded": menuOpen === item.id, className: "absolute right-1.5 top-1/2 -translate-y-1/2 w-6 h-6 rounded-md text-[rgb(var(--fg-muted))]/50 flex items-center justify-center hover:text-[rgb(var(--fg-default))] hover:bg-[rgb(var(--bg-subtle))] transition-all opacity-0 group-hover:opacity-100 focus-visible:opacity-100", children: _jsx(MoreVertical, { className: "w-3.5 h-3.5" }) }))] }, item.id))) }))] }, category.id))))] })) }), footer && !isCollapsed && (_jsx("div", { className: cn("shrink-0 border-t border-[rgb(var(--bd-default))] transition-opacity duration-200", isAnimating ? "opacity-0" : "opacity-100"), children: footer }))] }), menuOpen !== null && itemContextMenu && (_jsx("div", { ref: menuRef, className: "fixed bg-[rgb(var(--bg-surface))] border border-[rgb(var(--bd-default))] rounded-lg min-w-[170px] shadow-lg overflow-hidden z-[9999]", style: {
|
|
111
114
|
left: `${menuPosition.left}px`,
|
|
112
115
|
top: `${menuPosition.top}px`,
|
|
113
116
|
transform: 'translateY(-50%)'
|
|
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { TrendingUp, TrendingDown } from "lucide-react";
|
|
4
4
|
import { cn } from "../../../lib/utils.js";
|
|
5
5
|
import { Card } from "./card.js";
|
|
6
|
+
import { formatNumber } from '../../../lib/number-format.js';
|
|
6
7
|
const iconVariants = {
|
|
7
8
|
default: "bg-primary text-white",
|
|
8
9
|
accent: "bg-accent text-white",
|
|
@@ -12,7 +13,7 @@ const iconVariants = {
|
|
|
12
13
|
export function StatsCard({ title, value, icon: Icon, trend, description, className, variant = 'default' }) {
|
|
13
14
|
const TrendIcon = trend?.direction === 'up' ? TrendingUp : TrendingDown;
|
|
14
15
|
const trendColor = trend?.direction === 'up' ? 'text-success' : 'text-error';
|
|
15
|
-
return (_jsx(Card, { className: cn("p-6 min-h-[140px] w-full", className), children: _jsxs("div", { className: "flex items-start gap-4", children: [Icon && (_jsx("div", { className: cn("flex-shrink-0 p-3 rounded-lg", iconVariants[variant]), children: _jsx(Icon, { className: "h-6 w-6" }) })), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsxs("div", { className: "flex items-baseline flex-wrap gap-2", children: [_jsx("p", { className: "text-2xl font-semibold text-fg-default truncate", children: typeof value === 'number' ? value
|
|
16
|
+
return (_jsx(Card, { className: cn("p-6 min-h-[140px] w-full", className), children: _jsxs("div", { className: "flex items-start gap-4", children: [Icon && (_jsx("div", { className: cn("flex-shrink-0 p-3 rounded-lg", iconVariants[variant]), children: _jsx(Icon, { className: "h-6 w-6" }) })), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsxs("div", { className: "flex items-baseline flex-wrap gap-2", children: [_jsx("p", { className: "text-2xl font-semibold text-fg-default truncate", children: typeof value === 'number' ? formatNumber(value) : value }), trend && (_jsxs("div", { className: cn("flex items-center text-sm font-semibold", trendColor), children: [_jsx(TrendIcon, { className: "h-4 w-4 flex-shrink-0" }), _jsxs("span", { className: "ml-1", children: [Math.abs(trend.value), "%", trend.label && ` ${trend.label}`] })] }))] }), _jsx("p", { className: "text-sm font-medium text-fg-muted mt-2 line-clamp-2", children: title }), description && (_jsx("p", { className: "text-sm text-fg-subtle mt-1 line-clamp-1", children: description }))] })] }) }));
|
|
16
17
|
}
|
|
17
18
|
export function StatsGrid({ children, columns = 4, className }) {
|
|
18
19
|
const gridCols = {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How every number in the library is written. Indian grouping by default (20,00,000, not
|
|
3
|
+
* 2,000,000): the products built on this library keep Indian books, and a bare `toLocaleString()`
|
|
4
|
+
* follows whatever locale the browser happens to be set to, so the same figure read differently on
|
|
5
|
+
* two machines.
|
|
6
|
+
*
|
|
7
|
+
* A product that needs another convention calls `setNumberLocale()` once at start-up.
|
|
8
|
+
*/
|
|
9
|
+
export declare function setNumberLocale(locale: string): void;
|
|
10
|
+
export declare function getNumberLocale(): string;
|
|
11
|
+
/** A full number with grouping, up to two decimals. Empty for null, undefined or NaN. */
|
|
12
|
+
export declare function formatNumber(value: number | string | null | undefined, maximumFractionDigits?: number): string;
|
|
13
|
+
/**
|
|
14
|
+
* A short number for chart axes, where a full figure runs into its neighbour: 12,500 · 20 L ·
|
|
15
|
+
* 1.4 Cr in Indian grouping; 12.5K · 2M · 1.4B otherwise. Below a lakh (or a thousand) it is the
|
|
16
|
+
* full number.
|
|
17
|
+
*/
|
|
18
|
+
export declare function formatCompactNumber(value: number | string | null | undefined): string;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
/**
|
|
3
|
+
* How every number in the library is written. Indian grouping by default (20,00,000, not
|
|
4
|
+
* 2,000,000): the products built on this library keep Indian books, and a bare `toLocaleString()`
|
|
5
|
+
* follows whatever locale the browser happens to be set to, so the same figure read differently on
|
|
6
|
+
* two machines.
|
|
7
|
+
*
|
|
8
|
+
* A product that needs another convention calls `setNumberLocale()` once at start-up.
|
|
9
|
+
*/
|
|
10
|
+
let numberLocale = 'en-IN';
|
|
11
|
+
export function setNumberLocale(locale) {
|
|
12
|
+
numberLocale = locale;
|
|
13
|
+
}
|
|
14
|
+
export function getNumberLocale() {
|
|
15
|
+
return numberLocale;
|
|
16
|
+
}
|
|
17
|
+
/** A full number with grouping, up to two decimals. Empty for null, undefined or NaN. */
|
|
18
|
+
export function formatNumber(value, maximumFractionDigits = 2) {
|
|
19
|
+
if (value == null || value === '')
|
|
20
|
+
return '';
|
|
21
|
+
const n = typeof value === 'number' ? value : Number(value);
|
|
22
|
+
if (!Number.isFinite(n))
|
|
23
|
+
return typeof value === 'string' ? value : '';
|
|
24
|
+
return n.toLocaleString(numberLocale, { maximumFractionDigits });
|
|
25
|
+
}
|
|
26
|
+
const trim = (n) => n.toFixed(n >= 100 ? 0 : n >= 10 ? 1 : 2).replace(/\.?0+$/, '');
|
|
27
|
+
/**
|
|
28
|
+
* A short number for chart axes, where a full figure runs into its neighbour: 12,500 · 20 L ·
|
|
29
|
+
* 1.4 Cr in Indian grouping; 12.5K · 2M · 1.4B otherwise. Below a lakh (or a thousand) it is the
|
|
30
|
+
* full number.
|
|
31
|
+
*/
|
|
32
|
+
export function formatCompactNumber(value) {
|
|
33
|
+
if (value == null || value === '')
|
|
34
|
+
return '';
|
|
35
|
+
const n = typeof value === 'number' ? value : Number(value);
|
|
36
|
+
if (!Number.isFinite(n))
|
|
37
|
+
return String(value);
|
|
38
|
+
const sign = n < 0 ? '-' : '';
|
|
39
|
+
const a = Math.abs(n);
|
|
40
|
+
if (numberLocale === 'en-IN') {
|
|
41
|
+
if (a >= 1e7)
|
|
42
|
+
return `${sign}${trim(a / 1e7)} Cr`;
|
|
43
|
+
if (a >= 1e5)
|
|
44
|
+
return `${sign}${trim(a / 1e5)} L`;
|
|
45
|
+
return formatNumber(n, 0);
|
|
46
|
+
}
|
|
47
|
+
if (a >= 1e9)
|
|
48
|
+
return `${sign}${trim(a / 1e9)}B`;
|
|
49
|
+
if (a >= 1e6)
|
|
50
|
+
return `${sign}${trim(a / 1e6)}M`;
|
|
51
|
+
if (a >= 1e3)
|
|
52
|
+
return `${sign}${trim(a / 1e3)}K`;
|
|
53
|
+
return formatNumber(n, 0);
|
|
54
|
+
}
|
package/dist/lib/utils.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { clsx } from "clsx";
|
|
3
3
|
import { twMerge } from "tailwind-merge";
|
|
4
|
+
import { getNumberLocale } from './number-format.js';
|
|
4
5
|
export function cn(...inputs) {
|
|
5
6
|
return twMerge(clsx(inputs));
|
|
6
7
|
}
|
|
7
|
-
export function formatCurrency(amount, currency = '
|
|
8
|
-
return new Intl.NumberFormat(
|
|
8
|
+
export function formatCurrency(amount, currency = 'INR') {
|
|
9
|
+
return new Intl.NumberFormat(getNumberLocale(), {
|
|
9
10
|
style: 'currency',
|
|
10
11
|
currency,
|
|
11
12
|
}).format(amount);
|
|
@@ -18,7 +19,7 @@ export function formatDate(date) {
|
|
|
18
19
|
}).format(new Date(date));
|
|
19
20
|
}
|
|
20
21
|
export function formatNumber(num, decimals = 0) {
|
|
21
|
-
return new Intl.NumberFormat(
|
|
22
|
+
return new Intl.NumberFormat(getNumberLocale(), {
|
|
22
23
|
minimumFractionDigits: decimals,
|
|
23
24
|
maximumFractionDigits: decimals,
|
|
24
25
|
}).format(num);
|
package/dist/styles/index.css
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
@tailwind base;
|
|
2
|
-
@tailwind components;
|
|
3
|
-
@tailwind utilities;
|
|
4
|
-
|
|
5
|
-
@import './tokens.css';
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
|
4
|
+
|
|
5
|
+
@import './tokens.css';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "indas-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Indas UI — React component library for business apps: Tailwind design tokens, Radix primitives, Lucide icons, DataGrid, dashboards and charts.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Indas Analytics Pvt Ltd",
|
|
@@ -121,8 +121,8 @@
|
|
|
121
121
|
"exceljs": "^4.4.0",
|
|
122
122
|
"file-saver": "^2.0.5",
|
|
123
123
|
"framer-motion": "^12.23.12",
|
|
124
|
-
"jspdf": "^
|
|
125
|
-
"jspdf-autotable": "^5.0.
|
|
124
|
+
"jspdf": "^4.2.1",
|
|
125
|
+
"jspdf-autotable": "^5.0.8",
|
|
126
126
|
"ldrs": "^1.1.9",
|
|
127
127
|
"lucide-react": "^0.562.0",
|
|
128
128
|
"next-auth": "^4.24.7",
|
|
@@ -133,7 +133,7 @@
|
|
|
133
133
|
"rehype-highlight": "^7.0.2",
|
|
134
134
|
"rehype-raw": "^7.0.0",
|
|
135
135
|
"remark-gfm": "^4.0.1",
|
|
136
|
-
"svg2pdf.js": "^2.
|
|
136
|
+
"svg2pdf.js": "^2.8.1",
|
|
137
137
|
"tailwind-merge": "^3.3.1"
|
|
138
138
|
},
|
|
139
139
|
"devDependencies": {
|