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.
Files changed (133) hide show
  1. package/dist/auth.d.ts +1 -0
  2. package/dist/auth.js +5 -0
  3. package/dist/cjs/auth.js +10 -0
  4. package/dist/cjs/components/chat/ai-messages.js +3 -2
  5. package/dist/cjs/components/chat/chat.js +4 -3
  6. package/dist/cjs/components/dashboard/FilterBar.js +31 -8
  7. package/dist/cjs/components/dashboard/charts/Area.js +6 -4
  8. package/dist/cjs/components/dashboard/charts/Bar.js +6 -4
  9. package/dist/cjs/components/dashboard/charts/FlowMap.js +2 -1
  10. package/dist/cjs/components/dashboard/charts/Funnel.js +2 -1
  11. package/dist/cjs/components/dashboard/charts/GeoMap.js +2 -1
  12. package/dist/cjs/components/dashboard/charts/Line.js +5 -3
  13. package/dist/cjs/components/dashboard/charts/Pareto.js +2 -1
  14. package/dist/cjs/components/dashboard/charts/Sankey.js +4 -3
  15. package/dist/cjs/components/dashboard/charts/Sparkline.js +2 -1
  16. package/dist/cjs/components/dashboard/charts/Treemap.js +2 -1
  17. package/dist/cjs/components/dashboard/charts/index.js +7 -1
  18. package/dist/cjs/components/dashboard/kpi/KpiBreakdown.js +3 -2
  19. package/dist/cjs/components/dashboard/kpi/KpiComparison.js +2 -1
  20. package/dist/cjs/components/dashboard/kpi/KpiQuickStats.js +2 -1
  21. package/dist/cjs/components/dashboard/kpi/KpiRating.js +3 -2
  22. package/dist/cjs/components/dashboard/time-range.js +71 -27
  23. package/dist/cjs/components/datagrid/columns/ColumnContextMenu.js +2 -1
  24. package/dist/cjs/components/datagrid/controls/menu/view/CardView.js +2 -1
  25. package/dist/cjs/components/datagrid/controls/menu/view/ChartView.js +2 -1
  26. package/dist/cjs/components/datagrid/utils/grid-helpers.js +6 -5
  27. package/dist/cjs/components/layout/footer.js +2 -1
  28. package/dist/cjs/components/layout/side-panel.js +4 -1
  29. package/dist/cjs/components/layout/sidebar.js +17 -2
  30. package/dist/cjs/components/messaging/messaging-panel-content.js +2 -2
  31. package/dist/cjs/components/ui/display/stats-card.js +2 -1
  32. package/dist/cjs/components/ui/navigation/tabs.js +4 -1
  33. package/dist/cjs/contexts/EmailContext.js +3 -2
  34. package/dist/cjs/contexts/MessagingContext.js +2 -2
  35. package/dist/cjs/contexts/NavigationContext.js +17 -2
  36. package/dist/cjs/contexts/NotificationContext.js +2 -2
  37. package/dist/cjs/contexts/SessionAdapterContext.js +8 -0
  38. package/dist/cjs/hooks/useApproval.js +2 -2
  39. package/dist/cjs/hooks/useBusinessAudit.js +2 -2
  40. package/dist/cjs/hooks/useDraft.js +2 -2
  41. package/dist/cjs/lib/api/utility/menu.js +8 -7
  42. package/dist/cjs/lib/number-format.js +60 -0
  43. package/dist/cjs/lib/utils.js +4 -3
  44. package/dist/cjs/providers.js +3 -3
  45. package/dist/components/auth/index.js +0 -1
  46. package/dist/components/chat/ai-messages.js +3 -2
  47. package/dist/components/chat/chat.js +4 -3
  48. package/dist/components/chat/index.js +0 -1
  49. package/dist/components/dashboard/FilterBar.d.ts +5 -1
  50. package/dist/components/dashboard/FilterBar.js +31 -8
  51. package/dist/components/dashboard/charts/Area.d.ts +5 -1
  52. package/dist/components/dashboard/charts/Area.js +6 -4
  53. package/dist/components/dashboard/charts/Bar.d.ts +5 -1
  54. package/dist/components/dashboard/charts/Bar.js +6 -4
  55. package/dist/components/dashboard/charts/FlowMap.js +2 -1
  56. package/dist/components/dashboard/charts/Funnel.js +2 -1
  57. package/dist/components/dashboard/charts/GeoMap.js +2 -1
  58. package/dist/components/dashboard/charts/Line.d.ts +5 -1
  59. package/dist/components/dashboard/charts/Line.js +5 -3
  60. package/dist/components/dashboard/charts/Pareto.js +2 -1
  61. package/dist/components/dashboard/charts/Sankey.js +4 -3
  62. package/dist/components/dashboard/charts/Sparkline.js +2 -1
  63. package/dist/components/dashboard/charts/Treemap.js +2 -1
  64. package/dist/components/dashboard/charts/index.d.ts +1 -0
  65. package/dist/components/dashboard/charts/index.js +2 -1
  66. package/dist/components/dashboard/index.js +0 -1
  67. package/dist/components/dashboard/kpi/KpiBreakdown.js +3 -2
  68. package/dist/components/dashboard/kpi/KpiComparison.js +2 -1
  69. package/dist/components/dashboard/kpi/KpiQuickStats.js +2 -1
  70. package/dist/components/dashboard/kpi/KpiRating.js +3 -2
  71. package/dist/components/dashboard/kpi/index.js +0 -1
  72. package/dist/components/dashboard/time-range.d.ts +12 -1
  73. package/dist/components/dashboard/time-range.js +71 -27
  74. package/dist/components/datagrid/cells/index.js +0 -1
  75. package/dist/components/datagrid/columns/ColumnContextMenu.js +2 -1
  76. package/dist/components/datagrid/controls/menu/grouping/index.js +0 -1
  77. package/dist/components/datagrid/controls/menu/index.js +0 -1
  78. package/dist/components/datagrid/controls/menu/view/CardView.js +2 -1
  79. package/dist/components/datagrid/controls/menu/view/ChartView.js +2 -1
  80. package/dist/components/datagrid/index.js +0 -1
  81. package/dist/components/datagrid/utils/grid-helpers.d.ts +1 -1
  82. package/dist/components/datagrid/utils/grid-helpers.js +3 -2
  83. package/dist/components/forms/date-picker/index.js +0 -1
  84. package/dist/components/forms/dropdown/index.js +0 -1
  85. package/dist/components/forms/file-attachment/index.js +0 -1
  86. package/dist/components/forms/index.js +0 -1
  87. package/dist/components/forms/remark-input/index.js +0 -1
  88. package/dist/components/index.js +0 -1
  89. package/dist/components/layout/footer.js +2 -1
  90. package/dist/components/layout/index.js +0 -1
  91. package/dist/components/layout/side-panel.js +4 -1
  92. package/dist/components/layout/sidebar.js +17 -2
  93. package/dist/components/master/editable-cells/index.js +0 -1
  94. package/dist/components/master/index.js +0 -1
  95. package/dist/components/messaging/messaging-panel-content.js +2 -2
  96. package/dist/components/modals/index.js +0 -1
  97. package/dist/components/ui/display/stats-card.js +2 -1
  98. package/dist/components/ui/feedback/badge.d.ts +1 -1
  99. package/dist/components/ui/index.js +0 -1
  100. package/dist/components/ui/navigation/tabs.js +4 -1
  101. package/dist/contexts/EmailContext.js +3 -2
  102. package/dist/contexts/MessagingContext.js +2 -2
  103. package/dist/contexts/NavigationContext.js +17 -2
  104. package/dist/contexts/NotificationContext.js +2 -2
  105. package/dist/contexts/SessionAdapterContext.d.ts +5 -0
  106. package/dist/contexts/SessionAdapterContext.js +7 -0
  107. package/dist/hooks/index.js +0 -1
  108. package/dist/hooks/useApproval.js +2 -2
  109. package/dist/hooks/useBusinessAudit.js +2 -2
  110. package/dist/hooks/useDraft.js +2 -2
  111. package/dist/index.js +0 -1
  112. package/dist/lib/api/activity/audit/index.js +0 -1
  113. package/dist/lib/api/activity/email/index.js +0 -1
  114. package/dist/lib/api/activity/index.js +0 -1
  115. package/dist/lib/api/activity/notifications/index.js +0 -1
  116. package/dist/lib/api/activity/remarks/index.js +0 -1
  117. package/dist/lib/api/approval/index.js +0 -1
  118. package/dist/lib/api/auth/index.js +0 -1
  119. package/dist/lib/api/core/index.js +0 -1
  120. package/dist/lib/api/draft/index.js +0 -1
  121. package/dist/lib/api/enquiry/index.js +0 -1
  122. package/dist/lib/api/estimation/index.js +0 -1
  123. package/dist/lib/api/index.js +0 -1
  124. package/dist/lib/api/master/index.js +0 -1
  125. package/dist/lib/api/utility/index.js +0 -1
  126. package/dist/lib/api/utility/menu.d.ts +7 -3
  127. package/dist/lib/api/utility/menu.js +8 -7
  128. package/dist/lib/number-format.d.ts +18 -0
  129. package/dist/lib/number-format.js +54 -0
  130. package/dist/lib/utils.js +4 -3
  131. package/dist/providers.d.ts +0 -1
  132. package/dist/providers.js +2 -2
  133. package/package.json +23 -8
@@ -23,13 +23,13 @@
23
23
  Object.defineProperty(exports, "__esModule", { value: true });
24
24
  exports.useApproval = useApproval;
25
25
  const react_1 = require("react");
26
- const react_2 = require("next-auth/react");
26
+ const SessionAdapterContext_1 = require("../contexts/SessionAdapterContext.js");
27
27
  const approval_1 = require("../lib/api/approval/index.js");
28
28
  /**
29
29
  * useApproval Hook
30
30
  */
