@zauru-sdk/components 2.0.3 → 2.0.4
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 +8 -0
- package/dist/DynamicTable/GenericDynamicTable.d.ts +57 -1
- package/dist/NavBar/NavBar.types.d.ts +1 -0
- package/dist/Skeletons/LoadingWindow.d.ts +11 -0
- package/dist/Skeletons/index.d.ts +1 -0
- package/dist/esm/DynamicTable/GenericDynamicTable.js +128 -77
- package/dist/esm/Form/SelectField/index.js +5 -0
- package/dist/esm/Form/TextField/index.js +1 -1
- package/dist/esm/NavBar/NavBar.js +3 -3
- package/dist/esm/Skeletons/LoadingWindow.js +6 -0
- package/dist/esm/Skeletons/index.js +1 -0
- package/package.json +6 -6
- package/src/DynamicTable/GenericDynamicTable.tsx +260 -178
- package/src/Form/SelectField/index.tsx +5 -0
- package/src/Form/TextField/index.tsx +1 -1
- package/src/NavBar/NavBar.tsx +33 -23
- package/src/NavBar/NavBar.types.ts +1 -0
- package/src/Skeletons/LoadingWindow.tsx +94 -0
- package/src/Skeletons/index.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [2.0.4](https://github.com/intuitiva/zauru-typescript-sdk/compare/v2.0.3...v2.0.4) (2024-10-03)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @zauru-sdk/components
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
6
14
|
## [2.0.3](https://github.com/intuitiva/zauru-typescript-sdk/compare/v2.0.2...v2.0.3) (2024-10-03)
|
|
7
15
|
|
|
8
16
|
**Note:** Version bump only for package @zauru-sdk/components
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { GenericDynamicTableColumn, RowDataType, SelectFieldOption } from "@zauru-sdk/types";
|
|
3
|
+
export type FooterColumnConfig = {
|
|
4
|
+
content: React.ReactNode;
|
|
5
|
+
className?: string;
|
|
6
|
+
};
|
|
3
7
|
type Props = {
|
|
4
8
|
name?: string;
|
|
5
9
|
className?: string;
|
|
6
10
|
columns: GenericDynamicTableColumn[];
|
|
7
11
|
onChange?: (tableState?: any[]) => void;
|
|
8
12
|
defaultValue?: RowDataType[];
|
|
9
|
-
footerRow?:
|
|
13
|
+
footerRow?: FooterColumnConfig[];
|
|
10
14
|
thCSSProperties?: React.CSSProperties;
|
|
11
15
|
thElementsClassName?: string;
|
|
12
16
|
editable?: boolean;
|
|
@@ -16,6 +20,58 @@ type Props = {
|
|
|
16
20
|
defaultItemsPerPage?: number;
|
|
17
21
|
itemsPerPageOptions?: number[];
|
|
18
22
|
withoutBg?: boolean;
|
|
23
|
+
orientation?: "horizontal" | "vertical";
|
|
24
|
+
maxRows?: number;
|
|
19
25
|
};
|
|
26
|
+
/**
|
|
27
|
+
*
|
|
28
|
+
* @param props
|
|
29
|
+
* @returns
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
*<GenericDynamicTable
|
|
33
|
+
name="invoice_details"
|
|
34
|
+
withoutBg
|
|
35
|
+
editable={!show}
|
|
36
|
+
defaultValue={
|
|
37
|
+
invoiceDetailsDefaultValue ?? [{ id: crypto.randomUUID() }]
|
|
38
|
+
}
|
|
39
|
+
columns={[
|
|
40
|
+
{
|
|
41
|
+
label: "Producto",
|
|
42
|
+
name: "item_id",
|
|
43
|
+
type: "selectField",
|
|
44
|
+
options: productOptions,
|
|
45
|
+
disabled: show,
|
|
46
|
+
onChange: (rowData, value, setTableValue) => {
|
|
47
|
+
const price = getProductPrice(value);
|
|
48
|
+
setTableValue("price", price);
|
|
49
|
+
},
|
|
50
|
+
headerClassName: "text-center font-bold",
|
|
51
|
+
cellClassName: "text-center",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
label: "Cantidad",
|
|
55
|
+
name: "quantity",
|
|
56
|
+
type: "textField",
|
|
57
|
+
textFieldType: "number",
|
|
58
|
+
disabled: show,
|
|
59
|
+
onChange: (rowData, value, setTableValue) => {
|
|
60
|
+
const price = rowData["price"] ?? 0;
|
|
61
|
+
const quantity = Number(value) || 0;
|
|
62
|
+
setTableValue("total", price * quantity);
|
|
63
|
+
},
|
|
64
|
+
headerClassName: "text-right font-semibold",
|
|
65
|
+
cellClassName: "text-right",
|
|
66
|
+
}
|
|
67
|
+
]}
|
|
68
|
+
footerRow={[
|
|
69
|
+
{ content: "Total", className: "text-left font-bold" },
|
|
70
|
+
{ content: calculateTotal(), className: "text-center" },
|
|
71
|
+
{ content: "", className: "text-center" }
|
|
72
|
+
]}
|
|
73
|
+
maxRows={2}
|
|
74
|
+
/>
|
|
75
|
+
*/
|
|
20
76
|
export declare const GenericDynamicTable: (props: Props) => import("react/jsx-runtime").JSX.Element;
|
|
21
77
|
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
interface LoadingItem {
|
|
3
|
+
name: string;
|
|
4
|
+
loading: boolean;
|
|
5
|
+
}
|
|
6
|
+
interface LoadingWindowProps {
|
|
7
|
+
loadingItems?: LoadingItem[];
|
|
8
|
+
description?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare const LoadingWindow: React.FC<LoadingWindowProps>;
|
|
11
|
+
export default LoadingWindow;
|
|
@@ -5,18 +5,68 @@ import { TextField } from "../Form/TextField/index.js";
|
|
|
5
5
|
import { CheckBox } from "../Form/Checkbox/index.js";
|
|
6
6
|
import { createModal } from "../Modal/index.js";
|
|
7
7
|
import { Button } from "../Buttons/index.js";
|
|
8
|
-
import { useAppSelector } from "@zauru-sdk/redux";
|
|
9
8
|
import { generateClientUUID } from "@zauru-sdk/common";
|
|
10
9
|
import { LoadingInputSkeleton } from "../Skeletons/index.js";
|
|
11
10
|
import { WithTooltip } from "../WithTooltip/index.js";
|
|
12
11
|
import { TrashSvg } from "@zauru-sdk/icons";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
12
|
+
import { useFormContext } from "react-hook-form";
|
|
13
|
+
const GenericDynamicTableErrorComponent = ({ name }) => {
|
|
14
|
+
const { formState: { errors }, } = useFormContext() || { formState: {} }; // Obtener el contexto solo si existe
|
|
15
|
+
const error = errors ? errors[name ?? "-1"] : undefined;
|
|
16
|
+
return error ? (_jsxs("p", { className: `mt-2 text-sm text-red-600 dark:text-red-500`, children: [_jsx("span", { className: "font-medium", children: "Oops!" }), " ", error?.message?.toString()] })) : (_jsx(_Fragment, {}));
|
|
17
17
|
};
|
|
18
|
+
/**
|
|
19
|
+
*
|
|
20
|
+
* @param props
|
|
21
|
+
* @returns
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
*<GenericDynamicTable
|
|
25
|
+
name="invoice_details"
|
|
26
|
+
withoutBg
|
|
27
|
+
editable={!show}
|
|
28
|
+
defaultValue={
|
|
29
|
+
invoiceDetailsDefaultValue ?? [{ id: crypto.randomUUID() }]
|
|
30
|
+
}
|
|
31
|
+
columns={[
|
|
32
|
+
{
|
|
33
|
+
label: "Producto",
|
|
34
|
+
name: "item_id",
|
|
35
|
+
type: "selectField",
|
|
36
|
+
options: productOptions,
|
|
37
|
+
disabled: show,
|
|
38
|
+
onChange: (rowData, value, setTableValue) => {
|
|
39
|
+
const price = getProductPrice(value);
|
|
40
|
+
setTableValue("price", price);
|
|
41
|
+
},
|
|
42
|
+
headerClassName: "text-center font-bold",
|
|
43
|
+
cellClassName: "text-center",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
label: "Cantidad",
|
|
47
|
+
name: "quantity",
|
|
48
|
+
type: "textField",
|
|
49
|
+
textFieldType: "number",
|
|
50
|
+
disabled: show,
|
|
51
|
+
onChange: (rowData, value, setTableValue) => {
|
|
52
|
+
const price = rowData["price"] ?? 0;
|
|
53
|
+
const quantity = Number(value) || 0;
|
|
54
|
+
setTableValue("total", price * quantity);
|
|
55
|
+
},
|
|
56
|
+
headerClassName: "text-right font-semibold",
|
|
57
|
+
cellClassName: "text-right",
|
|
58
|
+
}
|
|
59
|
+
]}
|
|
60
|
+
footerRow={[
|
|
61
|
+
{ content: "Total", className: "text-left font-bold" },
|
|
62
|
+
{ content: calculateTotal(), className: "text-center" },
|
|
63
|
+
{ content: "", className: "text-center" }
|
|
64
|
+
]}
|
|
65
|
+
maxRows={2}
|
|
66
|
+
/>
|
|
67
|
+
*/
|
|
18
68
|
export const GenericDynamicTable = (props) => {
|
|
19
|
-
const { columns, onChange, className, footerRow, defaultValue = [], thCSSProperties, thElementsClassName = "", editable = true, searcheables = [], loading = false, paginated = true, defaultItemsPerPage = 10, itemsPerPageOptions = [10, 50, 100], name, withoutBg = false, } = props;
|
|
69
|
+
const { columns, onChange, className, footerRow, defaultValue = [], thCSSProperties, thElementsClassName = "", editable = true, searcheables = [], loading = false, paginated = true, defaultItemsPerPage = 10, itemsPerPageOptions = [10, 50, 100], name, withoutBg = false, orientation = "horizontal", maxRows, } = props;
|
|
20
70
|
const [tableData, setTableData] = useState(defaultValue);
|
|
21
71
|
const [deletedData, setDeletedData] = useState([]);
|
|
22
72
|
const [search, setSearch] = useState("");
|
|
@@ -38,6 +88,9 @@ export const GenericDynamicTable = (props) => {
|
|
|
38
88
|
return Math.ceil(filteredTableData.length / itemsPerPage);
|
|
39
89
|
};
|
|
40
90
|
const addRow = () => {
|
|
91
|
+
if (maxRows && tableData.length >= maxRows) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
41
94
|
const defs = {};
|
|
42
95
|
columns.forEach((x) => {
|
|
43
96
|
defs[`${x.name}`] =
|
|
@@ -64,76 +117,76 @@ export const GenericDynamicTable = (props) => {
|
|
|
64
117
|
setTableData((prevData) => prevData?.filter((x) => x.id !== rowId));
|
|
65
118
|
};
|
|
66
119
|
const handleChange = (name, value, rowId) => {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
120
|
+
setTableData((prevData) => {
|
|
121
|
+
const updatedData = prevData.map((row) => {
|
|
122
|
+
if (row.id === rowId) {
|
|
123
|
+
return { ...row, [name]: value };
|
|
124
|
+
}
|
|
125
|
+
return row;
|
|
126
|
+
});
|
|
127
|
+
onChange && onChange(updatedData);
|
|
128
|
+
return updatedData;
|
|
129
|
+
});
|
|
130
|
+
};
|
|
131
|
+
const renderHeader = () => {
|
|
132
|
+
if (orientation === "horizontal") {
|
|
133
|
+
return (_jsxs("tr", { style: { ...thCSSProperties }, children: [columns.map((column, index) => {
|
|
134
|
+
const ancho = column.width ?? (editable ? 94 : 100) / (columns.length ?? 1);
|
|
135
|
+
return (_jsx("th", { className: `text-left align-middle p-2 ${thElementsClassName} ${column.headerClassName || ""}`, style: { width: `${ancho}%` }, children: column.label }, index));
|
|
136
|
+
}), editable && _jsx("th", { style: { width: "4%" } })] }));
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
78
141
|
};
|
|
79
|
-
const renderHeader = () => (_jsxs("tr", { style: { ...thCSSProperties }, children: [columns.map((column, index) => {
|
|
80
|
-
const ancho = column.width ?? (editable ? 94 : 100) / (columns.length ?? 1);
|
|
81
|
-
return (_jsx("th", { className: `text-left align-middle p-2 ${thElementsClassName}`, style: { width: `${ancho}%` }, children: column.label }, index));
|
|
82
|
-
}), editable && _jsx("th", { style: { width: "4%" } })] }));
|
|
83
142
|
const renderRow = (rowData, index) => {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (column.type === "label") {
|
|
93
|
-
return (_jsx("td", { className: "align-middle p-1", children: _jsx("div", { children: defaultVal }) }, `${rowData.id}-${column.name}`));
|
|
94
|
-
}
|
|
95
|
-
const FieldComponent = column.type === "textField"
|
|
96
|
-
? TextField
|
|
97
|
-
: column.type === "checkbox"
|
|
98
|
-
? CheckBox
|
|
99
|
-
: SelectField;
|
|
100
|
-
const setTableValue = (columnName, newValue) => {
|
|
101
|
-
setTableData((prevState) => {
|
|
102
|
-
// Encontrar el índice de la fila que está cambiando
|
|
103
|
-
const rowIndex = prevState.findIndex((x) => x.id === rowData.id);
|
|
104
|
-
// Crear una copia del objeto en esa fila
|
|
105
|
-
const updatedRow = {
|
|
106
|
-
...prevState[rowIndex],
|
|
107
|
-
[columnName]: newValue,
|
|
108
|
-
};
|
|
109
|
-
// Copiar todo el array
|
|
110
|
-
const updatedData = [...prevState];
|
|
111
|
-
// Reemplazar el objeto en la fila que cambió
|
|
112
|
-
updatedData[rowIndex] = updatedRow;
|
|
113
|
-
return updatedData;
|
|
114
|
-
});
|
|
115
|
-
};
|
|
116
|
-
return (_jsx("td", { className: "align-middle p-1", children: column.loadingOptions ? (_jsx(LoadingInputSkeleton, {})) : (_jsx(FieldComponent, {
|
|
117
|
-
//name={column.name}
|
|
118
|
-
type: column.textFieldType, integer: !!column.integer, disabled: column.disabled, isClearable: true, onChange: (value) => {
|
|
119
|
-
const sendValue = value?.value ?? value;
|
|
120
|
-
handleChange(column.name, sendValue, rowData.id);
|
|
121
|
-
column.onChange &&
|
|
122
|
-
column.onChange(rowData, sendValue, setTableValue);
|
|
123
|
-
}, defaultValue: defaultVal, options: column.options ?? [] }, `${rowData.id}-${column.name}`)) }, `${rowData.id}-${column.name}`));
|
|
124
|
-
}), editable && (_jsx("td", { className: "align-middle w-16", children: _jsx(WithTooltip, { text: "Eliminar", children: _jsx("button", { className: "bg-red-500 hover:bg-red-600 font-bold py-1 px-2 rounded ml-2", onClick: (event) => {
|
|
125
|
-
event.preventDefault();
|
|
126
|
-
event.stopPropagation();
|
|
127
|
-
createModal({
|
|
128
|
-
title: "¿Está seguro que quiere eliminar este registro?",
|
|
129
|
-
description: "Una vez eliminada la información no podrá ser recuperada.",
|
|
130
|
-
}).then((response) => {
|
|
131
|
-
if (response === "OK") {
|
|
132
|
-
removeRow(rowData.id);
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
}, type: "button", children: _jsx(TrashSvg, {}) }) }) }))] }, rowData.id));
|
|
143
|
+
if (orientation === "horizontal") {
|
|
144
|
+
return (_jsxs("tr", { className: index % 2 === 0 ? `${withoutBg ? "" : "bg-gray-200"}` : "", children: [columns.map((column) => renderCell(rowData, column)), editable && renderDeleteButton(rowData)] }, rowData.id));
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
return columns.map((column) => (_jsxs("tr", { className: index % 2 === 0 ? `${withoutBg ? "" : "bg-gray-200"}` : "", children: [_jsx("th", { className: `text-left align-middle p-2 ${thElementsClassName} ${column.headerClassName || ""}`, children: column.label }), renderCell(rowData, column), editable &&
|
|
148
|
+
column === columns[columns.length - 1] &&
|
|
149
|
+
renderDeleteButton(rowData)] }, `${rowData.id}-${column.name}`)));
|
|
150
|
+
}
|
|
136
151
|
};
|
|
152
|
+
const renderCell = (rowData, column) => {
|
|
153
|
+
if (loading) {
|
|
154
|
+
return (_jsx("td", { className: `align-middle p-1 ${column.cellClassName || ""}`, children: _jsx(LoadingInputSkeleton, {}) }, `${rowData.id}-${column.name}`));
|
|
155
|
+
}
|
|
156
|
+
const tempVal = rowData[column.name];
|
|
157
|
+
const defaultVal = column.type === "selectField"
|
|
158
|
+
? column.options?.find((x) => x.value === tempVal)
|
|
159
|
+
: tempVal;
|
|
160
|
+
if (column.type === "label") {
|
|
161
|
+
return (_jsx("td", { className: `align-middle p-1 ${column.cellClassName || ""}`, children: _jsx("div", { children: defaultVal }) }, `${rowData.id}-${column.name}`));
|
|
162
|
+
}
|
|
163
|
+
const FieldComponent = column.type === "textField"
|
|
164
|
+
? TextField
|
|
165
|
+
: column.type === "checkbox"
|
|
166
|
+
? CheckBox
|
|
167
|
+
: SelectField;
|
|
168
|
+
const setTableValue = (columnName, newValue) => {
|
|
169
|
+
handleChange(columnName, newValue, rowData.id);
|
|
170
|
+
};
|
|
171
|
+
return (_jsx("td", { className: `align-middle p-1 ${column.cellClassName || ""}`, children: column.loadingOptions ? (_jsx(LoadingInputSkeleton, {})) : (_jsx(FieldComponent, { name: `${rowData.id}-${column.name}`, type: column.textFieldType, integer: !!column.integer, disabled: column.disabled, isClearable: true, onChange: (value) => {
|
|
172
|
+
const sendValue = value?.value ?? value;
|
|
173
|
+
handleChange(column.name, sendValue, rowData.id);
|
|
174
|
+
column.onChange &&
|
|
175
|
+
column.onChange(rowData, sendValue, setTableValue);
|
|
176
|
+
}, defaultValue: defaultVal, options: column.options ?? [] }, `${rowData.id}-${column.name}`)) }, `${rowData.id}-${column.name}`));
|
|
177
|
+
};
|
|
178
|
+
const renderDeleteButton = (rowData) => (_jsx("td", { className: "align-middle w-16", children: _jsx(WithTooltip, { text: "Eliminar", children: _jsx("button", { className: "bg-red-500 hover:bg-red-600 font-bold py-1 px-2 rounded ml-2", onClick: (event) => {
|
|
179
|
+
event.preventDefault();
|
|
180
|
+
event.stopPropagation();
|
|
181
|
+
createModal({
|
|
182
|
+
title: "¿Está seguro que quiere eliminar este registro?",
|
|
183
|
+
description: "Una vez eliminada la información no podrá ser recuperada.",
|
|
184
|
+
}).then((response) => {
|
|
185
|
+
if (response === "OK") {
|
|
186
|
+
removeRow(rowData.id);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
}, type: "button", children: _jsx(TrashSvg, {}) }) }) }));
|
|
137
190
|
const renderRows = () => {
|
|
138
191
|
let mapeable = filteredTableData.slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage);
|
|
139
192
|
if (loading) {
|
|
@@ -183,13 +236,11 @@ export const GenericDynamicTable = (props) => {
|
|
|
183
236
|
};
|
|
184
237
|
return (_jsxs(_Fragment, { children: [name && (_jsxs(_Fragment, { children: [_jsx(GenericDynamicTableErrorComponent, { name: name }), _jsx("input", { name: name, type: "hidden", value: JSON.stringify(tableData), hidden: true }), _jsx("input", { name: `deleted_${name}`, type: "hidden", value: JSON.stringify(deletedData), hidden: true })] })), _jsxs("div", { className: `${className}`, children: [searcheables.length > 0 && (_jsx("div", { children: _jsx(TextField, { className: "mb-2", name: "search", title: `Buscar por: ${searcheables
|
|
185
238
|
.map((x) => x.label)
|
|
186
|
-
.join(", ")}`, onChange: handleChangeSearch, disabled: loading }) })), _jsxs("table", { className: "w-full", children: [_jsx("thead", { children: renderHeader() }), _jsx("tbody", { children: renderRows() }),
|
|
187
|
-
return (_jsx("td", { className: "align-middle", children: footerRow[x] }, indx));
|
|
188
|
-
}) }) })) : editable ? (_jsx("tfoot", { children: _jsx("tr", { children: _jsx("td", { className: "align-middle", children: _jsx("button", { className: "bg-blue-500 hover:bg-blue-600 font-bold py-2 px-4 rounded", onClick: (event) => {
|
|
239
|
+
.join(", ")}`, onChange: handleChangeSearch, disabled: loading }) })), _jsxs("table", { className: "w-full", children: [orientation === "horizontal" && _jsx("thead", { children: renderHeader() }), _jsx("tbody", { children: renderRows() }), editable && (_jsx("tfoot", { children: _jsx("tr", { children: _jsx("td", { colSpan: orientation === "horizontal" ? columns.length + 1 : 2, className: "align-middle", children: (!maxRows || tableData.length < maxRows) && (_jsx("button", { className: "bg-blue-500 hover:bg-blue-600 font-bold py-2 px-4 rounded", onClick: (event) => {
|
|
189
240
|
event.preventDefault();
|
|
190
241
|
event.stopPropagation();
|
|
191
242
|
addRow();
|
|
192
|
-
}, type: "button", children: "+" }) }) }) })) : (_jsx(
|
|
243
|
+
}, type: "button", children: "+" })) }) }) })), footerRow && (_jsx("tfoot", { className: "border-t-2 border-black", children: _jsx("tr", { children: footerRow.map((column, index) => (_jsx("td", { colSpan: orientation === "vertical" ? 2 : 1, className: `align-middle ${column.className || ""}`, children: column.content }, index))) }) }))] }), paginated && totalPages() > 1 && (_jsxs("div", { className: "flex justify-between items-center mt-4", children: [_jsxs("div", { className: "flex items-center", children: [_jsx(Button, { type: "button", disabled: currentPage === 1, onClickSave: () => setCurrentPage((old) => Math.max(old - 1, 1)), children: "Anterior" }), _jsx("span", { className: "mx-2", children: `Página ${currentPage} de ${totalPages()}` }), _jsx(Button, { type: "button", disabled: currentPage === totalPages(), onClickSave: () => setCurrentPage((old) => Math.min(old + 1, totalPages())), children: "Siguiente" })] }), _jsx("div", { children: _jsx("select", { value: itemsPerPage, onChange: (e) => {
|
|
193
244
|
setItemsPerPage(Number(e.target.value));
|
|
194
245
|
setCurrentPage(1); // resetear la página al cambiar los elementos por página
|
|
195
246
|
}, children: itemsPerPageOptions.map((option) => (_jsxs("option", { value: option, children: [option, " elementos por p\u00E1gina"] }, option))) }) })] }))] })] }));
|
|
@@ -112,6 +112,11 @@ export const SelectField = (props) => {
|
|
|
112
112
|
handleOptionClick(filteredOptions[0]);
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
|
+
else if (isTabPressed) {
|
|
116
|
+
if (highlightedIndex >= 0) {
|
|
117
|
+
handleOptionClick(filteredOptions[highlightedIndex]);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
115
120
|
setIsTabPressed(false);
|
|
116
121
|
setIsEnterPressed(false);
|
|
117
122
|
setIsSearching(false);
|
|
@@ -3,7 +3,7 @@ import { IdeaIconSVG } from "@zauru-sdk/icons";
|
|
|
3
3
|
import { useEffect, useState } from "react";
|
|
4
4
|
import { useFormContext } from "react-hook-form";
|
|
5
5
|
export const TextField = (props) => {
|
|
6
|
-
const { id, name, defaultValue = "", hidden, type = "text", onChange, onKeyDown, disabled = false, readOnly = false, min, integer = false, stopChangeEvents, style, title, helpText, className, hint, required, } = props;
|
|
6
|
+
const { id, name, defaultValue = "", hidden, type = "text", onChange, onKeyDown, disabled = false, readOnly = false, min, integer = false, stopChangeEvents, style, title, helpText, className = "", hint, required, } = props;
|
|
7
7
|
const [showTooltip, setShowTooltip] = useState(false);
|
|
8
8
|
const [value, setValue] = useState(defaultValue);
|
|
9
9
|
const { register: tempRegister, formState: { errors }, setValue: setOnFormValue, } = useFormContext() || { formState: {} }; // Obtener el contexto solo si existe
|
|
@@ -3,11 +3,11 @@ import React, { useState, useEffect } from "react";
|
|
|
3
3
|
import { DropDownArrowSvgIcon, LogoutDropDownSvgIcon, MenuAlt4Svg, OpcionButtonSvgIcon, } from "@zauru-sdk/icons";
|
|
4
4
|
import { COLORS } from "./NavBar.utils.js";
|
|
5
5
|
import { Link, useNavigate } from "@remix-run/react";
|
|
6
|
-
const OptionsDropDownButton = ({ color, options, name }) => {
|
|
6
|
+
const OptionsDropDownButton = ({ color, options, name, specialColor, }) => {
|
|
7
7
|
const [showOptionsMenu, setShowOptionsMenu] = useState(true);
|
|
8
|
-
return (
|
|
8
|
+
return (_jsxs("div", { className: `${specialColor ? specialColor.bg700 : color.bg700} container text-white w-full sm:w-auto h-10 text-sm py-1 uppercase shadow hover:shadow-lg outline-none rounded-full focus:outline-none my-auto sm:my-0 sm:mr-1 mb-1 ease-linear transition-all duration-150`, children: [_jsxs("button", { onClick: () => setShowOptionsMenu(!showOptionsMenu), className: `relative flex items-center p-2 text-xs text-white ${color.bg700} active:${color.bg900} border border-transparent rounded-full uppercase focus:ring-opacity-40 focus:outline-none`, children: [name ?? _jsx(OpcionButtonSvgIcon, {}), _jsx(DropDownArrowSvgIcon, {})] }), _jsx("div", { className: "absolute right-0 z-20 w-56 py-2 mt-2 overflow-hidden bg-white rounded-md shadow-xl dark:bg-gray-800", hidden: showOptionsMenu, onMouseLeave: () => setShowOptionsMenu(true), children: options.map((option, index) => (_jsx(React.Fragment, { children: option }, index))) })] }));
|
|
9
9
|
};
|
|
10
|
-
const NavItem = ({ name, link, icon, color, specialColor, childrens = [], }) => (_jsx("li", { className: "nav-item", children: childrens.length > 0 ? (_jsx(OptionsDropDownButton, { name: name, color: color, options: childrens.map((x, index) => (_jsx(Link, { to: x.link, className:
|
|
10
|
+
const NavItem = ({ name, link, icon, color, specialColor, childrens = [], }) => (_jsx("li", { className: "nav-item", children: childrens.length > 0 ? (_jsx(OptionsDropDownButton, { name: name, color: color, specialColor: specialColor, options: childrens.map((x, index) => (_jsx(Link, { to: x.link, className: `block px-4 py-3 text-sm text-gray-600 capitalize transition-colors duration-200 transform dark:text-gray-300 hover:bg-red-100 dark:hover:bg-gray-700 dark:hover:text-white`, children: x.name }, index))) })) : (_jsx("div", { className: `${specialColor ? specialColor.bg700 : color.bg700} container text-white w-full sm:w-auto h-10 text-sm py-1 uppercase shadow hover:shadow-lg outline-none rounded-full focus:outline-none my-auto sm:my-0 sm:mr-1 mb-1 ease-linear transition-all duration-150`, children: _jsx(Link, { className: "px-3 flex items-center text-xs leading-snug text-white uppercase hover:opacity-75", to: link, prefetch: "none", children: _jsxs("div", { className: "mx-auto pt-2", children: [icon, _jsx("span", { children: name })] }) }) })) }));
|
|
11
11
|
export const NavBar = ({ title, loggedIn, items, selectedColor, version, }) => {
|
|
12
12
|
const color = COLORS[selectedColor];
|
|
13
13
|
const [NavBarOpen, setNavBarOpen] = useState(false);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { motion } from "framer-motion";
|
|
3
|
+
export const LoadingWindow = ({ loadingItems, description = "Por favor, espere mientras se carga la página.", }) => {
|
|
4
|
+
return (_jsx("div", { className: "min-h-screen bg-gray-100 flex flex-col justify-center items-center", children: _jsxs(motion.div, { initial: { opacity: 0, y: -20 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.5 }, className: "text-center flex flex-col items-center", children: [_jsx("img", { src: "/logo.png", alt: "Zauru Logo", className: "mb-8 h-20" }), _jsx("h1", { className: "text-3xl font-bold text-gray-800 mb-4", children: "Cargando..." }), _jsx("p", { className: "text-gray-600 mb-4", children: description }), loadingItems && (_jsx("ul", { className: "text-left mb-4", children: loadingItems.map((item, index) => (_jsxs("li", { className: "flex items-center mb-2", children: [_jsx("svg", { className: `${item.loading ? "" : "hidden"} w-5 h-5 text-blue-500 mr-2 animate-spin loading-spinner`, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" }) }), _jsx("svg", { className: `${item.loading ? "hidden" : ""} w-5 h-5 text-green-500 mr-2 check-mark`, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M5 13l4 4L19 7" }) }), _jsx("span", { children: item.name })] }, index))) })), _jsx(motion.div, { animate: { rotate: 360 }, transition: { duration: 2, repeat: Infinity, ease: "linear" }, className: "mt-8", children: _jsx("svg", { className: "w-12 h-12 text-blue-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: _jsx(motion.path, { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15", animate: { rotate: 360 }, transition: { duration: 2, repeat: Infinity, ease: "linear" } }) }) })] }) }));
|
|
5
|
+
};
|
|
6
|
+
export default LoadingWindow;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zauru-sdk/components",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.4",
|
|
4
4
|
"description": "Componentes reutilizables en las WebApps de Zauru.",
|
|
5
5
|
"main": "./dist/esm/index.js",
|
|
6
6
|
"module": "./dist/esm/index.js",
|
|
@@ -33,11 +33,11 @@
|
|
|
33
33
|
"@hookform/resolvers": "^3.9.0",
|
|
34
34
|
"@reduxjs/toolkit": "^2.2.1",
|
|
35
35
|
"@remix-run/react": "^2.8.1",
|
|
36
|
-
"@zauru-sdk/common": "^2.0.
|
|
37
|
-
"@zauru-sdk/hooks": "^2.0.
|
|
36
|
+
"@zauru-sdk/common": "^2.0.4",
|
|
37
|
+
"@zauru-sdk/hooks": "^2.0.4",
|
|
38
38
|
"@zauru-sdk/icons": "^2.0.0",
|
|
39
|
-
"@zauru-sdk/types": "^2.0.
|
|
40
|
-
"@zauru-sdk/utils": "^2.0.
|
|
39
|
+
"@zauru-sdk/types": "^2.0.4",
|
|
40
|
+
"@zauru-sdk/utils": "^2.0.4",
|
|
41
41
|
"framer-motion": "^11.7.0",
|
|
42
42
|
"jsonwebtoken": "^9.0.2",
|
|
43
43
|
"react": "^18.2.0",
|
|
@@ -49,5 +49,5 @@
|
|
|
49
49
|
"styled-components": "^5.3.5",
|
|
50
50
|
"zod": "^3.23.8"
|
|
51
51
|
},
|
|
52
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "78f1e67e363a88974b8b74b5c0f0637fa3782ef0"
|
|
53
53
|
}
|
|
@@ -9,11 +9,16 @@ import {
|
|
|
9
9
|
RowDataType,
|
|
10
10
|
SelectFieldOption,
|
|
11
11
|
} from "@zauru-sdk/types";
|
|
12
|
-
import { useAppSelector } from "@zauru-sdk/redux";
|
|
13
12
|
import { generateClientUUID } from "@zauru-sdk/common";
|
|
14
13
|
import { LoadingInputSkeleton } from "../Skeletons/index.js";
|
|
15
14
|
import { WithTooltip } from "../WithTooltip/index.js";
|
|
16
15
|
import { TrashSvg } from "@zauru-sdk/icons";
|
|
16
|
+
import { useFormContext } from "react-hook-form";
|
|
17
|
+
|
|
18
|
+
export type FooterColumnConfig = {
|
|
19
|
+
content: React.ReactNode;
|
|
20
|
+
className?: string;
|
|
21
|
+
};
|
|
17
22
|
|
|
18
23
|
type Props = {
|
|
19
24
|
name?: string;
|
|
@@ -21,7 +26,7 @@ type Props = {
|
|
|
21
26
|
columns: GenericDynamicTableColumn[];
|
|
22
27
|
onChange?: (tableState?: any[]) => void;
|
|
23
28
|
defaultValue?: RowDataType[];
|
|
24
|
-
footerRow?:
|
|
29
|
+
footerRow?: FooterColumnConfig[];
|
|
25
30
|
thCSSProperties?: React.CSSProperties;
|
|
26
31
|
thElementsClassName?: string;
|
|
27
32
|
editable?: boolean;
|
|
@@ -31,27 +36,76 @@ type Props = {
|
|
|
31
36
|
defaultItemsPerPage?: number;
|
|
32
37
|
itemsPerPageOptions?: number[];
|
|
33
38
|
withoutBg?: boolean;
|
|
39
|
+
orientation?: "horizontal" | "vertical";
|
|
40
|
+
maxRows?: number;
|
|
34
41
|
};
|
|
35
42
|
|
|
36
|
-
const GenericDynamicTableErrorComponent = ({
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}: {
|
|
40
|
-
name:
|
|
41
|
-
formName?: string;
|
|
42
|
-
}) => {
|
|
43
|
-
const { formValidations } = useAppSelector((state) => state.formValidation);
|
|
44
|
-
const error = formValidations[formName ?? "-1"]?.[name ?? "-1"];
|
|
43
|
+
const GenericDynamicTableErrorComponent = ({ name }: { name: string }) => {
|
|
44
|
+
const {
|
|
45
|
+
formState: { errors },
|
|
46
|
+
} = useFormContext() || { formState: {} }; // Obtener el contexto solo si existe
|
|
47
|
+
const error = errors ? errors[name ?? "-1"] : undefined;
|
|
45
48
|
|
|
46
49
|
return error ? (
|
|
47
50
|
<p className={`mt-2 text-sm text-red-600 dark:text-red-500`}>
|
|
48
|
-
<span className="font-medium">Oops!</span> {error}
|
|
51
|
+
<span className="font-medium">Oops!</span> {error?.message?.toString()}
|
|
49
52
|
</p>
|
|
50
53
|
) : (
|
|
51
54
|
<></>
|
|
52
55
|
);
|
|
53
56
|
};
|
|
54
57
|
|
|
58
|
+
/**
|
|
59
|
+
*
|
|
60
|
+
* @param props
|
|
61
|
+
* @returns
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
*<GenericDynamicTable
|
|
65
|
+
name="invoice_details"
|
|
66
|
+
withoutBg
|
|
67
|
+
editable={!show}
|
|
68
|
+
defaultValue={
|
|
69
|
+
invoiceDetailsDefaultValue ?? [{ id: crypto.randomUUID() }]
|
|
70
|
+
}
|
|
71
|
+
columns={[
|
|
72
|
+
{
|
|
73
|
+
label: "Producto",
|
|
74
|
+
name: "item_id",
|
|
75
|
+
type: "selectField",
|
|
76
|
+
options: productOptions,
|
|
77
|
+
disabled: show,
|
|
78
|
+
onChange: (rowData, value, setTableValue) => {
|
|
79
|
+
const price = getProductPrice(value);
|
|
80
|
+
setTableValue("price", price);
|
|
81
|
+
},
|
|
82
|
+
headerClassName: "text-center font-bold",
|
|
83
|
+
cellClassName: "text-center",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
label: "Cantidad",
|
|
87
|
+
name: "quantity",
|
|
88
|
+
type: "textField",
|
|
89
|
+
textFieldType: "number",
|
|
90
|
+
disabled: show,
|
|
91
|
+
onChange: (rowData, value, setTableValue) => {
|
|
92
|
+
const price = rowData["price"] ?? 0;
|
|
93
|
+
const quantity = Number(value) || 0;
|
|
94
|
+
setTableValue("total", price * quantity);
|
|
95
|
+
},
|
|
96
|
+
headerClassName: "text-right font-semibold",
|
|
97
|
+
cellClassName: "text-right",
|
|
98
|
+
}
|
|
99
|
+
]}
|
|
100
|
+
footerRow={[
|
|
101
|
+
{ content: "Total", className: "text-left font-bold" },
|
|
102
|
+
{ content: calculateTotal(), className: "text-center" },
|
|
103
|
+
{ content: "", className: "text-center" }
|
|
104
|
+
]}
|
|
105
|
+
maxRows={2}
|
|
106
|
+
/>
|
|
107
|
+
*/
|
|
108
|
+
|
|
55
109
|
export const GenericDynamicTable = (props: Props) => {
|
|
56
110
|
const {
|
|
57
111
|
columns,
|
|
@@ -69,6 +123,8 @@ export const GenericDynamicTable = (props: Props) => {
|
|
|
69
123
|
itemsPerPageOptions = [10, 50, 100],
|
|
70
124
|
name,
|
|
71
125
|
withoutBg = false,
|
|
126
|
+
orientation = "horizontal",
|
|
127
|
+
maxRows,
|
|
72
128
|
} = props;
|
|
73
129
|
|
|
74
130
|
const [tableData, setTableData] = useState<RowDataType[]>(defaultValue);
|
|
@@ -98,6 +154,9 @@ export const GenericDynamicTable = (props: Props) => {
|
|
|
98
154
|
};
|
|
99
155
|
|
|
100
156
|
const addRow = () => {
|
|
157
|
+
if (maxRows && tableData.length >= maxRows) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
101
160
|
const defs: { [key: string]: any } = {};
|
|
102
161
|
columns.forEach((x) => {
|
|
103
162
|
defs[`${x.name}`] =
|
|
@@ -126,161 +185,176 @@ export const GenericDynamicTable = (props: Props) => {
|
|
|
126
185
|
};
|
|
127
186
|
|
|
128
187
|
const handleChange = (name: string, value: any, rowId: string) => {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
188
|
+
setTableData((prevData) => {
|
|
189
|
+
const updatedData = prevData.map((row) => {
|
|
190
|
+
if (row.id === rowId) {
|
|
191
|
+
return { ...row, [name]: value };
|
|
192
|
+
}
|
|
193
|
+
return row;
|
|
194
|
+
});
|
|
195
|
+
onChange && onChange(updatedData);
|
|
196
|
+
return updatedData;
|
|
197
|
+
});
|
|
198
|
+
};
|
|
140
199
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
+
}
|
|
144
225
|
};
|
|
145
226
|
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
+
>
|
|
152
244
|
<th
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
245
|
+
className={`text-left align-middle p-2 ${thElementsClassName} ${
|
|
246
|
+
column.headerClassName || ""
|
|
247
|
+
}`}
|
|
156
248
|
>
|
|
157
249
|
{column.label}
|
|
158
250
|
</th>
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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];
|
|
251
|
+
{renderCell(rowData, column)}
|
|
252
|
+
{editable &&
|
|
253
|
+
column === columns[columns.length - 1] &&
|
|
254
|
+
renderDeleteButton(rowData)}
|
|
255
|
+
</tr>
|
|
256
|
+
));
|
|
257
|
+
}
|
|
258
|
+
};
|
|
183
259
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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];
|
|
188
275
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
className="align-middle p-1"
|
|
194
|
-
>
|
|
195
|
-
<div>{defaultVal}</div>
|
|
196
|
-
</td>
|
|
197
|
-
);
|
|
198
|
-
}
|
|
276
|
+
const defaultVal =
|
|
277
|
+
column.type === "selectField"
|
|
278
|
+
? column.options?.find((x) => x.value === tempVal)
|
|
279
|
+
: tempVal;
|
|
199
280
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
281
|
+
if (column.type === "label") {
|
|
282
|
+
return (
|
|
283
|
+
<td
|
|
284
|
+
key={`${rowData.id}-${column.name}`}
|
|
285
|
+
className={`align-middle p-1 ${column.cellClassName || ""}`}
|
|
286
|
+
>
|
|
287
|
+
<div>{defaultVal}</div>
|
|
288
|
+
</td>
|
|
289
|
+
);
|
|
290
|
+
}
|
|
206
291
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
...prevState[rowIndex],
|
|
214
|
-
[columnName]: newValue,
|
|
215
|
-
};
|
|
216
|
-
// Copiar todo el array
|
|
217
|
-
const updatedData = [...prevState];
|
|
292
|
+
const FieldComponent =
|
|
293
|
+
column.type === "textField"
|
|
294
|
+
? TextField
|
|
295
|
+
: column.type === "checkbox"
|
|
296
|
+
? CheckBox
|
|
297
|
+
: SelectField;
|
|
218
298
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
});
|
|
223
|
-
};
|
|
299
|
+
const setTableValue = (columnName: string, newValue: any) => {
|
|
300
|
+
handleChange(columnName, newValue, rowData.id);
|
|
301
|
+
};
|
|
224
302
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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>
|
|
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);
|
|
323
|
+
}}
|
|
324
|
+
defaultValue={defaultVal}
|
|
325
|
+
options={column.options ?? []}
|
|
326
|
+
/>
|
|
279
327
|
)}
|
|
280
|
-
</
|
|
328
|
+
</td>
|
|
281
329
|
);
|
|
282
330
|
};
|
|
283
331
|
|
|
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
|
+
|
|
284
358
|
const renderRows = () => {
|
|
285
359
|
let mapeable = filteredTableData.slice(
|
|
286
360
|
(currentPage - 1) * itemsPerPage,
|
|
@@ -372,42 +446,50 @@ export const GenericDynamicTable = (props: Props) => {
|
|
|
372
446
|
</div>
|
|
373
447
|
)}
|
|
374
448
|
<table className="w-full">
|
|
375
|
-
<thead>{renderHeader()}</thead>
|
|
449
|
+
{orientation === "horizontal" && <thead>{renderHeader()}</thead>}
|
|
376
450
|
<tbody>{renderRows()}</tbody>
|
|
377
|
-
{
|
|
378
|
-
<tfoot
|
|
451
|
+
{editable && (
|
|
452
|
+
<tfoot>
|
|
379
453
|
<tr>
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
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>
|
|
387
476
|
</tr>
|
|
388
477
|
</tfoot>
|
|
389
|
-
)
|
|
390
|
-
|
|
478
|
+
)}
|
|
479
|
+
{footerRow && (
|
|
480
|
+
<tfoot className="border-t-2 border-black">
|
|
391
481
|
<tr>
|
|
392
|
-
|
|
393
|
-
<
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
) => {
|
|
398
|
-
event.preventDefault();
|
|
399
|
-
event.stopPropagation();
|
|
400
|
-
addRow();
|
|
401
|
-
}}
|
|
402
|
-
type="button"
|
|
482
|
+
{footerRow.map((column, index) => (
|
|
483
|
+
<td
|
|
484
|
+
key={index}
|
|
485
|
+
colSpan={orientation === "vertical" ? 2 : 1}
|
|
486
|
+
className={`align-middle ${column.className || ""}`}
|
|
403
487
|
>
|
|
404
|
-
|
|
405
|
-
</
|
|
406
|
-
|
|
488
|
+
{column.content}
|
|
489
|
+
</td>
|
|
490
|
+
))}
|
|
407
491
|
</tr>
|
|
408
492
|
</tfoot>
|
|
409
|
-
) : (
|
|
410
|
-
<></>
|
|
411
493
|
)}
|
|
412
494
|
</table>
|
|
413
495
|
{paginated && totalPages() > 1 && (
|
|
@@ -178,7 +178,12 @@ export const SelectField = (props: Props) => {
|
|
|
178
178
|
} else {
|
|
179
179
|
handleOptionClick(filteredOptions[0]);
|
|
180
180
|
}
|
|
181
|
+
} else if (isTabPressed) {
|
|
182
|
+
if (highlightedIndex >= 0) {
|
|
183
|
+
handleOptionClick(filteredOptions[highlightedIndex]);
|
|
184
|
+
}
|
|
181
185
|
}
|
|
186
|
+
|
|
182
187
|
setIsTabPressed(false);
|
|
183
188
|
setIsEnterPressed(false);
|
|
184
189
|
setIsSearching(false);
|
package/src/NavBar/NavBar.tsx
CHANGED
|
@@ -15,30 +15,35 @@ import type {
|
|
|
15
15
|
} from "./NavBar.types.js";
|
|
16
16
|
import { Link, useNavigate } from "@remix-run/react";
|
|
17
17
|
|
|
18
|
-
const OptionsDropDownButton = ({
|
|
18
|
+
const OptionsDropDownButton = ({
|
|
19
|
+
color,
|
|
20
|
+
options,
|
|
21
|
+
name,
|
|
22
|
+
specialColor,
|
|
23
|
+
}: EntityProps) => {
|
|
19
24
|
const [showOptionsMenu, setShowOptionsMenu] = useState(true);
|
|
20
25
|
|
|
21
26
|
return (
|
|
22
|
-
<div
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
</
|
|
41
|
-
|
|
27
|
+
<div
|
|
28
|
+
className={`${
|
|
29
|
+
specialColor ? specialColor.bg700 : color.bg700
|
|
30
|
+
} container text-white w-full sm:w-auto h-10 text-sm py-1 uppercase shadow hover:shadow-lg outline-none rounded-full focus:outline-none my-auto sm:my-0 sm:mr-1 mb-1 ease-linear transition-all duration-150`}
|
|
31
|
+
>
|
|
32
|
+
<button
|
|
33
|
+
onClick={() => setShowOptionsMenu(!showOptionsMenu)}
|
|
34
|
+
className={`relative flex items-center p-2 text-xs text-white ${color.bg700} active:${color.bg900} border border-transparent rounded-full uppercase focus:ring-opacity-40 focus:outline-none`}
|
|
35
|
+
>
|
|
36
|
+
{name ?? <OpcionButtonSvgIcon />}
|
|
37
|
+
<DropDownArrowSvgIcon />
|
|
38
|
+
</button>
|
|
39
|
+
<div
|
|
40
|
+
className="absolute right-0 z-20 w-56 py-2 mt-2 overflow-hidden bg-white rounded-md shadow-xl dark:bg-gray-800"
|
|
41
|
+
hidden={showOptionsMenu}
|
|
42
|
+
onMouseLeave={() => setShowOptionsMenu(true)}
|
|
43
|
+
>
|
|
44
|
+
{options.map((option, index) => (
|
|
45
|
+
<React.Fragment key={index}>{option}</React.Fragment>
|
|
46
|
+
))}
|
|
42
47
|
</div>
|
|
43
48
|
</div>
|
|
44
49
|
);
|
|
@@ -57,8 +62,13 @@ const NavItem = ({
|
|
|
57
62
|
<OptionsDropDownButton
|
|
58
63
|
name={name}
|
|
59
64
|
color={color}
|
|
65
|
+
specialColor={specialColor}
|
|
60
66
|
options={childrens.map((x, index) => (
|
|
61
|
-
<Link
|
|
67
|
+
<Link
|
|
68
|
+
key={index}
|
|
69
|
+
to={x.link}
|
|
70
|
+
className={`block px-4 py-3 text-sm text-gray-600 capitalize transition-colors duration-200 transform dark:text-gray-300 hover:bg-red-100 dark:hover:bg-gray-700 dark:hover:text-white`}
|
|
71
|
+
>
|
|
62
72
|
{x.name}
|
|
63
73
|
</Link>
|
|
64
74
|
))}
|
|
@@ -67,7 +77,7 @@ const NavItem = ({
|
|
|
67
77
|
<div
|
|
68
78
|
className={`${
|
|
69
79
|
specialColor ? specialColor.bg700 : color.bg700
|
|
70
|
-
} container text-white w-full sm:w-auto h-10 text-sm py-1 uppercase shadow hover:shadow-lg outline-none rounded-full focus:outline-none my-auto sm:my-0 sm:mr-1 mb-1 ease-linear transition-all duration-150
|
|
80
|
+
} container text-white w-full sm:w-auto h-10 text-sm py-1 uppercase shadow hover:shadow-lg outline-none rounded-full focus:outline-none my-auto sm:my-0 sm:mr-1 mb-1 ease-linear transition-all duration-150`}
|
|
71
81
|
>
|
|
72
82
|
<Link
|
|
73
83
|
className="px-3 flex items-center text-xs leading-snug text-white uppercase hover:opacity-75"
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { motion } from "framer-motion";
|
|
3
|
+
|
|
4
|
+
interface LoadingItem {
|
|
5
|
+
name: string;
|
|
6
|
+
loading: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface LoadingWindowProps {
|
|
10
|
+
loadingItems?: LoadingItem[];
|
|
11
|
+
description?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const LoadingWindow: React.FC<LoadingWindowProps> = ({
|
|
15
|
+
loadingItems,
|
|
16
|
+
description = "Por favor, espere mientras se carga la página.",
|
|
17
|
+
}) => {
|
|
18
|
+
return (
|
|
19
|
+
<div className="min-h-screen bg-gray-100 flex flex-col justify-center items-center">
|
|
20
|
+
<motion.div
|
|
21
|
+
initial={{ opacity: 0, y: -20 }}
|
|
22
|
+
animate={{ opacity: 1, y: 0 }}
|
|
23
|
+
transition={{ duration: 0.5 }}
|
|
24
|
+
className="text-center flex flex-col items-center"
|
|
25
|
+
>
|
|
26
|
+
<img src="/logo.png" alt="Zauru Logo" className="mb-8 h-20" />
|
|
27
|
+
<h1 className="text-3xl font-bold text-gray-800 mb-4">Cargando...</h1>
|
|
28
|
+
<p className="text-gray-600 mb-4">{description}</p>
|
|
29
|
+
{loadingItems && (
|
|
30
|
+
<ul className="text-left mb-4">
|
|
31
|
+
{loadingItems.map((item, index) => (
|
|
32
|
+
<li key={index} className="flex items-center mb-2">
|
|
33
|
+
<svg
|
|
34
|
+
className={`${
|
|
35
|
+
item.loading ? "" : "hidden"
|
|
36
|
+
} w-5 h-5 text-blue-500 mr-2 animate-spin loading-spinner`}
|
|
37
|
+
fill="none"
|
|
38
|
+
viewBox="0 0 24 24"
|
|
39
|
+
stroke="currentColor"
|
|
40
|
+
>
|
|
41
|
+
<path
|
|
42
|
+
strokeLinecap="round"
|
|
43
|
+
strokeLinejoin="round"
|
|
44
|
+
strokeWidth={2}
|
|
45
|
+
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
|
|
46
|
+
/>
|
|
47
|
+
</svg>
|
|
48
|
+
<svg
|
|
49
|
+
className={`${
|
|
50
|
+
item.loading ? "hidden" : ""
|
|
51
|
+
} w-5 h-5 text-green-500 mr-2 check-mark`}
|
|
52
|
+
fill="none"
|
|
53
|
+
viewBox="0 0 24 24"
|
|
54
|
+
stroke="currentColor"
|
|
55
|
+
>
|
|
56
|
+
<path
|
|
57
|
+
strokeLinecap="round"
|
|
58
|
+
strokeLinejoin="round"
|
|
59
|
+
strokeWidth={2}
|
|
60
|
+
d="M5 13l4 4L19 7"
|
|
61
|
+
/>
|
|
62
|
+
</svg>
|
|
63
|
+
<span>{item.name}</span>
|
|
64
|
+
</li>
|
|
65
|
+
))}
|
|
66
|
+
</ul>
|
|
67
|
+
)}
|
|
68
|
+
<motion.div
|
|
69
|
+
animate={{ rotate: 360 }}
|
|
70
|
+
transition={{ duration: 2, repeat: Infinity, ease: "linear" }}
|
|
71
|
+
className="mt-8"
|
|
72
|
+
>
|
|
73
|
+
<svg
|
|
74
|
+
className="w-12 h-12 text-blue-500"
|
|
75
|
+
fill="none"
|
|
76
|
+
viewBox="0 0 24 24"
|
|
77
|
+
stroke="currentColor"
|
|
78
|
+
>
|
|
79
|
+
<motion.path
|
|
80
|
+
strokeLinecap="round"
|
|
81
|
+
strokeLinejoin="round"
|
|
82
|
+
strokeWidth={2}
|
|
83
|
+
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
|
|
84
|
+
animate={{ rotate: 360 }}
|
|
85
|
+
transition={{ duration: 2, repeat: Infinity, ease: "linear" }}
|
|
86
|
+
/>
|
|
87
|
+
</svg>
|
|
88
|
+
</motion.div>
|
|
89
|
+
</motion.div>
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export default LoadingWindow;
|
package/src/Skeletons/index.ts
CHANGED