@chenchaolong/plugin-trade-compliance-workbench 1.0.48 → 1.0.50

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 (84) hide show
  1. package/dist/lib/entities/settings.entity.d.ts +2 -0
  2. package/dist/lib/entities/settings.entity.d.ts.map +1 -1
  3. package/dist/lib/entities/settings.entity.js +8 -0
  4. package/dist/lib/entities/settings.entity.js.map +1 -1
  5. package/dist/lib/remote-components/trade-compliance-workbench/app.css +1 -1
  6. package/dist/lib/remote-components/trade-compliance-workbench/app.js +4 -4
  7. package/dist/lib/remote-ui/analytics-contracts.d.ts +121 -145
  8. package/dist/lib/remote-ui/analytics-contracts.d.ts.map +1 -1
  9. package/dist/lib/remote-ui/analytics-contracts.js +8 -3
  10. package/dist/lib/remote-ui/analytics-contracts.js.map +1 -1
  11. package/dist/lib/remote-ui/app-source.js +130 -38
  12. package/dist/lib/remote-ui/app-source.js.map +1 -1
  13. package/dist/lib/remote-ui/model.d.ts.map +1 -1
  14. package/dist/lib/remote-ui/model.js +7 -43
  15. package/dist/lib/remote-ui/model.js.map +1 -1
  16. package/dist/lib/remote-ui/pages/analytics-charts.d.ts +12 -0
  17. package/dist/lib/remote-ui/pages/analytics-charts.d.ts.map +1 -0
  18. package/dist/lib/remote-ui/pages/analytics-charts.js +41 -0
  19. package/dist/lib/remote-ui/pages/analytics-charts.js.map +1 -0
  20. package/dist/lib/remote-ui/pages/analytics-dashboard.d.ts +11 -0
  21. package/dist/lib/remote-ui/pages/analytics-dashboard.d.ts.map +1 -0
  22. package/dist/lib/remote-ui/pages/analytics-dashboard.js +59 -0
  23. package/dist/lib/remote-ui/pages/analytics-dashboard.js.map +1 -0
  24. package/dist/lib/remote-ui/pages/analytics-detail-list.d.ts +13 -0
  25. package/dist/lib/remote-ui/pages/analytics-detail-list.d.ts.map +1 -0
  26. package/dist/lib/remote-ui/pages/analytics-detail-list.js +33 -0
  27. package/dist/lib/remote-ui/pages/analytics-detail-list.js.map +1 -0
  28. package/dist/lib/remote-ui/pages/analytics-format.d.ts +7 -0
  29. package/dist/lib/remote-ui/pages/analytics-format.d.ts.map +1 -0
  30. package/dist/lib/remote-ui/pages/analytics-format.js +31 -0
  31. package/dist/lib/remote-ui/pages/analytics-format.js.map +1 -0
  32. package/dist/lib/remote-ui/pages/analytics.d.ts +6 -9
  33. package/dist/lib/remote-ui/pages/analytics.d.ts.map +1 -1
  34. package/dist/lib/remote-ui/pages/analytics.js +77 -372
  35. package/dist/lib/remote-ui/pages/analytics.js.map +1 -1
  36. package/dist/lib/remote-ui/pages/catalogs.css +1 -3
  37. package/dist/lib/remote-ui/pages/catalogs.d.ts.map +1 -1
  38. package/dist/lib/remote-ui/pages/catalogs.js +0 -2
  39. package/dist/lib/remote-ui/pages/catalogs.js.map +1 -1
  40. package/dist/lib/remote-ui/pages/files-prototype.css +1 -1
  41. package/dist/lib/remote-ui/pages/files.d.ts.map +1 -1
  42. package/dist/lib/remote-ui/pages/files.js +9 -2
  43. package/dist/lib/remote-ui/pages/files.js.map +1 -1
  44. package/dist/lib/remote-ui/pages/sales-files.css +6 -0
  45. package/dist/lib/remote-ui/pages/sales.d.ts.map +1 -1
  46. package/dist/lib/remote-ui/pages/sales.js +1 -2
  47. package/dist/lib/remote-ui/pages/sales.js.map +1 -1
  48. package/dist/lib/remote-ui/pages/settings.d.ts +1 -0
  49. package/dist/lib/remote-ui/pages/settings.d.ts.map +1 -1
  50. package/dist/lib/remote-ui/pages/settings.js +5 -2
  51. package/dist/lib/remote-ui/pages/settings.js.map +1 -1
  52. package/dist/lib/remote-ui/styles.css +160 -249
  53. package/dist/lib/runtime-validation.d.ts.map +1 -1
  54. package/dist/lib/runtime-validation.js +6 -35
  55. package/dist/lib/runtime-validation.js.map +1 -1
  56. package/dist/lib/sales-file.service.js +1 -1
  57. package/dist/lib/sales-file.service.js.map +1 -1
  58. package/dist/lib/schema-maintenance.d.ts +10 -0
  59. package/dist/lib/schema-maintenance.d.ts.map +1 -0
  60. package/dist/lib/schema-maintenance.js +34 -0
  61. package/dist/lib/schema-maintenance.js.map +1 -0
  62. package/dist/lib/types.d.ts +72 -63
  63. package/dist/lib/types.d.ts.map +1 -1
  64. package/dist/lib/types.js +2 -3
  65. package/dist/lib/types.js.map +1 -1
  66. package/dist/lib/workbench-view.provider.d.ts +12 -62
  67. package/dist/lib/workbench-view.provider.d.ts.map +1 -1
  68. package/dist/lib/workbench-view.provider.js +20 -4
  69. package/dist/lib/workbench-view.provider.js.map +1 -1
  70. package/dist/lib/workbench.middleware.d.ts +8 -8
  71. package/dist/lib/workbench.plugin.d.ts +3 -3
  72. package/dist/lib/workbench.plugin.d.ts.map +1 -1
  73. package/dist/lib/workbench.plugin.js +6 -6
  74. package/dist/lib/workbench.plugin.js.map +1 -1
  75. package/dist/lib/workbench.service.d.ts +4 -33
  76. package/dist/lib/workbench.service.d.ts.map +1 -1
  77. package/dist/lib/workbench.service.js +236 -265
  78. package/dist/lib/workbench.service.js.map +1 -1
  79. package/package.json +1 -1
  80. package/dist/lib/legacy-schema-cleanup.d.ts +0 -9
  81. package/dist/lib/legacy-schema-cleanup.d.ts.map +0 -1
  82. package/dist/lib/legacy-schema-cleanup.js +0 -28
  83. package/dist/lib/legacy-schema-cleanup.js.map +0 -1
  84. package/dist/lib/remote-ui/pages/analytics-prototype.css +0 -307
@@ -1,391 +1,96 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { Decimal } from 'decimal.js';
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
2
  import React from 'react';
4
- import { DataGrid, RiskNotice } from '../components/data-grid.js';
5
- import './analytics-prototype.css';
6
- export function analyticsPageSizeQuery(query, pageSize) {
7
- return query.mode === 'PURCHASE'
8
- ? { ...query, page: 1, pageSize }
9
- : { ...query, page: 1, pageSize };
10
- }
11
- export function AnalyticsPage({ data, committedQuery, busy, error, onQuery, onExportXlsx }) {
12
- const fallbackQuery = React.useMemo(() => queryFromData(data), [data.mode, data.dimension, data.details.page, data.details.pageSize]);
13
- const committed = committedQuery ?? fallbackQuery;
14
- const [mode, setMode] = React.useState(committed.mode);
15
- const [dimension, setDimension] = React.useState(committed.dimension);
16
- const [filters, setFilters] = React.useState(() => filterStateFromQuery(committed));
3
+ import { RiskNotice } from '../components/data-grid.js';
4
+ import { createLatestRequestGate } from '../model.js';
5
+ import { AnalyticsDashboard } from './analytics-dashboard.js';
6
+ import { AnalyticsDetailList } from './analytics-detail-list.js';
7
+ export { AnalyticsDetailList } from './analytics-detail-list.js';
8
+ const emptyDetailPage = () => ({ items: [], page: 1, pageSize: 20, total: 0, pageCount: 1 });
9
+ export function AnalyticsPage({ data, committedQuery, detailRefreshKey = 0, busy, error, onQuery, onDetailQuery, onDetailAction, onExportXlsx }) {
10
+ const committed = committedQuery ?? { filters: { orderDate: yearRange(data.currentYear) }, page: 1, pageSize: 20 };
11
+ const [filters, setFilters] = React.useState(committed.filters);
17
12
  const [period, setPeriod] = React.useState(() => periodFromQuery(committed, data.currentYear));
18
- const [activeDialog, setActiveDialog] = React.useState(null);
19
- const [drilldown, setDrilldown] = React.useState(null);
20
- const [infoModal, setInfoModal] = React.useState(null);
13
+ const [customVisible, setCustomVisible] = React.useState(false);
14
+ const [selection, setSelection] = React.useState(null);
15
+ const [detailPage, setDetailPage] = React.useState(emptyDetailPage);
16
+ const [detailBusy, setDetailBusy] = React.useState(false);
17
+ const [detailError, setDetailError] = React.useState(null);
18
+ const detailRequestGateRef = React.useRef(createLatestRequestGate());
19
+ const appliedDetailRefreshKeyRef = React.useRef(detailRefreshKey);
21
20
  React.useEffect(() => {
22
- setMode(committed.mode);
23
- setDimension(committed.dimension);
24
- setFilters(filterStateFromQuery(committed));
21
+ setFilters(committed.filters);
25
22
  setPeriod(periodFromQuery(committed, data.currentYear));
26
- }, [committed]);
27
- const typedFilters = toAnalyticsFilters(filters);
28
- const queryFor = (nextMode, nextDimension, page, nextFilters) => nextMode === 'PURCHASE'
29
- ? { mode: nextMode, dimension: nextDimension === 'SUPPLIER' ? 'SUPPLIER' : 'PRODUCT', filters: purchaseAnalyticsFilters(nextFilters), page, pageSize: data.details.pageSize || 20 }
30
- : { mode: nextMode, dimension: nextDimension === 'CUSTOMER' ? 'CUSTOMER' : 'PRODUCT', filters: salesAnalyticsFilters(nextFilters), page, pageSize: data.details.pageSize || 20 };
31
- const runQuery = (page = 1, nextMode = mode, nextDimension = dimension) => onQuery(queryFor(nextMode, nextDimension, page, typedFilters));
23
+ }, [committed, data.currentYear]);
24
+ const queryDashboard = (nextFilters) => onQuery({ filters: nextFilters, page: 1, pageSize: committed.pageSize });
32
25
  const applyPeriod = (nextPeriod) => {
33
- const orderDate = nextPeriod === 'year' ? yearRange(data.currentYear) : quickRange(3);
34
- const nextFilters = { ...typedFilters, orderDate };
26
+ const nextFilters = { orderDate: nextPeriod === 'year' ? yearRange(data.currentYear) : monthRange() };
35
27
  setPeriod(nextPeriod);
36
- setFilters(current => ({ ...current, orderDate }));
37
- void onQuery(queryFor(mode, dimension, 1, nextFilters));
28
+ setCustomVisible(false);
29
+ setFilters(nextFilters);
30
+ void queryDashboard(nextFilters);
38
31
  };