31
31
  function useApproval() {
32
- const { data: session } = (0, react_2.useSession)();
32
+ const session = (0, SessionAdapterContext_1.useOptionalSessionAdapter)()?.data ?? null;
33
33
  // State
34
34
  const [pendingApprovals, setPendingApprovals] = (0, react_1.useState)([]);
35
35
  const [isLoading, setIsLoading] = (0, react_1.useState)(false);
@@ -17,10 +17,10 @@
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
18
  exports.useBusinessAudit = useBusinessAudit;
19
19
  const react_1 = require("react");
20
- const react_2 = require("next-auth/react");
20
+ const SessionAdapterContext_1 = require("../contexts/SessionAdapterContext.js");
21
21
  const audit_1 = require("../lib/api/activity/audit/index.js");
22
22
  function useBusinessAudit() {
23
- const { data: session } = (0, react_2.useSession)();
23
+ const session = (0, SessionAdapterContext_1.useOptionalSessionAdapter)()?.data ?? null;
24
24
  const [isLogging, setIsLogging] = (0, react_1.useState)(false);
25
25
  const logAction = (0, react_1.useCallback)(async (action, module, details) => {
26
26
  if (!session)
@@ -22,13 +22,13 @@
22
22
  Object.defineProperty(exports, "__esModule", { value: true });
23
23
  exports.useDraft = useDraft;
24
24
  const react_1 = require("react");
25
- const react_2 = require("next-auth/react");
25
+ const SessionAdapterContext_1 = require("../contexts/SessionAdapterContext.js");
26
26
  const draft_1 = require("../lib/api/draft/index.js");
27
27
  /**
28
28
  * useDraft Hook - Milestone Based
29
29
  */
30
30
  function useDraft(module, currentState) {
31
- const { data: session } = (0, react_2.useSession)();
31
+ const session = (0, SessionAdapterContext_1.useOptionalSessionAdapter)()?.data ?? null;
32
32
  // State
33
33
  const [drafts, setDrafts] = (0, react_1.useState)([]);
34
34
  const [isLoadingDrafts, setIsLoadingDrafts] = (0, react_1.useState)(false);
@@ -10,15 +10,16 @@ exports.fetchDynamicMenuModules = fetchDynamicMenuModules;
10
10
  exports.groupAndSortModules = groupAndSortModules;
11
11
  exports.getDynamicNavigation = getDynamicNavigation;
12
12
  const client_1 = __importDefault(require("../core/client.js"));
13
- const react_1 = require("next-auth/react");
14
13
  /**
15
14
  * Fetch dynamic menu modules from external API
16
- * Now uses APIClient for proper authentication with company credentials
15
+ * Now uses APIClient for proper authentication with company credentials.
16
+ *
17
+ * The caller passes the session (from useSessionAdapter, which AuthSessionProvider fills from
18
+ * next-auth) rather than this module calling next-auth's getSession: importing next-auth here made
19
+ * every app installing indas-ui install next-auth too, since nearly every entry reaches this file.
17
20
  */
18
- async function fetchDynamicMenuModules(companyId, userId) {
21
+ async function fetchDynamicMenuModules(companyId, userId, session) {
19
22
  try {
20
- // Get session to pass to APIClient for company credentials
21
- const session = await (0, react_1.getSession)();
22
23
  if (!session) {
23
24
  return [];
24
25
  }
@@ -127,10 +128,10 @@ function groupAndSortModules(modules) {
127
128
  /**
128
129
  * Get dynamic navigation structure for sidebar
129
130
  */
130
- async function getDynamicNavigation(companyId, userId) {
131
+ async function getDynamicNavigation(companyId, userId, session) {
131
132
  if (!companyId || !userId) {
132
133
  return [];
133
134
  }
134
- const modules = await fetchDynamicMenuModules(companyId, userId);
135
+ const modules = await fetchDynamicMenuModules(companyId, userId, session);
135
136
  return groupAndSortModules(modules);
136
137
  }
@@ -0,0 +1,60 @@
1
+ 'use client';
2
+ "use strict";
3
+ /**
4
+ * How every number in the library is written. Indian grouping by default (20,00,000, not
5
+ * 2,000,000): the products built on this library keep Indian books, and a bare `toLocaleString()`
6
+ * follows whatever locale the browser happens to be set to, so the same figure read differently on
7
+ * two machines.
8
+ *
9
+ * A product that needs another convention calls `setNumberLocale()` once at start-up.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.setNumberLocale = setNumberLocale;
13
+ exports.getNumberLocale = getNumberLocale;
14
+ exports.formatNumber = formatNumber;
15
+ exports.formatCompactNumber = formatCompactNumber;
16
+ let numberLocale = 'en-IN';
17
+ function setNumberLocale(locale) {
18
+ numberLocale = locale;
19
+ }
20
+ function getNumberLocale() {
21
+ return numberLocale;
22
+ }
23
+ /** A full number with grouping, up to two decimals. Empty for null, undefined or NaN. */
24
+ function formatNumber(value, maximumFractionDigits = 2) {
25
+ if (value == null || value === '')
26
+ return '';
27
+ const n = typeof value === 'number' ? value : Number(value);
28
+ if (!Number.isFinite(n))
29
+ return typeof value === 'string' ? value : '';
30
+ return n.toLocaleString(numberLocale, { maximumFractionDigits });
31
+ }
32
+ const trim = (n) => n.toFixed(n >= 100 ? 0 : n >= 10 ? 1 : 2).replace(/\.?0+$/, '');
33
+ /**
34
+ * A short number for chart axes, where a full figure runs into its neighbour: 12,500 · 20 L ·
35
+ * 1.4 Cr in Indian grouping; 12.5K · 2M · 1.4B otherwise. Below a lakh (or a thousand) it is the
36
+ * full number.
37
+ */
38
+ function formatCompactNumber(value) {
39
+ if (value == null || value === '')
40
+ return '';
41
+ const n = typeof value === 'number' ? value : Number(value);
42
+ if (!Number.isFinite(n))
43
+ return String(value);
44
+ const sign = n < 0 ? '-' : '';
45
+ const a = Math.abs(n);
46
+ if (numberLocale === 'en-IN') {
47
+ if (a >= 1e7)
48
+ return `${sign}${trim(a / 1e7)} Cr`;
49
+ if (a >= 1e5)
50
+ return `${sign}${trim(a / 1e5)} L`;
51
+ return formatNumber(n, 0);
52
+ }
53
+ if (a >= 1e9)
54
+ return `${sign}${trim(a / 1e9)}B`;
55
+ if (a >= 1e6)
56
+ return `${sign}${trim(a / 1e6)}M`;
57
+ if (a >= 1e3)
58
+ return `${sign}${trim(a / 1e3)}K`;
59
+ return formatNumber(n, 0);
60
+ }
@@ -11,11 +11,12 @@ exports.getModuleRoutePath = getModuleRoutePath;
11
11
  exports.naturalCompare = naturalCompare;
12
12
  const clsx_1 = require("clsx");
13
13
  const tailwind_merge_1 = require("tailwind-merge");
14
+ const number_format_1 = require("./number-format.js");
14
15
  function cn(...inputs) {
15
16
  return (0, tailwind_merge_1.twMerge)((0, clsx_1.clsx)(inputs));
16
17
  }
17
- function formatCurrency(amount, currency = 'USD') {
18
- return new Intl.NumberFormat('en-US', {
18
+ function formatCurrency(amount, currency = 'INR') {
19
+ return new Intl.NumberFormat((0, number_format_1.getNumberLocale)(), {
19
20
  style: 'currency',
20
21
  currency,
21
22
  }).format(amount);
@@ -28,7 +29,7 @@ function formatDate(date) {
28
29
  }).format(new Date(date));
29
30
  }
30
31
  function formatNumber(num, decimals = 0) {
31
- return new Intl.NumberFormat('en-US', {
32
+ return new Intl.NumberFormat((0, number_format_1.getNumberLocale)(), {
32
33
  minimumFractionDigits: decimals,
33
34
  maximumFractionDigits: decimals,
34
35
  }).format(num);
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  "use strict";
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.cn = exports.QueryProvider = exports.useAPI = exports.APIProvider = exports.AuthSessionProvider = exports.useSessionAdapter = exports.SessionAdapterProvider = exports.useCurrency = exports.CurrencyProvider = exports.usePageTitle = exports.PageTitleProvider = exports.useSearchPreferences = exports.SearchPreferencesProvider = exports.useDevice = exports.DeviceProvider = exports.useLanguage = exports.LanguageProvider = exports.useAppConfig = exports.AppConfigProvider = exports.useQuickAlert = exports.useGlobalAlert = exports.GlobalAlertProvider = exports.ThemeContext = exports.ThemeScript = exports.ThemeProvider = void 0;
4
+ exports.cn = exports.QueryProvider = exports.useAPI = exports.APIProvider = exports.useSessionAdapter = exports.SessionAdapterProvider = exports.useCurrency = exports.CurrencyProvider = exports.usePageTitle = exports.PageTitleProvider = exports.useSearchPreferences = exports.SearchPreferencesProvider = exports.useDevice = exports.DeviceProvider = exports.useLanguage = exports.LanguageProvider = exports.useAppConfig = exports.AppConfigProvider = exports.useQuickAlert = exports.useGlobalAlert = exports.GlobalAlertProvider = exports.ThemeContext = exports.ThemeScript = exports.ThemeProvider = void 0;
5
5
  // Providers entry: `import { ThemeProvider } from 'indas-ui/providers'`
6
6
  var ThemeContext_1 = require("./contexts/ThemeContext.js");
7
7
  Object.defineProperty(exports, "ThemeProvider", { enumerable: true, get: function () { return ThemeContext_1.ThemeProvider; } });
@@ -32,8 +32,8 @@ Object.defineProperty(exports, "useCurrency", { enumerable: true, get: function
32
32
  var SessionAdapterContext_1 = require("./contexts/SessionAdapterContext.js");
33
33
  Object.defineProperty(exports, "SessionAdapterProvider", { enumerable: true, get: function () { return SessionAdapterContext_1.SessionAdapterProvider; } });
34
34
  Object.defineProperty(exports, "useSessionAdapter", { enumerable: true, get: function () { return SessionAdapterContext_1.useSessionAdapter; } });
35
- var session_provider_1 = require("./components/providers/session-provider.js");
36
- Object.defineProperty(exports, "AuthSessionProvider", { enumerable: true, get: function () { return session_provider_1.AuthSessionProvider; } });
35
+ // AuthSessionProvider is in 'indas-ui/auth': it is the one part built on next-auth, which apps
36
+ // install only when they use it.
37
37
  var api_provider_1 = require("./components/providers/api-provider.js");
38
38
  Object.defineProperty(exports, "APIProvider", { enumerable: true, get: function () { return api_provider_1.APIProvider; } });
39
39
  Object.defineProperty(exports, "useAPI", { enumerable: true, get: function () { return api_provider_1.useAPI; } });
@@ -1,3 +1,2 @@
1
- 'use client';
2
1
  export { ProtectedRoute } from './ProtectedRoute.js';
3
2
  export { UnauthorizedPage, MobileUnavailablePage } from './UnauthorizedPage.js';
@@ -10,6 +10,7 @@ import { cn } from '../../lib/utils.js';
10
10
  import { User, Copy, Check, Download, Share2, Pencil, Bot, RotateCcw, ThumbsUp, ThumbsDown } from 'lucide-react';
11
11
  import ReactMarkdown from 'react-markdown';
12
12
  import remarkGfm from 'remark-gfm';
13
+ import { formatNumber } from '../../lib/number-format.js';
13
14
  // Individual Message Bubble Component
14
15
  function Message({ message, onSelectItem, onEditMessage, onRegenerateAi, onFeedback, botAvatar, renderBotContent }) {
15
16
  const isUser = message.role === 'user';
@@ -158,7 +159,7 @@ function Message({ message, onSelectItem, onEditMessage, onRegenerateAi, onFeedb
158
159
  return full;
159
160
  });
160
161
  return processed;
161
- })() }) })) })) })) }), !isEditing && (_jsxs("div", { className: cn('flex items-center gap-2 mt-1 opacity-0 group-hover:opacity-100 transition-opacity duration-200', isUser ? 'justify-end' : 'justify-start'), children: [formattedTimestamp && (_jsx("span", { className: "text-[0.7rem] text-[rgb(var(--fg-muted))]/70 select-none", children: formattedTimestamp })), isUser && onEditMessage && (_jsx("button", { onClick: handleRegenerate, className: "w-7 h-7 rounded-md flex items-center justify-center text-[rgb(var(--fg-muted))] hover:text-[rgb(var(--fg-default))] hover:bg-[rgb(var(--bg-subtle))] transition-colors", "aria-label": "Regenerate response", title: "Regenerate", children: _jsx(RotateCcw, { className: "w-3.5 h-3.5" }) })), isUser && onEditMessage && (_jsx("button", { onClick: handleStartEdit, className: "w-7 h-7 rounded-md flex items-center justify-center text-[rgb(var(--fg-muted))] hover:text-[rgb(var(--fg-default))] hover:bg-[rgb(var(--bg-subtle))] transition-colors", "aria-label": "Edit message", title: "Edit", children: _jsx(Pencil, { className: "w-3.5 h-3.5" }) })), _jsx("button", { onClick: handleCopy, className: "w-7 h-7 rounded-md flex items-center justify-center text-[rgb(var(--fg-muted))] hover:text-[rgb(var(--fg-default))] hover:bg-[rgb(var(--bg-subtle))] transition-colors", "aria-label": "Copy message", title: "Copy", children: copied ? (_jsx(Check, { className: "w-3.5 h-3.5" })) : (_jsx(Copy, { className: "w-3.5 h-3.5" })) }), !isUser && onRegenerateAi && (_jsx("button", { onClick: handleRegenerateAi, className: "w-7 h-7 rounded-md flex items-center justify-center text-[rgb(var(--fg-muted))] hover:text-[rgb(var(--fg-default))] hover:bg-[rgb(var(--bg-subtle))] transition-colors", "aria-label": "Regenerate response", title: "Regenerate", children: _jsx(RotateCcw, { className: "w-3.5 h-3.5" }) })), !isUser && (_jsxs(_Fragment, { children: [_jsx("button", { onClick: () => handleFeedback('up'), className: "w-7 h-7 rounded-md flex items-center justify-center text-[rgb(var(--fg-muted))] hover:text-[rgb(var(--fg-default))] hover:bg-[rgb(var(--bg-subtle))] transition-colors", "aria-label": "Good response", title: "Good response", children: _jsx(ThumbsUp, { className: "w-3.5 h-3.5", fill: feedback === 'up' ? 'currentColor' : 'none' }) }), _jsx("button", { onClick: () => handleFeedback('down'), className: "w-7 h-7 rounded-md flex items-center justify-center text-[rgb(var(--fg-muted))] hover:text-[rgb(var(--fg-default))] hover:bg-[rgb(var(--bg-subtle))] transition-colors", "aria-label": "Bad response", title: "Bad response", children: _jsx(ThumbsDown, { className: "w-3.5 h-3.5", fill: feedback === 'down' ? 'currentColor' : 'none' }) })] })), !isUser && message.tokens && process.env.NEXT_PUBLIC_SHOW_TOKENS === 'true' && (_jsxs("span", { className: "text-[0.65rem] text-[rgb(var(--fg-muted))]/70 font-mono select-none ml-1", children: ["P: ", message.tokens.prompt.toLocaleString(), " R: ", message.tokens.completion.toLocaleString(), " T: ", message.tokens.total.toLocaleString()] }))] }))] })] }));
162
+ })() }) })) })) })) }), !isEditing && (_jsxs("div", { className: cn('flex items-center gap-2 mt-1 opacity-0 group-hover:opacity-100 transition-opacity duration-200', isUser ? 'justify-end' : 'justify-start'), children: [formattedTimestamp && (_jsx("span", { className: "text-[0.7rem] text-[rgb(var(--fg-muted))]/70 select-none", children: formattedTimestamp })), isUser && onEditMessage && (_jsx("button", { onClick: handleRegenerate, className: "w-7 h-7 rounded-md flex items-center justify-center text-[rgb(var(--fg-muted))] hover:text-[rgb(var(--fg-default))] hover:bg-[rgb(var(--bg-subtle))] transition-colors", "aria-label": "Regenerate response", title: "Regenerate", children: _jsx(RotateCcw, { className: "w-3.5 h-3.5" }) })), isUser && onEditMessage && (_jsx("button", { onClick: handleStartEdit, className: "w-7 h-7 rounded-md flex items-center justify-center text-[rgb(var(--fg-muted))] hover:text-[rgb(var(--fg-default))] hover:bg-[rgb(var(--bg-subtle))] transition-colors", "aria-label": "Edit message", title: "Edit", children: _jsx(Pencil, { className: "w-3.5 h-3.5" }) })), _jsx("button", { onClick: handleCopy, className: "w-7 h-7 rounded-md flex items-center justify-center text-[rgb(var(--fg-muted))] hover:text-[rgb(var(--fg-default))] hover:bg-[rgb(var(--bg-subtle))] transition-colors", "aria-label": "Copy message", title: "Copy", children: copied ? (_jsx(Check, { className: "w-3.5 h-3.5" })) : (_jsx(Copy, { className: "w-3.5 h-3.5" })) }), !isUser && onRegenerateAi && (_jsx("button", { onClick: handleRegenerateAi, className: "w-7 h-7 rounded-md flex items-center justify-center text-[rgb(var(--fg-muted))] hover:text-[rgb(var(--fg-default))] hover:bg-[rgb(var(--bg-subtle))] transition-colors", "aria-label": "Regenerate response", title: "Regenerate", children: _jsx(RotateCcw, { className: "w-3.5 h-3.5" }) })), !isUser && (_jsxs(_Fragment, { children: [_jsx("button", { onClick: () => handleFeedback('up'), className: "w-7 h-7 rounded-md flex items-center justify-center text-[rgb(var(--fg-muted))] hover:text-[rgb(var(--fg-default))] hover:bg-[rgb(var(--bg-subtle))] transition-colors", "aria-label": "Good response", title: "Good response", children: _jsx(ThumbsUp, { className: "w-3.5 h-3.5", fill: feedback === 'up' ? 'currentColor' : 'none' }) }), _jsx("button", { onClick: () => handleFeedback('down'), className: "w-7 h-7 rounded-md flex items-center justify-center text-[rgb(var(--fg-muted))] hover:text-[rgb(var(--fg-default))] hover:bg-[rgb(var(--bg-subtle))] transition-colors", "aria-label": "Bad response", title: "Bad response", children: _jsx(ThumbsDown, { className: "w-3.5 h-3.5", fill: feedback === 'down' ? 'currentColor' : 'none' }) })] })), !isUser && message.tokens && process.env.NEXT_PUBLIC_SHOW_TOKENS === 'true' && (_jsxs("span", { className: "text-[0.65rem] text-[rgb(var(--fg-muted))]/70 font-mono select-none ml-1", children: ["P: ", formatNumber(message.tokens.prompt), " R: ", formatNumber(message.tokens.completion), " T: ", formatNumber(message.tokens.total)] }))] }))] })] }));
162
163
  }
