indas-ui 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth.d.ts +1 -0
- package/dist/auth.js +5 -0
- package/dist/cjs/auth.js +10 -0
- package/dist/cjs/components/chat/ai-messages.js +3 -2
- package/dist/cjs/components/chat/chat.js +4 -3
- package/dist/cjs/components/dashboard/FilterBar.js +31 -8
- package/dist/cjs/components/dashboard/charts/Area.js +6 -4
- package/dist/cjs/components/dashboard/charts/Bar.js +6 -4
- package/dist/cjs/components/dashboard/charts/FlowMap.js +2 -1
- package/dist/cjs/components/dashboard/charts/Funnel.js +2 -1
- package/dist/cjs/components/dashboard/charts/GeoMap.js +2 -1
- package/dist/cjs/components/dashboard/charts/Line.js +5 -3
- package/dist/cjs/components/dashboard/charts/Pareto.js +2 -1
- package/dist/cjs/components/dashboard/charts/Sankey.js +4 -3
- package/dist/cjs/components/dashboard/charts/Sparkline.js +2 -1
- package/dist/cjs/components/dashboard/charts/Treemap.js +2 -1
- package/dist/cjs/components/dashboard/charts/index.js +7 -1
- package/dist/cjs/components/dashboard/kpi/KpiBreakdown.js +3 -2
- package/dist/cjs/components/dashboard/kpi/KpiComparison.js +2 -1
- package/dist/cjs/components/dashboard/kpi/KpiQuickStats.js +2 -1
- package/dist/cjs/components/dashboard/kpi/KpiRating.js +3 -2
- package/dist/cjs/components/dashboard/time-range.js +71 -27
- package/dist/cjs/components/datagrid/columns/ColumnContextMenu.js +2 -1
- package/dist/cjs/components/datagrid/controls/menu/view/CardView.js +2 -1
- package/dist/cjs/components/datagrid/controls/menu/view/ChartView.js +2 -1
- package/dist/cjs/components/datagrid/utils/grid-helpers.js +6 -5
- package/dist/cjs/components/layout/footer.js +2 -1
- package/dist/cjs/components/layout/side-panel.js +4 -1
- package/dist/cjs/components/layout/sidebar.js +17 -2
- package/dist/cjs/components/messaging/messaging-panel-content.js +2 -2
- package/dist/cjs/components/ui/display/stats-card.js +2 -1
- package/dist/cjs/components/ui/navigation/tabs.js +4 -1
- package/dist/cjs/contexts/EmailContext.js +3 -2
- package/dist/cjs/contexts/MessagingContext.js +2 -2
- package/dist/cjs/contexts/NavigationContext.js +17 -2
- package/dist/cjs/contexts/NotificationContext.js +2 -2
- package/dist/cjs/contexts/SessionAdapterContext.js +8 -0
- package/dist/cjs/hooks/useApproval.js +2 -2
- package/dist/cjs/hooks/useBusinessAudit.js +2 -2
- package/dist/cjs/hooks/useDraft.js +2 -2
- package/dist/cjs/lib/api/utility/menu.js +8 -7
- package/dist/cjs/lib/number-format.js +60 -0
- package/dist/cjs/lib/utils.js +4 -3
- package/dist/cjs/providers.js +3 -3
- package/dist/components/auth/index.js +0 -1
- package/dist/components/chat/ai-messages.js +3 -2
- package/dist/components/chat/chat.js +4 -3
- package/dist/components/chat/index.js +0 -1
- package/dist/components/dashboard/FilterBar.d.ts +5 -1
- package/dist/components/dashboard/FilterBar.js +31 -8
- package/dist/components/dashboard/charts/Area.d.ts +5 -1
- package/dist/components/dashboard/charts/Area.js +6 -4
- package/dist/components/dashboard/charts/Bar.d.ts +5 -1
- package/dist/components/dashboard/charts/Bar.js +6 -4
- package/dist/components/dashboard/charts/FlowMap.js +2 -1
- package/dist/components/dashboard/charts/Funnel.js +2 -1
- package/dist/components/dashboard/charts/GeoMap.js +2 -1
- package/dist/components/dashboard/charts/Line.d.ts +5 -1
- package/dist/components/dashboard/charts/Line.js +5 -3
- package/dist/components/dashboard/charts/Pareto.js +2 -1
- package/dist/components/dashboard/charts/Sankey.js +4 -3
- package/dist/components/dashboard/charts/Sparkline.js +2 -1
- package/dist/components/dashboard/charts/Treemap.js +2 -1
- package/dist/components/dashboard/charts/index.d.ts +1 -0
- package/dist/components/dashboard/charts/index.js +2 -1
- package/dist/components/dashboard/index.js +0 -1
- package/dist/components/dashboard/kpi/KpiBreakdown.js +3 -2
- package/dist/components/dashboard/kpi/KpiComparison.js +2 -1
- package/dist/components/dashboard/kpi/KpiQuickStats.js +2 -1
- package/dist/components/dashboard/kpi/KpiRating.js +3 -2
- package/dist/components/dashboard/kpi/index.js +0 -1
- package/dist/components/dashboard/time-range.d.ts +12 -1
- package/dist/components/dashboard/time-range.js +71 -27
- package/dist/components/datagrid/cells/index.js +0 -1
- package/dist/components/datagrid/columns/ColumnContextMenu.js +2 -1
- package/dist/components/datagrid/controls/menu/grouping/index.js +0 -1
- package/dist/components/datagrid/controls/menu/index.js +0 -1
- package/dist/components/datagrid/controls/menu/view/CardView.js +2 -1
- package/dist/components/datagrid/controls/menu/view/ChartView.js +2 -1
- package/dist/components/datagrid/index.js +0 -1
- package/dist/components/datagrid/utils/grid-helpers.d.ts +1 -1
- package/dist/components/datagrid/utils/grid-helpers.js +3 -2
- package/dist/components/forms/date-picker/index.js +0 -1
- package/dist/components/forms/dropdown/index.js +0 -1
- package/dist/components/forms/file-attachment/index.js +0 -1
- package/dist/components/forms/index.js +0 -1
- package/dist/components/forms/remark-input/index.js +0 -1
- package/dist/components/index.js +0 -1
- package/dist/components/layout/footer.js +2 -1
- package/dist/components/layout/index.js +0 -1
- package/dist/components/layout/side-panel.js +4 -1
- package/dist/components/layout/sidebar.js +17 -2
- package/dist/components/master/editable-cells/index.js +0 -1
- package/dist/components/master/index.js +0 -1
- package/dist/components/messaging/messaging-panel-content.js +2 -2
- package/dist/components/modals/index.js +0 -1
- package/dist/components/ui/display/stats-card.js +2 -1
- package/dist/components/ui/feedback/badge.d.ts +1 -1
- package/dist/components/ui/index.js +0 -1
- package/dist/components/ui/navigation/tabs.js +4 -1
- package/dist/contexts/EmailContext.js +3 -2
- package/dist/contexts/MessagingContext.js +2 -2
- package/dist/contexts/NavigationContext.js +17 -2
- package/dist/contexts/NotificationContext.js +2 -2
- package/dist/contexts/SessionAdapterContext.d.ts +5 -0
- package/dist/contexts/SessionAdapterContext.js +7 -0
- package/dist/hooks/index.js +0 -1
- package/dist/hooks/useApproval.js +2 -2
- package/dist/hooks/useBusinessAudit.js +2 -2
- package/dist/hooks/useDraft.js +2 -2
- package/dist/index.js +0 -1
- package/dist/lib/api/activity/audit/index.js +0 -1
- package/dist/lib/api/activity/email/index.js +0 -1
- package/dist/lib/api/activity/index.js +0 -1
- package/dist/lib/api/activity/notifications/index.js +0 -1
- package/dist/lib/api/activity/remarks/index.js +0 -1
- package/dist/lib/api/approval/index.js +0 -1
- package/dist/lib/api/auth/index.js +0 -1
- package/dist/lib/api/core/index.js +0 -1
- package/dist/lib/api/draft/index.js +0 -1
- package/dist/lib/api/enquiry/index.js +0 -1
- package/dist/lib/api/estimation/index.js +0 -1
- package/dist/lib/api/index.js +0 -1
- package/dist/lib/api/master/index.js +0 -1
- package/dist/lib/api/utility/index.js +0 -1
- package/dist/lib/api/utility/menu.d.ts +7 -3
- package/dist/lib/api/utility/menu.js +8 -7
- package/dist/lib/number-format.d.ts +18 -0
- package/dist/lib/number-format.js +54 -0
- package/dist/lib/utils.js +4 -3
- package/dist/providers.d.ts +0 -1
- package/dist/providers.js +2 -2
- package/package.json +23 -8
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
+
}
|
package/dist/cjs/lib/utils.js
CHANGED
|
@@ -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 = '
|
|
18
|
-
return new Intl.NumberFormat(
|
|
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(
|
|
32
|
+
return new Intl.NumberFormat((0, number_format_1.getNumberLocale)(), {
|
|
32
33
|
minimumFractionDigits: decimals,
|
|
33
34
|
maximumFractionDigits: decimals,
|
|
34
35
|
}).format(num);
|
package/dist/cjs/providers.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
36
|
-
|
|
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; } });
|
|
@@ -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
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
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}`
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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
|
|
29
|
+
return (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsxs("div", { className: "flex items-baseline justify-between gap-2", children: [_jsx("span", { className: "text-xs font-medium text-[rgb(var(--fg-default))] truncate", children: name }), _jsxs("span", { className: "text-xs font-semibold tabular-nums shrink-0", style: { color: barColor }, children: [achievement.toFixed(0), "%"] })] }), _jsxs("div", { className: "relative h-2.5 rounded-full bg-[rgb(var(--bg-subtle))] overflow-visible", children: [_jsx("div", { className: "absolute inset-y-0 left-0 rounded-full transition-all duration-300", style: { width: `${actualPct}%`, background: barGradient } }), _jsx("div", { className: "absolute top-1/2 -translate-y-1/2 -translate-x-1/2 w-1 h-4 rounded-full ring-2 ring-[rgb(var(--bg-surface))]", style: { left: `${targetPct}%`, backgroundColor: '#475569' }, title: `Target: ${formatNumber(target)}` })] }), _jsxs("div", { className: "flex items-baseline justify-between gap-2 text-[0.65rem] text-[rgb(var(--fg-muted))] tabular-nums", children: [_jsx("span", { children: formatNumber(actual) }), _jsx("span", { children: `Target ${formatNumber(target)}` })] })] }, `${name}-${idx}`));
|
|
29
30
|
}) }));
|
|
30
31
|
}
|
|
31
32
|
const categoryData = data.map(item => item[xKey]);
|
|
32
33
|
const option = {
|
|
33
34
|
tooltip: {
|
|
34
35
|
trigger: 'item',
|
|
36
|
+
valueFormatter: (v) => (typeof v === 'number' ? (valueFormatter ?? formatNumber)(v) : String(v ?? '')),
|
|
35
37
|
backgroundColor: 'rgba(255, 255, 255, 0.98)',
|
|
36
38
|
borderColor: '#e9ecef',
|
|
37
39
|
borderWidth: 1,
|
|
@@ -64,7 +66,7 @@ export function BarChart({ data, xKey, series, height = 300, showLegend = true,
|
|
|
64
66
|
type: horizontal ? 'value' : 'category',
|
|
65
67
|
data: horizontal ? undefined : categoryData,
|
|
66
68
|
axisLine: { lineStyle: { color: '#dee2e6' } },
|
|
67
|
-
axisLabel: { color: '#6c757d', fontSize: 11 },
|
|
69
|
+
axisLabel: horizontal ? { color: '#6c757d', fontSize: 11, hideOverlap: true, alignMaxLabel: 'right', formatter: (v) => (axisFormatter ?? formatCompactNumber)(v) } : { color: '#6c757d', fontSize: 11 },
|
|
68
70
|
axisTick: { show: false },
|
|
69
71
|
splitLine: horizontal && showGrid ? {
|
|
70
72
|
lineStyle: { color: '#f0f0f0', type: 'dashed' }
|
|
@@ -74,7 +76,7 @@ export function BarChart({ data, xKey, series, height = 300, showLegend = true,
|
|
|
74
76
|
type: horizontal ? 'category' : 'value',
|
|
75
77
|
data: horizontal ? categoryData : undefined,
|
|
76
78
|
axisLine: { show: false },
|
|
77
|
-
axisLabel: { color: '#6c757d', fontSize: 11 },
|
|
79
|
+
axisLabel: horizontal ? { color: '#6c757d', fontSize: 11 } : { color: '#6c757d', fontSize: 11, formatter: (v) => (axisFormatter ?? formatCompactNumber)(v) },
|
|
78
80
|
splitLine: !horizontal && showGrid ? {
|
|
79
81
|
lineStyle: { color: '#f0f0f0', type: 'dashed' }
|
|
80
82
|
} : { show: false }
|
|
@@ -6,6 +6,7 @@ import * as echarts from 'echarts/core';
|
|
|
6
6
|
import { cn } from '../../../lib/utils.js';
|
|
7
7
|
import { INDIA_GEOJSON } from './india-map.js';
|
|
8
8
|
import { getChartTheme } from './theme.js';
|
|
9
|
+
import { formatNumber } from '../../../lib/number-format.js';
|
|
9
10
|
/**
|
|
10
11
|
* FlowMap - Geographic flow/route visualization
|
|
11
12
|
*
|
|
@@ -161,7 +162,7 @@ export function FlowMap({ markers = [], lines = [], height = 400, markerColor =
|
|
|
161
162
|
}
|
|
162
163
|
if (params.seriesType === 'effectScatter' || params.seriesType === 'scatter') {
|
|
163
164
|
const value = params.value?.[2];
|
|
164
|
-
return `<strong>${params.name}</strong>${value ? `<br/>Value: ${value
|
|
165
|
+
return `<strong>${params.name}</strong>${value ? `<br/>Value: ${formatNumber(value)}` : ''}`;
|
|
165
166
|
}
|
|
166
167
|
return `<strong>${params.name}</strong>`;
|
|
167
168
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
import { cn } from '../../../lib/utils.js';
|
|
5
|
+
import { formatNumber } from '../../../lib/number-format.js';
|
|
5
6
|
const FUNNEL_COLORS = ['#5470c6', '#3ba272', '#fac858', '#ee6666', '#73c0de', '#9a60b4', '#fc8452', '#91cc75'];
|
|
6
7
|
/**
|
|
7
8
|
* FunnelChart — centered, tapering stage-bar funnel for conversion/process flows.
|
|
@@ -30,6 +31,6 @@ export function FunnelChart({ data, height = 300, sort = 'descending', className
|
|
|
30
31
|
background: `linear-gradient(90deg, ${color}, ${color}cc)`,
|
|
31
32
|
transform: isHover ? 'translateY(-1px)' : 'none',
|
|
32
33
|
boxShadow: isHover ? `0 4px 14px ${color}55` : `0 1px 2px rgba(0,0,0,0.06)`,
|
|
33
|
-
}, title: `${item.name}: ${item.value
|
|
34
|
+
}, title: `${item.name}: ${formatNumber(item.value)} (${pct.toFixed(1)}%)`, children: [_jsx("span", { className: "text-sm font-semibold text-white truncate", children: item.name }), _jsx("span", { className: "text-xs font-medium text-white/90 tabular-nums shrink-0", children: formatNumber(item.value) })] }) })] }, item.name));
|
|
34
35
|
}) }));
|
|
35
36
|
}
|