next-recomponents 2.0.4 → 2.0.5
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 +6 -4
- package/dist/index.d.ts +6 -4
- package/dist/index.js +359 -252
- package/dist/index.mjs +357 -250
- package/package.json +1 -1
- package/src/pop/index.tsx +179 -73
- package/src/table/index.tsx +348 -241
package/src/table/index.tsx
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { DataGrid, GridRowModel, GridValidRowModel } from "@mui/x-data-grid";
|
|
4
|
-
import { Box } from "@mui/material";
|
|
4
|
+
import { Box, Dialog } from "@mui/material";
|
|
5
5
|
import React, { useEffect, useMemo, useRef, useState } from "react";
|
|
6
6
|
import useExcel from "../use-excel";
|
|
7
7
|
import Button from "../button";
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
|
|
9
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
10
10
|
|
|
11
11
|
export interface TableButtonProps extends React.MouseEvent<
|
|
12
12
|
HTMLButtonElement,
|
|
@@ -15,8 +15,10 @@ export interface TableButtonProps extends React.MouseEvent<
|
|
|
15
15
|
row: Record<string, any>;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
type FooterAggregation = "sum" | "avg" | "count";
|
|
19
|
+
|
|
18
20
|
interface FooterType {
|
|
19
|
-
[key: string]:
|
|
21
|
+
[key: string]: FooterAggregation;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
interface TableProps {
|
|
@@ -34,37 +36,24 @@ interface TableProps {
|
|
|
34
36
|
hideColumns?: string[];
|
|
35
37
|
footer?: FooterType;
|
|
36
38
|
symbols?: any;
|
|
37
|
-
|
|
38
39
|
[key: string]: any;
|
|
39
40
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
>
|
|
58
|
-
{props.data[k]}
|
|
59
|
-
</td>
|
|
60
|
-
</tr>
|
|
61
|
-
))}
|
|
62
|
-
</tbody>
|
|
63
|
-
</table>
|
|
64
|
-
</div>
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
41
|
+
|
|
42
|
+
// ─── Height mapping ───────────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
const HEIGHT_MAP: Record<number, number> = {
|
|
45
|
+
1: 150,
|
|
46
|
+
2: 200,
|
|
47
|
+
3: 250,
|
|
48
|
+
4: 310,
|
|
49
|
+
5: 360,
|
|
50
|
+
6: 410,
|
|
51
|
+
7: 460,
|
|
52
|
+
8: 510,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// ─── Icons ────────────────────────────────────────────────────────────────────
|
|
56
|
+
|
|
68
57
|
function EditIcon() {
|
|
69
58
|
return (
|
|
70
59
|
<svg
|
|
@@ -76,66 +65,221 @@ function EditIcon() {
|
|
|
76
65
|
width="20px"
|
|
77
66
|
xmlns="http://www.w3.org/2000/svg"
|
|
78
67
|
>
|
|
79
|
-
<path d="M402.6 83.2l90.2 90.2c3.8 3.8 3.8 10 0 13.8L274.4 405.6l-92.8 10.3c-12.4 1.4-22.9-9.1-21.5-21.5l10.3-92.8L388.8 83.2c3.8-3.8 10-3.8 13.8 0zm162-22.9l-48.8-48.8c-15.2-15.2-39.9-15.2-55.2 0l-35.4 35.4c-3.8 3.8-3.8 10 0 13.8l90.2 90.2c3.8 3.8 10 3.8 13.8 0l35.4-35.4c15.2-15.3 15.2-40 0-55.2zM384 346.2V448H64V128h229.8c3.2 0 6.2-1.3 8.5-3.5l40-40c7.6-7.6 2.2-20.5-8.5-20.5H48C21.5 64 0 85.5 0 112v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V306.2c0-10.7-12.9-16-20.5-8.5l-40 40c-2.2 2.3-3.5 5.3-3.5 8.5z"
|
|
68
|
+
<path d="M402.6 83.2l90.2 90.2c3.8 3.8 3.8 10 0 13.8L274.4 405.6l-92.8 10.3c-12.4 1.4-22.9-9.1-21.5-21.5l10.3-92.8L388.8 83.2c3.8-3.8 10-3.8 13.8 0zm162-22.9l-48.8-48.8c-15.2-15.2-39.9-15.2-55.2 0l-35.4 35.4c-3.8 3.8-3.8 10 0 13.8l90.2 90.2c3.8 3.8 10 3.8 13.8 0l35.4-35.4c15.2-15.3 15.2-40 0-55.2zM384 346.2V448H64V128h229.8c3.2 0 6.2-1.3 8.5-3.5l40-40c7.6-7.6 2.2-20.5-8.5-20.5H48C21.5 64 0 85.5 0 112v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V306.2c0-10.7-12.9-16-20.5-8.5l-40 40c-2.2 2.3-3.5 5.3-3.5 8.5z" />
|
|
80
69
|
</svg>
|
|
81
70
|
);
|
|
82
71
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
72
|
+
|
|
73
|
+
// ─── KeyValueTable (non-array data) ──────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
function KeyValueTable({ data }: { data: Record<string, any> }) {
|
|
76
|
+
return (
|
|
77
|
+
<div className="bg-white border shadow rounded p-1">
|
|
78
|
+
<table className="rounded">
|
|
79
|
+
<tbody>
|
|
80
|
+
{Object.keys(data).map((key) => (
|
|
81
|
+
<tr key={key} className="border-b">
|
|
82
|
+
<th className="font-bold p-3 text-right">{key}</th>
|
|
83
|
+
<td
|
|
84
|
+
className={
|
|
85
|
+
typeof data[key] === "number" ? "text-right" : "text-center"
|
|
86
|
+
}
|
|
87
|
+
>
|
|
88
|
+
{data[key]}
|
|
89
|
+
</td>
|
|
90
|
+
</tr>
|
|
91
|
+
))}
|
|
92
|
+
</tbody>
|
|
93
|
+
</table>
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ─── CustomFooter ─────────────────────────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
const FOOTER_LABELS: Record<FooterAggregation, string> = {
|
|
101
|
+
sum: "Suma",
|
|
102
|
+
avg: "Promedio",
|
|
103
|
+
count: "Conteo",
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
function computeAggregation(
|
|
107
|
+
rows: GridValidRowModel[],
|
|
108
|
+
key: string,
|
|
109
|
+
type: FooterAggregation,
|
|
110
|
+
): number {
|
|
111
|
+
const values = rows.map((r) => Number(r[key] ?? 0)).filter((v) => !isNaN(v));
|
|
112
|
+
|
|
113
|
+
if (type === "sum") return values.reduce((acc, v) => acc + v, 0);
|
|
114
|
+
if (type === "count") return values.length;
|
|
115
|
+
if (type === "avg")
|
|
116
|
+
return values.length
|
|
117
|
+
? values.reduce((acc, v) => acc + v, 0) / values.length
|
|
118
|
+
: 0;
|
|
119
|
+
return 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function CustomFooter({
|
|
123
|
+
rows,
|
|
124
|
+
footer,
|
|
125
|
+
}: {
|
|
126
|
+
rows: GridValidRowModel[];
|
|
127
|
+
footer: FooterType;
|
|
128
|
+
}) {
|
|
129
|
+
const entries = Object.entries(footer);
|
|
130
|
+
if (!entries.length) return null;
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<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">
|
|
134
|
+
{entries.map(([key, type]) => {
|
|
135
|
+
const value = computeAggregation(rows, key, type);
|
|
136
|
+
const formatted =
|
|
137
|
+
type === "avg"
|
|
138
|
+
? value.toLocaleString(undefined, { maximumFractionDigits: 2 })
|
|
139
|
+
: value.toLocaleString();
|
|
140
|
+
return (
|
|
141
|
+
<span key={key}>
|
|
142
|
+
{FOOTER_LABELS[type]} de{" "}
|
|
143
|
+
<span className="text-gray-900">{key}</span>:{" "}
|
|
144
|
+
<span className="text-blue-700">{formatted}</span>
|
|
145
|
+
</span>
|
|
146
|
+
);
|
|
147
|
+
})}
|
|
148
|
+
</div>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ─── ModalDialog ──────────────────────────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
interface ModalDialogProps {
|
|
155
|
+
open: boolean;
|
|
156
|
+
onClose: () => void;
|
|
157
|
+
modal: React.ReactNode;
|
|
158
|
+
selectedRow: GridValidRowModel | undefined;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function ModalDialog({ open, onClose, modal, selectedRow }: ModalDialogProps) {
|
|
162
|
+
return (
|
|
163
|
+
<Dialog open={open} onClose={onClose} maxWidth="xl" fullWidth>
|
|
164
|
+
<div className="flex justify-end">
|
|
165
|
+
<button
|
|
166
|
+
onClick={onClose}
|
|
167
|
+
className="text-gray-500 hover:text-gray-800 text-xl font-bold p-5"
|
|
168
|
+
>
|
|
169
|
+
×
|
|
170
|
+
</button>
|
|
171
|
+
</div>
|
|
172
|
+
<div className="mt-4 m-auto p-5">
|
|
173
|
+
{selectedRow &&
|
|
174
|
+
React.cloneElement(
|
|
175
|
+
modal as React.ReactElement,
|
|
176
|
+
{ row: selectedRow } as any,
|
|
177
|
+
)}
|
|
178
|
+
</div>
|
|
179
|
+
</Dialog>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ─── Toolbar ──────────────────────────────────────────────────────────────────
|
|
184
|
+
|
|
185
|
+
interface ToolbarProps {
|
|
186
|
+
exportName?: string;
|
|
187
|
+
onSave?: (data: GridValidRowModel[]) => void;
|
|
188
|
+
onSelect?: (data: GridValidRowModel[]) => void;
|
|
189
|
+
rows: GridValidRowModel[];
|
|
190
|
+
filteredRows: GridValidRowModel[];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function Toolbar({
|
|
194
|
+
exportName,
|
|
87
195
|
onSave,
|
|
88
196
|
onSelect,
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}: TableProps) {
|
|
98
|
-
const [open, setOpen] = useState(false);
|
|
99
|
-
if (modal) {
|
|
100
|
-
buttons = { ...buttons, Modal: "" };
|
|
101
|
-
}
|
|
102
|
-
const handleOpen = () => setOpen(true);
|
|
103
|
-
const handleClose = () => {
|
|
104
|
-
setOpen(false);
|
|
105
|
-
setSelectedRows({
|
|
106
|
-
type: "include",
|
|
107
|
-
ids: new Set(),
|
|
108
|
-
});
|
|
197
|
+
rows,
|
|
198
|
+
filteredRows,
|
|
199
|
+
}: ToolbarProps) {
|
|
200
|
+
const excel = useExcel();
|
|
201
|
+
|
|
202
|
+
const stripMeta = (r: GridValidRowModel) => {
|
|
203
|
+
const { _edited, ...rest } = r;
|
|
204
|
+
return rest;
|
|
109
205
|
};
|
|
110
206
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
207
|
+
if (!exportName && !onSave && !onSelect) return null;
|
|
208
|
+
|
|
209
|
+
return (
|
|
210
|
+
<div className="flex gap-2 bg-gray-200 border shadow rounded p-2">
|
|
211
|
+
{exportName && (
|
|
212
|
+
<Button
|
|
213
|
+
className="bg-green-800 text-white"
|
|
214
|
+
onClick={() =>
|
|
215
|
+
excel.export(rows.map(stripMeta), `${exportName}.xlsx`)
|
|
216
|
+
}
|
|
217
|
+
>
|
|
218
|
+
Exportar
|
|
219
|
+
</Button>
|
|
220
|
+
)}
|
|
221
|
+
{onSelect ? (
|
|
222
|
+
<Button
|
|
223
|
+
disabled={filteredRows.length === 0}
|
|
224
|
+
color={filteredRows.length === 0 ? "white" : "primary"}
|
|
225
|
+
onClick={() => onSelect(filteredRows.map(stripMeta))}
|
|
226
|
+
>
|
|
227
|
+
Guardar Selección
|
|
228
|
+
</Button>
|
|
229
|
+
) : (
|
|
230
|
+
onSave && (
|
|
231
|
+
<Button onClick={() => onSave(rows.map(stripMeta))}>Guardar</Button>
|
|
232
|
+
)
|
|
233
|
+
)}
|
|
234
|
+
</div>
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ─── useColumns ───────────────────────────────────────────────────────────────
|
|
239
|
+
|
|
240
|
+
function useColumns(
|
|
241
|
+
rows: GridValidRowModel[],
|
|
242
|
+
options: {
|
|
243
|
+
flex: number;
|
|
244
|
+
editableFields?: string[];
|
|
245
|
+
buttons?: Record<string, any>;
|
|
246
|
+
hideColumns: string[];
|
|
247
|
+
modal?: React.ReactNode;
|
|
248
|
+
handleRowUpdate: (row: GridRowModel) => GridRowModel;
|
|
249
|
+
onModalOpen: (row: GridValidRowModel) => void;
|
|
250
|
+
},
|
|
251
|
+
) {
|
|
252
|
+
const {
|
|
253
|
+
flex,
|
|
254
|
+
editableFields,
|
|
255
|
+
buttons,
|
|
256
|
+
hideColumns,
|
|
257
|
+
modal,
|
|
258
|
+
handleRowUpdate,
|
|
259
|
+
onModalOpen,
|
|
260
|
+
} = options;
|
|
117
261
|
|
|
118
|
-
|
|
262
|
+
return useMemo(() => {
|
|
119
263
|
if (!rows.length) return [];
|
|
120
264
|
|
|
121
|
-
const
|
|
265
|
+
const cols = Object.keys(rows[0])
|
|
122
266
|
.filter((key) => !key.startsWith("_") && !hideColumns.includes(key))
|
|
123
|
-
.map((key
|
|
267
|
+
.map((key) => ({
|
|
124
268
|
field: key,
|
|
125
269
|
headerName: key,
|
|
126
270
|
flex,
|
|
127
|
-
editable: editableFields?.includes(key),
|
|
271
|
+
editable: editableFields?.includes(key) ?? false,
|
|
128
272
|
type: typeof rows[0][key] === "number" ? "number" : "string",
|
|
129
273
|
renderCell: buttons?.[key]
|
|
130
274
|
? (params: any) =>
|
|
131
275
|
React.cloneElement(buttons[key], {
|
|
132
|
-
className:
|
|
276
|
+
className: `${params?.className ?? ""} m-auto text-xs`,
|
|
133
277
|
children: params?.row?.[key],
|
|
134
278
|
onClick: (e: any) => {
|
|
135
279
|
e.row = params?.row;
|
|
136
280
|
if (buttons[key]?.props?.onClick) {
|
|
137
281
|
const newVal = buttons[key].props.onClick(e);
|
|
138
|
-
newVal
|
|
282
|
+
if (newVal) handleRowUpdate(newVal);
|
|
139
283
|
}
|
|
140
284
|
},
|
|
141
285
|
})
|
|
@@ -143,7 +287,7 @@ function IHTable({
|
|
|
143
287
|
}));
|
|
144
288
|
|
|
145
289
|
if (modal) {
|
|
146
|
-
|
|
290
|
+
cols.unshift({
|
|
147
291
|
field: "Modal",
|
|
148
292
|
headerName: "Modal",
|
|
149
293
|
flex,
|
|
@@ -153,9 +297,7 @@ function IHTable({
|
|
|
153
297
|
(
|
|
154
298
|
<Button
|
|
155
299
|
className="text-xs"
|
|
156
|
-
onClick={() =>
|
|
157
|
-
handleOpen();
|
|
158
|
-
}}
|
|
300
|
+
onClick={() => onModalOpen(params.row)}
|
|
159
301
|
icon={<EditIcon />}
|
|
160
302
|
>
|
|
161
303
|
{params?.row?.["Modal"]}
|
|
@@ -164,186 +306,151 @@ function IHTable({
|
|
|
164
306
|
});
|
|
165
307
|
}
|
|
166
308
|
|
|
167
|
-
return
|
|
309
|
+
return cols;
|
|
168
310
|
}, [rows]);
|
|
311
|
+
}
|
|
169
312
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
313
|
+
// ─── IHTable (array data) ─────────────────────────────────────────────────────
|
|
314
|
+
|
|
315
|
+
function IHTable({
|
|
316
|
+
data,
|
|
317
|
+
flex = 1,
|
|
318
|
+
editableFields,
|
|
319
|
+
onSave,
|
|
320
|
+
onSelect,
|
|
321
|
+
buttons,
|
|
322
|
+
exportName,
|
|
323
|
+
modal,
|
|
324
|
+
height = 510,
|
|
325
|
+
width = "100%",
|
|
326
|
+
header,
|
|
327
|
+
hideColumns = [],
|
|
328
|
+
footer = {},
|
|
329
|
+
}: TableProps) {
|
|
330
|
+
if (modal && onSelect)
|
|
331
|
+
throw new Error("Solo se puede usar modal o onSelect por separado");
|
|
332
|
+
|
|
333
|
+
const [open, setOpen] = useState(false);
|
|
334
|
+
const [rows, setRows] = useState<GridValidRowModel[]>(data);
|
|
335
|
+
const [selectedRows, setSelectedRows] = useState<any>({
|
|
336
|
+
type: "include",
|
|
337
|
+
ids: new Set(),
|
|
338
|
+
});
|
|
339
|
+
const [modalRow, setModalRow] = useState<GridValidRowModel | undefined>();
|
|
340
|
+
|
|
341
|
+
// Inject Modal button key if modal is provided
|
|
342
|
+
const resolvedButtons = modal ? { ...buttons, Modal: "" } : buttons;
|
|
343
|
+
|
|
344
|
+
useEffect(() => {
|
|
345
|
+
setRows(data);
|
|
346
|
+
}, [data]);
|
|
347
|
+
|
|
348
|
+
const handleModalOpen = (row: GridValidRowModel) => {
|
|
349
|
+
setModalRow(row);
|
|
350
|
+
setOpen(true);
|
|
351
|
+
};
|
|
352
|
+
const handleClose = () => {
|
|
353
|
+
setOpen(false);
|
|
354
|
+
setModalRow(undefined);
|
|
175
355
|
};
|
|
176
356
|
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
357
|
+
const handleRowUpdate = (newRow: GridRowModel) => {
|
|
358
|
+
if (!newRow.id) throw new Error("Fila sin id");
|
|
359
|
+
const updated = { ...newRow, _edited: true } as any;
|
|
360
|
+
setRows((prev) =>
|
|
361
|
+
prev.map((row) => (row.id === updated.id ? updated : row)),
|
|
362
|
+
);
|
|
363
|
+
return updated;
|
|
184
364
|
};
|
|
185
365
|
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
if (selectedRows?.type == "exclude") {
|
|
190
|
-
filtered = rows.filter(
|
|
191
|
-
(r) => !Array.from(selectedRows.ids).includes(r.id),
|
|
192
|
-
);
|
|
193
|
-
} else if (selectedRows?.type == "include") {
|
|
194
|
-
filtered = rows.filter((r) =>
|
|
195
|
-
Array.from(selectedRows.ids).includes(r.id),
|
|
196
|
-
);
|
|
366
|
+
const filteredRows = useMemo(() => {
|
|
367
|
+
if (selectedRows?.type === "exclude") {
|
|
368
|
+
return rows.filter((r) => !Array.from(selectedRows.ids).includes(r.id));
|
|
197
369
|
}
|
|
198
|
-
|
|
199
|
-
|
|
370
|
+
if (selectedRows?.type === "include") {
|
|
371
|
+
return rows.filter((r) => Array.from(selectedRows.ids).includes(r.id));
|
|
372
|
+
}
|
|
373
|
+
return [];
|
|
374
|
+
}, [selectedRows, rows]);
|
|
375
|
+
|
|
376
|
+
const columns = useColumns(rows, {
|
|
377
|
+
flex,
|
|
378
|
+
editableFields,
|
|
379
|
+
buttons: resolvedButtons,
|
|
380
|
+
hideColumns,
|
|
381
|
+
modal,
|
|
382
|
+
handleRowUpdate,
|
|
383
|
+
onModalOpen: handleModalOpen,
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
if (!rows.length) return null;
|
|
200
387
|
|
|
201
388
|
return (
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
handleClose();
|
|
218
|
-
}}
|
|
219
|
-
className="text-gray-500 hover:text-gray-800 text-xl font-bold p-5"
|
|
220
|
-
>
|
|
221
|
-
×
|
|
222
|
-
</button>
|
|
223
|
-
</div>
|
|
224
|
-
<div className="mt-4 m-auto p-5">
|
|
225
|
-
{selectedRows &&
|
|
226
|
-
Array.from(selectedRows?.ids).length > 0 &&
|
|
227
|
-
React.cloneElement(modal as any, {
|
|
228
|
-
row: rows.find((r) =>
|
|
229
|
-
Array.from(selectedRows?.ids).includes(r.id),
|
|
230
|
-
),
|
|
231
|
-
})}
|
|
232
|
-
</div>
|
|
233
|
-
</Dialog>
|
|
234
|
-
)}
|
|
235
|
-
{header && (
|
|
236
|
-
<div className="font-bold text-xl p-2 bg-blue-500 text-white border shadow rounded">
|
|
237
|
-
{header}
|
|
238
|
-
</div>
|
|
239
|
-
)}
|
|
240
|
-
{(exportName || onSave || onSelect) && (
|
|
241
|
-
<div className="flex gap-2 bg-gray-200 border shadow rounded p-2">
|
|
242
|
-
{exportName && (
|
|
243
|
-
<Button
|
|
244
|
-
className="bg-green-800 text-white"
|
|
245
|
-
onClick={(e) => {
|
|
246
|
-
excel.export(
|
|
247
|
-
rows.map(({ _edited, ...r }) => r),
|
|
248
|
-
`${exportName}.xlsx`,
|
|
249
|
-
);
|
|
250
|
-
}}
|
|
251
|
-
>
|
|
252
|
-
Exportar
|
|
253
|
-
</Button>
|
|
254
|
-
)}
|
|
255
|
-
{onSelect ? (
|
|
256
|
-
<Button
|
|
257
|
-
disabled={filtered.length == 0}
|
|
258
|
-
color={filtered.length == 0 ? "white" : "primary"}
|
|
259
|
-
onClick={(e) => {
|
|
260
|
-
onSelect?.(filtered.map(({ _edited, ...r }) => r));
|
|
261
|
-
}}
|
|
262
|
-
>
|
|
263
|
-
Guardar Selección
|
|
264
|
-
</Button>
|
|
265
|
-
) : (
|
|
266
|
-
onSave && (
|
|
267
|
-
<Button
|
|
268
|
-
onClick={(e) => {
|
|
269
|
-
onSave?.(rows.map(({ _edited, ...r }) => r));
|
|
270
|
-
}}
|
|
271
|
-
>
|
|
272
|
-
Guardar
|
|
273
|
-
</Button>
|
|
274
|
-
)
|
|
275
|
-
)}
|
|
276
|
-
</div>
|
|
277
|
-
)}
|
|
278
|
-
|
|
279
|
-
<DataGrid
|
|
280
|
-
rows={rows}
|
|
281
|
-
columns={columns as any}
|
|
282
|
-
checkboxSelection={Boolean(onSelect)}
|
|
283
|
-
rowSelectionModel={selectedRows}
|
|
284
|
-
onRowSelectionModelChange={(newSelection) => {
|
|
285
|
-
setSelectedRows(newSelection);
|
|
286
|
-
}}
|
|
287
|
-
sx={{
|
|
288
|
-
"& .MuiDataGrid-cell--editable": {
|
|
289
|
-
backgroundColor: "#c6d8f0",
|
|
290
|
-
fontWeight: 500,
|
|
291
|
-
},
|
|
292
|
-
}}
|
|
293
|
-
editMode="row"
|
|
294
|
-
processRowUpdate={handleRowUpdate}
|
|
295
|
-
pageSizeOptions={[5, 10]}
|
|
389
|
+
<Box
|
|
390
|
+
sx={{
|
|
391
|
+
display: "flex",
|
|
392
|
+
flexDirection: "column",
|
|
393
|
+
height: HEIGHT_MAP[rows.length] ?? height,
|
|
394
|
+
width,
|
|
395
|
+
zIndex: 999999999,
|
|
396
|
+
}}
|
|
397
|
+
>
|
|
398
|
+
{modal && (
|
|
399
|
+
<ModalDialog
|
|
400
|
+
open={open}
|
|
401
|
+
onClose={handleClose}
|
|
402
|
+
modal={modal}
|
|
403
|
+
selectedRow={modalRow}
|
|
296
404
|
/>
|
|
297
|
-
|
|
298
|
-
</Box>
|
|
299
|
-
)
|
|
300
|
-
);
|
|
301
|
-
}
|
|
405
|
+
)}
|
|
302
406
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
footer: FooterType;
|
|
309
|
-
}) {
|
|
310
|
-
const entries = Object.entries(footer);
|
|
311
|
-
if (!entries.length) return null;
|
|
407
|
+
{header && (
|
|
408
|
+
<div className="font-bold text-xl p-2 bg-blue-500 text-white border shadow rounded">
|
|
409
|
+
{header}
|
|
410
|
+
</div>
|
|
411
|
+
)}
|
|
312
412
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
? values.reduce((acc, v) => acc + v, 0) / values.length
|
|
321
|
-
: 0;
|
|
322
|
-
if (type === "count") return values.length;
|
|
323
|
-
return 0;
|
|
324
|
-
};
|
|
413
|
+
<Toolbar
|
|
414
|
+
exportName={exportName}
|
|
415
|
+
onSave={onSave}
|
|
416
|
+
onSelect={onSelect}
|
|
417
|
+
rows={rows}
|
|
418
|
+
filteredRows={filteredRows}
|
|
419
|
+
/>
|
|
325
420
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
421
|
+
<DataGrid
|
|
422
|
+
rows={rows}
|
|
423
|
+
columns={columns as any}
|
|
424
|
+
checkboxSelection={Boolean(onSelect)}
|
|
425
|
+
rowSelectionModel={selectedRows}
|
|
426
|
+
onRowSelectionModelChange={!modal ? setSelectedRows : undefined}
|
|
427
|
+
sx={{
|
|
428
|
+
"& .MuiDataGrid-cell--editable": {
|
|
429
|
+
backgroundColor: "#c6d8f0",
|
|
430
|
+
fontWeight: 500,
|
|
431
|
+
},
|
|
432
|
+
...(rows.length <= Object.keys(HEIGHT_MAP).length && {
|
|
433
|
+
"& .MuiDataGrid-filler": {
|
|
434
|
+
display: "none",
|
|
435
|
+
},
|
|
436
|
+
}),
|
|
437
|
+
}}
|
|
438
|
+
editMode="row"
|
|
439
|
+
processRowUpdate={handleRowUpdate}
|
|
440
|
+
pageSizeOptions={[5, 10]}
|
|
441
|
+
hideFooter={rows.length <= Object.keys(HEIGHT_MAP).length}
|
|
442
|
+
/>
|
|
331
443
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
{entries.map(([key, type]) => {
|
|
335
|
-
const value = compute(key, type);
|
|
336
|
-
const formatted =
|
|
337
|
-
type === "avg"
|
|
338
|
-
? value.toLocaleString(undefined, { maximumFractionDigits: 2 })
|
|
339
|
-
: value.toLocaleString();
|
|
340
|
-
return (
|
|
341
|
-
<span key={key}>
|
|
342
|
-
{label[type]} de <span className="text-gray-900">{key}</span>:{" "}
|
|
343
|
-
<span className="text-blue-700">{formatted}</span>
|
|
344
|
-
</span>
|
|
345
|
-
);
|
|
346
|
-
})}
|
|
347
|
-
</div>
|
|
444
|
+
<CustomFooter footer={footer} rows={rows} />
|
|
445
|
+
</Box>
|
|
348
446
|
);
|
|
349
447
|
}
|
|
448
|
+
|
|
449
|
+
// ─── Public export ────────────────────────────────────────────────────────────
|
|
450
|
+
|
|
451
|
+
export default function Table(props: TableProps) {
|
|
452
|
+
if (Array.isArray(props.data)) {
|
|
453
|
+
return <IHTable {...props} />;
|
|
454
|
+
}
|
|
455
|
+
return <KeyValueTable data={props.data} />;
|
|
456
|
+
}
|