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