@grafosoft/excel-validator 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +357 -0
- package/dist/components/BalanceTotals.d.ts +5 -0
- package/dist/components/BalanceTotals.js +13 -0
- package/dist/components/ExcelFile.d.ts +2 -0
- package/dist/components/ExcelFile.js +36 -0
- package/dist/components/ExcelLayout.d.ts +12 -0
- package/dist/components/ExcelLayout.js +509 -0
- package/dist/components/ExcelPreview.d.ts +2 -0
- package/dist/components/ExcelPreview.js +68 -0
- package/dist/components/ExcelValidator.d.ts +2 -0
- package/dist/components/ExcelValidator.js +82 -0
- package/dist/components/ThemeSwitcher.d.ts +1 -0
- package/dist/components/ThemeSwitcher.js +17 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/dist/types/SettingsProps.d.ts +7 -0
- package/dist/types/SettingsProps.js +2 -0
- package/dist/types/excelTypes.d.ts +47 -0
- package/dist/types/excelTypes.js +2 -0
- package/dist/utils/balances.d.ts +4 -0
- package/dist/utils/balances.js +20 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
# excel-validator
|
|
2
|
+
|
|
3
|
+
Componente React para validar archivos Excel (`.xlsx` / `.xls`) con reglas declarativas (`jsonValidator`) y catálogos remotos (`fetch` / `string-empty`).
|
|
4
|
+
|
|
5
|
+
## Requisitos
|
|
6
|
+
|
|
7
|
+
- Node.js 20+
|
|
8
|
+
- Next.js 16+
|
|
9
|
+
- React 19+
|
|
10
|
+
- Proyecto con Tailwind CSS (el componente usa clases utility)
|
|
11
|
+
- Proveedor de HeroUI y `next-themes` en el árbol de la app
|
|
12
|
+
|
|
13
|
+
## Instalación
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install excel-validator @heroui/react next-themes xlsx react-icons framer-motion
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Uso básico
|
|
20
|
+
|
|
21
|
+
### 1) Configura providers globales
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
"use client";
|
|
25
|
+
|
|
26
|
+
import { HeroUIProvider } from "@heroui/react";
|
|
27
|
+
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
|
28
|
+
|
|
29
|
+
export function Providers({ children }: { children: React.ReactNode }) {
|
|
30
|
+
return (
|
|
31
|
+
<HeroUIProvider>
|
|
32
|
+
<NextThemesProvider attribute="class" defaultTheme="light">
|
|
33
|
+
{children}
|
|
34
|
+
</NextThemesProvider>
|
|
35
|
+
</HeroUIProvider>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 2) Renderiza `ExcelLayout`
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
"use client";
|
|
44
|
+
|
|
45
|
+
import { ExcelLayout } from "excel-validator";
|
|
46
|
+
import type { SettingsProps } from "excel-validator";
|
|
47
|
+
|
|
48
|
+
export default function Page() {
|
|
49
|
+
const initData: SettingsProps = {
|
|
50
|
+
apikey: "794340c3-4956-4763-b8ab-145da3510100",
|
|
51
|
+
companyId: "1",
|
|
52
|
+
environment: "https://lab.globho.com/",
|
|
53
|
+
title: "Catálogo Cuental",
|
|
54
|
+
type: "items",
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const jsonValidator = [
|
|
58
|
+
"int",
|
|
59
|
+
{
|
|
60
|
+
type: "fetch",
|
|
61
|
+
url: "api/v1/codesystem/item-groups",
|
|
62
|
+
prop: "name",
|
|
63
|
+
},
|
|
64
|
+
"string(255)",
|
|
65
|
+
"$string(20)",
|
|
66
|
+
"$string(20)",
|
|
67
|
+
"decimal",
|
|
68
|
+
"decimal",
|
|
69
|
+
"decimal",
|
|
70
|
+
"string(255)",
|
|
71
|
+
"bool",
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
return <ExcelLayout initData={initData} jsonValidator={jsonValidator} />;
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## API
|
|
79
|
+
|
|
80
|
+
### `SettingsProps`
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
type SettingsProps = {
|
|
84
|
+
apikey: string;
|
|
85
|
+
companyId: string;
|
|
86
|
+
environment: string;
|
|
87
|
+
title: string;
|
|
88
|
+
type: string;
|
|
89
|
+
};
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Props de `ExcelLayout`
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
type ExcelLayoutProps = {
|
|
96
|
+
initData: SettingsProps;
|
|
97
|
+
jsonValidator: (string | { type: string; url: string; prop: string })[];
|
|
98
|
+
isBalances?: boolean;
|
|
99
|
+
};
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Formato de reglas (`jsonValidator`)
|
|
103
|
+
|
|
104
|
+
- `"int"`: valida entero
|
|
105
|
+
- `"decimal"`: valida decimal
|
|
106
|
+
- `"bool"`: valida booleano
|
|
107
|
+
- `"string(n)"`: valida texto con longitud máxima `n`
|
|
108
|
+
- `"string(yyyy-mm-dd)"`: valida formato fecha
|
|
109
|
+
- Regla opcional: prefijo `$`, por ejemplo `"$string(20)"`
|
|
110
|
+
- `{ type: "fetch", url, prop }`: valida contra catálogo remoto
|
|
111
|
+
- `{ type: "string-empty", url, prop, validation }`: permite vacío o valida con consulta remota
|
|
112
|
+
|
|
113
|
+
## Configuraciones de ejemplo (tomadas de `page.tsx`)
|
|
114
|
+
|
|
115
|
+
### PLAN DE CUENTAS
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
const jsonValidator = [
|
|
119
|
+
"int",
|
|
120
|
+
{
|
|
121
|
+
type: "string-empty",
|
|
122
|
+
url: "api/v1/codesystem/accounts",
|
|
123
|
+
prop: "code",
|
|
124
|
+
validation: "string(20)",
|
|
125
|
+
},
|
|
126
|
+
"string(255)",
|
|
127
|
+
"$string(30)",
|
|
128
|
+
"$string(10)",
|
|
129
|
+
"$string(30)",
|
|
130
|
+
"decimal",
|
|
131
|
+
];
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### SALDOS INICIALES
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
const jsonValidator = [
|
|
138
|
+
{
|
|
139
|
+
type: "fetch",
|
|
140
|
+
url: "api/v1/codesystem/accounts",
|
|
141
|
+
prop: "code",
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
type: "fetch",
|
|
145
|
+
url: "api/v1/codesystem/contacts",
|
|
146
|
+
prop: "code",
|
|
147
|
+
},
|
|
148
|
+
"decimal",
|
|
149
|
+
"decimal",
|
|
150
|
+
"$string(255)",
|
|
151
|
+
"$int",
|
|
152
|
+
];
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### CATÁLOGO GLOBHO
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
const jsonValidator = [
|
|
159
|
+
"int",
|
|
160
|
+
"string(255)",
|
|
161
|
+
{
|
|
162
|
+
type: "fetch",
|
|
163
|
+
url: "api/v1/codesystem/item-groups",
|
|
164
|
+
prop: "id",
|
|
165
|
+
},
|
|
166
|
+
"$string(20)",
|
|
167
|
+
"$string(20)",
|
|
168
|
+
"$string(500)",
|
|
169
|
+
"bool",
|
|
170
|
+
"decimal",
|
|
171
|
+
"$string(20)",
|
|
172
|
+
{
|
|
173
|
+
type: "fetch",
|
|
174
|
+
url: "api/v1/valueset/units",
|
|
175
|
+
prop: "code",
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
type: "fetch",
|
|
179
|
+
url: "api/v1/valueset/pharmaceuticalForms",
|
|
180
|
+
prop: "id",
|
|
181
|
+
},
|
|
182
|
+
"decimal",
|
|
183
|
+
"decimal",
|
|
184
|
+
{
|
|
185
|
+
type: "fetch",
|
|
186
|
+
url: "api/v1/valueset/taxes",
|
|
187
|
+
prop: "code",
|
|
188
|
+
},
|
|
189
|
+
"$string(255)",
|
|
190
|
+
"$string(100)",
|
|
191
|
+
"$string(100)",
|
|
192
|
+
"bool",
|
|
193
|
+
"int",
|
|
194
|
+
"$string(20)",
|
|
195
|
+
"decimal",
|
|
196
|
+
"decimal",
|
|
197
|
+
];
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### CATÁLOGO CUENTAL
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
const jsonValidator = [
|
|
204
|
+
"int",
|
|
205
|
+
{
|
|
206
|
+
type: "fetch",
|
|
207
|
+
url: "api/v1/codesystem/item-groups",
|
|
208
|
+
prop: "name",
|
|
209
|
+
},
|
|
210
|
+
"string(255)",
|
|
211
|
+
"$string(20)",
|
|
212
|
+
"$string(20)",
|
|
213
|
+
"decimal",
|
|
214
|
+
"decimal",
|
|
215
|
+
"decimal",
|
|
216
|
+
"string(255)",
|
|
217
|
+
"bool",
|
|
218
|
+
];
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### TERCEROS
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
const jsonValidator = [
|
|
225
|
+
"int",
|
|
226
|
+
{
|
|
227
|
+
type: "fetch",
|
|
228
|
+
url: "api/v1/valueset/contactTypes",
|
|
229
|
+
prop: "id",
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
type: "fetch",
|
|
233
|
+
url: "api/v1/valueset/contactPersons",
|
|
234
|
+
prop: "id",
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
type: "fetch",
|
|
238
|
+
url: "api/v1/valueset/identificationTypes",
|
|
239
|
+
prop: "id",
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
type: "string-empty",
|
|
243
|
+
url: "api/v1/codesystem/contacts",
|
|
244
|
+
prop: "code",
|
|
245
|
+
validation: "string(20)",
|
|
246
|
+
},
|
|
247
|
+
"$string(255)",
|
|
248
|
+
"$string(50)",
|
|
249
|
+
"$string(50)",
|
|
250
|
+
"$string(50)",
|
|
251
|
+
"$string(50)",
|
|
252
|
+
"$string(100)",
|
|
253
|
+
"$string(100)",
|
|
254
|
+
"$string(100)",
|
|
255
|
+
"$string(500)",
|
|
256
|
+
{
|
|
257
|
+
type: "fetch",
|
|
258
|
+
url: "api/v1/valueset/cities",
|
|
259
|
+
prop: "code",
|
|
260
|
+
},
|
|
261
|
+
"$string(100)",
|
|
262
|
+
{
|
|
263
|
+
type: "fetch",
|
|
264
|
+
url: "api/v1/valueset/contactRegimes",
|
|
265
|
+
prop: "id",
|
|
266
|
+
},
|
|
267
|
+
"$string(100)",
|
|
268
|
+
"$string(100)",
|
|
269
|
+
"bool",
|
|
270
|
+
"int",
|
|
271
|
+
"$string(100)",
|
|
272
|
+
"$string(100)",
|
|
273
|
+
"$string(100)",
|
|
274
|
+
"$string(yyyy-mm-dd)",
|
|
275
|
+
"$string(100)",
|
|
276
|
+
{
|
|
277
|
+
type: "fetch",
|
|
278
|
+
url: "api/v1/valueset/activities",
|
|
279
|
+
prop: "id",
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
type: "fetch",
|
|
283
|
+
url: "api/v1/valueset/contactResponsibilities",
|
|
284
|
+
prop: "id",
|
|
285
|
+
},
|
|
286
|
+
"$string(6)",
|
|
287
|
+
"int",
|
|
288
|
+
{
|
|
289
|
+
type: "fetch",
|
|
290
|
+
url: "api/v1/valueset/taxTypes",
|
|
291
|
+
prop: "id",
|
|
292
|
+
},
|
|
293
|
+
];
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### PACIENTES
|
|
297
|
+
|
|
298
|
+
```ts
|
|
299
|
+
const jsonValidator = [
|
|
300
|
+
"int",
|
|
301
|
+
"string(20)",
|
|
302
|
+
{
|
|
303
|
+
type: "fetch",
|
|
304
|
+
url: "api/v1/valueset/identificationTypes",
|
|
305
|
+
prop: "id",
|
|
306
|
+
},
|
|
307
|
+
"string(50)",
|
|
308
|
+
"$string(50)",
|
|
309
|
+
"string(50)",
|
|
310
|
+
"$string(50)",
|
|
311
|
+
"string(yyyy-mm-dd)",
|
|
312
|
+
{
|
|
313
|
+
type: "fetch",
|
|
314
|
+
url: "api/v1/valueset/genderGroups",
|
|
315
|
+
prop: "id",
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
type: "fetch",
|
|
319
|
+
url: "api/v1/valueset/genderIdentities",
|
|
320
|
+
prop: "id",
|
|
321
|
+
},
|
|
322
|
+
"$string(100)",
|
|
323
|
+
"$string(100)",
|
|
324
|
+
"$string(100)",
|
|
325
|
+
{
|
|
326
|
+
type: "fetch",
|
|
327
|
+
url: "api/v1/valueset/cities",
|
|
328
|
+
prop: "code",
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
type: "fetch",
|
|
332
|
+
url: "api/v1/valueset/patientRegimes",
|
|
333
|
+
prop: "code",
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
type: "fetch",
|
|
337
|
+
url: "api/v1/valueset/patientStatus",
|
|
338
|
+
prop: "id",
|
|
339
|
+
},
|
|
340
|
+
];
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## Reglas de ejecución para `initData`
|
|
344
|
+
|
|
345
|
+
- `environment` debe ser una URL base válida (ejemplo: `https://lab.globho.com/`).
|
|
346
|
+
- El componente normaliza automáticamente la barra final `/`.
|
|
347
|
+
- Las reglas `fetch` y `string-empty` construyen URL final con:
|
|
348
|
+
- `apikey`
|
|
349
|
+
- `companyId`
|
|
350
|
+
|
|
351
|
+
## Publicación del paquete
|
|
352
|
+
|
|
353
|
+
```bash
|
|
354
|
+
npm run build:lib
|
|
355
|
+
npm pack --dry-run
|
|
356
|
+
npm publish --access public
|
|
357
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BalanceTotals = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const balances_1 = require("../utils/balances");
|
|
6
|
+
const tb_1 = require("react-icons/tb");
|
|
7
|
+
const BalanceTotals = ({ headers, rows, }) => {
|
|
8
|
+
const totalDebit = (0, balances_1.sumColumn)(rows, headers, "ValorDebito");
|
|
9
|
+
const totalCredit = (0, balances_1.sumColumn)(rows, headers, "ValorCredito");
|
|
10
|
+
const difference = Math.abs(totalDebit - totalCredit);
|
|
11
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "mt-4 flex flex-col items-end gap-2 pt-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-sm text-default-500 font-medium uppercase tracking-wide text-right", children: "Total D\u00E9bito:" }), (0, jsx_runtime_1.jsxs)("span", { className: "text-xl font-semibold text-default-700 w-55 text-right", children: ["COP ", (0, balances_1.formatNumber)(totalDebit)] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3 border-b border-default-200 pb-3", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-sm text-default-500 font-medium uppercase tracking-wide text-right", children: "Total Cr\u00E9dito:" }), (0, jsx_runtime_1.jsxs)("span", { className: "text-xl font-semibold text-default-700 w-55 text-right", children: ["COP ", (0, balances_1.formatNumber)(totalCredit), " "] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3 pt-1", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-sm text-default-500 font-medium uppercase tracking-wide text-right", children: "Diferencia:" }), (0, jsx_runtime_1.jsxs)("div", { className: `flex justify-end gap-2 items-center text-xl font-semibold w-55 text-right ${difference === 0 ? "text-success-600" : "text-danger-600"}`, children: [difference !== 0 && (0, jsx_runtime_1.jsx)(tb_1.TbAlertCircle, { color: "red", size: 20 }), " COP", " ", (0, balances_1.formatNumber)(difference)] })] })] }));
|
|
12
|
+
};
|
|
13
|
+
exports.BalanceTotals = BalanceTotals;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ExcelFile = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("@heroui/react");
|
|
6
|
+
const react_2 = require("react");
|
|
7
|
+
const tb_1 = require("react-icons/tb");
|
|
8
|
+
const ExcelValidator_1 = require("./ExcelValidator");
|
|
9
|
+
const ExcelFile = ({ initData, fileContent, matrix, error, onFileSelected, onReset, validationErrors, onSelectError, hasBalanceDifference = false, }) => {
|
|
10
|
+
var _a;
|
|
11
|
+
const [isVisible, setIsVisible] = (0, react_2.useState)(false);
|
|
12
|
+
const inputRef = (0, react_2.useRef)(null);
|
|
13
|
+
const handleReset = () => {
|
|
14
|
+
setIsVisible(false);
|
|
15
|
+
onReset();
|
|
16
|
+
if (inputRef.current) {
|
|
17
|
+
inputRef.current.value = "";
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
const bytesFormat = (bytes) => {
|
|
21
|
+
if (typeof bytes !== "number" || bytes < 0) {
|
|
22
|
+
throw new Error("Debes pasar un número entero positivo");
|
|
23
|
+
}
|
|
24
|
+
const KB = 1024;
|
|
25
|
+
const MB = KB * 1024;
|
|
26
|
+
if (bytes >= MB) {
|
|
27
|
+
return (bytes / MB).toFixed(2) + " MB";
|
|
28
|
+
}
|
|
29
|
+
return (bytes / KB).toFixed(2) + " KB";
|
|
30
|
+
};
|
|
31
|
+
return ((0, jsx_runtime_1.jsxs)("section", { className: "h-full bg-content1 p-4 border-r border-default-200", children: [(0, jsx_runtime_1.jsxs)("h2", { className: "text-xl font-semibold text-default-700", children: ["Importa ", initData.title] }), error ? ((0, jsx_runtime_1.jsx)(react_1.Alert, { color: "danger", title: error, className: "mt-3", radius: "sm", hideIconWrapper: true })) : isVisible ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(react_1.Alert, { radius: "sm", hideIconWrapper: true, hideIcon: true, isVisible: isVisible, className: "mt-4", variant: "bordered", onClose: handleReset, children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2 text-default-700", children: [(0, jsx_runtime_1.jsx)(tb_1.TbFile, { size: 28 }), " ", (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-sm", children: fileContent === null || fileContent === void 0 ? void 0 : fileContent.name }), (0, jsx_runtime_1.jsx)("small", { className: "text-default-500", children: bytesFormat((_a = fileContent === null || fileContent === void 0 ? void 0 : fileContent.size) !== null && _a !== void 0 ? _a : 0) })] })] }) }), (0, jsx_runtime_1.jsx)(ExcelValidator_1.ExcelValidator, { initData: initData, fileContent: fileContent, matrix: matrix, validationErrors: validationErrors, onSelectError: onSelectError, hasBalanceDifference: hasBalanceDifference })] })) : ((0, jsx_runtime_1.jsx)("label", { className: "mt-4 flex cursor-pointer items-center justify-center rounded-lg border border-dashed border-default-300 px-4 py-8 text-sm font-medium text-default-600 transition hover:border-primary hover:text-primary", htmlFor: "excel-upload", children: (0, jsx_runtime_1.jsxs)("div", { className: "text-center flex flex-col items-center gap-2", children: [(0, jsx_runtime_1.jsx)(tb_1.TbUpload, { size: 28 }), (0, jsx_runtime_1.jsx)("p", { className: "font-semibold text-lg", children: "Seleccionar plantilla" }), (0, jsx_runtime_1.jsx)("small", { className: "text-default-500", children: "Adjunta un archivo .xlsx o .xls para validar y previsualizar." })] }) })), (0, jsx_runtime_1.jsx)("input", { id: "excel-upload", ref: inputRef, className: "hidden", type: "file", accept: ".xlsx,.xls", onChange: (event) => {
|
|
32
|
+
void onFileSelected(event);
|
|
33
|
+
setIsVisible(true);
|
|
34
|
+
} })] }));
|
|
35
|
+
};
|
|
36
|
+
exports.ExcelFile = ExcelFile;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { SettingsProps } from "../types/SettingsProps";
|
|
2
|
+
type ExcelLayoutProps = {
|
|
3
|
+
initData: SettingsProps;
|
|
4
|
+
jsonValidator: (string | {
|
|
5
|
+
type: string;
|
|
6
|
+
url: string;
|
|
7
|
+
prop: string;
|
|
8
|
+
})[];
|
|
9
|
+
isBalances?: boolean;
|
|
10
|
+
};
|
|
11
|
+
export declare function ExcelLayout({ initData, jsonValidator, isBalances, }: ExcelLayoutProps): import("react").JSX.Element;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.ExcelLayout = ExcelLayout;
|
|
38
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
39
|
+
const react_1 = require("react");
|
|
40
|
+
const XLSX = __importStar(require("xlsx"));
|
|
41
|
+
const ExcelFile_1 = require("./ExcelFile");
|
|
42
|
+
const ExcelPreview_1 = require("./ExcelPreview");
|
|
43
|
+
const ThemeSwitcher_1 = require("./ThemeSwitcher");
|
|
44
|
+
const balances_1 = require("../utils/balances");
|
|
45
|
+
const updateJsonValidatorFetchUrls = (validator, initData) => {
|
|
46
|
+
const environment = initData.environment.endsWith("/")
|
|
47
|
+
? initData.environment
|
|
48
|
+
: `${initData.environment}/`;
|
|
49
|
+
return validator.map((item) => {
|
|
50
|
+
if (typeof item === "object") {
|
|
51
|
+
const normalizedType = item.type.trim().replace(/^\$/, "").toLowerCase();
|
|
52
|
+
if (normalizedType !== "fetch" && normalizedType !== "string-empty") {
|
|
53
|
+
return item;
|
|
54
|
+
}
|
|
55
|
+
const cleanUrl = item.url.startsWith("/") ? item.url.slice(1) : item.url;
|
|
56
|
+
return Object.assign(Object.assign({}, item), { url: `${environment}${cleanUrl}?apikey=${initData.apikey}&companyId=${initData.companyId}` });
|
|
57
|
+
}
|
|
58
|
+
return item;
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
function ExcelLayout({ initData, jsonValidator, isBalances = false, }) {
|
|
62
|
+
const resolvedJsonValidator = (0, react_1.useMemo)(() => updateJsonValidatorFetchUrls(jsonValidator, initData), [jsonValidator, initData]);
|
|
63
|
+
const isSkippedRule = (item) => {
|
|
64
|
+
if (typeof item === "string") {
|
|
65
|
+
const normalizedRule = item.trim().replace(/^\$/, "").toLowerCase();
|
|
66
|
+
if (normalizedRule === "int" ||
|
|
67
|
+
normalizedRule === "decimal" ||
|
|
68
|
+
normalizedRule === "bool") {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
if (/^string\([^()]+\)$/.test(normalizedRule)) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
return item.trim().startsWith("$");
|
|
75
|
+
}
|
|
76
|
+
const normalizedType = item.type.trim().replace(/^\$/, "").toLowerCase();
|
|
77
|
+
if (normalizedType === "fetch" || normalizedType === "string-empty") {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
return item.type.trim().startsWith("$");
|
|
81
|
+
};
|
|
82
|
+
const rowsPerPage = 30;
|
|
83
|
+
const [fileContent, setFileContent] = (0, react_1.useState)(undefined);
|
|
84
|
+
const [matrix, setMatrix] = (0, react_1.useState)([]);
|
|
85
|
+
const [error, setError] = (0, react_1.useState)(null);
|
|
86
|
+
const [currentPage, setCurrentPage] = (0, react_1.useState)(1);
|
|
87
|
+
const [focusTarget, setFocusTarget] = (0, react_1.useState)(null);
|
|
88
|
+
const [fetchCatalogsByUrl, setFetchCatalogsByUrl] = (0, react_1.useState)({});
|
|
89
|
+
const [stringEmptyResultsByQuery, setStringEmptyResultsByQuery] = (0, react_1.useState)({});
|
|
90
|
+
(0, react_1.useEffect)(() => {
|
|
91
|
+
let isCancelled = false;
|
|
92
|
+
const fetchRules = resolvedJsonValidator.filter((item) => typeof item === "object" &&
|
|
93
|
+
!isSkippedRule(item) &&
|
|
94
|
+
item.type.trim().replace(/^\$/, "").toLowerCase() === "fetch");
|
|
95
|
+
const uniqueFetchUrls = Array.from(new Set(fetchRules.map((rule) => rule.url)));
|
|
96
|
+
if (uniqueFetchUrls.length === 0) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const loadCatalogs = async () => {
|
|
100
|
+
const entries = await Promise.all(uniqueFetchUrls.map(async (url) => {
|
|
101
|
+
try {
|
|
102
|
+
const response = await fetch(url);
|
|
103
|
+
if (!response.ok) {
|
|
104
|
+
return [url, []];
|
|
105
|
+
}
|
|
106
|
+
const data = (await response.json());
|
|
107
|
+
const catalog = Array.isArray(data)
|
|
108
|
+
? data
|
|
109
|
+
: [];
|
|
110
|
+
return [url, catalog];
|
|
111
|
+
}
|
|
112
|
+
catch (_a) {
|
|
113
|
+
return [url, []];
|
|
114
|
+
}
|
|
115
|
+
}));
|
|
116
|
+
if (!isCancelled) {
|
|
117
|
+
setFetchCatalogsByUrl(Object.fromEntries(entries));
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
void loadCatalogs();
|
|
121
|
+
return () => {
|
|
122
|
+
isCancelled = true;
|
|
123
|
+
};
|
|
124
|
+
}, [resolvedJsonValidator]);
|
|
125
|
+
(0, react_1.useEffect)(() => {
|
|
126
|
+
let isCancelled = false;
|
|
127
|
+
const stringEmptyRules = resolvedJsonValidator
|
|
128
|
+
.map((item, index) => ({ item, index }))
|
|
129
|
+
.filter((entry) => typeof entry.item === "object" &&
|
|
130
|
+
entry.item.type.trim().replace(/^\$/, "").toLowerCase() ===
|
|
131
|
+
"string-empty");
|
|
132
|
+
if (stringEmptyRules.length === 0 || matrix.length <= 1) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const filteredRows = matrix
|
|
136
|
+
.slice(1)
|
|
137
|
+
.filter((row) => { var _a, _b; return ((_b = (_a = row[0]) === null || _a === void 0 ? void 0 : _a.toString().trim()) !== null && _b !== void 0 ? _b : "") !== ""; });
|
|
138
|
+
const queryUrls = new Set();
|
|
139
|
+
stringEmptyRules.forEach(({ item, index }) => {
|
|
140
|
+
filteredRows.forEach((row) => {
|
|
141
|
+
var _a;
|
|
142
|
+
const cellValue = String((_a = row[index]) !== null && _a !== void 0 ? _a : "").trim();
|
|
143
|
+
if (cellValue !== "") {
|
|
144
|
+
queryUrls.add(`${item.url}&${encodeURIComponent(item.prop)}=${encodeURIComponent(cellValue)}`);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
if (queryUrls.size === 0) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const loadStringEmptyResults = async () => {
|
|
152
|
+
const entries = await Promise.all([...queryUrls].map(async (queryUrl) => {
|
|
153
|
+
try {
|
|
154
|
+
const response = await fetch(queryUrl);
|
|
155
|
+
if (!response.ok) {
|
|
156
|
+
return [queryUrl, []];
|
|
157
|
+
}
|
|
158
|
+
const data = (await response.json());
|
|
159
|
+
const results = Array.isArray(data)
|
|
160
|
+
? data
|
|
161
|
+
: [];
|
|
162
|
+
return [queryUrl, results];
|
|
163
|
+
}
|
|
164
|
+
catch (_a) {
|
|
165
|
+
return [queryUrl, []];
|
|
166
|
+
}
|
|
167
|
+
}));
|
|
168
|
+
if (!isCancelled) {
|
|
169
|
+
setStringEmptyResultsByQuery((prev) => (Object.assign(Object.assign({}, prev), Object.fromEntries(entries))));
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
void loadStringEmptyResults();
|
|
173
|
+
return () => {
|
|
174
|
+
isCancelled = true;
|
|
175
|
+
};
|
|
176
|
+
}, [resolvedJsonValidator, matrix]);
|
|
177
|
+
const validationErrors = (0, react_1.useMemo)(() => {
|
|
178
|
+
if (matrix.length <= 1 || resolvedJsonValidator.length === 0) {
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
const parseRule = (rawRule) => rawRule.trim().toLowerCase();
|
|
182
|
+
const escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
183
|
+
const buildDatePattern = (format) => {
|
|
184
|
+
const tokenRegex = /(yyyy|yy|mm|dd|m|d)/g;
|
|
185
|
+
const tokens = [...format.matchAll(tokenRegex)];
|
|
186
|
+
if (tokens.length === 0) {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
let pattern = "^";
|
|
190
|
+
let cursor = 0;
|
|
191
|
+
const tokenOrder = [];
|
|
192
|
+
tokens.forEach((tokenMatch) => {
|
|
193
|
+
var _a;
|
|
194
|
+
const token = tokenMatch[0];
|
|
195
|
+
const tokenStart = (_a = tokenMatch.index) !== null && _a !== void 0 ? _a : 0;
|
|
196
|
+
const literal = format.slice(cursor, tokenStart);
|
|
197
|
+
pattern += escapeRegExp(literal);
|
|
198
|
+
if (token === "yyyy") {
|
|
199
|
+
pattern += "(\\d{4})";
|
|
200
|
+
}
|
|
201
|
+
else if (token === "yy") {
|
|
202
|
+
pattern += "(\\d{2})";
|
|
203
|
+
}
|
|
204
|
+
else if (token === "mm") {
|
|
205
|
+
pattern += "(0[1-9]|1[0-2])";
|
|
206
|
+
}
|
|
207
|
+
else if (token === "m") {
|
|
208
|
+
pattern += "([1-9]|1[0-2])";
|
|
209
|
+
}
|
|
210
|
+
else if (token === "dd") {
|
|
211
|
+
pattern += "(0[1-9]|[12]\\d|3[01])";
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
pattern += "([1-9]|[12]\\d|3[01])";
|
|
215
|
+
}
|
|
216
|
+
tokenOrder.push(token);
|
|
217
|
+
cursor = tokenStart + token.length;
|
|
218
|
+
});
|
|
219
|
+
pattern += `${escapeRegExp(format.slice(cursor))}$`;
|
|
220
|
+
return {
|
|
221
|
+
tokenOrder,
|
|
222
|
+
regex: new RegExp(pattern),
|
|
223
|
+
};
|
|
224
|
+
};
|
|
225
|
+
const validateDateByFormat = (value, format) => {
|
|
226
|
+
const pattern = buildDatePattern(format);
|
|
227
|
+
if (!pattern) {
|
|
228
|
+
return {
|
|
229
|
+
valid: false,
|
|
230
|
+
message: `Formato de fecha no soportado: ${format}`,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
const matched = value.match(pattern.regex);
|
|
234
|
+
if (!matched) {
|
|
235
|
+
return {
|
|
236
|
+
valid: false,
|
|
237
|
+
message: `Debe cumplir el formato de fecha ${format}`,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
let day = null;
|
|
241
|
+
let month = null;
|
|
242
|
+
let year = null;
|
|
243
|
+
pattern.tokenOrder.forEach((token, index) => {
|
|
244
|
+
const currentValue = matched[index + 1];
|
|
245
|
+
if (!currentValue) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
if (token === "dd" || token === "d") {
|
|
249
|
+
day = Number(currentValue);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (token === "mm" || token === "m") {
|
|
253
|
+
month = Number(currentValue);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (token === "yyyy") {
|
|
257
|
+
year = Number(currentValue);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
year = 2000 + Number(currentValue);
|
|
261
|
+
});
|
|
262
|
+
if (day === null || month === null || year === null) {
|
|
263
|
+
return {
|
|
264
|
+
valid: false,
|
|
265
|
+
message: `El formato de fecha ${format} debe incluir día, mes y año`,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
const candidate = new Date(year, month - 1, day);
|
|
269
|
+
const isValidDate = candidate.getFullYear() === year &&
|
|
270
|
+
candidate.getMonth() === month - 1 &&
|
|
271
|
+
candidate.getDate() === day;
|
|
272
|
+
return {
|
|
273
|
+
valid: isValidDate,
|
|
274
|
+
message: isValidDate ? "" : "La fecha no es válida",
|
|
275
|
+
};
|
|
276
|
+
};
|
|
277
|
+
const validateByRule = (value, rawRule) => {
|
|
278
|
+
const isOptional = rawRule.trim().startsWith("$");
|
|
279
|
+
const rule = parseRule(rawRule.replace(/^\$/, ""));
|
|
280
|
+
const trimmedValue = value.trim();
|
|
281
|
+
if (rule === "int") {
|
|
282
|
+
if (trimmedValue === "") {
|
|
283
|
+
return isOptional
|
|
284
|
+
? { valid: true, message: "" }
|
|
285
|
+
: { valid: false, message: "Valor vacío" };
|
|
286
|
+
}
|
|
287
|
+
const isValid = /^-?\d+$/.test(trimmedValue);
|
|
288
|
+
return {
|
|
289
|
+
valid: isValid,
|
|
290
|
+
message: isValid ? "" : "Debe ser un número entero",
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
if (rule === "decimal") {
|
|
294
|
+
if (trimmedValue === "") {
|
|
295
|
+
return isOptional
|
|
296
|
+
? { valid: true, message: "" }
|
|
297
|
+
: { valid: false, message: "Valor vacío" };
|
|
298
|
+
}
|
|
299
|
+
if (trimmedValue.includes(",")) {
|
|
300
|
+
return {
|
|
301
|
+
valid: false,
|
|
302
|
+
message: "Debes quitar la coma (,) para que sea un número decimal válido",
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
const isValid = /^-?\d+(\.\d+)?$/.test(trimmedValue);
|
|
306
|
+
return {
|
|
307
|
+
valid: isValid,
|
|
308
|
+
message: isValid ? "" : "Debe ser un número decimal",
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
if (rule === "bool") {
|
|
312
|
+
if (trimmedValue === "") {
|
|
313
|
+
return isOptional
|
|
314
|
+
? { valid: true, message: "" }
|
|
315
|
+
: { valid: false, message: "Valor vacío" };
|
|
316
|
+
}
|
|
317
|
+
const isValid = trimmedValue === "SI" || trimmedValue === "NO";
|
|
318
|
+
return {
|
|
319
|
+
valid: isValid,
|
|
320
|
+
message: isValid ? "" : "Debe ser SI o NO",
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
const stringRuleMatch = rule.match(/^string\(([^()]+)\)$/);
|
|
324
|
+
if (stringRuleMatch) {
|
|
325
|
+
if (trimmedValue === "") {
|
|
326
|
+
return isOptional
|
|
327
|
+
? { valid: true, message: "" }
|
|
328
|
+
: { valid: false, message: "Valor vacío" };
|
|
329
|
+
}
|
|
330
|
+
const stringRuleArg = stringRuleMatch[1].trim();
|
|
331
|
+
if (/^\d+$/.test(stringRuleArg)) {
|
|
332
|
+
const maxLength = Number(stringRuleArg);
|
|
333
|
+
const isWithinLength = trimmedValue.length <= maxLength;
|
|
334
|
+
return {
|
|
335
|
+
valid: isWithinLength,
|
|
336
|
+
message: isWithinLength
|
|
337
|
+
? ""
|
|
338
|
+
: `Longitud máxima permitida: ${maxLength} caracteres`,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
return validateDateByFormat(trimmedValue, stringRuleArg);
|
|
342
|
+
}
|
|
343
|
+
return { valid: true, message: "" };
|
|
344
|
+
};
|
|
345
|
+
const filteredRows = matrix
|
|
346
|
+
.slice(1)
|
|
347
|
+
.filter((row) => { var _a, _b; return ((_b = (_a = row[0]) === null || _a === void 0 ? void 0 : _a.toString().trim()) !== null && _b !== void 0 ? _b : "") !== ""; });
|
|
348
|
+
const detectedErrors = [];
|
|
349
|
+
filteredRows.forEach((row, filteredRowIndex) => {
|
|
350
|
+
resolvedJsonValidator.forEach((rule, columnIndex) => {
|
|
351
|
+
var _a, _b, _c;
|
|
352
|
+
if (isSkippedRule(rule)) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
const value = String((_a = row[columnIndex]) !== null && _a !== void 0 ? _a : "");
|
|
356
|
+
const result = (() => {
|
|
357
|
+
var _a;
|
|
358
|
+
if (typeof rule === "string") {
|
|
359
|
+
return validateByRule(value, rule);
|
|
360
|
+
}
|
|
361
|
+
const normalizedType = rule.type
|
|
362
|
+
.trim()
|
|
363
|
+
.replace(/^\$/, "")
|
|
364
|
+
.toLowerCase();
|
|
365
|
+
const isOptional = rule.type.trim().startsWith("$");
|
|
366
|
+
if (normalizedType === "fetch") {
|
|
367
|
+
const trimmedValue = value.trim();
|
|
368
|
+
if (trimmedValue === "") {
|
|
369
|
+
return isOptional
|
|
370
|
+
? { valid: true, message: "" }
|
|
371
|
+
: { valid: false, message: "Valor vacío" };
|
|
372
|
+
}
|
|
373
|
+
const catalog = (_a = fetchCatalogsByUrl[rule.url]) !== null && _a !== void 0 ? _a : [];
|
|
374
|
+
const hasMatch = catalog.some((catalogItem) => {
|
|
375
|
+
const propValue = catalogItem[rule.prop];
|
|
376
|
+
return String(propValue !== null && propValue !== void 0 ? propValue : "").trim() === trimmedValue;
|
|
377
|
+
});
|
|
378
|
+
return {
|
|
379
|
+
valid: hasMatch,
|
|
380
|
+
message: hasMatch
|
|
381
|
+
? ""
|
|
382
|
+
: `El valor '${trimmedValue}' no existe en base de datos`,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
if (normalizedType === "string-empty") {
|
|
386
|
+
const trimmedValue = value.trim();
|
|
387
|
+
if (trimmedValue === "") {
|
|
388
|
+
return isOptional
|
|
389
|
+
? { valid: true, message: "" }
|
|
390
|
+
: { valid: false, message: "Valor vacío" };
|
|
391
|
+
}
|
|
392
|
+
if (rule.validation) {
|
|
393
|
+
const validationResult = validateByRule(trimmedValue, rule.validation);
|
|
394
|
+
if (!validationResult.valid) {
|
|
395
|
+
return validationResult;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
const queryKey = `${rule.url}&${encodeURIComponent(rule.prop)}=${encodeURIComponent(trimmedValue)}`;
|
|
399
|
+
const queryResult = stringEmptyResultsByQuery[queryKey];
|
|
400
|
+
if (queryResult === undefined) {
|
|
401
|
+
return { valid: true, message: "" };
|
|
402
|
+
}
|
|
403
|
+
const alreadyExists = queryResult.length > 0;
|
|
404
|
+
return {
|
|
405
|
+
valid: !alreadyExists,
|
|
406
|
+
message: alreadyExists
|
|
407
|
+
? `El valor '${trimmedValue}' ya existe`
|
|
408
|
+
: "",
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
return { valid: true, message: "" };
|
|
412
|
+
})();
|
|
413
|
+
if (!result.valid) {
|
|
414
|
+
const headers = (_b = matrix[0]) !== null && _b !== void 0 ? _b : [];
|
|
415
|
+
const columnName = ((_c = headers[columnIndex]) === null || _c === void 0 ? void 0 : _c.trim()) || `Columna ${columnIndex + 1}`;
|
|
416
|
+
detectedErrors.push({
|
|
417
|
+
id: `${filteredRowIndex}-${columnIndex}`,
|
|
418
|
+
row: filteredRowIndex + 1,
|
|
419
|
+
column: columnIndex + 1,
|
|
420
|
+
columnName,
|
|
421
|
+
message: result.message,
|
|
422
|
+
value,
|
|
423
|
+
filteredRowIndex,
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
return detectedErrors;
|
|
429
|
+
}, [
|
|
430
|
+
matrix,
|
|
431
|
+
resolvedJsonValidator,
|
|
432
|
+
fetchCatalogsByUrl,
|
|
433
|
+
stringEmptyResultsByQuery,
|
|
434
|
+
]);
|
|
435
|
+
const hasBalanceDifference = (0, react_1.useMemo)(() => {
|
|
436
|
+
var _a;
|
|
437
|
+
if (!isBalances || matrix.length <= 1) {
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
const headers = (_a = matrix[0]) !== null && _a !== void 0 ? _a : [];
|
|
441
|
+
const dataRows = matrix
|
|
442
|
+
.slice(1)
|
|
443
|
+
.filter((row) => { var _a, _b; return ((_b = (_a = row[0]) === null || _a === void 0 ? void 0 : _a.toString().trim()) !== null && _b !== void 0 ? _b : "") !== ""; });
|
|
444
|
+
const totalDebit = (0, balances_1.sumColumn)(dataRows, headers, "ValorDebito");
|
|
445
|
+
const totalCredit = (0, balances_1.sumColumn)(dataRows, headers, "ValorCredito");
|
|
446
|
+
return Math.abs(totalDebit - totalCredit) !== 0;
|
|
447
|
+
}, [isBalances, matrix]);
|
|
448
|
+
const handleFileSelected = async (event) => {
|
|
449
|
+
var _a;
|
|
450
|
+
const file = (_a = event.target.files) === null || _a === void 0 ? void 0 : _a[0];
|
|
451
|
+
if (!file) {
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
try {
|
|
455
|
+
setError(null);
|
|
456
|
+
setCurrentPage(1);
|
|
457
|
+
setFocusTarget(null);
|
|
458
|
+
const fileBuffer = await file.arrayBuffer();
|
|
459
|
+
const workbook = XLSX.read(fileBuffer, { type: "array" });
|
|
460
|
+
const firstSheetName = workbook.SheetNames[0];
|
|
461
|
+
if (!firstSheetName) {
|
|
462
|
+
setFileContent(file);
|
|
463
|
+
setMatrix([]);
|
|
464
|
+
setError("El archivo no contiene hojas para procesar.");
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
const worksheet = workbook.Sheets[firstSheetName];
|
|
468
|
+
const rawRows = XLSX.utils.sheet_to_json(worksheet, {
|
|
469
|
+
header: 1,
|
|
470
|
+
raw: false,
|
|
471
|
+
defval: "",
|
|
472
|
+
});
|
|
473
|
+
const normalizedRows = rawRows.map((row) => row.map((cell) => String(cell !== null && cell !== void 0 ? cell : "")));
|
|
474
|
+
const recordsCount = Math.max(normalizedRows.length - 1, 0);
|
|
475
|
+
if (recordsCount > 3000) {
|
|
476
|
+
setFileContent(file);
|
|
477
|
+
setMatrix([]);
|
|
478
|
+
setError("El archivo supera el máximo permitido de 3000 registros.");
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
setFileContent(file);
|
|
482
|
+
setMatrix(normalizedRows);
|
|
483
|
+
}
|
|
484
|
+
catch (_b) {
|
|
485
|
+
setFileContent(undefined);
|
|
486
|
+
setMatrix([]);
|
|
487
|
+
setCurrentPage(1);
|
|
488
|
+
setFocusTarget(null);
|
|
489
|
+
setError("No fue posible leer el archivo. Verifica que sea un Excel válido.");
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
const handleSelectError = (selectedError) => {
|
|
493
|
+
const targetPage = Math.floor(selectedError.filteredRowIndex / rowsPerPage) + 1;
|
|
494
|
+
setCurrentPage(targetPage);
|
|
495
|
+
setFocusTarget({
|
|
496
|
+
filteredRowIndex: selectedError.filteredRowIndex,
|
|
497
|
+
columnIndex: selectedError.column - 1,
|
|
498
|
+
token: Date.now(),
|
|
499
|
+
});
|
|
500
|
+
};
|
|
501
|
+
const handleReset = () => {
|
|
502
|
+
setFileContent(undefined);
|
|
503
|
+
setMatrix([]);
|
|
504
|
+
setError(null);
|
|
505
|
+
setCurrentPage(1);
|
|
506
|
+
setFocusTarget(null);
|
|
507
|
+
};
|
|
508
|
+
return ((0, jsx_runtime_1.jsxs)("main", { className: "grid min-h-screen grid-cols-1 md:grid-cols-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "space-y-4 md:col-span-1", children: [(0, jsx_runtime_1.jsx)(ThemeSwitcher_1.ThemeSwitcher, {}), (0, jsx_runtime_1.jsx)(ExcelFile_1.ExcelFile, { initData: initData, fileContent: fileContent, matrix: matrix, error: error, onFileSelected: handleFileSelected, onReset: handleReset, validationErrors: validationErrors, onSelectError: handleSelectError, hasBalanceDifference: hasBalanceDifference })] }), (0, jsx_runtime_1.jsx)("div", { className: "md:col-span-3", children: (0, jsx_runtime_1.jsx)(ExcelPreview_1.ExcelPreview, { initData: initData, fileContent: fileContent, matrix: matrix, error: error, validationErrors: validationErrors, focusTarget: focusTarget, currentPage: currentPage, onPageChange: setCurrentPage, isBalances: isBalances }) })] }));
|
|
509
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ExcelPreview = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("@heroui/react");
|
|
6
|
+
const react_2 = require("react");
|
|
7
|
+
const pi_1 = require("react-icons/pi");
|
|
8
|
+
const tb_1 = require("react-icons/tb");
|
|
9
|
+
const BalanceTotals_1 = require("./BalanceTotals");
|
|
10
|
+
const ExcelPreview = ({ initData, fileContent, matrix, error, validationErrors, focusTarget, currentPage, onPageChange, isBalances = false, }) => {
|
|
11
|
+
var _a;
|
|
12
|
+
const rowsPerPage = 30;
|
|
13
|
+
const cellRefs = (0, react_2.useRef)({});
|
|
14
|
+
const maxColumns = (0, react_2.useMemo)(() => matrix.reduce((max, row) => Math.max(max, row.length), 0), [matrix]);
|
|
15
|
+
const tableHeaders = (_a = matrix[0]) !== null && _a !== void 0 ? _a : [];
|
|
16
|
+
const allDataRows = matrix
|
|
17
|
+
.slice(1)
|
|
18
|
+
.filter((row) => { var _a, _b; return ((_b = (_a = row[0]) === null || _a === void 0 ? void 0 : _a.toString().trim()) !== null && _b !== void 0 ? _b : "") !== ""; });
|
|
19
|
+
const totalRecords = allDataRows.length;
|
|
20
|
+
const totalPages = Math.max(1, Math.ceil(totalRecords / rowsPerPage));
|
|
21
|
+
const effectivePage = Math.min(currentPage, totalPages);
|
|
22
|
+
const startIndex = (effectivePage - 1) * rowsPerPage;
|
|
23
|
+
const endIndex = startIndex + rowsPerPage;
|
|
24
|
+
const dataRows = allDataRows.slice(startIndex, endIndex);
|
|
25
|
+
const errorMap = (0, react_2.useMemo)(() => {
|
|
26
|
+
const map = new Set();
|
|
27
|
+
validationErrors.forEach((item) => {
|
|
28
|
+
map.add(`${item.filteredRowIndex}-${item.column - 1}`);
|
|
29
|
+
});
|
|
30
|
+
return map;
|
|
31
|
+
}, [validationErrors]);
|
|
32
|
+
(0, react_2.useEffect)(() => {
|
|
33
|
+
if (!focusTarget) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const targetPage = Math.floor(focusTarget.filteredRowIndex / rowsPerPage) + 1;
|
|
37
|
+
if (targetPage !== effectivePage) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const cellKey = `${focusTarget.filteredRowIndex}-${focusTarget.columnIndex}`;
|
|
41
|
+
const targetCell = cellRefs.current[cellKey];
|
|
42
|
+
if (!targetCell) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
requestAnimationFrame(() => {
|
|
46
|
+
targetCell.scrollIntoView({
|
|
47
|
+
behavior: "smooth",
|
|
48
|
+
block: "center",
|
|
49
|
+
inline: "center",
|
|
50
|
+
});
|
|
51
|
+
targetCell.focus();
|
|
52
|
+
});
|
|
53
|
+
}, [focusTarget, effectivePage]);
|
|
54
|
+
return ((0, jsx_runtime_1.jsxs)("section", { className: "h-full bg-gray-50 dark:bg-content1 border-l border-default-200 px-4 py-2", children: [!fileContent ? ((0, jsx_runtime_1.jsx)(react_1.Alert, { color: "primary", title: `Al importar ${initData.title} verás una previsualización de su contenido.`, className: "my-3", radius: "sm", hideIconWrapper: true, variant: "faded" })) : null, fileContent && matrix.length === 0 && !error ? ((0, jsx_runtime_1.jsx)(react_1.Alert, { color: "danger", title: `No se encontraron datos en el archivo seleccionado.`, className: "my-3", radius: "sm", hideIconWrapper: true, variant: "faded" })) : null, fileContent && matrix.length > 0 ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: "mt-4 max-h-[80vh] max-w-full overflow-x-auto overflow-y-auto ", children: (0, jsx_runtime_1.jsxs)("table", { className: "min-w-max w-full text-sm", children: [(0, jsx_runtime_1.jsx)("thead", { className: "sticky top-0 bg-default-100", children: (0, jsx_runtime_1.jsx)("tr", { children: Array.from({ length: maxColumns }).map((_, columnIndex) => ((0, jsx_runtime_1.jsx)("th", { className: "border-x border-t border-default-200 p-3 font-semibold text-default-700", children: tableHeaders[columnIndex] ||
|
|
55
|
+
`Columna ${columnIndex + 1}` }, `header-${columnIndex}`))) }) }), (0, jsx_runtime_1.jsx)("tbody", { className: "bg-content1", children: dataRows.map((row, rowIndex) => ((0, jsx_runtime_1.jsx)("tr", { children: Array.from({ length: maxColumns }).map((_, columnIndex) => (() => {
|
|
56
|
+
var _a, _b;
|
|
57
|
+
const absoluteRowIndex = startIndex + rowIndex;
|
|
58
|
+
const key = `${absoluteRowIndex}-${columnIndex}`;
|
|
59
|
+
const hasError = errorMap.has(key);
|
|
60
|
+
const isFocusedTarget = (focusTarget === null || focusTarget === void 0 ? void 0 : focusTarget.filteredRowIndex) === absoluteRowIndex &&
|
|
61
|
+
(focusTarget === null || focusTarget === void 0 ? void 0 : focusTarget.columnIndex) === columnIndex;
|
|
62
|
+
return ((0, jsx_runtime_1.jsx)("td", { ref: (element) => {
|
|
63
|
+
cellRefs.current[key] = element;
|
|
64
|
+
}, tabIndex: -1, className: `border border-default-200 p-3 align-top whitespace-nowrap outline-none transition-all duration-200 ease-in-out text-default-700
|
|
65
|
+
${isFocusedTarget ? "ring-[1.7px] ring-danger ring-inset" : ""}`, children: hasError ? ((0, jsx_runtime_1.jsxs)("div", { className: "flex justify-between", children: [(0, jsx_runtime_1.jsx)("p", { children: (_a = row[columnIndex]) !== null && _a !== void 0 ? _a : "" }), (0, jsx_runtime_1.jsx)(tb_1.TbAlertCircle, { color: "red", size: 20 })] })) : ((0, jsx_runtime_1.jsx)("p", { children: (_b = row[columnIndex]) !== null && _b !== void 0 ? _b : "" })) }, `cell-${rowIndex}-${columnIndex}`));
|
|
66
|
+
})()) }, `row-${rowIndex}`))) })] }) }), totalRecords > rowsPerPage ? ((0, jsx_runtime_1.jsxs)("div", { className: "mt-4 flex items-center justify-between gap-3", children: [(0, jsx_runtime_1.jsxs)("p", { className: "text-sm text-default-600", children: ["Mostrando ", startIndex + 1, "-", Math.min(endIndex, totalRecords), " de", " ", totalRecords, " registros"] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)(react_1.Button, { size: "sm", className: "text-sm border border-default-300 disabled:border-default-200 disabled:bg-default-100 disabled:text-default-500", variant: "bordered", disabled: effectivePage === 1, onPress: () => onPageChange(Math.max(1, effectivePage - 1)), isIconOnly: true, children: (0, jsx_runtime_1.jsx)(pi_1.PiCaretLeftBold, { size: 16 }) }), (0, jsx_runtime_1.jsxs)("span", { className: "text-sm text-default-700 mx-1", children: ["Pagina ", effectivePage, " de ", totalPages] }), (0, jsx_runtime_1.jsx)(react_1.Button, { size: "sm", className: "text-sm border border-default-300 disabled:border-default-200 disabled:bg-default-100 disabled:text-default-500", variant: "bordered", disabled: effectivePage === totalPages, onPress: () => onPageChange(Math.min(totalPages, effectivePage + 1)), isIconOnly: true, children: (0, jsx_runtime_1.jsx)(pi_1.PiCaretRightBold, { size: 16 }) })] })] })) : null, isBalances ? ((0, jsx_runtime_1.jsx)(BalanceTotals_1.BalanceTotals, { headers: tableHeaders, rows: allDataRows })) : null] })) : null] }));
|
|
67
|
+
};
|
|
68
|
+
exports.ExcelPreview = ExcelPreview;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ExcelValidator = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("@heroui/react");
|
|
6
|
+
const react_2 = require("react");
|
|
7
|
+
const tb_1 = require("react-icons/tb");
|
|
8
|
+
const ExcelValidator = ({ initData, fileContent, matrix, validationErrors, onSelectError, hasBalanceDifference = false, }) => {
|
|
9
|
+
const [selectedErrorId, setSelectedErrorId] = (0, react_2.useState)(null);
|
|
10
|
+
const [isUploadingTemplate, setIsUploadingTemplate] = (0, react_2.useState)(false);
|
|
11
|
+
const [uploadMessage, setUploadMessage] = (0, react_2.useState)(null);
|
|
12
|
+
if (!fileContent) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
const handleTXTFormat = async () => {
|
|
16
|
+
setIsUploadingTemplate(true);
|
|
17
|
+
setUploadMessage(null);
|
|
18
|
+
const dataRows = matrix
|
|
19
|
+
.slice(1)
|
|
20
|
+
.filter((row) => { var _a, _b; return ((_b = (_a = row[0]) === null || _a === void 0 ? void 0 : _a.toString().trim()) !== null && _b !== void 0 ? _b : "") !== ""; });
|
|
21
|
+
const maxColumns = matrix.reduce((max, row) => Math.max(max, row.length), 0);
|
|
22
|
+
const lines = dataRows.map((row) => {
|
|
23
|
+
const padded = Array.from({ length: maxColumns }, (_, i) => row[i] !== undefined && row[i] !== null ? String(row[i]) : "");
|
|
24
|
+
return padded.join("|");
|
|
25
|
+
});
|
|
26
|
+
const content = lines.join("\n");
|
|
27
|
+
const base64 = btoa(String.fromCharCode(...new TextEncoder().encode(content)));
|
|
28
|
+
try {
|
|
29
|
+
const response = await fetch(`http://lab.globho.com/api/v1/plains/upload?companyId=&apikey=`, {
|
|
30
|
+
method: "POST",
|
|
31
|
+
headers: {
|
|
32
|
+
"Content-Type": "application/json",
|
|
33
|
+
},
|
|
34
|
+
body: JSON.stringify({
|
|
35
|
+
content: base64,
|
|
36
|
+
extension: "txt",
|
|
37
|
+
name: `Plantilla ${initData.title}`,
|
|
38
|
+
type: initData.type,
|
|
39
|
+
}),
|
|
40
|
+
});
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
throw new Error("No se pudo enviar la plantilla");
|
|
43
|
+
}
|
|
44
|
+
setUploadMessage({
|
|
45
|
+
type: "success",
|
|
46
|
+
text: "La plantilla se envió correctamente.",
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
catch (_a) {
|
|
50
|
+
setUploadMessage({
|
|
51
|
+
type: "error",
|
|
52
|
+
text: "Ocurrió un error al enviar la plantilla.",
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
setIsUploadingTemplate(false);
|
|
57
|
+
setTimeout(() => {
|
|
58
|
+
setUploadMessage(null);
|
|
59
|
+
}, 3000);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
return ((0, jsx_runtime_1.jsxs)("section", { className: "mt-10 bg-content1 max-h-[calc(100vh-11.5rem)] overflow-y-auto pr-1", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex mb-3 justify-between items-center", children: [(0, jsx_runtime_1.jsx)("h2", { className: "text-xl font-semibold text-default-700", children: "Validaciones" }), (0, jsx_runtime_1.jsx)(react_1.Chip, { color: validationErrors.length > 0 ? "danger" : "success", variant: "solid", className: "text-white", startContent: validationErrors.length > 0 ? ((0, jsx_runtime_1.jsx)(tb_1.TbAlertCircle, { size: 20 })) : ((0, jsx_runtime_1.jsx)(tb_1.TbCheck, { size: 20 })), children: (0, jsx_runtime_1.jsxs)("p", { className: "pl-1", children: [validationErrors.length, " ", validationErrors.length === 1
|
|
63
|
+
? "error encontrado"
|
|
64
|
+
: "errores encontrados"] }) })] }), hasBalanceDifference ? ((0, jsx_runtime_1.jsx)(react_1.Alert, { color: "danger", title: "Hay diferencias en los saldos iniciales", variant: "faded", hideIcon: true, startContent: (0, jsx_runtime_1.jsx)(tb_1.TbAlertCircle, { size: 30 }), className: "mb-3" })) : null, validationErrors.length === 0 && !hasBalanceDifference ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(react_1.Alert, { color: "success", title: "Sin errores de validaci\u00F3n.", variant: "faded", hideIcon: true, startContent: (0, jsx_runtime_1.jsx)(tb_1.TbCircleCheck, { size: 30 }) }), (0, jsx_runtime_1.jsx)(react_1.Button, { color: "success", className: "mt-4 w-full", variant: "flat", endContent: (0, jsx_runtime_1.jsx)(tb_1.TbUpload, { size: 20 }), radius: "sm", isLoading: isUploadingTemplate, onPress: handleTXTFormat, children: "Enviar Plantilla" }), uploadMessage ? ((0, jsx_runtime_1.jsx)("p", { className: `mt-2 text-sm ${uploadMessage.type === "success"
|
|
65
|
+
? "text-success-600"
|
|
66
|
+
: "text-danger-600"}`, children: uploadMessage.text })) : null] })) : ((0, jsx_runtime_1.jsx)(react_1.Accordion, { selectionMode: "multiple", showDivider: false, children: Object.entries(validationErrors.reduce((groups, error) => {
|
|
67
|
+
const key = error.columnName;
|
|
68
|
+
if (!groups[key])
|
|
69
|
+
groups[key] = [];
|
|
70
|
+
groups[key].push(error);
|
|
71
|
+
return groups;
|
|
72
|
+
}, {})).map(([columnName, errors]) => ((0, jsx_runtime_1.jsx)(react_1.AccordionItem, { textValue: columnName, title: (0, jsx_runtime_1.jsxs)("div", { className: "flex justify-between items-center", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-default-700", children: `Columna ${columnName}` }), (0, jsx_runtime_1.jsx)(react_1.Chip, { color: "danger", variant: "faded", size: "sm", startContent: (0, jsx_runtime_1.jsx)(tb_1.TbAlertCircle, { size: 18 }), children: (0, jsx_runtime_1.jsx)("p", { className: "p-1 text-default-600", children: errors.length }) })] }), children: errors.map((error) => ((0, jsx_runtime_1.jsx)("button", { className: "w-full text-left mt-2", onClick: () => {
|
|
73
|
+
setSelectedErrorId(error.id);
|
|
74
|
+
onSelectError(error);
|
|
75
|
+
}, children: (0, jsx_runtime_1.jsx)(react_1.Alert, { color: "danger", title: error.message, hideIcon: true, startContent: (0, jsx_runtime_1.jsx)(tb_1.TbAlertCircle, { size: 30 }), classNames: {
|
|
76
|
+
title: `font-semibold text-default-700 ${selectedErrorId === error.id ? "text-red-600" : ""}`,
|
|
77
|
+
description: "text-default-500",
|
|
78
|
+
}, description: `Fila #${error.row}`, variant: "bordered", radius: "sm", className: `border-1 border-default-300 cursor-pointer transition-all duration-200 ease-in-out hover:scale-[0.98] ${selectedErrorId === error.id
|
|
79
|
+
? "bg-red-50 border-red-300"
|
|
80
|
+
: " data-[state=open]:bg-red-50"}` }) }, error.id))) }, columnName))) }))] }));
|
|
81
|
+
};
|
|
82
|
+
exports.ExcelValidator = ExcelValidator;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function ThemeSwitcher(): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.ThemeSwitcher = ThemeSwitcher;
|
|
5
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
|
+
const next_themes_1 = require("next-themes");
|
|
7
|
+
const react_1 = require("react");
|
|
8
|
+
function ThemeSwitcher() {
|
|
9
|
+
const [mounted, setMounted] = (0, react_1.useState)(false);
|
|
10
|
+
const { theme, setTheme } = (0, next_themes_1.useTheme)();
|
|
11
|
+
(0, react_1.useEffect)(() => {
|
|
12
|
+
setMounted(true);
|
|
13
|
+
}, []);
|
|
14
|
+
if (!mounted)
|
|
15
|
+
return null;
|
|
16
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "bg-content1 dark:text-white", children: ["The current theme is: ", theme, (0, jsx_runtime_1.jsx)("button", { onClick: () => setTheme("light"), children: "Light Mode" }), (0, jsx_runtime_1.jsx)("button", { onClick: () => setTheme("dark"), children: "Dark Mode" })] }));
|
|
17
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ExcelLayout = void 0;
|
|
4
|
+
var ExcelLayout_1 = require("./components/ExcelLayout");
|
|
5
|
+
Object.defineProperty(exports, "ExcelLayout", { enumerable: true, get: function () { return ExcelLayout_1.ExcelLayout; } });
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ChangeEvent } from "react";
|
|
2
|
+
import { SettingsProps } from "./SettingsProps";
|
|
3
|
+
export type ExcelMatrix = string[][];
|
|
4
|
+
export type ValidationError = {
|
|
5
|
+
id: string;
|
|
6
|
+
row: number;
|
|
7
|
+
column: number;
|
|
8
|
+
columnName: string;
|
|
9
|
+
message: string;
|
|
10
|
+
value: string;
|
|
11
|
+
filteredRowIndex: number;
|
|
12
|
+
};
|
|
13
|
+
export type FocusTarget = {
|
|
14
|
+
filteredRowIndex: number;
|
|
15
|
+
columnIndex: number;
|
|
16
|
+
token: number;
|
|
17
|
+
} | null;
|
|
18
|
+
export type ExcelFileProps = {
|
|
19
|
+
initData: SettingsProps;
|
|
20
|
+
fileContent: File | undefined;
|
|
21
|
+
matrix: ExcelMatrix;
|
|
22
|
+
error: string | null;
|
|
23
|
+
onFileSelected: (event: ChangeEvent<HTMLInputElement>) => Promise<void>;
|
|
24
|
+
onReset: () => void;
|
|
25
|
+
validationErrors: ValidationError[];
|
|
26
|
+
onSelectError: (error: ValidationError) => void;
|
|
27
|
+
hasBalanceDifference?: boolean;
|
|
28
|
+
};
|
|
29
|
+
export type ExcelValidatorProps = {
|
|
30
|
+
initData: SettingsProps;
|
|
31
|
+
fileContent: File | undefined;
|
|
32
|
+
matrix: ExcelMatrix;
|
|
33
|
+
validationErrors: ValidationError[];
|
|
34
|
+
onSelectError: (error: ValidationError) => void;
|
|
35
|
+
hasBalanceDifference?: boolean;
|
|
36
|
+
};
|
|
37
|
+
export type ExcelPreviewProps = {
|
|
38
|
+
initData: SettingsProps;
|
|
39
|
+
fileContent: File | undefined;
|
|
40
|
+
matrix: ExcelMatrix;
|
|
41
|
+
error: string | null;
|
|
42
|
+
validationErrors: ValidationError[];
|
|
43
|
+
focusTarget: FocusTarget;
|
|
44
|
+
currentPage: number;
|
|
45
|
+
onPageChange: (page: number) => void;
|
|
46
|
+
isBalances?: boolean;
|
|
47
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { ExcelMatrix } from "../types/excelTypes";
|
|
2
|
+
export declare const parseDecimal: (value: string) => number;
|
|
3
|
+
export declare const sumColumn: (rows: ExcelMatrix, headers: string[], colName: string) => number;
|
|
4
|
+
export declare const formatNumber: (n: number) => string;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatNumber = exports.sumColumn = exports.parseDecimal = void 0;
|
|
4
|
+
const parseDecimal = (value) => {
|
|
5
|
+
const n = parseFloat(value.trim().replace(",", "."));
|
|
6
|
+
return isNaN(n) ? 0 : n;
|
|
7
|
+
};
|
|
8
|
+
exports.parseDecimal = parseDecimal;
|
|
9
|
+
const sumColumn = (rows, headers, colName) => {
|
|
10
|
+
const idx = headers.findIndex((h) => h.trim().toLowerCase() === colName.toLowerCase());
|
|
11
|
+
if (idx === -1)
|
|
12
|
+
return 0;
|
|
13
|
+
return rows.reduce((acc, row) => { var _a; return acc + (0, exports.parseDecimal)((_a = row[idx]) !== null && _a !== void 0 ? _a : ""); }, 0);
|
|
14
|
+
};
|
|
15
|
+
exports.sumColumn = sumColumn;
|
|
16
|
+
const formatNumber = (n) => n.toLocaleString("es-CO", {
|
|
17
|
+
minimumFractionDigits: 2,
|
|
18
|
+
maximumFractionDigits: 2,
|
|
19
|
+
});
|
|
20
|
+
exports.formatNumber = formatNumber;
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@grafosoft/excel-validator",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "React component to validate Excel files against configurable JSON rules.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"private": false,
|
|
19
|
+
"scripts": {
|
|
20
|
+
"clean": "rm -rf dist",
|
|
21
|
+
"dev": "next dev",
|
|
22
|
+
"start": "next start",
|
|
23
|
+
"build:app": "next build",
|
|
24
|
+
"watch": "tsc --watch",
|
|
25
|
+
"build": "npm run build:lib",
|
|
26
|
+
"build:lib": "npm run clean && tsc -p tsconfig.build.json",
|
|
27
|
+
"lint": "eslint",
|
|
28
|
+
"prepublishOnly": "npm run build:lib"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@heroui/react": "2.8.10",
|
|
32
|
+
"framer-motion": "12.38.0",
|
|
33
|
+
"next": "16.2.6",
|
|
34
|
+
"next-themes": "^0.4.6",
|
|
35
|
+
"react": "19.2.4",
|
|
36
|
+
"react-dom": "19.2.4",
|
|
37
|
+
"react-icons": "5.0.1",
|
|
38
|
+
"xlsx": "^0.18.5"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"next": ">=16",
|
|
42
|
+
"react": ">=19",
|
|
43
|
+
"react-dom": ">=19"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@tailwindcss/postcss": "^4",
|
|
47
|
+
"@types/node": "^20",
|
|
48
|
+
"@types/react": "^19",
|
|
49
|
+
"@types/react-dom": "^19",
|
|
50
|
+
"eslint": "^9",
|
|
51
|
+
"eslint-config-next": "16.2.6",
|
|
52
|
+
"tailwindcss": "^4",
|
|
53
|
+
"typescript": "^5"
|
|
54
|
+
}
|
|
55
|
+
}
|