spotlibs-components 0.1.9 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/dist/{chunk-J3F77MZN.mjs → chunk-F6KSEYVB.mjs} +4 -35
  2. package/dist/chunk-F6KSEYVB.mjs.map +1 -0
  3. package/dist/chunk-UFE7HFT2.mjs +60 -0
  4. package/dist/chunk-UFE7HFT2.mjs.map +1 -0
  5. package/dist/chunk-YOSPWY5K.mjs +36 -0
  6. package/dist/chunk-YOSPWY5K.mjs.map +1 -0
  7. package/dist/{atoms → components/atoms}/icons/index.mjs +2 -1
  8. package/dist/components/index.d.mts +449 -0
  9. package/dist/components/index.mjs +7241 -0
  10. package/dist/components/index.mjs.map +1 -0
  11. package/dist/components/mui/index.d.mts +23 -0
  12. package/dist/components/mui/index.mjs +27 -0
  13. package/dist/components/mui/index.mjs.map +1 -0
  14. package/dist/components/types.d.mts +4 -0
  15. package/dist/{types-qsnVqq6_.d.mts → types-BkrxwBFm.d.mts} +443 -292
  16. package/dist/utils/index.d.mts +186 -0
  17. package/dist/utils/index.mjs +477 -0
  18. package/dist/utils/index.mjs.map +1 -0
  19. package/package.json +17 -2
  20. package/src/utils/admin-kuota-nasional/constant.js +95 -0
  21. package/src/utils/asset.js +10 -0
  22. package/src/utils/booleanUtils.js +19 -0
  23. package/src/utils/briguna-digital/constant.js +12 -0
  24. package/src/utils/compress.js +24 -0
  25. package/src/utils/compressImage.js +20 -0
  26. package/src/utils/constants.js +20 -0
  27. package/src/utils/cookies.js +102 -0
  28. package/src/utils/crypto.js +104 -0
  29. package/src/utils/decodeString.js +17 -0
  30. package/src/utils/disableFormElements.js +19 -0
  31. package/src/utils/excelUtils.js +60 -0
  32. package/src/utils/exception.js +86 -0
  33. package/src/utils/formatString.js +151 -0
  34. package/src/utils/formatters/account.js +98 -0
  35. package/src/utils/formatters/currency.js +125 -0
  36. package/src/utils/formatters/date.js +219 -0
  37. package/src/utils/formatters/index.js +47 -0
  38. package/src/utils/formatters/string.js +114 -0
  39. package/src/utils/generateImportExcel.js +546 -0
  40. package/src/utils/generateTimestamp.js +4 -0
  41. package/src/utils/handleApiError.js +17 -0
  42. package/src/utils/index.js +92 -0
  43. package/src/utils/json.js +55 -0
  44. package/src/utils/localStorage.js +8 -0
  45. package/src/utils/logger.js +46 -0
  46. package/src/utils/schema/globalSchema.js +10 -0
  47. package/src/utils/signature.js +75 -0
  48. package/src/utils/specifics/korporasi/prakarsaKorporasiUtils.js +87 -0
  49. package/src/utils/specifics/mikro/validationMaintenanceAgunanUtils.js +214 -0
  50. package/src/utils/static-data/korporasi/prakarsa/analisa-risiko-kredit-nasabah/analisa-kinerja-keuangan/listStaticData.js +290 -0
  51. package/src/utils/static-data/korporasi/prakarsa/appendix/proyeksi-laporan-arus-kas/listStaticData.js +7 -0
  52. package/src/utils/static-data/korporasi/prakarsa/appendix/proyeksi-laporan-keuangan/listStaticData.js +75 -0
  53. package/src/utils/static-data/korporasi/prakarsa/appendix/total-exposure-group/listStaticData.js +7 -0
  54. package/src/utils/static-data/korporasi/prakarsa/create/listStaticData.js +21 -0
  55. package/src/utils/static-data/korporasi/prakarsa/generals/listExcelConfigData.js +11 -0
  56. package/src/utils/static-data/korporasi/prakarsa/pengajuan-fasilitas-kredit/fasilitas-kredit/listStaticData.js +69 -0
  57. package/src/utils/static-data/korporasi-internasional/eksternal/listStaticData.js +193 -0
  58. package/src/utils/static-data/korporasi-internasional/master/listStaticData.js +24 -0
  59. package/src/utils/static-data/korporasi-internasional/uji-kepatuhan/listStaticData.js +250 -0
  60. package/src/utils/status.js +160 -0
  61. package/src/utils/store/adminKuotaNasional.js +7 -0
  62. package/src/utils/store/authStore.js +13 -0
  63. package/src/utils/store/bankGaransiStore.js +10 -0
  64. package/src/utils/store/korporasi/approval/interface/interfaceStore.js +10 -0
  65. package/src/utils/store/korporasi/general/generalKorporasiStore.js +13 -0
  66. package/src/utils/store/korporasi/loan-disbursement/loanDisbursementStore.js +77 -0
  67. package/src/utils/store/korporasi/prakarsa/fasilitas-kredit/fasilitasKreditStore.js +141 -0
  68. package/src/utils/store/migrasi-mandiri/migrasiMandiriStore.js +53 -0
  69. package/src/utils/store/monitoring-konsumer/monitoring-leads-KPP/monitoring-leads-kpp-internal.js +34 -0
  70. package/src/utils/store/prakarsaBankGaransiStore.js +21 -0
  71. package/src/utils/store/prognosa-crr/settingPrognosaROStore.js +9 -0
  72. package/src/utils/store/prognosaRMStore.js +30 -0
  73. package/src/utils/store/restrukStore.js +26 -0
  74. package/src/utils/store/verificationAccessLink.js +11 -0
  75. package/src/utils/store/warkat-bg-wholesale/warkatBgWholesaleStore.js +22 -0
  76. package/src/utils/stringUtils.js +226 -0
  77. package/src/utils/tipeBank.js +14 -0
  78. package/dist/chunk-J3F77MZN.mjs.map +0 -1
  79. package/dist/index.css +0 -32
  80. package/dist/index.css.map +0 -1
  81. package/dist/index.d.mts +0 -1726
  82. package/dist/index.mjs +0 -18584
  83. package/dist/index.mjs.map +0 -1
  84. package/dist/types.d.mts +0 -4
  85. /package/dist/{atoms → components/atoms}/icons/index.d.mts +0 -0
  86. /package/dist/{atoms → components/atoms}/icons/index.mjs.map +0 -0
  87. /package/dist/{types.mjs → components/types.mjs} +0 -0
  88. /package/dist/{types.mjs.map → components/types.mjs.map} +0 -0
