@tsingroc/tsingroc-components 5.0.2 → 5.1.0

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 (82) hide show
  1. package/dist/components/BarLineChart/BarLineChart.module.css +10 -0
  2. package/dist/components/BarLineChart/index.d.ts +66 -0
  3. package/dist/components/BarLineChart/index.js +590 -0
  4. package/dist/components/BaseBarChart/BaseBarChart.module.css +12 -0
  5. package/dist/components/BaseBarChart/index.d.ts +33 -0
  6. package/dist/components/BaseBarChart/index.js +121 -0
  7. package/dist/components/DataCellNumber/DataCellNumber.module.css +20 -0
  8. package/dist/components/DataCellNumber/index.d.ts +14 -0
  9. package/dist/components/DataCellNumber/index.js +142 -0
  10. package/dist/components/FlexColLayout/index.d.ts +6 -0
  11. package/dist/components/FlexColLayout/index.js +40 -0
  12. package/dist/components/FlexRowLayout/index.d.ts +5 -0
  13. package/dist/components/FlexRowLayout/index.js +45 -0
  14. package/dist/components/HighlightSyncedECharts/index.d.ts +47 -0
  15. package/dist/components/HighlightSyncedECharts/index.js +260 -0
  16. package/dist/components/HighlightSyncedTable/index.d.ts +8 -0
  17. package/dist/components/HighlightSyncedTable/index.js +183 -0
  18. package/dist/components/LoadingSection/index.d.ts +41 -0
  19. package/dist/components/LoadingSection/index.js +183 -0
  20. package/dist/components/LoadingSkeleton/index.d.ts +42 -0
  21. package/dist/components/LoadingSkeleton/index.js +634 -0
  22. package/dist/components/ScrollableTable/ScrollableTable.module.css +21 -0
  23. package/dist/components/ScrollableTable/index.d.ts +13 -0
  24. package/dist/components/ScrollableTable/index.js +29 -0
  25. package/dist/components/TsingrocTable/TsingrocTable.module.css +32 -0
  26. package/dist/components/TsingrocTable/index.d.ts +12 -0
  27. package/dist/components/TsingrocTable/index.js +23 -0
  28. package/dist/components/TsingrocTheme/index.js +3 -3
  29. package/dist/index.d.ts +4 -0
  30. package/dist/index.js +4 -0
  31. package/dist/pages/DayAheadReviewPage/components/PricePlot/index.d.ts +7 -0
  32. package/dist/pages/DayAheadReviewPage/components/PricePlot/index.js +136 -0
  33. package/dist/pages/DayAheadReviewPage/components/ProfitBarChart/ProfitBarChart.module.css +13 -0
  34. package/dist/pages/DayAheadReviewPage/components/ProfitBarChart/index.d.ts +17 -0
  35. package/dist/pages/DayAheadReviewPage/components/ProfitBarChart/index.js +278 -0
  36. package/dist/pages/DayAheadReviewPage/components/RevenueCard/RevenueCard.module.css +40 -0
  37. package/dist/pages/DayAheadReviewPage/components/RevenueCard/index.d.ts +9 -0
  38. package/dist/pages/DayAheadReviewPage/components/RevenueCard/index.js +195 -0
  39. package/dist/pages/DayAheadReviewPage/components/RevenueSummaryCard/RevenueSummaryCard.module.css +38 -0
  40. package/dist/pages/DayAheadReviewPage/components/RevenueSummaryCard/index.d.ts +10 -0
  41. package/dist/pages/DayAheadReviewPage/components/RevenueSummaryCard/index.js +117 -0
  42. package/dist/pages/DayAheadReviewPage/components/ReviewLineChart/ReviewLineChart.module.css +11 -0
  43. package/dist/pages/DayAheadReviewPage/components/ReviewLineChart/index.d.ts +53 -0
  44. package/dist/pages/DayAheadReviewPage/components/ReviewLineChart/index.js +398 -0
  45. package/dist/pages/DayAheadReviewPage/components/ReviewSummaryTable/ReviewSummaryTable.module.css +33 -0
  46. package/dist/pages/DayAheadReviewPage/components/ReviewSummaryTable/index.d.ts +17 -0
  47. package/dist/pages/DayAheadReviewPage/components/ReviewSummaryTable/index.js +187 -0
  48. package/dist/pages/DayAheadReviewPage/components/StrategyPlot/index.d.ts +10 -0
  49. package/dist/pages/DayAheadReviewPage/components/StrategyPlot/index.js +223 -0
  50. package/dist/pages/DayAheadReviewPage/components/SummaryTable/index.d.ts +7 -0
  51. package/dist/pages/DayAheadReviewPage/components/SummaryTable/index.js +39 -0
  52. package/dist/pages/DayAheadReviewPage/components/SummaryTable/useTableColumns.d.ts +10 -0
  53. package/dist/pages/DayAheadReviewPage/components/SummaryTable/useTableColumns.js +307 -0
  54. package/dist/pages/DayAheadReviewPage/hook/useDayAheadReviewDate.d.ts +137 -0
  55. package/dist/pages/DayAheadReviewPage/hook/useDayAheadReviewDate.js +252 -0
  56. package/dist/pages/DayAheadReviewPage/index.d.ts +149 -0
  57. package/dist/pages/DayAheadReviewPage/index.js +259 -0
  58. package/dist/pages/DayAheadReviewPage/layout/LeftChartContainer.d.ts +12 -0
  59. package/dist/pages/DayAheadReviewPage/layout/LeftChartContainer.js +236 -0
  60. package/dist/pages/DayAheadReviewPage/layout/ReviewPageLayout.d.ts +4 -0
  61. package/dist/pages/DayAheadReviewPage/layout/ReviewPageLayout.js +32 -0
  62. package/dist/pages/DayAheadReviewPage/layout/RightSummaryContainer.d.ts +14 -0
  63. package/dist/pages/DayAheadReviewPage/layout/RightSummaryContainer.js +199 -0
  64. package/dist/pages/DayAheadReviewPage/layout/TopDayReviewHeader.d.ts +9 -0
  65. package/dist/pages/DayAheadReviewPage/layout/TopDayReviewHeader.js +115 -0
  66. package/dist/pages/DayAheadReviewPage/types/dayahead.d.ts +172 -0
  67. package/dist/pages/DayAheadReviewPage/types/dayahead.js +1 -0
  68. package/dist/utils/accessibility.d.ts +114 -0
  69. package/dist/utils/accessibility.js +214 -0
  70. package/dist/utils/constants.d.ts +18 -0
  71. package/dist/utils/constants.js +34 -0
  72. package/dist/utils/export.d.ts +10 -0
  73. package/dist/utils/export.js +72 -0
  74. package/dist/utils/formatters.d.ts +46 -0
  75. package/dist/utils/formatters.js +84 -0
  76. package/dist/utils/index.d.ts +1 -0
  77. package/dist/utils/index.js +1 -0
  78. package/dist/utils/presenters.d.ts +24 -0
  79. package/dist/utils/presenters.js +48 -0
  80. package/dist/utils/ui.d.ts +116 -0
  81. package/dist/utils/ui.js +171 -0
  82. package/package.json +27 -25