163
164
  // Synthia thinking phrases — the fun ones
164
165
  const SYNTHIA_THINKING = [
@@ -260,6 +261,6 @@ export function Messages({ messages, isLoading = false, onExport, onShare, onSel
260
261
  }, { prompt: 0, completion: 0, total: 0 });
261
262
  if (totals.total === 0)
262
263
  return _jsx("div", {});
263
- return (_jsxs("span", { className: "text-[0.65rem] text-[rgb(var(--fg-muted))] font-mono", children: ["P:", totals.prompt.toLocaleString(), " \u00B7 C:", totals.completion.toLocaleString(), " \u00B7 T:", totals.total.toLocaleString(), " \u00B7 ", messages.filter(m => m.tokens).length, " calls"] }));
264
+ return (_jsxs("span", { className: "text-[0.65rem] text-[rgb(var(--fg-muted))] font-mono", children: ["P:", formatNumber(totals.prompt), " \u00B7 C:", formatNumber(totals.completion), " \u00B7 T:", formatNumber(totals.total), " \u00B7 ", messages.filter(m => m.tokens).length, " calls"] }));
264
265
  })() : _jsx("div", {}), _jsxs("div", { className: "flex gap-2", children: [onExport && (_jsxs("button", { onClick: onExport, className: "flex items-center gap-2 px-3 py-1.5 text-sm text-[rgb(var(--fg-muted))] hover:text-[rgb(var(--fg-default))] hover:bg-[rgb(var(--bg-subtle))] rounded-lg transition-colors", children: [_jsx(Download, { className: "w-4 h-4" }), "Export"] })), onShare && (_jsxs("button", { onClick: onShare, className: "flex items-center gap-2 px-3 py-1.5 text-sm text-[rgb(var(--fg-muted))] hover:text-[rgb(var(--fg-default))] hover:bg-[rgb(var(--bg-subtle))] rounded-lg transition-colors", children: [_jsx(Share2, { className: "w-4 h-4" }), "Share"] }))] })] })), _jsx("div", { className: "flex-1 overflow-y-auto", children: _jsxs("div", { className: "max-w-5xl mx-auto px-3 sm:px-4 py-6 space-y-6 animate-[fadeInUp_0.28s_ease-out]", children: [messages.map((message) => (_jsx(Message, { message: message, onSelectItem: onSelectItem, onEditMessage: onEditMessage, onRegenerateAi: onRegenerateAi, onFeedback: onFeedback, botAvatar: botAvatar, renderBotContent: renderBotContent }, message.id))), isLoading && _jsx(TypingIndicator, { lastUserMessage: messages.filter(m => m.role === 'user').at(-1)?.content }), _jsx("div", { ref: bottomRef })] }, fadeKey) })] }));
