spotlibs-components 0.1.10 → 0.1.12

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 (120) hide show
  1. package/dist/{chunk-OGJBPTUP.mjs → chunk-F6KSEYVB.mjs} +3 -3
  2. package/dist/{chunk-OGJBPTUP.mjs.map → chunk-F6KSEYVB.mjs.map} +1 -1
  3. package/dist/chunk-UFE7HFT2.mjs +60 -0
  4. package/dist/chunk-UFE7HFT2.mjs.map +1 -0
  5. package/dist/{chunk-NXFLMH2Q.mjs → chunk-YOSPWY5K.mjs} +3 -4
  6. package/dist/{chunk-NXFLMH2Q.mjs.map → chunk-YOSPWY5K.mjs.map} +1 -1
  7. package/dist/{atoms → components/atoms}/icons/index.mjs +2 -2
  8. package/dist/components/index.d.mts +449 -0
  9. package/dist/components/index.mjs +7053 -0
  10. package/dist/components/index.mjs.map +1 -0
  11. package/dist/{mui → components/mui}/index.mjs +1 -1
  12. package/dist/components/types.d.mts +4 -0
  13. package/dist/{types-B0iF5xX8.d.mts → types-D1akE7ak.d.mts} +334 -245
  14. package/dist/utils/index.d.mts +106 -0
  15. package/dist/utils/index.mjs +477 -0
  16. package/dist/utils/index.mjs.map +1 -0
  17. package/package.json +13 -2
  18. package/src/utils/admin-kuota-nasional/constant.d.ts +23 -0
  19. package/src/utils/admin-kuota-nasional/constant.js +95 -0
  20. package/src/utils/asset.d.ts +1 -0
  21. package/src/utils/asset.js +10 -0
  22. package/src/utils/booleanUtils.d.ts +2 -0
  23. package/src/utils/booleanUtils.js +19 -0
  24. package/src/utils/briguna-digital/constant.d.ts +2 -0
  25. package/src/utils/briguna-digital/constant.js +12 -0
  26. package/src/utils/compress.d.ts +2 -0
  27. package/src/utils/compress.js +24 -0
  28. package/src/utils/compressImage.d.ts +6 -0
  29. package/src/utils/compressImage.js +20 -0
  30. package/src/utils/constants.d.ts +5 -0
  31. package/src/utils/constants.js +20 -0
  32. package/src/utils/cookies.d.ts +19 -0
  33. package/src/utils/cookies.js +102 -0
  34. package/src/utils/crypto.d.ts +5 -0
  35. package/src/utils/crypto.js +104 -0
  36. package/src/utils/decodeString.d.ts +1 -0
  37. package/src/utils/decodeString.js +17 -0
  38. package/src/utils/disableFormElements.d.ts +5 -0
  39. package/src/utils/disableFormElements.js +19 -0
  40. package/src/utils/excelUtils.d.ts +25 -0
  41. package/src/utils/excelUtils.js +60 -0
  42. package/src/utils/exception.d.ts +19 -0
  43. package/src/utils/exception.js +86 -0
  44. package/src/utils/formatString.d.ts +25 -0
  45. package/src/utils/formatString.js +151 -0
  46. package/src/utils/formatters/account.d.ts +2 -0
  47. package/src/utils/formatters/account.js +98 -0
  48. package/src/utils/formatters/currency.d.ts +20 -0
  49. package/src/utils/formatters/currency.js +125 -0
  50. package/src/utils/formatters/date.d.ts +12 -0
  51. package/src/utils/formatters/date.js +219 -0
  52. package/src/utils/formatters/index.d.ts +47 -0
  53. package/src/utils/formatters/index.js +47 -0
  54. package/src/utils/formatters/string.d.ts +6 -0
  55. package/src/utils/formatters/string.js +114 -0
  56. package/src/utils/generateImportExcel.d.ts +35 -0
  57. package/src/utils/generateImportExcel.js +546 -0
  58. package/src/utils/generateTimestamp.d.ts +1 -0
  59. package/src/utils/generateTimestamp.js +4 -0
  60. package/src/utils/handleApiError.d.ts +1 -0
  61. package/src/utils/handleApiError.js +17 -0
  62. package/src/utils/index.d.ts +104 -0
  63. package/src/utils/index.js +92 -0
  64. package/src/utils/json.d.ts +4 -0
  65. package/src/utils/json.js +55 -0
  66. package/src/utils/localStorage.d.ts +1 -0
  67. package/src/utils/localStorage.js +8 -0
  68. package/src/utils/logger.d.ts +4 -0
  69. package/src/utils/logger.js +46 -0
  70. package/src/utils/schema/globalSchema.d.ts +8 -0
  71. package/src/utils/schema/globalSchema.js +10 -0
  72. package/src/utils/signature.d.ts +10 -0
  73. package/src/utils/signature.js +75 -0
  74. package/src/utils/specifics/korporasi/prakarsaKorporasiUtils.d.ts +5 -0
  75. package/src/utils/specifics/korporasi/prakarsaKorporasiUtils.js +87 -0
  76. package/src/utils/specifics/mikro/validationMaintenanceAgunanUtils.d.ts +2 -0
  77. package/src/utils/specifics/mikro/validationMaintenanceAgunanUtils.js +214 -0
  78. package/src/utils/static-data/korporasi/prakarsa/analisa-risiko-kredit-nasabah/analisa-kinerja-keuangan/listStaticData.js +290 -0
  79. package/src/utils/static-data/korporasi/prakarsa/appendix/proyeksi-laporan-arus-kas/listStaticData.js +7 -0
  80. package/src/utils/static-data/korporasi/prakarsa/appendix/proyeksi-laporan-keuangan/listStaticData.js +75 -0
  81. package/src/utils/static-data/korporasi/prakarsa/appendix/total-exposure-group/listStaticData.js +7 -0
  82. package/src/utils/static-data/korporasi/prakarsa/create/listStaticData.js +21 -0
  83. package/src/utils/static-data/korporasi/prakarsa/generals/listExcelConfigData.js +11 -0
  84. package/src/utils/static-data/korporasi/prakarsa/pengajuan-fasilitas-kredit/fasilitas-kredit/listStaticData.js +69 -0
  85. package/src/utils/static-data/korporasi-internasional/eksternal/listStaticData.js +193 -0
  86. package/src/utils/static-data/korporasi-internasional/master/listStaticData.js +24 -0
  87. package/src/utils/static-data/korporasi-internasional/uji-kepatuhan/listStaticData.js +250 -0
  88. package/src/utils/status.d.ts +37 -0
  89. package/src/utils/status.js +160 -0
  90. package/src/utils/store/adminKuotaNasional.js +7 -0
  91. package/src/utils/store/authStore.js +13 -0
  92. package/src/utils/store/bankGaransiStore.js +10 -0
  93. package/src/utils/store/korporasi/approval/interface/interfaceStore.js +10 -0
  94. package/src/utils/store/korporasi/general/generalKorporasiStore.js +13 -0
  95. package/src/utils/store/korporasi/loan-disbursement/loanDisbursementStore.js +77 -0
  96. package/src/utils/store/korporasi/prakarsa/fasilitas-kredit/fasilitasKreditStore.js +141 -0
  97. package/src/utils/store/migrasi-mandiri/migrasiMandiriStore.js +53 -0
  98. package/src/utils/store/monitoring-konsumer/monitoring-leads-KPP/monitoring-leads-kpp-internal.js +34 -0
  99. package/src/utils/store/prakarsaBankGaransiStore.js +21 -0
  100. package/src/utils/store/prognosa-crr/settingPrognosaROStore.js +9 -0
  101. package/src/utils/store/prognosaRMStore.js +30 -0
  102. package/src/utils/store/restrukStore.js +26 -0
  103. package/src/utils/store/verificationAccessLink.js +11 -0
  104. package/src/utils/store/warkat-bg-wholesale/warkatBgWholesaleStore.js +22 -0
  105. package/src/utils/stringUtils.d.ts +22 -0
  106. package/src/utils/stringUtils.js +226 -0
  107. package/src/utils/tipeBank.d.ts +3 -0
  108. package/src/utils/tipeBank.js +14 -0
  109. package/dist/index.css +0 -32
  110. package/dist/index.css.map +0 -1
  111. package/dist/index.d.mts +0 -1726
  112. package/dist/index.mjs +0 -18560
  113. package/dist/index.mjs.map +0 -1
  114. package/dist/types.d.mts +0 -4
  115. /package/dist/{atoms → components/atoms}/icons/index.d.mts +0 -0
  116. /package/dist/{atoms → components/atoms}/icons/index.mjs.map +0 -0
  117. /package/dist/{mui → components/mui}/index.d.mts +0 -0
  118. /package/dist/{mui → components/mui}/index.mjs.map +0 -0
  119. /package/dist/{types.mjs → components/types.mjs} +0 -0
  120. /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 @@