@@ -0,0 +1,214 @@
1
+ import { c as _c } from "react/compiler-runtime";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ /**
4
+ * Accessibility utility functions and helpers
5
+ */
6
+
7
+ /**
8
+ * Generate a unique ID for accessibility attributes
9
+ */
10
+ export function generateAriaId(prefix) {
11
+ return `${prefix}-${Math.random().toString(36).substr(2, 9)}`;
12
+ }
13
+
14
+ /**
15
+ * Common ARIA labels for UI elements
16
+ */
17
+ export const ARIA_LABELS = {
18
+ CLOSE: "关闭",
19
+ OPEN: "打开",
20
+ MENU: "菜单",
21
+ SEARCH: "搜索",
22
+ FILTER: "筛选",
23
+ EXPORT: "导出",
24
+ IMPORT: "导入",
25
+ SAVE: "保存",
26
+ CANCEL: "取消",
27
+ CONFIRM: "确认",
28
+ DELETE: "删除",
29
+ EDIT: "编辑",
30
+ ADD: "添加",
31
+ REMOVE: "移除",
32
+ REFRESH: "刷新",
33
+ LOAD_MORE: "加载更多",
34
+ PREVIOUS_PAGE: "上一页",
35
+ NEXT_PAGE: "下一页",
36
+ FIRST_PAGE: "首页",
37
+ LAST_PAGE: "末页",
38
+ LOADING: "加载中",
39
+ SUCCESS: "成功",
40
+ ERROR: "错误",
41
+ WARNING: "警告",
42
+ INFO: "信息"
43
+ };
44
+
45
+ /**
46
+ * ARIA roles for common UI patterns
47
+ */
48
+ export const ARIA_ROLES = {
49
+ BUTTON: "button",
50
+ LINK: "link",
51
+ MENU: "menu",
52
+ MENUITEM: "menuitem",
53
+ TAB: "tab",
54
+ TABPANEL: "tabpanel",
55
+ TABLIST: "tablist",
56
+ DIALOG: "dialog",
57
+ ALERT: "alert",
58
+ ALERTDIALOG: "alertdialog",
59
+ STATUS: "status",
60
+ PROGRESSBAR: "progressbar",
61
+ SPINBUTTON: "spinbutton",
62
+ COMBOBOX: "combobox",
63
+ LISTBOX: "listbox",
64
+ OPTION: "option",
65
+ SEARCH: "search",
66
+ NAVIGATION: "navigation",
67
+ BANNER: "banner",
68
+ MAIN: "main",
69
+ COMPLEMENTARY: "complementary",
70
+ CONTENTINFO: "contentinfo"
71
+ };
72
+
73
+ /**
74
+ * Accessible button props generator
75
+ */
76
+ export function getAccessibleButtonProps(label, props) {
77
+ return {
78
+ "aria-label": label,
79
+ role: ARIA_ROLES.BUTTON,
80
+ ...props
81
+ };
82
+ }
83
+
84
+ /**
85
+ * Accessible input props generator
86
+ */
87
+ export function getAccessibleInputProps(label, props) {
88
+ return {
89
+ "aria-label": label,
90
+ ...props
91
+ };
92
+ }
93
+
94
+ /**
95
+ * Generate screen reader only text
96
+ */
97
+ export function ScreenReaderOnly(t0) {
98
+ const $ = _c(3);
99
+ const {
100
+ children
101
+ } = t0;
102
+ let t1;
103
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
104
+ t1 = {
105
+ position: "absolute",
106
+ width: "1px",
107
+ height: "1px",
108
+ padding: 0,
109
+ margin: "-1px",
110
+ overflow: "hidden",
111
+ clip: "rect(0, 0, 0, 0)",
112
+ whiteSpace: "nowrap",
113
+ border: 0
114
+ };
115
+ $[0] = t1;
116
+ } else {
117
+ t1 = $[0];
118
+ }
119
+ let t2;
120
+ if ($[1] !== children) {
121
+ t2 = /*#__PURE__*/_jsx("span", {
122
+ style: t1,
123
+ children: children
124
+ });
125
+ $[1] = children;
126
+ $[2] = t2;
127
+ } else {
128
+ t2 = $[2];
129
+ }
130
+ return t2;
131
+ }
132
+
133
+ /**
134
+ * Keyboard event handler helpers
135
+ */
136
+ export const keyboard = {
137
+ /** Check if Enter or Space key was pressed */
138
+ isActivationKey: event => event.key === "Enter" || event.key === " " || event.key === "Spacebar",
139
+ /** Check if Escape key was pressed */
140
+ isEscapeKey: event => event.key === "Escape",
141
+ /** Check if Arrow keys were pressed */
142
+ isArrowKey: event => event.key.startsWith("Arrow"),
143
+ /** Check if Tab key was pressed */
144
+ isTabKey: event => event.key === "Tab"
145
+ };
146
+
147
+ /**
148
+ * Focus management utilities
149
+ */
150
+ export const focus = {
151
+ /** Trap focus within an element */
152
+ trapFocus: element => {
153
+ const focusableElements = element.querySelectorAll("a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex=\"-1\"])");
154
+ const firstFocusable = focusableElements[0];
155
+ const lastFocusable = focusableElements[focusableElements.length - 1];
156
+ const handleKeyDown = event => {
157
+ if (keyboard.isTabKey(event)) {
158
+ if (event.shiftKey) {
159
+ if (document.activeElement === firstFocusable) {
160
+ event.preventDefault();
161
+ lastFocusable?.focus();
162
+ }
163
+ } else {
164
+ if (document.activeElement === lastFocusable) {
165
+ event.preventDefault();
166
+ firstFocusable?.focus();
167
+ }
168
+ }
169
+ }
170
+ };
171
+ element.addEventListener("keydown", handleKeyDown);
172
+ firstFocusable?.focus();
173
+ return () => {
174
+ element.removeEventListener("keydown", handleKeyDown);
175
+ };
176
+ },
177
+ /** Restore focus to previous element */
178
+ restoreFocus: previousElement => {
179
+ if (previousElement) {
180
+ previousElement.focus();
181
+ }
182
+ }
183
+ };
184
+
185
+ /**
186
+ * Live region announcer for screen readers
187
+ */
188
+ export function announceToScreenReader(message, priority = "polite") {
189
+ const announcement = document.createElement("div");
190
+ announcement.setAttribute("aria-live", priority);
191
+ announcement.setAttribute("aria-atomic", "true");
192
+ announcement.setAttribute("role", "status");
193
+ Object.assign(announcement.style, {
194
+ position: "absolute",
195
+ left: "-10000px",
196
+ width: "1px",
197
+ height: "1px",
198
+ overflow: "hidden"
199
+ });
200
+ document.body.appendChild(announcement);
201
+ announcement.textContent = message;
202
+ setTimeout(() => {
203
+ document.body.removeChild(announcement);
204
+ }, 1000);
205
+ }
206
+
207
+ /**
208
+ * Get accessible color contrast (WCAG compliance)
209
+ */
210
+ export function getAccessibleColor(_bg, _fg, _level = "AA") {
211
+ // Simplified contrast check - in production, use a proper library
212
+ // This is a placeholder for the actual implementation
213
+ return true;
214
+ }
@@ -0,0 +1,18 @@
1
+ export declare const COLOR_PRICE_DAY_AHEAD = "#3773FF";
2
+ export declare const COLOR_PRICE_REALTIME = "#FF5900";
3
+ export declare const COLOR_POWER_GENERATION = "#8F59FA";
4
+ export declare const COLOR_POWER_TSINGROC = "#85008C";
5
+ export declare const COLOR_POWER_MANUAL = "#FF5900";
6
+ export declare const COLOR_SECONDARY_COLOR = "#FF5900";
7
+ export declare const COLOR_PROFIT_TSINGROC = "#85008C";
8
+ export declare const COLOR_PROFIT_MANUAL = "#FF5900";
9
+ export declare const COLOR_CHART_AXIS = "#ADADAD";
10
+ export declare const COLOR_CARD_BACKGROUND_TSINGROC = "#F5EAF6";
11
+ export declare const COLOR_CARD_CONTENT_TSINGROC = "#85008C";
12
+ export declare const COLOR_CARD_BACKGROUND_MANUAL = "#FFEFE8";
13
+ export declare const COLOR_CARD_CONTENT_MANUAL = "#FF5900";
14
+ export declare const COLOR_TABLE_HEADER = "#F2F0F7";
15
+ export declare const COLOR_TABLE_HOVER = "#DCDEE0";
16
+ export declare const COLOR_TABLE_CELL_TSINGROC = "#85008C";
17
+ export declare const COLOR_TABLE_CELL_MANUAL = "#FF5900";
18
+ export declare const COLOR_TABLE_CELL_TEXT = "#000000";
@@ -0,0 +1,34 @@
1
+ const PRIMARY_COLOR = "#85008C";
2
+ const PRIMARY_COLOR_LIGHT = "#8F59FA";
3
+ const PRIMARY_COLOR_LIGHTER = "#F5EAF6";
4
+ const PRIMARY_COLOR_LIGHTEST = "#DCDEE0";
5
+ const PRIMARY_COLOR_MOST_LIGHTEST = "#F2F0F7";
6
+ const SECONDARY_COLOR = "#FF5900";
7
+ const SECONDARY_COLOR_LIGHTEST = "#FFEFE8";
8
+ const TERTIARY_COLOR = "#3773FF";
9
+ // const TERTIARY_COLOR_LIGHT = "#1890ff";
10
+ const GREY = "#ADADAD";
11
+
12
+ // Chart colors
13
+ export const COLOR_PRICE_DAY_AHEAD = TERTIARY_COLOR;
14
+ export const COLOR_PRICE_REALTIME = SECONDARY_COLOR;
15
+ export const COLOR_POWER_GENERATION = PRIMARY_COLOR_LIGHT;
16
+ export const COLOR_POWER_TSINGROC = PRIMARY_COLOR;
17
+ export const COLOR_POWER_MANUAL = SECONDARY_COLOR;
18
+ export const COLOR_SECONDARY_COLOR = SECONDARY_COLOR;
19
+ export const COLOR_PROFIT_TSINGROC = PRIMARY_COLOR;
20
+ export const COLOR_PROFIT_MANUAL = SECONDARY_COLOR;
21
+ export const COLOR_CHART_AXIS = GREY;
22
+
23
+ // Card Color
24
+ export const COLOR_CARD_BACKGROUND_TSINGROC = PRIMARY_COLOR_LIGHTER;
25
+ export const COLOR_CARD_CONTENT_TSINGROC = PRIMARY_COLOR;
26
+ export const COLOR_CARD_BACKGROUND_MANUAL = SECONDARY_COLOR_LIGHTEST;
27
+ export const COLOR_CARD_CONTENT_MANUAL = SECONDARY_COLOR;
28
+
29
+ // Table Colors
30
+ export const COLOR_TABLE_HEADER = PRIMARY_COLOR_MOST_LIGHTEST;
31
+ export const COLOR_TABLE_HOVER = PRIMARY_COLOR_LIGHTEST;
32
+ export const COLOR_TABLE_CELL_TSINGROC = PRIMARY_COLOR;
33
+ export const COLOR_TABLE_CELL_MANUAL = SECONDARY_COLOR;
34
+ export const COLOR_TABLE_CELL_TEXT = "#000000";
@@ -0,0 +1,10 @@
1
+ import type { ExtendedReviewValue } from "../pages/DayAheadReviewPage/types/dayahead";
2
+ /**
3
+ * 将日前复盘数据导出为 Excel。
4
+ * 创建包含基础数据、详细数据和统计信息工作表的工作簿。
5
+ *
6
+ * @param data - 要导出的复盘值数组
7
+ * @param tradingDay - 复盘的交易日
8
+ * @param filename - 可选的自定义文件名
9
+ */
10
+ export declare function exportDayAheadReviewDataToExcel(data: ExtendedReviewValue[], tradingDay: string, filename?: string): void;
@@ -0,0 +1,72 @@
1
+ import * as XLSX from "@e965/xlsx";
2
+ import dayjs from "dayjs";
3
+ import { formatPrice, formatProfit, formatTime as formatReviewTime, formatVolume as formatReviewVolume } from "./formatters";
4
+
5
+ /**
6
+ * 将日前复盘数据导出为 Excel。
7
+ * 创建包含基础数据、详细数据和统计信息工作表的工作簿。
8
+ *
9
+ * @param data - 要导出的复盘值数组
10
+ * @param tradingDay - 复盘的交易日
11
+ * @param filename - 可选的自定义文件名
12
+ */
13
+ export function exportDayAheadReviewDataToExcel(data, tradingDay, filename) {
14
+ const basicWorksheetData = [["时段", "预测电量 (MWh)", "实际电量 (MWh)", "申报电量(智能体) (MWh)", "申报电量(人工) (MWh)", "日前价格 (¥/MWh)", "实时价格 (¥/MWh)"], ...data.map(item => [formatReviewTime(item.time), formatReviewVolume(item.forecastVolume), formatReviewVolume(item.actualVolume), formatReviewVolume(item.agentDeclaredVolume), formatReviewVolume(item.manualDeclaredVolume || 0), formatPrice(item.dayAheadPrice), formatPrice(item.realTimePrice)])];
15
+ const workbook = XLSX.utils.book_new();
16
+ const basicWorksheet = XLSX.utils.aoa_to_sheet(basicWorksheetData);
17
+ basicWorksheet["!cols"] = [{
18
+ width: 15
19
+ }, {
20
+ width: 18
21
+ }, {
22
+ width: 18
23
+ }, {
24
+ width: 22
25
+ }, {
26
+ width: 22
27
+ }, {
28
+ width: 18
29
+ }, {
30
+ width: 18
31
+ }];
32
+ XLSX.utils.book_append_sheet(workbook, basicWorksheet, "基础数据");
33
+ const detailedWorksheetData = [["时段", "预测电量 (MWh)", "实际电量 (MWh)", "申报电量(智能体) (MWh)", "申报电量(人工) (MWh)", "日前价格 (¥/MWh)", "实时价格 (¥/MWh)", "收益(智能体) (¥)", "收益(人工) (¥)"], ...data.map(item => [formatReviewTime(item.time), formatReviewVolume(item.forecastVolume), formatReviewVolume(item.actualVolume), formatReviewVolume(item.agentDeclaredVolume), formatReviewVolume(item.manualDeclaredVolume), formatPrice(item.dayAheadPrice), formatPrice(item.realTimePrice), formatProfit(item.agentProfit), formatProfit(item.manualProfit)])];
34
+ const detailedWorksheet = XLSX.utils.aoa_to_sheet(detailedWorksheetData);
35
+ detailedWorksheet["!cols"] = [{
36
+ width: 15
37
+ }, {
38
+ width: 18
39
+ }, {
40
+ width: 18
41
+ }, {
42
+ width: 22
43
+ }, {
44
+ width: 22
45
+ }, {
46
+ width: 18
47
+ }, {
48
+ width: 18
49
+ }, {
50
+ width: 18
51
+ }, {
52
+ width: 18
53
+ }];
54
+ XLSX.utils.book_append_sheet(workbook, detailedWorksheet, "详细数据");
55
+ const totalAgentProfit = data.reduce((sum, item) => sum + (item.agentProfit || 0), 0);
56
+ const totalManualProfit = data.reduce((sum, item) => sum + (item.manualProfit || 0), 0);
57
+ const totalForecastVolume = data.reduce((sum, item) => sum + (item.forecastVolume || 0), 0);
58
+ const totalActualVolume = data.reduce((sum, item) => sum + (item.actualVolume || 0), 0);
59
+ const totalAgentDeclaredVolume = data.reduce((sum, item) => sum + (item.agentDeclaredVolume || 0), 0);
60
+ const totalManualDeclaredVolume = data.reduce((sum, item) => sum + (item.manualDeclaredVolume || 0), 0);
61
+ const summaryInfoData = [["复盘信息", ""], ["交易日", tradingDay], ["", ""], ["数据统计", ""], ["总记录数", data.length.toString()], ["总预测电量 (MWh)", totalForecastVolume.toFixed(3)], ["总实际电量 (MWh)", totalActualVolume.toFixed(3)], ["总申报电量(智能体) (MWh)", totalAgentDeclaredVolume.toFixed(3)], ["总申报电量(人工) (MWh)", totalManualDeclaredVolume.toFixed(3)], ["", ""], ["收益统计", ""], ["总收益(智能体) (¥)", totalAgentProfit.toFixed(2)], ["总收益(人工) (¥)", totalManualProfit.toFixed(2)], ["收益差异 (¥)", (totalAgentProfit - totalManualProfit).toFixed(2)]];
62
+ const summaryInfoSheet = XLSX.utils.aoa_to_sheet(summaryInfoData);
63
+ summaryInfoSheet["!cols"] = [{
64
+ width: 25
65
+ }, {
66
+ width: 30
67
+ }];
68
+ XLSX.utils.book_append_sheet(workbook, summaryInfoSheet, "统计信息");
69
+ const defaultFilename = `日前交易复盘_${tradingDay}_${dayjs().format("YYYY-MM-DD")}.xlsx`;
70
+ const finalFilename = filename || defaultFilename;
71
+ XLSX.writeFile(workbook, finalFilename);
72
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Pure formatting functions for domain data.
3
+ *
4
+ * DESIGN PRINCIPLES:
5
+ * - 0 is a VALID value (e.g., "no electricity traded") and should be formatted
6
+ * - null/undefined means "no data" - return null/undefined (let components decide presentation)
7
+ * - Formatters are pure functions - they don't handle UI presentation
8
+ *
9
+ * Presentation logic (how to show null) is handled by components/ui.
10
+ */
11
+ export { presentCardValue } from "./ui";
12
+ /**
13
+ * Format time string to readable Chinese format.
14
+ * Returns null for null/undefined input.
15
+ */
16
+ export declare function formatTime(timeString: string | null | undefined): string | null;
17
+ /**
18
+ * Format numeric value with specified decimal places.
19
+ * Always shows 0 as "0.00", "0.000", etc.
20
+ * Returns null for null/undefined input (components decide presentation).
21
+ *
22
+ * @param value - The number to format
23
+ * @param fractionDigits - Number of decimal places (default: 2)
24
+ * @returns Formatted string or null
25
+ */
26
+ export declare function formatNumber(value: number | null | undefined, fractionDigits?: number): string | null;
27
+ /**
28
+ * Format price value to 2 decimal places.
29
+ * Uses formatNumber internally - always shows 0 as "0.00".
30
+ */
31
+ export declare function formatPrice(price: number | null | undefined): string | null;
32
+ /**
33
+ * Format volume value to 3 decimal places.
34
+ * Uses formatNumber internally - always shows 0 as "0.000".
35
+ */
36
+ export declare function formatVolume(volume: number | null | undefined): string | null;
37
+ /**
38
+ * Format profit value to 2 decimal places.
39
+ * Uses formatNumber internally - always shows 0 as "0.00".
40
+ */
41
+ export declare function formatProfit(profit: number | null | undefined): string | null;
42
+ /**
43
+ * Format trade direction to Chinese.
44
+ * Returns null for null/undefined/empty input.
45
+ */
46
+ export declare function formatDirection(direction: string | null | undefined): string | null;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Pure formatting functions for domain data.
3
+ *
4
+ * DESIGN PRINCIPLES:
5
+ * - 0 is a VALID value (e.g., "no electricity traded") and should be formatted
6
+ * - null/undefined means "no data" - return null/undefined (let components decide presentation)
7
+ * - Formatters are pure functions - they don't handle UI presentation
8
+ *
9
+ * Presentation logic (how to show null) is handled by components/ui.
10
+ */
11
+
12
+ // Re-export presentCardValue from ui for convenience
13
+ export { presentCardValue } from "./ui";
14
+
15
+ /**
16
+ * Format time string to readable Chinese format.
17
+ * Returns null for null/undefined input.
18
+ */
19
+ export function formatTime(timeString) {
20
+ if (!timeString) return null;
21
+ const date = new Date(timeString);
22
+ if (Number.isNaN(date.valueOf())) return null;
23
+ return date.toLocaleString("zh-CN", {
24
+ year: "numeric",
25
+ month: "2-digit",
26
+ day: "2-digit",
27
+ hour: "2-digit",
28
+ minute: "2-digit",
29
+ second: "2-digit"
30
+ });
31
+ }
32
+
33
+ /**
34
+ * Format numeric value with specified decimal places.
35
+ * Always shows 0 as "0.00", "0.000", etc.
36
+ * Returns null for null/undefined input (components decide presentation).
37
+ *
38
+ * @param value - The number to format
39
+ * @param fractionDigits - Number of decimal places (default: 2)
40
+ * @returns Formatted string or null
41
+ */
42
+ export function formatNumber(value, fractionDigits = 2) {
43
+ if (value === null || value === undefined) return null;
44
+ return value.toFixed(fractionDigits);
45
+ }
46
+
47
+ /**
48
+ * Format price value to 2 decimal places.
49
+ * Uses formatNumber internally - always shows 0 as "0.00".
50
+ */
51
+ export function formatPrice(price) {
52
+ return formatNumber(price, 2);
53
+ }
54
+
55
+ /**
56
+ * Format volume value to 3 decimal places.
57
+ * Uses formatNumber internally - always shows 0 as "0.000".
58
+ */
59
+ export function formatVolume(volume) {
60
+ return formatNumber(volume, 3);
61
+ }
62
+
63
+ /**
64
+ * Format profit value to 2 decimal places.
65
+ * Uses formatNumber internally - always shows 0 as "0.00".
66
+ */
67
+ export function formatProfit(profit) {
68
+ return formatNumber(profit, 2);
69
+ }
70
+
71
+ /**
72
+ * Format trade direction to Chinese.
73
+ * Returns null for null/undefined/empty input.
74
+ */
75
+ export function formatDirection(direction) {
76
+ if (!direction) return null;
77
+ const directionMap = {
78
+ buy: "买入",
79
+ sell: "卖出",
80
+ 买入: "买入",
81
+ 卖出: "卖出"
82
+ };
83
+ return directionMap[direction] || direction;
84
+ }
@@ -1,5 +1,6 @@
1
1
  export * from "./debug";
2
2
  export * from "./destructureLineDataItem";
3
+ export * from "./export";
3
4
  export * from "./filterMap";
4
5
  export * from "./math";
5
6
  export * from "./mock";
@@ -1,5 +1,6 @@
1
1
  export * from "./debug";
2
2
  export * from "./destructureLineDataItem";
3
+ export * from "./export";
3
4
  export * from "./filterMap";
4
5
  export * from "./math";
5
6
  export * from "./mock";
@@ -0,0 +1,24 @@
1
+ import type { Dayjs } from "dayjs";
2
+ import type { ExtendedReviewValue, ProfitPlotValue } from "../pages/DayAheadReviewPage/types/dayahead";
3
+ /**
4
+ * Returns true when any row has manual data (non-null manual fields).
5
+ * Used to show AI/manual mode selector: when manual fields are null, it's AI-only mode.
6
+ */
7
+ export declare function hasManualData(tableData: ExtendedReviewValue[]): boolean;
8
+ export declare function extractProfitData(tableData: ExtendedReviewValue[]): {
9
+ agentProfit: ProfitPlotValue[];
10
+ manualProfit: ProfitPlotValue[];
11
+ };
12
+ /**
13
+ * Hook for day-ahead trading days functionality.
14
+ * This is a simplified version that needs to be integrated with your TradingDayContext.
15
+ *
16
+ * @example
17
+ * ```tsx
18
+ * // In your component:
19
+ * const { disabledDate } = useDayAheadTradingDays();
20
+ * ```
21
+ */
22
+ export declare function useDayAheadTradingDays(): {
23
+ disabledDate: (current: Dayjs | null) => boolean;
24
+ };
@@ -0,0 +1,48 @@
1
+ import dayjs from "dayjs";
2
+ /**
3
+ * Returns true when any row has manual data (non-null manual fields).
4
+ * Used to show AI/manual mode selector: when manual fields are null, it's AI-only mode.
5
+ */
6
+ export function hasManualData(tableData) {
7
+ return tableData.some(item => item.manualDeclaredVolume != null || item.manualDeclaredPrice != null || item.manualClearanceVolume != null || item.manualProfit != null);
8
+ }
9
+ export function extractProfitData(tableData) {
10
+ const agentProfit = tableData.map(item => ({
11
+ time: item.time,
12
+ agentProfit: item.agentProfit ?? 0,
13
+ agentPredictBaseline: item.agentPredictBaseline ?? 0,
14
+ agentStrategyAdd: item.agentStrategyAdd ?? 0
15
+ }));
16
+ const manualProfit = tableData.map(item => ({
17
+ time: item.time,
18
+ manualProfit: item.manualProfit ?? 0,
19
+ agentProfit: 0
20
+ }));
21
+ return {
22
+ agentProfit,
23
+ manualProfit
24
+ };
25
+ }
26
+
27
+ /**
28
+ * Hook for day-ahead trading days functionality.
29
+ * This is a simplified version that needs to be integrated with your TradingDayContext.
30
+ *
31
+ * @example
32
+ * ```tsx
33
+ * // In your component:
34
+ * const { disabledDate } = useDayAheadTradingDays();
35
+ * ```
36
+ */
37
+ export function useDayAheadTradingDays() {
38
+ // This is a placeholder - in the actual implementation, this should
39
+ // connect to your TradingDayContext or similar state management
40
+ // For now, it returns a default implementation
41
+ return {
42
+ disabledDate: current => {
43
+ if (!current) return true;
44
+ // Disable future dates by default
45
+ return current.isAfter(dayjs(), "day");
46
+ }
47
+ };
48
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Presentation utilities for consistent null/undefined handling in components.
3
+ *
4
+ * These utilities handle how null/undefined values are displayed in the UI.
5
+ * Use these in components to apply consistent design tokens for missing data.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * import { presentCardValue, presentTableValue } from './ui';
10
+ * import { formatPrice, formatVolume } from './formatters';
11
+ *
12
+ * // In a card (shows "-" for null)
13
+ * <Statistic title="Price" value={presentCardValue(formatPrice(data.price))} />
14
+ *
15
+ * // In a table (shows "" for null)
16
+ * <Table.Column render={() => presentTableValue(formatVolume(data.volume))} />
17
+ * ```
18
+ */
19
+ /**
20
+ * Presentation options for null/undefined values.
21
+ */
22
+ export type NullPresentation = "empty" | "dash" | "nbsp" | "na" | "zero";
23
+ /**
24
+ * Default null presentation for different contexts.
25
+ */
26
+ export declare const NULL_PRESENTATION_DEFAULTS: {
27
+ /** Default for tables: show empty string */
28
+ readonly table: "empty";
29
+ /** Default for cards: show dash */
30
+ readonly card: "dash";
31
+ /** Default for charts: show dash */
32
+ readonly chart: "dash";
33
+ /** Default for form inputs: show empty */
34
+ readonly form: "empty";
35
+ };
36
+ /**
37
+ * Present a nullable value with consistent null handling.
38
+ *
39
+ * This is the core presenter function that handles how null/undefined values
40
+ * are displayed in the UI. Use the convenience wrappers (presentCardValue,
41
+ * presentTableValue) for common cases.
42
+ *
43
+ * @param value - The formatted value (could be null from formatters)
44
+ * @param options - How to present null values
45
+ * @returns Display string
46
+ *
47
+ * @example
48
+ * ```tsx
49
+ * // Custom presentation
50
+ * <span>{presentValue(formatPrice(price), { nullAs: "na" })}</span>
51
+ *
52
+ * // Show N/A for null values
53
+ * <span>{presentValue(formatVolume(volume), { nullAs: "na" })}</span>
54
+ *
55
+ * // Use non-breaking space to prevent layout shift
56
+ * <span>{presentValue(formatProfit(profit), { nullAs: "nbsp" })}</span>
57
+ * ```
58
+ */
59
+ export declare function presentValue(value: string | number | null | undefined, options?: {
60
+ /** How to present null/undefined (default: "empty") */
61
+ nullAs?: NullPresentation;
62
+ /** Optional fallback for zero values (default: show zero) */
63
+ zeroAs?: NullPresentation;
64
+ }): string;
65
+ /**
66
+ * Convenience presenter for card values (shows "-" for null).
67
+ *
68
+ * Use this in cards, statistics, and other UI elements where a dash "-".
69
+ *
70
+ * @example
71
+ * ```tsx
72
+ * import { presentCardValue } from './ui';
73
+ * import { formatPrice } from './formatters';
74
+ *
75
+ * <Statistic
76
+ * title="Current Price"
77
+ * value={presentCardValue(formatPrice(data.price))}
78
+ * />
79
+ * // null → "-", 0 → "0.00", 123.45 → "123.45"
80
+ * ```
81
+ */
82
+ export declare function presentCardValue(value: string | number | null | undefined): string;
83
+ /**
84
+ * Convenience presenter for table cells (shows "" for null).
85
+ *
86
+ * Use this in table cells and other compact UI elements where an empty string.
87
+ *
88
+ * @example
89
+ * ```tsx
90
+ * import { presentTableValue } from './ui';
91
+ * import { formatVolume } from './formatters';
92
+ *
93
+ * <Table.Column
94
+ * render={(_, record) => presentTableValue(formatVolume(record.volume))}
95
+ * />
96
+ * // null → "", 0 → "0.000", 123.456 → "123.456"
97
+ * ```
98
+ */
99
+ export declare function presentTableValue(value: string | number | null | undefined): string;
100
+ /**
101
+ * Convenience presenter for chart tooltips (shows "-" for null).
102
+ *
103
+ * Use this in chart tooltips, legends, and other data visualizations.
104
+ *
105
+ * @example
106
+ * ```tsx
107
+ * import { presentChartValue } from './ui';
108
+ * import { formatProfit } from './formatters';
109
+ *
110
+ * tooltip: {
111
+ * formatter: (params) => presentChartValue(formatProfit(params.value))
112
+ * }
113
+ * // null → "-", 0 → "0.00", 123.45 → "123.45"
114
+ * ```
115
+ */
116
+ export declare function presentChartValue(value: string | number | null | undefined): string;