@zauru-sdk/components 2.0.46 → 2.0.47

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.
@@ -14,10 +14,12 @@ import { LoadingInputSkeleton } from "../Skeletons/index.js";
14
14
  import { WithTooltip } from "../WithTooltip/index.js";
15
15
  import { TrashSvg } from "@zauru-sdk/icons";
16
16
  import { useFormContext } from "react-hook-form";
17
+ import { ComponentError } from "../Alerts/index.js";
17
18
 
18
19
  export type FooterColumnConfig = {
19
20
  content: React.ReactNode;
20
21
  className?: string;
22
+ name?: string;
21
23
  };
22
24
 
23
25
  type Props = {
@@ -65,6 +67,7 @@ const GenericDynamicTableErrorComponent = ({ name }: { name: string }) => {
65
67
  name="invoice_details"
66
68
  withoutBg
67
69
  editable={!show}
70
+ searcheables={[{ value: "id_number", label: "No. Contraseña" }]}
68
71
  defaultValue={
69
72
  invoiceDetailsDefaultValue ?? [{ id: crypto.randomUUID() }]
70
73
  }
@@ -105,7 +108,6 @@ const GenericDynamicTableErrorComponent = ({ name }: { name: string }) => {
105
108
  maxRows={2}
106
109
  />
107
110
  */
108
-
109
111
  export const GenericDynamicTable = (props: Props) => {
110
112
  const {
111
113
  columns,
@@ -127,412 +129,430 @@ export const GenericDynamicTable = (props: Props) => {
127
129
  maxRows,
128
130
  } = props;
129
131
 
130
- const [tableData, setTableData] = useState<RowDataType[]>(defaultValue);
131
- const [deletedData, setDeletedData] = useState<RowDataType[]>([]);
132
- const [search, setSearch] = useState("");
133
- const [filteredTableData, setFilteredTableData] =
134
- useState<RowDataType[]>(tableData);
135
- const [currentPage, setCurrentPage] = useState(1);
136
- const [itemsPerPage, setItemsPerPage] = useState(defaultItemsPerPage);
132
+ try {
133
+ const [tableData, setTableData] = useState<RowDataType[]>(defaultValue);
134
+ const [deletedData, setDeletedData] = useState<RowDataType[]>([]);
135
+ const [search, setSearch] = useState("");
136
+ const [filteredTableData, setFilteredTableData] =
137
+ useState<RowDataType[]>(tableData);
138
+ const [currentPage, setCurrentPage] = useState(1);
139
+ const [itemsPerPage, setItemsPerPage] = useState(defaultItemsPerPage);
137
140
 
138
- useEffect(() => {
139
- if (defaultValue.length) {
140
- setTableData(defaultValue);
141
- }
142
- }, []);
141
+ useEffect(() => {
142
+ if (defaultValue.length) {
143
+ setTableData(defaultValue);
144
+ }
145
+ }, []);
143
146
 
144
- useEffect(() => {
145
- setFilteredTableData(tableData);
146
- }, [tableData]);
147
+ useEffect(() => {
148
+ setFilteredTableData(tableData);
149
+ }, [tableData]);
147
150
 
148
- useEffect(() => {
149
- changeFilteredData();
150
- }, [tableData, search]);
151
+ useEffect(() => {
152
+ changeFilteredData();
153
+ }, [tableData, search]);
151
154
 
152
- const totalPages = () => {
153
- return Math.ceil(filteredTableData.length / itemsPerPage);
154
- };
155
+ const totalPages = () => {
156
+ return Math.ceil(filteredTableData.length / itemsPerPage);
157
+ };
155
158
 
156
- const addRow = () => {
157
- if (maxRows && tableData.length >= maxRows) {
158
- return;
159
- }
160
- const defs: { [key: string]: any } = {};
161
- columns.forEach((x) => {
162
- defs[`${x.name}`] =
163
- x.type == "label" || x.type == "textField"
164
- ? ""
165
- : x.type == "selectField"
166
- ? 0
167
- : x.type == "checkbox"
168
- ? false
169
- : 0;
170
- });
171
- setTableData((prevData) => [
172
- ...prevData,
173
- { id: generateClientUUID(), ...defs },
174
- ]);
175
- };
159
+ const addRow = () => {
160
+ if (maxRows && tableData.length >= maxRows) {
161
+ return;
162
+ }
163
+ const defs: { [key: string]: any } = {};
164
+ columns.forEach((x) => {
165
+ defs[`${x.name}`] =
166
+ x.type == "label" || x.type == "textField"
167
+ ? ""
168
+ : x.type == "selectField"
169
+ ? 0
170
+ : x.type == "checkbox"
171
+ ? false
172
+ : 0;
173
+ });
174
+ setTableData((prevData) => [
175
+ ...prevData,
176
+ { id: generateClientUUID(), ...defs },
177
+ ]);
178
+ };
176
179
 
177
- const removeRow = (rowId: string) => {
178
- const newDeletedData = [...deletedData];
179
- const deletedItem = tableData?.find((x) => x.id === rowId);
180
- if (deletedItem && !isNaN(deletedItem.id)) {
181
- newDeletedData.push(deletedItem);
182
- }
183
- setDeletedData(newDeletedData);
184
- setTableData((prevData) => prevData?.filter((x) => x.id !== rowId));
185
- };
180
+ const removeRow = (rowId: string) => {
181
+ const newDeletedData = [...deletedData];
182
+ const deletedItem = tableData?.find((x) => x.id === rowId);
183
+ if (deletedItem && !isNaN(deletedItem.id)) {
184
+ newDeletedData.push(deletedItem);
185
+ }
186
+ setDeletedData(newDeletedData);
187
+ setTableData((prevData) => prevData?.filter((x) => x.id !== rowId));
188
+ };
186
189
 
187
- const handleChange = (name: string, value: any, rowId: string) => {
188
- setTableData((prevData) => {
189
- const updatedData = prevData.map((row) => {
190
- if (row.id === rowId) {
191
- return { ...row, [name]: value };
192
- }
193
- return row;
190
+ const handleChange = (name: string, value: any, rowId: string) => {
191
+ setTableData((prevData) => {
192
+ const updatedData = prevData.map((row) => {
193
+ if (row.id === rowId) {
194
+ return { ...row, [name]: value };
195
+ }
196
+ return row;
197
+ });
198
+ onChange && onChange(updatedData);
199
+ return updatedData;
194
200
  });
195
- onChange && onChange(updatedData);
196
- return updatedData;
197
- });
198
- };
201
+ };
199
202
 
200
- const renderHeader = () => {
201
- if (orientation === "horizontal") {
202
- return (
203
- <tr style={{ ...thCSSProperties }}>
204
- {columns.map((column, index) => {
205
- const ancho =
206
- column.width ?? (editable ? 94 : 100) / (columns.length ?? 1);
207
- return (
208
- <th
209
- key={index}
210
- className={`text-left align-middle p-2 ${thElementsClassName} ${
211
- column.headerClassName || ""
212
- }`}
213
- style={{ width: `${ancho}%` }}
214
- >
215
- {column.label}
216
- </th>
217
- );
218
- })}
219
- {editable && <th style={{ width: "4%" }}></th>}
220
- </tr>
221
- );
222
- } else {
223
- return null;
224
- }
225
- };
203
+ const renderHeader = () => {
204
+ if (orientation === "horizontal") {
205
+ return (
206
+ <tr style={{ ...thCSSProperties }}>
207
+ {columns.map((column, index) => {
208
+ const ancho =
209
+ column.width ?? (editable ? 94 : 100) / (columns.length ?? 1);
210
+ return (
211
+ <th
212
+ key={index}
213
+ className={`text-left align-middle p-2 ${thElementsClassName} ${
214
+ column.headerClassName || ""
215
+ }`}
216
+ style={{ width: `${ancho}%` }}
217
+ >
218
+ {column.label}
219
+ </th>
220
+ );
221
+ })}
222
+ {editable && <th style={{ width: "4%" }}></th>}
223
+ </tr>
224
+ );
225
+ } else {
226
+ return null;
227
+ }
228
+ };
226
229
 
227
- const renderRow = (rowData: RowDataType, index: number) => {
228
- if (orientation === "horizontal") {
229
- return (
230
- <tr
231
- key={rowData.id}
232
- className={index % 2 === 0 ? `${withoutBg ? "" : "bg-gray-200"}` : ""}
233
- >
234
- {columns.map((column) => renderCell(rowData, column))}
235
- {editable && renderDeleteButton(rowData)}
236
- </tr>
237
- );
238
- } else {
239
- return columns.map((column) => (
240
- <tr
241
- key={`${rowData.id}-${column.name}`}
242
- className={index % 2 === 0 ? `${withoutBg ? "" : "bg-gray-200"}` : ""}
243
- >
244
- <th
245
- className={`text-left align-middle p-2 ${thElementsClassName} ${
246
- column.headerClassName || ""
247
- }`}
230
+ const renderRow = (rowData: RowDataType, index: number) => {
231
+ if (orientation === "horizontal") {
232
+ return (
233
+ <tr
234
+ key={rowData.id}
235
+ className={
236
+ index % 2 === 0 ? `${withoutBg ? "" : "bg-gray-200"}` : ""
237
+ }
248
238
  >
249
- {column.label}
250
- </th>
251
- {renderCell(rowData, column)}
252
- {editable &&
253
- column === columns[columns.length - 1] &&
254
- renderDeleteButton(rowData)}
255
- </tr>
256
- ));
257
- }
258
- };
239
+ {columns.map((column) => renderCell(rowData, column))}
240
+ {editable && renderDeleteButton(rowData)}
241
+ </tr>
242
+ );
243
+ } else {
244
+ return columns.map((column) => (
245
+ <tr
246
+ key={`${rowData.id}-${column.name}`}
247
+ className={
248
+ index % 2 === 0 ? `${withoutBg ? "" : "bg-gray-200"}` : ""
249
+ }
250
+ >
251
+ <th
252
+ className={`text-left align-middle p-2 ${thElementsClassName} ${
253
+ column.headerClassName || ""
254
+ }`}
255
+ >
256
+ {column.label}
257
+ </th>
258
+ {renderCell(rowData, column)}
259
+ {editable &&
260
+ column === columns[columns.length - 1] &&
261
+ renderDeleteButton(rowData)}
262
+ </tr>
263
+ ));
264
+ }
265
+ };
259
266
 
260
- const renderCell = (
261
- rowData: RowDataType,
262
- column: GenericDynamicTableColumn
263
- ) => {
264
- if (loading) {
265
- return (
266
- <td
267
- key={`${rowData.id}-${column.name}`}
268
- className={`align-middle p-1 ${column.cellClassName || ""}`}
269
- >
270
- <LoadingInputSkeleton />
271
- </td>
272
- );
273
- }
274
- const tempVal = rowData[column.name as any];
267
+ const renderCell = (
268
+ rowData: RowDataType,
269
+ column: GenericDynamicTableColumn
270
+ ) => {
271
+ if (loading) {
272
+ return (
273
+ <td
274
+ key={`${rowData.id}-${column.name}`}
275
+ className={`align-middle p-1 ${column.cellClassName || ""}`}
276
+ >
277
+ <LoadingInputSkeleton />
278
+ </td>
279
+ );
280
+ }
281
+ const tempVal = rowData[column.name as any];
282
+
283
+ const defaultVal =
284
+ column.type === "selectField"
285
+ ? column.options?.find((x) => x.value === tempVal)
286
+ : tempVal;
287
+
288
+ if (column.type === "label") {
289
+ return (
290
+ <td
291
+ key={`${rowData.id}-${column.name}`}
292
+ className={`align-middle p-1 ${column.cellClassName || ""}`}
293
+ >
294
+ <div>{defaultVal}</div>
295
+ </td>
296
+ );
297
+ }
298
+
299
+ const FieldComponent =
300
+ column.type === "textField"
301
+ ? TextField
302
+ : column.type === "checkbox"
303
+ ? CheckBox
304
+ : SelectField;
275
305
 
276
- const defaultVal =
277
- column.type === "selectField"
278
- ? column.options?.find((x) => x.value === tempVal)
279
- : tempVal;
306
+ const setTableValue = (columnName: string, newValue: any) => {
307
+ handleChange(columnName, newValue, rowData.id);
308
+ };
280
309
 
281
- if (column.type === "label") {
282
310
  return (
283
311
  <td
284
312
  key={`${rowData.id}-${column.name}`}
285
313
  className={`align-middle p-1 ${column.cellClassName || ""}`}
286
314
  >
287
- <div>{defaultVal}</div>
315
+ {column.loadingOptions ? (
316
+ <LoadingInputSkeleton />
317
+ ) : (
318
+ <FieldComponent
319
+ key={`${rowData.id}-${column.name}`}
320
+ name={`${rowData.id}-${column.name}`}
321
+ type={column.textFieldType}
322
+ integer={!!column.integer}
323
+ disabled={column.disabled}
324
+ isClearable
325
+ onChange={(value: any) => {
326
+ const sendValue = value?.value ?? value;
327
+ handleChange(column.name, sendValue, rowData.id);
328
+ column.onChange &&
329
+ column.onChange(rowData, sendValue, setTableValue);
330
+ }}
331
+ defaultValue={defaultVal}
332
+ options={column.options ?? []}
333
+ />
334
+ )}
288
335
  </td>
289
336
  );
290
- }
291
-
292
- const FieldComponent =
293
- column.type === "textField"
294
- ? TextField
295
- : column.type === "checkbox"
296
- ? CheckBox
297
- : SelectField;
298
-
299
- const setTableValue = (columnName: string, newValue: any) => {
300
- handleChange(columnName, newValue, rowData.id);
301
337
  };
302
338
 
303
- return (
304
- <td
305
- key={`${rowData.id}-${column.name}`}
306
- className={`align-middle p-1 ${column.cellClassName || ""}`}
307
- >
308
- {column.loadingOptions ? (
309
- <LoadingInputSkeleton />
310
- ) : (
311
- <FieldComponent
312
- key={`${rowData.id}-${column.name}`}
313
- name={`${rowData.id}-${column.name}`}
314
- type={column.textFieldType}
315
- integer={!!column.integer}
316
- disabled={column.disabled}
317
- isClearable
318
- onChange={(value: any) => {
319
- const sendValue = value?.value ?? value;
320
- handleChange(column.name, sendValue, rowData.id);
321
- column.onChange &&
322
- column.onChange(rowData, sendValue, setTableValue);
339
+ const renderDeleteButton = (rowData: RowDataType) => (
340
+ <td className="align-middle w-16">
341
+ <WithTooltip text="Eliminar">
342
+ <button
343
+ className="bg-red-500 hover:bg-red-600 font-bold py-1 px-2 rounded ml-2"
344
+ onClick={(
345
+ event: React.MouseEvent<HTMLButtonElement, MouseEvent>
346
+ ) => {
347
+ event.preventDefault();
348
+ event.stopPropagation();
349
+ createModal({
350
+ title: "¿Está seguro que quiere eliminar este registro?",
351
+ description:
352
+ "Una vez eliminada la información no podrá ser recuperada.",
353
+ }).then((response) => {
354
+ if (response === "OK") {
355
+ removeRow(rowData.id);
356
+ }
357
+ });
323
358
  }}
324
- defaultValue={defaultVal}
325
- options={column.options ?? []}
326
- />
327
- )}
359
+ type="button"
360
+ >
361
+ <TrashSvg />
362
+ </button>
363
+ </WithTooltip>
328
364
  </td>
329
365
  );
330
- };
331
366
 
332
- const renderDeleteButton = (rowData: RowDataType) => (
333
- <td className="align-middle w-16">
334
- <WithTooltip text="Eliminar">
335
- <button
336
- className="bg-red-500 hover:bg-red-600 font-bold py-1 px-2 rounded ml-2"
337
- onClick={(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
338
- event.preventDefault();
339
- event.stopPropagation();
340
- createModal({
341
- title: "¿Está seguro que quiere eliminar este registro?",
342
- description:
343
- "Una vez eliminada la información no podrá ser recuperada.",
344
- }).then((response) => {
345
- if (response === "OK") {
346
- removeRow(rowData.id);
347
- }
348
- });
349
- }}
350
- type="button"
351
- >
352
- <TrashSvg />
353
- </button>
354
- </WithTooltip>
355
- </td>
356
- );
357
-
358
- const renderRows = () => {
359
- let mapeable = filteredTableData.slice(
360
- (currentPage - 1) * itemsPerPage,
361
- currentPage * itemsPerPage
362
- );
367
+ const renderRows = () => {
368
+ let mapeable = filteredTableData.slice(
369
+ (currentPage - 1) * itemsPerPage,
370
+ currentPage * itemsPerPage
371
+ );
363
372
 
364
- if (loading) {
365
- mapeable = [
366
- { id: 1 },
367
- { id: 2 },
368
- { id: 3 },
369
- { id: 4 },
370
- { id: 5 },
371
- { id: 6 },
372
- { id: 7 },
373
- { id: 8 },
374
- { id: 9 },
375
- { id: 10 },
376
- ] as RowDataType[];
377
- }
373
+ if (loading) {
374
+ mapeable = [
375
+ { id: 1 },
376
+ { id: 2 },
377
+ { id: 3 },
378
+ { id: 4 },
379
+ { id: 5 },
380
+ { id: 6 },
381
+ { id: 7 },
382
+ { id: 8 },
383
+ { id: 9 },
384
+ { id: 10 },
385
+ ] as RowDataType[];
386
+ }
378
387
 
379
- return mapeable.map((rowData, index) => renderRow(rowData, index));
380
- };
388
+ return mapeable.map((rowData, index) => renderRow(rowData, index));
389
+ };
381
390
 
382
- const handleChangeSearch = (newSearch: string) => {
383
- setSearch(newSearch);
384
- };
391
+ const handleChangeSearch = (newSearch: string) => {
392
+ setSearch(newSearch);
393
+ };
385
394
 
386
- const changeFilteredData = () => {
387
- if (search) {
388
- const filteredData = tableData.filter((rowData) => {
389
- for (const searchable of searcheables) {
390
- const column = columns.find((col) => col.name === searchable.value);
391
- if (column) {
392
- const tempVal = rowData[column.name as any];
393
- const defaultVal =
394
- column.type === "selectField"
395
- ? column.options?.find((x) => x.value === tempVal)?.label
396
- : tempVal;
397
- if (
398
- defaultVal
399
- ?.toString()
400
- .toLowerCase()
401
- .includes(search.toLowerCase())
402
- ) {
403
- return true;
395
+ const changeFilteredData = () => {
396
+ if (search) {
397
+ const filteredData = tableData.filter((rowData) => {
398
+ for (const searchable of searcheables) {
399
+ const column = columns.find((col) => col.name === searchable.value);
400
+ if (column) {
401
+ const tempVal = rowData[column.name as any];
402
+ const defaultVal =
403
+ column.type === "selectField"
404
+ ? column.options?.find((x) => x.value === tempVal)?.label
405
+ : tempVal;
406
+ if (
407
+ defaultVal
408
+ ?.toString()
409
+ .toLowerCase()
410
+ .includes(search.toLowerCase())
411
+ ) {
412
+ return true;
413
+ }
404
414
  }
405
415
  }
406
- }
407
- return false;
408
- });
409
- setFilteredTableData(filteredData);
410
- } else {
411
- setFilteredTableData(tableData);
412
- }
413
- };
416
+ return false;
417
+ });
418
+ setFilteredTableData(filteredData);
419
+ } else {
420
+ setFilteredTableData(tableData);
421
+ }
422
+ };
414
423
 
415
- return (
416
- <>
417
- {name && (
418
- <>
419
- <GenericDynamicTableErrorComponent name={name} />
420
- <input
421
- name={name}
422
- type="hidden"
423
- value={JSON.stringify(tableData)}
424
- hidden
425
- />
426
- <input
427
- name={`deleted_${name}`}
428
- type="hidden"
429
- value={JSON.stringify(deletedData)}
430
- hidden
431
- />
432
- </>
433
- )}
434
- <div className={`${className}`}>
435
- {searcheables.length > 0 && (
436
- <div>
437
- <TextField
438
- className="mb-2"
439
- name="search"
440
- title={`Buscar por: ${searcheables
441
- .map((x) => x.label)
442
- .join(", ")}`}
443
- onChange={handleChangeSearch}
444
- disabled={loading}
424
+ return (
425
+ <>
426
+ {name && (
427
+ <>
428
+ <GenericDynamicTableErrorComponent name={name} />
429
+ <input
430
+ name={name}
431
+ type="hidden"
432
+ value={JSON.stringify(tableData)}
433
+ hidden
434
+ />
435
+ <input
436
+ name={`deleted_${name}`}
437
+ type="hidden"
438
+ value={JSON.stringify(deletedData)}
439
+ hidden
445
440
  />
446
- </div>
441
+ </>
447
442
  )}
448
- <table className="w-full">
449
- {orientation === "horizontal" && <thead>{renderHeader()}</thead>}
450
- <tbody>{renderRows()}</tbody>
451
- {editable && (
452
- <tfoot>
453
- <tr>
454
- <td
455
- colSpan={
456
- orientation === "horizontal" ? columns.length + 1 : 2
457
- }
458
- className="align-middle"
459
- >
460
- {(!maxRows || tableData.length < maxRows) && (
461
- <button
462
- className="bg-blue-500 hover:bg-blue-600 font-bold py-2 px-4 rounded"
463
- onClick={(
464
- event: React.MouseEvent<HTMLButtonElement, MouseEvent>
465
- ) => {
466
- event.preventDefault();
467
- event.stopPropagation();
468
- addRow();
469
- }}
470
- type="button"
471
- >
472
- +
473
- </button>
474
- )}
475
- </td>
476
- </tr>
477
- </tfoot>
443
+ <div className={`${className}`}>
444
+ {searcheables.length > 0 && (
445
+ <div>
446
+ <TextField
447
+ className="mb-2"
448
+ name="search"
449
+ title={`Buscar por: ${searcheables
450
+ .map((x) => x.label)
451
+ .join(", ")}`}
452
+ onChange={handleChangeSearch}
453
+ disabled={loading}
454
+ />
455
+ </div>
478
456
  )}
479
- {footerRow && (
480
- <tfoot className="border-t-2 border-black">
481
- <tr>
482
- {footerRow.map((column, index) => (
457
+ <table className="w-full">
458
+ {orientation === "horizontal" && <thead>{renderHeader()}</thead>}
459
+ <tbody>{renderRows()}</tbody>
460
+ {editable && (
461
+ <tfoot>
462
+ <tr>
483
463
  <td
484
- key={index}
485
- colSpan={orientation === "vertical" ? 2 : 1}
486
- className={`align-middle ${column.className || ""}`}
464
+ colSpan={
465
+ orientation === "horizontal" ? columns.length + 1 : 2
466
+ }
467
+ className="align-middle"
487
468
  >
488
- {column.content}
469
+ {(!maxRows || tableData.length < maxRows) && (
470
+ <button
471
+ className="bg-blue-500 hover:bg-blue-600 font-bold py-2 px-4 rounded"
472
+ onClick={(
473
+ event: React.MouseEvent<HTMLButtonElement, MouseEvent>
474
+ ) => {
475
+ event.preventDefault();
476
+ event.stopPropagation();
477
+ addRow();
478
+ }}
479
+ type="button"
480
+ >
481
+ +
482
+ </button>
483
+ )}
489
484
  </td>
490
- ))}
491
- </tr>
492
- </tfoot>
493
- )}
494
- </table>
495
- {paginated && totalPages() > 1 && (
496
- <div className="flex justify-between items-center mt-4">
497
- <div className="flex items-center">
498
- <Button
499
- type="button"
500
- disabled={currentPage === 1}
501
- onClickSave={() =>
502
- setCurrentPage((old) => Math.max(old - 1, 1))
503
- }
504
- >
505
- Anterior
506
- </Button>
507
- <span className="mx-2">{`Página ${currentPage} de ${totalPages()}`}</span>
508
- <Button
509
- type="button"
510
- disabled={currentPage === totalPages()}
511
- onClickSave={() =>
512
- setCurrentPage((old) => Math.min(old + 1, totalPages()))
513
- }
514
- >
515
- Siguiente
516
- </Button>
517
- </div>
518
- <div>
519
- <select
520
- value={itemsPerPage}
521
- onChange={(e) => {
522
- setItemsPerPage(Number(e.target.value));
523
- setCurrentPage(1); // resetear la página al cambiar los elementos por página
524
- }}
525
- >
526
- {itemsPerPageOptions.map((option) => (
527
- <option key={option} value={option}>
528
- {option} elementos por página
529
- </option>
530
- ))}
531
- </select>
485
+ </tr>
486
+ </tfoot>
487
+ )}
488
+ {footerRow && (
489
+ <tfoot className="border-t-2 border-black">
490
+ <tr>
491
+ {columns.map((column, index) => {
492
+ const footerCell = footerRow.find(
493
+ (fc) => fc.name === column.name
494
+ );
495
+ return (
496
+ <td
497
+ key={index}
498
+ colSpan={orientation === "vertical" ? 2 : 1}
499
+ className={`align-middle ${
500
+ footerCell?.className || ""
501
+ }`}
502
+ >
503
+ {footerCell ? footerCell.content : <></>}
504
+ </td>
505
+ );
506
+ })}
507
+ {editable && <td></td>}
508
+ </tr>
509
+ </tfoot>
510
+ )}
511
+ </table>
512
+ {paginated && totalPages() > 1 && (
513
+ <div className="flex justify-between items-center mt-4">
514
+ <div className="flex items-center">
515
+ <Button
516
+ type="button"
517
+ disabled={currentPage === 1}
518
+ onClickSave={() =>
519
+ setCurrentPage((old) => Math.max(old - 1, 1))
520
+ }
521
+ >
522
+ Anterior
523
+ </Button>
524
+ <span className="mx-2">{`Página ${currentPage} de ${totalPages()}`}</span>
525
+ <Button
526
+ type="button"
527
+ disabled={currentPage === totalPages()}
528
+ onClickSave={() =>
529
+ setCurrentPage((old) => Math.min(old + 1, totalPages()))
530
+ }
531
+ >
532
+ Siguiente
533
+ </Button>
534
+ </div>
535
+ <div>
536
+ <select
537
+ value={itemsPerPage}
538
+ onChange={(e) => {
539
+ setItemsPerPage(Number(e.target.value));
540
+ setCurrentPage(1); // resetear la página al cambiar los elementos por página
541
+ }}
542
+ >
543
+ {itemsPerPageOptions.map((option) => (
544
+ <option key={option} value={option}>
545
+ {option} elementos por página
546
+ </option>
547
+ ))}
548
+ </select>
549
+ </div>
532
550
  </div>
533
- </div>
534
- )}
535
- </div>
536
- </>
537
- );
551
+ )}
552
+ </div>
553
+ </>
554
+ );
555
+ } catch (error: any) {
556
+ return <ComponentError error={error} componentName="GenericDynamicTable" />;
557
+ }
538
558
  };