1
+ export declare function generateVcifId(): string;
@@ -0,0 +1,4 @@
1
+ export function generateVcifId() {
2
+ const timestamp = Date.now();
3
+ return `VCIF${timestamp}`;
4
+ }
@@ -0,0 +1 @@
1
+ export declare function handleApiError(err: unknown, res: unknown): void;
@@ -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,104 @@
1
+ // Formatters - Currency
2
+ export declare function formatCurrency(
3
+ value: number | string | null | undefined,
4
+ options?: {
5
+ locale?: string;
6
+ currency?: string;
7
+ minFractionDigits?: number;
8
+ maxFractionDigits?: number;
9
+ }
10
+ ): string;
11
+ export declare function formatRupiah(value: number | string | null | undefined): string;
12
+ export declare function formatNominal(
13
+ value: number | string | null | undefined,
14
+ options?: {
15
+ locale?: string;
16
+ minFractionDigits?: number;
17
+ maxFractionDigits?: number;
18
+ decimalSeparator?: string;
19
+ }
20
+ ): string;
21
+ export declare function formatDigit(value: number | string): string;
22
+
23
+ // Formatters - Account
24
+ export declare function formatAccountNumber(value: string | null | undefined): string;
25
+ export declare function formatNPWP(value: string | null | undefined): string;
26
+
27
+ // Formatters - Date
28
+ export declare function toYearFirstDash(value: Date | string): string;
29
+ export declare function toDateFirstSlash(value: Date | string): string;
30
+ export declare function formatDateToDDSlashMMSlashYYYY(date: Date): string;
31
+ export declare function formatDateToDDMMYYYY(date: Date): string;
32
+ export declare function yearFirstDashToDateFirstDash(value: string): string;
33
+ export declare function yearFirstDashToDateFirstSlash(value: string): string;
34
+ export declare function dateFirstSlashToYearFirstDash(value: string): string;
35
+ export declare function sevenDigitToDateFirstSlash(value: string): string;
36
+ export declare function sevenDigitToYearFirstDash(value: string): string;
37
+ export declare function sevenDigitToDateFirstDash(value: string): string;
38
+ export declare function toDateTimeHourMinute(value: string): string;
39
+ export declare function ddmmyyToSlashDate(value: string): string;
40
+
41
+ // Formatters - String
42
+ export declare function formatResponseDesc(desc: string): string;
43
+ export declare function titleCase(str: string): string;
44
+ export declare function snakeToCamelWithSpace(snakeStr: string): string;
45
+ export declare function stringToBoolean(str: string): boolean | undefined;
46
+ export declare function matchWiths(targetStr: string, values?: string[]): boolean;
47
+ export declare function maskString(value: string, type?: string): string;
48
+
49
+ // JSON
50
+ export declare function parseJSON(value: string): unknown;
51
+ export declare function jsonToStringLimiter(value?: unknown): string | null;
52
+ export declare function errorState(name: string, errors: unknown): unknown;
53
+ export declare function ascendingStringify(obj: unknown): string;
54
+
55
+ // Asset
56
+ export declare function callAsset(uri?: string): string;
57
+
58
+ // Boolean Utils
59
+ export declare function checkRoleUser(roles: string[], listRole: string[]): boolean;
60
+ export declare function toggleUsingDummyWbenchEndpoint(status: boolean): boolean;
61
+
62
+ // Constants
63
+ export declare const NOT_AUTHORIZE_RESPONSE_CODE: string[];
64
+ export declare const PUBLIC_PATHS: string[];
65
+ export declare const COOKIE_MAX_SIZE: number;
66
+ export declare const CLIENT_PUBLIC_API_ROUTES: string[];
67
+ export declare const CLIENT_ALLOWED_ORIGINS: Array<string | RegExp>;
68
+
69
+ // Disable Form
70
+ export declare function disableFormElements(selector: string): void;
71
+ export declare function disableFormBuilder(
72
+ fields: Array<Record<string, unknown> | null>,
73
+ roleAccess: boolean
74
+ ): Array<Record<string, unknown> | null>;
75
+
76
+ // Generate Timestamp
77
+ export declare function generateVcifId(): string;
78
+
79
+ // LocalStorage
80
+ export declare function getLocalStorageItem(key: string): unknown;
81
+
82
+ // Tipe Bank
83
+ export declare function getTipeBankKorporasi(
84
+ data: Array<{ status_perusahaan: string; tipe_bank: string }>
85
+ ): string;
86
+
87
+ // Exception
88
+ export declare const HEADER_EXCEPTION: string;
89
+ export declare const ACCESS_EXCEPTION: string;
90
+ export declare const PARAMETER_EXCEPTION: string;
91
+ export declare const NOTFOUND_EXCEPTION: string;
92
+ export declare const INVALIDRULE_EXCEPTION: string;
93
+ export declare const THIRDPARTY_EXCEPTION: string;
94
+ export declare const WAITING_EXCEPTION: string;
95
+ export declare const UNSUPPORTED_EXCEPTION: string;
96
+ export declare const RUNTIME_EXCEPTION: string;
97
+ export declare const EXCEPTION_MESSAGES: Record<string, string>;
98
+ export declare class ApiException extends Error {
99
+ responseCode: string;
100
+ responseDesc: string;
101
+ constructor(responseCode: string, responseDesc?: string);
102
+ }
103
+ export declare function isExceptionCode(responseCode: string): boolean;
104
+ export declare function getExceptionMessage(responseCode: string): string;