@zauru-sdk/components 1.0.54 → 1.0.60
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/CHANGELOG.md +48 -0
- package/LICENCE.md +11 -11
- package/package.json +7 -7
- package/src/Alerts/ErrorBoundaryAlert/ErrorBoundaryAlert.tsx +66 -66
- package/src/Alerts/StaticAlert.tsx +121 -121
- package/src/Alerts/index.ts +2 -2
- package/src/BlockUI/BlockUI.tsx +50 -50
- package/src/BlockUI/index.tsx +1 -1
- package/src/Buttons/Button.tsx +90 -90
- package/src/Buttons/index.ts +1 -1
- package/src/Card/Card.tsx +24 -24
- package/src/Card/index.ts +1 -1
- package/src/Chat/ChatLayout.tsx +131 -131
- package/src/Chat/ChatMessageHistory.tsx +142 -142
- package/src/Chat/index.ts +2 -2
- package/src/ConnectionState/ConnectionState.tsx +27 -27
- package/src/ConnectionState/index.tsx +1 -1
- package/src/Containers/BodyContainer.tsx +14 -14
- package/src/Containers/ButtonSectionContainer.tsx +21 -21
- package/src/Containers/Container.tsx +39 -39
- package/src/Containers/DoubleContainer.tsx +48 -48
- package/src/Containers/MainContainer.tsx +17 -17
- package/src/Containers/OutletContainer.tsx +14 -14
- package/src/Containers/SubContainer.tsx +37 -37
- package/src/Containers/index.ts +7 -7
- package/src/DynamicTable/BasicPrintDynamicTable.tsx +73 -73
- package/src/DynamicTable/DynamicPrintTable.tsx +288 -288
- package/src/DynamicTable/DynamicTable.tsx +405 -405
- package/src/DynamicTable/GenericDynamicTable.tsx +456 -456
- package/src/DynamicTable/index.tsx +4 -4
- package/src/Footer/Footer.tsx +50 -50
- package/src/Footer/index.tsx +1 -1
- package/src/Form/Checkbox/index.tsx +96 -96
- package/src/Form/Checklist/index.tsx +35 -35
- package/src/Form/DatePicker/index.tsx +132 -132
- package/src/Form/DynamicBaculoForm/index.tsx +361 -361
- package/src/Form/FieldContainer/DoubleFieldContainer.tsx +35 -35
- package/src/Form/FieldContainer/QuadrupleFieldContainer.tsx +36 -36
- package/src/Form/FieldContainer/TripleFieldContainer.tsx +35 -35
- package/src/Form/FieldContainer/index.ts +3 -3
- package/src/Form/FileUpload/index.tsx +184 -184
- package/src/Form/FormButtons/index.tsx +78 -78
- package/src/Form/FormLayout/index.tsx +37 -37
- package/src/Form/SelectField/index.tsx +237 -237
- package/src/Form/TextArea/index.tsx +125 -125
- package/src/Form/TextField/index.tsx +194 -194
- package/src/Form/TimePicker/index.tsx +127 -127
- package/src/Form/YesNo/index.tsx +77 -77
- package/src/Form/index.ts +13 -13
- package/src/Labels/InfoLabel/index.tsx +21 -21
- package/src/Labels/index.tsx +1 -1
- package/src/Layouts/homeLayout/index.tsx +34 -34
- package/src/Layouts/index.ts +1 -1
- package/src/LineSeparator/LineSeparator.tsx +3 -3
- package/src/LineSeparator/index.tsx +1 -1
- package/src/Modal/Modal.tsx +104 -104
- package/src/Modal/index.tsx +1 -1
- package/src/NavBar/NavBar.tsx +223 -223
- package/src/NavBar/NavBar.types.ts +64 -64
- package/src/NavBar/NavBar.utils.ts +58 -58
- package/src/NavBar/index.tsx +5 -5
- package/src/ProgressBar/ProgressBar.tsx +25 -25
- package/src/ProgressBar/ProgressCircle.tsx +75 -75
- package/src/ProgressBar/index.tsx +2 -2
- package/src/Skeletons/LoadingInputSkeleton.tsx +12 -12
- package/src/Skeletons/index.ts +1 -1
- package/src/Tab/Tab.tsx +63 -63
- package/src/Tab/index.ts +1 -1
- package/src/Table/ZauruTable.tsx +265 -265
- package/src/Table/index.tsx +1 -1
- package/src/TaskList/TaskList.tsx +88 -88
- package/src/TaskList/index.ts +1 -1
- package/src/Titles/LabelArray.tsx +17 -17
- package/src/Titles/TableColumnTitle.tsx +9 -9
- package/src/Titles/TitleH1.tsx +10 -10
- package/src/Titles/TitleH2.tsx +10 -10
- package/src/Titles/TitleH3.tsx +10 -10
- package/src/Titles/index.ts +5 -5
- package/src/Tooltip/Tooltip.tsx +42 -42
- package/src/Tooltip/index.ts +1 -1
- package/src/WithTooltip/WithTooltip.tsx +21 -21
- package/src/WithTooltip/index.tsx +1 -1
- package/src/Wizards/StepWizard.tsx +88 -88
- package/src/Wizards/index.ts +1 -1
- package/src/Zendesk/Chat.tsx +83 -83
- package/src/Zendesk/index.ts +2 -2
- package/src/Zendesk/zendesk.config.ts +40 -40
- package/src/index.ts +24 -24
- package/src/postcss.config.mjs +5 -5
- package/src/tailwind.config.ts +10 -10
- package/src/tailwind.css +3 -3
- package/tsconfig.cjs.json +8 -8
- package/tsconfig.esm.json +11 -11
- package/tsconfig.json +17 -17
|
@@ -1,456 +1,456 @@
|
|
|
1
|
-
import React, { useEffect, useState } from "react";
|
|
2
|
-
import { SelectFieldWithoutValidation } from "../Form/SelectField/index.js";
|
|
3
|
-
import { TextFieldWithoutValidation } from "../Form/TextField/index.js";
|
|
4
|
-
import { CheckboxWithoutValidation } from "../Form/Checkbox/index.js";
|
|
5
|
-
import { createModal } from "../Modal/index.js";
|
|
6
|
-
import { Button } from "../Buttons/index.js";
|
|
7
|
-
import {
|
|
8
|
-
GenericDynamicTableColumn,
|
|
9
|
-
RowDataType,
|
|
10
|
-
SelectFieldOption,
|
|
11
|
-
} from "@zauru-sdk/types";
|
|
12
|
-
import { useAppSelector } from "@zauru-sdk/redux";
|
|
13
|
-
import { generateClientUUID } from "@zauru-sdk/common";
|
|
14
|
-
import { LoadingInputSkeleton } from "../Skeletons/index.js";
|
|
15
|
-
import { WithTooltip } from "../WithTooltip/index.js";
|
|
16
|
-
import { TrashSvg } from "@zauru-sdk/icons";
|
|
17
|
-
|
|
18
|
-
type Props = {
|
|
19
|
-
name?: string;
|
|
20
|
-
className?: string;
|
|
21
|
-
columns: GenericDynamicTableColumn[];
|
|
22
|
-
onChange?: (tableState?: any[]) => void;
|
|
23
|
-
defaultValue?: RowDataType[];
|
|
24
|
-
footerRow?: RowDataType;
|
|
25
|
-
thCSSProperties?: React.CSSProperties;
|
|
26
|
-
thElementsClassName?: string;
|
|
27
|
-
editable?: boolean;
|
|
28
|
-
searcheables?: SelectFieldOption[];
|
|
29
|
-
loading?: boolean;
|
|
30
|
-
paginated?: boolean;
|
|
31
|
-
defaultItemsPerPage?: number;
|
|
32
|
-
itemsPerPageOptions?: number[];
|
|
33
|
-
withoutBg?: boolean;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const GenericDynamicTableErrorComponent = ({
|
|
37
|
-
name,
|
|
38
|
-
formName,
|
|
39
|
-
}: {
|
|
40
|
-
name: string;
|
|
41
|
-
formName?: string;
|
|
42
|
-
}) => {
|
|
43
|
-
const { formValidations } = useAppSelector((state) => state.formValidation);
|
|
44
|
-
const error = formValidations[formName ?? "-1"]?.[name ?? "-1"];
|
|
45
|
-
|
|
46
|
-
return error ? (
|
|
47
|
-
<p className={`mt-2 text-sm text-red-600 dark:text-red-500`}>
|
|
48
|
-
<span className="font-medium">Oops!</span> {error}
|
|
49
|
-
</p>
|
|
50
|
-
) : (
|
|
51
|
-
<></>
|
|
52
|
-
);
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
export const GenericDynamicTable = (props: Props) => {
|
|
56
|
-
const {
|
|
57
|
-
columns,
|
|
58
|
-
onChange,
|
|
59
|
-
className,
|
|
60
|
-
footerRow,
|
|
61
|
-
defaultValue = [],
|
|
62
|
-
thCSSProperties,
|
|
63
|
-
thElementsClassName = "",
|
|
64
|
-
editable = true,
|
|
65
|
-
searcheables = [],
|
|
66
|
-
loading = false,
|
|
67
|
-
paginated = true,
|
|
68
|
-
defaultItemsPerPage = 10,
|
|
69
|
-
itemsPerPageOptions = [10, 50, 100],
|
|
70
|
-
name,
|
|
71
|
-
withoutBg = false,
|
|
72
|
-
} = props;
|
|
73
|
-
|
|
74
|
-
const [tableData, setTableData] = useState<RowDataType[]>(defaultValue);
|
|
75
|
-
const [deletedData, setDeletedData] = useState<RowDataType[]>([]);
|
|
76
|
-
const [search, setSearch] = useState("");
|
|
77
|
-
const [filteredTableData, setFilteredTableData] =
|
|
78
|
-
useState<RowDataType[]>(tableData);
|
|
79
|
-
const [currentPage, setCurrentPage] = useState(1);
|
|
80
|
-
const [itemsPerPage, setItemsPerPage] = useState(defaultItemsPerPage);
|
|
81
|
-
|
|
82
|
-
useEffect(() => {
|
|
83
|
-
if (defaultValue.length) {
|
|
84
|
-
setTableData(defaultValue);
|
|
85
|
-
}
|
|
86
|
-
}, []);
|
|
87
|
-
|
|
88
|
-
useEffect(() => {
|
|
89
|
-
setFilteredTableData(tableData);
|
|
90
|
-
}, [tableData]);
|
|
91
|
-
|
|
92
|
-
useEffect(() => {
|
|
93
|
-
changeFilteredData();
|
|
94
|
-
}, [tableData, search]);
|
|
95
|
-
|
|
96
|
-
const totalPages = () => {
|
|
97
|
-
return Math.ceil(filteredTableData.length / itemsPerPage);
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
const addRow = () => {
|
|
101
|
-
const defs: { [key: string]: any } = {};
|
|
102
|
-
columns.forEach((x) => {
|
|
103
|
-
defs[`${x.name}`] =
|
|
104
|
-
x.type == "label" || x.type == "textField"
|
|
105
|
-
? ""
|
|
106
|
-
: x.type == "selectField"
|
|
107
|
-
? 0
|
|
108
|
-
: x.type == "checkbox"
|
|
109
|
-
? false
|
|
110
|
-
: 0;
|
|
111
|
-
});
|
|
112
|
-
setTableData((prevData) => [
|
|
113
|
-
...prevData,
|
|
114
|
-
{ id: generateClientUUID(), ...defs },
|
|
115
|
-
]);
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
const removeRow = (rowId: string) => {
|
|
119
|
-
const newDeletedData = [...deletedData];
|
|
120
|
-
const deletedItem = tableData?.find((x) => x.id === rowId);
|
|
121
|
-
if (deletedItem && !isNaN(deletedItem.id)) {
|
|
122
|
-
newDeletedData.push(deletedItem);
|
|
123
|
-
}
|
|
124
|
-
setDeletedData(newDeletedData);
|
|
125
|
-
setTableData((prevData) => prevData?.filter((x) => x.id !== rowId));
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
const handleChange = (name: string, value: any, rowId: string) => {
|
|
129
|
-
// Encontrar el índice de la fila que está cambiando
|
|
130
|
-
const rowIndex = tableData.findIndex((x) => x.id === rowId);
|
|
131
|
-
|
|
132
|
-
// Crear una copia del objeto en esa fila
|
|
133
|
-
const updatedRow = { ...tableData[rowIndex], [name]: value };
|
|
134
|
-
|
|
135
|
-
// Copiar todo el array
|
|
136
|
-
const updatedData = [...tableData];
|
|
137
|
-
|
|
138
|
-
// Reemplazar el objeto en la fila que cambió
|
|
139
|
-
updatedData[rowIndex] = updatedRow;
|
|
140
|
-
|
|
141
|
-
// Actualizar el estado con el nuevo array
|
|
142
|
-
setTableData(updatedData);
|
|
143
|
-
onChange && onChange(updatedData);
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
const renderHeader = () => (
|
|
147
|
-
<tr style={{ ...thCSSProperties }}>
|
|
148
|
-
{columns.map((column, index) => {
|
|
149
|
-
const ancho =
|
|
150
|
-
column.width ?? (editable ? 94 : 100) / (columns.length ?? 1);
|
|
151
|
-
return (
|
|
152
|
-
<th
|
|
153
|
-
key={index}
|
|
154
|
-
className={`text-left align-middle p-2 ${thElementsClassName}`}
|
|
155
|
-
style={{ width: `${ancho}%` }}
|
|
156
|
-
>
|
|
157
|
-
{column.label}
|
|
158
|
-
</th>
|
|
159
|
-
);
|
|
160
|
-
})}
|
|
161
|
-
{editable && <th style={{ width: "4%" }}></th>}
|
|
162
|
-
</tr>
|
|
163
|
-
);
|
|
164
|
-
|
|
165
|
-
const renderRow = (rowData: RowDataType, index: number) => {
|
|
166
|
-
return (
|
|
167
|
-
<tr
|
|
168
|
-
key={rowData.id}
|
|
169
|
-
className={index % 2 === 0 ? `${withoutBg ? "" : "bg-gray-200"}` : ""}
|
|
170
|
-
>
|
|
171
|
-
{columns.map((column) => {
|
|
172
|
-
if (loading) {
|
|
173
|
-
return (
|
|
174
|
-
<td
|
|
175
|
-
key={`${rowData.id}-${column.name}`}
|
|
176
|
-
className="align-middle p-1"
|
|
177
|
-
>
|
|
178
|
-
<LoadingInputSkeleton />
|
|
179
|
-
</td>
|
|
180
|
-
);
|
|
181
|
-
}
|
|
182
|
-
const tempVal = rowData[column.name as any];
|
|
183
|
-
|
|
184
|
-
const defaultVal =
|
|
185
|
-
column.type === "selectField"
|
|
186
|
-
? column.options?.find((x) => x.value === tempVal)
|
|
187
|
-
: tempVal;
|
|
188
|
-
|
|
189
|
-
if (column.type === "label") {
|
|
190
|
-
return (
|
|
191
|
-
<td
|
|
192
|
-
key={`${rowData.id}-${column.name}`}
|
|
193
|
-
className="align-middle p-1"
|
|
194
|
-
>
|
|
195
|
-
<div>{defaultVal}</div>
|
|
196
|
-
</td>
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const FieldComponent =
|
|
201
|
-
column.type === "textField"
|
|
202
|
-
? TextFieldWithoutValidation
|
|
203
|
-
: column.type === "checkbox"
|
|
204
|
-
? CheckboxWithoutValidation
|
|
205
|
-
: SelectFieldWithoutValidation;
|
|
206
|
-
|
|
207
|
-
const setTableValue = (columnName: string, newValue: any) => {
|
|
208
|
-
setTableData((prevState) => {
|
|
209
|
-
// Encontrar el índice de la fila que está cambiando
|
|
210
|
-
const rowIndex = prevState.findIndex((x) => x.id === rowData.id);
|
|
211
|
-
// Crear una copia del objeto en esa fila
|
|
212
|
-
const updatedRow = {
|
|
213
|
-
...prevState[rowIndex],
|
|
214
|
-
[columnName]: newValue,
|
|
215
|
-
};
|
|
216
|
-
// Copiar todo el array
|
|
217
|
-
const updatedData = [...prevState];
|
|
218
|
-
|
|
219
|
-
// Reemplazar el objeto en la fila que cambió
|
|
220
|
-
updatedData[rowIndex] = updatedRow;
|
|
221
|
-
return updatedData;
|
|
222
|
-
});
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
return (
|
|
226
|
-
<td
|
|
227
|
-
key={`${rowData.id}-${column.name}`}
|
|
228
|
-
className="align-middle p-1"
|
|
229
|
-
>
|
|
230
|
-
{column.loadingOptions ? (
|
|
231
|
-
<LoadingInputSkeleton />
|
|
232
|
-
) : (
|
|
233
|
-
<FieldComponent
|
|
234
|
-
key={`${rowData.id}-${column.name}`}
|
|
235
|
-
//name={column.name}
|
|
236
|
-
type={column.textFieldType}
|
|
237
|
-
integer={!!column.integer}
|
|
238
|
-
disabled={column.disabled}
|
|
239
|
-
isClearable
|
|
240
|
-
onChange={(value: any) => {
|
|
241
|
-
const sendValue = value?.value ?? value;
|
|
242
|
-
handleChange(column.name, sendValue, rowData.id);
|
|
243
|
-
column.onChange &&
|
|
244
|
-
column.onChange(rowData, sendValue, setTableValue);
|
|
245
|
-
}}
|
|
246
|
-
defaultValue={defaultVal}
|
|
247
|
-
options={column.options ?? []}
|
|
248
|
-
/>
|
|
249
|
-
)}
|
|
250
|
-
</td>
|
|
251
|
-
);
|
|
252
|
-
})}
|
|
253
|
-
{editable && (
|
|
254
|
-
<td className="align-middle w-16">
|
|
255
|
-
<WithTooltip text="Eliminar">
|
|
256
|
-
<button
|
|
257
|
-
className="bg-red-500 hover:bg-red-600 font-bold py-1 px-2 rounded ml-2"
|
|
258
|
-
onClick={(
|
|
259
|
-
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
|
|
260
|
-
) => {
|
|
261
|
-
event.preventDefault();
|
|
262
|
-
event.stopPropagation();
|
|
263
|
-
createModal({
|
|
264
|
-
title: "¿Está seguro que quiere eliminar este registro?",
|
|
265
|
-
description:
|
|
266
|
-
"Una vez eliminada la información no podrá ser recuperada.",
|
|
267
|
-
}).then((response) => {
|
|
268
|
-
if (response === "OK") {
|
|
269
|
-
removeRow(rowData.id);
|
|
270
|
-
}
|
|
271
|
-
});
|
|
272
|
-
}}
|
|
273
|
-
type="button"
|
|
274
|
-
>
|
|
275
|
-
<TrashSvg />
|
|
276
|
-
</button>
|
|
277
|
-
</WithTooltip>
|
|
278
|
-
</td>
|
|
279
|
-
)}
|
|
280
|
-
</tr>
|
|
281
|
-
);
|
|
282
|
-
};
|
|
283
|
-
|
|
284
|
-
const renderRows = () => {
|
|
285
|
-
let mapeable = filteredTableData.slice(
|
|
286
|
-
(currentPage - 1) * itemsPerPage,
|
|
287
|
-
currentPage * itemsPerPage
|
|
288
|
-
);
|
|
289
|
-
|
|
290
|
-
if (loading) {
|
|
291
|
-
mapeable = [
|
|
292
|
-
{ id: 1 },
|
|
293
|
-
{ id: 2 },
|
|
294
|
-
{ id: 3 },
|
|
295
|
-
{ id: 4 },
|
|
296
|
-
{ id: 5 },
|
|
297
|
-
{ id: 6 },
|
|
298
|
-
{ id: 7 },
|
|
299
|
-
{ id: 8 },
|
|
300
|
-
{ id: 9 },
|
|
301
|
-
{ id: 10 },
|
|
302
|
-
] as RowDataType[];
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
return mapeable.map((rowData, index) => renderRow(rowData, index));
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
const handleChangeSearch = (newSearch: string) => {
|
|
309
|
-
setSearch(newSearch);
|
|
310
|
-
};
|
|
311
|
-
|
|
312
|
-
const changeFilteredData = () => {
|
|
313
|
-
if (search) {
|
|
314
|
-
const filteredData = tableData.filter((rowData) => {
|
|
315
|
-
for (const searchable of searcheables) {
|
|
316
|
-
const column = columns.find((col) => col.name === searchable.value);
|
|
317
|
-
if (column) {
|
|
318
|
-
const tempVal = rowData[column.name as any];
|
|
319
|
-
const defaultVal =
|
|
320
|
-
column.type === "selectField"
|
|
321
|
-
? column.options?.find((x) => x.value === tempVal)?.label
|
|
322
|
-
: tempVal;
|
|
323
|
-
if (
|
|
324
|
-
defaultVal
|
|
325
|
-
?.toString()
|
|
326
|
-
.toLowerCase()
|
|
327
|
-
.includes(search.toLowerCase())
|
|
328
|
-
) {
|
|
329
|
-
return true;
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
return false;
|
|
334
|
-
});
|
|
335
|
-
setFilteredTableData(filteredData);
|
|
336
|
-
} else {
|
|
337
|
-
setFilteredTableData(tableData);
|
|
338
|
-
}
|
|
339
|
-
};
|
|
340
|
-
|
|
341
|
-
return (
|
|
342
|
-
<>
|
|
343
|
-
{name && (
|
|
344
|
-
<>
|
|
345
|
-
<GenericDynamicTableErrorComponent name={name} />
|
|
346
|
-
<input
|
|
347
|
-
name={name}
|
|
348
|
-
type="hidden"
|
|
349
|
-
value={JSON.stringify(tableData)}
|
|
350
|
-
hidden
|
|
351
|
-
/>
|
|
352
|
-
<input
|
|
353
|
-
name={`deleted_${name}`}
|
|
354
|
-
type="hidden"
|
|
355
|
-
value={JSON.stringify(deletedData)}
|
|
356
|
-
hidden
|
|
357
|
-
/>
|
|
358
|
-
</>
|
|
359
|
-
)}
|
|
360
|
-
<div className={`${className}`}>
|
|
361
|
-
{searcheables.length > 0 && (
|
|
362
|
-
<div>
|
|
363
|
-
<TextFieldWithoutValidation
|
|
364
|
-
className="mb-2"
|
|
365
|
-
name="search"
|
|
366
|
-
title={`Buscar por: ${searcheables
|
|
367
|
-
.map((x) => x.label)
|
|
368
|
-
.join(", ")}`}
|
|
369
|
-
onChange={handleChangeSearch}
|
|
370
|
-
disabled={loading}
|
|
371
|
-
/>
|
|
372
|
-
</div>
|
|
373
|
-
)}
|
|
374
|
-
<table className="w-full">
|
|
375
|
-
<thead>{renderHeader()}</thead>
|
|
376
|
-
<tbody>{renderRows()}</tbody>
|
|
377
|
-
{footerRow && !editable ? (
|
|
378
|
-
<tfoot className="border-t-2 border-black">
|
|
379
|
-
<tr>
|
|
380
|
-
{Object.keys(footerRow ?? {})?.map((x, indx) => {
|
|
381
|
-
return (
|
|
382
|
-
<td className="align-middle" key={indx}>
|
|
383
|
-
{(footerRow as any)[x]}
|
|
384
|
-
</td>
|
|
385
|
-
);
|
|
386
|
-
})}
|
|
387
|
-
</tr>
|
|
388
|
-
</tfoot>
|
|
389
|
-
) : editable ? (
|
|
390
|
-
<tfoot>
|
|
391
|
-
<tr>
|
|
392
|
-
<td className="align-middle">
|
|
393
|
-
<button
|
|
394
|
-
className="bg-blue-500 hover:bg-blue-600 font-bold py-2 px-4 rounded"
|
|
395
|
-
onClick={(
|
|
396
|
-
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
|
|
397
|
-
) => {
|
|
398
|
-
event.preventDefault();
|
|
399
|
-
event.stopPropagation();
|
|
400
|
-
addRow();
|
|
401
|
-
}}
|
|
402
|
-
type="button"
|
|
403
|
-
>
|
|
404
|
-
+
|
|
405
|
-
</button>
|
|
406
|
-
</td>
|
|
407
|
-
</tr>
|
|
408
|
-
</tfoot>
|
|
409
|
-
) : (
|
|
410
|
-
<></>
|
|
411
|
-
)}
|
|
412
|
-
</table>
|
|
413
|
-
{paginated && totalPages() > 1 && (
|
|
414
|
-
<div className="flex justify-between items-center mt-4">
|
|
415
|
-
<div className="flex items-center">
|
|
416
|
-
<Button
|
|
417
|
-
type="button"
|
|
418
|
-
disabled={currentPage === 1}
|
|
419
|
-
onClickSave={() =>
|
|
420
|
-
setCurrentPage((old) => Math.max(old - 1, 1))
|
|
421
|
-
}
|
|
422
|
-
>
|
|
423
|
-
Anterior
|
|
424
|
-
</Button>
|
|
425
|
-
<span className="mx-2">{`Página ${currentPage} de ${totalPages()}`}</span>
|
|
426
|
-
<Button
|
|
427
|
-
type="button"
|
|
428
|
-
disabled={currentPage === totalPages()}
|
|
429
|
-
onClickSave={() =>
|
|
430
|
-
setCurrentPage((old) => Math.min(old + 1, totalPages()))
|
|
431
|
-
}
|
|
432
|
-
>
|
|
433
|
-
Siguiente
|
|
434
|
-
</Button>
|
|
435
|
-
</div>
|
|
436
|
-
<div>
|
|
437
|
-
<select
|
|
438
|
-
value={itemsPerPage}
|
|
439
|
-
onChange={(e) => {
|
|
440
|
-
setItemsPerPage(Number(e.target.value));
|
|
441
|
-
setCurrentPage(1); // resetear la página al cambiar los elementos por página
|
|
442
|
-
}}
|
|
443
|
-
>
|
|
444
|
-
{itemsPerPageOptions.map((option) => (
|
|
445
|
-
<option key={option} value={option}>
|
|
446
|
-
{option} elementos por página
|
|
447
|
-
</option>
|
|
448
|
-
))}
|
|
449
|
-
</select>
|
|
450
|
-
</div>
|
|
451
|
-
</div>
|
|
452
|
-
)}
|
|
453
|
-
</div>
|
|
454
|
-
</>
|
|
455
|
-
);
|
|
456
|
-
};
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
2
|
+
import { SelectFieldWithoutValidation } from "../Form/SelectField/index.js";
|
|
3
|
+
import { TextFieldWithoutValidation } from "../Form/TextField/index.js";
|
|
4
|
+
import { CheckboxWithoutValidation } from "../Form/Checkbox/index.js";
|
|
5
|
+
import { createModal } from "../Modal/index.js";
|
|
6
|
+
import { Button } from "../Buttons/index.js";
|
|
7
|
+
import {
|
|
8
|
+
GenericDynamicTableColumn,
|
|
9
|
+
RowDataType,
|
|
10
|
+
SelectFieldOption,
|
|
11
|
+
} from "@zauru-sdk/types";
|
|
12
|
+
import { useAppSelector } from "@zauru-sdk/redux";
|
|
13
|
+
import { generateClientUUID } from "@zauru-sdk/common";
|
|
14
|
+
import { LoadingInputSkeleton } from "../Skeletons/index.js";
|
|
15
|
+
import { WithTooltip } from "../WithTooltip/index.js";
|
|
16
|
+
import { TrashSvg } from "@zauru-sdk/icons";
|
|
17
|
+
|
|
18
|
+
type Props = {
|
|
19
|
+
name?: string;
|
|
20
|
+
className?: string;
|
|
21
|
+
columns: GenericDynamicTableColumn[];
|
|
22
|
+
onChange?: (tableState?: any[]) => void;
|
|
23
|
+
defaultValue?: RowDataType[];
|
|
24
|
+
footerRow?: RowDataType;
|
|
25
|
+
thCSSProperties?: React.CSSProperties;
|
|
26
|
+
thElementsClassName?: string;
|
|
27
|
+
editable?: boolean;
|
|
28
|
+
searcheables?: SelectFieldOption[];
|
|
29
|
+
loading?: boolean;
|
|
30
|
+
paginated?: boolean;
|
|
31
|
+
defaultItemsPerPage?: number;
|
|
32
|
+
itemsPerPageOptions?: number[];
|
|
33
|
+
withoutBg?: boolean;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const GenericDynamicTableErrorComponent = ({
|
|
37
|
+
name,
|
|
38
|
+
formName,
|
|
39
|
+
}: {
|
|
40
|
+
name: string;
|
|
41
|
+
formName?: string;
|
|
42
|
+
}) => {
|
|
43
|
+
const { formValidations } = useAppSelector((state) => state.formValidation);
|
|
44
|
+
const error = formValidations[formName ?? "-1"]?.[name ?? "-1"];
|
|
45
|
+
|
|
46
|
+
return error ? (
|
|
47
|
+
<p className={`mt-2 text-sm text-red-600 dark:text-red-500`}>
|
|
48
|
+
<span className="font-medium">Oops!</span> {error}
|
|
49
|
+
</p>
|
|
50
|
+
) : (
|
|
51
|
+
<></>
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const GenericDynamicTable = (props: Props) => {
|
|
56
|
+
const {
|
|
57
|
+
columns,
|
|
58
|
+
onChange,
|
|
59
|
+
className,
|
|
60
|
+
footerRow,
|
|
61
|
+
defaultValue = [],
|
|
62
|
+
thCSSProperties,
|
|
63
|
+
thElementsClassName = "",
|
|
64
|
+
editable = true,
|
|
65
|
+
searcheables = [],
|
|
66
|
+
loading = false,
|
|
67
|
+
paginated = true,
|
|
68
|
+
defaultItemsPerPage = 10,
|
|
69
|
+
itemsPerPageOptions = [10, 50, 100],
|
|
70
|
+
name,
|
|
71
|
+
withoutBg = false,
|
|
72
|
+
} = props;
|
|
73
|
+
|
|
74
|
+
const [tableData, setTableData] = useState<RowDataType[]>(defaultValue);
|
|
75
|
+
const [deletedData, setDeletedData] = useState<RowDataType[]>([]);
|
|
76
|
+
const [search, setSearch] = useState("");
|
|
77
|
+
const [filteredTableData, setFilteredTableData] =
|
|
78
|
+
useState<RowDataType[]>(tableData);
|
|
79
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
80
|
+
const [itemsPerPage, setItemsPerPage] = useState(defaultItemsPerPage);
|
|
81
|
+
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (defaultValue.length) {
|
|
84
|
+
setTableData(defaultValue);
|
|
85
|
+
}
|
|
86
|
+
}, []);
|
|
87
|
+
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
setFilteredTableData(tableData);
|
|
90
|
+
}, [tableData]);
|
|
91
|
+
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
changeFilteredData();
|
|
94
|
+
}, [tableData, search]);
|
|
95
|
+
|
|
96
|
+
const totalPages = () => {
|
|
97
|
+
return Math.ceil(filteredTableData.length / itemsPerPage);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const addRow = () => {
|
|
101
|
+
const defs: { [key: string]: any } = {};
|
|
102
|
+
columns.forEach((x) => {
|
|
103
|
+
defs[`${x.name}`] =
|
|
104
|
+
x.type == "label" || x.type == "textField"
|
|
105
|
+
? ""
|
|
106
|
+
: x.type == "selectField"
|
|
107
|
+
? 0
|
|
108
|
+
: x.type == "checkbox"
|
|
109
|
+
? false
|
|
110
|
+
: 0;
|
|
111
|
+
});
|
|
112
|
+
setTableData((prevData) => [
|
|
113
|
+
...prevData,
|
|
114
|
+
{ id: generateClientUUID(), ...defs },
|
|
115
|
+
]);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const removeRow = (rowId: string) => {
|
|
119
|
+
const newDeletedData = [...deletedData];
|
|
120
|
+
const deletedItem = tableData?.find((x) => x.id === rowId);
|
|
121
|
+
if (deletedItem && !isNaN(deletedItem.id)) {
|
|
122
|
+
newDeletedData.push(deletedItem);
|
|
123
|
+
}
|
|
124
|
+
setDeletedData(newDeletedData);
|
|
125
|
+
setTableData((prevData) => prevData?.filter((x) => x.id !== rowId));
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const handleChange = (name: string, value: any, rowId: string) => {
|
|
129
|
+
// Encontrar el índice de la fila que está cambiando
|
|
130
|
+
const rowIndex = tableData.findIndex((x) => x.id === rowId);
|
|
131
|
+
|
|
132
|
+
// Crear una copia del objeto en esa fila
|
|
133
|
+
const updatedRow = { ...tableData[rowIndex], [name]: value };
|
|
134
|
+
|
|
135
|
+
// Copiar todo el array
|
|
136
|
+
const updatedData = [...tableData];
|
|
137
|
+
|
|
138
|
+
// Reemplazar el objeto en la fila que cambió
|
|
139
|
+
updatedData[rowIndex] = updatedRow;
|
|
140
|
+
|
|
141
|
+
// Actualizar el estado con el nuevo array
|
|
142
|
+
setTableData(updatedData);
|
|
143
|
+
onChange && onChange(updatedData);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const renderHeader = () => (
|
|
147
|
+
<tr style={{ ...thCSSProperties }}>
|
|
148
|
+
{columns.map((column, index) => {
|
|
149
|
+
const ancho =
|
|
150
|
+
column.width ?? (editable ? 94 : 100) / (columns.length ?? 1);
|
|
151
|
+
return (
|
|
152
|
+
<th
|
|
153
|
+
key={index}
|
|
154
|
+
className={`text-left align-middle p-2 ${thElementsClassName}`}
|
|
155
|
+
style={{ width: `${ancho}%` }}
|
|
156
|
+
>
|
|
157
|
+
{column.label}
|
|
158
|
+
</th>
|
|
159
|
+
);
|
|
160
|
+
})}
|
|
161
|
+
{editable && <th style={{ width: "4%" }}></th>}
|
|
162
|
+
</tr>
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
const renderRow = (rowData: RowDataType, index: number) => {
|
|
166
|
+
return (
|
|
167
|
+
<tr
|
|
168
|
+
key={rowData.id}
|
|
169
|
+
className={index % 2 === 0 ? `${withoutBg ? "" : "bg-gray-200"}` : ""}
|
|
170
|
+
>
|
|
171
|
+
{columns.map((column) => {
|
|
172
|
+
if (loading) {
|
|
173
|
+
return (
|
|
174
|
+
<td
|
|
175
|
+
key={`${rowData.id}-${column.name}`}
|
|
176
|
+
className="align-middle p-1"
|
|
177
|
+
>
|
|
178
|
+
<LoadingInputSkeleton />
|
|
179
|
+
</td>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
const tempVal = rowData[column.name as any];
|
|
183
|
+
|
|
184
|
+
const defaultVal =
|
|
185
|
+
column.type === "selectField"
|
|
186
|
+
? column.options?.find((x) => x.value === tempVal)
|
|
187
|
+
: tempVal;
|
|
188
|
+
|
|
189
|
+
if (column.type === "label") {
|
|
190
|
+
return (
|
|
191
|
+
<td
|
|
192
|
+
key={`${rowData.id}-${column.name}`}
|
|
193
|
+
className="align-middle p-1"
|
|
194
|
+
>
|
|
195
|
+
<div>{defaultVal}</div>
|
|
196
|
+
</td>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const FieldComponent =
|
|
201
|
+
column.type === "textField"
|
|
202
|
+
? TextFieldWithoutValidation
|
|
203
|
+
: column.type === "checkbox"
|
|
204
|
+
? CheckboxWithoutValidation
|
|
205
|
+
: SelectFieldWithoutValidation;
|
|
206
|
+
|
|
207
|
+
const setTableValue = (columnName: string, newValue: any) => {
|
|
208
|
+
setTableData((prevState) => {
|
|
209
|
+
// Encontrar el índice de la fila que está cambiando
|
|
210
|
+
const rowIndex = prevState.findIndex((x) => x.id === rowData.id);
|
|
211
|
+
// Crear una copia del objeto en esa fila
|
|
212
|
+
const updatedRow = {
|
|
213
|
+
...prevState[rowIndex],
|
|
214
|
+
[columnName]: newValue,
|
|
215
|
+
};
|
|
216
|
+
// Copiar todo el array
|
|
217
|
+
const updatedData = [...prevState];
|
|
218
|
+
|
|
219
|
+
// Reemplazar el objeto en la fila que cambió
|
|
220
|
+
updatedData[rowIndex] = updatedRow;
|
|
221
|
+
return updatedData;
|
|
222
|
+
});
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
return (
|
|
226
|
+
<td
|
|
227
|
+
key={`${rowData.id}-${column.name}`}
|
|
228
|
+
className="align-middle p-1"
|
|
229
|
+
>
|
|
230
|
+
{column.loadingOptions ? (
|
|
231
|
+
<LoadingInputSkeleton />
|
|
232
|
+
) : (
|
|
233
|
+
<FieldComponent
|
|
234
|
+
key={`${rowData.id}-${column.name}`}
|
|
235
|
+
//name={column.name}
|
|
236
|
+
type={column.textFieldType}
|
|
237
|
+
integer={!!column.integer}
|
|
238
|
+
disabled={column.disabled}
|
|
239
|
+
isClearable
|
|
240
|
+
onChange={(value: any) => {
|
|
241
|
+
const sendValue = value?.value ?? value;
|
|
242
|
+
handleChange(column.name, sendValue, rowData.id);
|
|
243
|
+
column.onChange &&
|
|
244
|
+
column.onChange(rowData, sendValue, setTableValue);
|
|
245
|
+
}}
|
|
246
|
+
defaultValue={defaultVal}
|
|
247
|
+
options={column.options ?? []}
|
|
248
|
+
/>
|
|
249
|
+
)}
|
|
250
|
+
</td>
|
|
251
|
+
);
|
|
252
|
+
})}
|
|
253
|
+
{editable && (
|
|
254
|
+
<td className="align-middle w-16">
|
|
255
|
+
<WithTooltip text="Eliminar">
|
|
256
|
+
<button
|
|
257
|
+
className="bg-red-500 hover:bg-red-600 font-bold py-1 px-2 rounded ml-2"
|
|
258
|
+
onClick={(
|
|
259
|
+
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
|
|
260
|
+
) => {
|
|
261
|
+
event.preventDefault();
|
|
262
|
+
event.stopPropagation();
|
|
263
|
+
createModal({
|
|
264
|
+
title: "¿Está seguro que quiere eliminar este registro?",
|
|
265
|
+
description:
|
|
266
|
+
"Una vez eliminada la información no podrá ser recuperada.",
|
|
267
|
+
}).then((response) => {
|
|
268
|
+
if (response === "OK") {
|
|
269
|
+
removeRow(rowData.id);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
}}
|
|
273
|
+
type="button"
|
|
274
|
+
>
|
|
275
|
+
<TrashSvg />
|
|
276
|
+
</button>
|
|
277
|
+
</WithTooltip>
|
|
278
|
+
</td>
|
|
279
|
+
)}
|
|
280
|
+
</tr>
|
|
281
|
+
);
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
const renderRows = () => {
|
|
285
|
+
let mapeable = filteredTableData.slice(
|
|
286
|
+
(currentPage - 1) * itemsPerPage,
|
|
287
|
+
currentPage * itemsPerPage
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
if (loading) {
|
|
291
|
+
mapeable = [
|
|
292
|
+
{ id: 1 },
|
|
293
|
+
{ id: 2 },
|
|
294
|
+
{ id: 3 },
|
|
295
|
+
{ id: 4 },
|
|
296
|
+
{ id: 5 },
|
|
297
|
+
{ id: 6 },
|
|
298
|
+
{ id: 7 },
|
|
299
|
+
{ id: 8 },
|
|
300
|
+
{ id: 9 },
|
|
301
|
+
{ id: 10 },
|
|
302
|
+
] as RowDataType[];
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return mapeable.map((rowData, index) => renderRow(rowData, index));
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const handleChangeSearch = (newSearch: string) => {
|
|
309
|
+
setSearch(newSearch);
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const changeFilteredData = () => {
|
|
313
|
+
if (search) {
|
|
314
|
+
const filteredData = tableData.filter((rowData) => {
|
|
315
|
+
for (const searchable of searcheables) {
|
|
316
|
+
const column = columns.find((col) => col.name === searchable.value);
|
|
317
|
+
if (column) {
|
|
318
|
+
const tempVal = rowData[column.name as any];
|
|
319
|
+
const defaultVal =
|
|
320
|
+
column.type === "selectField"
|
|
321
|
+
? column.options?.find((x) => x.value === tempVal)?.label
|
|
322
|
+
: tempVal;
|
|
323
|
+
if (
|
|
324
|
+
defaultVal
|
|
325
|
+
?.toString()
|
|
326
|
+
.toLowerCase()
|
|
327
|
+
.includes(search.toLowerCase())
|
|
328
|
+
) {
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return false;
|
|
334
|
+
});
|
|
335
|
+
setFilteredTableData(filteredData);
|
|
336
|
+
} else {
|
|
337
|
+
setFilteredTableData(tableData);
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
return (
|
|
342
|
+
<>
|
|
343
|
+
{name && (
|
|
344
|
+
<>
|
|
345
|
+
<GenericDynamicTableErrorComponent name={name} />
|
|
346
|
+
<input
|
|
347
|
+
name={name}
|
|
348
|
+
type="hidden"
|
|
349
|
+
value={JSON.stringify(tableData)}
|
|
350
|
+
hidden
|
|
351
|
+
/>
|
|
352
|
+
<input
|
|
353
|
+
name={`deleted_${name}`}
|
|
354
|
+
type="hidden"
|
|
355
|
+
value={JSON.stringify(deletedData)}
|
|
356
|
+
hidden
|
|
357
|
+
/>
|
|
358
|
+
</>
|
|
359
|
+
)}
|
|
360
|
+
<div className={`${className}`}>
|
|
361
|
+
{searcheables.length > 0 && (
|
|
362
|
+
<div>
|
|
363
|
+
<TextFieldWithoutValidation
|
|
364
|
+
className="mb-2"
|
|
365
|
+
name="search"
|
|
366
|
+
title={`Buscar por: ${searcheables
|
|
367
|
+
.map((x) => x.label)
|
|
368
|
+
.join(", ")}`}
|
|
369
|
+
onChange={handleChangeSearch}
|
|
370
|
+
disabled={loading}
|
|
371
|
+
/>
|
|
372
|
+
</div>
|
|
373
|
+
)}
|
|
374
|
+
<table className="w-full">
|
|
375
|
+
<thead>{renderHeader()}</thead>
|
|
376
|
+
<tbody>{renderRows()}</tbody>
|
|
377
|
+
{footerRow && !editable ? (
|
|
378
|
+
<tfoot className="border-t-2 border-black">
|
|
379
|
+
<tr>
|
|
380
|
+
{Object.keys(footerRow ?? {})?.map((x, indx) => {
|
|
381
|
+
return (
|
|
382
|
+
<td className="align-middle" key={indx}>
|
|
383
|
+
{(footerRow as any)[x]}
|
|
384
|
+
</td>
|
|
385
|
+
);
|
|
386
|
+
})}
|
|
387
|
+
</tr>
|
|
388
|
+
</tfoot>
|
|
389
|
+
) : editable ? (
|
|
390
|
+
<tfoot>
|
|
391
|
+
<tr>
|
|
392
|
+
<td className="align-middle">
|
|
393
|
+
<button
|
|
394
|
+
className="bg-blue-500 hover:bg-blue-600 font-bold py-2 px-4 rounded"
|
|
395
|
+
onClick={(
|
|
396
|
+
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
|
|
397
|
+
) => {
|
|
398
|
+
event.preventDefault();
|
|
399
|
+
event.stopPropagation();
|
|
400
|
+
addRow();
|
|
401
|
+
}}
|
|
402
|
+
type="button"
|
|
403
|
+
>
|
|
404
|
+
+
|
|
405
|
+
</button>
|
|
406
|
+
</td>
|
|
407
|
+
</tr>
|
|
408
|
+
</tfoot>
|
|
409
|
+
) : (
|
|
410
|
+
<></>
|
|
411
|
+
)}
|
|
412
|
+
</table>
|
|
413
|
+
{paginated && totalPages() > 1 && (
|
|
414
|
+
<div className="flex justify-between items-center mt-4">
|
|
415
|
+
<div className="flex items-center">
|
|
416
|
+
<Button
|
|
417
|
+
type="button"
|
|
418
|
+
disabled={currentPage === 1}
|
|
419
|
+
onClickSave={() =>
|
|
420
|
+
setCurrentPage((old) => Math.max(old - 1, 1))
|
|
421
|
+
}
|
|
422
|
+
>
|
|
423
|
+
Anterior
|
|
424
|
+
</Button>
|
|
425
|
+
<span className="mx-2">{`Página ${currentPage} de ${totalPages()}`}</span>
|
|
426
|
+
<Button
|
|
427
|
+
type="button"
|
|
428
|
+
disabled={currentPage === totalPages()}
|
|
429
|
+
onClickSave={() =>
|
|
430
|
+
setCurrentPage((old) => Math.min(old + 1, totalPages()))
|
|
431
|
+
}
|
|
432
|
+
>
|
|
433
|
+
Siguiente
|
|
434
|
+
</Button>
|
|
435
|
+
</div>
|
|
436
|
+
<div>
|
|
437
|
+
<select
|
|
438
|
+
value={itemsPerPage}
|
|
439
|
+
onChange={(e) => {
|
|
440
|
+
setItemsPerPage(Number(e.target.value));
|
|
441
|
+
setCurrentPage(1); // resetear la página al cambiar los elementos por página
|
|
442
|
+
}}
|
|
443
|
+
>
|
|
444
|
+
{itemsPerPageOptions.map((option) => (
|
|
445
|
+
<option key={option} value={option}>
|
|
446
|
+
{option} elementos por página
|
|
447
|
+
</option>
|
|
448
|
+
))}
|
|
449
|
+
</select>
|
|
450
|
+
</div>
|
|
451
|
+
</div>
|
|
452
|
+
)}
|
|
453
|
+
</div>
|
|
454
|
+
</>
|
|
455
|
+
);
|
|
456
|
+
};
|