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.
- package/dist/{chunk-OGJBPTUP.mjs → chunk-F6KSEYVB.mjs} +3 -3
- package/dist/{chunk-OGJBPTUP.mjs.map → chunk-F6KSEYVB.mjs.map} +1 -1
- package/dist/chunk-UFE7HFT2.mjs +60 -0
- package/dist/chunk-UFE7HFT2.mjs.map +1 -0
- package/dist/{chunk-NXFLMH2Q.mjs → chunk-YOSPWY5K.mjs} +3 -4
- package/dist/{chunk-NXFLMH2Q.mjs.map → chunk-YOSPWY5K.mjs.map} +1 -1
- package/dist/{atoms → components/atoms}/icons/index.mjs +2 -2
- package/dist/components/index.d.mts +449 -0
- package/dist/components/index.mjs +7053 -0
- package/dist/components/index.mjs.map +1 -0
- package/dist/{mui → components/mui}/index.mjs +1 -1
- package/dist/components/types.d.mts +4 -0
- package/dist/{types-B0iF5xX8.d.mts → types-D1akE7ak.d.mts} +334 -245
- package/dist/utils/index.d.mts +106 -0
- package/dist/utils/index.mjs +477 -0
- package/dist/utils/index.mjs.map +1 -0
- package/package.json +13 -2
- package/src/utils/admin-kuota-nasional/constant.d.ts +23 -0
- package/src/utils/admin-kuota-nasional/constant.js +95 -0
- package/src/utils/asset.d.ts +1 -0
- package/src/utils/asset.js +10 -0
- package/src/utils/booleanUtils.d.ts +2 -0
- package/src/utils/booleanUtils.js +19 -0
- package/src/utils/briguna-digital/constant.d.ts +2 -0
- package/src/utils/briguna-digital/constant.js +12 -0
- package/src/utils/compress.d.ts +2 -0
- package/src/utils/compress.js +24 -0
- package/src/utils/compressImage.d.ts +6 -0
- package/src/utils/compressImage.js +20 -0
- package/src/utils/constants.d.ts +5 -0
- package/src/utils/constants.js +20 -0
- package/src/utils/cookies.d.ts +19 -0
- package/src/utils/cookies.js +102 -0
- package/src/utils/crypto.d.ts +5 -0
- package/src/utils/crypto.js +104 -0
- package/src/utils/decodeString.d.ts +1 -0
- package/src/utils/decodeString.js +17 -0
- package/src/utils/disableFormElements.d.ts +5 -0
- package/src/utils/disableFormElements.js +19 -0
- package/src/utils/excelUtils.d.ts +25 -0
- package/src/utils/excelUtils.js +60 -0
- package/src/utils/exception.d.ts +19 -0
- package/src/utils/exception.js +86 -0
- package/src/utils/formatString.d.ts +25 -0
- package/src/utils/formatString.js +151 -0
- package/src/utils/formatters/account.d.ts +2 -0
- package/src/utils/formatters/account.js +98 -0
- package/src/utils/formatters/currency.d.ts +20 -0
- package/src/utils/formatters/currency.js +125 -0
- package/src/utils/formatters/date.d.ts +12 -0
- package/src/utils/formatters/date.js +219 -0
- package/src/utils/formatters/index.d.ts +47 -0
- package/src/utils/formatters/index.js +47 -0
- package/src/utils/formatters/string.d.ts +6 -0
- package/src/utils/formatters/string.js +114 -0
- package/src/utils/generateImportExcel.d.ts +35 -0
- package/src/utils/generateImportExcel.js +546 -0
- package/src/utils/generateTimestamp.d.ts +1 -0
- package/src/utils/generateTimestamp.js +4 -0
- package/src/utils/handleApiError.d.ts +1 -0
- package/src/utils/handleApiError.js +17 -0
- package/src/utils/index.d.ts +104 -0
- package/src/utils/index.js +92 -0
- package/src/utils/json.d.ts +4 -0
- package/src/utils/json.js +55 -0
- package/src/utils/localStorage.d.ts +1 -0
- package/src/utils/localStorage.js +8 -0
- package/src/utils/logger.d.ts +4 -0
- package/src/utils/logger.js +46 -0
- package/src/utils/schema/globalSchema.d.ts +8 -0
- package/src/utils/schema/globalSchema.js +10 -0
- package/src/utils/signature.d.ts +10 -0
- package/src/utils/signature.js +75 -0
- package/src/utils/specifics/korporasi/prakarsaKorporasiUtils.d.ts +5 -0
- package/src/utils/specifics/korporasi/prakarsaKorporasiUtils.js +87 -0
- package/src/utils/specifics/mikro/validationMaintenanceAgunanUtils.d.ts +2 -0
- package/src/utils/specifics/mikro/validationMaintenanceAgunanUtils.js +214 -0
- package/src/utils/static-data/korporasi/prakarsa/analisa-risiko-kredit-nasabah/analisa-kinerja-keuangan/listStaticData.js +290 -0
- package/src/utils/static-data/korporasi/prakarsa/appendix/proyeksi-laporan-arus-kas/listStaticData.js +7 -0
- package/src/utils/static-data/korporasi/prakarsa/appendix/proyeksi-laporan-keuangan/listStaticData.js +75 -0
- package/src/utils/static-data/korporasi/prakarsa/appendix/total-exposure-group/listStaticData.js +7 -0
- package/src/utils/static-data/korporasi/prakarsa/create/listStaticData.js +21 -0
- package/src/utils/static-data/korporasi/prakarsa/generals/listExcelConfigData.js +11 -0
- package/src/utils/static-data/korporasi/prakarsa/pengajuan-fasilitas-kredit/fasilitas-kredit/listStaticData.js +69 -0
- package/src/utils/static-data/korporasi-internasional/eksternal/listStaticData.js +193 -0
- package/src/utils/static-data/korporasi-internasional/master/listStaticData.js +24 -0
- package/src/utils/static-data/korporasi-internasional/uji-kepatuhan/listStaticData.js +250 -0
- package/src/utils/status.d.ts +37 -0
- package/src/utils/status.js +160 -0
- package/src/utils/store/adminKuotaNasional.js +7 -0
- package/src/utils/store/authStore.js +13 -0
- package/src/utils/store/bankGaransiStore.js +10 -0
- package/src/utils/store/korporasi/approval/interface/interfaceStore.js +10 -0
- package/src/utils/store/korporasi/general/generalKorporasiStore.js +13 -0
- package/src/utils/store/korporasi/loan-disbursement/loanDisbursementStore.js +77 -0
- package/src/utils/store/korporasi/prakarsa/fasilitas-kredit/fasilitasKreditStore.js +141 -0
- package/src/utils/store/migrasi-mandiri/migrasiMandiriStore.js +53 -0
- package/src/utils/store/monitoring-konsumer/monitoring-leads-KPP/monitoring-leads-kpp-internal.js +34 -0
- package/src/utils/store/prakarsaBankGaransiStore.js +21 -0
- package/src/utils/store/prognosa-crr/settingPrognosaROStore.js +9 -0
- package/src/utils/store/prognosaRMStore.js +30 -0
- package/src/utils/store/restrukStore.js +26 -0
- package/src/utils/store/verificationAccessLink.js +11 -0
- package/src/utils/store/warkat-bg-wholesale/warkatBgWholesaleStore.js +22 -0
- package/src/utils/stringUtils.d.ts +22 -0
- package/src/utils/stringUtils.js +226 -0
- package/src/utils/tipeBank.d.ts +3 -0
- package/src/utils/tipeBank.js +14 -0
- package/dist/index.css +0 -32
- package/dist/index.css.map +0 -1
- package/dist/index.d.mts +0 -1726
- package/dist/index.mjs +0 -18560
- package/dist/index.mjs.map +0 -1
- package/dist/types.d.mts +0 -4
- /package/dist/{atoms → components/atoms}/icons/index.d.mts +0 -0
- /package/dist/{atoms → components/atoms}/icons/index.mjs.map +0 -0
- /package/dist/{mui → components/mui}/index.d.mts +0 -0
- /package/dist/{mui → components/mui}/index.mjs.map +0 -0
- /package/dist/{types.mjs → components/types.mjs} +0 -0
- /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 @@
|
|
|
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;
|