@eztrak/shared 0.18.0 → 0.19.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.
package/README.md CHANGED
@@ -1074,6 +1074,36 @@ Opens on [http://localhost:6006](http://localhost:6006) with stories for `Breadc
1074
1074
  | `@eztrak/shared/components` | `EztrakTabs`, `CustomPagination`, `Breadcrumbs`, `CustomCellEditor`, `SearchInput`, `DropdownFilter`, `ResetFiltersButton`, `TooltipText`, `ToolTip`, `CardSkeleton`, `Modal`, `Loader`, `TableLayoutToolbarControls`, `ResetColumnsButton`, and related types |
1075
1075
  | `@eztrak/shared/components/tabs.css` | Default tab styles (CSS variables) |
1076
1076
  | `@eztrak/shared/components/loader.css` | Loader spinner styles |
1077
+ | `@eztrak/shared/excel` | Styled Excel export (`exportExcelReport`). Not on the main entry, so `xlsx-js-style` stays out of apps that do not import it |
1078
+
1079
+ ### Excel export
1080
+
1081
+ Import `exportExcelReport` from `@eztrak/shared/excel`. It writes a styled workbook and returns `{ ok, fileName }` or `{ ok: false, code, error }`. It does not toast.
1082
+
1083
+ ```js
1084
+ import { exportExcelReport } from "@eztrak/shared/excel";
1085
+
1086
+ exportExcelReport({
1087
+ data: rows,
1088
+ fileName: "Budget",
1089
+ columns: [
1090
+ {
1091
+ field: "amount",
1092
+ headerName: "Amount",
1093
+ excel: {
1094
+ headerFill: "1F4E79",
1095
+ headerColor: "FFFFFF",
1096
+ fill: "FFF2CC",
1097
+ color: "7F6000",
1098
+ align: "right",
1099
+ numFmt: '"$"#,##0.00',
1100
+ },
1101
+ },
1102
+ ],
1103
+ });
1104
+ ```
1105
+
1106
+ `columns` may nest `children` the way AG Grid column groups do. Merges follow that tree. Set `theme` to change the workbook defaults (header `2F75B6` on white text, body `1F2933` on white, border `8FA4BF`). A column `excel` block overrides header fill, header text, body fill, body text, bold, align, and `numFmt`. `valueFormatter` or `format` supplies the cell value. `formatDateFields` formats fields whose name contains `date` when no formatter is set.
1077
1107
 
1078
1108
  ### EztrakTabs props
1079
1109
 
@@ -0,0 +1,146 @@
1
+ type ExcelAlign = "left" | "center" | "right";
2
+ /** Per-column Excel colors. Hex is RRGGBB; a leading # is accepted. */
3
+ type ExcelColumnStyle = {
4
+ headerFill?: string;
5
+ headerColor?: string;
6
+ fill?: string;
7
+ color?: string;
8
+ bold?: boolean;
9
+ align?: ExcelAlign;
10
+ /** Excel number format, for example '"$"#,##0.00'. */
11
+ numFmt?: string;
12
+ };
13
+ type ExcelTheme = {
14
+ headerFill?: string;
15
+ headerColor?: string;
16
+ fill?: string;
17
+ color?: string;
18
+ borderColor?: string;
19
+ fontName?: string;
20
+ fontSize?: number;
21
+ };
22
+ type ExcelFormatParams = {
23
+ value: unknown;
24
+ data: Record<string, unknown>;
25
+ };
26
+ type ExcelExportColumn = {
27
+ field?: string;
28
+ headerName?: string;
29
+ children?: ExcelExportColumn[];
30
+ hide?: boolean;
31
+ checkboxSelection?: boolean;
32
+ headerCheckboxSelection?: boolean;
33
+ valueFormatter?: (params: ExcelFormatParams) => unknown;
34
+ format?: (params: ExcelFormatParams) => unknown;
35
+ excel?: ExcelColumnStyle;
36
+ };
37
+ type ExportExcelErrorCode = "empty" | "invalid" | "no-columns" | "filename" | "write";
38
+ type ExportExcelFailure = {
39
+ ok: false;
40
+ code: ExportExcelErrorCode;
41
+ error: string;
42
+ };
43
+ type ExportExcelReportResult = {
44
+ ok: true;
45
+ fileName: string;
46
+ } | ExportExcelFailure;
47
+ type ExportExcelReportOptions = {
48
+ data?: unknown[] | null;
49
+ fileName?: string;
50
+ columns?: ExcelExportColumn[];
51
+ sheetName?: string;
52
+ theme?: ExcelTheme;
53
+ /** Drop columns with hide: true. Default false, matching vault grids. */
54
+ skipHidden?: boolean;
55
+ /**
56
+ * Format leaf fields whose name contains "date" when they have no formatter.
57
+ * Vault turns this on and passes its own date formatter.
58
+ */
59
+ formatDateFields?: boolean;
60
+ formatDate?: (value: unknown) => unknown;
61
+ /**
62
+ * Date suffix for the file name. Omit to use the local YYYY-MM-DD.
63
+ * Pass null when the caller could not produce a stamp.
64
+ */
65
+ dateStamp?: string | null;
66
+ };
67
+ type ResolvedTheme = {
68
+ headerFill: string;
69
+ headerColor: string;
70
+ fill: string;
71
+ color: string;
72
+ borderColor: string;
73
+ fontName: string;
74
+ fontSize: number;
75
+ };
76
+ type ExcelCellStyle = {
77
+ font: {
78
+ name: string;
79
+ sz: number;
80
+ bold?: boolean;
81
+ color: {
82
+ rgb: string;
83
+ };
84
+ };
85
+ fill: {
86
+ patternType: "solid";
87
+ fgColor: {
88
+ rgb: string;
89
+ };
90
+ };
91
+ alignment: {
92
+ vertical: "center";
93
+ horizontal: ExcelAlign;
94
+ wrapText: false;
95
+ };
96
+ border: {
97
+ top: {
98
+ style: "thin";
99
+ color: {
100
+ rgb: string;
101
+ };
102
+ };
103
+ bottom: {
104
+ style: "thin";
105
+ color: {
106
+ rgb: string;
107
+ };
108
+ };
109
+ left: {
110
+ style: "thin";
111
+ color: {
112
+ rgb: string;
113
+ };
114
+ };
115
+ right: {
116
+ style: "thin";
117
+ color: {
118
+ rgb: string;
119
+ };
120
+ };
121
+ };
122
+ numFmt?: string;
123
+ };
124
+ type WorkBook = {
125
+ SheetNames: string[];
126
+ Sheets: Record<string, WorkSheet>;
127
+ };
128
+ type WorkSheet = Record<string, unknown>;
129
+ declare const DEFAULT_EXCEL_THEME: ResolvedTheme;
130
+ declare const excelHeaderStyle: (theme?: ExcelTheme, column?: ExcelColumnStyle) => ExcelCellStyle;
131
+ declare const excelDataStyle: (theme?: ExcelTheme, column?: ExcelColumnStyle) => ExcelCellStyle;
132
+ type BuiltExcelWorkbook = {
133
+ ok: true;
134
+ workbook: WorkBook;
135
+ fileName: string;
136
+ sheetName: string;
137
+ } | ExportExcelFailure;
138
+ /**
139
+ * Build a styled workbook. Does not write a file.
140
+ * Header merges follow the column tree, so two groups with the same title stay separate.
141
+ */
142
+ declare const buildExcelWorkbook: (options?: ExportExcelReportOptions) => BuiltExcelWorkbook;
143
+ /** Write a styled .xlsx. Returns a result; it does not toast. */
144
+ declare const exportExcelReport: (options?: ExportExcelReportOptions) => ExportExcelReportResult;
145
+
146
+ export { type BuiltExcelWorkbook, DEFAULT_EXCEL_THEME, type ExcelAlign, type ExcelCellStyle, type ExcelColumnStyle, type ExcelExportColumn, type ExcelFormatParams, type ExcelTheme, type ExportExcelErrorCode, type ExportExcelFailure, type ExportExcelReportOptions, type ExportExcelReportResult, buildExcelWorkbook, excelDataStyle, excelHeaderStyle, exportExcelReport };
@@ -0,0 +1,146 @@
1
+ type ExcelAlign = "left" | "center" | "right";
2
+ /** Per-column Excel colors. Hex is RRGGBB; a leading # is accepted. */
3
+ type ExcelColumnStyle = {
4
+ headerFill?: string;
5
+ headerColor?: string;
6
+ fill?: string;
7
+ color?: string;
8
+ bold?: boolean;
9
+ align?: ExcelAlign;
10
+ /** Excel number format, for example '"$"#,##0.00'. */
11
+ numFmt?: string;
12
+ };
13
+ type ExcelTheme = {
14
+ headerFill?: string;
15
+ headerColor?: string;
16
+ fill?: string;
17
+ color?: string;
18
+ borderColor?: string;
19
+ fontName?: string;
20
+ fontSize?: number;
21
+ };
22
+ type ExcelFormatParams = {
23
+ value: unknown;
24
+ data: Record<string, unknown>;
25
+ };
26
+ type ExcelExportColumn = {
27
+ field?: string;
28
+ headerName?: string;
29
+ children?: ExcelExportColumn[];
30
+ hide?: boolean;
31
+ checkboxSelection?: boolean;
32
+ headerCheckboxSelection?: boolean;
33
+ valueFormatter?: (params: ExcelFormatParams) => unknown;
34
+ format?: (params: ExcelFormatParams) => unknown;
35
+ excel?: ExcelColumnStyle;
36
+ };
37
+ type ExportExcelErrorCode = "empty" | "invalid" | "no-columns" | "filename" | "write";
38
+ type ExportExcelFailure = {
39
+ ok: false;
40
+ code: ExportExcelErrorCode;
41
+ error: string;
42
+ };
43
+ type ExportExcelReportResult = {
44
+ ok: true;
45
+ fileName: string;
46
+ } | ExportExcelFailure;
47
+ type ExportExcelReportOptions = {
48
+ data?: unknown[] | null;
49
+ fileName?: string;
50
+ columns?: ExcelExportColumn[];
51
+ sheetName?: string;
52
+ theme?: ExcelTheme;
53
+ /** Drop columns with hide: true. Default false, matching vault grids. */
54
+ skipHidden?: boolean;
55
+ /**
56
+ * Format leaf fields whose name contains "date" when they have no formatter.
57
+ * Vault turns this on and passes its own date formatter.
58
+ */
59
+ formatDateFields?: boolean;
60
+ formatDate?: (value: unknown) => unknown;
61
+ /**
62
+ * Date suffix for the file name. Omit to use the local YYYY-MM-DD.
63
+ * Pass null when the caller could not produce a stamp.
64
+ */
65
+ dateStamp?: string | null;
66
+ };
67
+ type ResolvedTheme = {
68
+ headerFill: string;
69
+ headerColor: string;
70
+ fill: string;
71
+ color: string;
72
+ borderColor: string;
73
+ fontName: string;
74
+ fontSize: number;
75
+ };
76
+ type ExcelCellStyle = {
77
+ font: {
78
+ name: string;
79
+ sz: number;
80
+ bold?: boolean;
81
+ color: {
82
+ rgb: string;
83
+ };
84
+ };
85
+ fill: {
86
+ patternType: "solid";
87
+ fgColor: {
88
+ rgb: string;
89
+ };
90
+ };
91
+ alignment: {
92
+ vertical: "center";
93
+ horizontal: ExcelAlign;
94
+ wrapText: false;
95
+ };
96
+ border: {
97
+ top: {
98
+ style: "thin";
99
+ color: {
100
+ rgb: string;
101
+ };
102
+ };
103
+ bottom: {
104
+ style: "thin";
105
+ color: {
106
+ rgb: string;
107
+ };
108
+ };
109
+ left: {
110
+ style: "thin";
111
+ color: {
112
+ rgb: string;
113
+ };
114
+ };
115
+ right: {
116
+ style: "thin";
117
+ color: {
118
+ rgb: string;
119
+ };
120
+ };
121
+ };
122
+ numFmt?: string;
123
+ };
124
+ type WorkBook = {
125
+ SheetNames: string[];
126
+ Sheets: Record<string, WorkSheet>;
127
+ };
128
+ type WorkSheet = Record<string, unknown>;
129
+ declare const DEFAULT_EXCEL_THEME: ResolvedTheme;
130
+ declare const excelHeaderStyle: (theme?: ExcelTheme, column?: ExcelColumnStyle) => ExcelCellStyle;
131
+ declare const excelDataStyle: (theme?: ExcelTheme, column?: ExcelColumnStyle) => ExcelCellStyle;
132
+ type BuiltExcelWorkbook = {
133
+ ok: true;
134
+ workbook: WorkBook;
135
+ fileName: string;
136
+ sheetName: string;
137
+ } | ExportExcelFailure;
138
+ /**
139
+ * Build a styled workbook. Does not write a file.
140
+ * Header merges follow the column tree, so two groups with the same title stay separate.
141
+ */
142
+ declare const buildExcelWorkbook: (options?: ExportExcelReportOptions) => BuiltExcelWorkbook;
143
+ /** Write a styled .xlsx. Returns a result; it does not toast. */
144
+ declare const exportExcelReport: (options?: ExportExcelReportOptions) => ExportExcelReportResult;
145
+
146
+ export { type BuiltExcelWorkbook, DEFAULT_EXCEL_THEME, type ExcelAlign, type ExcelCellStyle, type ExcelColumnStyle, type ExcelExportColumn, type ExcelFormatParams, type ExcelTheme, type ExportExcelErrorCode, type ExportExcelFailure, type ExportExcelReportOptions, type ExportExcelReportResult, buildExcelWorkbook, excelDataStyle, excelHeaderStyle, exportExcelReport };
@@ -0,0 +1,355 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/excel/index.ts
31
+ var excel_exports = {};
32
+ __export(excel_exports, {
33
+ DEFAULT_EXCEL_THEME: () => DEFAULT_EXCEL_THEME,
34
+ buildExcelWorkbook: () => buildExcelWorkbook,
35
+ excelDataStyle: () => excelDataStyle,
36
+ excelHeaderStyle: () => excelHeaderStyle,
37
+ exportExcelReport: () => exportExcelReport
38
+ });
39
+ module.exports = __toCommonJS(excel_exports);
40
+ var XLSX = __toESM(require("xlsx-js-style"));
41
+ var ERRORS = {
42
+ empty: "No valid data to download. Please ensure data is available.",
43
+ invalid: "Invalid data structure. Please ensure data is correctly formatted.",
44
+ "no-columns": "No data to export. Please check the data source.",
45
+ filename: "Failed to generate file name. Please try again."
46
+ };
47
+ var DEFAULT_EXCEL_THEME = {
48
+ headerFill: "2F75B6",
49
+ headerColor: "FFFFFF",
50
+ fill: "FFFFFF",
51
+ color: "1F2933",
52
+ borderColor: "8FA4BF",
53
+ fontName: "Calibri",
54
+ fontSize: 11
55
+ };
56
+ var fail = (code) => ({
57
+ ok: false,
58
+ code,
59
+ error: ERRORS[code]
60
+ });
61
+ var rgb = (input, fallback) => {
62
+ if (!input) return fallback;
63
+ const hex = input.trim().replace(/^#/, "").toUpperCase();
64
+ return /^[0-9A-F]{6}$/.test(hex) ? hex : fallback;
65
+ };
66
+ var resolveTheme = (theme) => ({
67
+ headerFill: rgb(theme?.headerFill, DEFAULT_EXCEL_THEME.headerFill),
68
+ headerColor: rgb(theme?.headerColor, DEFAULT_EXCEL_THEME.headerColor),
69
+ fill: rgb(theme?.fill, DEFAULT_EXCEL_THEME.fill),
70
+ color: rgb(theme?.color, DEFAULT_EXCEL_THEME.color),
71
+ borderColor: rgb(theme?.borderColor, DEFAULT_EXCEL_THEME.borderColor),
72
+ fontName: theme?.fontName?.trim() || DEFAULT_EXCEL_THEME.fontName,
73
+ fontSize: typeof theme?.fontSize === "number" && theme.fontSize > 0 ? theme.fontSize : DEFAULT_EXCEL_THEME.fontSize
74
+ });
75
+ var gridBorder = (color) => {
76
+ const edge = () => ({ style: "thin", color: { rgb: color } });
77
+ return { top: edge(), bottom: edge(), left: edge(), right: edge() };
78
+ };
79
+ var excelHeaderStyle = (theme, column) => {
80
+ const resolved = resolveTheme(theme);
81
+ return {
82
+ font: {
83
+ name: resolved.fontName,
84
+ sz: resolved.fontSize,
85
+ bold: true,
86
+ color: { rgb: rgb(column?.headerColor, resolved.headerColor) }
87
+ },
88
+ fill: {
89
+ patternType: "solid",
90
+ fgColor: { rgb: rgb(column?.headerFill, resolved.headerFill) }
91
+ },
92
+ alignment: {
93
+ vertical: "center",
94
+ horizontal: column?.align ?? "left",
95
+ wrapText: false
96
+ },
97
+ border: gridBorder(resolved.borderColor)
98
+ };
99
+ };
100
+ var excelDataStyle = (theme, column) => {
101
+ const resolved = resolveTheme(theme);
102
+ const font = {
103
+ name: resolved.fontName,
104
+ sz: resolved.fontSize,
105
+ color: { rgb: rgb(column?.color, resolved.color) }
106
+ };
107
+ if (column?.bold) font.bold = true;
108
+ const style = {
109
+ font,
110
+ fill: {
111
+ patternType: "solid",
112
+ fgColor: { rgb: rgb(column?.fill, resolved.fill) }
113
+ },
114
+ alignment: {
115
+ vertical: "center",
116
+ horizontal: column?.align ?? "left",
117
+ wrapText: false
118
+ },
119
+ border: gridBorder(resolved.borderColor)
120
+ };
121
+ if (column?.numFmt) style.numFmt = column.numFmt;
122
+ return style;
123
+ };
124
+ var getNestedValue = (obj, path) => {
125
+ if (!obj || typeof obj !== "object") return "";
126
+ try {
127
+ const value = path.split(".").reduce((acc, key) => {
128
+ if (acc == null || typeof acc !== "object") return void 0;
129
+ return acc[key];
130
+ }, obj);
131
+ return value ?? "";
132
+ } catch {
133
+ return "";
134
+ }
135
+ };
136
+ var isGroup = (col) => Array.isArray(col.children) && col.children.length > 0;
137
+ var isSkippedLeaf = (col) => {
138
+ if (col.checkboxSelection || col.headerCheckboxSelection) return true;
139
+ const field = typeof col.field === "string" ? col.field.trim() : "";
140
+ return !field || field === "checkbox" || field === "checkboxSelection";
141
+ };
142
+ var collectLeaves = (cols, groups, skipHidden) => {
143
+ const leaves = [];
144
+ cols.forEach((col) => {
145
+ if (!col || typeof col !== "object") return;
146
+ if (skipHidden && col.hide) return;
147
+ if (isGroup(col)) {
148
+ const group = {
149
+ name: col.headerName || "",
150
+ excel: col.excel
151
+ };
152
+ leaves.push(...collectLeaves(col.children ?? [], [...groups, group], skipHidden));
153
+ return;
154
+ }
155
+ if (isSkippedLeaf(col)) return;
156
+ const field = col.field.trim();
157
+ leaves.push({
158
+ field,
159
+ headerName: (col.headerName || field).trim() || field,
160
+ column: col,
161
+ groups
162
+ });
163
+ });
164
+ return leaves;
165
+ };
166
+ var labelFromKey = (key) => key.replace(/([A-Z])/g, " $1").replace(/^\w/, (char) => char.toUpperCase()).trim();
167
+ var columnsFromRows = (rows) => {
168
+ const first = rows[0];
169
+ if (!first || typeof first !== "object" || Array.isArray(first)) {
170
+ return fail("invalid");
171
+ }
172
+ return Object.keys(first).filter((key) => key !== "checkbox" && key !== "checkboxSelection" && key.trim()).map((key) => ({ field: key, headerName: labelFromKey(key) }));
173
+ };
174
+ var formatLeafValue = (row, leaf, options) => {
175
+ let value = getNestedValue(row, leaf.field);
176
+ const formatter = leaf.column.format || leaf.column.valueFormatter;
177
+ if (typeof formatter === "function") {
178
+ try {
179
+ return formatter({ value, data: row }) ?? "";
180
+ } catch {
181
+ return value ?? "";
182
+ }
183
+ }
184
+ const fieldLooksLikeDate = leaf.field.toLowerCase().includes("date");
185
+ if (options.formatDateFields && fieldLooksLikeDate && typeof options.formatDate === "function") {
186
+ try {
187
+ return options.formatDate(value) ?? "";
188
+ } catch {
189
+ return value ?? "";
190
+ }
191
+ }
192
+ if (value instanceof Date) return value;
193
+ if (value != null && typeof value === "object") return "";
194
+ const numFmt = leaf.column.excel?.numFmt;
195
+ if (numFmt) {
196
+ if (typeof value === "number" && Number.isFinite(value)) return value;
197
+ if (typeof value === "string" && value.trim() !== "" && Number.isFinite(Number(value))) {
198
+ return Number(value);
199
+ }
200
+ }
201
+ return value ?? "";
202
+ };
203
+ var localDateStamp = () => {
204
+ const date = /* @__PURE__ */ new Date();
205
+ const year = date.getFullYear();
206
+ const month = String(date.getMonth() + 1).padStart(2, "0");
207
+ const day = String(date.getDate()).padStart(2, "0");
208
+ return `${year}-${month}-${day}`;
209
+ };
210
+ var resolveDateStamp = (dateStamp) => {
211
+ if (dateStamp === null) return null;
212
+ if (typeof dateStamp === "string" && dateStamp.trim()) return dateStamp.trim();
213
+ return localDateStamp();
214
+ };
215
+ var safeSheetName = (name) => {
216
+ const cleaned = (name || "Report").replace(/[:\\/?*[\]]/g, " ").trim() || "Report";
217
+ return cleaned.slice(0, 31);
218
+ };
219
+ var buildHeaderGrid = (leaves) => {
220
+ const totalHeaderRows = Math.max(...leaves.map((leaf) => leaf.groups.length + 1), 1);
221
+ const headerRows = Array.from(
222
+ { length: totalHeaderRows },
223
+ () => Array.from({ length: leaves.length }, () => "")
224
+ );
225
+ const merges = [];
226
+ for (let depth = 0; depth < totalHeaderRows - 1; depth += 1) {
227
+ let colIndex = 0;
228
+ while (colIndex < leaves.length) {
229
+ const group = leaves[colIndex].groups[depth];
230
+ if (!group) {
231
+ colIndex += 1;
232
+ continue;
233
+ }
234
+ let span = 1;
235
+ while (colIndex + span < leaves.length && leaves[colIndex + span].groups[depth] === group) {
236
+ span += 1;
237
+ }
238
+ headerRows[depth][colIndex] = group.name;
239
+ if (span > 1) {
240
+ merges.push({
241
+ s: { r: depth, c: colIndex },
242
+ e: { r: depth, c: colIndex + span - 1 }
243
+ });
244
+ }
245
+ colIndex += span;
246
+ }
247
+ }
248
+ leaves.forEach((leaf, colIndex) => {
249
+ const row = leaf.groups.length;
250
+ headerRows[row][colIndex] = leaf.headerName;
251
+ if (row < totalHeaderRows - 1) {
252
+ merges.push({
253
+ s: { r: row, c: colIndex },
254
+ e: { r: totalHeaderRows - 1, c: colIndex }
255
+ });
256
+ }
257
+ });
258
+ return { headerRows, merges, totalHeaderRows };
259
+ };
260
+ var headerStyleFor = (leaf, row, theme) => {
261
+ const source = row < leaf.groups.length ? leaf.groups[row].excel : leaf.column.excel;
262
+ return excelHeaderStyle(theme, source);
263
+ };
264
+ var columnWidth = (leaf, values) => {
265
+ const headerLength = Math.max(
266
+ leaf.headerName.length,
267
+ ...leaf.groups.map((group) => group.name.length),
268
+ 0
269
+ );
270
+ const dataLength = values.reduce((max, value) => {
271
+ return Math.max(max, String(value ?? "").length);
272
+ }, 0);
273
+ return { wch: Math.min(Math.max(headerLength, dataLength, 12), 50) };
274
+ };
275
+ var buildExcelWorkbook = (options = {}) => {
276
+ const data = options.data;
277
+ if (!Array.isArray(data) || data.length === 0) return fail("empty");
278
+ const provided = Array.isArray(options.columns) ? options.columns : [];
279
+ const columns = provided.length ? provided : columnsFromRows(data);
280
+ if (!Array.isArray(columns)) return columns;
281
+ const leaves = collectLeaves(columns, [], options.skipHidden === true);
282
+ if (!leaves.length) return fail("no-columns");
283
+ const dateStamp = resolveDateStamp(options.dateStamp);
284
+ if (!dateStamp) return fail("filename");
285
+ const { headerRows, merges, totalHeaderRows } = buildHeaderGrid(leaves);
286
+ const records = data.map(
287
+ (row) => row && typeof row === "object" && !Array.isArray(row) ? row : {}
288
+ );
289
+ const dataRows = records.map(
290
+ (row) => leaves.map((leaf) => formatLeafValue(row, leaf, options))
291
+ );
292
+ const sheet = XLSX.utils.aoa_to_sheet([...headerRows, ...dataRows]);
293
+ const lastCell = XLSX.utils.encode_cell({
294
+ r: headerRows.length + dataRows.length - 1,
295
+ c: leaves.length - 1
296
+ });
297
+ sheet["!ref"] = `A1:${lastCell}`;
298
+ sheet["!merges"] = merges;
299
+ sheet["!cols"] = leaves.map(
300
+ (leaf, index) => columnWidth(
301
+ leaf,
302
+ dataRows.map((row) => row[index])
303
+ )
304
+ );
305
+ sheet["!rows"] = Array.from({ length: totalHeaderRows }, () => ({ hpt: 22 }));
306
+ const range = XLSX.utils.decode_range(sheet["!ref"]);
307
+ for (let row = range.s.r; row <= range.e.r; row += 1) {
308
+ for (let col = range.s.c; col <= range.e.c; col += 1) {
309
+ const address = XLSX.utils.encode_cell({ r: row, c: col });
310
+ const cell = sheet[address] ?? {
311
+ t: "s",
312
+ v: ""
313
+ };
314
+ const leaf = leaves[col];
315
+ if (row < totalHeaderRows) {
316
+ cell.s = headerStyleFor(leaf, row, options.theme);
317
+ } else {
318
+ const style = excelDataStyle(options.theme, leaf.column.excel);
319
+ if (leaf.column.excel?.numFmt) cell.z = leaf.column.excel.numFmt;
320
+ cell.s = style;
321
+ }
322
+ sheet[address] = cell;
323
+ }
324
+ }
325
+ const sheetName = safeSheetName(options.sheetName);
326
+ const workbook = XLSX.utils.book_new();
327
+ XLSX.utils.book_append_sheet(workbook, sheet, sheetName);
328
+ const baseName = options.fileName == null ? "" : String(options.fileName);
329
+ return {
330
+ ok: true,
331
+ workbook,
332
+ sheetName,
333
+ fileName: `${baseName ? `${baseName}_` : ""}${dateStamp}.xlsx`
334
+ };
335
+ };
336
+ var exportExcelReport = (options = {}) => {
337
+ const built = buildExcelWorkbook(options);
338
+ if (!built.ok) return built;
339
+ try {
340
+ XLSX.writeFile(built.workbook, built.fileName, { cellStyles: true });
341
+ } catch (error) {
342
+ const message = error instanceof Error ? error.message : "Unknown error";
343
+ return { ok: false, code: "write", error: message };
344
+ }
345
+ return { ok: true, fileName: built.fileName };
346
+ };
347
+ // Annotate the CommonJS export names for ESM import in node:
348
+ 0 && (module.exports = {
349
+ DEFAULT_EXCEL_THEME,
350
+ buildExcelWorkbook,
351
+ excelDataStyle,
352
+ excelHeaderStyle,
353
+ exportExcelReport
354
+ });
355
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/excel/index.ts"],"sourcesContent":["import * as XLSX from \"xlsx-js-style\";\r\n\r\nexport type ExcelAlign = \"left\" | \"center\" | \"right\";\r\n\r\n/** Per-column Excel colors. Hex is RRGGBB; a leading # is accepted. */\r\nexport type ExcelColumnStyle = {\r\n headerFill?: string;\r\n headerColor?: string;\r\n fill?: string;\r\n color?: string;\r\n bold?: boolean;\r\n align?: ExcelAlign;\r\n /** Excel number format, for example '\"$\"#,##0.00'. */\r\n numFmt?: string;\r\n};\r\n\r\nexport type ExcelTheme = {\r\n headerFill?: string;\r\n headerColor?: string;\r\n fill?: string;\r\n color?: string;\r\n borderColor?: string;\r\n fontName?: string;\r\n fontSize?: number;\r\n};\r\n\r\nexport type ExcelFormatParams = {\r\n value: unknown;\r\n data: Record<string, unknown>;\r\n};\r\n\r\nexport type ExcelExportColumn = {\r\n field?: string;\r\n headerName?: string;\r\n children?: ExcelExportColumn[];\r\n hide?: boolean;\r\n checkboxSelection?: boolean;\r\n headerCheckboxSelection?: boolean;\r\n valueFormatter?: (params: ExcelFormatParams) => unknown;\r\n format?: (params: ExcelFormatParams) => unknown;\r\n excel?: ExcelColumnStyle;\r\n};\r\n\r\nexport type ExportExcelErrorCode =\r\n | \"empty\"\r\n | \"invalid\"\r\n | \"no-columns\"\r\n | \"filename\"\r\n | \"write\";\r\n\r\nexport type ExportExcelFailure = {\r\n ok: false;\r\n code: ExportExcelErrorCode;\r\n error: string;\r\n};\r\n\r\nexport type ExportExcelReportResult =\r\n | { ok: true; fileName: string }\r\n | ExportExcelFailure;\r\n\r\nexport type ExportExcelReportOptions = {\r\n data?: unknown[] | null;\r\n fileName?: string;\r\n columns?: ExcelExportColumn[];\r\n sheetName?: string;\r\n theme?: ExcelTheme;\r\n /** Drop columns with hide: true. Default false, matching vault grids. */\r\n skipHidden?: boolean;\r\n /**\r\n * Format leaf fields whose name contains \"date\" when they have no formatter.\r\n * Vault turns this on and passes its own date formatter.\r\n */\r\n formatDateFields?: boolean;\r\n formatDate?: (value: unknown) => unknown;\r\n /**\r\n * Date suffix for the file name. Omit to use the local YYYY-MM-DD.\r\n * Pass null when the caller could not produce a stamp.\r\n */\r\n dateStamp?: string | null;\r\n};\r\n\r\ntype ResolvedTheme = {\r\n headerFill: string;\r\n headerColor: string;\r\n fill: string;\r\n color: string;\r\n borderColor: string;\r\n fontName: string;\r\n fontSize: number;\r\n};\r\n\r\nexport type ExcelCellStyle = {\r\n font: {\r\n name: string;\r\n sz: number;\r\n bold?: boolean;\r\n color: { rgb: string };\r\n };\r\n fill: { patternType: \"solid\"; fgColor: { rgb: string } };\r\n alignment: {\r\n vertical: \"center\";\r\n horizontal: ExcelAlign;\r\n wrapText: false;\r\n };\r\n border: {\r\n top: { style: \"thin\"; color: { rgb: string } };\r\n bottom: { style: \"thin\"; color: { rgb: string } };\r\n left: { style: \"thin\"; color: { rgb: string } };\r\n right: { style: \"thin\"; color: { rgb: string } };\r\n };\r\n numFmt?: string;\r\n};\r\n\r\ntype HeaderGroup = {\r\n name: string;\r\n excel?: ExcelColumnStyle;\r\n};\r\n\r\ntype LeafColumn = {\r\n field: string;\r\n headerName: string;\r\n column: ExcelExportColumn;\r\n groups: HeaderGroup[];\r\n};\r\n\r\ntype WorkBook = {\r\n SheetNames: string[];\r\n Sheets: Record<string, WorkSheet>;\r\n};\r\n\r\ntype WorkSheet = Record<string, unknown>;\r\n\r\nconst ERRORS: Record<Exclude<ExportExcelErrorCode, \"write\">, string> = {\r\n empty: \"No valid data to download. Please ensure data is available.\",\r\n invalid: \"Invalid data structure. Please ensure data is correctly formatted.\",\r\n \"no-columns\": \"No data to export. Please check the data source.\",\r\n filename: \"Failed to generate file name. Please try again.\",\r\n};\r\n\r\nexport const DEFAULT_EXCEL_THEME: ResolvedTheme = {\r\n headerFill: \"2F75B6\",\r\n headerColor: \"FFFFFF\",\r\n fill: \"FFFFFF\",\r\n color: \"1F2933\",\r\n borderColor: \"8FA4BF\",\r\n fontName: \"Calibri\",\r\n fontSize: 11,\r\n};\r\n\r\nconst fail = (\r\n code: Exclude<ExportExcelErrorCode, \"write\">,\r\n): ExportExcelFailure => ({\r\n ok: false,\r\n code,\r\n error: ERRORS[code],\r\n});\r\n\r\nconst rgb = (input: string | undefined, fallback: string): string => {\r\n if (!input) return fallback;\r\n const hex = input.trim().replace(/^#/, \"\").toUpperCase();\r\n return /^[0-9A-F]{6}$/.test(hex) ? hex : fallback;\r\n};\r\n\r\nconst resolveTheme = (theme?: ExcelTheme): ResolvedTheme => ({\r\n headerFill: rgb(theme?.headerFill, DEFAULT_EXCEL_THEME.headerFill),\r\n headerColor: rgb(theme?.headerColor, DEFAULT_EXCEL_THEME.headerColor),\r\n fill: rgb(theme?.fill, DEFAULT_EXCEL_THEME.fill),\r\n color: rgb(theme?.color, DEFAULT_EXCEL_THEME.color),\r\n borderColor: rgb(theme?.borderColor, DEFAULT_EXCEL_THEME.borderColor),\r\n fontName: theme?.fontName?.trim() || DEFAULT_EXCEL_THEME.fontName,\r\n fontSize:\r\n typeof theme?.fontSize === \"number\" && theme.fontSize > 0\r\n ? theme.fontSize\r\n : DEFAULT_EXCEL_THEME.fontSize,\r\n});\r\n\r\nconst gridBorder = (color: string): ExcelCellStyle[\"border\"] => {\r\n const edge = () => ({ style: \"thin\" as const, color: { rgb: color } });\r\n return { top: edge(), bottom: edge(), left: edge(), right: edge() };\r\n};\r\n\r\nexport const excelHeaderStyle = (\r\n theme?: ExcelTheme,\r\n column?: ExcelColumnStyle,\r\n): ExcelCellStyle => {\r\n const resolved = resolveTheme(theme);\r\n return {\r\n font: {\r\n name: resolved.fontName,\r\n sz: resolved.fontSize,\r\n bold: true,\r\n color: { rgb: rgb(column?.headerColor, resolved.headerColor) },\r\n },\r\n fill: {\r\n patternType: \"solid\",\r\n fgColor: { rgb: rgb(column?.headerFill, resolved.headerFill) },\r\n },\r\n alignment: {\r\n vertical: \"center\",\r\n horizontal: column?.align ?? \"left\",\r\n wrapText: false,\r\n },\r\n border: gridBorder(resolved.borderColor),\r\n };\r\n};\r\n\r\nexport const excelDataStyle = (\r\n theme?: ExcelTheme,\r\n column?: ExcelColumnStyle,\r\n): ExcelCellStyle => {\r\n const resolved = resolveTheme(theme);\r\n const font: ExcelCellStyle[\"font\"] = {\r\n name: resolved.fontName,\r\n sz: resolved.fontSize,\r\n color: { rgb: rgb(column?.color, resolved.color) },\r\n };\r\n if (column?.bold) font.bold = true;\r\n const style: ExcelCellStyle = {\r\n font,\r\n fill: {\r\n patternType: \"solid\",\r\n fgColor: { rgb: rgb(column?.fill, resolved.fill) },\r\n },\r\n alignment: {\r\n vertical: \"center\",\r\n horizontal: column?.align ?? \"left\",\r\n wrapText: false,\r\n },\r\n border: gridBorder(resolved.borderColor),\r\n };\r\n if (column?.numFmt) style.numFmt = column.numFmt;\r\n return style;\r\n};\r\n\r\nconst getNestedValue = (obj: unknown, path: string): unknown => {\r\n if (!obj || typeof obj !== \"object\") return \"\";\r\n try {\r\n const value = path.split(\".\").reduce<unknown>((acc, key) => {\r\n if (acc == null || typeof acc !== \"object\") return undefined;\r\n return (acc as Record<string, unknown>)[key];\r\n }, obj);\r\n return value ?? \"\";\r\n } catch {\r\n return \"\";\r\n }\r\n};\r\n\r\nconst isGroup = (col: ExcelExportColumn): boolean =>\r\n Array.isArray(col.children) && col.children.length > 0;\r\n\r\nconst isSkippedLeaf = (col: ExcelExportColumn): boolean => {\r\n if (col.checkboxSelection || col.headerCheckboxSelection) return true;\r\n const field = typeof col.field === \"string\" ? col.field.trim() : \"\";\r\n return !field || field === \"checkbox\" || field === \"checkboxSelection\";\r\n};\r\n\r\nconst collectLeaves = (\r\n cols: ExcelExportColumn[],\r\n groups: HeaderGroup[],\r\n skipHidden: boolean,\r\n): LeafColumn[] => {\r\n const leaves: LeafColumn[] = [];\r\n cols.forEach((col) => {\r\n if (!col || typeof col !== \"object\") return;\r\n if (skipHidden && col.hide) return;\r\n if (isGroup(col)) {\r\n const group: HeaderGroup = {\r\n name: col.headerName || \"\",\r\n excel: col.excel,\r\n };\r\n leaves.push(...collectLeaves(col.children ?? [], [...groups, group], skipHidden));\r\n return;\r\n }\r\n if (isSkippedLeaf(col)) return;\r\n const field = col.field!.trim();\r\n leaves.push({\r\n field,\r\n headerName: (col.headerName || field).trim() || field,\r\n column: col,\r\n groups,\r\n });\r\n });\r\n return leaves;\r\n};\r\n\r\nconst labelFromKey = (key: string): string =>\r\n key\r\n .replace(/([A-Z])/g, \" $1\")\r\n .replace(/^\\w/, (char) => char.toUpperCase())\r\n .trim();\r\n\r\nconst columnsFromRows = (rows: unknown[]): ExcelExportColumn[] | ExportExcelFailure => {\r\n const first = rows[0];\r\n if (!first || typeof first !== \"object\" || Array.isArray(first)) {\r\n return fail(\"invalid\");\r\n }\r\n return Object.keys(first)\r\n .filter((key) => key !== \"checkbox\" && key !== \"checkboxSelection\" && key.trim())\r\n .map((key) => ({ field: key, headerName: labelFromKey(key) }));\r\n};\r\n\r\nconst formatLeafValue = (\r\n row: Record<string, unknown>,\r\n leaf: LeafColumn,\r\n options: ExportExcelReportOptions,\r\n): unknown => {\r\n let value = getNestedValue(row, leaf.field);\r\n const formatter = leaf.column.format || leaf.column.valueFormatter;\r\n if (typeof formatter === \"function\") {\r\n try {\r\n return formatter({ value, data: row }) ?? \"\";\r\n } catch {\r\n return value ?? \"\";\r\n }\r\n }\r\n\r\n const fieldLooksLikeDate = leaf.field.toLowerCase().includes(\"date\");\r\n if (options.formatDateFields && fieldLooksLikeDate && typeof options.formatDate === \"function\") {\r\n try {\r\n return options.formatDate(value) ?? \"\";\r\n } catch {\r\n return value ?? \"\";\r\n }\r\n }\r\n\r\n if (value instanceof Date) return value;\r\n if (value != null && typeof value === \"object\") return \"\";\r\n\r\n const numFmt = leaf.column.excel?.numFmt;\r\n if (numFmt) {\r\n if (typeof value === \"number\" && Number.isFinite(value)) return value;\r\n if (typeof value === \"string\" && value.trim() !== \"\" && Number.isFinite(Number(value))) {\r\n return Number(value);\r\n }\r\n }\r\n\r\n return value ?? \"\";\r\n};\r\n\r\nconst localDateStamp = (): string => {\r\n const date = new Date();\r\n const year = date.getFullYear();\r\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\r\n const day = String(date.getDate()).padStart(2, \"0\");\r\n return `${year}-${month}-${day}`;\r\n};\r\n\r\nconst resolveDateStamp = (dateStamp: string | null | undefined): string | null => {\r\n if (dateStamp === null) return null;\r\n if (typeof dateStamp === \"string\" && dateStamp.trim()) return dateStamp.trim();\r\n return localDateStamp();\r\n};\r\n\r\nconst safeSheetName = (name: string | undefined): string => {\r\n const cleaned = (name || \"Report\").replace(/[:\\\\/?*[\\]]/g, \" \").trim() || \"Report\";\r\n return cleaned.slice(0, 31);\r\n};\r\n\r\nconst buildHeaderGrid = (leaves: LeafColumn[]) => {\r\n const totalHeaderRows = Math.max(...leaves.map((leaf) => leaf.groups.length + 1), 1);\r\n const headerRows = Array.from({ length: totalHeaderRows }, () =>\r\n Array.from({ length: leaves.length }, () => \"\"),\r\n );\r\n const merges: { s: { r: number; c: number }; e: { r: number; c: number } }[] = [];\r\n\r\n for (let depth = 0; depth < totalHeaderRows - 1; depth += 1) {\r\n let colIndex = 0;\r\n while (colIndex < leaves.length) {\r\n const group = leaves[colIndex].groups[depth];\r\n if (!group) {\r\n colIndex += 1;\r\n continue;\r\n }\r\n let span = 1;\r\n while (colIndex + span < leaves.length && leaves[colIndex + span].groups[depth] === group) {\r\n span += 1;\r\n }\r\n headerRows[depth][colIndex] = group.name;\r\n if (span > 1) {\r\n merges.push({\r\n s: { r: depth, c: colIndex },\r\n e: { r: depth, c: colIndex + span - 1 },\r\n });\r\n }\r\n colIndex += span;\r\n }\r\n }\r\n\r\n leaves.forEach((leaf, colIndex) => {\r\n const row = leaf.groups.length;\r\n headerRows[row][colIndex] = leaf.headerName;\r\n if (row < totalHeaderRows - 1) {\r\n merges.push({\r\n s: { r: row, c: colIndex },\r\n e: { r: totalHeaderRows - 1, c: colIndex },\r\n });\r\n }\r\n });\r\n\r\n return { headerRows, merges, totalHeaderRows };\r\n};\r\n\r\nconst headerStyleFor = (\r\n leaf: LeafColumn,\r\n row: number,\r\n theme: ExcelTheme | undefined,\r\n): ExcelCellStyle => {\r\n const source = row < leaf.groups.length ? leaf.groups[row].excel : leaf.column.excel;\r\n return excelHeaderStyle(theme, source);\r\n};\r\n\r\nconst columnWidth = (leaf: LeafColumn, values: unknown[]): { wch: number } => {\r\n const headerLength = Math.max(\r\n leaf.headerName.length,\r\n ...leaf.groups.map((group) => group.name.length),\r\n 0,\r\n );\r\n const dataLength = values.reduce<number>((max, value) => {\r\n return Math.max(max, String(value ?? \"\").length);\r\n }, 0);\r\n return { wch: Math.min(Math.max(headerLength, dataLength, 12), 50) };\r\n};\r\n\r\nexport type BuiltExcelWorkbook =\r\n | { ok: true; workbook: WorkBook; fileName: string; sheetName: string }\r\n | ExportExcelFailure;\r\n\r\n/**\r\n * Build a styled workbook. Does not write a file.\r\n * Header merges follow the column tree, so two groups with the same title stay separate.\r\n */\r\nexport const buildExcelWorkbook = (\r\n options: ExportExcelReportOptions = {},\r\n): BuiltExcelWorkbook => {\r\n const data = options.data;\r\n if (!Array.isArray(data) || data.length === 0) return fail(\"empty\");\r\n\r\n const provided = Array.isArray(options.columns) ? options.columns : [];\r\n const columns = provided.length\r\n ? provided\r\n : columnsFromRows(data);\r\n if (!Array.isArray(columns)) return columns;\r\n\r\n const leaves = collectLeaves(columns, [], options.skipHidden === true);\r\n if (!leaves.length) return fail(\"no-columns\");\r\n\r\n const dateStamp = resolveDateStamp(options.dateStamp);\r\n if (!dateStamp) return fail(\"filename\");\r\n\r\n const { headerRows, merges, totalHeaderRows } = buildHeaderGrid(leaves);\r\n const records = data.map((row) =>\r\n row && typeof row === \"object\" && !Array.isArray(row)\r\n ? (row as Record<string, unknown>)\r\n : {},\r\n );\r\n const dataRows = records.map((row) =>\r\n leaves.map((leaf) => formatLeafValue(row, leaf, options)),\r\n );\r\n\r\n const sheet = XLSX.utils.aoa_to_sheet([...headerRows, ...dataRows]) as WorkSheet;\r\n const lastCell = XLSX.utils.encode_cell({\r\n r: headerRows.length + dataRows.length - 1,\r\n c: leaves.length - 1,\r\n });\r\n sheet[\"!ref\"] = `A1:${lastCell}`;\r\n sheet[\"!merges\"] = merges;\r\n sheet[\"!cols\"] = leaves.map((leaf, index) =>\r\n columnWidth(\r\n leaf,\r\n dataRows.map((row) => row[index]),\r\n ),\r\n );\r\n sheet[\"!rows\"] = Array.from({ length: totalHeaderRows }, () => ({ hpt: 22 }));\r\n\r\n const range = XLSX.utils.decode_range(sheet[\"!ref\"] as string);\r\n for (let row = range.s.r; row <= range.e.r; row += 1) {\r\n for (let col = range.s.c; col <= range.e.c; col += 1) {\r\n const address = XLSX.utils.encode_cell({ r: row, c: col });\r\n const cell = (sheet[address] as { t?: string; v?: unknown; z?: string; s?: ExcelCellStyle } | undefined) ?? {\r\n t: \"s\",\r\n v: \"\",\r\n };\r\n const leaf = leaves[col];\r\n if (row < totalHeaderRows) {\r\n cell.s = headerStyleFor(leaf, row, options.theme);\r\n } else {\r\n const style = excelDataStyle(options.theme, leaf.column.excel);\r\n if (leaf.column.excel?.numFmt) cell.z = leaf.column.excel.numFmt;\r\n cell.s = style;\r\n }\r\n sheet[address] = cell;\r\n }\r\n }\r\n\r\n const sheetName = safeSheetName(options.sheetName);\r\n const workbook = XLSX.utils.book_new() as WorkBook;\r\n XLSX.utils.book_append_sheet(workbook, sheet, sheetName);\r\n\r\n const baseName = options.fileName == null ? \"\" : String(options.fileName);\r\n return {\r\n ok: true,\r\n workbook,\r\n sheetName,\r\n fileName: `${baseName ? `${baseName}_` : \"\"}${dateStamp}.xlsx`,\r\n };\r\n};\r\n\r\n/** Write a styled .xlsx. Returns a result; it does not toast. */\r\nexport const exportExcelReport = (\r\n options: ExportExcelReportOptions = {},\r\n): ExportExcelReportResult => {\r\n const built = buildExcelWorkbook(options);\r\n if (!built.ok) return built;\r\n try {\r\n XLSX.writeFile(built.workbook, built.fileName, { cellStyles: true });\r\n } catch (error) {\r\n const message = error instanceof Error ? error.message : \"Unknown error\";\r\n return { ok: false, code: \"write\", error: message };\r\n }\r\n return { ok: true, fileName: built.fileName };\r\n};\r\n"],"mappings":"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","names":[]}
@@ -0,0 +1,316 @@
1
+ // src/excel/index.ts
2
+ import * as XLSX from "xlsx-js-style";
3
+ var ERRORS = {
4
+ empty: "No valid data to download. Please ensure data is available.",
5
+ invalid: "Invalid data structure. Please ensure data is correctly formatted.",
6
+ "no-columns": "No data to export. Please check the data source.",
7
+ filename: "Failed to generate file name. Please try again."
8
+ };
9
+ var DEFAULT_EXCEL_THEME = {
10
+ headerFill: "2F75B6",
11
+ headerColor: "FFFFFF",
12
+ fill: "FFFFFF",
13
+ color: "1F2933",
14
+ borderColor: "8FA4BF",
15
+ fontName: "Calibri",
16
+ fontSize: 11
17
+ };
18
+ var fail = (code) => ({
19
+ ok: false,
20
+ code,
21
+ error: ERRORS[code]
22
+ });
23
+ var rgb = (input, fallback) => {
24
+ if (!input) return fallback;
25
+ const hex = input.trim().replace(/^#/, "").toUpperCase();
26
+ return /^[0-9A-F]{6}$/.test(hex) ? hex : fallback;
27
+ };
28
+ var resolveTheme = (theme) => ({
29
+ headerFill: rgb(theme?.headerFill, DEFAULT_EXCEL_THEME.headerFill),
30
+ headerColor: rgb(theme?.headerColor, DEFAULT_EXCEL_THEME.headerColor),
31
+ fill: rgb(theme?.fill, DEFAULT_EXCEL_THEME.fill),
32
+ color: rgb(theme?.color, DEFAULT_EXCEL_THEME.color),
33
+ borderColor: rgb(theme?.borderColor, DEFAULT_EXCEL_THEME.borderColor),
34
+ fontName: theme?.fontName?.trim() || DEFAULT_EXCEL_THEME.fontName,
35
+ fontSize: typeof theme?.fontSize === "number" && theme.fontSize > 0 ? theme.fontSize : DEFAULT_EXCEL_THEME.fontSize
36
+ });
37
+ var gridBorder = (color) => {
38
+ const edge = () => ({ style: "thin", color: { rgb: color } });
39
+ return { top: edge(), bottom: edge(), left: edge(), right: edge() };
40
+ };
41
+ var excelHeaderStyle = (theme, column) => {
42
+ const resolved = resolveTheme(theme);
43
+ return {
44
+ font: {
45
+ name: resolved.fontName,
46
+ sz: resolved.fontSize,
47
+ bold: true,
48
+ color: { rgb: rgb(column?.headerColor, resolved.headerColor) }
49
+ },
50
+ fill: {
51
+ patternType: "solid",
52
+ fgColor: { rgb: rgb(column?.headerFill, resolved.headerFill) }
53
+ },
54
+ alignment: {
55
+ vertical: "center",
56
+ horizontal: column?.align ?? "left",
57
+ wrapText: false
58
+ },
59
+ border: gridBorder(resolved.borderColor)
60
+ };
61
+ };
62
+ var excelDataStyle = (theme, column) => {
63
+ const resolved = resolveTheme(theme);
64
+ const font = {
65
+ name: resolved.fontName,
66
+ sz: resolved.fontSize,
67
+ color: { rgb: rgb(column?.color, resolved.color) }
68
+ };
69
+ if (column?.bold) font.bold = true;
70
+ const style = {
71
+ font,
72
+ fill: {
73
+ patternType: "solid",
74
+ fgColor: { rgb: rgb(column?.fill, resolved.fill) }
75
+ },
76
+ alignment: {
77
+ vertical: "center",
78
+ horizontal: column?.align ?? "left",
79
+ wrapText: false
80
+ },
81
+ border: gridBorder(resolved.borderColor)
82
+ };
83
+ if (column?.numFmt) style.numFmt = column.numFmt;
84
+ return style;
85
+ };
86
+ var getNestedValue = (obj, path) => {
87
+ if (!obj || typeof obj !== "object") return "";
88
+ try {
89
+ const value = path.split(".").reduce((acc, key) => {
90
+ if (acc == null || typeof acc !== "object") return void 0;
91
+ return acc[key];
92
+ }, obj);
93
+ return value ?? "";
94
+ } catch {
95
+ return "";
96
+ }
97
+ };
98
+ var isGroup = (col) => Array.isArray(col.children) && col.children.length > 0;
99
+ var isSkippedLeaf = (col) => {
100
+ if (col.checkboxSelection || col.headerCheckboxSelection) return true;
101
+ const field = typeof col.field === "string" ? col.field.trim() : "";
102
+ return !field || field === "checkbox" || field === "checkboxSelection";
103
+ };
104
+ var collectLeaves = (cols, groups, skipHidden) => {
105
+ const leaves = [];
106
+ cols.forEach((col) => {
107
+ if (!col || typeof col !== "object") return;
108
+ if (skipHidden && col.hide) return;
109
+ if (isGroup(col)) {
110
+ const group = {
111
+ name: col.headerName || "",
112
+ excel: col.excel
113
+ };
114
+ leaves.push(...collectLeaves(col.children ?? [], [...groups, group], skipHidden));
115
+ return;
116
+ }
117
+ if (isSkippedLeaf(col)) return;
118
+ const field = col.field.trim();
119
+ leaves.push({
120
+ field,
121
+ headerName: (col.headerName || field).trim() || field,
122
+ column: col,
123
+ groups
124
+ });
125
+ });
126
+ return leaves;
127
+ };
128
+ var labelFromKey = (key) => key.replace(/([A-Z])/g, " $1").replace(/^\w/, (char) => char.toUpperCase()).trim();
129
+ var columnsFromRows = (rows) => {
130
+ const first = rows[0];
131
+ if (!first || typeof first !== "object" || Array.isArray(first)) {
132
+ return fail("invalid");
133
+ }
134
+ return Object.keys(first).filter((key) => key !== "checkbox" && key !== "checkboxSelection" && key.trim()).map((key) => ({ field: key, headerName: labelFromKey(key) }));
135
+ };
136
+ var formatLeafValue = (row, leaf, options) => {
137
+ let value = getNestedValue(row, leaf.field);
138
+ const formatter = leaf.column.format || leaf.column.valueFormatter;
139
+ if (typeof formatter === "function") {
140
+ try {
141
+ return formatter({ value, data: row }) ?? "";
142
+ } catch {
143
+ return value ?? "";
144
+ }
145
+ }
146
+ const fieldLooksLikeDate = leaf.field.toLowerCase().includes("date");
147
+ if (options.formatDateFields && fieldLooksLikeDate && typeof options.formatDate === "function") {
148
+ try {
149
+ return options.formatDate(value) ?? "";
150
+ } catch {
151
+ return value ?? "";
152
+ }
153
+ }
154
+ if (value instanceof Date) return value;
155
+ if (value != null && typeof value === "object") return "";
156
+ const numFmt = leaf.column.excel?.numFmt;
157
+ if (numFmt) {
158
+ if (typeof value === "number" && Number.isFinite(value)) return value;
159
+ if (typeof value === "string" && value.trim() !== "" && Number.isFinite(Number(value))) {
160
+ return Number(value);
161
+ }
162
+ }
163
+ return value ?? "";
164
+ };
165
+ var localDateStamp = () => {
166
+ const date = /* @__PURE__ */ new Date();
167
+ const year = date.getFullYear();
168
+ const month = String(date.getMonth() + 1).padStart(2, "0");
169
+ const day = String(date.getDate()).padStart(2, "0");
170
+ return `${year}-${month}-${day}`;
171
+ };
172
+ var resolveDateStamp = (dateStamp) => {
173
+ if (dateStamp === null) return null;
174
+ if (typeof dateStamp === "string" && dateStamp.trim()) return dateStamp.trim();
175
+ return localDateStamp();
176
+ };
177
+ var safeSheetName = (name) => {
178
+ const cleaned = (name || "Report").replace(/[:\\/?*[\]]/g, " ").trim() || "Report";
179
+ return cleaned.slice(0, 31);
180
+ };
181
+ var buildHeaderGrid = (leaves) => {
182
+ const totalHeaderRows = Math.max(...leaves.map((leaf) => leaf.groups.length + 1), 1);
183
+ const headerRows = Array.from(
184
+ { length: totalHeaderRows },
185
+ () => Array.from({ length: leaves.length }, () => "")
186
+ );
187
+ const merges = [];
188
+ for (let depth = 0; depth < totalHeaderRows - 1; depth += 1) {
189
+ let colIndex = 0;
190
+ while (colIndex < leaves.length) {
191
+ const group = leaves[colIndex].groups[depth];
192
+ if (!group) {
193
+ colIndex += 1;
194
+ continue;
195
+ }
196
+ let span = 1;
197
+ while (colIndex + span < leaves.length && leaves[colIndex + span].groups[depth] === group) {
198
+ span += 1;
199
+ }
200
+ headerRows[depth][colIndex] = group.name;
201
+ if (span > 1) {
202
+ merges.push({
203
+ s: { r: depth, c: colIndex },
204
+ e: { r: depth, c: colIndex + span - 1 }
205
+ });
206
+ }
207
+ colIndex += span;
208
+ }
209
+ }
210
+ leaves.forEach((leaf, colIndex) => {
211
+ const row = leaf.groups.length;
212
+ headerRows[row][colIndex] = leaf.headerName;
213
+ if (row < totalHeaderRows - 1) {
214
+ merges.push({
215
+ s: { r: row, c: colIndex },
216
+ e: { r: totalHeaderRows - 1, c: colIndex }
217
+ });
218
+ }
219
+ });
220
+ return { headerRows, merges, totalHeaderRows };
221
+ };
222
+ var headerStyleFor = (leaf, row, theme) => {
223
+ const source = row < leaf.groups.length ? leaf.groups[row].excel : leaf.column.excel;
224
+ return excelHeaderStyle(theme, source);
225
+ };
226
+ var columnWidth = (leaf, values) => {
227
+ const headerLength = Math.max(
228
+ leaf.headerName.length,
229
+ ...leaf.groups.map((group) => group.name.length),
230
+ 0
231
+ );
232
+ const dataLength = values.reduce((max, value) => {
233
+ return Math.max(max, String(value ?? "").length);
234
+ }, 0);
235
+ return { wch: Math.min(Math.max(headerLength, dataLength, 12), 50) };
236
+ };
237
+ var buildExcelWorkbook = (options = {}) => {
238
+ const data = options.data;
239
+ if (!Array.isArray(data) || data.length === 0) return fail("empty");
240
+ const provided = Array.isArray(options.columns) ? options.columns : [];
241
+ const columns = provided.length ? provided : columnsFromRows(data);
242
+ if (!Array.isArray(columns)) return columns;
243
+ const leaves = collectLeaves(columns, [], options.skipHidden === true);
244
+ if (!leaves.length) return fail("no-columns");
245
+ const dateStamp = resolveDateStamp(options.dateStamp);
246
+ if (!dateStamp) return fail("filename");
247
+ const { headerRows, merges, totalHeaderRows } = buildHeaderGrid(leaves);
248
+ const records = data.map(
249
+ (row) => row && typeof row === "object" && !Array.isArray(row) ? row : {}
250
+ );
251
+ const dataRows = records.map(
252
+ (row) => leaves.map((leaf) => formatLeafValue(row, leaf, options))
253
+ );
254
+ const sheet = XLSX.utils.aoa_to_sheet([...headerRows, ...dataRows]);
255
+ const lastCell = XLSX.utils.encode_cell({
256
+ r: headerRows.length + dataRows.length - 1,
257
+ c: leaves.length - 1
258
+ });
259
+ sheet["!ref"] = `A1:${lastCell}`;
260
+ sheet["!merges"] = merges;
261
+ sheet["!cols"] = leaves.map(
262
+ (leaf, index) => columnWidth(
263
+ leaf,
264
+ dataRows.map((row) => row[index])
265
+ )
266
+ );
267
+ sheet["!rows"] = Array.from({ length: totalHeaderRows }, () => ({ hpt: 22 }));
268
+ const range = XLSX.utils.decode_range(sheet["!ref"]);
269
+ for (let row = range.s.r; row <= range.e.r; row += 1) {
270
+ for (let col = range.s.c; col <= range.e.c; col += 1) {
271
+ const address = XLSX.utils.encode_cell({ r: row, c: col });
272
+ const cell = sheet[address] ?? {
273
+ t: "s",
274
+ v: ""
275
+ };
276
+ const leaf = leaves[col];
277
+ if (row < totalHeaderRows) {
278
+ cell.s = headerStyleFor(leaf, row, options.theme);
279
+ } else {
280
+ const style = excelDataStyle(options.theme, leaf.column.excel);
281
+ if (leaf.column.excel?.numFmt) cell.z = leaf.column.excel.numFmt;
282
+ cell.s = style;
283
+ }
284
+ sheet[address] = cell;
285
+ }
286
+ }
287
+ const sheetName = safeSheetName(options.sheetName);
288
+ const workbook = XLSX.utils.book_new();
289
+ XLSX.utils.book_append_sheet(workbook, sheet, sheetName);
290
+ const baseName = options.fileName == null ? "" : String(options.fileName);
291
+ return {
292
+ ok: true,
293
+ workbook,
294
+ sheetName,
295
+ fileName: `${baseName ? `${baseName}_` : ""}${dateStamp}.xlsx`
296
+ };
297
+ };
298
+ var exportExcelReport = (options = {}) => {
299
+ const built = buildExcelWorkbook(options);
300
+ if (!built.ok) return built;
301
+ try {
302
+ XLSX.writeFile(built.workbook, built.fileName, { cellStyles: true });
303
+ } catch (error) {
304
+ const message = error instanceof Error ? error.message : "Unknown error";
305
+ return { ok: false, code: "write", error: message };
306
+ }
307
+ return { ok: true, fileName: built.fileName };
308
+ };
309
+ export {
310
+ DEFAULT_EXCEL_THEME,
311
+ buildExcelWorkbook,
312
+ excelDataStyle,
313
+ excelHeaderStyle,
314
+ exportExcelReport
315
+ };
316
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/excel/index.ts"],"sourcesContent":["import * as XLSX from \"xlsx-js-style\";\r\n\r\nexport type ExcelAlign = \"left\" | \"center\" | \"right\";\r\n\r\n/** Per-column Excel colors. Hex is RRGGBB; a leading # is accepted. */\r\nexport type ExcelColumnStyle = {\r\n headerFill?: string;\r\n headerColor?: string;\r\n fill?: string;\r\n color?: string;\r\n bold?: boolean;\r\n align?: ExcelAlign;\r\n /** Excel number format, for example '\"$\"#,##0.00'. */\r\n numFmt?: string;\r\n};\r\n\r\nexport type ExcelTheme = {\r\n headerFill?: string;\r\n headerColor?: string;\r\n fill?: string;\r\n color?: string;\r\n borderColor?: string;\r\n fontName?: string;\r\n fontSize?: number;\r\n};\r\n\r\nexport type ExcelFormatParams = {\r\n value: unknown;\r\n data: Record<string, unknown>;\r\n};\r\n\r\nexport type ExcelExportColumn = {\r\n field?: string;\r\n headerName?: string;\r\n children?: ExcelExportColumn[];\r\n hide?: boolean;\r\n checkboxSelection?: boolean;\r\n headerCheckboxSelection?: boolean;\r\n valueFormatter?: (params: ExcelFormatParams) => unknown;\r\n format?: (params: ExcelFormatParams) => unknown;\r\n excel?: ExcelColumnStyle;\r\n};\r\n\r\nexport type ExportExcelErrorCode =\r\n | \"empty\"\r\n | \"invalid\"\r\n | \"no-columns\"\r\n | \"filename\"\r\n | \"write\";\r\n\r\nexport type ExportExcelFailure = {\r\n ok: false;\r\n code: ExportExcelErrorCode;\r\n error: string;\r\n};\r\n\r\nexport type ExportExcelReportResult =\r\n | { ok: true; fileName: string }\r\n | ExportExcelFailure;\r\n\r\nexport type ExportExcelReportOptions = {\r\n data?: unknown[] | null;\r\n fileName?: string;\r\n columns?: ExcelExportColumn[];\r\n sheetName?: string;\r\n theme?: ExcelTheme;\r\n /** Drop columns with hide: true. Default false, matching vault grids. */\r\n skipHidden?: boolean;\r\n /**\r\n * Format leaf fields whose name contains \"date\" when they have no formatter.\r\n * Vault turns this on and passes its own date formatter.\r\n */\r\n formatDateFields?: boolean;\r\n formatDate?: (value: unknown) => unknown;\r\n /**\r\n * Date suffix for the file name. Omit to use the local YYYY-MM-DD.\r\n * Pass null when the caller could not produce a stamp.\r\n */\r\n dateStamp?: string | null;\r\n};\r\n\r\ntype ResolvedTheme = {\r\n headerFill: string;\r\n headerColor: string;\r\n fill: string;\r\n color: string;\r\n borderColor: string;\r\n fontName: string;\r\n fontSize: number;\r\n};\r\n\r\nexport type ExcelCellStyle = {\r\n font: {\r\n name: string;\r\n sz: number;\r\n bold?: boolean;\r\n color: { rgb: string };\r\n };\r\n fill: { patternType: \"solid\"; fgColor: { rgb: string } };\r\n alignment: {\r\n vertical: \"center\";\r\n horizontal: ExcelAlign;\r\n wrapText: false;\r\n };\r\n border: {\r\n top: { style: \"thin\"; color: { rgb: string } };\r\n bottom: { style: \"thin\"; color: { rgb: string } };\r\n left: { style: \"thin\"; color: { rgb: string } };\r\n right: { style: \"thin\"; color: { rgb: string } };\r\n };\r\n numFmt?: string;\r\n};\r\n\r\ntype HeaderGroup = {\r\n name: string;\r\n excel?: ExcelColumnStyle;\r\n};\r\n\r\ntype LeafColumn = {\r\n field: string;\r\n headerName: string;\r\n column: ExcelExportColumn;\r\n groups: HeaderGroup[];\r\n};\r\n\r\ntype WorkBook = {\r\n SheetNames: string[];\r\n Sheets: Record<string, WorkSheet>;\r\n};\r\n\r\ntype WorkSheet = Record<string, unknown>;\r\n\r\nconst ERRORS: Record<Exclude<ExportExcelErrorCode, \"write\">, string> = {\r\n empty: \"No valid data to download. Please ensure data is available.\",\r\n invalid: \"Invalid data structure. Please ensure data is correctly formatted.\",\r\n \"no-columns\": \"No data to export. Please check the data source.\",\r\n filename: \"Failed to generate file name. Please try again.\",\r\n};\r\n\r\nexport const DEFAULT_EXCEL_THEME: ResolvedTheme = {\r\n headerFill: \"2F75B6\",\r\n headerColor: \"FFFFFF\",\r\n fill: \"FFFFFF\",\r\n color: \"1F2933\",\r\n borderColor: \"8FA4BF\",\r\n fontName: \"Calibri\",\r\n fontSize: 11,\r\n};\r\n\r\nconst fail = (\r\n code: Exclude<ExportExcelErrorCode, \"write\">,\r\n): ExportExcelFailure => ({\r\n ok: false,\r\n code,\r\n error: ERRORS[code],\r\n});\r\n\r\nconst rgb = (input: string | undefined, fallback: string): string => {\r\n if (!input) return fallback;\r\n const hex = input.trim().replace(/^#/, \"\").toUpperCase();\r\n return /^[0-9A-F]{6}$/.test(hex) ? hex : fallback;\r\n};\r\n\r\nconst resolveTheme = (theme?: ExcelTheme): ResolvedTheme => ({\r\n headerFill: rgb(theme?.headerFill, DEFAULT_EXCEL_THEME.headerFill),\r\n headerColor: rgb(theme?.headerColor, DEFAULT_EXCEL_THEME.headerColor),\r\n fill: rgb(theme?.fill, DEFAULT_EXCEL_THEME.fill),\r\n color: rgb(theme?.color, DEFAULT_EXCEL_THEME.color),\r\n borderColor: rgb(theme?.borderColor, DEFAULT_EXCEL_THEME.borderColor),\r\n fontName: theme?.fontName?.trim() || DEFAULT_EXCEL_THEME.fontName,\r\n fontSize:\r\n typeof theme?.fontSize === \"number\" && theme.fontSize > 0\r\n ? theme.fontSize\r\n : DEFAULT_EXCEL_THEME.fontSize,\r\n});\r\n\r\nconst gridBorder = (color: string): ExcelCellStyle[\"border\"] => {\r\n const edge = () => ({ style: \"thin\" as const, color: { rgb: color } });\r\n return { top: edge(), bottom: edge(), left: edge(), right: edge() };\r\n};\r\n\r\nexport const excelHeaderStyle = (\r\n theme?: ExcelTheme,\r\n column?: ExcelColumnStyle,\r\n): ExcelCellStyle => {\r\n const resolved = resolveTheme(theme);\r\n return {\r\n font: {\r\n name: resolved.fontName,\r\n sz: resolved.fontSize,\r\n bold: true,\r\n color: { rgb: rgb(column?.headerColor, resolved.headerColor) },\r\n },\r\n fill: {\r\n patternType: \"solid\",\r\n fgColor: { rgb: rgb(column?.headerFill, resolved.headerFill) },\r\n },\r\n alignment: {\r\n vertical: \"center\",\r\n horizontal: column?.align ?? \"left\",\r\n wrapText: false,\r\n },\r\n border: gridBorder(resolved.borderColor),\r\n };\r\n};\r\n\r\nexport const excelDataStyle = (\r\n theme?: ExcelTheme,\r\n column?: ExcelColumnStyle,\r\n): ExcelCellStyle => {\r\n const resolved = resolveTheme(theme);\r\n const font: ExcelCellStyle[\"font\"] = {\r\n name: resolved.fontName,\r\n sz: resolved.fontSize,\r\n color: { rgb: rgb(column?.color, resolved.color) },\r\n };\r\n if (column?.bold) font.bold = true;\r\n const style: ExcelCellStyle = {\r\n font,\r\n fill: {\r\n patternType: \"solid\",\r\n fgColor: { rgb: rgb(column?.fill, resolved.fill) },\r\n },\r\n alignment: {\r\n vertical: \"center\",\r\n horizontal: column?.align ?? \"left\",\r\n wrapText: false,\r\n },\r\n border: gridBorder(resolved.borderColor),\r\n };\r\n if (column?.numFmt) style.numFmt = column.numFmt;\r\n return style;\r\n};\r\n\r\nconst getNestedValue = (obj: unknown, path: string): unknown => {\r\n if (!obj || typeof obj !== \"object\") return \"\";\r\n try {\r\n const value = path.split(\".\").reduce<unknown>((acc, key) => {\r\n if (acc == null || typeof acc !== \"object\") return undefined;\r\n return (acc as Record<string, unknown>)[key];\r\n }, obj);\r\n return value ?? \"\";\r\n } catch {\r\n return \"\";\r\n }\r\n};\r\n\r\nconst isGroup = (col: ExcelExportColumn): boolean =>\r\n Array.isArray(col.children) && col.children.length > 0;\r\n\r\nconst isSkippedLeaf = (col: ExcelExportColumn): boolean => {\r\n if (col.checkboxSelection || col.headerCheckboxSelection) return true;\r\n const field = typeof col.field === \"string\" ? col.field.trim() : \"\";\r\n return !field || field === \"checkbox\" || field === \"checkboxSelection\";\r\n};\r\n\r\nconst collectLeaves = (\r\n cols: ExcelExportColumn[],\r\n groups: HeaderGroup[],\r\n skipHidden: boolean,\r\n): LeafColumn[] => {\r\n const leaves: LeafColumn[] = [];\r\n cols.forEach((col) => {\r\n if (!col || typeof col !== \"object\") return;\r\n if (skipHidden && col.hide) return;\r\n if (isGroup(col)) {\r\n const group: HeaderGroup = {\r\n name: col.headerName || \"\",\r\n excel: col.excel,\r\n };\r\n leaves.push(...collectLeaves(col.children ?? [], [...groups, group], skipHidden));\r\n return;\r\n }\r\n if (isSkippedLeaf(col)) return;\r\n const field = col.field!.trim();\r\n leaves.push({\r\n field,\r\n headerName: (col.headerName || field).trim() || field,\r\n column: col,\r\n groups,\r\n });\r\n });\r\n return leaves;\r\n};\r\n\r\nconst labelFromKey = (key: string): string =>\r\n key\r\n .replace(/([A-Z])/g, \" $1\")\r\n .replace(/^\\w/, (char) => char.toUpperCase())\r\n .trim();\r\n\r\nconst columnsFromRows = (rows: unknown[]): ExcelExportColumn[] | ExportExcelFailure => {\r\n const first = rows[0];\r\n if (!first || typeof first !== \"object\" || Array.isArray(first)) {\r\n return fail(\"invalid\");\r\n }\r\n return Object.keys(first)\r\n .filter((key) => key !== \"checkbox\" && key !== \"checkboxSelection\" && key.trim())\r\n .map((key) => ({ field: key, headerName: labelFromKey(key) }));\r\n};\r\n\r\nconst formatLeafValue = (\r\n row: Record<string, unknown>,\r\n leaf: LeafColumn,\r\n options: ExportExcelReportOptions,\r\n): unknown => {\r\n let value = getNestedValue(row, leaf.field);\r\n const formatter = leaf.column.format || leaf.column.valueFormatter;\r\n if (typeof formatter === \"function\") {\r\n try {\r\n return formatter({ value, data: row }) ?? \"\";\r\n } catch {\r\n return value ?? \"\";\r\n }\r\n }\r\n\r\n const fieldLooksLikeDate = leaf.field.toLowerCase().includes(\"date\");\r\n if (options.formatDateFields && fieldLooksLikeDate && typeof options.formatDate === \"function\") {\r\n try {\r\n return options.formatDate(value) ?? \"\";\r\n } catch {\r\n return value ?? \"\";\r\n }\r\n }\r\n\r\n if (value instanceof Date) return value;\r\n if (value != null && typeof value === \"object\") return \"\";\r\n\r\n const numFmt = leaf.column.excel?.numFmt;\r\n if (numFmt) {\r\n if (typeof value === \"number\" && Number.isFinite(value)) return value;\r\n if (typeof value === \"string\" && value.trim() !== \"\" && Number.isFinite(Number(value))) {\r\n return Number(value);\r\n }\r\n }\r\n\r\n return value ?? \"\";\r\n};\r\n\r\nconst localDateStamp = (): string => {\r\n const date = new Date();\r\n const year = date.getFullYear();\r\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\r\n const day = String(date.getDate()).padStart(2, \"0\");\r\n return `${year}-${month}-${day}`;\r\n};\r\n\r\nconst resolveDateStamp = (dateStamp: string | null | undefined): string | null => {\r\n if (dateStamp === null) return null;\r\n if (typeof dateStamp === \"string\" && dateStamp.trim()) return dateStamp.trim();\r\n return localDateStamp();\r\n};\r\n\r\nconst safeSheetName = (name: string | undefined): string => {\r\n const cleaned = (name || \"Report\").replace(/[:\\\\/?*[\\]]/g, \" \").trim() || \"Report\";\r\n return cleaned.slice(0, 31);\r\n};\r\n\r\nconst buildHeaderGrid = (leaves: LeafColumn[]) => {\r\n const totalHeaderRows = Math.max(...leaves.map((leaf) => leaf.groups.length + 1), 1);\r\n const headerRows = Array.from({ length: totalHeaderRows }, () =>\r\n Array.from({ length: leaves.length }, () => \"\"),\r\n );\r\n const merges: { s: { r: number; c: number }; e: { r: number; c: number } }[] = [];\r\n\r\n for (let depth = 0; depth < totalHeaderRows - 1; depth += 1) {\r\n let colIndex = 0;\r\n while (colIndex < leaves.length) {\r\n const group = leaves[colIndex].groups[depth];\r\n if (!group) {\r\n colIndex += 1;\r\n continue;\r\n }\r\n let span = 1;\r\n while (colIndex + span < leaves.length && leaves[colIndex + span].groups[depth] === group) {\r\n span += 1;\r\n }\r\n headerRows[depth][colIndex] = group.name;\r\n if (span > 1) {\r\n merges.push({\r\n s: { r: depth, c: colIndex },\r\n e: { r: depth, c: colIndex + span - 1 },\r\n });\r\n }\r\n colIndex += span;\r\n }\r\n }\r\n\r\n leaves.forEach((leaf, colIndex) => {\r\n const row = leaf.groups.length;\r\n headerRows[row][colIndex] = leaf.headerName;\r\n if (row < totalHeaderRows - 1) {\r\n merges.push({\r\n s: { r: row, c: colIndex },\r\n e: { r: totalHeaderRows - 1, c: colIndex },\r\n });\r\n }\r\n });\r\n\r\n return { headerRows, merges, totalHeaderRows };\r\n};\r\n\r\nconst headerStyleFor = (\r\n leaf: LeafColumn,\r\n row: number,\r\n theme: ExcelTheme | undefined,\r\n): ExcelCellStyle => {\r\n const source = row < leaf.groups.length ? leaf.groups[row].excel : leaf.column.excel;\r\n return excelHeaderStyle(theme, source);\r\n};\r\n\r\nconst columnWidth = (leaf: LeafColumn, values: unknown[]): { wch: number } => {\r\n const headerLength = Math.max(\r\n leaf.headerName.length,\r\n ...leaf.groups.map((group) => group.name.length),\r\n 0,\r\n );\r\n const dataLength = values.reduce<number>((max, value) => {\r\n return Math.max(max, String(value ?? \"\").length);\r\n }, 0);\r\n return { wch: Math.min(Math.max(headerLength, dataLength, 12), 50) };\r\n};\r\n\r\nexport type BuiltExcelWorkbook =\r\n | { ok: true; workbook: WorkBook; fileName: string; sheetName: string }\r\n | ExportExcelFailure;\r\n\r\n/**\r\n * Build a styled workbook. Does not write a file.\r\n * Header merges follow the column tree, so two groups with the same title stay separate.\r\n */\r\nexport const buildExcelWorkbook = (\r\n options: ExportExcelReportOptions = {},\r\n): BuiltExcelWorkbook => {\r\n const data = options.data;\r\n if (!Array.isArray(data) || data.length === 0) return fail(\"empty\");\r\n\r\n const provided = Array.isArray(options.columns) ? options.columns : [];\r\n const columns = provided.length\r\n ? provided\r\n : columnsFromRows(data);\r\n if (!Array.isArray(columns)) return columns;\r\n\r\n const leaves = collectLeaves(columns, [], options.skipHidden === true);\r\n if (!leaves.length) return fail(\"no-columns\");\r\n\r\n const dateStamp = resolveDateStamp(options.dateStamp);\r\n if (!dateStamp) return fail(\"filename\");\r\n\r\n const { headerRows, merges, totalHeaderRows } = buildHeaderGrid(leaves);\r\n const records = data.map((row) =>\r\n row && typeof row === \"object\" && !Array.isArray(row)\r\n ? (row as Record<string, unknown>)\r\n : {},\r\n );\r\n const dataRows = records.map((row) =>\r\n leaves.map((leaf) => formatLeafValue(row, leaf, options)),\r\n );\r\n\r\n const sheet = XLSX.utils.aoa_to_sheet([...headerRows, ...dataRows]) as WorkSheet;\r\n const lastCell = XLSX.utils.encode_cell({\r\n r: headerRows.length + dataRows.length - 1,\r\n c: leaves.length - 1,\r\n });\r\n sheet[\"!ref\"] = `A1:${lastCell}`;\r\n sheet[\"!merges\"] = merges;\r\n sheet[\"!cols\"] = leaves.map((leaf, index) =>\r\n columnWidth(\r\n leaf,\r\n dataRows.map((row) => row[index]),\r\n ),\r\n );\r\n sheet[\"!rows\"] = Array.from({ length: totalHeaderRows }, () => ({ hpt: 22 }));\r\n\r\n const range = XLSX.utils.decode_range(sheet[\"!ref\"] as string);\r\n for (let row = range.s.r; row <= range.e.r; row += 1) {\r\n for (let col = range.s.c; col <= range.e.c; col += 1) {\r\n const address = XLSX.utils.encode_cell({ r: row, c: col });\r\n const cell = (sheet[address] as { t?: string; v?: unknown; z?: string; s?: ExcelCellStyle } | undefined) ?? {\r\n t: \"s\",\r\n v: \"\",\r\n };\r\n const leaf = leaves[col];\r\n if (row < totalHeaderRows) {\r\n cell.s = headerStyleFor(leaf, row, options.theme);\r\n } else {\r\n const style = excelDataStyle(options.theme, leaf.column.excel);\r\n if (leaf.column.excel?.numFmt) cell.z = leaf.column.excel.numFmt;\r\n cell.s = style;\r\n }\r\n sheet[address] = cell;\r\n }\r\n }\r\n\r\n const sheetName = safeSheetName(options.sheetName);\r\n const workbook = XLSX.utils.book_new() as WorkBook;\r\n XLSX.utils.book_append_sheet(workbook, sheet, sheetName);\r\n\r\n const baseName = options.fileName == null ? \"\" : String(options.fileName);\r\n return {\r\n ok: true,\r\n workbook,\r\n sheetName,\r\n fileName: `${baseName ? `${baseName}_` : \"\"}${dateStamp}.xlsx`,\r\n };\r\n};\r\n\r\n/** Write a styled .xlsx. Returns a result; it does not toast. */\r\nexport const exportExcelReport = (\r\n options: ExportExcelReportOptions = {},\r\n): ExportExcelReportResult => {\r\n const built = buildExcelWorkbook(options);\r\n if (!built.ok) return built;\r\n try {\r\n XLSX.writeFile(built.workbook, built.fileName, { cellStyles: true });\r\n } catch (error) {\r\n const message = error instanceof Error ? error.message : \"Unknown error\";\r\n return { ok: false, code: \"write\", error: message };\r\n }\r\n return { ok: true, fileName: built.fileName };\r\n};\r\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eztrak/shared",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "homepage": "https://github.com/mehrancentangle/eztrak-packages",
5
5
  "description": "Shared hooks, utils, and UI for Eztrak federation apps",
6
6
  "main": "./dist/index.js",
@@ -34,18 +34,25 @@
34
34
  "require": "./dist/components/index.js"
35
35
  },
