sheetra 1.0.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/dist/index.js ADDED
@@ -0,0 +1,2419 @@
1
+ 'use strict';
2
+
3
+ class DateFormatter {
4
+ static format(date, format) {
5
+ const d = this.parseDate(date);
6
+ if (!d)
7
+ return '';
8
+ if (format) {
9
+ return this.formatWithPattern(d, format);
10
+ }
11
+ // Default format: YYYY-MM-DD
12
+ return d.toISOString().split('T')[0];
13
+ }
14
+ static toExcelDate(date) {
15
+ const d = this.parseDate(date);
16
+ if (!d)
17
+ return 0;
18
+ // Excel date starts from 1900-01-01
19
+ const excelEpoch = new Date(1900, 0, 1);
20
+ const diffTime = d.getTime() - excelEpoch.getTime();
21
+ const diffDays = diffTime / (1000 * 60 * 60 * 24);
22
+ // Add 1 because Excel thinks 1900-01-01 is day 1
23
+ // Subtract 1 for the 1900 leap year bug
24
+ return diffDays + 1;
25
+ }
26
+ static parseDate(date) {
27
+ if (date instanceof Date) {
28
+ return date;
29
+ }
30
+ if (typeof date === 'string' || typeof date === 'number') {
31
+ const d = new Date(date);
32
+ if (!isNaN(d.getTime())) {
33
+ return d;
34
+ }
35
+ }
36
+ return null;
37
+ }
38
+ static formatWithPattern(date, pattern) {
39
+ const map = {
40
+ 'YYYY': date.getFullYear().toString(),
41
+ 'YY': date.getFullYear().toString().slice(-2),
42
+ 'MM': this.pad(date.getMonth() + 1),
43
+ 'M': (date.getMonth() + 1).toString(),
44
+ 'DD': this.pad(date.getDate()),
45
+ 'D': date.getDate().toString(),
46
+ 'HH': this.pad(date.getHours()),
47
+ 'H': date.getHours().toString(),
48
+ 'mm': this.pad(date.getMinutes()),
49
+ 'm': date.getMinutes().toString(),
50
+ 'ss': this.pad(date.getSeconds()),
51
+ 's': date.getSeconds().toString()
52
+ };
53
+ return pattern.replace(/YYYY|YY|MM|M|DD|D|HH|H|mm|m|ss|s/g, match => map[match] || match);
54
+ }
55
+ static pad(num) {
56
+ return num.toString().padStart(2, '0');
57
+ }
58
+ }
59
+
60
+ class NumberFormatter {
61
+ static format(value, format) {
62
+ if (value === null || value === undefined)
63
+ return '';
64
+ if (format) {
65
+ return this.formatWithPattern(value, format);
66
+ }
67
+ return value.toString();
68
+ }
69
+ static formatWithPattern(value, pattern) {
70
+ // Handle currency format
71
+ if (pattern.includes('$')) {
72
+ return this.formatCurrency(value, pattern);
73
+ }
74
+ // Handle percentage
75
+ if (pattern.includes('%')) {
76
+ return this.formatPercentage(value, pattern);
77
+ }
78
+ // Handle decimal places
79
+ const decimalMatch = pattern.match(/\.(0+)/);
80
+ if (decimalMatch) {
81
+ const decimals = decimalMatch[1].length;
82
+ return value.toFixed(decimals);
83
+ }
84
+ return value.toString();
85
+ }
86
+ static formatCurrency(value, pattern) {
87
+ const symbol = pattern.includes('$') ? '$' : '';
88
+ const decimals = pattern.includes('.00') ? 2 : 0;
89
+ const formatted = value.toFixed(decimals);
90
+ const parts = formatted.split('.');
91
+ parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
92
+ return symbol + parts.join('.');
93
+ }
94
+ static formatPercentage(value, pattern) {
95
+ const percentValue = value * 100;
96
+ const decimals = pattern.includes('.00') ? 2 : 0;
97
+ return percentValue.toFixed(decimals) + '%';
98
+ }
99
+ }
100
+
101
+ class Cell {
102
+ constructor(value, style) {
103
+ this.value = null;
104
+ this.type = 'string';
105
+ if (value !== undefined) {
106
+ this.setValue(value);
107
+ }
108
+ if (style) {
109
+ this.setStyle(style);
110
+ }
111
+ }
112
+ setValue(value) {
113
+ this.value = value;
114
+ this.inferType();
115
+ return this;
116
+ }
117
+ setStyle(style) {
118
+ this.style = style;
119
+ return this;
120
+ }
121
+ setFormula(formula) {
122
+ this.formula = formula;
123
+ this.type = 'formula';
124
+ return this;
125
+ }
126
+ setType(type) {
127
+ this.type = type;
128
+ return this;
129
+ }
130
+ inferType() {
131
+ if (this.value instanceof Date) {
132
+ this.type = 'date';
133
+ }
134
+ else if (typeof this.value === 'number') {
135
+ this.type = 'number';
136
+ }
137
+ else if (typeof this.value === 'boolean') {
138
+ this.type = 'boolean';
139
+ }
140
+ else {
141
+ this.type = 'string';
142
+ }
143
+ }
144
+ getFormattedValue() {
145
+ var _a, _b, _c, _d, _e, _f;
146
+ if (this.value === null || this.value === undefined) {
147
+ return '';
148
+ }
149
+ switch (this.type) {
150
+ case 'date': {
151
+ const dateFormat = typeof ((_a = this.style) === null || _a === void 0 ? void 0 : _a.numberFormat) === 'string'
152
+ ? this.style.numberFormat
153
+ : (_c = (_b = this.style) === null || _b === void 0 ? void 0 : _b.numberFormat) === null || _c === void 0 ? void 0 : _c.format;
154
+ return DateFormatter.format(this.value, dateFormat);
155
+ }
156
+ case 'number': {
157
+ const numberFormat = typeof ((_d = this.style) === null || _d === void 0 ? void 0 : _d.numberFormat) === 'string'
158
+ ? this.style.numberFormat
159
+ : (_f = (_e = this.style) === null || _e === void 0 ? void 0 : _e.numberFormat) === null || _f === void 0 ? void 0 : _f.format;
160
+ return NumberFormatter.format(this.value, numberFormat);
161
+ }
162
+ case 'boolean':
163
+ return this.value ? 'TRUE' : 'FALSE';
164
+ case 'formula':
165
+ return this.formula || '';
166
+ default:
167
+ return String(this.value);
168
+ }
169
+ }
170
+ toData() {
171
+ return {
172
+ value: this.value,
173
+ style: this.style,
174
+ formula: this.formula,
175
+ type: this.type
176
+ };
177
+ }
178
+ static fromData(data) {
179
+ const cell = new Cell();
180
+ cell.value = data.value;
181
+ cell.style = data.style;
182
+ cell.formula = data.formula;
183
+ const validTypes = ['string', 'number', 'boolean', 'date', 'formula'];
184
+ cell.type = validTypes.includes(data.type) ? data.type : 'string';
185
+ if (cell.value && !cell.type) {
186
+ cell.inferType();
187
+ }
188
+ return cell;
189
+ }
190
+ }
191
+
192
+ class Row {
193
+ constructor(cells) {
194
+ this.cells = [];
195
+ this.hidden = false;
196
+ this.outlineLevel = 0;
197
+ this.collapsed = false;
198
+ if (cells) {
199
+ this.cells = cells;
200
+ }
201
+ }
202
+ addCell(cell) {
203
+ this.cells.push(cell);
204
+ return this;
205
+ }
206
+ createCell(value, style) {
207
+ const cell = new Cell(value, style);
208
+ this.cells.push(cell);
209
+ return cell;
210
+ }
211
+ setCell(index, value, style) {
212
+ while (this.cells.length <= index) {
213
+ this.cells.push(new Cell());
214
+ }
215
+ this.cells[index].setValue(value).setStyle(style);
216
+ return this;
217
+ }
218
+ getCell(index) {
219
+ var _a;
220
+ return (_a = this.cells[index]) === null || _a === void 0 ? void 0 : _a.toData();
221
+ }
222
+ getCells() {
223
+ return this.cells;
224
+ }
225
+ setHeight(height) {
226
+ this.height = height;
227
+ return this;
228
+ }
229
+ setHidden(hidden) {
230
+ this.hidden = hidden;
231
+ return this;
232
+ }
233
+ setOutlineLevel(level, collapsed = false) {
234
+ this.outlineLevel = level;
235
+ this.collapsed = collapsed;
236
+ return this;
237
+ }
238
+ toData() {
239
+ return {
240
+ cells: this.cells.map(cell => cell.toData()),
241
+ height: this.height,
242
+ hidden: this.hidden,
243
+ outlineLevel: this.outlineLevel,
244
+ collapsed: this.collapsed
245
+ };
246
+ }
247
+ static fromData(data) {
248
+ const row = new Row(data.cells.map(cellData => Cell.fromData(cellData)));
249
+ row.height = data.height;
250
+ row.hidden = data.hidden || false;
251
+ row.outlineLevel = data.outlineLevel || 0;
252
+ row.collapsed = data.collapsed || false;
253
+ return row;
254
+ }
255
+ }
256
+
257
+ class Column {
258
+ constructor(width) {
259
+ this.hidden = false;
260
+ this.outlineLevel = 0;
261
+ this.collapsed = false;
262
+ this.width = width;
263
+ }
264
+ setWidth(width) {
265
+ this.width = width;
266
+ return this;
267
+ }
268
+ setHidden(hidden) {
269
+ this.hidden = hidden;
270
+ return this;
271
+ }
272
+ setOutlineLevel(level, collapsed = false) {
273
+ this.outlineLevel = level;
274
+ this.collapsed = collapsed;
275
+ return this;
276
+ }
277
+ toData() {
278
+ return {
279
+ width: this.width,
280
+ hidden: this.hidden,
281
+ outlineLevel: this.outlineLevel,
282
+ collapsed: this.collapsed
283
+ };
284
+ }
285
+ static fromData(data) {
286
+ const column = new Column(data.width);
287
+ column.hidden = data.hidden || false;
288
+ column.outlineLevel = data.outlineLevel || 0;
289
+ column.collapsed = data.collapsed || false;
290
+ return column;
291
+ }
292
+ }
293
+
294
+ class Worksheet {
295
+ constructor(name) {
296
+ this.rows = [];
297
+ this.columns = [];
298
+ this.mergedCells = [];
299
+ this.name = name;
300
+ }
301
+ getName() {
302
+ return this.name;
303
+ }
304
+ setName(name) {
305
+ this.name = name;
306
+ return this;
307
+ }
308
+ addRow(row) {
309
+ this.rows.push(row);
310
+ return this;
311
+ }
312
+ createRow(index) {
313
+ const row = new Row();
314
+ if (index !== undefined) {
315
+ this.rows.splice(index, 0, row);
316
+ }
317
+ else {
318
+ this.rows.push(row);
319
+ }
320
+ return row;
321
+ }
322
+ getRow(index) {
323
+ return this.rows[index];
324
+ }
325
+ getRows() {
326
+ return this.rows;
327
+ }
328
+ removeRow(index) {
329
+ if (index >= 0 && index < this.rows.length) {
330
+ this.rows.splice(index, 1);
331
+ return true;
332
+ }
333
+ return false;
334
+ }
335
+ addColumn(column) {
336
+ this.columns.push(column);
337
+ return this;
338
+ }
339
+ createColumn(width) {
340
+ const column = new Column(width);
341
+ this.columns.push(column);
342
+ return column;
343
+ }
344
+ getColumn(index) {
345
+ return this.columns[index];
346
+ }
347
+ getColumns() {
348
+ return this.columns;
349
+ }
350
+ setCell(row, col, value, style) {
351
+ while (this.rows.length <= row) {
352
+ this.rows.push(new Row());
353
+ }
354
+ this.rows[row].setCell(col, value, style);
355
+ return this;
356
+ }
357
+ getCell(row, col) {
358
+ const rowObj = this.rows[row];
359
+ return rowObj === null || rowObj === void 0 ? void 0 : rowObj.getCell(col);
360
+ }
361
+ mergeCells(startRow, startCol, endRow, endCol) {
362
+ this.mergedCells.push({ startRow, startCol, endRow, endCol });
363
+ return this;
364
+ }
365
+ setFreezePane(row, col) {
366
+ this.freezePane = { rows: row, columns: col };
367
+ return this;
368
+ }
369
+ setPrintOptions(options) {
370
+ this.printOptions = options;
371
+ return this;
372
+ }
373
+ setOutlineLevel(row, level, collapsed = false) {
374
+ if (this.rows[row]) {
375
+ this.rows[row].setOutlineLevel(level, collapsed);
376
+ }
377
+ return this;
378
+ }
379
+ setColumnOutlineLevel(col, level, collapsed = false) {
380
+ while (this.columns.length <= col) {
381
+ this.columns.push(new Column());
382
+ }
383
+ this.columns[col].setOutlineLevel(level, collapsed);
384
+ return this;
385
+ }
386
+ toData() {
387
+ return {
388
+ name: this.name,
389
+ rows: this.rows.map(row => row.toData()),
390
+ columns: this.columns.map(col => col.toData()),
391
+ mergeCells: this.mergedCells,
392
+ freezePane: this.freezePane,
393
+ printOptions: this.printOptions
394
+ };
395
+ }
396
+ static fromData(data) {
397
+ const sheet = new Worksheet(data.name);
398
+ sheet.rows = data.rows.map(rowData => Row.fromData(rowData));
399
+ sheet.columns = (data.columns || []).map(colData => Column.fromData(colData));
400
+ sheet.mergedCells = data.mergeCells || [];
401
+ sheet.freezePane = data.freezePane;
402
+ sheet.printOptions = data.printOptions;
403
+ return sheet;
404
+ }
405
+ }
406
+
407
+ class CSVWriter {
408
+ static async write(workbook, _options) {
409
+ const sheets = workbook['sheets'];
410
+ const csvData = this.generateCSVData(sheets[0]); // Use first sheet for CSV
411
+ return new Blob([csvData], { type: 'text/csv;charset=utf-8;' });
412
+ }
413
+ static generateCSVData(sheet) {
414
+ const rows = sheet.getRows();
415
+ const csvRows = [];
416
+ rows.forEach((row) => {
417
+ const rowData = [];
418
+ row.getCells().forEach(cell => {
419
+ const cellData = cell.toData();
420
+ let value = '';
421
+ if (cellData.value !== null && cellData.value !== undefined) {
422
+ if (cellData.type === 'date' && cellData.value instanceof Date) {
423
+ value = cellData.value.toISOString().split('T')[0];
424
+ }
425
+ else {
426
+ value = String(cellData.value);
427
+ }
428
+ }
429
+ // Escape quotes and wrap in quotes if contains comma or quote
430
+ if (value.includes(',') || value.includes('"') || value.includes('\n')) {
431
+ value = '"' + value.replace(/"/g, '""') + '"';
432
+ }
433
+ rowData.push(value);
434
+ });
435
+ csvRows.push(rowData.join(','));
436
+ });
437
+ return csvRows.join('\n');
438
+ }
439
+ }
440
+
441
+ class JSONWriter {
442
+ static async write(workbook, _options) {
443
+ const sheets = workbook['sheets'];
444
+ const jsonData = this.generateJSONData(sheets[0]); // Use first sheet for JSON
445
+ return new Blob([JSON.stringify(jsonData, null, 2)], {
446
+ type: 'application/json;charset=utf-8;'
447
+ });
448
+ }
449
+ static generateJSONData(sheet) {
450
+ const rows = sheet.getRows();
451
+ if (rows.length === 0)
452
+ return [];
453
+ // Assume first row contains headers
454
+ const headers = rows[0].getCells().map((cell) => {
455
+ const value = cell.toData().value;
456
+ return value ? String(value) : `Column${headers.length + 1}`;
457
+ });
458
+ const data = [];
459
+ for (let i = 1; i < rows.length; i++) {
460
+ const row = rows[i];
461
+ const rowData = {};
462
+ row.getCells().forEach((cell, index) => {
463
+ if (index < headers.length) {
464
+ const cellData = cell.toData();
465
+ let value = cellData.value;
466
+ if (cellData.type === 'date' && value instanceof Date) {
467
+ value = value.toISOString();
468
+ }
469
+ rowData[headers[index]] = value;
470
+ }
471
+ });
472
+ data.push(rowData);
473
+ }
474
+ return data;
475
+ }
476
+ }
477
+
478
+ class ExcelWriter {
479
+ static async write(workbook, _options) {
480
+ const data = this.generateExcelData(workbook);
481
+ const buffer = this.createExcelFile(data);
482
+ const uint8Buffer = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
483
+ const arrayBuffer = uint8Buffer.slice().buffer;
484
+ return new Blob([arrayBuffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
485
+ }
486
+ static generateExcelData(workbook) {
487
+ const sheets = workbook['sheets'];
488
+ return {
489
+ Sheets: sheets.reduce((acc, sheet, index) => {
490
+ const sheetName = sheet.getName() || `Sheet${index + 1}`;
491
+ acc[sheetName] = this.generateSheetData(sheet);
492
+ return acc;
493
+ }, {}),
494
+ SheetNames: sheets.map((sheet, index) => sheet.getName() || `Sheet${index + 1}`)
495
+ };
496
+ }
497
+ static generateSheetData(sheet) {
498
+ const rows = sheet.getRows();
499
+ const data = [];
500
+ rows.forEach((row) => {
501
+ const rowData = [];
502
+ row.getCells().forEach((cell) => {
503
+ const cellData = cell.toData();
504
+ if (cellData.formula) {
505
+ rowData.push({ f: cellData.formula });
506
+ }
507
+ else if (cellData.type === 'date' &&
508
+ (typeof cellData.value === 'string' ||
509
+ typeof cellData.value === 'number' ||
510
+ cellData.value instanceof Date)) {
511
+ rowData.push(DateFormatter.toExcelDate(cellData.value));
512
+ }
513
+ else {
514
+ rowData.push(cellData.value);
515
+ }
516
+ });
517
+ data.push(rowData);
518
+ });
519
+ return {
520
+ '!ref': this.getSheetRange(data),
521
+ '!rows': rows.map(row => ({
522
+ hpt: row['height'],
523
+ hidden: row['hidden'],
524
+ outlineLevel: row['outlineLevel'],
525
+ collapsed: row['collapsed']
526
+ })),
527
+ '!cols': sheet.getColumns().map(col => ({
528
+ wch: col['width'],
529
+ hidden: col['hidden'],
530
+ outlineLevel: col['outlineLevel'],
531
+ collapsed: col['collapsed']
532
+ })),
533
+ '!merges': sheet['mergeCells'],
534
+ '!freeze': sheet['freezePane']
535
+ };
536
+ }
537
+ static getSheetRange(data) {
538
+ if (data.length === 0)
539
+ return 'A1:A1';
540
+ const lastRow = data.length;
541
+ const lastCol = Math.max(...data.map(row => row.length));
542
+ return `A1:${this.columnToLetter(lastCol)}${lastRow}`;
543
+ }
544
+ static columnToLetter(column) {
545
+ let letters = '';
546
+ while (column > 0) {
547
+ const temp = (column - 1) % 26;
548
+ letters = String.fromCharCode(temp + 65) + letters;
549
+ column = (column - temp - 1) / 26;
550
+ }
551
+ return letters || 'A';
552
+ }
553
+ static createExcelFile(data) {
554
+ // This is a simplified Excel file generator
555
+ // In a real implementation, you'd generate proper XLSX binary format
556
+ // For now, we'll return a simple XML-based structure
557
+ const xmlHeader = '<?xml version="1.0" encoding="UTF-8"?>';
558
+ const workbook = this.generateWorkbookXML(data);
559
+ const encoder = new TextEncoder();
560
+ return encoder.encode(xmlHeader + workbook);
561
+ }
562
+ static generateWorkbookXML(data) {
563
+ let xml = '<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">';
564
+ xml += '<sheets>';
565
+ data.SheetNames.forEach((name, index) => {
566
+ xml += `<sheet name="${name}" sheetId="${index + 1}" r:id="rId${index + 1}"/>`;
567
+ });
568
+ xml += '</sheets>';
569
+ xml += '</workbook>';
570
+ return xml;
571
+ }
572
+ }
573
+
574
+ class Workbook {
575
+ constructor(data) {
576
+ this.sheets = [];
577
+ this.properties = {};
578
+ if (data) {
579
+ this.fromData(data);
580
+ }
581
+ }
582
+ addSheet(sheet) {
583
+ this.sheets.push(sheet);
584
+ return this;
585
+ }
586
+ createSheet(name) {
587
+ const sheet = new Worksheet(name);
588
+ this.sheets.push(sheet);
589
+ return sheet;
590
+ }
591
+ getSheet(index) {
592
+ return this.sheets[index];
593
+ }
594
+ getSheetByName(name) {
595
+ return this.sheets.find(s => s.getName() === name);
596
+ }
597
+ removeSheet(index) {
598
+ if (index >= 0 && index < this.sheets.length) {
599
+ this.sheets.splice(index, 1);
600
+ return true;
601
+ }
602
+ return false;
603
+ }
604
+ setProperty(key, value) {
605
+ this.properties[key] = value;
606
+ return this;
607
+ }
608
+ toData() {
609
+ return {
610
+ sheets: this.sheets.map(sheet => sheet.toData()),
611
+ properties: this.properties
612
+ };
613
+ }
614
+ fromData(data) {
615
+ this.sheets = data.sheets.map(sheetData => Worksheet.fromData(sheetData));
616
+ this.properties = data.properties || {};
617
+ }
618
+ async export(options = {}) {
619
+ const { format = 'xlsx' } = options;
620
+ switch (format) {
621
+ case 'csv':
622
+ return CSVWriter.write(this, options);
623
+ case 'json':
624
+ return JSONWriter.write(this, options);
625
+ case 'xlsx':
626
+ default:
627
+ return ExcelWriter.write(this, options);
628
+ }
629
+ }
630
+ download(options = {}) {
631
+ const { filename = 'workbook.xlsx' } = options;
632
+ this.export(options).then(blob => {
633
+ const url = window.URL.createObjectURL(blob);
634
+ const a = document.createElement('a');
635
+ a.href = url;
636
+ a.download = filename;
637
+ a.click();
638
+ window.URL.revokeObjectURL(url);
639
+ });
640
+ }
641
+ static create() {
642
+ return new Workbook();
643
+ }
644
+ static fromData(data) {
645
+ return new Workbook(data);
646
+ }
647
+ }
648
+
649
+ class StyleBuilder {
650
+ constructor() {
651
+ this.style = {};
652
+ }
653
+ bold(bold = true) {
654
+ this.style.bold = bold;
655
+ return this;
656
+ }
657
+ italic(italic = true) {
658
+ this.style.italic = italic;
659
+ return this;
660
+ }
661
+ underline(underline = true) {
662
+ this.style.underline = underline;
663
+ return this;
664
+ }
665
+ fontSize(size) {
666
+ this.style.fontSize = size;
667
+ return this;
668
+ }
669
+ fontFamily(family) {
670
+ this.style.fontFamily = family;
671
+ return this;
672
+ }
673
+ color(color) {
674
+ this.style.color = color;
675
+ return this;
676
+ }
677
+ backgroundColor(color) {
678
+ this.style.backgroundColor = color;
679
+ return this;
680
+ }
681
+ align(alignment) {
682
+ this.style.alignment = alignment;
683
+ return this;
684
+ }
685
+ verticalAlign(alignment) {
686
+ this.style.verticalAlignment = alignment;
687
+ return this;
688
+ }
689
+ wrapText(wrap = true) {
690
+ this.style.wrapText = wrap;
691
+ return this;
692
+ }
693
+ border(border) {
694
+ this.style.border = border;
695
+ return this;
696
+ }
697
+ borderTop(style = 'thin', color) {
698
+ if (!this.style.border)
699
+ this.style.border = {};
700
+ this.style.border.top = { style, color };
701
+ return this;
702
+ }
703
+ borderRight(style = 'thin', color) {
704
+ if (!this.style.border)
705
+ this.style.border = {};
706
+ this.style.border.right = { style, color };
707
+ return this;
708
+ }
709
+ borderBottom(style = 'thin', color) {
710
+ if (!this.style.border)
711
+ this.style.border = {};
712
+ this.style.border.bottom = { style, color };
713
+ return this;
714
+ }
715
+ borderLeft(style = 'thin', color) {
716
+ if (!this.style.border)
717
+ this.style.border = {};
718
+ this.style.border.left = { style, color };
719
+ return this;
720
+ }
721
+ borderAll(style = 'thin', color) {
722
+ const borderStyle = { style, color };
723
+ this.style.border = {
724
+ top: borderStyle,
725
+ bottom: borderStyle,
726
+ left: borderStyle,
727
+ right: borderStyle
728
+ };
729
+ return this;
730
+ }
731
+ numberFormat(format) {
732
+ this.style.numberFormat = format;
733
+ return this;
734
+ }
735
+ build() {
736
+ return { ...this.style };
737
+ }
738
+ static create() {
739
+ return new StyleBuilder();
740
+ }
741
+ }
742
+
743
+ class ExportBuilder {
744
+ constructor(sheetName = 'Sheet1') {
745
+ this.workbook = new Workbook();
746
+ this.currentSheet = new Worksheet(sheetName);
747
+ this.workbook.addSheet(this.currentSheet);
748
+ }
749
+ addHeaderRow(headers, style) {
750
+ const row = this.currentSheet.createRow();
751
+ headers.forEach(header => {
752
+ row.createCell(header, style);
753
+ });
754
+ return this;
755
+ }
756
+ addDataRows(data, fields) {
757
+ data.forEach(item => {
758
+ const row = this.currentSheet.createRow();
759
+ if (fields && fields.length > 0) {
760
+ fields.forEach(field => {
761
+ const value = this.getNestedValue(item, field);
762
+ row.createCell(value);
763
+ });
764
+ }
765
+ else if (Array.isArray(item)) {
766
+ item.forEach(value => row.createCell(value));
767
+ }
768
+ else if (typeof item === 'object') {
769
+ Object.values(item).forEach(value => row.createCell(value));
770
+ }
771
+ else {
772
+ row.createCell(item);
773
+ }
774
+ });
775
+ return this;
776
+ }
777
+ addSection(config) {
778
+ // Add section header
779
+ const headerRow = this.currentSheet.createRow();
780
+ headerRow.setOutlineLevel(config.level);
781
+ const headerCell = headerRow.createCell(config.title);
782
+ headerCell.setStyle(StyleBuilder.create()
783
+ .bold(true)
784
+ .backgroundColor('#E0E0E0')
785
+ .build());
786
+ // Add section data
787
+ if (config.data && config.data.length > 0) {
788
+ if (config.groupBy && config.subSections) {
789
+ // Handle grouped data
790
+ const groupedData = this.groupData(config.data, config.groupBy);
791
+ Object.entries(groupedData).forEach(([key, items]) => {
792
+ const subHeaderRow = this.currentSheet.createRow();
793
+ subHeaderRow.setOutlineLevel(config.level + 1);
794
+ subHeaderRow.createCell(`${config.groupBy}: ${key}`);
795
+ items.forEach((item) => {
796
+ const dataRow = this.currentSheet.createRow();
797
+ dataRow.setOutlineLevel(config.level + 2);
798
+ if (config.fields) {
799
+ config.fields.forEach((field) => {
800
+ dataRow.createCell(this.getNestedValue(item, field));
801
+ });
802
+ }
803
+ });
804
+ });
805
+ }
806
+ else {
807
+ // Add data rows with outline level
808
+ config.data.forEach((item) => {
809
+ const row = this.currentSheet.createRow();
810
+ row.setOutlineLevel(config.level + 1);
811
+ if (config.fields) {
812
+ config.fields.forEach((field) => {
813
+ row.createCell(this.getNestedValue(item, field));
814
+ });
815
+ }
816
+ });
817
+ }
818
+ }
819
+ return this;
820
+ }
821
+ addSections(sections) {
822
+ sections.forEach(section => this.addSection(section));
823
+ return this;
824
+ }
825
+ setColumnWidths(widths) {
826
+ widths.forEach((width) => {
827
+ this.currentSheet.createColumn(width);
828
+ });
829
+ return this;
830
+ }
831
+ autoSizeColumns() {
832
+ const rows = this.currentSheet.getRows();
833
+ const maxLengths = [];
834
+ rows.forEach(row => {
835
+ row.getCells().forEach((cell, index) => {
836
+ const value = cell.toData().value;
837
+ const length = value ? String(value).length : 0;
838
+ if (!maxLengths[index] || length > maxLengths[index]) {
839
+ maxLengths[index] = Math.min(length, 50); // Cap at 50 characters
840
+ }
841
+ });
842
+ });
843
+ maxLengths.forEach((length) => {
844
+ this.currentSheet.createColumn(Math.max(length, 10)); // Minimum width 10
845
+ });
846
+ return this;
847
+ }
848
+ addStyle(style) {
849
+ // Apply style to last row
850
+ const rows = this.currentSheet.getRows();
851
+ if (rows.length > 0) {
852
+ const lastRow = rows[rows.length - 1];
853
+ lastRow.getCells().forEach(cell => {
854
+ cell.setStyle(style);
855
+ });
856
+ }
857
+ return this;
858
+ }
859
+ groupData(data, field) {
860
+ return data.reduce((groups, item) => {
861
+ const key = this.getNestedValue(item, field);
862
+ if (!groups[key]) {
863
+ groups[key] = [];
864
+ }
865
+ groups[key].push(item);
866
+ return groups;
867
+ }, {});
868
+ }
869
+ getNestedValue(obj, path) {
870
+ return path.split('.').reduce((current, key) => {
871
+ return current && current[key] !== undefined ? current[key] : undefined;
872
+ }, obj);
873
+ }
874
+ build() {
875
+ return this.workbook;
876
+ }
877
+ async export(options) {
878
+ return this.workbook.export(options);
879
+ }
880
+ download(options = {}) {
881
+ const filename = options.filename || 'export.xlsx';
882
+ const format = options.format || (filename.endsWith('.csv') ? 'csv' :
883
+ filename.endsWith('.json') ? 'json' :
884
+ 'xlsx');
885
+ let writer;
886
+ if (format === 'xlsx')
887
+ writer = ExcelWriter;
888
+ else if (format === 'csv')
889
+ writer = CSVWriter;
890
+ else if (format === 'json')
891
+ writer = JSONWriter;
892
+ else
893
+ throw new Error('Unsupported format');
894
+ writer.write(this.workbook, options).then((blob) => {
895
+ const url = URL.createObjectURL(blob);
896
+ const a = document.createElement('a');
897
+ a.href = url;
898
+ a.download = filename;
899
+ document.body.appendChild(a);
900
+ a.click();
901
+ setTimeout(() => {
902
+ document.body.removeChild(a);
903
+ URL.revokeObjectURL(url);
904
+ }, 100);
905
+ });
906
+ }
907
+ static create(sheetName) {
908
+ return new ExportBuilder(sheetName);
909
+ }
910
+ }
911
+
912
+ /**
913
+ * SheetBuilder provides a fluent API for building Excel worksheets
914
+ */
915
+ class SheetBuilder {
916
+ /**
917
+ * Create a new SheetBuilder instance
918
+ * @param name Worksheet name
919
+ */
920
+ constructor(name = 'Sheet1') {
921
+ this.currentRow = 0;
922
+ this.worksheet = new Worksheet(name);
923
+ }
924
+ /**
925
+ * Get the underlying worksheet
926
+ */
927
+ getWorksheet() {
928
+ return this.worksheet;
929
+ }
930
+ /**
931
+ * Set the worksheet name
932
+ */
933
+ setName(name) {
934
+ this.worksheet.setName(name);
935
+ return this;
936
+ }
937
+ /**
938
+ * Add a header row with styling
939
+ * @param headers Array of header text
940
+ * @param style Optional style for all headers
941
+ */
942
+ addHeaderRow(headers, style) {
943
+ const row = this.createRow();
944
+ headers.forEach((header) => {
945
+ const cellStyle = style || StyleBuilder.create()
946
+ .bold(true)
947
+ .backgroundColor('#F0F0F0')
948
+ .borderAll('thin')
949
+ .align('center')
950
+ .build();
951
+ row.createCell(header, cellStyle);
952
+ });
953
+ return this;
954
+ }
955
+ /**
956
+ * Add a title row
957
+ * @param title Title text
958
+ * @param colSpan Number of columns to span
959
+ */
960
+ addTitle(title, colSpan = 1) {
961
+ const row = this.createRow();
962
+ const cell = row.createCell(title);
963
+ cell.setStyle(StyleBuilder.create()
964
+ .bold(true)
965
+ .fontSize(14)
966
+ .align('center')
967
+ .build());
968
+ if (colSpan > 1) {
969
+ this.mergeCells(this.currentRow - 1, 0, this.currentRow - 1, colSpan - 1);
970
+ }
971
+ return this;
972
+ }
973
+ /**
974
+ * Add a subtitle row
975
+ * @param subtitle Subtitle text
976
+ * @param colSpan Number of columns to span
977
+ */
978
+ addSubtitle(subtitle, colSpan = 1) {
979
+ const row = this.createRow();
980
+ const cell = row.createCell(subtitle);
981
+ cell.setStyle(StyleBuilder.create()
982
+ .italic(true)
983
+ .color('#666666')
984
+ .align('center')
985
+ .build());
986
+ if (colSpan > 1) {
987
+ this.mergeCells(this.currentRow - 1, 0, this.currentRow - 1, colSpan - 1);
988
+ }
989
+ return this;
990
+ }
991
+ /**
992
+ * Add a row of data
993
+ * @param data Array of cell values
994
+ * @param styles Optional array of styles per cell
995
+ */
996
+ addRow(data, styles) {
997
+ const row = this.createRow();
998
+ data.forEach((value, index) => {
999
+ const style = styles && styles[index] ? styles[index] : undefined;
1000
+ row.createCell(value, style);
1001
+ });
1002
+ return this;
1003
+ }
1004
+ /**
1005
+ * Add multiple rows of data
1006
+ * @param rows Array of row data
1007
+ * @param styles Optional array of styles per row or per cell
1008
+ */
1009
+ addRows(rows, styles) {
1010
+ rows.forEach((rowData, rowIndex) => {
1011
+ const rowStyles = styles && styles[rowIndex];
1012
+ if (Array.isArray(rowStyles)) {
1013
+ this.addRow(rowData, rowStyles);
1014
+ }
1015
+ else {
1016
+ this.addRow(rowData, rowStyles ? [rowStyles] : undefined);
1017
+ }
1018
+ });
1019
+ return this;
1020
+ }
1021
+ /**
1022
+ * Add data from objects
1023
+ * @param data Array of objects
1024
+ * @param fields Fields to extract (keys or dot notation paths)
1025
+ * @param headers Optional header labels
1026
+ */
1027
+ addObjects(data, fields, headers) {
1028
+ // Add headers if provided
1029
+ if (headers) {
1030
+ this.addHeaderRow(headers);
1031
+ }
1032
+ // Add data rows
1033
+ data.forEach(item => {
1034
+ const rowData = fields.map(field => this.getNestedValue(item, field));
1035
+ this.addRow(rowData);
1036
+ });
1037
+ return this;
1038
+ }
1039
+ /**
1040
+ * Add a section with header and data
1041
+ * @param title Section title
1042
+ * @param data Section data
1043
+ * @param fields Fields to display
1044
+ * @param level Outline level
1045
+ */
1046
+ addSection(title, data, fields, level = 0) {
1047
+ // Add section header
1048
+ const headerRow = this.createRow();
1049
+ headerRow.setOutlineLevel(level);
1050
+ const headerCell = headerRow.createCell(title);
1051
+ headerCell.setStyle(StyleBuilder.create()
1052
+ .bold(true)
1053
+ .fontSize(12)
1054
+ .backgroundColor('#E0E0E0')
1055
+ .borderAll('thin')
1056
+ .build());
1057
+ // Merge header across all columns
1058
+ if (fields.length > 1) {
1059
+ this.mergeCells(this.currentRow - 1, 0, this.currentRow - 1, fields.length - 1);
1060
+ }
1061
+ // Add field headers
1062
+ const fieldRow = this.createRow();
1063
+ fieldRow.setOutlineLevel(level + 1);
1064
+ fields.forEach(field => {
1065
+ fieldRow.createCell(this.formatFieldName(field), StyleBuilder.create()
1066
+ .bold(true)
1067
+ .backgroundColor('#F5F5F5')
1068
+ .borderAll('thin')
1069
+ .build());
1070
+ });
1071
+ // Add data rows
1072
+ data.forEach(item => {
1073
+ const row = this.createRow();
1074
+ row.setOutlineLevel(level + 2);
1075
+ fields.forEach(field => {
1076
+ const value = this.getNestedValue(item, field);
1077
+ row.createCell(value);
1078
+ });
1079
+ });
1080
+ return this;
1081
+ }
1082
+ /**
1083
+ * Add grouped data with sub-sections
1084
+ * @param data Data to group
1085
+ * @param groupBy Field to group by
1086
+ * @param fields Fields to display
1087
+ * @param level Outline level
1088
+ */
1089
+ addGroupedData(data, groupBy, fields, level = 0) {
1090
+ const groups = this.groupData(data, groupBy);
1091
+ Object.entries(groups).forEach(([key, items]) => {
1092
+ // Add group header
1093
+ const groupRow = this.createRow();
1094
+ groupRow.setOutlineLevel(level);
1095
+ const groupCell = groupRow.createCell(`${groupBy}: ${key}`);
1096
+ groupCell.setStyle(StyleBuilder.create()
1097
+ .bold(true)
1098
+ .backgroundColor('#E8E8E8')
1099
+ .build());
1100
+ if (fields.length > 1) {
1101
+ this.mergeCells(this.currentRow - 1, 0, this.currentRow - 1, fields.length - 1);
1102
+ }
1103
+ // Add field headers
1104
+ const fieldRow = this.createRow();
1105
+ fieldRow.setOutlineLevel(level + 1);
1106
+ fields.forEach(field => {
1107
+ fieldRow.createCell(this.formatFieldName(field), StyleBuilder.create()
1108
+ .bold(true)
1109
+ .backgroundColor('#F5F5F5')
1110
+ .build());
1111
+ });
1112
+ // Add items
1113
+ items.forEach((item) => {
1114
+ const row = this.createRow();
1115
+ row.setOutlineLevel(level + 2);
1116
+ fields.forEach(field => {
1117
+ const value = this.getNestedValue(item, field);
1118
+ row.createCell(value);
1119
+ });
1120
+ });
1121
+ // Add group summary
1122
+ this.addGroupSummary(items, fields, level + 1);
1123
+ });
1124
+ return this;
1125
+ }
1126
+ /**
1127
+ * Add summary row for a group
1128
+ */
1129
+ addGroupSummary(items, fields, level) {
1130
+ const summaryRow = this.createRow();
1131
+ summaryRow.setOutlineLevel(level + 1);
1132
+ // Create summary cells
1133
+ fields.forEach((_, index) => {
1134
+ if (index === 0) {
1135
+ summaryRow.createCell('Group Summary', StyleBuilder.create()
1136
+ .italic(true)
1137
+ .bold(true)
1138
+ .build());
1139
+ }
1140
+ else {
1141
+ const numericValues = items
1142
+ .map(item => this.getNestedValue(item, fields[index]))
1143
+ .filter(val => typeof val === 'number');
1144
+ if (numericValues.length > 0) {
1145
+ const sum = numericValues.reduce((a, b) => a + b, 0);
1146
+ summaryRow.createCell(sum, StyleBuilder.create()
1147
+ .bold(true)
1148
+ .numberFormat('#,##0.00')
1149
+ .build());
1150
+ }
1151
+ else {
1152
+ summaryRow.createCell('');
1153
+ }
1154
+ }
1155
+ });
1156
+ return this;
1157
+ }
1158
+ /**
1159
+ * Add a summary row with totals
1160
+ * @param fields Fields to summarize
1161
+ * @param functions Summary functions (sum, average, count, min, max)
1162
+ * @param label Summary label
1163
+ */
1164
+ addSummaryRow(fields, functions, label = 'Total') {
1165
+ const row = this.createRow();
1166
+ fields.forEach((_, index) => {
1167
+ if (index === 0) {
1168
+ row.createCell(label, StyleBuilder.create()
1169
+ .bold(true)
1170
+ .borderTop('double')
1171
+ .build());
1172
+ }
1173
+ else {
1174
+ const function_ = functions[index - 1] || 'sum';
1175
+ row.createCell(`=${function_.toUpperCase()}(${this.getColumnRange(index)})`, StyleBuilder.create()
1176
+ .bold(true)
1177
+ .borderTop('double')
1178
+ .numberFormat('#,##0.00')
1179
+ .build());
1180
+ }
1181
+ });
1182
+ return this;
1183
+ }
1184
+ /**
1185
+ * Set column widths
1186
+ * @param widths Array of column widths
1187
+ */
1188
+ setColumnWidths(widths) {
1189
+ widths.forEach((width, index) => {
1190
+ const column = this.getOrCreateColumn(index);
1191
+ column.setWidth(width);
1192
+ });
1193
+ return this;
1194
+ }
1195
+ /**
1196
+ * Auto-size columns based on content
1197
+ * @param maxWidth Maximum width in characters
1198
+ */
1199
+ autoSizeColumns(maxWidth = 50) {
1200
+ const rows = this.worksheet.getRows();
1201
+ const columnWidths = new Map();
1202
+ rows.forEach(row => {
1203
+ row.getCells().forEach((cell, index) => {
1204
+ const cellData = cell.toData();
1205
+ const value = cellData.value;
1206
+ const length = value ? String(value).length : 0;
1207
+ const currentMax = columnWidths.get(index) || 0;
1208
+ columnWidths.set(index, Math.min(Math.max(length, currentMax), maxWidth));
1209
+ });
1210
+ });
1211
+ columnWidths.forEach((width, index) => {
1212
+ const column = this.getOrCreateColumn(index);
1213
+ column.setWidth(Math.max(width + 2, 8)); // Add padding, minimum 8
1214
+ });
1215
+ return this;
1216
+ }
1217
+ /**
1218
+ * Set column to auto-fit
1219
+ * @param colIndex Column index
1220
+ */
1221
+ setColumnAutoFit(colIndex) {
1222
+ const column = this.getOrCreateColumn(colIndex);
1223
+ // Auto-fit will be applied during export
1224
+ column.setWidth(0); // 0 indicates auto-fit
1225
+ return this;
1226
+ }
1227
+ /**
1228
+ * Hide a column
1229
+ * @param colIndex Column index
1230
+ */
1231
+ hideColumn(colIndex) {
1232
+ const column = this.getOrCreateColumn(colIndex);
1233
+ column.setHidden(true);
1234
+ return this;
1235
+ }
1236
+ /**
1237
+ * Hide a row
1238
+ * @param rowIndex Row index
1239
+ */
1240
+ hideRow(rowIndex) {
1241
+ const row = this.worksheet.getRow(rowIndex);
1242
+ if (row) {
1243
+ row.setHidden(true);
1244
+ }
1245
+ return this;
1246
+ }
1247
+ /**
1248
+ * Set outline level for a row
1249
+ * @param rowIndex Row index
1250
+ * @param level Outline level (0-7)
1251
+ * @param collapsed Whether the outline is collapsed
1252
+ */
1253
+ setRowOutlineLevel(rowIndex, level, collapsed = false) {
1254
+ const row = this.worksheet.getRow(rowIndex);
1255
+ if (row) {
1256
+ row.setOutlineLevel(level, collapsed);
1257
+ }
1258
+ return this;
1259
+ }
1260
+ /**
1261
+ * Set outline level for a column
1262
+ * @param colIndex Column index
1263
+ * @param level Outline level (0-7)
1264
+ * @param collapsed Whether the outline is collapsed
1265
+ */
1266
+ setColumnOutlineLevel(colIndex, level, collapsed = false) {
1267
+ const column = this.getOrCreateColumn(colIndex);
1268
+ column.setOutlineLevel(level, collapsed);
1269
+ return this;
1270
+ }
1271
+ /**
1272
+ * Create an outline group for rows
1273
+ * @param startRow Start row index
1274
+ * @param endRow End row index
1275
+ * @param level Outline level
1276
+ * @param collapsed Whether the group is collapsed
1277
+ */
1278
+ groupRows(startRow, endRow, level = 1, collapsed = false) {
1279
+ for (let i = startRow; i <= endRow; i++) {
1280
+ const row = this.worksheet.getRow(i);
1281
+ if (row) {
1282
+ row.setOutlineLevel(level, collapsed && i === startRow);
1283
+ }
1284
+ }
1285
+ return this;
1286
+ }
1287
+ /**
1288
+ * Create an outline group for columns
1289
+ * @param startCol Start column index
1290
+ * @param endCol End column index
1291
+ * @param level Outline level
1292
+ * @param collapsed Whether the group is collapsed
1293
+ */
1294
+ groupColumns(startCol, endCol, level = 1, collapsed = false) {
1295
+ for (let i = startCol; i <= endCol; i++) {
1296
+ const column = this.getOrCreateColumn(i);
1297
+ column.setOutlineLevel(level, collapsed && i === startCol);
1298
+ }
1299
+ return this;
1300
+ }
1301
+ /**
1302
+ * Merge cells
1303
+ * @param startRow Start row
1304
+ * @param startCol Start column
1305
+ * @param endRow End row
1306
+ * @param endCol End column
1307
+ */
1308
+ mergeCells(startRow, startCol, endRow, endCol) {
1309
+ this.worksheet.mergeCells(startRow, startCol, endRow, endCol);
1310
+ return this;
1311
+ }
1312
+ /**
1313
+ * Freeze panes
1314
+ * @param rows Number of rows to freeze
1315
+ * @param columns Number of columns to freeze
1316
+ */
1317
+ freezePanes(rows = 0, columns = 0) {
1318
+ this.worksheet.setFreezePane(rows, columns);
1319
+ return this;
1320
+ }
1321
+ /**
1322
+ * Set print options
1323
+ * @param options Print options
1324
+ */
1325
+ setPrintOptions(options) {
1326
+ this.worksheet.setPrintOptions(options);
1327
+ return this;
1328
+ }
1329
+ /**
1330
+ * Set header and footer
1331
+ * @param headerFooter Header/footer configuration
1332
+ */
1333
+ setHeaderFooter(headerFooter) {
1334
+ // Store in worksheet (would be implemented in worksheet class)
1335
+ this.worksheet.headerFooter = headerFooter;
1336
+ return this;
1337
+ }
1338
+ /**
1339
+ * Add auto-filter
1340
+ * @param startRow Start row
1341
+ * @param startCol Start column
1342
+ * @param endRow End row
1343
+ * @param endCol End column
1344
+ */
1345
+ addAutoFilter(startRow, startCol, endRow, endCol) {
1346
+ const range = `${this.columnToLetter(startCol)}${startRow + 1}:${this.columnToLetter(endCol)}${endRow + 1}`;
1347
+ this.worksheet.autoFilter = { range };
1348
+ return this;
1349
+ }
1350
+ /**
1351
+ * Add a table
1352
+ * @param config Table configuration
1353
+ */
1354
+ addTable(config) {
1355
+ if (!this.worksheet.tables) {
1356
+ this.worksheet.tables = [];
1357
+ }
1358
+ this.worksheet.tables.push(config);
1359
+ return this;
1360
+ }
1361
+ /**
1362
+ * Add data validation to a cell or range
1363
+ * @param range Cell range (e.g., 'A1:B10')
1364
+ * @param validation Data validation rules
1365
+ */
1366
+ addDataValidation(range, validation) {
1367
+ if (!this.worksheet.dataValidations) {
1368
+ this.worksheet.dataValidations = {};
1369
+ }
1370
+ this.worksheet.dataValidations[range] = validation;
1371
+ return this;
1372
+ }
1373
+ /**
1374
+ * Add conditional formatting
1375
+ * @param range Cell range
1376
+ * @param rules Conditional formatting rules
1377
+ */
1378
+ addConditionalFormatting(range, rules) {
1379
+ if (!this.worksheet.conditionalFormats) {
1380
+ this.worksheet.conditionalFormats = {};
1381
+ }
1382
+ this.worksheet.conditionalFormats[range] = rules;
1383
+ return this;
1384
+ }
1385
+ /**
1386
+ * Add a chart
1387
+ * @param config Chart configuration
1388
+ */
1389
+ addChart(config) {
1390
+ if (!this.worksheet.charts) {
1391
+ this.worksheet.charts = [];
1392
+ }
1393
+ this.worksheet.charts.push(config);
1394
+ return this;
1395
+ }
1396
+ /**
1397
+ * Add an image or drawing
1398
+ * @param drawing Drawing configuration
1399
+ */
1400
+ addDrawing(drawing) {
1401
+ if (!this.worksheet.drawings) {
1402
+ this.worksheet.drawings = [];
1403
+ }
1404
+ this.worksheet.drawings.push(drawing);
1405
+ return this;
1406
+ }
1407
+ /**
1408
+ * Set cell value with style
1409
+ * @param row Row index
1410
+ * @param col Column index
1411
+ * @param value Cell value
1412
+ * @param style Cell style
1413
+ */
1414
+ setCell(row, col, value, style) {
1415
+ this.worksheet.setCell(row, col, value, style);
1416
+ return this;
1417
+ }
1418
+ /**
1419
+ * Get cell value
1420
+ * @param row Row index
1421
+ * @param col Column index
1422
+ */
1423
+ getCell(row, col) {
1424
+ return this.worksheet.getCell(row, col);
1425
+ }
1426
+ /**
1427
+ * Add a comment to a cell
1428
+ * @param row Row index
1429
+ * @param col Column index
1430
+ * @param comment Comment text
1431
+ * @param author Comment author
1432
+ */
1433
+ addComment(row, col, comment, author) {
1434
+ const cell = this.worksheet.getCell(row, col);
1435
+ if (cell) {
1436
+ // Would need to extend Cell class to support comments
1437
+ cell.comment = { text: comment, author };
1438
+ }
1439
+ return this;
1440
+ }
1441
+ /**
1442
+ * Add a hyperlink to a cell
1443
+ * @param row Row index
1444
+ * @param col Column index
1445
+ * @param url Hyperlink URL
1446
+ * @param displayText Display text (optional)
1447
+ */
1448
+ addHyperlink(row, col, url, displayText) {
1449
+ this.worksheet.setCell(row, col, displayText || url);
1450
+ const cell = this.worksheet.getCell(row, col);
1451
+ if (cell) {
1452
+ cell.hyperlink = url;
1453
+ }
1454
+ return this;
1455
+ }
1456
+ /**
1457
+ * Apply a style to a range
1458
+ * @param startRow Start row
1459
+ * @param startCol Start column
1460
+ * @param endRow End row
1461
+ * @param endCol End column
1462
+ * @param style Style to apply
1463
+ */
1464
+ applyStyleToRange(startRow, startCol, endRow, endCol, style) {
1465
+ for (let r = startRow; r <= endRow; r++) {
1466
+ for (let c = startCol; c <= endCol; c++) {
1467
+ const cell = this.worksheet.getCell(r, c);
1468
+ if (cell) {
1469
+ // Would need to update cell style
1470
+ cell.style = { ...cell.style, ...style };
1471
+ }
1472
+ }
1473
+ }
1474
+ return this;
1475
+ }
1476
+ /**
1477
+ * Insert a blank row
1478
+ * @param count Number of blank rows to insert
1479
+ */
1480
+ insertBlankRows(count = 1) {
1481
+ for (let i = 0; i < count; i++) {
1482
+ this.createRow();
1483
+ }
1484
+ return this;
1485
+ }
1486
+ /**
1487
+ * Create a new row
1488
+ */
1489
+ createRow() {
1490
+ const row = this.worksheet.createRow();
1491
+ this.currentRow++;
1492
+ return row;
1493
+ }
1494
+ /**
1495
+ * Get or create a column
1496
+ */
1497
+ getOrCreateColumn(index) {
1498
+ let column = this.worksheet.getColumn(index);
1499
+ if (!column) {
1500
+ column = this.worksheet.createColumn();
1501
+ }
1502
+ return column;
1503
+ }
1504
+ /**
1505
+ * Get nested value from object using dot notation
1506
+ */
1507
+ getNestedValue(obj, path) {
1508
+ return path.split('.').reduce((current, key) => {
1509
+ return current && current[key] !== undefined ? current[key] : undefined;
1510
+ }, obj);
1511
+ }
1512
+ /**
1513
+ * Format field name for display
1514
+ */
1515
+ formatFieldName(field) {
1516
+ return field
1517
+ .split('.')
1518
+ .pop()
1519
+ .split('_')
1520
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
1521
+ .join(' ');
1522
+ }
1523
+ /**
1524
+ * Group data by field
1525
+ */
1526
+ groupData(data, field) {
1527
+ return data.reduce((groups, item) => {
1528
+ const key = this.getNestedValue(item, field);
1529
+ if (!groups[key]) {
1530
+ groups[key] = [];
1531
+ }
1532
+ groups[key].push(item);
1533
+ return groups;
1534
+ }, {});
1535
+ }
1536
+ /**
1537
+ * Convert column index to letter (A, B, C, ...)
1538
+ */
1539
+ columnToLetter(column) {
1540
+ let letters = '';
1541
+ while (column >= 0) {
1542
+ letters = String.fromCharCode((column % 26) + 65) + letters;
1543
+ column = Math.floor(column / 26) - 1;
1544
+ }
1545
+ return letters;
1546
+ }
1547
+ /**
1548
+ * Get column range for formula (e.g., A:A)
1549
+ */
1550
+ getColumnRange(colIndex) {
1551
+ const colLetter = this.columnToLetter(colIndex);
1552
+ return `${colLetter}:${colLetter}`;
1553
+ }
1554
+ /**
1555
+ * Build and return the worksheet
1556
+ */
1557
+ build() {
1558
+ return this.worksheet;
1559
+ }
1560
+ /**
1561
+ * Reset the builder
1562
+ */
1563
+ reset() {
1564
+ this.worksheet = new Worksheet(this.worksheet.getName());
1565
+ this.currentRow = 0;
1566
+ return this;
1567
+ }
1568
+ /**
1569
+ * Create a new SheetBuilder instance
1570
+ */
1571
+ static create(name) {
1572
+ return new SheetBuilder(name);
1573
+ }
1574
+ /**
1575
+ * Create from existing worksheet
1576
+ */
1577
+ static fromWorksheet(worksheet) {
1578
+ const builder = new SheetBuilder(worksheet.getName());
1579
+ builder.worksheet = worksheet;
1580
+ return builder;
1581
+ }
1582
+ }
1583
+
1584
+ /**
1585
+ * Builder class for creating collapsible sections in worksheets
1586
+ * Supports nested sections, grouping, summaries, and conditional styling
1587
+ */
1588
+ class SectionBuilder {
1589
+ constructor(worksheet) {
1590
+ this.currentRow = 0;
1591
+ this.sections = new Map();
1592
+ this.styles = new Map();
1593
+ this.formatters = new Map();
1594
+ this.conditionalFormats = [];
1595
+ this.worksheet = worksheet;
1596
+ }
1597
+ /**
1598
+ * Add a section to the worksheet
1599
+ */
1600
+ addSection(config) {
1601
+ const section = new Section(this.worksheet, config, this.currentRow);
1602
+ if (config.title !== undefined) {
1603
+ this.sections.set(config.title, section);
1604
+ }
1605
+ // Build the section
1606
+ this.currentRow = section.build();
1607
+ return this;
1608
+ }
1609
+ /**
1610
+ * Add multiple sections at once
1611
+ */
1612
+ addSections(configs) {
1613
+ configs.forEach(config => this.addSection(config));
1614
+ return this;
1615
+ }
1616
+ /**
1617
+ * Add a nested section (child of current section)
1618
+ */
1619
+ addNestedSection(parentTitle, config) {
1620
+ const parent = this.sections.get(parentTitle);
1621
+ if (parent) {
1622
+ parent.addSubSection(config);
1623
+ }
1624
+ return this;
1625
+ }
1626
+ /**
1627
+ * Create a section from data with automatic grouping
1628
+ */
1629
+ createFromData(data, options) {
1630
+ const { title, groupBy, fields = Object.keys(data[0] || {}), fieldLabels = {}, level = 0, collapsed = false, summary } = options;
1631
+ // If no grouping, create a simple section
1632
+ if (!groupBy) {
1633
+ return this.addSection({
1634
+ title,
1635
+ level,
1636
+ collapsed,
1637
+ data,
1638
+ fields: fields,
1639
+ fieldLabels,
1640
+ summary: summary ? {
1641
+ fields: summary.fields,
1642
+ function: summary.functions[0] || 'count',
1643
+ label: 'Total'
1644
+ } : undefined
1645
+ });
1646
+ }
1647
+ // Group the data
1648
+ const groups = this.groupData(data, groupBy);
1649
+ // Create main section with groups as subsections
1650
+ const sectionConfig = {
1651
+ title,
1652
+ level,
1653
+ collapsed,
1654
+ data: [],
1655
+ subSections: Object.entries(groups).map(([key, items]) => ({
1656
+ title: `${String(groupBy)}: ${key}`,
1657
+ level: level + 1,
1658
+ collapsed: true,
1659
+ data: items,
1660
+ fields: fields,
1661
+ fieldLabels,
1662
+ summary: summary ? {
1663
+ fields: summary.fields,
1664
+ function: summary.functions[0] || 'count',
1665
+ label: `Total for ${key}`
1666
+ } : undefined
1667
+ }))
1668
+ };
1669
+ return this.addSection(sectionConfig);
1670
+ }
1671
+ /**
1672
+ * Add a summary section (totals, averages, etc.)
1673
+ */
1674
+ addSummarySection(data, fields, functions, options) {
1675
+ const { level = 0, style, showPercentage = false, label = 'Summary' } = options || {};
1676
+ // Calculate summary values
1677
+ const summary = {};
1678
+ fields.forEach((field, index) => {
1679
+ const func = functions[index] || functions[0];
1680
+ const values = data.map(item => this.getNestedValue(item, field)).filter(v => v != null);
1681
+ switch (func) {
1682
+ case 'sum':
1683
+ summary[field] = values.reduce((a, b) => a + b, 0);
1684
+ break;
1685
+ case 'average':
1686
+ summary[field] = values.reduce((a, b) => a + b, 0) / values.length;
1687
+ break;
1688
+ case 'count':
1689
+ summary[field] = values.length;
1690
+ break;
1691
+ case 'min':
1692
+ summary[field] = Math.min(...values);
1693
+ break;
1694
+ case 'max':
1695
+ summary[field] = Math.max(...values);
1696
+ break;
1697
+ }
1698
+ // Add percentage if requested
1699
+ if (showPercentage && func === 'count') {
1700
+ const total = data.length;
1701
+ summary[`${field}_percentage`] = ((summary[field] / total) * 100).toFixed(1) + '%';
1702
+ }
1703
+ });
1704
+ // Add the summary row
1705
+ const summaryRow = this.worksheet.createRow();
1706
+ summaryRow.setOutlineLevel(level + 1);
1707
+ // Add label cell
1708
+ const labelCell = summaryRow.createCell(`${label}:`);
1709
+ labelCell.setStyle(StyleBuilder.create()
1710
+ .bold(true)
1711
+ .italic(true)
1712
+ .backgroundColor('#f0f0f0')
1713
+ .build());
1714
+ // Add summary values
1715
+ fields.forEach(field => {
1716
+ const value = summary[field];
1717
+ const cell = summaryRow.createCell(value);
1718
+ if (style) {
1719
+ cell.setStyle(style);
1720
+ }
1721
+ else {
1722
+ cell.setStyle(StyleBuilder.create()
1723
+ .bold(true)
1724
+ .numberFormat(functions[0] === 'count' ? '#,##0' : '#,##0.00')
1725
+ .build());
1726
+ }
1727
+ });
1728
+ return this;
1729
+ }
1730
+ /**
1731
+ * Add a hierarchical section (tree structure)
1732
+ */
1733
+ addHierarchicalSection(items, childrenAccessor, options) {
1734
+ const { title = 'Hierarchy', level = 0, fields, collapsed = true, showCount = true } = options || {};
1735
+ const buildHierarchy = (items, currentLevel) => {
1736
+ return items.map(item => {
1737
+ const children = childrenAccessor(item);
1738
+ const hasChildren = children && children.length > 0;
1739
+ // Get display value for title
1740
+ const titleField = (fields === null || fields === void 0 ? void 0 : fields[0]) || 'name';
1741
+ const titleValue = this.getNestedValue(item, titleField);
1742
+ const count = hasChildren ? ` (${children.length})` : '';
1743
+ return {
1744
+ title: `${titleValue}${showCount && hasChildren ? count : ''}`,
1745
+ level: currentLevel,
1746
+ collapsed,
1747
+ data: [item],
1748
+ fields: fields,
1749
+ subSections: hasChildren ? buildHierarchy(children, currentLevel + 1) : undefined
1750
+ };
1751
+ });
1752
+ };
1753
+ const hierarchySections = buildHierarchy(items, level + 1);
1754
+ return this.addSection({
1755
+ title: title,
1756
+ level,
1757
+ collapsed,
1758
+ data: [],
1759
+ subSections: hierarchySections
1760
+ });
1761
+ }
1762
+ /**
1763
+ * Add a pivot-like section with multiple dimensions
1764
+ */
1765
+ addPivotSection(data, dimensions, options) {
1766
+ const { level = 0, showGrandTotals = true, showSubTotals = true } = options || {};
1767
+ // Group by row dimensions
1768
+ const rowGroups = this.groupMultiLevel(data, dimensions.rows);
1769
+ // Create sections for each row group
1770
+ Object.entries(rowGroups).forEach(([rowKey, rowItems]) => {
1771
+ const rowSection = {
1772
+ title: rowKey,
1773
+ level: level + 1,
1774
+ collapsed: true,
1775
+ data: [],
1776
+ subSections: []
1777
+ };
1778
+ // Group by column dimensions within each row
1779
+ if (dimensions.columns.length > 0) {
1780
+ const colGroups = this.groupMultiLevel(rowItems, dimensions.columns);
1781
+ Object.entries(colGroups).forEach(([colKey, colItems]) => {
1782
+ // Calculate values for this cell
1783
+ const values = {};
1784
+ dimensions.values.forEach(v => {
1785
+ const nums = colItems.map(item => item[v.field]).filter((n) => !isNaN(n));
1786
+ switch (v.aggregate) {
1787
+ case 'sum':
1788
+ values[v.field] = nums.reduce((a, b) => a + b, 0);
1789
+ break;
1790
+ case 'average':
1791
+ values[v.field] = nums.length ? nums.reduce((a, b) => a + b, 0) / nums.length : 0;
1792
+ break;
1793
+ case 'count':
1794
+ values[v.field] = colItems.length;
1795
+ break;
1796
+ case 'min':
1797
+ values[v.field] = Math.min(...nums);
1798
+ break;
1799
+ case 'max':
1800
+ values[v.field] = Math.max(...nums);
1801
+ break;
1802
+ }
1803
+ });
1804
+ rowSection.subSections.push({
1805
+ title: colKey,
1806
+ level: level + 2,
1807
+ data: [values],
1808
+ fields: dimensions.values.map(v => v.field)
1809
+ });
1810
+ });
1811
+ // Add row subtotal
1812
+ if (showSubTotals) {
1813
+ const subtotalValues = {};
1814
+ dimensions.values.forEach(v => {
1815
+ const nums = rowItems.map(item => item[v.field]).filter((n) => !isNaN(n));
1816
+ switch (v.aggregate) {
1817
+ case 'sum':
1818
+ subtotalValues[v.field] = nums.reduce((a, b) => a + b, 0);
1819
+ break;
1820
+ case 'average':
1821
+ subtotalValues[v.field] = nums.length ? nums.reduce((a, b) => a + b, 0) / nums.length : 0;
1822
+ break;
1823
+ case 'count':
1824
+ subtotalValues[v.field] = rowItems.length;
1825
+ break;
1826
+ case 'min':
1827
+ subtotalValues[v.field] = Math.min(...nums);
1828
+ break;
1829
+ case 'max':
1830
+ subtotalValues[v.field] = Math.max(...nums);
1831
+ break;
1832
+ }
1833
+ });
1834
+ rowSection.subSections.push({
1835
+ title: `${rowKey} Subtotal`,
1836
+ level: level + 2,
1837
+ data: [subtotalValues],
1838
+ fields: dimensions.values.map(v => v.field)
1839
+ });
1840
+ }
1841
+ }
1842
+ this.addSection(rowSection);
1843
+ });
1844
+ // Add grand total
1845
+ if (showGrandTotals) {
1846
+ const grandTotalValues = {};
1847
+ dimensions.values.forEach(v => {
1848
+ const nums = data.map(item => item[v.field]).filter((n) => !isNaN(n));
1849
+ switch (v.aggregate) {
1850
+ case 'sum':
1851
+ grandTotalValues[v.field] = nums.reduce((a, b) => a + b, 0);
1852
+ break;
1853
+ case 'average':
1854
+ grandTotalValues[v.field] = nums.length ? nums.reduce((a, b) => a + b, 0) / nums.length : 0;
1855
+ break;
1856
+ case 'count':
1857
+ grandTotalValues[v.field] = data.length;
1858
+ break;
1859
+ case 'min':
1860
+ grandTotalValues[v.field] = Math.min(...nums);
1861
+ break;
1862
+ case 'max':
1863
+ grandTotalValues[v.field] = Math.max(...nums);
1864
+ break;
1865
+ }
1866
+ });
1867
+ this.addSection({
1868
+ title: 'Grand Total',
1869
+ level,
1870
+ data: [grandTotalValues],
1871
+ fields: dimensions.values.map(v => v.field)
1872
+ });
1873
+ }
1874
+ return this;
1875
+ }
1876
+ /**
1877
+ * Add a timeline section (grouped by date periods)
1878
+ */
1879
+ addTimelineSection(data, dateField, period, options) {
1880
+ const { fields, level = 0, showTrends = true, format } = options || {};
1881
+ // Group by date period
1882
+ const grouped = this.groupByDate(data, dateField, period, format);
1883
+ // Create sections for each period
1884
+ Object.entries(grouped).forEach(([periodKey, periodData]) => {
1885
+ const section = {
1886
+ title: periodKey,
1887
+ level: level + 1,
1888
+ collapsed: true,
1889
+ data: periodData,
1890
+ fields
1891
+ };
1892
+ // Add trend indicators if requested
1893
+ if (showTrends && periodData.length > 0) {
1894
+ const prevPeriod = this.getPreviousPeriod(periodKey, grouped);
1895
+ if (prevPeriod) {
1896
+ const trend = this.calculateTrend(periodData, prevPeriod, (fields === null || fields === void 0 ? void 0 : fields[0]) || 'value');
1897
+ section.summary = {
1898
+ fields: [(fields === null || fields === void 0 ? void 0 : fields[0]) || 'value'],
1899
+ function: 'sum',
1900
+ label: `Trend: ${trend > 0 ? '↑' : '↓'} ${Math.abs(trend).toFixed(1)}%`
1901
+ };
1902
+ }
1903
+ }
1904
+ this.addSection(section);
1905
+ });
1906
+ return this;
1907
+ }
1908
+ /**
1909
+ * Add a filtered section
1910
+ */
1911
+ addFilteredSection(config, filters) {
1912
+ const filteredData = this.applyFilters(config.data, filters);
1913
+ return this.addSection({
1914
+ ...config,
1915
+ data: filteredData,
1916
+ title: `${config.title} (Filtered)`
1917
+ });
1918
+ }
1919
+ /**
1920
+ * Add conditional formatting to the current section
1921
+ */
1922
+ addConditionalFormat(rule) {
1923
+ this.conditionalFormats.push(rule);
1924
+ return this;
1925
+ }
1926
+ /**
1927
+ * Add a custom formatter for a field
1928
+ */
1929
+ addFormatter(field, formatter) {
1930
+ this.formatters.set(field, formatter);
1931
+ return this;
1932
+ }
1933
+ /**
1934
+ * Register a reusable style
1935
+ */
1936
+ registerStyle(name, style) {
1937
+ this.styles.set(name, style);
1938
+ return this;
1939
+ }
1940
+ /**
1941
+ * Apply a registered style
1942
+ */
1943
+ applyStyle(styleName) {
1944
+ const style = this.styles.get(styleName);
1945
+ if (style) {
1946
+ const lastRow = this.worksheet.getRows().slice(-1)[0];
1947
+ if (lastRow) {
1948
+ lastRow.getCells().forEach(cell => cell.setStyle(style));
1949
+ }
1950
+ }
1951
+ return this;
1952
+ }
1953
+ /**
1954
+ * Get the current row count
1955
+ */
1956
+ getCurrentRow() {
1957
+ return this.currentRow;
1958
+ }
1959
+ /**
1960
+ * Reset the builder
1961
+ */
1962
+ reset() {
1963
+ this.currentRow = 0;
1964
+ this.sections.clear();
1965
+ return this;
1966
+ }
1967
+ // Private helper methods
1968
+ groupData(data, groupBy) {
1969
+ return data.reduce((groups, item) => {
1970
+ const key = typeof groupBy === 'function'
1971
+ ? groupBy(item)
1972
+ : String(item[groupBy]);
1973
+ if (!groups[key]) {
1974
+ groups[key] = [];
1975
+ }
1976
+ groups[key].push(item);
1977
+ return groups;
1978
+ }, {});
1979
+ }
1980
+ groupMultiLevel(data, dimensions) {
1981
+ return data.reduce((groups, item) => {
1982
+ const key = dimensions.map(dim => this.getNestedValue(item, dim)).join(' › ');
1983
+ if (!groups[key]) {
1984
+ groups[key] = [];
1985
+ }
1986
+ groups[key].push(item);
1987
+ return groups;
1988
+ }, {});
1989
+ }
1990
+ groupByDate(data, dateField, period, format) {
1991
+ return data.reduce((groups, item) => {
1992
+ const date = new Date(this.getNestedValue(item, dateField));
1993
+ let key;
1994
+ switch (period) {
1995
+ case 'day':
1996
+ key = format || date.toISOString().split('T')[0];
1997
+ break;
1998
+ case 'week': {
1999
+ const week = this.getWeekNumber(date);
2000
+ key = format || `${date.getFullYear()}-W${week}`;
2001
+ break;
2002
+ }
2003
+ case 'month':
2004
+ key = format || `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
2005
+ break;
2006
+ case 'quarter': {
2007
+ const quarter = Math.floor(date.getMonth() / 3) + 1;
2008
+ key = format || `${date.getFullYear()}-Q${quarter}`;
2009
+ break;
2010
+ }
2011
+ case 'year':
2012
+ key = format || String(date.getFullYear());
2013
+ break;
2014
+ }
2015
+ if (!groups[key]) {
2016
+ groups[key] = [];
2017
+ }
2018
+ groups[key].push(item);
2019
+ return groups;
2020
+ }, {});
2021
+ }
2022
+ getWeekNumber(date) {
2023
+ const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
2024
+ const dayNum = d.getUTCDay() || 7;
2025
+ d.setUTCDate(d.getUTCDate() + 4 - dayNum);
2026
+ const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
2027
+ return Math.ceil(((d.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);
2028
+ }
2029
+ getPreviousPeriod(currentKey, groups) {
2030
+ const keys = Object.keys(groups).sort();
2031
+ const currentIndex = keys.indexOf(currentKey);
2032
+ if (currentIndex > 0) {
2033
+ return groups[keys[currentIndex - 1]];
2034
+ }
2035
+ return null;
2036
+ }
2037
+ calculateTrend(current, previous, field) {
2038
+ const currentSum = current.reduce((sum, item) => sum + (item[field] || 0), 0);
2039
+ const previousSum = previous.reduce((sum, item) => sum + (item[field] || 0), 0);
2040
+ if (previousSum === 0)
2041
+ return 0;
2042
+ return ((currentSum - previousSum) / previousSum) * 100;
2043
+ }
2044
+ applyFilters(data, filters) {
2045
+ return data.filter(item => {
2046
+ // Date range filter
2047
+ if (filters.dateRange) {
2048
+ const dateValue = new Date(this.getNestedValue(item, filters.dateRange.field || 'date'));
2049
+ const start = new Date(filters.dateRange.start);
2050
+ const end = new Date(filters.dateRange.end);
2051
+ if (dateValue < start || dateValue > end) {
2052
+ return false;
2053
+ }
2054
+ }
2055
+ // Field filters
2056
+ if (filters.filters) {
2057
+ for (const [field, values] of Object.entries(filters.filters)) {
2058
+ const itemValue = this.getNestedValue(item, field);
2059
+ const arrValues = values;
2060
+ if (arrValues.length > 0 && !arrValues.includes(itemValue)) {
2061
+ return false;
2062
+ }
2063
+ }
2064
+ }
2065
+ // Search filter
2066
+ if (filters.search) {
2067
+ const searchTerm = filters.search.toLowerCase();
2068
+ const matches = Object.values(item).some(val => String(val).toLowerCase().includes(searchTerm));
2069
+ if (!matches)
2070
+ return false;
2071
+ }
2072
+ // Custom filter
2073
+ if (filters.customFilter && !filters.customFilter(item)) {
2074
+ return false;
2075
+ }
2076
+ return true;
2077
+ }).slice(filters.offset || 0, (filters.offset || 0) + (filters.limit || Infinity));
2078
+ }
2079
+ getNestedValue(obj, path) {
2080
+ return path.split('.').reduce((current, key) => {
2081
+ return current && current[key] !== undefined ? current[key] : undefined;
2082
+ }, obj);
2083
+ }
2084
+ }
2085
+ /**
2086
+ * Internal Section class for building collapsible sections
2087
+ */
2088
+ class Section {
2089
+ constructor(worksheet, config, startRow) {
2090
+ this.subSections = [];
2091
+ this.worksheet = worksheet;
2092
+ this.config = config;
2093
+ this.startRow = startRow;
2094
+ }
2095
+ /**
2096
+ * Build the section in the worksheet
2097
+ */
2098
+ build() {
2099
+ let currentRow = this.startRow;
2100
+ // Add section header
2101
+ if (this.config.title) {
2102
+ const headerRow = this.worksheet.createRow();
2103
+ headerRow.setOutlineLevel(this.config.level);
2104
+ const headerCell = headerRow.createCell(this.config.title);
2105
+ // Apply header style
2106
+ const headerStyle = this.config.headerStyle ||
2107
+ StyleBuilder.create()
2108
+ .bold(true)
2109
+ .fontSize(12)
2110
+ .backgroundColor(this.getHeaderColor(this.config.level))
2111
+ .borderAll('thin')
2112
+ .build();
2113
+ headerCell.setStyle(headerStyle);
2114
+ // Add collapse/expand indicator if section has data or subsections
2115
+ if (this.hasContent()) {
2116
+ const indicatorCell = headerRow.createCell(this.config.collapsed ? '▶' : '▼');
2117
+ indicatorCell.setStyle(StyleBuilder.create()
2118
+ .bold(true)
2119
+ .color('#666666')
2120
+ .build());
2121
+ }
2122
+ currentRow++;
2123
+ }
2124
+ // Add section data if not collapsed
2125
+ if (!this.config.collapsed && this.config.data.length > 0) {
2126
+ // Add headers if fields are specified
2127
+ if (this.config.fields && this.config.fields.length > 0) {
2128
+ const headerRow = this.worksheet.createRow();
2129
+ headerRow.setOutlineLevel(this.config.level + 1);
2130
+ this.config.fields.forEach((field) => {
2131
+ var _a;
2132
+ const label = ((_a = this.config.fieldLabels) === null || _a === void 0 ? void 0 : _a[field]) || field;
2133
+ const cell = headerRow.createCell(label);
2134
+ cell.setStyle(StyleBuilder.create()
2135
+ .bold(true)
2136
+ .backgroundColor('#e6e6e6')
2137
+ .borderAll('thin')
2138
+ .build());
2139
+ });
2140
+ currentRow++;
2141
+ }
2142
+ // Add data rows
2143
+ this.config.data.forEach((item) => {
2144
+ const dataRow = this.worksheet.createRow();
2145
+ dataRow.setOutlineLevel(this.config.level + 1);
2146
+ const fields = this.config.fields || Object.keys(item);
2147
+ fields.forEach((field) => {
2148
+ const value = this.getNestedValue(item, field);
2149
+ const cell = dataRow.createCell(value);
2150
+ // Apply conditional formatting if any
2151
+ if (this.config.conditionalStyles) {
2152
+ this.applyConditionalStyles(cell, item, field);
2153
+ }
2154
+ // Apply field-specific style
2155
+ if (this.config.style) {
2156
+ cell.setStyle(this.config.style);
2157
+ }
2158
+ });
2159
+ currentRow++;
2160
+ });
2161
+ // Add summary row if configured
2162
+ if (this.config.summary) {
2163
+ this.addSummaryRow();
2164
+ currentRow++;
2165
+ }
2166
+ }
2167
+ // Build sub-sections
2168
+ if (this.config.subSections && !this.config.collapsed) {
2169
+ this.config.subSections.forEach((subConfig) => {
2170
+ const subSection = new Section(this.worksheet, subConfig, currentRow);
2171
+ this.subSections.push(subSection);
2172
+ currentRow = subSection.build();
2173
+ });
2174
+ }
2175
+ return currentRow;
2176
+ }
2177
+ /**
2178
+ * Add a sub-section
2179
+ */
2180
+ addSubSection(config) {
2181
+ if (!this.config.subSections) {
2182
+ this.config.subSections = [];
2183
+ }
2184
+ this.config.subSections.push(config);
2185
+ }
2186
+ /**
2187
+ * Check if section has any content
2188
+ */
2189
+ hasContent() {
2190
+ return this.config.data.length > 0 ||
2191
+ (this.config.subSections && this.config.subSections.length > 0);
2192
+ }
2193
+ /**
2194
+ * Get header color based on outline level
2195
+ */
2196
+ getHeaderColor(level) {
2197
+ const colors = [
2198
+ '#e6f2ff', // Level 0 - Light blue
2199
+ '#f0f0f0', // Level 1 - Light gray
2200
+ '#f9f9f9', // Level 2 - Lighter gray
2201
+ '#ffffff', // Level 3+ - White
2202
+ ];
2203
+ return colors[Math.min(level, colors.length - 1)];
2204
+ }
2205
+ /**
2206
+ * Add summary row (totals, averages, etc.)
2207
+ */
2208
+ addSummaryRow() {
2209
+ if (!this.config.summary)
2210
+ return;
2211
+ const summaryRow = this.worksheet.createRow();
2212
+ summaryRow.setOutlineLevel(this.config.level + 2);
2213
+ // Add label cell
2214
+ const labelCell = summaryRow.createCell(this.config.summary.label || 'Total');
2215
+ labelCell.setStyle(StyleBuilder.create()
2216
+ .bold(true)
2217
+ .italic(true)
2218
+ .backgroundColor('#f5f5f5')
2219
+ .build());
2220
+ // Calculate and add summary values
2221
+ const fields = this.config.fields || [];
2222
+ fields.forEach((field) => {
2223
+ if (this.config.summary.fields.includes(field)) {
2224
+ const values = this.config.data
2225
+ .map((item) => this.getNestedValue(item, field))
2226
+ .filter((v) => v != null && !isNaN(v));
2227
+ let result;
2228
+ switch (this.config.summary.function) {
2229
+ case 'sum':
2230
+ result = values.reduce((a, b) => a + b, 0);
2231
+ break;
2232
+ case 'average':
2233
+ result = values.length ? values.reduce((a, b) => a + b, 0) / values.length : 0;
2234
+ break;
2235
+ case 'count':
2236
+ result = values.length;
2237
+ break;
2238
+ case 'min':
2239
+ result = Math.min(...values);
2240
+ break;
2241
+ case 'max':
2242
+ result = Math.max(...values);
2243
+ break;
2244
+ default:
2245
+ result = 0;
2246
+ }
2247
+ const cell = summaryRow.createCell(result);
2248
+ cell.setStyle(StyleBuilder.create()
2249
+ .bold(true)
2250
+ .numberFormat(this.config.summary.function === 'count' ? '#,##0' : '#,##0.00')
2251
+ .build());
2252
+ }
2253
+ else {
2254
+ summaryRow.createCell('');
2255
+ }
2256
+ });
2257
+ }
2258
+ /**
2259
+ * Apply conditional formatting to a cell
2260
+ */
2261
+ applyConditionalStyles(cell, item, field) {
2262
+ if (!this.config.conditionalStyles)
2263
+ return;
2264
+ for (const rule of this.config.conditionalStyles) {
2265
+ if (rule.field === field && rule.condition(item)) {
2266
+ cell.setStyle(rule.style);
2267
+ break;
2268
+ }
2269
+ }
2270
+ }
2271
+ /**
2272
+ * Get nested value from object
2273
+ */
2274
+ getNestedValue(obj, path) {
2275
+ return path.split('.').reduce((current, key) => {
2276
+ return current && current[key] !== undefined ? current[key] : undefined;
2277
+ }, obj);
2278
+ }
2279
+ }
2280
+
2281
+ /**
2282
+ * Format currency values
2283
+ */
2284
+ const formatCurrency = (value) => {
2285
+ return new Intl.NumberFormat('en-US', {
2286
+ style: 'currency',
2287
+ currency: 'USD',
2288
+ minimumFractionDigits: 2,
2289
+ maximumFractionDigits: 2,
2290
+ }).format(value);
2291
+ };
2292
+ /**
2293
+ * Format date values
2294
+ */
2295
+ const formatDate = (date) => {
2296
+ const d = typeof date === 'string' ? new Date(date) : date;
2297
+ return new Intl.DateTimeFormat('en-US', {
2298
+ year: 'numeric',
2299
+ month: 'short',
2300
+ day: 'numeric',
2301
+ }).format(d);
2302
+ };
2303
+ /**
2304
+ * Format number with commas
2305
+ */
2306
+ const formatNumber = (num) => {
2307
+ return new Intl.NumberFormat('en-US').format(num);
2308
+ };
2309
+ /**
2310
+ * Truncate text with ellipsis
2311
+ */
2312
+ const truncateText = (text, maxLength) => {
2313
+ if (text.length <= maxLength)
2314
+ return text;
2315
+ return text.slice(0, maxLength) + '...';
2316
+ };
2317
+ /**
2318
+ * Generate random ID
2319
+ */
2320
+ const generateId = () => {
2321
+ return Math.random().toString(36).substring(2, 15) +
2322
+ Math.random().toString(36).substring(2, 15);
2323
+ };
2324
+ /**
2325
+ * Debounce function
2326
+ */
2327
+ const debounce = (func, wait) => {
2328
+ let timeout;
2329
+ return (...args) => {
2330
+ clearTimeout(timeout);
2331
+ timeout = setTimeout(() => func(...args), wait);
2332
+ };
2333
+ };
2334
+ /**
2335
+ * Group array by key
2336
+ */
2337
+ const groupBy = (array, key) => {
2338
+ return array.reduce((result, item) => {
2339
+ const groupKey = String(item[key]);
2340
+ if (!result[groupKey]) {
2341
+ result[groupKey] = [];
2342
+ }
2343
+ result[groupKey].push(item);
2344
+ return result;
2345
+ }, {});
2346
+ };
2347
+ /**
2348
+ * Calculate percentage
2349
+ */
2350
+ const calculatePercentage = (value, total) => {
2351
+ if (total === 0)
2352
+ return 0;
2353
+ return (value / total) * 100;
2354
+ };
2355
+ /**
2356
+ * Download file
2357
+ */
2358
+ const downloadFile = (content, filename, type) => {
2359
+ const blob = new Blob([content], { type });
2360
+ const url = window.URL.createObjectURL(blob);
2361
+ const link = document.createElement('a');
2362
+ link.href = url;
2363
+ link.download = filename;
2364
+ link.click();
2365
+ window.URL.revokeObjectURL(url);
2366
+ };
2367
+
2368
+ // Core exports
2369
+ function exportToExcel(data, options) {
2370
+ const builder = ExportBuilder.create((options === null || options === void 0 ? void 0 : options.sheetName) || 'Sheet1');
2371
+ if (options === null || options === void 0 ? void 0 : options.headers) {
2372
+ builder.addHeaderRow(options.headers);
2373
+ }
2374
+ builder.addDataRows(data, options === null || options === void 0 ? void 0 : options.fields);
2375
+ if (options === null || options === void 0 ? void 0 : options.columnWidths) {
2376
+ builder.setColumnWidths(options.columnWidths);
2377
+ }
2378
+ return builder.build();
2379
+ }
2380
+ function exportToCSV(data, options) {
2381
+ // Simplified CSV export
2382
+ const headers = (options === null || options === void 0 ? void 0 : options.headers) || Object.keys(data[0] || {});
2383
+ const rows = [headers];
2384
+ data.forEach(item => {
2385
+ const row = headers.map((header) => item[header] || '');
2386
+ rows.push(row);
2387
+ });
2388
+ return rows.map(row => row.map((cell) => String(cell).includes(',') ? `"${cell}"` : cell).join(',')).join('\n');
2389
+ }
2390
+ // Version
2391
+ const VERSION = '1.0.0';
2392
+
2393
+ exports.CSVWriter = CSVWriter;
2394
+ exports.Cell = Cell;
2395
+ exports.Column = Column;
2396
+ exports.DateFormatter = DateFormatter;
2397
+ exports.ExcelWriter = ExcelWriter;
2398
+ exports.ExportBuilder = ExportBuilder;
2399
+ exports.JSONWriter = JSONWriter;
2400
+ exports.NumberFormatter = NumberFormatter;
2401
+ exports.Row = Row;
2402
+ exports.SectionBuilder = SectionBuilder;
2403
+ exports.SheetBuilder = SheetBuilder;
2404
+ exports.StyleBuilder = StyleBuilder;
2405
+ exports.VERSION = VERSION;
2406
+ exports.Workbook = Workbook;
2407
+ exports.Worksheet = Worksheet;
2408
+ exports.calculatePercentage = calculatePercentage;
2409
+ exports.debounce = debounce;
2410
+ exports.downloadFile = downloadFile;
2411
+ exports.exportToCSV = exportToCSV;
2412
+ exports.exportToExcel = exportToExcel;
2413
+ exports.formatCurrency = formatCurrency;
2414
+ exports.formatDate = formatDate;
2415
+ exports.formatNumber = formatNumber;
2416
+ exports.generateId = generateId;
2417
+ exports.groupBy = groupBy;
2418
+ exports.truncateText = truncateText;
2419
+ //# sourceMappingURL=index.js.map