265
266
  }
@@ -5,7 +5,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
5
5
  * Domain-specific branding (logo, title, messages) is injected via props.
6
6
  */
7
7
  import { useState, useEffect, useCallback, useRef } from 'react';
8
- import { useSession } from 'next-auth/react';
8
+ import { useOptionalSessionAdapter } from '../../contexts/SessionAdapterContext.js';
9
9
  import { useRouter } from 'next/navigation';
10
10
  import { X, Plus, Maximize2, Download, Share2, Volume2, VolumeX, Bot, PanelLeftOpen, Star, Pencil, Trash2, ChevronDown, Search, Calendar } from 'lucide-react';
11
11
  import { cn } from '../../lib/utils.js';
@@ -18,6 +18,7 @@ import { MultimodalInput } from './multimodal-input.js';
18
18
  import { ChatPromptNavigator } from './chat-prompt-navigator.js';
19
19
  import { DatePicker } from '../forms/date-picker/index.js';
20
20
  import { getLocalDateString } from '../../lib/utils.js';
21
+ import { formatNumber } from '../../lib/number-format.js';
21
22
  function buildSendPayload(ctx, message, autoTitle) {
22
23
  if (ctx.mode === 'new') {
23
24
  return { message, conversationId: undefined, newChat: true, title: autoTitle };
@@ -61,7 +62,7 @@ function bucketForDate(iso, now = new Date()) {
61
62
  return { id: `m-${d.getFullYear()}-${d.getMonth()}`, label: monthLabel, sort };
62
63
  }
63
64
  export function Chat({ mode = 'fullpage', externalMuted, title = 'Chat', sidebarTitle, bubbleLogo, logo, MessagesComponent, fullpagePath, onBeforeEditMessage, initialMessage, initialConversationId, headerActions, onMessagesChange }) {
64
- const { data: session } = useSession();
65
+ const session = useOptionalSessionAdapter()?.data ?? null;
65
66
  const router = useRouter();
66
67
  const alerts = useGlobalAlert();
67
68
  const { t } = useLanguage();
@@ -1019,7 +1020,7 @@ export function Chat({ mode = 'fullpage', externalMuted, title = 'Chat', sidebar
1019
1020
  }, { prompt: 0, completion: 0, total: 0 });
1020
1021
  if (totals.total === 0)
1021
1022
  return null;
1022
- return (_jsxs("span", { className: "text-[0.65rem] text-[rgb(var(--fg-muted))] font-mono", children: ["Prompt: ", totals.prompt.toLocaleString(), " \u00B7 Response: ", totals.completion.toLocaleString(), " \u00B7 Total: ", totals.total.toLocaleString(), " tokens \u00B7 ", messages.filter(m => m.tokens).length, " calls"] }));
1023
+ return (_jsxs("span", { className: "text-[0.65rem] text-[rgb(var(--fg-muted))] font-mono", children: ["Prompt: ", formatNumber(totals.prompt), " \u00B7 Response: ", formatNumber(totals.completion), " \u00B7 Total: ", formatNumber(totals.total), " tokens \u00B7 ", messages.filter(m => m.tokens).length, " calls"] }));
1023
1024
  })(), _jsxs("div", { className: "absolute right-3 flex items-center gap-0.5", children: [headerActions, _jsx("button", { onClick: () => setIsMuted(!isMuted), className: "w-7 h-7 flex items-center justify-center text-[rgb(var(--fg-muted))] hover:text-[rgb(var(--fg-default))] hover:bg-[rgb(var(--bg-subtle))] rounded-md transition-colors", title: isMuted ? "Unmute sounds" : "Mute sounds", children: isMuted ? _jsx(VolumeX, { className: "w-3.5 h-3.5" }) : _jsx(Volume2, { className: "w-3.5 h-3.5" }) }), messages.length > 0 && (_jsxs(_Fragment, { children: [_jsx("button", { onClick: handleExport, className: "w-7 h-7 flex items-center justify-center text-[rgb(var(--fg-muted))] hover:text-[rgb(var(--fg-default))] hover:bg-[rgb(var(--bg-subtle))] rounded-md transition-colors", title: t('Export'), children: _jsx(Download, { className: "w-3.5 h-3.5" }) }), _jsx("button", { onClick: handleShare, className: "w-7 h-7 flex items-center justify-center text-[rgb(var(--fg-muted))] hover:text-[rgb(var(--fg-default))] hover:bg-[rgb(var(--bg-subtle))] rounded-md transition-colors", title: t('Share'), children: _jsx(Share2, { className: "w-3.5 h-3.5" }) })] })), fullpagePath && mode === 'modal' && (_jsx("button", { onClick: () => {
1024
1025
  const target = currentConversationId
1025
1026
  ? `${fullpagePath}?c=${currentConversationId}`
@@ -1,4 +1,3 @@
1
- 'use client';
2
1
  // Chat Components - Centralized exports
3
2
  //
4
3
  // Only the backend-agnostic pieces are exported. `Chat` itself is deliberately left out: it is
@@ -50,6 +50,8 @@ export interface FilterBarProps {
50
50
  title: string;
51
51
  /** Dynamic context label (shown as subtitle) */
52
52
  contextLabel?: string;
53
+ /** One line under the title saying what the page shows. */
54
+ description?: React.ReactNode;
53
55
  /** Secondary toggle (e.g., Job/Value) */
54
56
  secondaryToggle?: {
55
57
  options: ToggleOption[];
@@ -99,10 +101,12 @@ export interface FilterBarProps {
99
101
  onInsightsClick?: () => void;
100
102
  /** Fiscal-year start year (e.g. 2026 for FY 2026-2027). Drives fiscal Week/Month/Quarter/Year options. */
101
103
  fiscalStartYear?: number;
104
+ /** Offer "All Time" at the end of the Year list (needs `fiscalStartYear`). */
105
+ allTime?: boolean;
102
106
  /** Additional content */
103
107
  children?: React.ReactNode;
104
108
  className?: string;
105
109
  /** Layout variant: 'default' or 'centered' (title in center) */
106
110
  layout?: 'default' | 'centered';
107
111
  }
108
- export declare function FilterBar({ title, contextLabel, secondaryToggle, timeFilter, onTimeFilterChange, timeFilterSelection, onTimeFilterSelectionChange, hideTimeFilter, filters, filterValues, onFilterChange, dateFrom, dateTo, onDateFromChange, onDateToChange, onApply, onReset, reports, onReportSelect, onExport, hideFilterButton, hideDateRange, showApplyButton, forecast, onForecastChange, onShare, showInsights, insightsActive, onInsightsClick, children, className, layout, fiscalStartYear }: FilterBarProps): React.JSX.Element;
112
+ export declare function FilterBar({ title, contextLabel, description, secondaryToggle, timeFilter, onTimeFilterChange, timeFilterSelection, onTimeFilterSelectionChange, hideTimeFilter, filters, filterValues, onFilterChange, dateFrom, dateTo, onDateFromChange, onDateToChange, onApply, onReset, reports, onReportSelect, onExport, hideFilterButton, hideDateRange, showApplyButton, forecast, onForecastChange, onShare, showInsights, insightsActive, onInsightsClick, children, className, layout, fiscalStartYear, allTime, }: FilterBarProps): React.JSX.Element;
@@ -214,7 +214,7 @@ const fmtFiscalDay = (d) => `${String(d.getDate()).padStart(2, '0')} ${SHORT_MON
214
214
  // Fiscal-year-aware dropdown options. FY runs 1 Apr (fyStart) → 31 Mar (fyStart+1).
215
215
  // Weeks are W1..W52/53 counting 7-day spans from 1 Apr; months ordered Apr→Mar;
216
216
  // quarters Q1 Apr-Jun … Q4 Jan-Mar; year shown as "YYYY-YYYY".
217
- function buildTimeOptions(fyStart) {
217
+ function buildTimeOptions(fyStart, allTime = false) {
218
218
  const fyOpen = new Date(fyStart, 3, 1);
219
219
  const fyClose = new Date(fyStart + 1, 2, 31);
220
220
  // Monday-aligned weeks clamped to the FY: W1 = 1 Apr → first Sunday (partial),
@@ -242,7 +242,10 @@ function buildTimeOptions(fyStart) {
242
242
  { value: 'Q3', label: `Q3 (Oct – Dec ${fyStart})` },
243
243
  { value: 'Q4', label: `Q4 (Jan – Mar ${fyStart + 1})` },
244
244
  ];
245
- const years = [{ value: `${fyStart}`, label: `${fyStart}-${fyStart + 1}` }];
245
+ // This financial year and the two before it, newest first; "All Time" last where the host offers it.
246
+ const years = [0, 1, 2].map(n => ({ value: `${fyStart - n}`, label: `FY ${fyStart - n}-${String(fyStart - n + 1).slice(2)}` }));
247
+ if (allTime)
248
+ years.push({ value: 'all', label: 'All Time' });
246
249
  return [
247
250
  { value: 'Day', label: 'Day', dropdownOptions: DAY_OPTIONS, quickOptions: DAY_QUICK },
248
251
  { value: 'Week', label: 'Week', dropdownOptions: weeks, quickOptions: WEEK_QUICK },
@@ -318,11 +321,31 @@ function ForecastControl({ forecast, onForecastChange, timeFilter, onTimeFilterC
318
321
  ? 'text-[rgb(var(--fg-muted))] hover:text-[rgb(var(--fg-default))] hover:bg-[rgb(var(--bg-subtle))]'
319
322
  : 'text-[rgb(var(--fg-muted))] hover:bg-[rgb(var(--bg-subtle))] hover:border-[rgb(var(--color-primary))] hover:text-[rgb(var(--color-primary))]'), title: t('Forecast'), children: [_jsx(TrendingUp, { className: "w-4 h-4" }), forecast.enabled && !open && (_jsx("span", { className: "absolute -top-1 -right-1 w-2 h-2 bg-[rgb(var(--color-primary))] rounded-full" }))] }), open && (_jsxs("div", { className: cn('absolute top-full right-0 mt-2 z-50 w-96 max-w-[calc(100vw-2rem)]', 'bg-[rgb(var(--bg-surface))] rounded-xl shadow-xl border border-[rgb(var(--bd-default))]', 'overflow-hidden'), children: [_jsxs("div", { className: "px-3 py-2 border-b border-[rgb(var(--bd-default))] flex items-center justify-between gap-2", children: [_jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [_jsx("span", { className: "text-xs font-semibold text-[rgb(var(--fg-default))] shrink-0", children: t('Forecast') }), onTimeFilterChange ? (_jsx(Dropdown, { options: TIME_FILTER_OPTIONS.map(o => ({ value: o.value, label: t(o.label) })), value: timeFilter, onValueChange: (v) => onTimeFilterChange(v), searchable: false, size: "sm", triggerClassName: "h-6 text-[11px] min-w-[6.5rem]" })) : (_jsxs("span", { className: "text-[10px] text-[rgb(var(--fg-muted))]", children: ["\u00B7 ", t(timeFilter)] }))] }), _jsx(Switch, { checked: forecast.enabled, onCheckedChange: (enabled) => onForecastChange({ ...forecast, enabled }) })] }), _jsxs("div", { className: cn('p-3 space-y-3', !forecast.enabled && 'opacity-50 pointer-events-none'), children: [_jsx(Tabs, { tabs: FORECAST_METHODS.map(m => ({ id: m.value, label: t(m.label) })), activeTab: forecast.method, onTabChange: (tabId) => onForecastChange({ ...forecast, method: tabId }), variant: "rounded", size: "sm", fullWidth: true }), activeMethod && (_jsx("p", { className: "text-[10px] text-[rgb(var(--fg-muted))]", children: t(activeMethod.hint) })), _jsxs("div", { className: "grid grid-cols-2 gap-3", children: [_jsxs("div", { children: [_jsx("label", { className: "text-[10px] font-semibold text-[rgb(var(--fg-muted))] uppercase tracking-wider mb-1 block", children: t('Based On Last') }), _jsx(Stepper, { value: forecast.period, min: limits.min, max: limits.max, onChange: setPeriod }), _jsx("p", { className: "mt-1 text-[10px] text-[rgb(var(--fg-muted))] truncate", children: resolvePeriodRange(timeFilter, forecast.period, -1) })] }), _jsxs("div", { children: [_jsx("label", { className: "text-[10px] font-semibold text-[rgb(var(--fg-muted))] uppercase tracking-wider mb-1 block", children: t('Forecast Ahead') }), _jsx(Stepper, { value: forecast.horizon, min: horizonLimits.min, max: horizonLimits.max, onChange: setHorizon }), _jsx("p", { className: "mt-1 text-[10px] text-[rgb(var(--fg-muted))] truncate", children: resolvePeriodRange(timeFilter, forecast.horizon, 1) })] })] })] })] }))] }));
320
323
  }
321
- export function FilterBar({ title, contextLabel, secondaryToggle, timeFilter = 'Week', onTimeFilterChange, timeFilterSelection, onTimeFilterSelectionChange, hideTimeFilter = false, filters = [], filterValues = {}, onFilterChange, dateFrom, dateTo, onDateFromChange, onDateToChange, onApply, onReset, reports = [], onReportSelect, onExport, hideFilterButton = false, hideDateRange = false, showApplyButton = false, forecast, onForecastChange, onShare, showInsights = false, insightsActive = false, onInsightsClick, children, className, layout = 'default', fiscalStartYear }) {
324
+ export function FilterBar({ title, contextLabel, description, secondaryToggle, timeFilter = 'Week', onTimeFilterChange, timeFilterSelection, onTimeFilterSelectionChange, hideTimeFilter = false, filters = [], filterValues = {}, onFilterChange, dateFrom, dateTo, onDateFromChange, onDateToChange, onApply, onReset, reports = [], onReportSelect, onExport, hideFilterButton = false, hideDateRange = false, showApplyButton = false, forecast, onForecastChange, onShare, showInsights = false, insightsActive = false, onInsightsClick, children, className, layout = 'default', fiscalStartYear, allTime = false, }) {
322
325
  const [popoverOpen, setPopoverOpen] = useState(false);
323
326
  const popoverRef = useRef(null);
324
327
  const buttonRef = useRef(null);
325
- const timeOptions = useMemo(() => (fiscalStartYear ? buildTimeOptions(fiscalStartYear) : TIME_FILTER_OPTIONS), [fiscalStartYear]);
328
+ // The divider between the secondary toggle and the time tabs only makes sense while they share a
329
+ // line. When the row wraps (narrow screens, long toggle labels), it would be left hanging at the
330
+ // end of the first line, so it hides.
331
+ const toggleRef = useRef(null);
332
+ const timeTabsRef = useRef(null);
333
+ const [tabsWrapped, setTabsWrapped] = useState(false);
334
+ useEffect(() => {
335
+ const a = toggleRef.current;
336
+ const b = timeTabsRef.current;
337
+ if (!a || !b || typeof ResizeObserver === 'undefined')
338
+ return;
339
+ const check = () => setTabsWrapped(a.offsetTop !== b.offsetTop);
340
+ const ro = new ResizeObserver(check);
341
+ ro.observe(a);
342
+ ro.observe(b);
343
+ if (a.parentElement)
344
+ ro.observe(a.parentElement);
345
+ check();
346
+ return () => ro.disconnect();
347
+ }, [secondaryToggle, hideTimeFilter]);
348
+ const timeOptions = useMemo(() => (fiscalStartYear ? buildTimeOptions(fiscalStartYear, allTime) : TIME_FILTER_OPTIONS), [fiscalStartYear, allTime]);
326
349
  // Handle click outside. Ignore clicks landing inside a portalled dropdown/select
327
350
  // list (rendered to document.body, outside popoverRef) so selecting a filter
328
351
  // option doesn't close this popover before the value commits.
@@ -385,10 +408,10 @@ export function FilterBar({ title, contextLabel, secondaryToggle, timeFilter = '
385
408
  ...(filter === 'Year' && { years: selectedValues }),
386
409
  };
387
410
  onTimeFilterSelectionChange?.(newSelection);
388
- } }))] }), _jsxs("div", { className: "flex-1 text-center", children: [_jsx("h1", { className: "text-sm font-semibold text-[rgb(var(--fg-default))]", children: title }), contextLabel && (_jsx("span", { className: "text-xs text-[rgb(var(--fg-muted))]", children: contextLabel }))] }), _jsxs("div", { className: "flex items-center gap-3", children: [!hideFilterButton && (_jsxs("div", { className: "relative", children: [_jsxs("button", { ref: buttonRef, onClick: () => setPopoverOpen(!popoverOpen), className: cn('w-8 h-8 rounded-md flex items-center justify-center transition-colors relative', 'border border-[rgb(var(--bd-default))] bg-[rgb(var(--bg-surface))]', 'text-[rgb(var(--fg-muted))] hover:bg-[rgb(var(--bg-subtle))] hover:border-[rgb(var(--color-primary))] hover:text-[rgb(var(--color-primary))]', popoverOpen && 'bg-[rgb(var(--color-primary))] text-white border-[rgb(var(--color-primary))] hover:bg-[rgb(var(--color-primary))]'), title: "Filters", children: [_jsx(ListFilter, { className: "w-4 h-4" }), hasActiveFilters && !popoverOpen && (_jsx("span", { className: "absolute -top-1 -right-1 w-2 h-2 bg-[rgb(var(--color-primary))] rounded-full" }))] }), popoverOpen && (_jsxs("div", { ref: popoverRef, className: cn('absolute top-full right-0 mt-2 z-50', 'w-[95vw] sm:w-auto', 'bg-[rgb(var(--bg-surface))] rounded-xl shadow-xl border border-[rgb(var(--bd-default))]', 'overflow-hidden'), children: [_jsxs("div", { className: "px-3 py-2 border-b border-[rgb(var(--bd-default))] flex items-center justify-between", children: [_jsx("span", { className: "text-xs font-semibold text-[rgb(var(--fg-default))]", children: "Filters" }), _jsx("button", { onClick: () => setPopoverOpen(false), className: "w-5 h-5 rounded flex items-center justify-center hover:bg-[rgb(var(--bg-subtle))] transition-colors", children: _jsx(X, { className: "w-3 h-3 text-[rgb(var(--fg-muted))]" }) })] }), _jsxs("div", { className: "p-3 space-y-3", children: [filters.length > 0 && (_jsx("div", { className: "grid gap-3", style: { gridTemplateColumns: `repeat(${filters.length}, minmax(140px, 1fr))` }, children: filters.map((filter) => (_jsxs("div", { children: [_jsx("label", { className: "text-[10px] font-semibold text-[rgb(var(--fg-muted))] uppercase tracking-wider mb-1 block", children: filter.label }), _jsx(Dropdown, { options: filter.options, multiSelect: filter.type === 'multiselect', value: filterValues[filter.id] ?? (filter.type === 'multiselect' ? [] : ''), onValueChange: (value) => onFilterChange?.(filter.id, value), placeholder: filter.placeholder || `All ${filter.label}s`, searchable: true, className: "w-full" })] }, filter.id))) })), children, !hideDateRange && (_jsx(DatePicker, { mode: "range", value: dateRangeValue, onChange: handleDateRangeChange, inline: true }))] })] }))] })), forecast && onForecastChange && (_jsx(ForecastControl, { forecast: forecast, onForecastChange: onForecastChange, timeFilter: timeFilter, onTimeFilterChange: onTimeFilterChange })), showApplyButton && (_jsx("button", { onClick: onApply, className: cn('h-8 px-4 rounded-md flex items-center gap-1.5', 'bg-[rgb(var(--color-primary))] text-white', 'text-xs font-semibold', 'shadow-sm hover:shadow-md transition-all', 'hover:brightness-110'), children: "Apply" })), (reports.length > 0 || onShare || onExport) && (_jsx(OverflowMenu, { reports: reports, onReportSelect: onReportSelect, onShare: onShare, onExport: onExport }))] })] }) }) }));
411
+ } }))] }), _jsxs("div", { className: "flex-1 text-center", children: [_jsx("h1", { className: "text-sm font-semibold text-[rgb(var(--fg-default))]", children: title }), contextLabel && (_jsx("span", { className: "text-xs text-[rgb(var(--fg-muted))]", children: contextLabel })), description && _jsx("p", { className: "mt-0.5 text-xs text-[rgb(var(--fg-muted))]", children: description })] }), _jsxs("div", { className: "flex items-center gap-3", children: [!hideFilterButton && (_jsxs("div", { className: "relative", children: [_jsxs("button", { ref: buttonRef, onClick: () => setPopoverOpen(!popoverOpen), className: cn('w-8 h-8 rounded-md flex items-center justify-center transition-colors relative', 'border border-[rgb(var(--bd-default))] bg-[rgb(var(--bg-surface))]', 'text-[rgb(var(--fg-muted))] hover:bg-[rgb(var(--bg-subtle))] hover:border-[rgb(var(--color-primary))] hover:text-[rgb(var(--color-primary))]', popoverOpen && 'bg-[rgb(var(--color-primary))] text-white border-[rgb(var(--color-primary))] hover:bg-[rgb(var(--color-primary))]'), title: "Filters", children: [_jsx(ListFilter, { className: "w-4 h-4" }), hasActiveFilters && !popoverOpen && (_jsx("span", { className: "absolute -top-1 -right-1 w-2 h-2 bg-[rgb(var(--color-primary))] rounded-full" }))] }), popoverOpen && (_jsxs("div", { ref: popoverRef, className: cn('absolute top-full right-0 mt-2 z-50', 'w-[95vw] sm:w-auto', 'bg-[rgb(var(--bg-surface))] rounded-xl shadow-xl border border-[rgb(var(--bd-default))]', 'overflow-hidden'), children: [_jsxs("div", { className: "px-3 py-2 border-b border-[rgb(var(--bd-default))] flex items-center justify-between", children: [_jsx("span", { className: "text-xs font-semibold text-[rgb(var(--fg-default))]", children: "Filters" }), _jsx("button", { onClick: () => setPopoverOpen(false), className: "w-5 h-5 rounded flex items-center justify-center hover:bg-[rgb(var(--bg-subtle))] transition-colors", children: _jsx(X, { className: "w-3 h-3 text-[rgb(var(--fg-muted))]" }) })] }), _jsxs("div", { className: "p-3 space-y-3", children: [filters.length > 0 && (_jsx("div", { className: "grid gap-3", style: { gridTemplateColumns: `repeat(${filters.length}, minmax(140px, 1fr))` }, children: filters.map((filter) => (_jsxs("div", { children: [_jsx("label", { className: "text-[10px] font-semibold text-[rgb(var(--fg-muted))] uppercase tracking-wider mb-1 block", children: filter.label }), _jsx(Dropdown, { options: filter.options, multiSelect: filter.type === 'multiselect', value: filterValues[filter.id] ?? (filter.type === 'multiselect' ? [] : ''), onValueChange: (value) => onFilterChange?.(filter.id, value), placeholder: filter.placeholder || `All ${filter.label}s`, searchable: true, className: "w-full" })] }, filter.id))) })), children, !hideDateRange && (_jsx(DatePicker, { mode: "range", value: dateRangeValue, onChange: handleDateRangeChange, inline: true }))] })] }))] })), forecast && onForecastChange && (_jsx(ForecastControl, { forecast: forecast, onForecastChange: onForecastChange, timeFilter: timeFilter, onTimeFilterChange: onTimeFilterChange })), showApplyButton && (_jsx("button", { onClick: onApply, className: cn('h-8 px-4 rounded-md flex items-center gap-1.5', 'bg-[rgb(var(--color-primary))] text-white', 'text-xs font-semibold', 'shadow-sm hover:shadow-md transition-all', 'hover:brightness-110'), children: "Apply" })), (reports.length > 0 || onShare || onExport) && (_jsx(OverflowMenu, { reports: reports, onReportSelect: onReportSelect, onShare: onShare, onExport: onExport }))] })] }) }) }));
389
412
  }
390
413
  // Default layout: Title centered, controls on right
391
- return (_jsxs("header", { className: cn('bg-[rgb(var(--bg-surface))] border-b border-[rgb(var(--bd-default))]', 'px-4 py-3 lg:px-6', className), children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("div", { className: "flex-1 min-w-0 flex items-center gap-2.5", children: [_jsx("h1", { className: "text-base sm:text-xl font-bold text-[rgb(var(--fg-default))] leading-tight truncate", children: title }), contextLabel && (_jsxs("span", { className: "hidden sm:inline-flex items-center gap-1.5 shrink-0 h-6 px-2.5 rounded-full border border-[rgb(var(--bd-default))] bg-[rgb(var(--bg-subtle))] text-xs font-medium text-[rgb(var(--fg-muted))]", children: [_jsx(CalendarDays, { className: "w-3.5 h-3.5" }), contextLabel] }))] }), _jsxs("div", { className: "flex items-center gap-3", children: [secondaryToggle && (_jsxs(_Fragment, { children: [_jsx("div", { className: "hidden sm:block", children: _jsx(Tabs, { tabs: secondaryToggle.options.map(opt => ({ id: opt.value, label: opt.label })), activeTab: secondaryToggle.value, onTabChange: secondaryToggle.onChange, size: "sm" }) }), _jsx("div", { className: "hidden sm:block h-4 w-px bg-[rgb(var(--bd-default))] self-center" })] })), !hideTimeFilter && (_jsx("div", { className: "hidden sm:block", children: _jsx(Tabs, { tabs: timeOptions.map(opt => ({ id: opt.value, label: opt.label, dropdownOptions: opt.dropdownOptions, quickOptions: opt.quickOptions })), activeTab: timeFilter, onTabChange: (tabId) => onTimeFilterChange?.(tabId), variant: "rounded", size: "sm", dropdownValues: {
414
+ return (_jsxs("header", { className: cn('bg-[rgb(var(--bg-surface))] border-b border-[rgb(var(--bd-default))]', 'px-4 py-3 lg:px-6', className), children: [_jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-2", children: [_jsxs("div", { className: "flex-auto min-w-0 flex items-center gap-2.5", children: [_jsx("h1", { className: "text-base sm:text-xl font-bold text-[rgb(var(--fg-default))] leading-tight truncate", children: title }), contextLabel && (_jsxs("span", { className: "hidden sm:inline-flex items-center gap-1.5 shrink-0 h-6 px-2.5 rounded-full border border-[rgb(var(--bd-default))] bg-[rgb(var(--bg-subtle))] text-xs font-medium text-[rgb(var(--fg-muted))]", children: [_jsx(CalendarDays, { className: "w-3.5 h-3.5" }), contextLabel] }))] }), _jsxs("div", { className: "ml-auto flex flex-wrap items-center justify-end gap-x-3 gap-y-2", children: [secondaryToggle && (_jsxs(_Fragment, { children: [_jsx("div", { ref: toggleRef, className: "hidden sm:block", children: _jsx(Tabs, { tabs: secondaryToggle.options.map(opt => ({ id: opt.value, label: opt.label })), activeTab: secondaryToggle.value, onTabChange: secondaryToggle.onChange, size: "sm" }) }), _jsx("div", { className: cn('hidden h-4 w-px bg-[rgb(var(--bd-default))] self-center', !tabsWrapped && 'sm:block') })] })), !hideTimeFilter && (_jsx("div", { ref: timeTabsRef, className: "hidden sm:block", children: _jsx(Tabs, { tabs: timeOptions.map(opt => ({ id: opt.value, label: opt.label, dropdownOptions: opt.dropdownOptions, quickOptions: opt.quickOptions })), activeTab: timeFilter, onTabChange: (tabId) => onTimeFilterChange?.(tabId), variant: "rounded", size: "sm", dropdownValues: {
392
415
  Day: timeFilterSelection?.days || [],
393
416
  Week: timeFilterSelection?.weeks || [],
394
417
  Month: timeFilterSelection?.months || [],
@@ -407,7 +430,7 @@ export function FilterBar({ title, contextLabel, secondaryToggle, timeFilter = '
407
430
  onTimeFilterSelectionChange?.(newSelection);
408
431
  } }) })), _jsxs("div", { className: "flex items-center gap-0.5 rounded-lg border border-[rgb(var(--bd-default))] bg-[rgb(var(--bg-surface))] p-0.5", children: [!hideFilterButton && (_jsxs("div", { className: "relative", children: [_jsxs("button", { ref: buttonRef, onClick: () => setPopoverOpen(!popoverOpen), className: cn('w-7 h-7 rounded-md flex items-center justify-center transition-colors relative', popoverOpen
409
432
  ? 'bg-[rgb(var(--color-primary))] text-white shadow-sm'
410
- : 'text-[rgb(var(--fg-muted))] hover:text-[rgb(var(--fg-default))] hover:bg-[rgb(var(--bg-subtle))]'), title: "Filters", children: [_jsx(ListFilter, { className: "w-4 h-4" }), hasActiveFilters && !popoverOpen && (_jsx("span", { className: "absolute -top-1 -right-1 w-2 h-2 bg-[rgb(var(--color-primary))] rounded-full" }))] }), popoverOpen && (_jsxs("div", { ref: popoverRef, className: cn('absolute top-full right-0 mt-2 z-50', 'w-[95vw] sm:w-auto', 'bg-[rgb(var(--bg-surface))] rounded-xl shadow-xl border border-[rgb(var(--bd-default))]', 'overflow-hidden'), children: [_jsxs("div", { className: "px-3 py-2 border-b border-[rgb(var(--bd-default))] flex items-center justify-between", children: [_jsx("span", { className: "text-xs font-semibold text-[rgb(var(--fg-default))]", children: "Filters" }), _jsx("button", { onClick: () => setPopoverOpen(false), className: "w-5 h-5 rounded flex items-center justify-center hover:bg-[rgb(var(--bg-subtle))] transition-colors", children: _jsx(X, { className: "w-3 h-3 text-[rgb(var(--fg-muted))]" }) })] }), _jsxs("div", { className: "p-3 space-y-3", children: [filters.length > 0 && (_jsx("div", { className: "grid gap-3", style: { gridTemplateColumns: `repeat(${Math.min(filters.length, 3)}, minmax(140px, 1fr))` }, children: filters.map((filter) => (_jsxs("div", { children: [_jsx("label", { className: "text-[10px] font-semibold text-[rgb(var(--fg-muted))] uppercase tracking-wider mb-1 block", children: filter.label }), _jsx(Dropdown, { options: filter.options, multiSelect: filter.type === 'multiselect', value: filterValues[filter.id] ?? (filter.type === 'multiselect' ? [] : ''), onValueChange: (value) => onFilterChange?.(filter.id, value), placeholder: filter.placeholder || `All ${filter.label}s`, searchable: true, className: "w-full" })] }, filter.id))) })), children, !hideDateRange && (_jsx(DatePicker, { mode: "range", value: dateRangeValue, onChange: handleDateRangeChange, inline: true }))] })] }))] })), forecast && onForecastChange && (_jsx(ForecastControl, { forecast: forecast, onForecastChange: onForecastChange, timeFilter: timeFilter, onTimeFilterChange: onTimeFilterChange, bare: true })), (showInsights || reports.length > 0 || onShare || onExport) && (_jsx(OverflowMenu, { showInsights: showInsights, insightsActive: insightsActive, onInsightsClick: onInsightsClick, reports: reports, onReportSelect: onReportSelect, onShare: onShare, onExport: onExport }))] }), showApplyButton && (_jsx("button", { onClick: onApply, className: cn('h-8 px-4 rounded-md flex items-center gap-1.5', 'bg-[rgb(var(--color-primary))] text-white', 'text-xs font-semibold', 'shadow-sm hover:shadow-md transition-all', 'hover:brightness-110'), children: "Apply" }))] })] }), contextLabel && (_jsxs("span", { className: "sm:hidden mt-2 inline-flex items-center gap-1.5 h-6 px-2.5 rounded-full border border-[rgb(var(--bd-default))] bg-[rgb(var(--bg-subtle))] text-xs font-medium text-[rgb(var(--fg-muted))]", children: [_jsx(CalendarDays, { className: "w-3.5 h-3.5" }), contextLabel] })), (!hideTimeFilter || secondaryToggle) && (_jsxs("div", { className: "sm:hidden mt-2 flex items-center gap-2 overflow-x-auto", children: [secondaryToggle && (_jsx(Tabs, { tabs: secondaryToggle.options.map(opt => ({ id: opt.value, label: opt.label })), activeTab: secondaryToggle.value, onTabChange: secondaryToggle.onChange, size: "sm" })), !hideTimeFilter && (_jsx(Tabs, { tabs: timeOptions.map(opt => ({ id: opt.value, label: opt.label, dropdownOptions: opt.dropdownOptions, quickOptions: opt.quickOptions })), activeTab: timeFilter, onTabChange: (tabId) => onTimeFilterChange?.(tabId), variant: "rounded", size: "sm", dropdownValues: {
433
+ : 'text-[rgb(var(--fg-muted))] hover:text-[rgb(var(--fg-default))] hover:bg-[rgb(var(--bg-subtle))]'), title: "Filters", children: [_jsx(ListFilter, { className: "w-4 h-4" }), hasActiveFilters && !popoverOpen && (_jsx("span", { className: "absolute -top-1 -right-1 w-2 h-2 bg-[rgb(var(--color-primary))] rounded-full" }))] }), popoverOpen && (_jsxs("div", { ref: popoverRef, className: cn('absolute top-full right-0 mt-2 z-50', 'w-[95vw] sm:w-auto', 'bg-[rgb(var(--bg-surface))] rounded-xl shadow-xl border border-[rgb(var(--bd-default))]', 'overflow-hidden'), children: [_jsxs("div", { className: "px-3 py-2 border-b border-[rgb(var(--bd-default))] flex items-center justify-between", children: [_jsx("span", { className: "text-xs font-semibold text-[rgb(var(--fg-default))]", children: "Filters" }), _jsx("button", { onClick: () => setPopoverOpen(false), className: "w-5 h-5 rounded flex items-center justify-center hover:bg-[rgb(var(--bg-subtle))] transition-colors", children: _jsx(X, { className: "w-3 h-3 text-[rgb(var(--fg-muted))]" }) })] }), _jsxs("div", { className: "p-3 space-y-3", children: [filters.length > 0 && (_jsx("div", { className: "grid gap-3", style: { gridTemplateColumns: `repeat(${Math.min(filters.length, 3)}, minmax(140px, 1fr))` }, children: filters.map((filter) => (_jsxs("div", { children: [_jsx("label", { className: "text-[10px] font-semibold text-[rgb(var(--fg-muted))] uppercase tracking-wider mb-1 block", children: filter.label }), _jsx(Dropdown, { options: filter.options, multiSelect: filter.type === 'multiselect', value: filterValues[filter.id] ?? (filter.type === 'multiselect' ? [] : ''), onValueChange: (value) => onFilterChange?.(filter.id, value), placeholder: filter.placeholder || `All ${filter.label}s`, searchable: true, className: "w-full" })] }, filter.id))) })), children, !hideDateRange && (_jsx(DatePicker, { mode: "range", value: dateRangeValue, onChange: handleDateRangeChange, inline: true }))] })] }))] })), forecast && onForecastChange && (_jsx(ForecastControl, { forecast: forecast, onForecastChange: onForecastChange, timeFilter: timeFilter, onTimeFilterChange: onTimeFilterChange, bare: true })), (showInsights || reports.length > 0 || onShare || onExport) && (_jsx(OverflowMenu, { showInsights: showInsights, insightsActive: insightsActive, onInsightsClick: onInsightsClick, reports: reports, onReportSelect: onReportSelect, onShare: onShare, onExport: onExport }))] }), showApplyButton && (_jsx("button", { onClick: onApply, className: cn('h-8 px-4 rounded-md flex items-center gap-1.5', 'bg-[rgb(var(--color-primary))] text-white', 'text-xs font-semibold', 'shadow-sm hover:shadow-md transition-all', 'hover:brightness-110'), children: "Apply" }))] })] }), contextLabel && (_jsxs("span", { className: "sm:hidden mt-2 inline-flex items-center gap-1.5 h-6 px-2.5 rounded-full border border-[rgb(var(--bd-default))] bg-[rgb(var(--bg-subtle))] text-xs font-medium text-[rgb(var(--fg-muted))]", children: [_jsx(CalendarDays, { className: "w-3.5 h-3.5" }), contextLabel] })), (!hideTimeFilter || secondaryToggle) && (_jsxs("div", { className: "sm:hidden mt-2 flex flex-col items-start gap-2", children: [secondaryToggle && (_jsx(Tabs, { tabs: secondaryToggle.options.map(opt => ({ id: opt.value, label: opt.label })), activeTab: secondaryToggle.value, onTabChange: secondaryToggle.onChange, size: "sm" })), !hideTimeFilter && (_jsx(Tabs, { tabs: timeOptions.map(opt => ({ id: opt.value, label: opt.label, dropdownOptions: opt.dropdownOptions, quickOptions: opt.quickOptions })), activeTab: timeFilter, onTabChange: (tabId) => onTimeFilterChange?.(tabId), variant: "rounded", size: "sm", dropdownValues: {
411
434
  Day: timeFilterSelection?.days || [],
412
435
  Week: timeFilterSelection?.weeks || [],
413
436
  Month: timeFilterSelection?.months || [],
@@ -424,5 +447,5 @@ export function FilterBar({ title, contextLabel, secondaryToggle, timeFilter = '
424
447
  ...(filter === 'Year' && { years: selectedValues }),
425
448
  };
426
449
  onTimeFilterSelectionChange?.(newSelection);
427
- } }))] }))] }));
450
+ } }))] })), description && _jsx("p", { className: "mt-1 text-sm text-[rgb(var(--fg-muted))]", children: description })] }));
428
451
  }
@@ -20,9 +20,13 @@ export interface AreaChartProps {
20
20
  showLegend?: boolean;
21
21
  showGrid?: boolean;
22
22
  smooth?: boolean;
23
+ /** How a value reads in the tooltip. Default: the full number, Indian grouping (20,00,000). */
24
+ valueFormatter?: (value: number) => string;
25
+ /** How a value reads on the value axis. Default: compact (12,500 · 20 L · 1.4 Cr). */
26
+ axisFormatter?: (value: number) => string;
23
27
  className?: string;
24
28
  }
25
29
  /**
26
30
  * AreaChart - ECharts Area Chart component
27
31
  */
28
- export declare function AreaChart({ data, xKey, series, height, showLegend, showGrid, smooth, className }: AreaChartProps): import("react").JSX.Element;
32
+ export declare function AreaChart({ data, xKey, series, height, showLegend, showGrid, smooth, valueFormatter, axisFormatter, className }: AreaChartProps): import("react").JSX.Element;
@@ -2,6 +2,7 @@
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import ReactECharts from 'echarts-for-react';
4
4
  import { cn } from '../../../lib/utils.js';
5
+ import { formatCompactNumber, formatNumber } from '../../../lib/number-format.js';
5
6
  // Convert any color (hex, rgb(), rgba(), space-separated rgb()) to rgba() with given alpha (0–1).
6
7
  // Hex+alpha-suffix concat (e.g. color + 'cc') breaks for non-hex inputs and crashes canvas gradients.
7
8
  function withAlpha(color, alpha) {
@@ -31,10 +32,11 @@ function withAlpha(color, alpha) {
31
32
  /**
32
33
  * AreaChart - ECharts Area Chart component
33
34
  */
34
- export function AreaChart({ data, xKey, series, height = 300, showLegend = true, showGrid = true, smooth = true, className }) {
35
+ export function AreaChart({ data, xKey, series, height = 300, showLegend = true, showGrid = true, smooth = true, valueFormatter, axisFormatter, className }) {
35
36
  const option = {
36
37
  tooltip: {
37
38
  trigger: 'axis',
39
+ valueFormatter: (v) => (typeof v === 'number' ? (valueFormatter ?? formatNumber)(v) : String(v ?? '')),
38
40
  backgroundColor: 'rgba(255, 255, 255, 0.95)',
39
41
  borderColor: '#e9ecef',
40
42
  borderWidth: 1,
@@ -70,13 +72,13 @@ export function AreaChart({ data, xKey, series, height = 300, showLegend = true,
70
72
  },
71
73
  yAxis: series.some(s => s.secondaryAxis)
72
74
  ? [
73
- { type: 'value', axisLine: { show: false }, axisLabel: { color: '#6c757d', fontSize: 11 }, splitLine: { show: showGrid, lineStyle: { color: '#f0f0f0', type: 'dashed' } } },
74
- { type: 'value', axisLine: { show: false }, axisLabel: { color: '#6c757d', fontSize: 11 }, splitLine: { show: false } },
75
+ { type: 'value', axisLine: { show: false }, axisLabel: { color: '#6c757d', fontSize: 11, formatter: (v) => (axisFormatter ?? formatCompactNumber)(v) }, splitLine: { show: showGrid, lineStyle: { color: '#f0f0f0', type: 'dashed' } } },
76
+ { type: 'value', axisLine: { show: false }, axisLabel: { color: '#6c757d', fontSize: 11, formatter: (v) => (axisFormatter ?? formatCompactNumber)(v) }, splitLine: { show: false } },
75
77
  ]
76
78
  : {
77
79
  type: 'value',
78
80
  axisLine: { show: false },
79
- axisLabel: { color: '#6c757d', fontSize: 11 },
81
+ axisLabel: { color: '#6c757d', fontSize: 11, formatter: (v) => (axisFormatter ?? formatCompactNumber)(v) },
80
82
  splitLine: { show: showGrid, lineStyle: { color: '#f0f0f0', type: 'dashed' } }
81
83
  },
82
84
  series: series.map((s, index) => {
@@ -19,9 +19,13 @@ export interface BarChartProps {
19
19
  * Renders a KPI-vs-target bullet (track + actual fill + target marker + achievement %).
20
20
  */
21
21
  targetKey?: string;
22
+ /** How a value reads in the tooltip. Default: the full number, Indian grouping (20,00,000). */
23
+ valueFormatter?: (value: number) => string;
24
+ /** How a value reads on the value axis. Default: compact (12,500 · 20 L · 1.4 Cr). */
25
+ axisFormatter?: (value: number) => string;
22
26
  className?: string;
23
27
  }
24
28
  /**
25
29
  * BarChart - ECharts Bar Chart component
26
30
  */
27
- export declare function BarChart({ data, xKey, series, height, showLegend, showGrid, horizontal, targetKey, className }: BarChartProps): import("react").JSX.Element;
31
+ export declare function BarChart({ data, xKey, series, height, showLegend, showGrid, horizontal, targetKey, valueFormatter, axisFormatter, className }: BarChartProps): import("react").JSX.Element;
@@ -2,10 +2,11 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import ReactECharts from 'echarts-for-react';
4
4
  import { cn } from '../../../lib/utils.js';
5
+ import { formatCompactNumber, formatNumber } from '../../../lib/number-format.js';
5
6
  /**
6
7
  * BarChart - ECharts Bar Chart component
7
8
  */
8
- export function BarChart({ data, xKey, series, height = 300, showLegend = true, showGrid = true, horizontal = false, targetKey, className }) {
9
+ export function BarChart({ data, xKey, series, height = 300, showLegend = true, showGrid = true, horizontal = false, targetKey, valueFormatter, axisFormatter, className }) {
9
10
  // Bullet mode: horizontal KPI-vs-target rows (CSS-rendered, not ECharts).
10
11
  if (horizontal && targetKey) {
11
12
  const actualKey = series[0]?.key;
@@ -25,13 +26,14 @@ export function BarChart({ data, xKey, series, height = 300, showLegend = true,
25
26
  : met
26
27
  ? 'linear-gradient(90deg, #34d399 0%, #10b981 100%)'
27
28
  : 'linear-gradient(90deg, #fbbf24 0%, #f59e0b 100%)';
28
- return (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsxs("div", { className: "flex items-baseline justify-between gap-2", children: [_jsx("span", { className: "text-xs font-medium text-[rgb(var(--fg-default))] truncate", children: name }), _jsxs("span", { className: "text-xs font-semibold tabular-nums shrink-0", style: { color: barColor }, children: [achievement.toFixed(0), "%"] })] }), _jsxs("div", { className: "relative h-2.5 rounded-full bg-[rgb(var(--bg-subtle))] overflow-visible", children: [_jsx("div", { className: "absolute inset-y-0 left-0 rounded-full transition-all duration-300", style: { width: `${actualPct}%`, background: barGradient } }), _jsx("div", { className: "absolute top-1/2 -translate-y-1/2 -translate-x-1/2 w-1 h-4 rounded-full ring-2 ring-[rgb(var(--bg-surface))]", style: { left: `${targetPct}%`, backgroundColor: '#475569' }, title: `Target: ${target.toLocaleString()}` })] }), _jsxs("div", { className: "flex items-baseline justify-between gap-2 text-[0.65rem] text-[rgb(var(--fg-muted))] tabular-nums", children: [_jsx("span", { children: actual.toLocaleString() }), _jsx("span", { children: `Target ${target.toLocaleString()}` })] })] }, `${name}-${idx}`));
29
+ return (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsxs("div", { className: "flex items-baseline justify-between gap-2", children: [_jsx("span", { className: "text-xs font-medium text-[rgb(var(--fg-default))] truncate", children: name }), _jsxs("span", { className: "text-xs font-semibold tabular-nums shrink-0", style: { color: barColor }, children: [achievement.toFixed(0), "%"] })] }), _jsxs("div", { className: "relative h-2.5 rounded-full bg-[rgb(var(--bg-subtle))] overflow-visible", children: [_jsx("div", { className: "absolute inset-y-0 left-0 rounded-full transition-all duration-300", style: { width: `${actualPct}%`, background: barGradient } }), _jsx("div", { className: "absolute top-1/2 -translate-y-1/2 -translate-x-1/2 w-1 h-4 rounded-full ring-2 ring-[rgb(var(--bg-surface))]", style: { left: `${targetPct}%`, backgroundColor: '#475569' }, title: `Target: ${formatNumber(target)}` })] }), _jsxs("div", { className: "flex items-baseline justify-between gap-2 text-[0.65rem] text-[rgb(var(--fg-muted))] tabular-nums", children: [_jsx("span", { children: formatNumber(actual) }), _jsx("span", { children: `Target ${formatNumber(target)}` })] })] }, `${name}-${idx}`));
29
30
  }) }));
30
31
  }
31
32
  const categoryData = data.map(item => item[xKey]);
32
33
  const option = {
33
34
  tooltip: {
34
35
  trigger: 'item',
36
+ valueFormatter: (v) => (typeof v === 'number' ? (valueFormatter ?? formatNumber)(v) : String(v ?? '')),
35
37
  backgroundColor: 'rgba(255, 255, 255, 0.98)',
36
38
  borderColor: '#e9ecef',
37
39
  borderWidth: 1,
@@ -64,7 +66,7 @@ export function BarChart({ data, xKey, series, height = 300, showLegend = true,
64
66
  type: horizontal ? 'value' : 'category',
65
67
  data: horizontal ? undefined : categoryData,
66
68
  axisLine: { lineStyle: { color: '#dee2e6' } },
67
- axisLabel: { color: '#6c757d', fontSize: 11 },
69
+ axisLabel: horizontal ? { color: '#6c757d', fontSize: 11, hideOverlap: true, alignMaxLabel: 'right', formatter: (v) => (axisFormatter ?? formatCompactNumber)(v) } : { color: '#6c757d', fontSize: 11 },
68
70
  axisTick: { show: false },
69
71
  splitLine: horizontal && showGrid ? {
70
72
  lineStyle: { color: '#f0f0f0', type: 'dashed' }
@@ -74,7 +76,7 @@ export function BarChart({ data, xKey, series, height = 300, showLegend = true,
74
76
  type: horizontal ? 'category' : 'value',
75
77
  data: horizontal ? categoryData : undefined,
76
78
  axisLine: { show: false },
77
- axisLabel: { color: '#6c757d', fontSize: 11 },
79
+ axisLabel: horizontal ? { color: '#6c757d', fontSize: 11 } : { color: '#6c757d', fontSize: 11, formatter: (v) => (axisFormatter ?? formatCompactNumber)(v) },
78
80
  splitLine: !horizontal && showGrid ? {
79
81
  lineStyle: { color: '#f0f0f0', type: 'dashed' }
80
82
  } : { show: false }
@@ -6,6 +6,7 @@ import * as echarts from 'echarts/core';
6
6
  import { cn } from '../../../lib/utils.js';
7
7
  import { INDIA_GEOJSON } from './india-map.js';
8
8
  import { getChartTheme } from './theme.js';
9
+ import { formatNumber } from '../../../lib/number-format.js';
9
10
  /**
10
11
  * FlowMap - Geographic flow/route visualization
11
12
  *
@@ -161,7 +162,7 @@ export function FlowMap({ markers = [], lines = [], height = 400, markerColor =
161
162
  }
162
163
  if (params.seriesType === 'effectScatter' || params.seriesType === 'scatter') {
163
164
  const value = params.value?.[2];
164
- return `<strong>${params.name}</strong>${value ? `<br/>Value: ${value.toLocaleString()}` : ''}`;
165
+ return `<strong>${params.name}</strong>${value ? `<br/>Value: ${formatNumber(value)}` : ''}`;
165
166
  }
166
167
  return `<strong>${params.name}</strong>`;
167
168
  }
@@ -2,6 +2,7 @@
2
2
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
3
3
  import { useState } from 'react';
4
4
  import { cn } from '../../../lib/utils.js';
5
+ import { formatNumber } from '../../../lib/number-format.js';
5
6
  const FUNNEL_COLORS = ['#5470c6', '#3ba272', '#fac858', '#ee6666', '#73c0de', '#9a60b4', '#fc8452', '#91cc75'];
6
7
  /**
7
8
  * FunnelChart — centered, tapering stage-bar funnel for conversion/process flows.
@@ -30,6 +31,6 @@ export function FunnelChart({ data, height = 300, sort = 'descending', className
30
31
  background: `linear-gradient(90deg, ${color}, ${color}cc)`,
31
32
  transform: isHover ? 'translateY(-1px)' : 'none',
32
33
  boxShadow: isHover ? `0 4px 14px ${color}55` : `0 1px 2px rgba(0,0,0,0.06)`,
33
- }, title: `${item.name}: ${item.value.toLocaleString()} (${pct.toFixed(1)}%)`, children: [_jsx("span", { className: "text-sm font-semibold text-white truncate", children: item.name }), _jsx("span", { className: "text-xs font-medium text-white/90 tabular-nums shrink-0", children: item.value.toLocaleString() })] }) })] }, item.name));
34
+ }, title: `${item.name}: ${formatNumber(item.value)} (${pct.toFixed(1)}%)`, children: [_jsx("span", { className: "text-sm font-semibold text-white truncate", children: item.name }), _jsx("span", { className: "text-xs font-medium text-white/90 tabular-nums shrink-0", children: formatNumber(item.value) })] }) })] }, item.name));
34
35
  }) }));
35
36
  }