next-recomponents 2.0.29 → 2.0.31
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/index.d.mts +1 -20
- package/dist/index.d.ts +1 -20
- package/dist/index.js +174 -247
- package/dist/index.mjs +181 -254
- package/package.json +1 -1
- package/src/table/index.tsx +228 -293
package/src/table/index.tsx
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { DataGrid, GridRowModel, GridValidRowModel } from "@mui/x-data-grid";
|
|
4
4
|
import { Box, Dialog } from "@mui/material";
|
|
5
|
-
import React, { useEffect, useMemo,
|
|
5
|
+
import React, { useEffect, useMemo, useState } from "react";
|
|
6
6
|
import useExcel from "../use-excel";
|
|
7
7
|
import Button from "../button";
|
|
8
8
|
import regularExpresions from "../regular-expresions";
|
|
@@ -26,7 +26,6 @@ type GridDensity = "compact" | "standard" | "comfortable";
|
|
|
26
26
|
|
|
27
27
|
interface TableProps {
|
|
28
28
|
data: any;
|
|
29
|
-
/** Muestra un input de búsqueda general que filtra filas por palabras clave */
|
|
30
29
|
searchable?: boolean;
|
|
31
30
|
flex?: number;
|
|
32
31
|
editableFields?: string[];
|
|
@@ -48,28 +47,10 @@ interface TableProps {
|
|
|
48
47
|
className?: string;
|
|
49
48
|
fontSize?: string;
|
|
50
49
|
colSize?: Record<string, number>;
|
|
51
|
-
/**
|
|
52
|
-
* Alto fijo de fila en px.
|
|
53
|
-
* Si se omite usa el default de MUI según `density`
|
|
54
|
-
* (compact ≈ 36, standard ≈ 52, comfortable ≈ 68).
|
|
55
|
-
* Se ignora automáticamente cuando `wrapText` es true.
|
|
56
|
-
*/
|
|
57
50
|
rowHeight?: number;
|
|
58
|
-
/**
|
|
59
|
-
* Cuando es true el texto largo hace wrap en lugar de truncarse,
|
|
60
|
-
* y el alto de cada fila crece para mostrar todo el contenido.
|
|
61
|
-
* Internamente activa `getRowHeight={() => "auto"}` en el DataGrid.
|
|
62
|
-
*/
|
|
63
51
|
wrapText?: boolean;
|
|
64
|
-
/**
|
|
65
|
-
* Densidad visual de la tabla.
|
|
66
|
-
* Afecta el padding de celdas y encabezados independientemente
|
|
67
|
-
* del tamaño de fuente.
|
|
68
|
-
* - "compact" → padding mínimo, tabla muy densa
|
|
69
|
-
* - "standard" → (default) comportamiento por defecto de MUI
|
|
70
|
-
* - "comfortable" → padding generoso, fácil de leer
|
|
71
|
-
*/
|
|
72
52
|
density?: GridDensity;
|
|
53
|
+
autoHeight?: boolean;
|
|
73
54
|
[key: string]: any;
|
|
74
55
|
}
|
|
75
56
|
|
|
@@ -106,7 +87,7 @@ function EditIcon() {
|
|
|
106
87
|
);
|
|
107
88
|
}
|
|
108
89
|
|
|
109
|
-
// ─── KeyValueTable
|
|
90
|
+
// ─── KeyValueTable ────────────────────────────────────────────────────────────
|
|
110
91
|
|
|
111
92
|
function KeyValueTable({ data }: { data: Record<string, any> }) {
|
|
112
93
|
return (
|
|
@@ -131,7 +112,7 @@ function KeyValueTable({ data }: { data: Record<string, any> }) {
|
|
|
131
112
|
);
|
|
132
113
|
}
|
|
133
114
|
|
|
134
|
-
// ───
|
|
115
|
+
// ─── Footer ───────────────────────────────────────────────────────────────────
|
|
135
116
|
|
|
136
117
|
const FOOTER_LABELS: Record<FooterAggregation, string> = {
|
|
137
118
|
sum: "Suma",
|
|
@@ -152,6 +133,7 @@ function computeAggregation(
|
|
|
152
133
|
return values.length
|
|
153
134
|
? values.reduce((acc, v) => acc + v, 0) / values.length
|
|
154
135
|
: 0;
|
|
136
|
+
|
|
155
137
|
return 0;
|
|
156
138
|
}
|
|
157
139
|
|
|
@@ -163,21 +145,19 @@ function CustomFooter({
|
|
|
163
145
|
footer: FooterType;
|
|
164
146
|
}) {
|
|
165
147
|
const entries = Object.entries(footer);
|
|
148
|
+
|
|
166
149
|
if (!entries.length) return null;
|
|
167
150
|
|
|
168
151
|
return (
|
|
169
152
|
<div className="flex justify-end gap-6 px-4 py-2 bg-gray-100 border-t border-gray-300 text-sm font-semibold text-gray-700">
|
|
170
153
|
{entries.map(([key, type]) => {
|
|
171
154
|
const value = computeAggregation(rows, key, type);
|
|
172
|
-
|
|
173
|
-
minimumFractionDigits: value % 1 !== 0 ? 2 : 0,
|
|
174
|
-
maximumFractionDigits: value % 1 !== 0 ? 2 : 0,
|
|
175
|
-
});
|
|
155
|
+
|
|
176
156
|
return (
|
|
177
157
|
<span key={key}>
|
|
178
158
|
{FOOTER_LABELS[type]} de{" "}
|
|
179
159
|
<span className="text-gray-900">{key}</span>:{" "}
|
|
180
|
-
<span className="text-blue-700">{
|
|
160
|
+
<span className="text-blue-700">{value.toLocaleString()}</span>
|
|
181
161
|
</span>
|
|
182
162
|
);
|
|
183
163
|
})}
|
|
@@ -185,31 +165,66 @@ function CustomFooter({
|
|
|
185
165
|
);
|
|
186
166
|
}
|
|
187
167
|
|
|
188
|
-
// ───
|
|
168
|
+
// ─── Modal ────────────────────────────────────────────────────────────────────
|
|
189
169
|
|
|
190
170
|
interface ModalDialogProps {
|
|
191
171
|
open: boolean;
|
|
192
172
|
onClose: () => void;
|
|
193
173
|
modal: React.ReactNode;
|
|
194
174
|
selectedRow: GridValidRowModel | undefined;
|
|
175
|
+
onPrev: () => void;
|
|
176
|
+
onNext: () => void;
|
|
177
|
+
hasPrev: boolean;
|
|
178
|
+
hasNext: boolean;
|
|
195
179
|
}
|
|
196
180
|
|
|
197
|
-
function ModalDialog({
|
|
181
|
+
function ModalDialog({
|
|
182
|
+
open,
|
|
183
|
+
onClose,
|
|
184
|
+
modal,
|
|
185
|
+
selectedRow,
|
|
186
|
+
onPrev,
|
|
187
|
+
onNext,
|
|
188
|
+
hasPrev,
|
|
189
|
+
hasNext,
|
|
190
|
+
}: ModalDialogProps) {
|
|
198
191
|
return (
|
|
199
192
|
<Dialog open={open} maxWidth="xl" fullWidth>
|
|
200
|
-
<div className="flex justify-
|
|
193
|
+
<div className="flex items-center justify-between p-4 border-b">
|
|
194
|
+
<div className="flex gap-2">
|
|
195
|
+
<button
|
|
196
|
+
disabled={!hasPrev}
|
|
197
|
+
onClick={onPrev}
|
|
198
|
+
className="px-4 py-2 rounded border bg-gray-100 hover:bg-gray-200 disabled:opacity-40"
|
|
199
|
+
>
|
|
200
|
+
← Anterior
|
|
201
|
+
</button>
|
|
202
|
+
|
|
203
|
+
<button
|
|
204
|
+
disabled={!hasNext}
|
|
205
|
+
onClick={onNext}
|
|
206
|
+
className="px-4 py-2 rounded border bg-gray-100 hover:bg-gray-200 disabled:opacity-40"
|
|
207
|
+
>
|
|
208
|
+
Siguiente →
|
|
209
|
+
</button>
|
|
210
|
+
</div>
|
|
211
|
+
|
|
201
212
|
<button
|
|
202
213
|
onClick={onClose}
|
|
203
|
-
className="
|
|
214
|
+
className="font-bold w-[35px] h-[35px] flex items-center justify-center hover:animate-pulse border shadow rounded bg-red-500 text-white"
|
|
204
215
|
>
|
|
205
216
|
×
|
|
206
217
|
</button>
|
|
207
218
|
</div>
|
|
219
|
+
|
|
208
220
|
<div className="mt-4 m-auto p-5">
|
|
209
221
|
{selectedRow &&
|
|
210
222
|
React.cloneElement(
|
|
211
223
|
modal as React.ReactElement,
|
|
212
|
-
{
|
|
224
|
+
{
|
|
225
|
+
row: selectedRow,
|
|
226
|
+
hide: onClose,
|
|
227
|
+
} as any,
|
|
213
228
|
)}
|
|
214
229
|
</div>
|
|
215
230
|
</Dialog>
|
|
@@ -218,21 +233,7 @@ function ModalDialog({ open, onClose, modal, selectedRow }: ModalDialogProps) {
|
|
|
218
233
|
|
|
219
234
|
// ─── Toolbar ──────────────────────────────────────────────────────────────────
|
|
220
235
|
|
|
221
|
-
|
|
222
|
-
exportName?: string;
|
|
223
|
-
onSave?: (data: GridValidRowModel[]) => void;
|
|
224
|
-
onSelect?: (data: GridValidRowModel[]) => void;
|
|
225
|
-
rows: GridValidRowModel[];
|
|
226
|
-
filteredRows: GridValidRowModel[];
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function Toolbar({
|
|
230
|
-
exportName,
|
|
231
|
-
onSave,
|
|
232
|
-
onSelect,
|
|
233
|
-
rows,
|
|
234
|
-
filteredRows,
|
|
235
|
-
}: ToolbarProps) {
|
|
236
|
+
function Toolbar({ exportName, onSave, onSelect, rows, filteredRows }: any) {
|
|
236
237
|
const excel = useExcel();
|
|
237
238
|
|
|
238
239
|
const stripMeta = (r: GridValidRowModel) => {
|
|
@@ -243,7 +244,7 @@ function Toolbar({
|
|
|
243
244
|
if (!exportName && !onSave && !onSelect) return null;
|
|
244
245
|
|
|
245
246
|
return (
|
|
246
|
-
<div className="flex gap-2
|
|
247
|
+
<div className="flex gap-2 p-2 text-xs">
|
|
247
248
|
{exportName && (
|
|
248
249
|
<Button
|
|
249
250
|
className="bg-green-800 text-white"
|
|
@@ -254,6 +255,7 @@ function Toolbar({
|
|
|
254
255
|
Exportar
|
|
255
256
|
</Button>
|
|
256
257
|
)}
|
|
258
|
+
|
|
257
259
|
{onSelect ? (
|
|
258
260
|
<Button
|
|
259
261
|
disabled={filteredRows.length === 0}
|
|
@@ -271,158 +273,7 @@ function Toolbar({
|
|
|
271
273
|
);
|
|
272
274
|
}
|
|
273
275
|
|
|
274
|
-
// ───
|
|
275
|
-
|
|
276
|
-
function useColumns(
|
|
277
|
-
rows: GridValidRowModel[],
|
|
278
|
-
currentCoin: string,
|
|
279
|
-
options: {
|
|
280
|
-
flex: number;
|
|
281
|
-
editableFields?: string[];
|
|
282
|
-
buttons?: Record<string, any>;
|
|
283
|
-
hideColumns: string[];
|
|
284
|
-
modal?: React.ReactNode;
|
|
285
|
-
wrapText?: boolean;
|
|
286
|
-
handleRowUpdate: (row: GridRowModel) => GridRowModel;
|
|
287
|
-
onModalOpen: (row: GridValidRowModel) => void;
|
|
288
|
-
},
|
|
289
|
-
colSize?: Record<string, number>,
|
|
290
|
-
) {
|
|
291
|
-
const {
|
|
292
|
-
flex,
|
|
293
|
-
editableFields,
|
|
294
|
-
buttons,
|
|
295
|
-
hideColumns,
|
|
296
|
-
modal,
|
|
297
|
-
wrapText,
|
|
298
|
-
handleRowUpdate,
|
|
299
|
-
onModalOpen,
|
|
300
|
-
} = options;
|
|
301
|
-
|
|
302
|
-
return useMemo(() => {
|
|
303
|
-
if (!rows.length) return [];
|
|
304
|
-
|
|
305
|
-
const cols = Object.keys(rows[0])
|
|
306
|
-
.filter((key) => !key.startsWith("_") && !hideColumns.includes(key))
|
|
307
|
-
.map((key) => ({
|
|
308
|
-
field: key,
|
|
309
|
-
headerName: key,
|
|
310
|
-
valueFormatter: (value: any) => {
|
|
311
|
-
if (value == null || value === "") return "";
|
|
312
|
-
const isDate = /(\d{4}-\d{2}-\d{2})(T[\d:,.+-]*(Z)?)?/;
|
|
313
|
-
if (`${value}`.match(isDate)) {
|
|
314
|
-
return value
|
|
315
|
-
.toString()
|
|
316
|
-
.split("T")[0]
|
|
317
|
-
.split("-")
|
|
318
|
-
.reverse()
|
|
319
|
-
.join("/");
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const splited = `${value}`.split(".");
|
|
323
|
-
const hasDecimals =
|
|
324
|
-
splited.length == 2 &&
|
|
325
|
-
splited.every((v: any) => `${v}`.match(regularExpresions.number));
|
|
326
|
-
|
|
327
|
-
if (hasDecimals) {
|
|
328
|
-
return [
|
|
329
|
-
currentCoin,
|
|
330
|
-
(+`${value}`).toLocaleString("en-US", {
|
|
331
|
-
minimumFractionDigits: 2,
|
|
332
|
-
maximumFractionDigits: 2,
|
|
333
|
-
}),
|
|
334
|
-
].join(" ");
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
const isNumber = typeof value === "number";
|
|
338
|
-
|
|
339
|
-
if (isNumber) {
|
|
340
|
-
if (isNaN(value)) return value;
|
|
341
|
-
return value;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
return value;
|
|
345
|
-
},
|
|
346
|
-
flex: key == "id" ? false : !colSize?.[key],
|
|
347
|
-
width: key == "id" ? 80 : (colSize?.[key] ?? undefined),
|
|
348
|
-
editable: editableFields?.includes(key) ?? false,
|
|
349
|
-
type: typeof rows[0][key] === "number" ? "number" : "string",
|
|
350
|
-
// When wrapText is enabled, allow cells to grow vertically
|
|
351
|
-
...(wrapText && {
|
|
352
|
-
renderHeader: (params: any) => (
|
|
353
|
-
<span
|
|
354
|
-
style={{
|
|
355
|
-
whiteSpace: "normal",
|
|
356
|
-
lineHeight: 1.3,
|
|
357
|
-
wordBreak: "break-word",
|
|
358
|
-
}}
|
|
359
|
-
>
|
|
360
|
-
{params.colDef.headerName}
|
|
361
|
-
</span>
|
|
362
|
-
),
|
|
363
|
-
}),
|
|
364
|
-
renderCell: buttons?.[key]
|
|
365
|
-
? (params: any) => {
|
|
366
|
-
const children =
|
|
367
|
-
buttons?.[key]?.type == "input" ? null : params?.row?.[key];
|
|
368
|
-
|
|
369
|
-
return React.cloneElement(buttons[key], {
|
|
370
|
-
className: `${params?.className ?? ""} m-auto text-xs`,
|
|
371
|
-
children,
|
|
372
|
-
row: params?.row,
|
|
373
|
-
onClick: async (e: TableButtonProps) => {
|
|
374
|
-
e.row = params?.row;
|
|
375
|
-
if (buttons[key]?.props?.onClick) {
|
|
376
|
-
const newVal = await buttons[key].props.onClick(e);
|
|
377
|
-
|
|
378
|
-
if (newVal) handleRowUpdate({ ...e.row, newVal });
|
|
379
|
-
}
|
|
380
|
-
},
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
: wrapText
|
|
384
|
-
? (params: any) => (
|
|
385
|
-
// Plain cell with wrap — no custom button
|
|
386
|
-
<span
|
|
387
|
-
style={{
|
|
388
|
-
whiteSpace: "normal",
|
|
389
|
-
wordBreak: "break-word",
|
|
390
|
-
lineHeight: 1.4,
|
|
391
|
-
padding: "6px 0",
|
|
392
|
-
display: "block",
|
|
393
|
-
}}
|
|
394
|
-
>
|
|
395
|
-
{params.formattedValue ?? params.value}
|
|
396
|
-
</span>
|
|
397
|
-
)
|
|
398
|
-
: null,
|
|
399
|
-
}));
|
|
400
|
-
|
|
401
|
-
if (modal) {
|
|
402
|
-
cols.unshift({
|
|
403
|
-
field: "Modal",
|
|
404
|
-
headerName: "Modal",
|
|
405
|
-
editable: false,
|
|
406
|
-
type: "string",
|
|
407
|
-
width: 10,
|
|
408
|
-
renderCell: (params: any) =>
|
|
409
|
-
(
|
|
410
|
-
<Button
|
|
411
|
-
className="text-xs"
|
|
412
|
-
onClick={() => onModalOpen(params.row)}
|
|
413
|
-
icon={<EditIcon />}
|
|
414
|
-
>
|
|
415
|
-
{params?.row?.["Modal"]}
|
|
416
|
-
</Button>
|
|
417
|
-
) as any,
|
|
418
|
-
} as any);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
return cols;
|
|
422
|
-
}, [rows, wrapText]);
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// ─── SearchBar ────────────────────────────────────────────────────────────────
|
|
276
|
+
// ─── Search ───────────────────────────────────────────────────────────────────
|
|
426
277
|
|
|
427
278
|
function SearchBar({
|
|
428
279
|
value,
|
|
@@ -433,50 +284,32 @@ function SearchBar({
|
|
|
433
284
|
}) {
|
|
434
285
|
return (
|
|
435
286
|
<div className="flex items-center gap-2 px-2 pb-1">
|
|
436
|
-
<svg
|
|
437
|
-
className="text-gray-400 shrink-0"
|
|
438
|
-
width="14"
|
|
439
|
-
height="14"
|
|
440
|
-
viewBox="0 0 20 20"
|
|
441
|
-
fill="currentColor"
|
|
442
|
-
>
|
|
443
|
-
<path
|
|
444
|
-
fillRule="evenodd"
|
|
445
|
-
d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
|
|
446
|
-
clipRule="evenodd"
|
|
447
|
-
/>
|
|
448
|
-
</svg>
|
|
449
287
|
<input
|
|
450
288
|
type="text"
|
|
451
289
|
value={value}
|
|
452
290
|
onChange={(e) => onChange(e.target.value)}
|
|
453
291
|
placeholder="Buscar…"
|
|
454
|
-
className="w-full max-w-xs text-xs border border-gray-300 rounded px-2 py-1
|
|
292
|
+
className="w-full max-w-xs text-xs border border-gray-300 rounded px-2 py-1"
|
|
455
293
|
/>
|
|
456
|
-
{value && (
|
|
457
|
-
<button
|
|
458
|
-
onClick={() => onChange("")}
|
|
459
|
-
className="text-gray-400 hover:text-gray-600 text-sm leading-none"
|
|
460
|
-
title="Limpiar búsqueda"
|
|
461
|
-
>
|
|
462
|
-
×
|
|
463
|
-
</button>
|
|
464
|
-
)}
|
|
465
294
|
</div>
|
|
466
295
|
);
|
|
467
296
|
}
|
|
468
297
|
|
|
469
|
-
/** Devuelve true si la fila contiene TODAS las palabras del query */
|
|
470
298
|
function rowMatchesSearch(row: GridValidRowModel, query: string): boolean {
|
|
471
299
|
if (!query.trim()) return true;
|
|
300
|
+
|
|
472
301
|
const words = query.trim().toLowerCase().split(/\s+/);
|
|
302
|
+
|
|
473
303
|
const rowText = Object.values(row)
|
|
474
304
|
.filter((v) => v != null && v !== "")
|
|
475
305
|
.join(" ")
|
|
476
306
|
.toLowerCase();
|
|
307
|
+
|
|
477
308
|
return words.every((word) => rowText.includes(word));
|
|
478
309
|
}
|
|
479
310
|
|
|
311
|
+
// ─── Main Table ───────────────────────────────────────────────────────────────
|
|
312
|
+
|
|
480
313
|
function IHTable({
|
|
481
314
|
data,
|
|
482
315
|
flex = 1,
|
|
@@ -500,92 +333,205 @@ function IHTable({
|
|
|
500
333
|
wrapText = false,
|
|
501
334
|
density = "standard",
|
|
502
335
|
searchable = false,
|
|
336
|
+
autoHeight = false,
|
|
503
337
|
}: TableProps) {
|
|
504
|
-
if (modal && onSelect)
|
|
505
|
-
throw new Error("Solo se puede usar modal o onSelect por separado");
|
|
506
|
-
|
|
507
338
|
const [open, setOpen] = useState(false);
|
|
339
|
+
|
|
508
340
|
const [rows, setRows] = useState<GridValidRowModel[]>(data);
|
|
341
|
+
|
|
342
|
+
const [modalIndex, setModalIndex] = useState<number | null>(null);
|
|
343
|
+
|
|
509
344
|
const [selectedRows, setSelectedRows] = useState<any>({
|
|
510
345
|
type: "include",
|
|
511
346
|
ids: new Set(),
|
|
512
347
|
});
|
|
513
|
-
const [modalRow, setModalRow] = useState<GridValidRowModel | undefined>();
|
|
514
|
-
const [searchQuery, setSearchQuery] = useState("");
|
|
515
348
|
|
|
516
|
-
|
|
517
|
-
const resolvedButtons = modal ? { ...buttons, Modal: "" } : buttons;
|
|
349
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
518
350
|
|
|
519
351
|
useEffect(() => {
|
|
520
352
|
setRows(data);
|
|
521
353
|
}, [data]);
|
|
522
354
|
|
|
355
|
+
const filteredRows = useMemo(() => {
|
|
356
|
+
if (selectedRows?.type === "exclude") {
|
|
357
|
+
return rows.filter((r) => !Array.from(selectedRows.ids).includes(r.id));
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (selectedRows?.type === "include") {
|
|
361
|
+
return rows.filter((r) => Array.from(selectedRows.ids).includes(r.id));
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return [];
|
|
365
|
+
}, [selectedRows, rows]);
|
|
366
|
+
|
|
367
|
+
const displayRows = useMemo(
|
|
368
|
+
() =>
|
|
369
|
+
searchable ? rows.filter((r) => rowMatchesSearch(r, searchQuery)) : rows,
|
|
370
|
+
[rows, searchQuery, searchable],
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
const modalRow = modalIndex != null ? displayRows[modalIndex] : undefined;
|
|
374
|
+
|
|
523
375
|
const handleModalOpen = (row: GridValidRowModel) => {
|
|
524
|
-
|
|
376
|
+
const index = displayRows.findIndex((r) => r.id === row.id);
|
|
377
|
+
|
|
378
|
+
if (index === -1) return;
|
|
379
|
+
|
|
380
|
+
setModalIndex(index);
|
|
525
381
|
setOpen(true);
|
|
526
382
|
};
|
|
383
|
+
|
|
527
384
|
const handleClose = async () => {
|
|
528
|
-
const pass = onCloseModal ? await onCloseModal
|
|
385
|
+
const pass = onCloseModal ? await onCloseModal(modalRow) : true;
|
|
529
386
|
|
|
530
387
|
if (!pass) return;
|
|
388
|
+
|
|
531
389
|
setOpen(false);
|
|
532
|
-
|
|
390
|
+
setModalIndex(null);
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
const handlePrevRow = () => {
|
|
394
|
+
setModalIndex((prev) => {
|
|
395
|
+
if (prev == null) return prev;
|
|
396
|
+
return Math.max(prev - 1, 0);
|
|
397
|
+
});
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
const handleNextRow = () => {
|
|
401
|
+
setModalIndex((prev) => {
|
|
402
|
+
if (prev == null) return prev;
|
|
403
|
+
return Math.min(prev + 1, displayRows.length - 1);
|
|
404
|
+
});
|
|
533
405
|
};
|
|
534
406
|
|
|
535
407
|
const handleRowUpdate = (newRow: GridRowModel) => {
|
|
536
408
|
if (!newRow.id) throw new Error("Fila sin id");
|
|
537
|
-
|
|
409
|
+
|
|
410
|
+
const updated: any = { ...newRow, _edited: true };
|
|
411
|
+
|
|
538
412
|
setRows((prev) =>
|
|
539
413
|
prev.map((row) => (row.id === updated.id ? updated : row)),
|
|
540
414
|
);
|
|
415
|
+
|
|
541
416
|
return updated;
|
|
542
417
|
};
|
|
543
418
|
|
|
544
|
-
const
|
|
545
|
-
if (selectedRows?.type === "exclude") {
|
|
546
|
-
return rows.filter((r) => !Array.from(selectedRows.ids).includes(r.id));
|
|
547
|
-
}
|
|
548
|
-
if (selectedRows?.type === "include") {
|
|
549
|
-
return rows.filter((r) => Array.from(selectedRows.ids).includes(r.id));
|
|
550
|
-
}
|
|
551
|
-
return [];
|
|
552
|
-
}, [selectedRows, rows]);
|
|
419
|
+
const resolvedButtons = modal ? { ...buttons, Modal: "" } : buttons;
|
|
553
420
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
() =>
|
|
557
|
-
searchable ? rows.filter((r) => rowMatchesSearch(r, searchQuery)) : rows,
|
|
558
|
-
[rows, searchQuery, searchable],
|
|
559
|
-
);
|
|
421
|
+
const columns = useMemo(() => {
|
|
422
|
+
if (!displayRows.length) return [];
|
|
560
423
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
424
|
+
const cols = Object.keys(displayRows[0])
|
|
425
|
+
.filter((key) => !key.startsWith("_") && !hideColumns.includes(key))
|
|
426
|
+
.map((key) => ({
|
|
427
|
+
field: key,
|
|
428
|
+
headerName: key,
|
|
429
|
+
flex: key == "id" ? false : !colSize?.[key],
|
|
430
|
+
width: key == "id" ? 80 : (colSize?.[key] ?? undefined),
|
|
431
|
+
editable: editableFields?.includes(key) ?? false,
|
|
432
|
+
type: typeof displayRows[0][key] === "number" ? "number" : "string",
|
|
433
|
+
|
|
434
|
+
valueFormatter: (value: any) => {
|
|
435
|
+
if (value == null || value === "") return "";
|
|
436
|
+
|
|
437
|
+
const isDate = /(\d{4}-\d{2}-\d{2})(T[\d:,.+-]*(Z)?)?/;
|
|
438
|
+
|
|
439
|
+
if (`${value}`.match(isDate)) {
|
|
440
|
+
return value
|
|
441
|
+
.toString()
|
|
442
|
+
.split("T")[0]
|
|
443
|
+
.split("-")
|
|
444
|
+
.reverse()
|
|
445
|
+
.join("/");
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const splited = `${value}`.split(".");
|
|
449
|
+
|
|
450
|
+
const hasDecimals =
|
|
451
|
+
splited.length == 2 &&
|
|
452
|
+
splited.every((v: any) => `${v}`.match(regularExpresions.number));
|
|
453
|
+
|
|
454
|
+
if (hasDecimals) {
|
|
455
|
+
return [
|
|
456
|
+
currentCoin,
|
|
457
|
+
(+`${value}`).toLocaleString("en-US", {
|
|
458
|
+
minimumFractionDigits: 2,
|
|
459
|
+
maximumFractionDigits: 2,
|
|
460
|
+
}),
|
|
461
|
+
].join(" ");
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
return value;
|
|
465
|
+
},
|
|
466
|
+
|
|
467
|
+
renderCell: resolvedButtons?.[key]
|
|
468
|
+
? (params: any) => {
|
|
469
|
+
const children =
|
|
470
|
+
resolvedButtons?.[key]?.type == "input"
|
|
471
|
+
? null
|
|
472
|
+
: params?.row?.[key];
|
|
473
|
+
|
|
474
|
+
return React.cloneElement(resolvedButtons[key], {
|
|
475
|
+
className: `${params?.className ?? ""} m-auto text-xs`,
|
|
476
|
+
children,
|
|
477
|
+
row: params?.row,
|
|
478
|
+
|
|
479
|
+
onClick: async (e: TableButtonProps) => {
|
|
480
|
+
e.row = params?.row;
|
|
481
|
+
|
|
482
|
+
if (resolvedButtons[key]?.props?.onClick) {
|
|
483
|
+
const newVal = await resolvedButtons[key].props.onClick(e);
|
|
484
|
+
|
|
485
|
+
if (newVal) {
|
|
486
|
+
handleRowUpdate({ ...e.row, newVal });
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
},
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
: null,
|
|
493
|
+
}));
|
|
494
|
+
|
|
495
|
+
if (modal) {
|
|
496
|
+
cols.unshift({
|
|
497
|
+
field: "Modal",
|
|
498
|
+
headerName: "Modal",
|
|
499
|
+
width: 100,
|
|
500
|
+
|
|
501
|
+
renderCell: (params: any) => (
|
|
502
|
+
<Button
|
|
503
|
+
className="text-xs"
|
|
504
|
+
onClick={() => handleModalOpen(params.row)}
|
|
505
|
+
icon={<EditIcon />}
|
|
506
|
+
>
|
|
507
|
+
Abrir
|
|
508
|
+
</Button>
|
|
509
|
+
),
|
|
510
|
+
} as any);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
return cols;
|
|
514
|
+
}, [displayRows]);
|
|
576
515
|
|
|
577
516
|
if (!rows.length) return null;
|
|
578
517
|
|
|
579
|
-
// When wrapText is active we must use getRowHeight; rowHeight prop is ignored.
|
|
580
518
|
const rowHeightProps = wrapText
|
|
581
519
|
? { getRowHeight: () => "auto" as const }
|
|
582
520
|
: rowHeight != null
|
|
583
521
|
? { rowHeight }
|
|
584
522
|
: {};
|
|
585
523
|
|
|
524
|
+
const containerHeight = autoHeight
|
|
525
|
+
? undefined
|
|
526
|
+
: (HEIGHT_MAP[displayRows.length] ?? height);
|
|
527
|
+
|
|
528
|
+
const hideFooter =
|
|
529
|
+
autoHeight || displayRows.length <= Object.keys(HEIGHT_MAP).length;
|
|
530
|
+
|
|
586
531
|
return (
|
|
587
532
|
<div className="m-2">
|
|
588
|
-
{header && <div className="font-bold
|
|
533
|
+
{header && <div className="font-bold p-2">{header}</div>}
|
|
534
|
+
|
|
589
535
|
<div className="flex justify-between">
|
|
590
536
|
<Toolbar
|
|
591
537
|
exportName={exportName}
|
|
@@ -594,6 +540,7 @@ function IHTable({
|
|
|
594
540
|
rows={rows}
|
|
595
541
|
filteredRows={filteredRows}
|
|
596
542
|
/>
|
|
543
|
+
|
|
597
544
|
{searchable && (
|
|
598
545
|
<SearchBar value={searchQuery} onChange={setSearchQuery} />
|
|
599
546
|
)}
|
|
@@ -603,9 +550,8 @@ function IHTable({
|
|
|
603
550
|
sx={{
|
|
604
551
|
display: "flex",
|
|
605
552
|
flexDirection: "column",
|
|
606
|
-
height:
|
|
553
|
+
height: containerHeight,
|
|
607
554
|
width,
|
|
608
|
-
zIndex: 999999999,
|
|
609
555
|
}}
|
|
610
556
|
>
|
|
611
557
|
{modal && (
|
|
@@ -614,6 +560,10 @@ function IHTable({
|
|
|
614
560
|
onClose={handleClose}
|
|
615
561
|
modal={modal}
|
|
616
562
|
selectedRow={modalRow}
|
|
563
|
+
onPrev={handlePrevRow}
|
|
564
|
+
onNext={handleNextRow}
|
|
565
|
+
hasPrev={(modalIndex ?? 0) > 0}
|
|
566
|
+
hasNext={(modalIndex ?? -1) < displayRows.length - 1}
|
|
617
567
|
/>
|
|
618
568
|
)}
|
|
619
569
|
|
|
@@ -622,60 +572,45 @@ function IHTable({
|
|
|
622
572
|
rows={displayRows}
|
|
623
573
|
columns={columns as any}
|
|
624
574
|
density={density}
|
|
575
|
+
autoHeight={autoHeight}
|
|
625
576
|
checkboxSelection={Boolean(onSelect)}
|
|
626
577
|
rowSelectionModel={selectedRows}
|
|
627
578
|
onRowSelectionModelChange={!modal ? setSelectedRows : undefined}
|
|
628
579
|
{...rowHeightProps}
|
|
629
580
|
sx={{
|
|
630
581
|
fontSize,
|
|
582
|
+
|
|
631
583
|
"& .MuiDataGrid-cell": {
|
|
632
584
|
fontSize,
|
|
633
|
-
// Allow cells to wrap text when wrapText is enabled
|
|
634
|
-
...(wrapText && {
|
|
635
|
-
alignItems: "flex-start",
|
|
636
|
-
paddingTop: "8px",
|
|
637
|
-
paddingBottom: "8px",
|
|
638
|
-
whiteSpace: "normal",
|
|
639
|
-
wordBreak: "break-word",
|
|
640
|
-
}),
|
|
641
585
|
},
|
|
586
|
+
|
|
642
587
|
"& .MuiDataGrid-columnHeader": {
|
|
643
588
|
fontSize,
|
|
644
|
-
...(wrapText && {
|
|
645
|
-
whiteSpace: "normal",
|
|
646
|
-
"& .MuiDataGrid-columnHeaderTitle": {
|
|
647
|
-
whiteSpace: "normal",
|
|
648
|
-
lineHeight: 1.3,
|
|
649
|
-
wordBreak: "break-word",
|
|
650
|
-
},
|
|
651
|
-
}),
|
|
652
589
|
},
|
|
590
|
+
|
|
653
591
|
"& .MuiDataGrid-cell--editable": {
|
|
654
592
|
backgroundColor: "#c6d8f0",
|
|
655
593
|
fontWeight: 500,
|
|
656
594
|
},
|
|
657
|
-
...(displayRows.length <= Object.keys(HEIGHT_MAP).length && {
|
|
658
|
-
"& .MuiDataGrid-filler": {
|
|
659
|
-
display: "none",
|
|
660
|
-
},
|
|
661
|
-
}),
|
|
662
595
|
}}
|
|
663
596
|
editMode="row"
|
|
664
597
|
processRowUpdate={handleRowUpdate}
|
|
665
598
|
pageSizeOptions={[5, 10]}
|
|
666
|
-
hideFooter={
|
|
599
|
+
hideFooter={hideFooter}
|
|
667
600
|
/>
|
|
668
601
|
</Box>
|
|
602
|
+
|
|
669
603
|
<CustomFooter footer={footer} rows={displayRows} />
|
|
670
604
|
</div>
|
|
671
605
|
);
|
|
672
606
|
}
|
|
673
607
|
|
|
674
|
-
// ───
|
|
608
|
+
// ─── Export ───────────────────────────────────────────────────────────────────
|
|
675
609
|
|
|
676
610
|
export default function Table(props: TableProps) {
|
|
677
611
|
if (Array.isArray(props.data)) {
|
|
678
612
|
return <IHTable {...props} />;
|
|
679
613
|
}
|
|
614
|
+
|
|
680
615
|
return <KeyValueTable data={props.data} />;
|
|
681
616
|
}
|