@@ -0,0 +1,546 @@
1
+ /* eslint-disable no-undef */
2
+ import ExcelJS from 'exceljs';
3
+ import * as XLSX from 'xlsx';
4
+ import { Decimal } from 'decimal.js';
5
+ import { ModalErrorUtil } from '@/helpers/modal';
6
+
7
+ /**
8
+ * Generate and download template excel with dynamic configuration
9
+ * @param {Object} config - Configuration object for generating Excel
10
+ * @param {number} [config.year=new Date().getFullYear()] - Year
11
+ * @param {Array} config.data - Array of data to be rendered
12
+ * @param {Array<string>} [config.headers1=[]] - First row headers
13
+ * @param {Array<string>} [config.headers2=[]] - Second row headers
14
+ * @param {string} [config.title=''] - Title of the document
15
+ * @param {Array<number>} [config.disabledMonths=[]] - Array of months to disable (1-12)
16
+ * @param {number} [config.startDataRow=3] - Starting row index for data
17
+ * @param {boolean} [config.isLocked=false] - Whether to enable month-based cell locking
18
+ * @param {string} [config.password=''] - Password for protection
19
+ * @param {number} [config.freezeColumns=3] - Number of columns to freeze
20
+ * @param {boolean} [config.freezeHeader=false] - Whether to freeze header rows
21
+ * @param {string} [config.filename='Export'] - Output filename
22
+ * @param {boolean} [config.isTotalCell=false] - Whether to add total column
23
+ * @param {Object} [config.mappings] - Object mapping for data to excel columns
24
+ */
25
+ export async function generateAnnualTemplateExcel({
26
+ year = new Date().getFullYear(),
27
+ data = [],
28
+ headers1 = [],
29
+ headers2 = [],
30
+ title = '',
31
+ disabledMonths = [],
32
+ startDataRow = 3,
33
+ isLocked = false,
34
+ password = '',
35
+ freezeColumns = 3,
36
+ freezeHeader = false,
37
+ filename = 'Export',
38
+ isTotalCell = false,
39
+ mappings = {}
40
+ }) {
41
+ const workbook = new ExcelJS.Workbook();
42
+ const sheet = workbook.addWorksheet(title || `Export ${year}`);
43
+
44
+ const COLOR_HEADER = "A6C8EC";
45
+ const COLOR_VALUE_ID = "D9D9D9";
46
+ const COLOR_WHITE = "FFFFFFFF";
47
+ const FONT_DEFAULT = { name: "Arial", size: 11 };
48
+
49
+ // Add headers
50
+ if (headers1.length > 0) {
51
+ sheet.addRow(headers1);
52
+ }
53
+ if (headers2.length > 0) {
54
+ sheet.addRow(headers2);
55
+ } else if (headers1.length > 0) {
56
+ // If no second header row, add empty row for vertical merging
57
+ sheet.addRow(Array(headers1.length).fill(''));
58
+ }
59
+
60
+ // Handle header merging
61
+ if (headers1.length > 0) {
62
+ // Process each column
63
+ for (let col = 0; col < headers1.length; col++) {
64
+ const header1 = headers1[col];
65
+ const header2 = headers2[col] || '';
66
+
67
+ // Skip empty header1 cells
68
+ if (header1 === '') continue;
69
+
70
+ // Case 1: Vertical merge when header2 is empty or doesn't exist
71
+ if (header2 === '') {
72
+ try {
73
+ sheet.mergeCells(1, col + 1, 2, col + 1);
74
+ } catch (e) {
75
+ // Skip if already merged
76
+ }
77
+ }
78
+
79
+ // Case 2: Horizontal merge in header1 if next cells are empty
80
+ let span = 1;
81
+ while (col + span < headers1.length && headers1[col + span] === '') {
82
+ span++;
83
+ }
84
+ if (span > 1) {
85
+ try {
86
+ sheet.mergeCells(1, col + 1, 1, col + span);
87
+ } catch (e) {
88
+ // Skip if already merged
89
+ }
90
+ col += span - 1; // Skip the merged columns in next iteration
91
+ }
92
+ }
93
+ }
94
+
95
+ // Set title cell if there are headers
96
+ if (headers1.length > 0) {
97
+ // Find the range for title based on the month columns (empty strings in headers1)
98
+ const monthStartCol = headers1.findIndex(h => h === '') + 1;
99
+ // Count consecutive empty strings to determine the month span
100
+ let monthSpan = 0;
101
+ for (let i = monthStartCol - 1; i < headers1.length; i++) {
102
+ if (headers1[i] === '') monthSpan++;
103
+ else break;
104
+ }
105
+ // Title should span across all month columns
106
+ const lastCol = monthStartCol + monthSpan - 1;
107
+
108
+ // Unmerge any existing merged cells in the range before merging
109
+ const startCell = sheet.getCell(1, monthStartCol);
110
+ const endCell = sheet.getCell(1, lastCol);
111
+ const titleRange = `${startCell._address}:${endCell._address}`;
112
+
113
+ try {
114
+ // Try to unmerge first to prevent errors
115
+ sheet.unMergeCells(titleRange);
116
+ } catch (e) {
117
+ // Ignore if cells weren't merged
118
+ }
119
+
120
+ sheet.mergeCells(titleRange);
121
+ const titleCell = sheet.getCell(1, monthStartCol);
122
+ titleCell.value = title || `Export Data Tahun ${year}`;
123
+ titleCell.font = { ...FONT_DEFAULT, bold: true };
124
+ titleCell.alignment = { horizontal: "center", vertical: "middle" };
125
+ titleCell.fill = {
126
+ type: "pattern",
127
+ pattern: "solid",
128
+ fgColor: { argb: COLOR_HEADER }
129
+ };
130
+ titleCell.border = {
131
+ top: { style: "thin" },
132
+ left: { style: "thin" },
133
+ bottom: { style: "thin" },
134
+ right: { style: "thin" }
135
+ };
136
+ }
137
+
138
+ // Style all headers
139
+ [1, 2].forEach(row => {
140
+ if ((row === 1 && headers1.length > 0) || (row === 2 && headers2.length > 0)) {
141
+ // Use headers1 length as the column count since it includes all columns
142
+ const totalColumns = headers1.length;
143
+ for (let col = 1; col <= totalColumns; col++) {
144
+ const cell = sheet.getRow(row).getCell(col);
145
+ if (cell.value !== null) {
146
+ cell.font = { ...FONT_DEFAULT, bold: true };
147
+ cell.fill = {
148
+ type: "pattern",
149
+ pattern: "solid",
150
+ fgColor: { argb: COLOR_HEADER }
151
+ };
152
+ cell.alignment = { horizontal: "center", vertical: "middle" };
153
+ cell.border = {
154
+ top: { style: "thin" },
155
+ left: { style: "thin" },
156
+ bottom: { style: "thin" },
157
+ right: { style: "thin" }
158
+ };
159
+ }
160
+ }
161
+ }
162
+ });
163
+
164
+ // Add data rows
165
+ data.forEach((item, idx) => {
166
+ const rowIdx = idx + startDataRow;
167
+ const row = sheet.getRow(rowIdx);
168
+
169
+ // Map data to columns based on mappings, but skip 'no' mapping since we handle it directly
170
+ Object.entries(mappings).forEach(([key, colIndex]) => {
171
+ if (key !== 'no') { // Skip 'no' mapping since we handle it manually
172
+ row.getCell(colIndex).value = item[key] || '';
173
+ }
174
+ });
175
+
176
+ // Set row number explicitly
177
+ row.getCell(1).value = idx + 1;
178
+
179
+ // If monthly data exists in mapping
180
+ const monthlyData = Array(12).fill(0);
181
+ for (let i = 0; i < 12; i++) {
182
+ const monthKey = `month_${i + 1}`;
183
+ if (item[monthKey] !== undefined) {
184
+ monthlyData[i] = item[monthKey];
185
+ }
186
+ }
187
+
188
+ // Add monthly values
189
+ for (let i = 0; i < 12; i++) {
190
+ const cell = row.getCell(i + Object.keys(mappings).length + 1);
191
+ cell.value = monthlyData[i];
192
+ cell.numFmt = "#,##0.00";
193
+ }
194
+
195
+ // Add total if required
196
+ if (isTotalCell) {
197
+ const totalCell = row.getCell(Object.keys(mappings).length + 13);
198
+ totalCell.value = { formula: `SUM(${row.getCell(Object.keys(mappings).length + 1)._address}:${row.getCell(Object.keys(mappings).length + 12)._address})` };
199
+ totalCell.numFmt = "#,##0.00";
200
+ }
201
+
202
+ row.commit();
203
+ });
204
+
205
+ // Style data rows
206
+ const totalColumns = headers1.length; // Use headers1 length as it includes all columns including Total
207
+ for (let r = startDataRow; r < startDataRow + data.length; r++) {
208
+ for (let c = 1; c <= totalColumns; c++) {
209
+ const cell = sheet.getRow(r).getCell(c);
210
+ cell.font = { ...FONT_DEFAULT };
211
+
212
+ // Style based on column type
213
+ const isMonthColumn = c > Object.keys(mappings).length && c <= Object.keys(mappings).length + 12;
214
+ const isTotalColumn = isTotalCell && c === totalColumns;
215
+
216
+ if (isMonthColumn) {
217
+ const monthIndex = c - Object.keys(mappings).length;
218
+
219
+ // Apply the new disabled months logic
220
+ let cellIsLocked = false;
221
+ if (isLocked) {
222
+ // Use the directly provided disabledMonths array
223
+ cellIsLocked = disabledMonths.includes(monthIndex);
224
+ }
225
+
226
+ cell.fill = {
227
+ type: "pattern",
228
+ pattern: "solid",
229
+ fgColor: { argb: cellIsLocked ? COLOR_VALUE_ID : COLOR_WHITE }
230
+ };
231
+ cell.protection = { locked: cellIsLocked };
232
+ cell.alignment = { horizontal: "right" };
233
+ } else if (isTotalColumn) {
234
+ cell.fill = {
235
+ type: "pattern",
236
+ pattern: "solid",
237
+ fgColor: { argb: COLOR_VALUE_ID }
238
+ };
239
+ cell.protection = { locked: true };
240
+ cell.alignment = { horizontal: "right" };
241
+ } else {
242
+ cell.fill = {
243
+ type: "pattern",
244
+ pattern: "solid",
245
+ fgColor: { argb: COLOR_VALUE_ID }
246
+ };
247
+ cell.protection = { locked: true };
248
+ cell.alignment = { horizontal: c === 1 ? "center" : "left" };
249
+ }
250
+
251
+ cell.border = {
252
+ top: { style: "thin" },
253
+ left: { style: "thin" },
254
+ bottom: { style: "thin" },
255
+ right: { style: "thin" }
256
+ };
257
+ }
258
+ }
259
+
260
+ // Set column widths
261
+ const getMaxWidth = (arr) => Math.max(...arr.map(v => v ? v.toString().length : 0));
262
+
263
+ // For mapped columns
264
+ Object.entries(mappings).forEach(([key, colIndex]) => {
265
+ const values = data.map(item => item[key]);
266
+ const headerText = headers1[colIndex - 1] || headers2[colIndex - 1] || key;
267
+ sheet.getColumn(colIndex).width = getMaxWidth([headerText, ...values]) + 5;
268
+ });
269
+
270
+ // For month columns and total
271
+ for (let i = Object.keys(mappings).length + 1; i <= totalColumns; i++) {
272
+ const isTotalColumn = isTotalCell && i === totalColumns;
273
+ sheet.getColumn(i).width = isTotalColumn ? 25 : 22;
274
+ }
275
+
276
+ if (password) {
277
+ sheet.protect(password, {
278
+ selectLockedCells: true,
279
+ selectUnlockedCells: true,
280
+ formatCells: false,
281
+ formatColumns: false,
282
+ formatRows: false,
283
+ insertColumns: false,
284
+ insertRows: false,
285
+ insertHyperlinks: false,
286
+ deleteColumns: false,
287
+ deleteRows: false,
288
+ sort: false,
289
+ autoFilter: false,
290
+ pivotTables: false,
291
+ objects: false,
292
+ scenarios: false
293
+ });
294
+ }
295
+
296
+ if (freezeColumns > 0 || freezeHeader) {
297
+ const freezeOptions = { state: "frozen" };
298
+
299
+ if (freezeColumns > 0) {
300
+ freezeOptions.xSplit = freezeColumns;
301
+ }
302
+
303
+ if (freezeHeader) {
304
+ // Freeze header rows based on the number of header rows present
305
+ const headerRowCount = (headers1.length > 0 ? 1 : 0) + (headers2.length > 0 ? 1 : 0);
306
+ if (headerRowCount > 0) {
307
+ freezeOptions.ySplit = headerRowCount;
308
+ }
309
+ }
310
+
311
+ sheet.views = [freezeOptions];
312
+ }
313
+
314
+ const buffer = await workbook.xlsx.writeBuffer();
315
+ const blob = new Blob([buffer], {
316
+ type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
317
+ });
318
+ const link = document.createElement("a");
319
+ link.href = URL.createObjectURL(blob);
320
+ link.download = `${filename}.xlsx`;
321
+ document.body.appendChild(link);
322
+ link.click();
323
+ document.body.removeChild(link);
324
+ }
325
+
326
+ /**
327
+ * Import and validate excel template with dynamic configuration
328
+ * @param {Object} config - Configuration object for importing Excel
329
+ * @param {number} [config.maxValue=9999999999999] - Maximum allowed value for numeric fields
330
+ * @param {File} config.file - The Excel file to import
331
+ * @param {string} [config.sheetName=''] - Specific sheet name to read. If empty, reads first sheet
332
+ * @param {number} [config.headerRow=1] - Row number where headers start (1-based)
333
+ * @param {Array<string>} [config.requiredHeaders=[]] - Array of required header names
334
+ * @param {Object} [config.mappings] - Column mapping for data import (e.g., { 'Kantor Cabang': 'branch_desc' })
335
+ * @param {boolean} [config.hasMonthlyData=true] - Whether the template contains monthly data columns
336
+ * @param {function} [config.validateRow] - Optional custom row validation function
337
+ * @returns {Promise<Array>} - Parsed and validated data
338
+ */
339
+ export const importAnnualTemplateExcel = ({
340
+ maxValue = 9999999999999,
341
+ file,
342
+ sheetName = '',
343
+ headerRow = 1,
344
+ requiredHeaders = [],
345
+ mappings = {},
346
+ hasMonthlyData = true,
347
+ validateRow
348
+ }) => {
349
+ // Validate file type
350
+ const validTypes = [
351
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
352
+ 'application/vnd.ms-excel'
353
+ ];
354
+ const fileName = file.name || '';
355
+ const isValidExt = fileName.endsWith('.xlsx') || fileName.endsWith('.xls');
356
+
357
+ if (!validTypes.includes(file.type) && !isValidExt) {
358
+ ModalErrorUtil.showModal('File yang diupload harus memiliki tipe XLSX atau XLS');
359
+ return Promise.reject(new Error('File yang diupload harus memiliki tipe XLSX atau XLS'));
360
+ }
361
+
362
+ return new Promise((resolve, reject) => {
363
+ try {
364
+ const reader = new FileReader();
365
+
366
+ reader.onload = (e) => {
367
+ try {
368
+ const data = new Uint8Array(e.target.result);
369
+ const workbook = XLSX.read(data, { type: 'array' });
370
+
371
+ // Get the specified sheet or the first sheet
372
+ const sheet = sheetName
373
+ ? workbook.Sheets[sheetName]
374
+ : workbook.Sheets[workbook.SheetNames[0]];
375
+
376
+ if (!sheet) {
377
+ reject(new Error(`Sheet ${sheetName || 'pertama'} tidak ditemukan`));
378
+ return;
379
+ }
380
+
381
+ // Convert to JSON with header option
382
+ const jsonData = XLSX.utils.sheet_to_json(sheet, {
383
+ header: 1,
384
+ blankrows: false,
385
+ defval: ''
386
+ });
387
+
388
+ if (jsonData.length < headerRow) {
389
+ reject(new Error('File Excel kosong atau tidak memiliki data yang cukup'));
390
+ return;
391
+ }
392
+
393
+ // Get headers from the specified header row (convert to 0-based index)
394
+ const headers = jsonData[headerRow - 1].map(h => String(h).trim());
395
+
396
+ // For dual-header templates, also get the second row for month names
397
+ let monthHeaders = [];
398
+ if (hasMonthlyData && jsonData.length > headerRow) {
399
+ monthHeaders = jsonData[headerRow].map(h => String(h).trim());
400
+ }
401
+
402
+ // Validate required headers
403
+ const missingHeaders = requiredHeaders.filter(required => {
404
+ const requiredLower = required.toLowerCase();
405
+ return !headers.some(h => h.toLowerCase() === requiredLower);
406
+ });
407
+
408
+ if (missingHeaders.length > 0) {
409
+ reject(new Error(`Kolom yang diperlukan tidak ditemukan: ${missingHeaders.join(', ')}`));
410
+ return;
411
+ }
412
+
413
+ // Define month mappings to match form field names: ex. {branch}_{month}
414
+ const monthMap = {
415
+ 'januari': 'jan',
416
+ 'jan': 'jan',
417
+ 'februari': 'feb',
418
+ 'feb': 'feb',
419
+ 'maret': 'mar',
420
+ 'mar': 'mar',
421
+ 'april': 'april',
422
+ 'apr': 'april',
423
+ 'mei': 'mei',
424
+ 'juni': 'jun',
425
+ 'jun': 'jun',
426
+ 'juli': 'jul',
427
+ 'jul': 'jul',
428
+ 'agustus': 'agu',
429
+ 'agu': 'agu',
430
+ 'september': 'sept',
431
+ 'sept': 'sept',
432
+ 'oktober': 'okt',
433
+ 'okt': 'okt',
434
+ 'november': 'nov',
435
+ 'nov': 'nov',
436
+ 'desember': 'des',
437
+ 'des': 'des'
438
+ };
439
+
440
+ // Create column mappings
441
+ const columnMappings = headers.map((header, index) => {
442
+ const headerLower = header.toLowerCase();
443
+ const monthHeader = monthHeaders[index] || '';
444
+ const monthHeaderLower = monthHeader.toLowerCase();
445
+
446
+ // Check custom mappings first
447
+ for (const [mappingKey, mappingValue] of Object.entries(mappings)) {
448
+ if (mappingKey.toLowerCase() === headerLower) {
449
+ return { index, key: mappingValue, isMonth: false };
450
+ }
451
+ }
452
+
453
+ // Then check for month columns in the second header row if hasMonthlyData is true
454
+ if (hasMonthlyData && monthMap[monthHeaderLower]) {
455
+ return { index, key: monthMap[monthHeaderLower], isMonth: true };
456
+ }
457
+
458
+ return { index, key: header, isMonth: false };
459
+ });
460
+
461
+ // Process data rows (start from headerRow + 1 to skip both header rows)
462
+ const result = [];
463
+ for (let i = headerRow + 1; i < jsonData.length; i++) {
464
+ const row = jsonData[i];
465
+ if (!row || row.length === 0) continue;
466
+
467
+ const newRow = {};
468
+ let hasData = false;
469
+
470
+ // Map columns according to configuration
471
+ columnMappings.forEach(({ index, key, isMonth }) => {
472
+ let value = row[index];
473
+
474
+ // Handle monthly data columns
475
+ if (hasMonthlyData && isMonth) {
476
+ // Enhanced type handling like in excel-utils.js
477
+ if (typeof value === 'string') {
478
+ // Check if string contains only valid numeric characters
479
+ if (!/^[\d.,\-+]*$/.test(value.trim())) {
480
+ value = 0;
481
+ } else {
482
+ // Check for multiple commas - only one comma allowed as decimal separator
483
+ const commaCount = (value.match(/,/g) || []).length;
484
+ if (commaCount > 1) {
485
+ value = 0;
486
+ } else {
487
+ // Replace comma with dot for decimal conversion (European format)
488
+ value = value.replace(/,/g, '.').trim();
489
+ const numValue = new Decimal(value || 0);
490
+
491
+ // Validate and constrain the value
492
+ if (numValue.isNaN() || numValue.isNegative()) {
493
+ value = 0;
494
+ } else if (numValue.gt(maxValue)) {
495
+ value = maxValue;
496
+ } else {
497
+ value = numValue.toNumber();
498
+ }
499
+ }
500
+ }
501
+ } else if (typeof value === 'number') {
502
+ const numValue = new Decimal(value);
503
+ if (numValue.isNegative()) {
504
+ value = 0;
505
+ } else if (numValue.gt(maxValue)) {
506
+ value = maxValue;
507
+ } else {
508
+ value = numValue.toNumber();
509
+ }
510
+ } else {
511
+ // For any other type (null, undefined, boolean, etc.), set to 0
512
+ value = 0;
513
+ }
514
+
515
+ // Store monthly value directly using the month key
516
+ newRow[key] = value;
517
+ hasData = true;
518
+ } else {
519
+ newRow[key] = value;
520
+ if (value) hasData = true;
521
+ }
522
+ });
523
+
524
+ // Skip empty rows
525
+ if (!hasData && Object.values(newRow).every(v => !v)) continue;
526
+
527
+ // Apply custom row validation if provided
528
+ if (validateRow && !validateRow(newRow)) continue;
529
+
530
+ result.push(newRow);
531
+ }
532
+
533
+ resolve(result);
534
+ } catch (error) {
535
+ reject(new Error(`Error parsing Excel file: ${error.message}`));
536
+ }
537
+ };
538
+
539
+ reader.onerror = (error) => reject(error);
540
+ reader.readAsArrayBuffer(file);
541
+
542
+ } catch (error) {
543
+ reject(error);
544
+ }
545
+ });
546
+ };
@@ -0,0 +1,4 @@
1
+ export function generateVcifId() {
2
+ const timestamp = Date.now();
3
+ return `VCIF${timestamp}`;
4
+ }
@@ -0,0 +1,17 @@
1
+ export function handleApiError(err, res) {
2
+ // ! Check if response has already been sent to avoid ERR_HTTP_HEADERS_SENT
3
+ if (res.headersSent) {
4
+ return;
5
+ }
6
+
7
+ // ! Get status from error (axios errors have err.response.status, others might have err.status)
8
+ const status = err?.response?.status || err?.status;
9
+
10
+ if (err.response?.data) {
11
+ res.status(status).json(err.response?.data);
12
+ } else if (err?.message) {
13
+ res.status(status).json({ error: err.message });
14
+ } else {
15
+ res.status(status).json(err);
16
+ }
17
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Utils Entry Point
3
+ *
4
+ * Export semua utility functions yang bisa dipakai oleh microfrontend apps.
5
+ * Import di microfrontend: import { formatCurrency, formatAccountNumber } from "spotlibs-components/utils";
6
+ */
7
+
8
+ // Formatters (consolidated)
9
+ export {
10
+ formatAccountNumber,
11
+ formatNPWP,
12
+ formatCurrency,
13
+ formatRupiah,
14
+ formatNominal,
15
+ formatDigit,
16
+ toYearFirstDash,
17
+ toDateFirstSlash,
18
+ formatDateToDDSlashMMSlashYYYY,
19
+ formatDateToDDMMYYYY,
20
+ yearFirstDashToDateFirstDash,
21
+ yearFirstDashToDateFirstSlash,
22
+ dateFirstSlashToYearFirstDash,
23
+ sevenDigitToDateFirstSlash,
24
+ sevenDigitToYearFirstDash,
25
+ sevenDigitToDateFirstDash,
26
+ toDateTimeHourMinute,
27
+ ddmmyyToSlashDate,
28
+ formatResponseDesc,
29
+ titleCase,
30
+ snakeToCamelWithSpace,
31
+ stringToBoolean,
32
+ matchWiths,
33
+ maskString,
34
+ } from "./formatters";
35
+
36
+ // JSON utilities
37
+ export { parseJSON, jsonToStringLimiter, errorState, ascendingStringify } from "./json";
38
+
39
+ // Asset utility
40
+ export { callAsset } from "./asset";
41
+
42
+ // Boolean utilities
43
+ export { checkRoleUser, toggleUsingDummyWbenchEndpoint } from "./booleanUtils";
44
+
45
+ // Constants
46
+ export {
47
+ NOT_AUTHORIZE_RESPONSE_CODE,
48
+ PUBLIC_PATHS,
49
+ COOKIE_MAX_SIZE,
50
+ CLIENT_PUBLIC_API_ROUTES,
51
+ CLIENT_ALLOWED_ORIGINS,
52
+ } from "./constants";
53
+
54
+ // Disable form utilities
55
+ export { disableFormElements, disableFormBuilder } from "./disableFormElements";
56
+
57
+ // Generate timestamp
58
+ export { generateVcifId } from "./generateTimestamp";
59
+
60
+ // LocalStorage
61
+ export { getLocalStorageItem } from "./localStorage";
62
+
63
+ // Tipe Bank
64
+ export { getTipeBankKorporasi } from "./tipeBank";
65
+
66
+ // Exception
67
+ export {
68
+ HEADER_EXCEPTION,
69
+ ACCESS_EXCEPTION,
70
+ PARAMETER_EXCEPTION,
71
+ NOTFOUND_EXCEPTION,
72
+ INVALIDRULE_EXCEPTION,
73
+ THIRDPARTY_EXCEPTION,
74
+ WAITING_EXCEPTION,
75
+ UNSUPPORTED_EXCEPTION,
76
+ RUNTIME_EXCEPTION,
77
+ EXCEPTION_MESSAGES,
78
+ ApiException,
79
+ isExceptionCode,
80
+ getExceptionMessage,
81
+ } from "./exception";
82
+
83
+ // Format String (legacy exports - for backward compatibility)
84
+ export {
85
+ formatCurrency as formatCurrencyLegacy,
86
+ formatNominal as formatNominalLegacy,
87
+ dashedAccount,
88
+ dashedNPWP,
89
+ formatResponseDesc as formatResponseDescLegacy,
90
+ titleCase as titleCaseLegacy,
91
+ stringToBoolean as stringToBooleanLegacy,
92
+ } from "./formatString";