36
36
  "./components/tabs.css": "./dist/components/tabs/styles.css",
37
- "./components/loader.css": "./dist/components/Loader/Loader.css"
37
+ "./components/loader.css": "./dist/components/Loader/Loader.css",
38
+ "./excel": {
39
+ "types": "./dist/excel/index.d.ts",
40
+ "import": "./dist/excel/index.mjs",
41
+ "require": "./dist/excel/index.js"
42
+ }
38
43
  },
39
44
  "scripts": {
40
45
  "build": "tsup && node -e \"const fs=require('fs');const tabs='dist/components/tabs';fs.mkdirSync(tabs,{recursive:true});fs.copyFileSync('src/components/tabs/styles.css',tabs+'/styles.css');const loader='dist/components/Loader';fs.mkdirSync(loader,{recursive:true});fs.copyFileSync('src/components/Loader/Loader.css',loader+'/Loader.css')\"",
41
46
  "prepublishOnly": "npm run build",
42
47
  "test:format-date": "npm run build && node scripts/check-format-date.mjs",
48
+ "test:excel": "npm run build && node scripts/check-excel-export.mjs",
43
49
  "storybook": "storybook dev -p 6006",
44
50
  "build:storybook": "storybook build"
45
51
  },
46
52
  "dependencies": {
47
53
  "clsx": "^2.1.1",
48
- "tailwind-merge": "^3.0.1"
54
+ "tailwind-merge": "^3.0.1",
55
+ "xlsx-js-style": "^1.2.0"
49
56
  },
50
57
  "peerDependencies": {
51
58
  "@reduxjs/toolkit": "^2.0.0",