39
- const exportCurrent = (scope) => {
40
- if (!onExportXlsx)
41
- return;
42
- const request = queryFor(mode, dimension, scope === 'details' ? data.details.page : 1, typedFilters);
43
- void onExportXlsx(request.mode === 'PURCHASE' ? { mode: request.mode, dimension: request.dimension, filters: request.filters, format: 'XLSX' } : { mode: request.mode, dimension: request.dimension, filters: request.filters, format: 'XLSX' });
44
- setActiveDialog(null);
32
+ const applyCustom = () => {
33
+ setPeriod('custom');
34
+ setCustomVisible(false);
35
+ void queryDashboard(filters);
45
36
  };
46
- return _jsxs("section", { className: "tcw-analytics-page", "aria-label": "\u6570\u636E\u5206\u6790", children: [_jsxs("div", { className: "tcw-analytics-cockpit-hero", children: [_jsxs("div", { children: [_jsx("h1", { children: "\u5916\u8D38\u5408\u89C4\u7ECF\u8425\u9A7E\u9A76\u8231" }), _jsx("p", { children: "\u7ECF\u8425\u8D44\u91D1\u3001\u5408\u540C\u5BA1\u6838\u3001\u5546\u54C1\u7BA1\u63A7\u3001\u5236\u88C1\u547D\u4E2D\u3001\u5229\u6DA6\u5F02\u5E38\u7EDF\u4E00\u76D1\u63A7" })] }), _jsxs("div", { className: "tcw-analytics-page-actions", children: [onExportXlsx && _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", "aria-label": "\u5BFC\u51FA\u6570\u636E", onClick: () => setActiveDialog('export'), children: [_jsx("i", { className: "ri-download-2-line", "aria-hidden": "true" }), "\u5BFC\u51FA\u62A5\u8868"] }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: () => void runQuery(), children: [_jsx("i", { className: "ri-refresh-line", "aria-hidden": "true" }), "\u5237\u65B0"] })] })] }), _jsx("div", { className: "tcw-analytics-query", children: _jsxs("div", { className: "tcw-analytics-toolbar", children: [_jsx("span", { className: "tcw-analytics-label", children: "\u7EDF\u8BA1\u5468\u671F\uFF1A" }), _jsxs("div", { className: "tcw-segmented-control tcw-analytics-period", role: "group", "aria-label": "\u7EDF\u8BA1\u65F6\u95F4", children: [_jsxs("button", { type: "button", "aria-pressed": period === 'year', onClick: () => applyPeriod('year'), children: [_jsx("i", { className: "ri-calendar-event-line", "aria-hidden": "true" }), "\u672C\u5E74"] }), _jsxs("button", { type: "button", "aria-pressed": period === 'month', onClick: () => applyPeriod('month'), children: [_jsx("i", { className: "ri-calendar-check-line", "aria-hidden": "true" }), "\u672C\u6708"] }), _jsxs("button", { type: "button", "aria-pressed": period === 'custom', onClick: () => { setPeriod('custom'); setActiveDialog('filter'); }, children: [_jsx("i", { className: "ri-calendar-2-line", "aria-hidden": "true" }), "\u81EA\u5B9A\u4E49\u533A\u95F4"] })] })] }) }), _jsx(AnalyticsCockpit, { data: data, onDrilldown: setDrilldown, onInfo: setInfoModal }), error && _jsx(RiskNotice, { children: error }), busy && _jsxs("div", { className: "tcw-inline-loading", role: "status", children: [_jsx("span", { className: "tcw-spinner", "aria-hidden": "true" }), "\u6570\u636E\u52A0\u8F7D\u4E2D"] }), drilldown && _jsx(AnalyticsDrilldownPanel, { data: data, drilldown: drilldown, onClose: () => setDrilldown(null) }), infoModal && _jsx(AnalyticsInfoModal, { info: infoModal, onClose: () => setInfoModal(null) }), _jsx(AdvancedFilterModal, { active: activeDialog === 'filter', filters: filters, onFilterChange: (key, value) => setFilters(current => ({ ...current, [key]: value })), onApply: () => { setPeriod('custom'); void runQuery(); setActiveDialog(null); }, onReset: () => { setPeriod('year'); setFilters({ orderDate: yearRange(data.currentYear) }); void onQuery(queryFor(mode, dimension, 1, { orderDate: yearRange(data.currentYear) })); setActiveDialog(null); }, onClose: () => setActiveDialog(null) }), onExportXlsx && _jsx(BusinessExportModal, { active: activeDialog === 'export', total: data.details.total, onClose: () => setActiveDialog(null), onExport: exportCurrent })] });
47
- }
48
- const analyticsExportFields = [
49
- { key: 'label', label: '客户/供应商名称' },
50
- { key: 'dimension', label: '统计维度' },
51
- { key: 'orderCount', label: '订单数' },
52
- { key: 'amountCny', label: '订单金额(CNY)' },
53
- { key: 'status', label: '审核状态' },
54
- { key: 'createdAt', label: '创建时间' }
55
- ];
56
- function AnalyticsCockpit({ data, onDrilldown, onInfo }) {
57
- const dashboard = data.dashboard;
58
- const customerValueRows = dashboard.customerValueTop5.map(item => ({ key: item.customerId, name: item.customerName, salesAmountCny: item.salesAmountCny, estimatedGrossProfitCny: item.estimatedGrossProfitCny, grossProfitRate: item.estimatedGrossProfitRate, unreceivedCny: item.unreceivedCny, riskStatus: item.riskStatus }));
59
- const receivableRiskRows = dashboard.receivableRiskTop5.map(item => ({ key: item.customerId, name: item.customerName, unreceivedCny: item.unreceivedCny, orderCount: item.orderCount, latestOrderDate: item.latestOrderDate }));
60
- const supplierRiskRows = dashboard.supplierRiskTop5.map(item => ({ key: item.supplierId, name: item.supplierName, unpaidCny: item.unpaidCny, sanctionHitCount: item.sanctionHitCount, productRiskHitCount: item.productRiskHitCount, purchaseOrderCount: item.purchaseOrderCount }));
61
- const salesFileRows = dashboard.salesFileReviews.map(item => ({ key: item.fileId, fileName: item.fileName, fileType: item.fileType, orderNo: item.salesOrderNo, reason: item.reason, status: '待复核' }));
62
- const riskOrderRows = dashboard.riskOrders.map(item => ({ key: item.orderKey, orderNo: item.orderNo, partyName: item.partyName, riskType: item.riskType, riskAmountCny: item.riskAmountCny, status: item.status }));
63
- const monthlyDeltas = monthlyDeltaLabels(dashboard.monthlyTrend);
64
- const kpis = [
65
- { label: '采购总额', value: compactMoney(dashboard.kpis.purchaseTotalCny), subtext: monthlyDeltas.purchase, fullValue: `${money(dashboard.kpis.purchaseTotalCny)} CNY`, icon: 'ri-shopping-cart-line', tone: 'money' },
66
- { label: '销售总额', value: compactMoney(dashboard.kpis.salesTotalCny), subtext: monthlyDeltas.sales, fullValue: `${money(dashboard.kpis.salesTotalCny)} CNY`, icon: 'ri-bar-chart-line', tone: 'money' },
67
- { label: '已收款金额', value: compactMoney(dashboard.kpis.receivedCny), subtext: `回款率 ${amountRatio(dashboard.kpis.receivedCny, dashboard.kpis.salesTotalCny)}`, fullValue: `${money(dashboard.kpis.receivedCny)} CNY`, icon: 'ri-time-line', tone: 'success' },
68
- { label: '未收款金额', value: compactMoney(dashboard.kpis.unreceivedCny), subtext: `销售占比 ${amountRatio(dashboard.kpis.unreceivedCny, dashboard.kpis.salesTotalCny)}`, fullValue: `${money(dashboard.kpis.unreceivedCny)} CNY`, icon: 'ri-alarm-warning-line', tone: 'warn' },
69
- { label: '已付款金额', value: compactMoney(dashboard.kpis.paidCny), subtext: `付款率 ${amountRatio(dashboard.kpis.paidCny, dashboard.kpis.purchaseTotalCny)}`, fullValue: `${money(dashboard.kpis.paidCny)} CNY`, icon: 'ri-secure-payment-line', tone: 'success' },
70
- { label: '未付款金额', value: compactMoney(dashboard.kpis.unpaidCny), subtext: `采购占比 ${amountRatio(dashboard.kpis.unpaidCny, dashboard.kpis.purchaseTotalCny)}`, fullValue: `${money(dashboard.kpis.unpaidCny)} CNY`, icon: 'ri-error-warning-line', tone: 'warn' },
71
- { label: '未退税金额', value: compactMoney(dashboard.kpis.unrefundedCny), subtext: `退税缺口 ${amountRatio(dashboard.kpis.unrefundedCny, addMoney(dashboard.kpis.refundedCny, dashboard.kpis.unrefundedCny))}`, fullValue: `${money(dashboard.kpis.unrefundedCny)} CNY`, icon: 'ri-refund-2-line', tone: 'warn' },
72
- { label: '预计毛利率', value: percent(dashboard.kpis.estimatedGrossProfitRate), subtext: `毛利 ${compactMoney(dashboard.kpis.estimatedGrossProfitCny)}`, icon: 'ri-funds-box-line', tone: 'success' },
73
- { label: '合规待办数', value: `${dashboard.kpis.complianceTodoCount} 项`, subtext: `队列 ${dashboard.todos.length} 类`, icon: 'ri-shield-check-line', tone: 'warn' },
74
- { label: '风险敞口金额', value: compactMoney(dashboard.kpis.riskExposureCny), subtext: `资金风险占比 ${amountRatio(dashboard.kpis.riskExposureCny, addMoney(dashboard.kpis.salesTotalCny, dashboard.kpis.purchaseTotalCny))}`, fullValue: `${money(dashboard.kpis.riskExposureCny)} CNY`, icon: 'ri-shield-flash-line', tone: 'risk' }
75
- ];
76
- return _jsxs("section", { className: "tcw-analytics-cockpit", "aria-label": "\u5916\u8D38\u5408\u89C4\u7ECF\u8425\u9A7E\u9A76\u8231", children: [_jsx("div", { className: "tcw-analytics-kpi-grid", children: kpis.map(item => _jsxs("button", { type: "button", className: `is-${item.tone}`, title: 'fullValue' in item ? item.fullValue : item.value, onClick: () => onDrilldown({ title: `${item.label}下钻明细`, tone: item.tone === 'success' || item.tone === 'warn' ? 'money' : item.tone }), children: [_jsx("span", { children: item.label }), _jsx("i", { className: item.icon, "aria-hidden": "true" }), _jsx("strong", { children: item.value }), _jsx("small", { children: item.subtext.startsWith('较上月') ? _jsxs(_Fragment, { children: [_jsx("em", { children: "\u8F83\u4E0A\u6708" }), _jsx("b", { children: item.subtext.replace('较上月 ', '') })] }) : item.subtext })] }, item.label)) }), _jsxs("div", { className: "tcw-analytics-cockpit-layout", children: [_jsxs("div", { className: "tcw-analytics-layout-row tcw-analytics-layout-row--charts", children: [_jsx(Panel, { title: "\u5408\u89C4\u5F85\u529E\u961F\u5217", icon: "ri-list-check-3", className: "tcw-analytics-panel--queue", children: _jsx(TodoQueue, { rows: dashboard.todos, onOpen: todo => onInfo({ title: `${todo.label}列表`, rows: todoDetailRows(todo) }) }) }), _jsxs(Panel, { title: "\u6708\u5EA6\u7ECF\u8425\u8D8B\u52BF", icon: "ri-bar-chart-box-line", className: "tcw-analytics-panel--trend", children: [_jsxs("div", { className: "tcw-analytics-chart-legend", children: [_jsxs("span", { children: [_jsx("i", { className: "is-sales" }), "\u9500\u552E\u989D"] }), _jsxs("span", { children: [_jsx("i", { className: "is-purchase" }), "\u91C7\u8D2D\u989D"] }), _jsxs("span", { children: [_jsx("i", { className: "is-profit" }), "\u9884\u8BA1\u6BDB\u5229"] }), _jsxs("span", { children: [_jsx("i", { className: "is-received" }), "\u56DE\u6B3E\u91D1\u989D"] })] }), _jsx(TrendLineChart, { points: dashboard.monthlyTrend })] }), _jsx(Panel, { title: "\u8D44\u91D1\u98CE\u9669\u7ED3\u6784", icon: "ri-stack-line", className: "tcw-analytics-panel--capital", children: _jsx(MoneyStructure, { kpis: dashboard.kpis }) }), _jsx(Panel, { title: "\u5408\u89C4\u98CE\u9669\u5206\u5E03", icon: "ri-donut-chart-line", className: "tcw-analytics-panel--risk", children: _jsx(RiskDistribution, { items: dashboard.riskDistribution, onOpen: item => onInfo({ title: `${item.label}明细`, rows: [{ label: item.label, value: `${item.count} 项${item.amountCny ? ` · ${money(item.amountCny)} CNY` : ''}`, actionLabel: '查看' }] }) }) })] }), _jsxs("div", { className: "tcw-analytics-layout-row tcw-analytics-layout-row--top5", children: [_jsx(Panel, { title: "\u5BA2\u6237\u4EF7\u503C TOP5", icon: "ri-vip-crown-line", className: "tcw-analytics-panel--customer", children: _jsx(CustomerValue, { rows: customerValueRows }) }), _jsx(Panel, { title: "\u672A\u6536\u98CE\u9669 TOP5", icon: "ri-bank-line", className: "tcw-analytics-panel--receivable", children: _jsx(ReceivableRisk, { rows: receivableRiskRows }) }), _jsx(Panel, { title: "\u4F9B\u5E94\u5546\u98CE\u9669 TOP5", icon: "ri-truck-line", className: "tcw-analytics-panel--supplier", children: _jsx(SupplierRisk, { rows: supplierRiskRows }) })] }), _jsxs("div", { className: "tcw-analytics-layout-row tcw-analytics-layout-row--bottom", children: [_jsx(Panel, { title: "\u9500\u552E\u6587\u4EF6\u590D\u6838", icon: "ri-file-search-line", className: "tcw-analytics-panel--files", children: _jsx(SalesFileReviews, { rows: salesFileRows, onOpen: row => onInfo({ title: `${row.fileName}复核`, rows: [{ label: row.fileName, value: `${row.fileType} · ${row.orderNo} · ${row.status}`, actionLabel: '复核' }] }) }) }), _jsx(Panel, { title: "\u98CE\u9669\u8BA2\u5355\u660E\u7EC6", icon: "ri-file-warning-line", className: "tcw-analytics-panel--risk-orders", children: _jsx(RiskOrders, { rows: riskOrderRows, onOpen: row => onInfo({ title: `${row.orderNo}风险明细`, rows: [{ label: row.orderNo, value: `${row.partyName} · ${row.riskType} · ${money(row.riskAmountCny)} CNY`, actionLabel: '跟进' }] }) }) }), _jsx(Panel, { title: "\u6307\u6807\u6570\u636E\u6765\u6E90", icon: "ri-database-2-line", className: "tcw-analytics-panel--source", children: _jsx(DataSourceList, { rows: dashboard.dataSources, onOpen: source => onInfo({ title: source.label, rows: [{ label: source.label, value: source.description, actionLabel: '查看' }] }) }) })] })] })] });
77
- }
78
- function Panel({ title, icon, className = '', action, children }) {
79
- return _jsxs("section", { className: `tcw-analytics-panel ${className}`.trim(), children: [_jsxs("header", { children: [_jsxs("strong", { children: [_jsx("i", { className: icon, "aria-hidden": "true" }), title] }), action] }), children] });
80
- }
81
- function MoneyStructure({ kpis }) {
82
- const items = [
83
- { label: '已收款', value: kpis.receivedCny, color: '#16a34a' },
84
- { label: '未收款', value: kpis.unreceivedCny, color: '#f97316' },
85
- { label: '已付款', value: kpis.paidCny, color: '#1769ff' },
86
- { label: '未付款', value: kpis.unpaidCny, color: '#ff7a00' },
87
- { label: '已退税', value: kpis.refundedCny, color: '#26b6c8' },
88
- { label: '未退税', value: kpis.unrefundedCny, color: '#ff4d55' }
89
- ];
90
- const total = Math.max(items.reduce((sum, item) => sum + (Number(item.value) || 0), 0), 1);
91
- const max = Math.max(...items.map(item => Number(item.value) || 0), 1);
92
- return _jsxs("div", { className: "tcw-analytics-money-structure", children: [_jsxs("div", { className: "tcw-analytics-money-head", children: [_jsx("span", { children: "\u91D1\u989D\uFF08\u4E07\u5143\uFF09" }), _jsx("span", { children: "\u5360\u6BD4" })] }), items.map(item => _jsxs("div", { className: "tcw-analytics-money-row", children: [_jsxs("span", { children: [_jsx("i", { style: { background: item.color } }), item.label] }), _jsx("em", { children: _jsx("i", { style: { width: `${Math.max(5, (Number(item.value) || 0) / max * 100)}%`, background: item.color } }) }), _jsx("b", { children: compactNumber(Number(item.value) / 10000) }), _jsx("strong", { children: percentRatio(Number(item.value) || 0, total) })] }, item.label))] });
93
- }
94
- function RiskDistribution({ items, onOpen }) {
95
- const total = items.reduce((sum, item) => sum + item.count, 0);
96
- const colors = ['#1769ff', '#26b6c8', '#ff9f1c', '#8b5cf6', '#5ac85a', '#ff4d55'];
97
- const segments = items.length ? items.map((item, index) => ({ item, color: colors[index % colors.length] })) : [{ item: { key: 'empty', label: '暂无风险', count: 1, amountCny: '0' }, color: '#e7edf5' }];
98
- let cursor = 0;
99
- const conic = segments.map(({ item, color }) => {
100
- const start = cursor;
101
- cursor += ((total > 0 ? item.count / total : 1) * 100);
102
- return `${color} ${start.toFixed(2)}% ${cursor.toFixed(2)}%`;
103
- }).join(', ');
104
- const style = { '--risk-donut': `conic-gradient(${conic})` };
105
- return _jsxs("div", { className: "tcw-analytics-risk-distribution", children: [_jsxs("div", { className: "tcw-analytics-donut", style: style, children: [_jsx("strong", { children: "\u5408\u8BA1" }), _jsx("span", { children: total })] }), _jsx("dl", { children: items.map((item, index) => _jsxs("button", { type: "button", onClick: () => onOpen(item), children: [_jsxs("dt", { children: [_jsx("i", { className: "ri-checkbox-blank-circle-fill", style: { color: colors[index % colors.length] }, "aria-hidden": "true" }), item.label] }), _jsxs("dd", { children: [item.count, " (", percentRatio(item.count, total), ")"] })] }, item.key)) })] });
106
- }
107
- function CustomerValue({ rows }) {
108
- return rows.length ? _jsxs("table", { className: "tcw-analytics-dashboard-table tcw-analytics-table--customer", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", {}), _jsx("th", { children: "\u5BA2\u6237\u540D\u79F0" }), _jsx("th", { children: "\u9500\u552E\u989D\uFF08\u4EBA\u6C11\u5E01\uFF09" }), _jsx("th", { children: "\u9884\u8BA1\u6BDB\u5229\uFF08\u4EBA\u6C11\u5E01\uFF09" }), _jsx("th", { children: "\u6BDB\u5229\u7387" }), _jsx("th", { children: "\u672A\u6536\u6B3E\u91D1\u989D\uFF08\u4EBA\u6C11\u5E01\uFF09" }), _jsx("th", { children: "\u98CE\u9669\u72B6\u6001" })] }) }), _jsx("tbody", { children: rows.map((row, index) => _jsxs("tr", { children: [_jsx("td", { children: index + 1 }), _jsx("td", { title: row.name, children: row.name }), _jsx("td", { children: money(row.salesAmountCny) }), _jsx("td", { children: money(row.estimatedGrossProfitCny) }), _jsx("td", { children: percent(row.grossProfitRate) }), _jsx("td", { children: money(row.unreceivedCny) }), _jsx("td", { children: _jsx(RiskBadge, { status: row.riskStatus }) })] }, row.key)) })] }) : _jsx(EmptyText, { text: "\u6682\u65E0\u5BA2\u6237\u4EF7\u503C\u6392\u540D" });
109
- }
110
- function ReceivableRisk({ rows }) {
111
- return rows.length ? _jsxs("table", { className: "tcw-analytics-dashboard-table tcw-analytics-table--receivable", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "\u5BA2\u6237\u540D\u79F0" }), _jsx("th", { children: "\u672A\u6536\u6B3E\u91D1\u989D\uFF08\u4EBA\u6C11\u5E01\uFF09" }), _jsx("th", { children: "\u903E\u671F\u5929\u6570" }), _jsx("th", { children: "\u5173\u8054\u9500\u552E\u8BA2\u5355\u6570" }), _jsx("th", { children: "\u6700\u8FD1\u6536\u6B3E\u65E5\u671F" })] }) }), _jsx("tbody", { children: rows.map((row, index) => _jsxs("tr", { children: [_jsx("td", { title: row.name, children: row.name }), _jsx("td", { children: money(row.unreceivedCny) }), _jsx("td", { children: 67 - index * 9 }), _jsx("td", { children: row.orderCount }), _jsx("td", { children: shortDate(row.latestOrderDate) })] }, row.key)) })] }) : _jsx(EmptyText, { text: "\u6682\u65E0\u672A\u6536\u98CE\u9669" });
112
- }
113
- function SupplierRisk({ rows }) {
114
- return rows.length ? _jsxs("table", { className: "tcw-analytics-dashboard-table tcw-analytics-table--supplier", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "\u4F9B\u5E94\u5546\u540D\u79F0" }), _jsx("th", { children: "\u672A\u4ED8\u6B3E\u91D1\u989D\uFF08\u4EBA\u6C11\u5E01\uFF09" }), _jsx("th", { children: "\u547D\u4E2D\u5236\u88C1\u72B6\u6001" }), _jsx("th", { children: "\u5173\u8054\u91C7\u8D2D\u8BA2\u5355\u6570" }), _jsx("th", { children: "\u5546\u54C1\u98CE\u9669\u547D\u4E2D\u6570" })] }) }), _jsx("tbody", { children: rows.map(row => _jsxs("tr", { children: [_jsx("td", { title: row.name, children: row.name }), _jsx("td", { children: money(row.unpaidCny) }), _jsx("td", { children: _jsx("span", { className: row.sanctionHitCount > 0 ? 'tcw-analytics-status is-danger' : 'tcw-analytics-status is-ok', children: row.sanctionHitCount > 0 ? '命中制裁' : '未命中' }) }), _jsx("td", { children: row.purchaseOrderCount }), _jsx("td", { children: row.productRiskHitCount })] }, row.key)) })] }) : _jsx(EmptyText, { text: "\u6682\u65E0\u4F9B\u5E94\u5546\u98CE\u9669" });
115
- }
116
- function SalesFileReviews({ rows, onOpen }) {
117
- return rows.length ? _jsxs("table", { className: "tcw-analytics-dashboard-table tcw-analytics-table--files", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "\u6587\u4EF6\u7F16\u53F7" }), _jsx("th", { children: "\u6587\u4EF6\u7C7B\u578B" }), _jsx("th", { children: "\u5173\u8054\u9500\u552E\u8BA2\u5355" }), _jsx("th", { children: "\u5F02\u5E38\u539F\u56E0" }), _jsx("th", { children: "\u64CD\u4F5C" })] }) }), _jsx("tbody", { children: rows.map(row => _jsxs("tr", { children: [_jsx("td", { title: row.fileName, children: row.fileName }), _jsx("td", { children: row.fileType }), _jsx("td", { children: row.orderNo }), _jsx("td", { title: row.reason, children: row.reason }), _jsx("td", { children: _jsxs("button", { type: "button", className: "tcw-analytics-row-action", onClick: () => onOpen(row), children: [_jsx("i", { className: "ri-file-search-line", "aria-hidden": "true" }), "\u590D\u6838"] }) })] }, row.key)) })] }) : _jsx(EmptyText, { text: "\u6682\u65E0\u9500\u552E\u6587\u4EF6\u590D\u6838" });
118
- }
119
- function RiskOrders({ rows, onOpen }) {
120
- return rows.length ? _jsxs("table", { className: "tcw-analytics-dashboard-table tcw-analytics-risk-orders", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "\u8BA2\u5355\u7F16\u53F7" }), _jsx("th", { children: "\u5BA2\u6237" }), _jsx("th", { children: "\u98CE\u9669\u7C7B\u578B" }), _jsx("th", { children: "\u98CE\u9669\u91D1\u989D\uFF08\u4EBA\u6C11\u5E01\uFF09" }), _jsx("th", { children: "\u72B6\u6001" }), _jsx("th", { children: "\u64CD\u4F5C" })] }) }), _jsx("tbody", { children: rows.map(row => _jsxs("tr", { children: [_jsx("td", { children: row.orderNo }), _jsx("td", { title: row.partyName, children: row.partyName }), _jsx("td", { children: _jsx("em", { children: row.riskType }) }), _jsx("td", { children: money(row.riskAmountCny) }), _jsx("td", { children: _jsx("span", { className: "tcw-analytics-status is-warn", children: row.status }) }), _jsx("td", { children: _jsxs("button", { type: "button", className: "tcw-analytics-row-action", onClick: () => onOpen(row), children: [_jsx("i", { className: "ri-eye-line", "aria-hidden": "true" }), "\u8DDF\u8FDB"] }) })] }, row.key)) })] }) : _jsx(EmptyText, { text: "\u6682\u65E0\u98CE\u9669\u8BA2\u5355" });
121
- }
122
- function DataSourceList({ rows, onOpen }) {
123
- const icons = ['ri-file-list-3-line', 'ri-file-chart-line', 'ri-file-shield-2-line', 'ri-file-warning-line', 'ri-file-check-line', 'ri-file-copy-2-line', 'ri-file-paper-2-line', 'ri-file-text-line', 'ri-file-list-2-line', 'ri-refund-2-line', 'ri-file-search-line', 'ri-exchange-cny-line'];
124
- return _jsx("div", { className: "tcw-analytics-source-list", children: rows.map((source, index) => _jsxs("button", { type: "button", title: source.description, onClick: () => onOpen(source), children: [_jsx("i", { className: icons[index % icons.length], "aria-hidden": "true" }), _jsx("strong", { children: source.label })] }, source.label)) });
125
- }
126
- function TodoQueue({ rows, onOpen }) {
127
- return rows.length ? _jsxs("div", { className: "tcw-analytics-todo-board", children: [_jsxs("div", { className: "tcw-analytics-todo-head", children: [_jsx("span", { children: "\u5F85\u529E\u4E8B\u9879" }), _jsx("span", { children: "\u6570\u91CF" }), _jsx("span", { children: "\u5173\u8054\u91D1\u989D" }), _jsx("span", { children: "\u4F18\u5148\u7EA7" })] }), rows.map((row, index) => _jsxs("button", { type: "button", className: "tcw-analytics-todo-row", onClick: () => onOpen(row), children: [_jsxs("span", { title: row.description, children: [_jsx("i", { className: "ri-file-list-2-line", "aria-hidden": "true" }), row.label] }), _jsx("b", { children: row.count }), _jsx("em", { children: row.amountCny ? `¥${money(row.amountCny)}` : '' }), _jsx(RiskBadge, { status: row.priority === 'high' ? 'HIGH' : row.priority === 'medium' ? 'MEDIUM' : 'LOW' })] }, row.key))] }) : _jsx(EmptyText, { text: "\u6682\u65E0\u5408\u89C4\u5F85\u529E" });
128
- }
129
- function RiskBadge({ status }) {
130
- const label = status === 'HIGH' ? '高' : status === 'MEDIUM' ? '中' : status === 'LOW' ? '低' : status;
131
- return _jsx("span", { className: `tcw-analytics-risk-badge is-${status.toLocaleLowerCase()}`, children: label });
132
- }
133
- function TrendLineChart({ points }) {
134
- const months = Array.from({ length: 12 }, (_, index) => points.find(point => point.month === index + 1) ?? { month: index + 1, purchaseAmountCny: '0', salesAmountCny: '0', estimatedGrossProfitCny: '0', receivedCny: '0' });
135
- const max = Math.max(...months.flatMap(point => [Number(point.purchaseAmountCny) || 0, Number(point.salesAmountCny) || 0, Number(point.estimatedGrossProfitCny) || 0, Number(point.receivedCny) || 0]), 1);
136
- const roundedMax = Math.max(1, Math.ceil(max / 1000 / 5000) * 5000);
137
- const yTicks = Array.from({ length: 5 }, (_, index) => roundedMax - (roundedMax / 4) * index);
138
- const coords = (key) => months.map((point, index) => {
139
- const x = 56 + index * (516 / 11);
140
- const y = 132 - ((Number(point[key]) || 0) / max) * 104;
141
- return { x, y };
142
- });
143
- const line = (key) => coords(key).map(point => `${point.x.toFixed(1)},${point.y.toFixed(1)}`).join(' ');
144
- return _jsxs("div", { className: "tcw-analytics-line-chart", children: [_jsx("span", { className: "tcw-analytics-trend-unit", children: "\u91D1\u989D\uFF08\u4E07\u5143\uFF09" }), _jsxs("svg", { viewBox: "0 0 600 170", role: "img", "aria-label": "\u6708\u5EA6\u7ECF\u8425\u8D8B\u52BF\u6298\u7EBF\u56FE", children: [_jsx("g", { className: "tcw-analytics-line-grid", children: [28, 54, 80, 106, 132].map((y, index) => _jsxs(React.Fragment, { children: [_jsx("line", { x1: "56", x2: "576", y1: y, y2: y }), _jsx("text", { className: "tcw-analytics-trend-axis", x: "42", y: y + 4, children: formatChartTick(yTicks[index]) })] }, y)) }), _jsx("polyline", { className: "is-purchase", points: line('purchaseAmountCny') }), _jsx("polyline", { className: "is-sales", points: line('salesAmountCny') }), _jsx("polyline", { className: "is-profit", points: line('estimatedGrossProfitCny') }), _jsx("polyline", { className: "is-received", points: line('receivedCny') }), ['salesAmountCny', 'purchaseAmountCny', 'estimatedGrossProfitCny', 'receivedCny'].flatMap(key => coords(key).map((point, index) => _jsx("circle", { className: key === 'salesAmountCny' ? 'is-sales' : key === 'purchaseAmountCny' ? 'is-purchase' : key === 'receivedCny' ? 'is-received' : 'is-profit', cx: point.x, cy: point.y, r: "3" }, `${key}-${index}`))), months.map((point, index) => _jsxs("text", { x: 56 + index * (516 / 11), y: "156", children: [point.month, "\u6708"] }, point.month))] })] });
145
- }
146
- function monthlyDeltaLabels(points) {
147
- const sorted = [...points].sort((a, b) => a.month - b.month);
148
- let latestIndex = -1;
149
- for (let index = sorted.length - 1; index >= 0; index -= 1) {
150
- const point = sorted[index];
151
- if (Number(point.purchaseAmountCny) > 0 || Number(point.salesAmountCny) > 0 || Number(point.receivedCny) > 0) {
152
- latestIndex = index;
153
- break;
37
+ const loadDetail = React.useCallback(async (nextSelection, page, pageSize) => {
38
+ const requestId = detailRequestGateRef.current.begin();
39
+ setDetailBusy(true);
40
+ setDetailError(null);
41
+ try {
42
+ let result = await onDetailQuery({ kind: nextSelection.kind, categoryKey: nextSelection.categoryKey, filters, page, pageSize });
43
+ if (page > result.pageCount)
44
+ result = await onDetailQuery({ kind: nextSelection.kind, categoryKey: nextSelection.categoryKey, filters, page: result.pageCount, pageSize });
45
+ if (!detailRequestGateRef.current.commit(requestId))
46
+ return;
47
+ setDetailPage(result);
48
+ }
49
+ catch (cause) {
50
+ if (!detailRequestGateRef.current.commit(requestId))
51
+ return;
52
+ setDetailError(cause instanceof Error ? cause.message : '明细加载失败');
53
+ setDetailPage({ items: [], page, pageSize, total: 0, pageCount: 1 });
154
54
  }
155
- }
156
- const latest = latestIndex >= 0 ? sorted[latestIndex] : undefined;
157
- const previous = latestIndex > 0 ? sorted[latestIndex - 1] : undefined;
158
- return {
159
- purchase: `较上月 ${deltaLabel(latest?.purchaseAmountCny, previous?.purchaseAmountCny)}`,
160
- sales: `较上月 ${deltaLabel(latest?.salesAmountCny, previous?.salesAmountCny)}`,
161
- received: `较上月 ${deltaLabel(latest?.receivedCny, previous?.receivedCny)}`,
162
- profit: `较上月 ${deltaLabel(latest?.estimatedGrossProfitCny, previous?.estimatedGrossProfitCny)}`
55
+ finally {
56
+ detailRequestGateRef.current.finish(requestId);
57
+ if (detailRequestGateRef.current.activeCount() === 0)
58
+ setDetailBusy(false);
59
+ }
60
+ }, [filters, onDetailQuery]);
61
+ const openDetail = nextSelection => {
62
+ setSelection(nextSelection);
63
+ setDetailPage(emptyDetailPage());
64
+ void loadDetail(nextSelection, 1, 20);
163
65
  };
164
- }
165
- function EmptyText({ text }) {
166
- return _jsx("small", { className: "tcw-analytics-empty", children: text });
167
- }
168
- function AdvancedFilterModal({ active, filters, onFilterChange, onApply, onReset, onClose }) {
169
- return _jsx("div", { className: "tcw-analytics-modal-deck", hidden: !active, "aria-hidden": !active, children: _jsx(AnalyticsModal, { title: "\u81EA\u5B9A\u4E49\u533A\u95F4", active: active, onClose: onClose, footer: _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: onReset, children: [_jsx("i", { className: "ri-restart-line", "aria-hidden": "true" }), "\u91CD\u7F6E"] }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", onClick: onApply, children: [_jsx("i", { className: "ri-check-line", "aria-hidden": "true" }), "\u786E\u5B9A"] })] }), children: _jsx("div", { className: "tcw-analytics-modal-grid", children: _jsx(ModalRange, { label: "\u7EDF\u8BA1\u65E5\u671F", value: filters.orderDate, onChange: value => onFilterChange('orderDate', value) }) }) }) });
170
- }
171
- function AnalyticsInfoModal({ info, onClose }) {
172
- return _jsx("div", { className: "tcw-analytics-modal-deck", "aria-hidden": false, children: _jsx(AnalyticsModal, { title: info.title, active: true, onClose: onClose, footer: _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: onClose, children: [_jsx("i", { className: "ri-close-line", "aria-hidden": "true" }), "\u5173\u95ED"] }), children: info.rows.length ? _jsxs("table", { className: "tcw-analytics-info-list", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "\u540D\u79F0" }), _jsx("th", { children: "\u660E\u7EC6" }), _jsx("th", { children: "\u64CD\u4F5C" })] }) }), _jsx("tbody", { children: info.rows.map(row => _jsxs("tr", { children: [_jsx("td", { children: row.label }), _jsx("td", { children: row.value }), _jsx("td", { children: _jsxs("button", { type: "button", className: "tcw-analytics-info-action", children: [_jsx("i", { className: "ri-eye-line", "aria-hidden": "true" }), row.actionLabel ?? '查看'] }) })] }, `${row.label}-${row.value}`)) })] }) : _jsx(EmptyText, { text: "\u6682\u65E0\u660E\u7EC6" }) }) });
173
- }
174
- function BusinessExportModal({ active, total, onClose, onExport }) {
175
- return _jsx("div", { className: "tcw-analytics-modal-deck", hidden: !active, "aria-hidden": !active, children: _jsx(AnalyticsModal, { title: "\u4E1A\u52A1\u5BFC\u51FA", active: active, onClose: onClose, footer: _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: onClose, children: [_jsx("i", { className: "ri-close-line", "aria-hidden": "true" }), "\u53D6\u6D88"] }), children: _jsxs("div", { className: "tcw-analytics-export-panel", children: [_jsxs("button", { type: "button", onClick: () => onExport('summary'), children: [_jsx("i", { className: "ri-dashboard-line", "aria-hidden": "true" }), _jsx("strong", { children: "\u7BA1\u7406\u6C47\u603B\u62A5\u8868" }), _jsx("span", { children: "\u6307\u6807\u6C47\u603B\u3001TOP \u6392\u540D\u548C\u8D8B\u52BF\u6570\u636E\uFF0C\u9002\u5408\u7ECF\u8425\u590D\u76D8\u3002" })] }), _jsxs("button", { type: "button", onClick: () => onExport('details'), children: [_jsx("i", { className: "ri-file-list-3-line", "aria-hidden": "true" }), _jsx("strong", { children: "\u5F53\u524D\u660E\u7EC6\u6E05\u5355" }), _jsxs("span", { children: ["\u5BFC\u51FA\u5F53\u524D\u7B5B\u9009\u540E\u7684 ", total, " \u6761\u660E\u7EC6\u6570\u636E\u3002"] })] }), _jsxs("button", { type: "button", onClick: () => onExport('exceptions'), children: [_jsx("i", { className: "ri-alarm-warning-line", "aria-hidden": "true" }), _jsx("strong", { children: "\u5F02\u5E38/\u98CE\u9669\u6E05\u5355" }), _jsx("span", { children: "\u672A\u6536\u6B3E\u3001\u672A\u4ED8\u6B3E\u3001\u672A\u9000\u7A0E\u548C\u5408\u89C4\u5F85\u5904\u7406\u4E8B\u9879\u3002" })] })] }) }) });
176
- }
177
- function AnalyticsDetailsModal({ mode, dimension, data, onClose, onPageChange, onPageSizeChange }) {
178
- return _jsx("div", { className: "tcw-analytics-modal-deck", "aria-hidden": false, children: _jsx(AnalyticsModal, { title: "\u5B8C\u6574\u5206\u6790\u660E\u7EC6", active: true, onClose: onClose, footer: _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: onClose, children: [_jsx("i", { className: "ri-close-line", "aria-hidden": "true" }), "\u5173\u95ED"] }), children: _jsxs("div", { className: "tcw-analytics-detail-modal", children: [_jsxs("div", { className: "tcw-analytics-detail-note", children: [_jsx("i", { className: "ri-information-line", "aria-hidden": "true" }), "\u5B8C\u6574\u660E\u7EC6\u7528\u4E8E\u6838\u5BF9\u9A7E\u9A76\u8231\u6307\u6807\u53E3\u5F84\uFF0C\u4E0D\u76F4\u63A5\u5360\u7528\u9A7E\u9A76\u8231\u4E3B\u89C6\u56FE\u3002"] }), _jsx(DataGrid, { columns: detailColumns(mode, dimension), rows: data.details.items, rowKey: "key", pagination: data.details, onPageChange: onPageChange, onPageSizeChange: onPageSizeChange, emptyText: "\u6682\u65E0\u5206\u6790\u660E\u7EC6" })] }) }) });
179
- }
180
- function AnalyticsDrilldownPanel({ data, drilldown, onClose }) {
181
- const total = data.details.items.reduce((sum, row) => sum.plus(row.amountCny), new Decimal(0)).toFixed(2);
182
- return _jsxs("section", { className: `tcw-analytics-drilldown is-${drilldown.tone}`, "aria-label": "\u4E0B\u94BB\u660E\u7EC6", children: [_jsxs("header", { children: [_jsx("strong", { children: drilldown.title }), _jsxs("button", { type: "button", onClick: onClose, children: [_jsx("i", { className: "ri-close-line", "aria-hidden": "true" }), "\u5173\u95ED"] })] }), _jsxs("div", { className: "tcw-analytics-drilldown-metrics", children: [_jsxs("article", { children: [_jsx("span", { children: "\u4E0B\u94BB\u660E\u7EC6" }), _jsx("strong", { children: data.details.total })] }), _jsxs("article", { children: [_jsx("span", { children: "\u5F53\u524D\u9875\u8BA2\u5355\u6570" }), _jsx("strong", { children: data.details.items.reduce((sum, row) => sum + row.orderCount, 0) })] }), _jsxs("article", { children: [_jsx("span", { children: "\u5F53\u524D\u9875\u91D1\u989D\uFF08CNY\uFF09" }), _jsx("strong", { children: money(total) })] })] })] });
183
- }
184
- function AnalyticsDialogs({ activeDialog, mode, dimension, filters, data, chartType, exportScope, exportFormat, exportFields, onFilterChange, onApplyFilters, onResetFilters, onClose, onDialog, onChartType, onExportScope, onExportFormat, onToggleExportField, onExport }) {
185
- const open = activeDialog !== null;
186
- return _jsxs("div", { className: "tcw-analytics-modal-deck", hidden: !open, "aria-hidden": !open, children: [_jsx(AnalyticsModal, { title: "\u7B5B\u9009\u6761\u4EF6", active: activeDialog === 'filter', onClose: onClose, footer: _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: onResetFilters, children: [_jsx("i", { className: "ri-restart-line", "aria-hidden": "true" }), "\u91CD\u7F6E"] }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", onClick: onApplyFilters, children: [_jsx("i", { className: "ri-check-line", "aria-hidden": "true" }), "\u786E\u5B9A"] })] }), children: _jsxs("div", { className: "tcw-analytics-modal-grid", children: [_jsx("div", { className: "tcw-analytics-quick-range", "aria-label": "\u7EDF\u8BA1\u65F6\u95F4", children: ['今日', '近7天', '近30天', '本月'].map((label, index) => _jsxs("button", { type: "button", className: index === 2 ? 'is-active' : undefined, onClick: () => onFilterChange('orderDate', quickRange(index)), children: [_jsx("i", { className: "ri-calendar-line", "aria-hidden": "true" }), label] }, label)) }), _jsx(ModalRange, { label: "\u4E0B\u5355\u65E5\u671F", value: filters.orderDate, onChange: value => onFilterChange('orderDate', value) }), _jsx(ModalText, { label: mode === 'PURCHASE' ? '供应商' : '客户', value: String(filters[mode === 'PURCHASE' ? 'supplier' : 'customer'] ?? ''), onChange: value => onFilterChange(mode === 'PURCHASE' ? 'supplier' : 'customer', value) }), _jsx(ModalText, { label: "\u5546\u54C1\u540D\u79F0", value: String(filters.productName ?? ''), onChange: value => onFilterChange('productName', value) }), _jsx(ModalText, { label: "\u6D77\u5173\u7F16\u7801", value: String(filters.hsCode ?? ''), onChange: value => onFilterChange('hsCode', value) }), ['客户类型', '国家/地区', '产品类别', '风险等级', '审核状态', '金额范围'].map(label => _jsxs("label", { className: "tcw-form-field", children: [_jsx("span", { children: label }), _jsx("input", { disabled: true, placeholder: "\u540E\u7AEF\u7B5B\u9009\u5B57\u6BB5\u5F85\u63A5\u5165" })] }, label))] }) }), _jsx(AnalyticsModal, { title: "\u5BFC\u51FA\u6570\u636E", active: activeDialog === 'export', onClose: onClose, footer: _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: onClose, children: [_jsx("i", { className: "ri-close-line", "aria-hidden": "true" }), "\u53D6\u6D88"] }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", disabled: exportFormat === 'CSV', onClick: onExport, children: [_jsx("i", { className: "ri-download-2-line", "aria-hidden": "true" }), "\u5BFC\u51FA"] })] }), children: _jsxs("div", { className: "tcw-analytics-export-panel", children: [_jsxs("fieldset", { children: [_jsx("legend", { children: "\u5BFC\u51FA\u8303\u56F4" }), _jsxs("label", { children: [_jsx("input", { type: "radio", checked: exportScope === 'page', onChange: () => onExportScope('page') }), "\u5F53\u524D\u9875\u6570\u636E"] }), _jsxs("label", { children: [_jsx("input", { type: "radio", checked: exportScope === 'all', onChange: () => onExportScope('all') }), "\u5168\u90E8\u6570\u636E\uFF08\u5171 ", data.details.total, " \u6761\uFF09"] })] }), _jsxs("fieldset", { children: [_jsx("legend", { children: "\u5BFC\u51FA\u5B57\u6BB5" }), _jsx("div", { className: "tcw-analytics-export-fields", children: analyticsExportFields.map(field => _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: exportFields.has(field.key), onChange: () => onToggleExportField(field.key) }), field.label] }, field.key)) })] }), _jsxs("fieldset", { children: [_jsx("legend", { children: "\u5BFC\u51FA\u683C\u5F0F" }), _jsxs("label", { children: [_jsx("input", { type: "radio", checked: exportFormat === 'XLSX', onChange: () => onExportFormat('XLSX') }), "Excel\uFF08.xlsx\uFF09"] }), _jsxs("label", { children: [_jsx("input", { type: "radio", checked: exportFormat === 'CSV', onChange: () => onExportFormat('CSV') }), "CSV\uFF08.csv\uFF09"] })] })] }) }), _jsx(AnalyticsModal, { title: "\u56FE\u8868\u914D\u7F6E", active: activeDialog === 'chart', onClose: onClose, footer: _jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", onClick: onClose, children: [_jsx("i", { className: "ri-check-line", "aria-hidden": "true" }), "\u786E\u5B9A"] }), children: _jsxs("div", { className: "tcw-analytics-chart-config", children: [[['bar', '柱状图', 'ri-bar-chart-fill'], ['line', '折线图', 'ri-line-chart-line'], ['donut', '饼图', 'ri-pie-chart-line']].map(([type, label, icon]) => _jsxs("button", { type: "button", className: chartType === type ? 'is-active' : undefined, onClick: () => onChartType(type), children: [_jsx("i", { className: icon, "aria-hidden": "true" }), label] }, type)), _jsxs("label", { className: "tcw-form-field", children: [_jsx("span", { children: "\u7EF4\u5EA6\uFF08X\u8F74\uFF09" }), _jsx("input", { readOnly: true, value: dimensionLabel(dimension) })] }), _jsxs("label", { className: "tcw-form-field", children: [_jsx("span", { children: "\u6307\u6807\uFF08Y\u8F74\uFF09" }), _jsx("input", { readOnly: true, value: "\u8BA2\u5355\u91D1\u989D\uFF08CNY\uFF09" })] })] }) }), _jsx(AnalyticsModal, { title: "\u4FDD\u5B58\u62A5\u8868", active: activeDialog === 'save', onClose: onClose, footer: _jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", onClick: onClose, children: [_jsx("i", { className: "ri-save-line", "aria-hidden": "true" }), "\u4FDD\u5B58"] }), children: _jsxs("div", { className: "tcw-analytics-modal-grid", children: [_jsxs("label", { className: "tcw-form-field", children: [_jsx("span", { children: "\u62A5\u8868\u540D\u79F0" }), _jsx("input", { placeholder: "\u8BF7\u8F93\u5165\u62A5\u8868\u540D\u79F0" })] }), _jsxs("label", { className: "tcw-form-field", children: [_jsx("span", { children: "\u62A5\u8868\u63CF\u8FF0" }), _jsx("textarea", { placeholder: "\u8BF7\u8F93\u5165\u62A5\u8868\u63CF\u8FF0\uFF08\u9009\u586B\uFF09" })] })] }) }), _jsx(AnalyticsModal, { title: "\u5B9A\u65F6\u62A5\u544A\u8BBE\u7F6E", active: activeDialog === 'schedule', onClose: onClose, footer: _jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", onClick: onClose, children: [_jsx("i", { className: "ri-save-line", "aria-hidden": "true" }), "\u4FDD\u5B58"] }), children: _jsxs("div", { className: "tcw-analytics-modal-grid", children: [_jsxs("label", { className: "tcw-form-field", children: [_jsx("span", { children: "\u62A5\u544A\u540D\u79F0" }), _jsx("input", { placeholder: "\u8BF7\u8F93\u5165\u62A5\u544A\u540D\u79F0" })] }), _jsxs("label", { className: "tcw-form-field", children: [_jsx("span", { children: "\u53D1\u9001\u9891\u7387" }), _jsx("select", { children: _jsx("option", { children: "\u6BCF\u5468\u4E00 09:00" }) })] }), _jsxs("label", { className: "tcw-form-field", children: [_jsx("span", { children: "\u63A5\u6536\u4EBA" }), _jsx("input", { placeholder: "\u9009\u62E9\u63A5\u6536\u4EBA" })] })] }) }), _jsx(AnalyticsModal, { title: "\u5220\u9664\u786E\u8BA4", active: activeDialog === 'delete', onClose: onClose, footer: _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: onClose, children: [_jsx("i", { className: "ri-close-line", "aria-hidden": "true" }), "\u53D6\u6D88"] }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--danger", onClick: onClose, children: [_jsx("i", { className: "ri-delete-bin-line", "aria-hidden": "true" }), "\u786E\u8BA4\u5220\u9664"] })] }), children: _jsxs("div", { className: "tcw-confirm-message is-danger", children: [_jsx("i", { className: "ri-error-warning-line", "aria-hidden": "true" }), _jsx("p", { children: "\u786E\u8BA4\u5220\u9664\u6B64\u62A5\u8868\u5417\uFF1F\u5220\u9664\u540E\u5C06\u65E0\u6CD5\u6062\u590D\u3002" })] }) }), _jsx(AnalyticsModal, { title: "\u6307\u6807\u8BF4\u660E", active: activeDialog === 'metrics', onClose: onClose, footer: _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: onClose, children: [_jsx("i", { className: "ri-close-line", "aria-hidden": "true" }), "\u5173\u95ED"] }), children: _jsxs("dl", { className: "tcw-analytics-metric-help", children: [_jsxs("div", { children: [_jsx("dt", { children: "\u8BA2\u5355\u91D1\u989D\uFF08CNY\uFF09" }), _jsx("dd", { children: "\u5F53\u524D\u7B5B\u9009\u6761\u4EF6\u4E0B\u7684\u8BA2\u5355\u91D1\u989D\u6C47\u603B\u3002" })] }), _jsxs("div", { children: [_jsx("dt", { children: "\u98CE\u9669\u7B49\u7EA7\u5206\u5E03" }), _jsx("dd", { children: "\u4F9D\u636E\u5F53\u524D\u660E\u7EC6\u6570\u636E\u63A8\u7B97\u7684\u98CE\u9669\u6982\u89C8\u3002" })] }), _jsxs("div", { children: [_jsx("dt", { children: "\u5408\u89C4\u5BA1\u6838\u6982\u89C8" }), _jsx("dd", { children: "\u7528\u4E8E\u89C2\u5BDF\u5BA1\u6838\u72B6\u6001\u5206\u5E03\u548C\u5904\u7406\u538B\u529B\u3002" })] })] }) })] });
187
- }
188
- function AnalyticsModal({ title, active, onClose, footer, children }) {
189
- return _jsxs("section", { className: "tcw-analytics-modal", hidden: !active, role: "dialog", "aria-label": title, children: [_jsxs("header", { children: [_jsx("strong", { className: "tcw-analytics-modal-title", children: title }), _jsx("button", { type: "button", className: "tcw-icon-button", onClick: onClose, "aria-label": "\u5173\u95ED", children: _jsx("i", { className: "ri-close-line", "aria-hidden": "true" }) })] }), _jsx("div", { className: "tcw-modal-body", children: children }), _jsx("footer", { children: footer })] });
190
- }
191
- function ModalText({ label, value, onChange }) {
192
- return _jsxs("label", { className: "tcw-form-field", children: [_jsx("span", { children: label }), _jsx("input", { type: "search", value: value, placeholder: `请输入${label}`, onChange: event => onChange(event.target.value) })] });
193
- }
194
- function ModalRange({ label, value, onChange }) {
195
- const range = value && typeof value === 'object' ? value : {};
196
- return _jsxs("label", { className: "tcw-form-field tcw-form-field--full", children: [_jsx("span", { children: label }), _jsxs("div", { className: "tcw-analytics-date-range", children: [_jsx("input", { type: "date", value: range.from ?? '', onChange: event => onChange({ ...range, from: event.target.value }) }), _jsx("em", { children: "\u81F3" }), _jsx("input", { type: "date", value: range.to ?? '', onChange: event => onChange({ ...range, to: event.target.value }) })] })] });
197
- }
198
- function quickRange(index) {
199
- const today = new Date();
200
- const end = today.toISOString().slice(0, 10);
201
- const start = new Date(today);
202
- if (index === 1)
203
- start.setDate(today.getDate() - 6);
204
- if (index === 2)
205
- start.setDate(today.getDate() - 29);
206
- if (index === 3)
207
- start.setDate(1);
208
- return { from: start.toISOString().slice(0, 10), to: end };
209
- }
210
- function yearRange(year) {
211
- return { from: `${year}-01-01`, to: `${year}-12-31` };
66
+ React.useEffect(() => {
67
+ if (appliedDetailRefreshKeyRef.current === detailRefreshKey)
68
+ return;
69
+ appliedDetailRefreshKeyRef.current = detailRefreshKey;
70
+ if (!selection)
71
+ return;
72
+ void loadDetail(selection, detailPage.page, detailPage.pageSize);
73
+ }, [detailPage.page, detailPage.pageSize, detailRefreshKey, loadDetail, selection]);
74
+ if (selection)
75
+ return _jsx(AnalyticsDetailList, { title: selection.title, page: detailPage, busy: detailBusy, error: detailError, onBack: () => { detailRequestGateRef.current.invalidate(); setSelection(null); setDetailError(null); setDetailBusy(false); }, onPageChange: page => void loadDetail(selection, page, detailPage.pageSize), onPageSizeChange: pageSize => void loadDetail(selection, 1, pageSize), onAction: row => void onDetailAction?.(row) });
76
+ return _jsxs("section", { className: "tcw-analytics-page", "aria-label": "\u6570\u636E\u5206\u6790", children: [_jsxs("div", { className: "tcw-analytics-cockpit-hero", children: [_jsxs("div", { children: [_jsx("h1", { children: "\u5916\u8D38\u5408\u89C4\u7ECF\u8425\u9A7E\u9A76\u8231" }), _jsx("p", { children: "\u7ECF\u8425\u8D44\u91D1\u3001\u5408\u540C\u5BA1\u6838\u3001\u5546\u54C1\u7BA1\u63A7\u3001\u5236\u88C1\u547D\u4E2D\u3001\u5229\u6DA6\u5F02\u5E38\u7EDF\u4E00\u76D1\u63A7" })] }), _jsxs("div", { className: "tcw-analytics-page-actions", children: [onExportXlsx && _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: () => void onExportXlsx({ filters, format: 'XLSX' }), children: [_jsx("i", { className: "ri-download-2-line", "aria-hidden": "true" }), "\u5BFC\u51FA\u62A5\u8868"] }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: () => void queryDashboard(filters), children: [_jsx("i", { className: "ri-refresh-line", "aria-hidden": "true" }), "\u5237\u65B0"] })] })] }), _jsx("div", { className: "tcw-analytics-query", children: _jsxs("div", { className: "tcw-analytics-toolbar", children: [_jsx("span", { className: "tcw-analytics-label", children: "\u7EDF\u8BA1\u5468\u671F\uFF1A" }), _jsxs("div", { className: "tcw-segmented-control tcw-analytics-period", role: "group", "aria-label": "\u7EDF\u8BA1\u65F6\u95F4", children: [_jsxs("button", { type: "button", "aria-pressed": period === 'year', onClick: () => applyPeriod('year'), children: [_jsx("i", { className: "ri-calendar-event-line", "aria-hidden": "true" }), "\u672C\u5E74"] }), _jsxs("button", { type: "button", "aria-pressed": period === 'month', onClick: () => applyPeriod('month'), children: [_jsx("i", { className: "ri-calendar-check-line", "aria-hidden": "true" }), "\u672C\u6708"] }), _jsxs("button", { type: "button", "aria-pressed": period === 'custom', onClick: () => { setPeriod('custom'); setCustomVisible(true); }, children: [_jsx("i", { className: "ri-calendar-2-line", "aria-hidden": "true" }), "\u81EA\u5B9A\u4E49\u533A\u95F4"] })] }), customVisible && _jsxs("div", { className: "tcw-analytics-custom-range", children: [_jsx("input", { type: "date", "aria-label": "\u7EDF\u8BA1\u5F00\u59CB\u65E5\u671F", value: filters.orderDate?.from ?? '', onChange: event => setFilters({ orderDate: { ...filters.orderDate, from: event.target.value } }) }), _jsx("span", { children: "\u81F3" }), _jsx("input", { type: "date", "aria-label": "\u7EDF\u8BA1\u7ED3\u675F\u65E5\u671F", value: filters.orderDate?.to ?? '', onChange: event => setFilters({ orderDate: { ...filters.orderDate, to: event.target.value } }) }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", onClick: applyCustom, children: [_jsx("i", { className: "ri-check-line", "aria-hidden": "true" }), "\u5E94\u7528"] })] })] }) }), _jsx(AnalyticsDashboard, { data: data, onOpenDetail: openDetail }), error && _jsx(RiskNotice, { children: error }), busy && _jsxs("div", { className: "tcw-inline-loading", role: "status", children: [_jsx("span", { className: "tcw-spinner", "aria-hidden": "true" }), "\u6570\u636E\u52A0\u8F7D\u4E2D"] })] });
77
+ }
78
+ function yearRange(year) { return { from: `${year}-01-01`, to: `${year}-12-31` }; }
79
+ function monthRange(now = new Date()) {
80
+ const formatter = new Intl.DateTimeFormat('en-CA', { timeZone: 'Asia/Shanghai', year: 'numeric', month: '2-digit', day: '2-digit' });
81
+ const today = formatter.format(now);
82
+ return { from: `${today.slice(0, 7)}-01`, to: today };
212
83
  }
213
84
  function periodFromQuery(query, year) {
214
85
  const range = query.filters.orderDate;
215
- if (!range?.from || !range.to)
86
+ if (!range?.from && !range?.to)
216
87
  return 'year';
217
- const yearDates = yearRange(year);
218
- if (range.from === yearDates.from && range.to === yearDates.to)
88
+ const annual = yearRange(year);
89
+ const monthly = monthRange();
90
+ if (range?.from === annual.from && range?.to === annual.to)
219
91
  return 'year';
220
- const month = quickRange(3);
221
- if (range.from === month.from && range.to === month.to)
92
+ if (range?.from === monthly.from && range?.to === monthly.to)
222
93
  return 'month';
223
94
  return 'custom';
224
95
  }
225
- function todoDetailRows(todo) {
226
- const count = Math.max(1, Math.min(todo.count, 5));
227
- return Array.from({ length: count }, (_, index) => ({
228
- label: `${todo.label}-${String(index + 1).padStart(2, '0')}`,
229
- value: `${todo.description} · 优先级 ${todo.priority === 'high' ? '高' : todo.priority === 'medium' ? '中' : '低'} · 待处理`,
230
- actionLabel: '审核'
231
- }));
232
- }
233
- function formatChartTick(value) {
234
- return value >= 1 ? value.toLocaleString('zh-CN', { maximumFractionDigits: 0 }) : value.toLocaleString('zh-CN', { maximumFractionDigits: 1 });
235
- }
236
- function PrototypeState({ index, title, area, children }) {
237
- return _jsxs("section", { className: "tcw-analytics-prototype-state", style: area ? { gridArea: area } : undefined, "aria-label": `${index} ${title}`, children: [_jsxs("div", { className: "tcw-analytics-prototype-label", children: [_jsx("span", { className: "tcw-analytics-prototype-index", children: index }), _jsx("strong", { children: title })] }), children] });
238
- }
239
- function PrototypeCard({ title, children, className = '' }) {
240
- return _jsxs("div", { className: `tcw-analytics-prototype-card ${className}`.trim(), children: [_jsxs("header", { children: [_jsx("strong", { children: title }), _jsx("i", { className: "ri-close-line", "aria-hidden": "true" })] }), children] });
241
- }
242
- function PrototypeField({ label, children }) {
243
- return _jsxs("label", { className: "tcw-analytics-prototype-field", children: [_jsx("span", { children: label }), children] });
244
- }
245
- function PrototypeActions({ danger = false, confirm = '确定' }) {
246
- return _jsxs("footer", { children: [_jsxs("button", { type: "button", children: [_jsx("i", { className: "ri-restart-line", "aria-hidden": "true" }), "\u53D6\u6D88"] }), _jsxs("button", { type: "button", className: danger ? 'is-danger' : 'is-primary', children: [_jsx("i", { className: danger ? 'ri-delete-bin-line' : 'ri-check-line', "aria-hidden": "true" }), confirm] })] });
247
- }
248
- export function AnalyticsPrototypeBoard({ onClose }) {
249
- return _jsxs("div", { className: "tcw-analytics-prototype-shell", children: [_jsxs("button", { type: "button", className: "tcw-analytics-prototype-close", onClick: onClose, children: [_jsx("i", { className: "ri-close-line", "aria-hidden": "true" }), "\u5173\u95ED\u539F\u578B\u72B6\u6001\u603B\u89C8"] }), _jsxs("div", { className: "tcw-analytics-prototype-board", children: [_jsx(PrototypeState, { index: 1, title: "\u6570\u636E\u5206\u6790\u9996\u9875", area: "home", children: _jsx(AnalyticsPrototypeHome, {}) }), _jsx(PrototypeState, { index: 2, title: "\u7B5B\u9009\u6761\u4EF6\u5F39\u7A97", area: "filter", children: _jsx(AnalyticsPrototypeFilter, {}) }), _jsx(PrototypeState, { index: 3, title: "\u98CE\u9669\u7B49\u7EA7\u4E0B\u94BB\u660E\u7EC6", area: "drill", children: _jsx(AnalyticsPrototypeDrilldown, {}) }), _jsx(PrototypeState, { index: 4, title: "\u5BFC\u51FA\u6570\u636E", area: "export", children: _jsx(AnalyticsPrototypeExport, {}) }), _jsxs("div", { className: "tcw-analytics-prototype-bottom", style: { gridArea: 'bottom' }, children: [_jsx(PrototypeState, { index: 5, title: "\u56FE\u8868\u914D\u7F6E", children: _jsx(AnalyticsPrototypeChartConfig, {}) }), _jsx(PrototypeState, { index: 6, title: "\u4FDD\u5B58\u62A5\u8868", children: _jsx(AnalyticsPrototypeSave, {}) }), _jsx(PrototypeState, { index: 7, title: "\u5B9A\u65F6\u62A5\u544A\u8BBE\u7F6E", children: _jsx(AnalyticsPrototypeSchedule, {}) }), _jsx(PrototypeState, { index: 8, title: "\u5220\u9664\u786E\u8BA4", children: _jsx(AnalyticsPrototypeDelete, {}) }), _jsx(PrototypeState, { index: 9, title: "\u6307\u6807\u8BF4\u660E", children: _jsx(AnalyticsPrototypeMetrics, {}) })] })] })] });
250
- }
251
- function AnalyticsPrototypeHome() {
252
- const metrics = [['客户总数', '1,256'], ['订单总数', '3,246'], ['销售金额(USD)', '12,456,789'], ['高风险订单数', '87'], ['受制裁命中数', '23']];
253
- return _jsxs("div", { className: "tcw-analytics-prototype-home", children: [_jsxs("header", { children: [_jsx("i", { className: "ri-shield-check-line", "aria-hidden": "true" }), _jsx("strong", { children: "\u5916\u8D38\u5408\u89C4\u5DE5\u4F5C\u53F0" }), _jsx("span", { children: "\u6570\u636E\u5206\u6790" }), _jsx("time", { children: "2024-07-01 - 2024-07-31" })] }), _jsx("div", { className: "tcw-analytics-prototype-metrics", children: metrics.map(([label, value], index) => _jsxs("article", { children: [_jsx("span", { children: label }), _jsx("strong", { className: index === 3 ? 'is-risk' : '', children: value }), _jsxs("small", { children: ["\u8F83\u4E0A\u6708 ", _jsx("b", { children: index % 2 ? '↑ 8.3%' : '↑ 12.5%' })] })] }, label)) }), _jsxs("div", { className: "tcw-analytics-prototype-charts", children: [_jsxs("section", { children: [_jsx("strong", { children: "\u5408\u89C4\u98CE\u9669\u8D8B\u52BF" }), _jsxs("div", { className: "tcw-prototype-line-chart", children: [_jsx("i", {}), _jsx("i", {}), _jsx("i", {})] })] }), _jsxs("section", { children: [_jsx("strong", { children: "\u98CE\u9669\u7B49\u7EA7\u5206\u5E03" }), _jsxs("div", { className: "tcw-prototype-donut", children: [_jsx("b", { children: "3,246" }), _jsx("span", { children: "\u8BA2\u5355\u603B\u6570" })] })] }), _jsxs("section", { children: [_jsx("strong", { children: "\u5BA2\u6237\u54C1\u7C7B\u9500\u552E\u91D1\u989D\uFF08USD\uFF09" }), _jsx("div", { className: "tcw-prototype-bars", children: [72, 64, 61, 51, 56, 24].map((height, index) => _jsx("i", { style: { height: `${height}%` } }, index)) })] }), _jsxs("section", { children: [_jsx("strong", { children: "\u5408\u89C4\u5BA1\u6838\u6982\u89C8" }), _jsxs("table", { children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "\u5BA1\u6838\u72B6\u6001" }), _jsx("th", { children: "\u8BA2\u5355\u6570" }), _jsx("th", { children: "\u5360\u6BD4" })] }) }), _jsxs("tbody", { children: [_jsxs("tr", { children: [_jsx("td", { children: "\u5F85\u5BA1\u6838" }), _jsx("td", { children: "156" }), _jsx("td", { children: "4.8%" })] }), _jsxs("tr", { children: [_jsx("td", { children: "\u5BA1\u6838\u4E2D" }), _jsx("td", { children: "327" }), _jsx("td", { children: "10.1%" })] }), _jsxs("tr", { children: [_jsx("td", { children: "\u5BA1\u6838\u901A\u8FC7" }), _jsx("td", { children: "2,642" }), _jsx("td", { children: "81.3%" })] })] })] })] })] })] });
254
- }
255
- function AnalyticsPrototypeFilter() {
256
- return _jsxs(PrototypeCard, { title: "\u7B5B\u9009\u6761\u4EF6", children: [_jsxs("div", { className: "tcw-analytics-prototype-form", children: [_jsxs(PrototypeField, { label: "\u7EDF\u8BA1\u65F6\u95F4", children: [_jsxs("div", { className: "tcw-prototype-quick", children: [_jsxs("button", { type: "button", children: [_jsx("i", { className: "ri-calendar-line", "aria-hidden": "true" }), "\u4ECA\u65E5"] }), _jsxs("button", { type: "button", children: [_jsx("i", { className: "ri-calendar-line", "aria-hidden": "true" }), "\u8FD17\u5929"] }), _jsxs("button", { type: "button", className: "is-active", children: [_jsx("i", { className: "ri-calendar-check-line", "aria-hidden": "true" }), "\u8FD130\u5929"] }), _jsxs("button", { type: "button", children: [_jsx("i", { className: "ri-calendar-line", "aria-hidden": "true" }), "\u672C\u6708"] })] }), _jsx("input", { readOnly: true, value: "2024-07-01 \u2192 2024-07-31" })] }), _jsx(PrototypeField, { label: "\u5BA2\u6237\u7C7B\u578B", children: _jsx("select", { defaultValue: "", children: _jsx("option", { value: "", children: "\u8BF7\u9009\u62E9\u5BA2\u6237\u7C7B\u578B" }) }) }), _jsx(PrototypeField, { label: "\u56FD\u5BB6/\u5730\u533A", children: _jsx("select", { defaultValue: "", children: _jsx("option", { value: "", children: "\u8BF7\u9009\u62E9\u56FD\u5BB6/\u5730\u533A" }) }) }), _jsx(PrototypeField, { label: "\u4EA7\u54C1\u7C7B\u522B", children: _jsx("select", { defaultValue: "", children: _jsx("option", { value: "", children: "\u8BF7\u9009\u62E9\u4EA7\u54C1\u7C7B\u522B" }) }) }), _jsx(PrototypeField, { label: "\u98CE\u9669\u7B49\u7EA7", children: _jsxs("div", { className: "tcw-prototype-checks", children: [_jsx("span", { children: "\u5168\u90E8" }), _jsx("span", { children: "\u25A1 \u9AD8\u98CE\u9669" }), _jsx("span", { children: "\u25A1 \u4E2D\u98CE\u9669" }), _jsx("span", { children: "\u25A1 \u4F4E\u98CE\u9669" })] }) }), _jsx(PrototypeField, { label: "\u5BA1\u6838\u72B6\u6001", children: _jsxs("div", { className: "tcw-prototype-checks", children: [_jsx("span", { children: "\u5168\u90E8" }), _jsx("span", { children: "\u25A1 \u5F85\u5BA1\u6838" }), _jsx("span", { children: "\u25A1 \u5BA1\u6838\u4E2D" }), _jsx("span", { children: "\u25A1 \u5BA1\u6838\u901A\u8FC7" })] }) }), _jsx(PrototypeField, { label: "\u91D1\u989D\u8303\u56F4\uFF08USD\uFF09", children: _jsxs("div", { className: "tcw-prototype-range", children: [_jsx("input", { placeholder: "\u6700\u5C0F\u503C" }), _jsx("input", { placeholder: "\u6700\u5927\u503C" })] }) })] }), _jsx(PrototypeActions, { confirm: "\u786E\u5B9A" })] });
257
- }
258
- function AnalyticsPrototypeDrilldown() {
259
- return _jsxs("div", { className: "tcw-analytics-prototype-drill", children: [_jsxs("header", { children: [_jsx("span", { children: "\u6570\u636E\u5206\u6790" }), _jsx("i", { className: "ri-arrow-right-s-line", "aria-hidden": "true" }), _jsx("strong", { children: "\u98CE\u9669\u7B49\u7EA7\u660E\u7EC6" })] }), _jsxs("div", { className: "tcw-prototype-drill-filters", children: [_jsx("span", { children: "\u7EDF\u8BA1\u65F6\u95F4\uFF1A2024-07-01 \u2192 2024-07-31" }), _jsx("span", { children: "\u98CE\u9669\u7B49\u7EA7\uFF1A\u9AD8\u98CE\u9669" }), _jsxs("button", { type: "button", children: [_jsx("i", { className: "ri-restart-line", "aria-hidden": "true" }), "\u91CD\u7F6E"] })] }), _jsx("div", { className: "tcw-analytics-prototype-metrics", children: [['高风险订单数', '87'], ['高风险金额(USD)', '1,234,567'], ['涉及客户数', '56'], ['平均订单金额(USD)', '14,190']].map(([label, value]) => _jsxs("article", { children: [_jsx("span", { children: label }), _jsx("strong", { children: value }), _jsx("small", { children: "\u8F83\u4E0A\u6708 \u2191 5.6%" })] }, label)) }), _jsx("strong", { children: "\u9AD8\u98CE\u9669\u8BA2\u5355\u660E\u7EC6" }), _jsxs("table", { children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "\u8BA2\u5355\u53F7" }), _jsx("th", { children: "\u5BA2\u6237\u540D\u79F0" }), _jsx("th", { children: "\u56FD\u5BB6/\u5730\u533A" }), _jsx("th", { children: "\u4EA7\u54C1\u7C7B\u522B" }), _jsx("th", { children: "\u8BA2\u5355\u91D1\u989D\uFF08USD\uFF09" }), _jsx("th", { children: "\u98CE\u9669\u655E\u53E3" }), _jsx("th", { children: "\u5BA1\u6838\u72B6\u6001" }), _jsx("th", { children: "\u64CD\u4F5C" })] }) }), _jsx("tbody", { children: ['0001', '0002', '0003', '0004', '0005'].map((id, index) => _jsxs("tr", { children: [_jsxs("td", { children: ["SO-202407-", id] }), _jsx("td", { children: ['ABC Trading Co., Ltd.', 'Global Import LLC', 'Middle East Supply', 'Afro Tech Ltd.', 'Asia Pacific Co.'][index] }), _jsx("td", { children: "\u4F0A\u6717" }), _jsx("td", { children: "\u7535\u5B50\u5143\u4EF6" }), _jsx("td", { children: 25_000 - index * 2_500 }), _jsx("td", { children: "\u547D\u4E2D\u5236\u88C1\u540D\u5355" }), _jsx("td", { children: "\u5BA1\u6838\u4E2D" }), _jsx("td", { children: _jsx("a", { children: "\u67E5\u770B" }) })] }, id)) })] })] });
260
- }
261
- function AnalyticsPrototypeExport() {
262
- return _jsxs(PrototypeCard, { title: "\u5BFC\u51FA\u6570\u636E", children: [_jsxs("div", { className: "tcw-analytics-prototype-export", children: [_jsx(PrototypeField, { label: "\u5BFC\u51FA\u8303\u56F4", children: _jsx("span", { children: "\u25C9 \u5F53\u524D\u9875\u6570\u636E\u3000\u25CB \u5168\u90E8\u6570\u636E\uFF08\u5171 3,246 \u6761\uFF09" }) }), _jsx(PrototypeField, { label: "\u5BFC\u51FA\u5B57\u6BB5", children: _jsx("div", { className: "tcw-prototype-export-list", children: ['全部字段', '客户名称', '国家/地区', '产品类别', '订单金额(USD)', '风险等级', '审核状态', '创建时间'].map(field => _jsxs("span", { children: ["\u2611 ", field] }, field)) }) }), _jsx(PrototypeField, { label: "\u5BFC\u51FA\u683C\u5F0F", children: _jsx("span", { children: "\u25C9 Excel\uFF08.xlsx\uFF09\u3000\u25CB CSV\uFF08.csv\uFF09" }) })] }), _jsx(PrototypeActions, { confirm: "\u5BFC\u51FA" })] });
263
- }
264
- function AnalyticsPrototypeChartConfig() {
265
- return _jsxs(PrototypeCard, { title: "\u56FE\u8868\u914D\u7F6E", children: [_jsxs("div", { className: "tcw-analytics-prototype-form", children: [_jsx(PrototypeField, { label: "\u56FE\u8868\u7C7B\u578B", children: _jsxs("div", { className: "tcw-prototype-chart-types", children: [_jsxs("button", { type: "button", className: "is-active", children: [_jsx("i", { className: "ri-bar-chart-fill", "aria-hidden": "true" }), "\u67F1\u72B6\u56FE"] }), _jsxs("button", { type: "button", children: [_jsx("i", { className: "ri-line-chart-line", "aria-hidden": "true" }), "\u6298\u7EBF\u56FE"] }), _jsxs("button", { type: "button", children: [_jsx("i", { className: "ri-pie-chart-line", "aria-hidden": "true" }), "\u997C\u56FE"] })] }) }), _jsx(PrototypeField, { label: "\u7EF4\u5EA6\uFF08X\u8F74\uFF09", children: _jsx("select", { children: _jsx("option", { children: "\u4EA7\u54C1\u7C7B\u522B" }) }) }), _jsx(PrototypeField, { label: "\u6307\u6807\uFF08Y\u8F74\uFF09", children: _jsx("select", { children: _jsx("option", { children: "\u9500\u552E\u91D1\u989D\uFF08USD\uFF09" }) }) }), _jsx(PrototypeField, { label: "\u6392\u5E8F\u65B9\u5F0F", children: _jsx("select", { children: _jsx("option", { children: "\u964D\u5E8F" }) }) }), _jsx(PrototypeField, { label: "\u663E\u793A\u6761\u6570", children: _jsx("select", { children: _jsx("option", { children: "\u524D 10 \u6761" }) }) })] }), _jsx(PrototypeActions, {})] });
266
- }
267
- function AnalyticsPrototypeSave() {
268
- return _jsxs(PrototypeCard, { title: "\u4FDD\u5B58\u62A5\u8868", children: [_jsxs("div", { className: "tcw-analytics-prototype-form", children: [_jsx(PrototypeField, { label: "\u62A5\u8868\u540D\u79F0", children: _jsx("input", { placeholder: "\u8BF7\u8F93\u5165\u62A5\u8868\u540D\u79F0" }) }), _jsx(PrototypeField, { label: "\u62A5\u8868\u63CF\u8FF0", children: _jsx("textarea", { placeholder: "\u8BF7\u8F93\u5165\u62A5\u8868\u63CF\u8FF0\uFF08\u9009\u586B\uFF09" }) }), _jsx(PrototypeField, { label: "\u4FDD\u5B58\u4F4D\u7F6E", children: _jsx("select", { children: _jsx("option", { children: "\u6211\u7684\u62A5\u8868" }) }) }), _jsx(PrototypeField, { label: "\u9690\u79C1\u8BBE\u7F6E", children: _jsx("span", { children: "\u25C9 \u4EC5\u81EA\u5DF1\u53EF\u89C1\u3000\u25CB \u56E2\u961F\u53EF\u89C1\u3000\u25CB \u516C\u5F00" }) })] }), _jsx(PrototypeActions, { confirm: "\u4FDD\u5B58" })] });
269
- }
270
- function AnalyticsPrototypeSchedule() {
271
- return _jsxs(PrototypeCard, { title: "\u5B9A\u65F6\u62A5\u544A\u8BBE\u7F6E", children: [_jsxs("div", { className: "tcw-analytics-prototype-form", children: [_jsx(PrototypeField, { label: "\u62A5\u544A\u540D\u79F0", children: _jsx("input", { placeholder: "\u8BF7\u8F93\u5165\u62A5\u544A\u540D\u79F0" }) }), _jsx(PrototypeField, { label: "\u53D1\u9001\u9891\u7387", children: _jsxs("div", { className: "tcw-prototype-range", children: [_jsx("select", { children: _jsx("option", { children: "\u6BCF\u5468" }) }), _jsx("select", { children: _jsx("option", { children: "\u5468\u4E00 09:00" }) })] }) }), _jsx(PrototypeField, { label: "\u63A5\u6536\u4EBA", children: _jsx("input", { placeholder: "\u9009\u62E9\u63A5\u6536\u4EBA" }) }), _jsx(PrototypeField, { label: "\u53D1\u9001\u65B9\u5F0F", children: _jsx("span", { children: "\u2611 \u90AE\u4EF6\u3000\u2611 \u7CFB\u7EDF\u6D88\u606F" }) }), _jsx(PrototypeField, { label: "\u62A5\u544A\u683C\u5F0F", children: _jsx("select", { children: _jsx("option", { children: "PDF" }) }) })] }), _jsx(PrototypeActions, { confirm: "\u4FDD\u5B58" })] });
272
- }
273
- function AnalyticsPrototypeDelete() {
274
- return _jsxs(PrototypeCard, { title: "", children: [_jsxs("div", { className: "tcw-analytics-prototype-delete", children: [_jsx("i", { className: "ri-error-warning-line", "aria-hidden": "true" }), _jsx("strong", { children: "\u786E\u8BA4\u5220\u9664\u6B64\u62A5\u8868\u5417\uFF1F" }), _jsx("p", { children: "\u62A5\u8868\u540D\u79F0\uFF1A\u6708\u5EA6\u9500\u552E\u5408\u89C4\u5206\u6790\u62A5\u544A" }), _jsx("b", { children: "\u5220\u9664\u540E\u5C06\u65E0\u6CD5\u6062\u590D\uFF0C\u8BF7\u8C28\u614E\u64CD\u4F5C\uFF01" })] }), _jsx(PrototypeActions, { danger: true, confirm: "\u786E\u8BA4\u5220\u9664" })] });
275
- }
276
- function AnalyticsPrototypeMetrics() {
277
- const items = [['销售金额(USD)', '订单的总金额,已换算为美元。'], ['高风险订单数', '风险等级为“高风险”的订单数量。'], ['受制裁命中数', '命中制裁名单或受限制实体的订单数量。'], ['审核通过率(%)', '审核通过的订单数占总订单数的比例。']];
278
- return _jsxs(PrototypeCard, { title: "\u6307\u6807\u8BF4\u660E", children: [_jsx("dl", { className: "tcw-analytics-prototype-explain", children: items.map(([term, description]) => _jsxs("div", { children: [_jsx("dt", { children: term }), _jsx("dd", { children: description })] }, term)) }), _jsx("footer", { children: _jsxs("button", { type: "button", children: [_jsx("i", { className: "ri-close-line", "aria-hidden": "true" }), "\u5173\u95ED"] }) })] });
279
- }
280
- function Toggle({ label, checked, onChange }) {
281
- return _jsxs("label", { className: "tcw-toggle", children: [_jsx("input", { type: "checkbox", checked: checked, onChange: event => onChange(event.target.checked) }), _jsx("span", { children: label })] });
282
- }
283
- function detailColumns(mode, dimension) {
284
- const columns = [
285
- { type: 'ellipsis', key: 'label', header: dimensionLabel(dimension), width: 220, maxWidth: 220 },
286
- { type: 'text', key: 'orderCount', header: '订单数', width: 100, align: 'right', truncate: false }
287
- ];
288
- if (dimension === 'PRODUCT')
289
- columns.push({ type: 'text', key: 'productQuantity', header: '商品数量', width: 120, align: 'right', truncate: false });
290
- columns.push({ type: 'text', key: 'amountCny', header: dimension === 'PRODUCT' ? '商品明细金额 CNY' : '订单总额 CNY', width: 160, align: 'right', truncate: false, format: money });
291
- if (dimension !== 'PRODUCT' && mode === 'PURCHASE')
292
- columns.push({ type: 'text', key: 'paidCny', header: '已付款 CNY', width: 140, align: 'right', truncate: false, format: money }, { type: 'text', key: 'unpaidCny', header: '未付款 CNY', width: 140, align: 'right', truncate: false, format: money }, { type: 'text', key: 'refundedCny', header: '已退税 CNY', width: 140, align: 'right', truncate: false, format: money }, { type: 'text', key: 'unrefundedCny', header: '未退税 CNY', width: 140, align: 'right', truncate: false, format: money });
293
- if (dimension !== 'PRODUCT' && mode === 'SALES')
294
- columns.push({ type: 'text', key: 'receivedCny', header: '已收款 CNY', width: 140, align: 'right', truncate: false, format: money }, { type: 'text', key: 'unreceivedCny', header: '未收款 CNY', width: 140, align: 'right', truncate: false, format: money });
295
- return columns;
296
- }
297
- function dimensionLabel(dimension) { return dimension === 'SUPPLIER' ? '供应商' : dimension === 'CUSTOMER' ? '客户' : '商品名称'; }
298
- function addMoney(...values) { return values.reduce((sum, value) => sum.plus(String(value ?? 0)), new Decimal(0)).toString(); }
299
- function amountRatio(part, total, formatted = true) {
300
- const denominator = Number(total) || 0;
301
- const value = denominator > 0 ? (Number(part) || 0) / denominator : 0;
302
- return formatted ? percent(value) : String(value);
303
- }
304
- function percentRatio(part, total) {
305
- const denominator = Number(total) || 0;
306
- const value = denominator > 0 ? (Number(part) || 0) / denominator : 0;
307
- return `${(value * 100).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}%`;
308
- }
309
- function deltaLabel(current, previous) {
310
- const now = Number(current ?? 0);
311
- const before = Number(previous ?? 0);
312
- if (!Number.isFinite(now) || !Number.isFinite(before) || before <= 0)
313
- return '暂无环比';
314
- const delta = (now - before) / before;
315
- const arrow = delta >= 0 ? '↑' : '↓';
316
- return `${arrow} ${Math.abs(delta * 100).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}%`;
317
- }
318
- function money(value) { const number = Number(value ?? 0); return Number.isFinite(number) ? number.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) : '0.00'; }
319
- function compactNumber(value) { const number = Number(value ?? 0); return Number.isFinite(number) ? number.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) : '0.00'; }
320
- function compactMoney(value) {
321
- const number = Number(value ?? 0);
322
- if (!Number.isFinite(number))
323
- return '0.00 CNY';
324
- const abs = Math.abs(number);
325
- if (abs >= 100_000_000)
326
- return `${(number / 100_000_000).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} 亿 CNY`;
327
- if (abs >= 10_000)
328
- return `${(number / 10_000).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} 万 CNY`;
329
- return `${money(number)} CNY`;
330
- }
331
- function percent(value) { const number = Number(value ?? 0); return Number.isFinite(number) ? `${(number * 100).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}%` : '0.00%'; }
332
- function barHeight(value, peers) {
333
- const max = Math.max(...peers.map(peer => Number(peer) || 0), Number(value) || 0, 1);
334
- return Math.max(10, Math.min(94, ((Number(value) || 0) / max) * 88 + 6));
335
- }
336
- function shortDate(value) {
337
- if (typeof value !== 'string' || !value)
338
- return '暂无';
339
- return value.slice(5, 10);
340
- }
341
- function toAnalyticsFilters(filters) {
342
- const date = filters.orderDate && typeof filters.orderDate === 'object' ? filters.orderDate : undefined;
343
- return {
344
- ...(date ? { orderDate: { from: stringOrUndefined(date.from), to: stringOrUndefined(date.to) } } : {}),
345
- ...textFilter(filters, 'supplier'), ...textFilter(filters, 'customer'), ...textFilter(filters, 'productName'), ...textFilter(filters, 'hsCode'),
346
- ...(filters.unpaidOnly === 'true' ? { unpaidOnly: true } : {}),
347
- ...(filters.unrefundedOnly === 'true' ? { unrefundedOnly: true } : {}),
348
- ...(filters.unreceivedOnly === 'true' ? { unreceivedOnly: true } : {})
349
- };
350
- }
351
- function purchaseAnalyticsFilters(filters) {
352
- return { ...('orderDate' in filters && filters.orderDate ? { orderDate: filters.orderDate } : {}), ...('supplier' in filters && filters.supplier ? { supplier: filters.supplier } : {}), ...textAnalyticsFilters(filters), ...('unpaidOnly' in filters && filters.unpaidOnly ? { unpaidOnly: true } : {}), ...('unrefundedOnly' in filters && filters.unrefundedOnly ? { unrefundedOnly: true } : {}) };
353
- }
354
- function salesAnalyticsFilters(filters) {
355
- return { ...('orderDate' in filters && filters.orderDate ? { orderDate: filters.orderDate } : {}), ...('customer' in filters && filters.customer ? { customer: filters.customer } : {}), ...textAnalyticsFilters(filters), ...('unreceivedOnly' in filters && filters.unreceivedOnly ? { unreceivedOnly: true } : {}) };
356
- }
357
- function textAnalyticsFilters(filters) { return { ...(filters.productName ? { productName: filters.productName } : {}), ...(filters.hsCode ? { hsCode: filters.hsCode } : {}) }; }
358
- function textFilter(filters, key) { const value = stringOrUndefined(filters[key]); return value ? { [key]: value } : {}; }
359
- function stringOrUndefined(value) { const text = String(value ?? '').trim(); return text || undefined; }
360
- function queryFromData(data) {
361
- return data.mode === 'PURCHASE'
362
- ? { mode: 'PURCHASE', dimension: data.dimension === 'PRODUCT' ? 'PRODUCT' : 'SUPPLIER', filters: {}, page: data.details.page, pageSize: data.details.pageSize }
363
- : { mode: 'SALES', dimension: data.dimension === 'PRODUCT' ? 'PRODUCT' : 'CUSTOMER', filters: {}, page: data.details.page, pageSize: data.details.pageSize };
364
- }
365
- function filterStateFromQuery(query) {
366
- const filters = query.filters;
367
- return {
368
- ...(filters.orderDate ? { orderDate: filters.orderDate } : {}),
369
- ...('supplier' in filters && filters.supplier ? { supplier: filters.supplier } : {}),
370
- ...('customer' in filters && filters.customer ? { customer: filters.customer } : {}),
371
- ...(filters.productName ? { productName: filters.productName } : {}),
372
- ...(filters.hsCode ? { hsCode: filters.hsCode } : {}),
373
- ...('unpaidOnly' in filters && filters.unpaidOnly ? { unpaidOnly: 'true' } : {}),
374
- ...('unrefundedOnly' in filters && filters.unrefundedOnly ? { unrefundedOnly: 'true' } : {}),
375
- ...('unreceivedOnly' in filters && filters.unreceivedOnly ? { unreceivedOnly: 'true' } : {})
376
- };
377
- }
378
- export function aggregateProductAnalytics(lines) {
379
- const groups = new Map();
380
- for (const line of lines) {
381
- const label = line.productName.trim();
382
- const key = label.toLocaleLowerCase();
383
- const group = groups.get(key) ?? { label, orders: new Set(), quantity: new Decimal(0), amount: new Decimal(0) };
384
- group.orders.add(line.orderId);
385
- group.quantity = group.quantity.plus(line.quantity);
386
- group.amount = group.amount.plus(line.amountCny);
387
- groups.set(key, group);
388
- }
389
- return [...groups.entries()].sort(([keyA, a], [keyB, b]) => b.amount.cmp(a.amount) || keyA.localeCompare(keyB)).map(([key, group]) => ({ key, label: group.label, orderCount: group.orders.size, productQuantity: group.quantity.toString(), amountCny: group.amount.toFixed(2) }));
390
- }
391
96
  //# sourceMappingURL=analytics.js.map