@zauru-sdk/components 2.0.77 → 2.0.78
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 +2 -0
- package/dist/Footer/Footer.d.ts +1 -1
- package/dist/Form/RadioButton/index.d.ts +18 -0
- package/dist/Form/index.d.ts +1 -0
- package/dist/HOC/ValidateEmployeeAccess/index.d.ts +2 -1
- package/dist/Modal/ItemModal.d.ts +24 -0
- package/dist/Modal/index.d.ts +1 -0
- package/dist/esm/Containers/BodyContainer.js +2 -2
- package/dist/esm/DynamicTable/GenericDynamicTable.js +20 -10
- package/dist/esm/Footer/Footer.js +2 -1
- package/dist/esm/Form/RadioButton/index.js +26 -0
- package/dist/esm/Form/SelectField/index.js +2 -6
- package/dist/esm/Form/index.js +1 -0
- package/dist/esm/HOC/ValidateEmployeeAccess/index.js +4 -1
- package/dist/esm/Layouts/errorLayout/index.js +15 -9
- package/dist/esm/Modal/ItemModal.js +54 -0
- package/dist/esm/Modal/index.js +1 -0
- package/dist/esm/NavBar/NavBar.js +0 -13
- package/dist/esm/Skeletons/LoadingWindow.js +2 -2
- package/package.json +6 -6
- package/src/Containers/BodyContainer.tsx +1 -5
- package/src/DynamicTable/GenericDynamicTable.tsx +25 -10
- package/src/Footer/Footer.tsx +4 -3
- package/src/Form/RadioButton/index.tsx +130 -0
- package/src/Form/SelectField/index.tsx +2 -5
- package/src/Form/index.ts +1 -0
- package/src/HOC/ValidateEmployeeAccess/index.tsx +6 -0
- package/src/Layouts/errorLayout/index.tsx +69 -57
- package/src/Modal/ItemModal.tsx +186 -0
- package/src/Modal/index.tsx +1 -0
- package/src/NavBar/NavBar.tsx +0 -22
- package/src/Skeletons/LoadingWindow.tsx +2 -2
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.78](https://github.com/intuitiva/zauru-typescript-sdk/compare/v2.0.77...v2.0.78) (2024-12-17)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @zauru-sdk/components
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
6
14
|
## [2.0.77](https://github.com/intuitiva/zauru-typescript-sdk/compare/v2.0.76...v2.0.77) (2024-12-13)
|
|
7
15
|
|
|
8
16
|
**Note:** Version bump only for package @zauru-sdk/components
|
package/dist/Footer/Footer.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
type FooterProps = {
|
|
2
2
|
href: string;
|
|
3
3
|
showConnection?: boolean;
|
|
4
|
-
selectedColor: "purple" | "pink" | "indigo" | "cyan" | "slate" | "green" | "red" | "sky";
|
|
4
|
+
selectedColor: "purple" | "pink" | "indigo" | "cyan" | "slate" | "green" | "red" | "sky" | "yellow";
|
|
5
5
|
version?: string;
|
|
6
6
|
};
|
|
7
7
|
export declare const Footer: ({ href, selectedColor, showConnection, version, }: FooterProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { SelectFieldOption } from "@zauru-sdk/types";
|
|
2
|
+
import React from "react";
|
|
3
|
+
type Props = {
|
|
4
|
+
id?: string;
|
|
5
|
+
name: string;
|
|
6
|
+
title?: string;
|
|
7
|
+
options: SelectFieldOption[];
|
|
8
|
+
defaultValue?: string | number;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
required?: boolean;
|
|
11
|
+
onChange?: (value: string | number, event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
12
|
+
helpText?: string;
|
|
13
|
+
hint?: string;
|
|
14
|
+
className?: string;
|
|
15
|
+
orientation?: "horizontal" | "vertical";
|
|
16
|
+
};
|
|
17
|
+
export declare const RadioButton: (props: Props) => import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
export {};
|
package/dist/Form/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export declare const ValidateEmployeeAccess: ({ children, permissionVariableName, showIfNoPermission, }: {
|
|
1
|
+
export declare const ValidateEmployeeAccess: ({ children, permissionVariableName, showIfNoPermission, noPermissionComponent, }: {
|
|
2
2
|
children: React.ReactNode;
|
|
3
3
|
permissionVariableName: string;
|
|
4
4
|
showIfNoPermission?: boolean;
|
|
5
|
+
noPermissionComponent?: React.ReactNode;
|
|
5
6
|
}) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
type Item = {
|
|
2
|
+
name: string;
|
|
3
|
+
code: string;
|
|
4
|
+
unitPrice: number;
|
|
5
|
+
stock: number;
|
|
6
|
+
currencyPrefix: string;
|
|
7
|
+
imageUrl: string;
|
|
8
|
+
};
|
|
9
|
+
type ItemCategory = {
|
|
10
|
+
name: string;
|
|
11
|
+
items: Item[];
|
|
12
|
+
};
|
|
13
|
+
type ItemModalProps = {
|
|
14
|
+
itemList: ItemCategory[];
|
|
15
|
+
onClose: (selectedItem: Item | null) => void;
|
|
16
|
+
config?: {
|
|
17
|
+
itemSize: {
|
|
18
|
+
width: string;
|
|
19
|
+
height: string;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
export declare const createItemModal: (itemList: ItemCategory[], config?: ItemModalProps["config"]) => Promise<Item | null>;
|
|
24
|
+
export {};
|
package/dist/Modal/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
export const BodyContainer = (props) => {
|
|
3
3
|
const { children } = props;
|
|
4
|
-
return
|
|
4
|
+
return _jsx("body", { className: "flex flex-col min-h-screen m-0", children: children });
|
|
5
5
|
};
|
|
@@ -68,7 +68,7 @@ const GenericDynamicTableErrorComponent = ({ name }) => {
|
|
|
68
68
|
/>
|
|
69
69
|
*/
|
|
70
70
|
export const GenericDynamicTable = (props) => {
|
|
71
|
-
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;
|
|
71
|
+
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, confirmDelete = true, addRowButtonHandler, } = props;
|
|
72
72
|
try {
|
|
73
73
|
const [tableData, setTableData] = useState(defaultValue);
|
|
74
74
|
const [deletedData, setDeletedData] = useState([]);
|
|
@@ -181,14 +181,19 @@ export const GenericDynamicTable = (props) => {
|
|
|
181
181
|
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) => {
|
|
182
182
|
event.preventDefault();
|
|
183
183
|
event.stopPropagation();
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
184
|
+
if (confirmDelete) {
|
|
185
|
+
createModal({
|
|
186
|
+
title: "¿Está seguro que quiere eliminar este registro?",
|
|
187
|
+
description: "Una vez eliminada la información no podrá ser recuperada.",
|
|
188
|
+
}).then((response) => {
|
|
189
|
+
if (response === "OK") {
|
|
190
|
+
removeRow(rowData.id);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
removeRow(rowData.id);
|
|
196
|
+
}
|
|
192
197
|
}, type: "button", children: _jsx(TrashSvg, {}) }) }) }));
|
|
193
198
|
const renderRows = () => {
|
|
194
199
|
let mapeable = filteredTableData.slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage);
|
|
@@ -242,7 +247,12 @@ export const GenericDynamicTable = (props) => {
|
|
|
242
247
|
.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) => {
|
|
243
248
|
event.preventDefault();
|
|
244
249
|
event.stopPropagation();
|
|
245
|
-
|
|
250
|
+
if (addRowButtonHandler) {
|
|
251
|
+
addRowButtonHandler(tableData, setTableData);
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
addRow();
|
|
255
|
+
}
|
|
246
256
|
}, type: "button", children: "+" })) }) }) })), footerRow && (_jsx("tfoot", { className: "border-t-2 border-black", children: _jsxs("tr", { children: [columns.map((column, index) => {
|
|
247
257
|
const footerCell = footerRow.find((fc) => fc.name === column.name);
|
|
248
258
|
return (_jsx("td", { colSpan: orientation === "vertical" ? 2 : 1, className: `align-middle ${footerCell?.className || ""}`, children: footerCell ? footerCell.content : _jsx(_Fragment, {}) }, index));
|
|
@@ -9,8 +9,9 @@ const COLORS = {
|
|
|
9
9
|
green: "bg-green-500",
|
|
10
10
|
red: "bg-red-500",
|
|
11
11
|
sky: "bg-sky-500",
|
|
12
|
+
yellow: "bg-yellow-500",
|
|
12
13
|
};
|
|
13
14
|
export const Footer = ({ href, selectedColor, showConnection = false, version = "2.0.5", }) => {
|
|
14
15
|
const color = COLORS[selectedColor];
|
|
15
|
-
return (_jsx("footer", { className: `inset-x-0 bottom-0 px-2 py-[20px] ${color}`, children: _jsxs("div", { className: "px-4 mx-auto flex flex-wrap items-center justify-center", children: [_jsxs("p", { className: "text-white text-[1.2rem]", children: [`Creado en `, " ", _jsx("a", { href: href, children: "Zauru" }), " ", `con ❤️
|
|
16
|
+
return (_jsx("footer", { className: `inset-x-0 bottom-0 px-2 py-[20px] ${color}`, children: _jsxs("div", { className: "px-4 mx-auto flex flex-wrap items-center justify-center", children: [_jsxs("p", { className: "text-white text-[1.2rem]", children: [`Creado en `, " ", _jsx("a", { href: href, children: "Zauru" }), " ", `con ❤️ v.${version}`] }), showConnection && (_jsx("div", { className: "ml-5", children: _jsx(ConnectionState, {}) }))] }) }));
|
|
16
17
|
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { useFormContext } from "react-hook-form";
|
|
4
|
+
export const RadioButton = (props) => {
|
|
5
|
+
const { id, name, title, options, defaultValue, disabled = false, required = false, onChange, helpText, hint, className = "", orientation = "vertical", } = props;
|
|
6
|
+
const [selectedValue, setSelectedValue] = useState(defaultValue);
|
|
7
|
+
const { register: tempRegister, formState: { errors }, setValue: setOnFormValue, } = useFormContext() || { formState: {} };
|
|
8
|
+
const error = errors ? errors[name] : undefined;
|
|
9
|
+
const color = error ? "red" : "gray";
|
|
10
|
+
const register = tempRegister ? tempRegister(name, { required }) : undefined; // Solo usar register si está disponible
|
|
11
|
+
const handleChange = (event) => {
|
|
12
|
+
const newValue = event.target.value;
|
|
13
|
+
setSelectedValue(newValue);
|
|
14
|
+
if (register) {
|
|
15
|
+
register.onChange(event);
|
|
16
|
+
}
|
|
17
|
+
if (setOnFormValue) {
|
|
18
|
+
setOnFormValue(name, newValue);
|
|
19
|
+
}
|
|
20
|
+
onChange && onChange(newValue, event);
|
|
21
|
+
};
|
|
22
|
+
const containerClass = orientation === "horizontal"
|
|
23
|
+
? "flex items-center gap-4 flex-wrap" // Estilo para disposición horizontal
|
|
24
|
+
: ""; // Vertical no necesita clase especial
|
|
25
|
+
return (_jsxs("div", { className: `col-span-6 sm:col-span-3 ${className}`, children: [title && (_jsxs("label", { htmlFor: id ?? name, className: `block mb-1 text-sm font-medium text-${color}-700 dark:text-${color}-500`, children: [title, required && _jsx("span", { className: "text-red-500", children: "*" })] })), _jsx("div", { className: containerClass, children: options.map((option) => (_jsxs("div", { className: `flex items-center mb-2 ${disabled ? "opacity-50 pointer-events-none" : ""}`, children: [_jsx("input", { type: "radio", id: `${name}-${option.value}`, value: option.value, disabled: disabled, checked: selectedValue === option.value, className: `h-4 w-4 text-indigo-600 border-${color}-300 focus:ring-indigo-500`, ...(register ?? {}), onChange: handleChange }), _jsx("label", { htmlFor: `${name}-${option.value}`, className: `ml-2 block text-sm font-medium text-${color}-900 dark:text-${color}-500`, children: option.label })] }, option.value))) }), helpText && (_jsx("p", { className: `mt-2 italic text-sm text-${color}-500 dark:text-${color}-400`, children: helpText })), error && (_jsxs("p", { className: `mt-2 text-sm text-${color}-600 dark:text-${color}-500`, children: [_jsx("span", { className: "font-medium", children: "Oops!" }), " ", error.message?.toString()] })), !error && hint && (_jsx("p", { className: `mt-2 italic text-sm text-${color}-500 dark:text-${color}-400`, children: hint }))] }));
|
|
26
|
+
};
|
|
@@ -154,12 +154,8 @@ export const SelectField = (props) => {
|
|
|
154
154
|
}
|
|
155
155
|
else if (e.key === "Backspace" && (value || valueMulti.length > 0)) {
|
|
156
156
|
e.preventDefault();
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
else {
|
|
161
|
-
setInputValue("");
|
|
162
|
-
}
|
|
157
|
+
handleClear();
|
|
158
|
+
setInputValue("");
|
|
163
159
|
setFilteredOptions(options);
|
|
164
160
|
setIsOpen(true);
|
|
165
161
|
}
|
package/dist/esm/Form/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useGetEmployeeProfile, useGetSessionAttribute, } from "@zauru-sdk/hooks";
|
|
3
3
|
import { Link } from "@remix-run/react";
|
|
4
|
-
export const ValidateEmployeeAccess = ({ children, permissionVariableName, showIfNoPermission = false, }) => {
|
|
4
|
+
export const ValidateEmployeeAccess = ({ children, permissionVariableName, showIfNoPermission = false, noPermissionComponent, }) => {
|
|
5
5
|
const { data: employee } = useGetEmployeeProfile();
|
|
6
6
|
const variable_string = useGetSessionAttribute(permissionVariableName, "sessionVariable");
|
|
7
7
|
const variable = variable_string?.split(",");
|
|
@@ -9,5 +9,8 @@ export const ValidateEmployeeAccess = ({ children, permissionVariableName, showI
|
|
|
9
9
|
if (showIfNoPermission && !hasPermission) {
|
|
10
10
|
return (_jsxs("div", { className: "bg-gray-900 text-white min-h-screen flex flex-col items-center justify-center p-4", children: [_jsx("img", { src: "/logo.png", alt: "Zauru Logo", className: "mb-8 h-20" }), _jsx("h1", { className: "text-5xl font-extrabold text-red-500 mb-6", children: "\u00A1Acceso Denegado!" }), _jsx("div", { className: "w-full max-w-2xl", children: _jsx("p", { className: "text-2xl text-gray-300 mb-8 text-center", children: "Lo sentimos, no tienes permiso para acceder a esta p\u00E1gina." }) }), _jsx(Link, { to: "/", className: "bg-blue-600 text-white py-3 px-8 rounded-full text-lg font-semibold hover:bg-blue-700 transition duration-300 transform hover:scale-105", children: "Regresar al inicio" }), _jsx("div", { className: "mt-12 text-gray-500", children: _jsx("p", { children: "Si crees que esto es un error, por favor contacta a soporte." }) })] }));
|
|
11
11
|
}
|
|
12
|
+
if (noPermissionComponent && !hasPermission) {
|
|
13
|
+
return _jsx(_Fragment, { children: noPermissionComponent });
|
|
14
|
+
}
|
|
12
15
|
return _jsx(_Fragment, { children: hasPermission ? children : null });
|
|
13
16
|
};
|
|
@@ -2,13 +2,19 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { isRouteErrorResponse, Links, Meta, Scripts, useRouteError, Link, } from "@remix-run/react";
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
export const ErrorLayout = ({ from, error: parentError, }) => {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
? parentError
|
|
13
|
-
|
|
5
|
+
try {
|
|
6
|
+
const error = useRouteError();
|
|
7
|
+
const [showDetails, setShowDetails] = useState(!!parentError);
|
|
8
|
+
return (_jsxs("html", { lang: "es", className: "bg-gray-900 text-white", children: [_jsxs("head", { children: [_jsx("meta", { charSet: "utf-8" }), _jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }), _jsx("title", { children: "\u00A1Ups! Algo sali\u00F3 mal" }), _jsx(Meta, {}), _jsx(Links, {})] }), _jsxs("body", { className: "min-h-screen flex flex-col items-center justify-center p-4", children: [_jsx("img", { src: "/logo.png", alt: "Zauru Logo", className: "mb-8 h-20" }), _jsx("h1", { className: "text-5xl font-extrabold text-red-500 mb-6", children: "\u00A1Ups!" }), _jsxs("div", { className: "w-full max-w-2xl flex flex-col items-center", children: [_jsx("p", { className: "text-2xl text-gray-300 mb-8 text-center", children: isRouteErrorResponse(error)
|
|
9
|
+
? `Error ${error.status}: ${error.statusText}`
|
|
10
|
+
: error instanceof Error
|
|
11
|
+
? error.message
|
|
12
|
+
: "Ha ocurrido un error inesperado" }), from && (_jsxs("p", { className: "text-lg text-gray-400 mb-4 text-center", children: ["Error lanzado desde: ", from] })), parentError && (_jsxs("div", { className: "mb-4 text-center", children: [_jsx("button", { onClick: () => setShowDetails(!showDetails), className: "text-blue-400 hover:text-blue-300 transition duration-300", children: showDetails ? "Ocultar detalles" : "Ver más detalles" }), showDetails && (_jsx("p", { className: "mt-2 text-gray-400 text-sm", children: parentError instanceof Error
|
|
13
|
+
? parentError.message
|
|
14
|
+
: String(parentError) }))] }))] }), _jsx(Link, { to: "/", className: "bg-blue-600 text-white py-3 px-8 rounded-full text-lg font-semibold hover:bg-blue-700 transition duration-300 transform hover:scale-105", children: "Regresar al inicio" }), _jsx("div", { className: "mt-12 text-gray-500 text-center", children: _jsx("p", { children: "Si el problema persiste, por favor contacta a soporte." }) }), _jsx(Scripts, {})] })] }));
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
console.error(error);
|
|
18
|
+
return (_jsxs("html", { lang: "es", className: "bg-gray-900 text-white", children: [_jsx("head", { children: _jsx("title", { children: "\u00A1Ups! Algo sali\u00F3 mal" }) }), _jsxs("body", { children: ["Error al renderizar el layout de error ", error] })] }));
|
|
19
|
+
}
|
|
14
20
|
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { createRoot } from "react-dom/client";
|
|
4
|
+
const ItemSelectionModal = ({ itemList, onClose, config, }) => {
|
|
5
|
+
const defaultConfig = {
|
|
6
|
+
itemSize: {
|
|
7
|
+
width: config?.itemSize?.width || "150px",
|
|
8
|
+
height: config?.itemSize?.height || "200px",
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
const [searchTerm, setSearchTerm] = useState("");
|
|
12
|
+
const [expandedCategories, setExpandedCategories] = useState({});
|
|
13
|
+
const toggleCategory = (categoryName) => {
|
|
14
|
+
setExpandedCategories((prev) => ({
|
|
15
|
+
...prev,
|
|
16
|
+
[categoryName]: !prev[categoryName],
|
|
17
|
+
}));
|
|
18
|
+
};
|
|
19
|
+
const handleItemClick = (item) => {
|
|
20
|
+
onClose(item);
|
|
21
|
+
};
|
|
22
|
+
const filteredList = itemList
|
|
23
|
+
.map((category) => ({
|
|
24
|
+
...category,
|
|
25
|
+
items: category.items.filter((item) => item.name.toLowerCase().includes(searchTerm.toLowerCase())),
|
|
26
|
+
}))
|
|
27
|
+
.filter((category) => category.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
28
|
+
category.items.length > 0);
|
|
29
|
+
return (_jsx("div", { className: "fixed inset-0 bg-black bg-opacity-50 flex justify-center items-center z-50", children: _jsxs("div", { className: "bg-white p-4 rounded-lg shadow-lg w-11/12 max-w-4xl h-5/6 overflow-y-auto", children: [_jsxs("div", { className: "flex flex-col mb-4", children: [_jsxs("div", { className: "flex justify-between items-center mb-4", children: [_jsx("h2", { className: "text-2xl font-bold", children: "Seleccionar un \u00CDtem" }), _jsx("button", { className: "text-gray-500 hover:text-gray-800", onClick: () => onClose(null), children: "\u00D7" })] }), _jsx("input", { type: "text", placeholder: "Buscar por nombre o categor\u00EDa...", value: searchTerm, onChange: (e) => setSearchTerm(e.target.value), className: "p-2 border rounded-lg w-full" })] }), filteredList.length === 0 ? (_jsx("p", { className: "text-gray-500 text-center", children: "No se encontraron resultados." })) : (filteredList.map((category) => (_jsxs("div", { className: "mb-4", children: [_jsxs("h3", { className: "text-lg font-semibold mb-2 cursor-pointer flex justify-between items-center hover:text-blue-600", onClick: () => toggleCategory(category.name), children: [_jsxs("span", { children: [category.name, " ", _jsxs("span", { className: "text-gray-500", children: ["(", category.items.length, ")"] })] }), _jsx("span", { className: `transform transition-transform duration-200 ${expandedCategories[category.name] ? "rotate-90" : ""}`, children: "\u25B6" })] }), expandedCategories[category.name] && (_jsx("div", { className: "grid gap-5", style: {
|
|
30
|
+
gridTemplateColumns: `repeat(auto-fit, minmax(${defaultConfig.itemSize.width}, 1fr))`,
|
|
31
|
+
}, children: category.items.map((item) => (_jsxs("div", { className: "border rounded-lg shadow-lg hover:shadow-xl cursor-pointer relative", onClick: () => handleItemClick(item), style: {
|
|
32
|
+
backgroundImage: `url(${item.imageUrl})`,
|
|
33
|
+
backgroundSize: "cover",
|
|
34
|
+
backgroundPosition: "center",
|
|
35
|
+
width: defaultConfig.itemSize.width,
|
|
36
|
+
height: defaultConfig.itemSize.height,
|
|
37
|
+
}, children: [_jsx("div", { className: "bg-white bg-opacity-80 p-2 rounded absolute top-0 left-0 right-0", children: _jsx("h4", { className: "font-bold text-sm text-center", children: item.name }) }), _jsx("div", { className: "absolute bottom-0 left-0 bg-white bg-opacity-80 p-2 rounded", children: _jsxs("span", { className: "text-xs", children: ["Stock: ", item.stock] }) }), _jsx("div", { className: "absolute bottom-0 right-0 bg-white bg-opacity-80 p-2 rounded", children: _jsxs("span", { className: "text-xs", children: [item.currencyPrefix, item.unitPrice] }) })] }, item.code))) }))] }, category.name))))] }) }));
|
|
38
|
+
};
|
|
39
|
+
// Función para crear el modal
|
|
40
|
+
export const createItemModal = (itemList, config) => {
|
|
41
|
+
return new Promise((resolve) => {
|
|
42
|
+
const handleClose = (selectedItem) => {
|
|
43
|
+
resolve(selectedItem);
|
|
44
|
+
removeModal();
|
|
45
|
+
};
|
|
46
|
+
const removeModal = () => {
|
|
47
|
+
document.body.removeChild(modalWrapper);
|
|
48
|
+
};
|
|
49
|
+
const modalWrapper = document.createElement("div");
|
|
50
|
+
document.body.appendChild(modalWrapper);
|
|
51
|
+
const root = createRoot(modalWrapper);
|
|
52
|
+
root.render(_jsx(ItemSelectionModal, { itemList: itemList, onClose: handleClose, config: config }));
|
|
53
|
+
});
|
|
54
|
+
};
|
package/dist/esm/Modal/index.js
CHANGED
|
@@ -11,25 +11,13 @@ const NavItem = ({ name, link, icon, color, specialColor, childrens = [], }) =>
|
|
|
11
11
|
export const NavBar = ({ title, loggedIn, items, selectedColor, version, }) => {
|
|
12
12
|
const color = COLORS[selectedColor];
|
|
13
13
|
const [NavBarOpen, setNavBarOpen] = useState(false);
|
|
14
|
-
const [isDarkMode, setIsDarkMode] = useState(false);
|
|
15
14
|
const [currentVersion, setCurrentVersion] = useState(version);
|
|
16
15
|
const navigate = useNavigate();
|
|
17
|
-
useEffect(() => {
|
|
18
|
-
if (isDarkMode) {
|
|
19
|
-
document.documentElement.classList.add("dark");
|
|
20
|
-
}
|
|
21
|
-
else {
|
|
22
|
-
document.documentElement.classList.remove("dark");
|
|
23
|
-
}
|
|
24
|
-
}, [isDarkMode]);
|
|
25
16
|
useEffect(() => {
|
|
26
17
|
if (version !== currentVersion) {
|
|
27
18
|
setCurrentVersion(version);
|
|
28
19
|
}
|
|
29
20
|
}, [version, currentVersion]);
|
|
30
|
-
const toggleDarkMode = () => {
|
|
31
|
-
setIsDarkMode(!isDarkMode);
|
|
32
|
-
};
|
|
33
21
|
const refreshPage = () => {
|
|
34
22
|
navigate(0);
|
|
35
23
|
};
|
|
@@ -43,7 +31,6 @@ export const NavBar = ({ title, loggedIn, items, selectedColor, version, }) => {
|
|
|
43
31
|
}) }));
|
|
44
32
|
const options = (_jsxs(_Fragment, { children: [_jsx("ul", { className: "w-full lg:flex lg:items-center", children: renderNavItems(items.filter((item) => item.loggedIn === loggedIn)) }), _jsx("ul", { className: "sm:flex sm:flex-col lg:flex-row ml-auto", children: loggedIn && (_jsx(OptionsDropDownButton, { color: color, options: [
|
|
45
33
|
_jsx(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`, to: "/logout", prefetch: "none", children: _jsxs("div", { className: "mx-auto pt-2", children: [_jsx(LogoutDropDownSvgIcon, {}), _jsx("span", { children: "Cerrar sesi\u00F3n" })] }) }),
|
|
46
|
-
_jsx("button", { className: `block w-full text-left px-4 py-3 text-sm text-gray-600 capitalize transition-colors duration-200 transform dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 dark:hover:text-white`, onClick: toggleDarkMode, children: _jsxs("div", { className: "mx-auto pt-2", children: [isDarkMode ? "🌞" : "🌙", _jsx("span", { children: isDarkMode ? "Modo Claro" : "Modo Oscuro" })] }) }),
|
|
47
34
|
] })) })] }));
|
|
48
35
|
return (_jsx("nav", { className: `py-3 ${color.bg600}`, children: _jsxs("div", { className: "flex items-center justify-between ml-5 mr-5", children: [_jsxs("div", { className: "flex justify-between items-center w-full lg:w-auto", children: [_jsx(Link, { className: "text-sm font-bold leading-relaxed inline-block mr-4 py-2 whitespace-nowrap uppercase text-white", to: "/home", prefetch: "none", children: _jsxs(_Fragment, { children: [_jsx("div", { className: "inline-block mr-2 mb-2 align-middle", children: _jsx("img", { className: "w-auto h-7", src: "/logo.png", alt: "logo-zauru" }) }), title] }) }), version !== currentVersion && (_jsx("button", { className: `ml-2 px-2 py-1 text-xs text-white ${color.bg700} rounded-full hover:${color.bg900} transition-colors duration-200`, onClick: refreshPage, children: "\uD83D\uDD04 Actualizar versi\u00F3n" })), _jsx("button", { className: `rounded lg:hidden focus:outline-none focus:ring focus:${color.ring600} focus:ring-opacity-50`, "aria-label": "Toggle mobile menu", type: "button", onClick: () => setNavBarOpen(!NavBarOpen), children: _jsx(MenuAlt4Svg, { open: NavBarOpen }) })] }), _jsx("div", { className: `lg:hidden fixed top-0 left-0 z-50 w-64 h-full ${color.bg700} dark:bg-gray-900 shadow-lg transform ${NavBarOpen ? "translate-x-0" : "-translate-x-full"} transition-transform duration-300 ease-in-out overflow-y-auto`, children: _jsx("div", { className: "p-4", children: options }) }), _jsx("div", { className: "hidden lg:flex lg:items-center w-full lg:w-auto", children: options })] }) }));
|
|
49
36
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
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-
|
|
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-200 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
5
|
};
|
|
6
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.78",
|
|
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.78",
|
|
37
|
+
"@zauru-sdk/hooks": "^2.0.78",
|
|
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.78",
|
|
40
|
+
"@zauru-sdk/utils": "^2.0.78",
|
|
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": "3fa94199738bbc2e4144bd618bc85eb12bae2925"
|
|
53
53
|
}
|
|
@@ -6,9 +6,5 @@ type Props = {
|
|
|
6
6
|
|
|
7
7
|
export const BodyContainer = (props: Props) => {
|
|
8
8
|
const { children } = props;
|
|
9
|
-
return
|
|
10
|
-
<>
|
|
11
|
-
<body className="flex flex-col min-h-screen m-0">{children}</body>
|
|
12
|
-
</>
|
|
13
|
-
);
|
|
9
|
+
return <body className="flex flex-col min-h-screen m-0">{children}</body>;
|
|
14
10
|
};
|
|
@@ -40,6 +40,11 @@ type Props = {
|
|
|
40
40
|
withoutBg?: boolean;
|
|
41
41
|
orientation?: "horizontal" | "vertical";
|
|
42
42
|
maxRows?: number;
|
|
43
|
+
confirmDelete?: boolean;
|
|
44
|
+
addRowButtonHandler?: (
|
|
45
|
+
tableData: RowDataType[],
|
|
46
|
+
setTableData: (data: RowDataType[]) => void
|
|
47
|
+
) => void;
|
|
43
48
|
};
|
|
44
49
|
|
|
45
50
|
const GenericDynamicTableErrorComponent = ({ name }: { name: string }) => {
|
|
@@ -127,6 +132,8 @@ export const GenericDynamicTable = (props: Props) => {
|
|
|
127
132
|
withoutBg = false,
|
|
128
133
|
orientation = "horizontal",
|
|
129
134
|
maxRows,
|
|
135
|
+
confirmDelete = true,
|
|
136
|
+
addRowButtonHandler,
|
|
130
137
|
} = props;
|
|
131
138
|
|
|
132
139
|
try {
|
|
@@ -346,15 +353,19 @@ export const GenericDynamicTable = (props: Props) => {
|
|
|
346
353
|
) => {
|
|
347
354
|
event.preventDefault();
|
|
348
355
|
event.stopPropagation();
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
356
|
+
if (confirmDelete) {
|
|
357
|
+
createModal({
|
|
358
|
+
title: "¿Está seguro que quiere eliminar este registro?",
|
|
359
|
+
description:
|
|
360
|
+
"Una vez eliminada la información no podrá ser recuperada.",
|
|
361
|
+
}).then((response) => {
|
|
362
|
+
if (response === "OK") {
|
|
363
|
+
removeRow(rowData.id);
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
} else {
|
|
367
|
+
removeRow(rowData.id);
|
|
368
|
+
}
|
|
358
369
|
}}
|
|
359
370
|
type="button"
|
|
360
371
|
>
|
|
@@ -474,7 +485,11 @@ export const GenericDynamicTable = (props: Props) => {
|
|
|
474
485
|
) => {
|
|
475
486
|
event.preventDefault();
|
|
476
487
|
event.stopPropagation();
|
|
477
|
-
|
|
488
|
+
if (addRowButtonHandler) {
|
|
489
|
+
addRowButtonHandler(tableData, setTableData);
|
|
490
|
+
} else {
|
|
491
|
+
addRow();
|
|
492
|
+
}
|
|
478
493
|
}}
|
|
479
494
|
type="button"
|
|
480
495
|
>
|
package/src/Footer/Footer.tsx
CHANGED
|
@@ -11,7 +11,8 @@ type FooterProps = {
|
|
|
11
11
|
| "slate"
|
|
12
12
|
| "green"
|
|
13
13
|
| "red"
|
|
14
|
-
| "sky"
|
|
14
|
+
| "sky"
|
|
15
|
+
| "yellow";
|
|
15
16
|
version?: string;
|
|
16
17
|
};
|
|
17
18
|
|
|
@@ -24,6 +25,7 @@ const COLORS = {
|
|
|
24
25
|
green: "bg-green-500",
|
|
25
26
|
red: "bg-red-500",
|
|
26
27
|
sky: "bg-sky-500",
|
|
28
|
+
yellow: "bg-yellow-500",
|
|
27
29
|
};
|
|
28
30
|
|
|
29
31
|
export const Footer = ({
|
|
@@ -38,8 +40,7 @@ export const Footer = ({
|
|
|
38
40
|
<footer className={`inset-x-0 bottom-0 px-2 py-[20px] ${color}`}>
|
|
39
41
|
<div className="px-4 mx-auto flex flex-wrap items-center justify-center">
|
|
40
42
|
<p className="text-white text-[1.2rem]">
|
|
41
|
-
{`Creado en `} <a href={href}>Zauru</a>{
|
|
42
|
-
{`con ❤️ ${new Date().getFullYear()} v.${version}`}
|
|
43
|
+
{`Creado en `} <a href={href}>Zauru</a> {`con ❤️ v.${version}`}
|
|
43
44
|
</p>
|
|
44
45
|
{showConnection && (
|
|
45
46
|
<div className="ml-5">
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { SelectFieldOption } from "@zauru-sdk/types";
|
|
2
|
+
import React, { useState } from "react";
|
|
3
|
+
import { useFormContext } from "react-hook-form";
|
|
4
|
+
|
|
5
|
+
type Props = {
|
|
6
|
+
id?: string;
|
|
7
|
+
name: string;
|
|
8
|
+
title?: string;
|
|
9
|
+
options: SelectFieldOption[];
|
|
10
|
+
defaultValue?: string | number;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
required?: boolean;
|
|
13
|
+
onChange?: (
|
|
14
|
+
value: string | number,
|
|
15
|
+
event: React.ChangeEvent<HTMLInputElement>
|
|
16
|
+
) => void;
|
|
17
|
+
helpText?: string;
|
|
18
|
+
hint?: string;
|
|
19
|
+
className?: string;
|
|
20
|
+
orientation?: "horizontal" | "vertical";
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const RadioButton = (props: Props) => {
|
|
24
|
+
const {
|
|
25
|
+
id,
|
|
26
|
+
name,
|
|
27
|
+
title,
|
|
28
|
+
options,
|
|
29
|
+
defaultValue,
|
|
30
|
+
disabled = false,
|
|
31
|
+
required = false,
|
|
32
|
+
onChange,
|
|
33
|
+
helpText,
|
|
34
|
+
hint,
|
|
35
|
+
className = "",
|
|
36
|
+
orientation = "vertical",
|
|
37
|
+
} = props;
|
|
38
|
+
|
|
39
|
+
const [selectedValue, setSelectedValue] = useState(defaultValue);
|
|
40
|
+
const {
|
|
41
|
+
register: tempRegister,
|
|
42
|
+
formState: { errors },
|
|
43
|
+
setValue: setOnFormValue,
|
|
44
|
+
} = useFormContext() || { formState: {} };
|
|
45
|
+
|
|
46
|
+
const error = errors ? errors[name] : undefined;
|
|
47
|
+
const color = error ? "red" : "gray";
|
|
48
|
+
|
|
49
|
+
const register = tempRegister ? tempRegister(name, { required }) : undefined; // Solo usar register si está disponible
|
|
50
|
+
|
|
51
|
+
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
52
|
+
const newValue = event.target.value;
|
|
53
|
+
setSelectedValue(newValue);
|
|
54
|
+
|
|
55
|
+
if (register) {
|
|
56
|
+
register.onChange(event);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (setOnFormValue) {
|
|
60
|
+
setOnFormValue(name, newValue);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
onChange && onChange(newValue, event);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const containerClass =
|
|
67
|
+
orientation === "horizontal"
|
|
68
|
+
? "flex items-center gap-4 flex-wrap" // Estilo para disposición horizontal
|
|
69
|
+
: ""; // Vertical no necesita clase especial
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<div className={`col-span-6 sm:col-span-3 ${className}`}>
|
|
73
|
+
{title && (
|
|
74
|
+
<label
|
|
75
|
+
htmlFor={id ?? name}
|
|
76
|
+
className={`block mb-1 text-sm font-medium text-${color}-700 dark:text-${color}-500`}
|
|
77
|
+
>
|
|
78
|
+
{title}
|
|
79
|
+
{required && <span className="text-red-500">*</span>}
|
|
80
|
+
</label>
|
|
81
|
+
)}
|
|
82
|
+
<div className={containerClass}>
|
|
83
|
+
{options.map((option) => (
|
|
84
|
+
<div
|
|
85
|
+
key={option.value}
|
|
86
|
+
className={`flex items-center mb-2 ${
|
|
87
|
+
disabled ? "opacity-50 pointer-events-none" : ""
|
|
88
|
+
}`}
|
|
89
|
+
>
|
|
90
|
+
<input
|
|
91
|
+
type="radio"
|
|
92
|
+
id={`${name}-${option.value}`}
|
|
93
|
+
value={option.value}
|
|
94
|
+
disabled={disabled}
|
|
95
|
+
checked={selectedValue === option.value}
|
|
96
|
+
className={`h-4 w-4 text-indigo-600 border-${color}-300 focus:ring-indigo-500`}
|
|
97
|
+
{...(register ?? {})}
|
|
98
|
+
onChange={handleChange}
|
|
99
|
+
/>
|
|
100
|
+
<label
|
|
101
|
+
htmlFor={`${name}-${option.value}`}
|
|
102
|
+
className={`ml-2 block text-sm font-medium text-${color}-900 dark:text-${color}-500`}
|
|
103
|
+
>
|
|
104
|
+
{option.label}
|
|
105
|
+
</label>
|
|
106
|
+
</div>
|
|
107
|
+
))}
|
|
108
|
+
</div>
|
|
109
|
+
{helpText && (
|
|
110
|
+
<p
|
|
111
|
+
className={`mt-2 italic text-sm text-${color}-500 dark:text-${color}-400`}
|
|
112
|
+
>
|
|
113
|
+
{helpText}
|
|
114
|
+
</p>
|
|
115
|
+
)}
|
|
116
|
+
{error && (
|
|
117
|
+
<p className={`mt-2 text-sm text-${color}-600 dark:text-${color}-500`}>
|
|
118
|
+
<span className="font-medium">Oops!</span> {error.message?.toString()}
|
|
119
|
+
</p>
|
|
120
|
+
)}
|
|
121
|
+
{!error && hint && (
|
|
122
|
+
<p
|
|
123
|
+
className={`mt-2 italic text-sm text-${color}-500 dark:text-${color}-400`}
|
|
124
|
+
>
|
|
125
|
+
{hint}
|
|
126
|
+
</p>
|
|
127
|
+
)}
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
};
|
|
@@ -226,11 +226,8 @@ export const SelectField = (props: Props) => {
|
|
|
226
226
|
handleOptionClick(filteredOptions[highlightedIndex]);
|
|
227
227
|
} else if (e.key === "Backspace" && (value || valueMulti.length > 0)) {
|
|
228
228
|
e.preventDefault();
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
} else {
|
|
232
|
-
setInputValue("");
|
|
233
|
-
}
|
|
229
|
+
handleClear();
|
|
230
|
+
setInputValue("");
|
|
234
231
|
setFilteredOptions(options);
|
|
235
232
|
setIsOpen(true);
|
|
236
233
|
}
|
package/src/Form/index.ts
CHANGED
|
@@ -8,10 +8,12 @@ export const ValidateEmployeeAccess = ({
|
|
|
8
8
|
children,
|
|
9
9
|
permissionVariableName,
|
|
10
10
|
showIfNoPermission = false,
|
|
11
|
+
noPermissionComponent,
|
|
11
12
|
}: {
|
|
12
13
|
children: React.ReactNode;
|
|
13
14
|
permissionVariableName: string;
|
|
14
15
|
showIfNoPermission?: boolean;
|
|
16
|
+
noPermissionComponent?: React.ReactNode;
|
|
15
17
|
}) => {
|
|
16
18
|
const { data: employee } = useGetEmployeeProfile();
|
|
17
19
|
const variable_string = useGetSessionAttribute(
|
|
@@ -47,5 +49,9 @@ export const ValidateEmployeeAccess = ({
|
|
|
47
49
|
);
|
|
48
50
|
}
|
|
49
51
|
|
|
52
|
+
if (noPermissionComponent && !hasPermission) {
|
|
53
|
+
return <>{noPermissionComponent}</>;
|
|
54
|
+
}
|
|
55
|
+
|
|
50
56
|
return <>{hasPermission ? children : null}</>;
|
|
51
57
|
};
|
|
@@ -15,63 +15,75 @@ export const ErrorLayout = ({
|
|
|
15
15
|
from?: string;
|
|
16
16
|
error?: Error;
|
|
17
17
|
}) => {
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
try {
|
|
19
|
+
const error = useRouteError();
|
|
20
|
+
const [showDetails, setShowDetails] = useState(!!parentError);
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
</p>
|
|
41
|
-
{from && (
|
|
42
|
-
<p className="text-lg text-gray-400 mb-4 text-center">
|
|
43
|
-
Error lanzado desde: {from}
|
|
22
|
+
return (
|
|
23
|
+
<html lang="es" className="bg-gray-900 text-white">
|
|
24
|
+
<head>
|
|
25
|
+
<meta charSet="utf-8" />
|
|
26
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
27
|
+
<title>¡Ups! Algo salió mal</title>
|
|
28
|
+
<Meta />
|
|
29
|
+
<Links />
|
|
30
|
+
</head>
|
|
31
|
+
<body className="min-h-screen flex flex-col items-center justify-center p-4">
|
|
32
|
+
<img src="/logo.png" alt="Zauru Logo" className="mb-8 h-20" />
|
|
33
|
+
<h1 className="text-5xl font-extrabold text-red-500 mb-6">¡Ups!</h1>
|
|
34
|
+
<div className="w-full max-w-2xl flex flex-col items-center">
|
|
35
|
+
<p className="text-2xl text-gray-300 mb-8 text-center">
|
|
36
|
+
{isRouteErrorResponse(error)
|
|
37
|
+
? `Error ${error.status}: ${error.statusText}`
|
|
38
|
+
: error instanceof Error
|
|
39
|
+
? error.message
|
|
40
|
+
: "Ha ocurrido un error inesperado"}
|
|
44
41
|
</p>
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
>
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
{
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
42
|
+
{from && (
|
|
43
|
+
<p className="text-lg text-gray-400 mb-4 text-center">
|
|
44
|
+
Error lanzado desde: {from}
|
|
45
|
+
</p>
|
|
46
|
+
)}
|
|
47
|
+
{parentError && (
|
|
48
|
+
<div className="mb-4 text-center">
|
|
49
|
+
<button
|
|
50
|
+
onClick={() => setShowDetails(!showDetails)}
|
|
51
|
+
className="text-blue-400 hover:text-blue-300 transition duration-300"
|
|
52
|
+
>
|
|
53
|
+
{showDetails ? "Ocultar detalles" : "Ver más detalles"}
|
|
54
|
+
</button>
|
|
55
|
+
{showDetails && (
|
|
56
|
+
<p className="mt-2 text-gray-400 text-sm">
|
|
57
|
+
{parentError instanceof Error
|
|
58
|
+
? parentError.message
|
|
59
|
+
: String(parentError)}
|
|
60
|
+
</p>
|
|
61
|
+
)}
|
|
62
|
+
</div>
|
|
63
|
+
)}
|
|
64
|
+
</div>
|
|
65
|
+
<Link
|
|
66
|
+
to="/"
|
|
67
|
+
className="bg-blue-600 text-white py-3 px-8 rounded-full text-lg font-semibold hover:bg-blue-700 transition duration-300 transform hover:scale-105"
|
|
68
|
+
>
|
|
69
|
+
Regresar al inicio
|
|
70
|
+
</Link>
|
|
71
|
+
<div className="mt-12 text-gray-500 text-center">
|
|
72
|
+
<p>Si el problema persiste, por favor contacta a soporte.</p>
|
|
73
|
+
</div>
|
|
74
|
+
<Scripts />
|
|
75
|
+
</body>
|
|
76
|
+
</html>
|
|
77
|
+
);
|
|
78
|
+
} catch (error: any) {
|
|
79
|
+
console.error(error);
|
|
80
|
+
return (
|
|
81
|
+
<html lang="es" className="bg-gray-900 text-white">
|
|
82
|
+
<head>
|
|
83
|
+
<title>¡Ups! Algo salió mal</title>
|
|
84
|
+
</head>
|
|
85
|
+
<body>Error al renderizar el layout de error {error}</body>
|
|
86
|
+
</html>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
77
89
|
};
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { createRoot } from "react-dom/client";
|
|
3
|
+
|
|
4
|
+
// Tipos de datos
|
|
5
|
+
type Item = {
|
|
6
|
+
name: string;
|
|
7
|
+
code: string;
|
|
8
|
+
unitPrice: number;
|
|
9
|
+
stock: number;
|
|
10
|
+
currencyPrefix: string;
|
|
11
|
+
imageUrl: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type ItemCategory = { name: string; items: Item[] };
|
|
15
|
+
|
|
16
|
+
type ItemModalProps = {
|
|
17
|
+
itemList: ItemCategory[];
|
|
18
|
+
onClose: (selectedItem: Item | null) => void;
|
|
19
|
+
config?: { itemSize: { width: string; height: string } }; // Tamaño configurable
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const ItemSelectionModal: React.FC<ItemModalProps> = ({
|
|
23
|
+
itemList,
|
|
24
|
+
onClose,
|
|
25
|
+
config,
|
|
26
|
+
}) => {
|
|
27
|
+
const defaultConfig = {
|
|
28
|
+
itemSize: {
|
|
29
|
+
width: config?.itemSize?.width || "150px",
|
|
30
|
+
height: config?.itemSize?.height || "200px",
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const [searchTerm, setSearchTerm] = useState("");
|
|
35
|
+
const [expandedCategories, setExpandedCategories] = useState<{
|
|
36
|
+
[key: string]: boolean;
|
|
37
|
+
}>({});
|
|
38
|
+
|
|
39
|
+
const toggleCategory = (categoryName: string) => {
|
|
40
|
+
setExpandedCategories((prev) => ({
|
|
41
|
+
...prev,
|
|
42
|
+
[categoryName]: !prev[categoryName],
|
|
43
|
+
}));
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const handleItemClick = (item: Item) => {
|
|
47
|
+
onClose(item);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const filteredList = itemList
|
|
51
|
+
.map((category) => ({
|
|
52
|
+
...category,
|
|
53
|
+
items: category.items.filter((item) =>
|
|
54
|
+
item.name.toLowerCase().includes(searchTerm.toLowerCase())
|
|
55
|
+
),
|
|
56
|
+
}))
|
|
57
|
+
.filter(
|
|
58
|
+
(category) =>
|
|
59
|
+
category.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
60
|
+
category.items.length > 0
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div className="fixed inset-0 bg-black bg-opacity-50 flex justify-center items-center z-50">
|
|
65
|
+
<div className="bg-white p-4 rounded-lg shadow-lg w-11/12 max-w-4xl h-5/6 overflow-y-auto">
|
|
66
|
+
{/* Header */}
|
|
67
|
+
<div className="flex flex-col mb-4">
|
|
68
|
+
<div className="flex justify-between items-center mb-4">
|
|
69
|
+
<h2 className="text-2xl font-bold">Seleccionar un Ítem</h2>
|
|
70
|
+
<button
|
|
71
|
+
className="text-gray-500 hover:text-gray-800"
|
|
72
|
+
onClick={() => onClose(null)}
|
|
73
|
+
>
|
|
74
|
+
×
|
|
75
|
+
</button>
|
|
76
|
+
</div>
|
|
77
|
+
{/* Barra de búsqueda */}
|
|
78
|
+
<input
|
|
79
|
+
type="text"
|
|
80
|
+
placeholder="Buscar por nombre o categoría..."
|
|
81
|
+
value={searchTerm}
|
|
82
|
+
onChange={(e) => setSearchTerm(e.target.value)}
|
|
83
|
+
className="p-2 border rounded-lg w-full"
|
|
84
|
+
/>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
{/* Lista de categorías e ítems */}
|
|
88
|
+
{filteredList.length === 0 ? (
|
|
89
|
+
<p className="text-gray-500 text-center">
|
|
90
|
+
No se encontraron resultados.
|
|
91
|
+
</p>
|
|
92
|
+
) : (
|
|
93
|
+
filteredList.map((category) => (
|
|
94
|
+
<div key={category.name} className="mb-4">
|
|
95
|
+
<h3
|
|
96
|
+
className="text-lg font-semibold mb-2 cursor-pointer flex justify-between items-center hover:text-blue-600"
|
|
97
|
+
onClick={() => toggleCategory(category.name)}
|
|
98
|
+
>
|
|
99
|
+
<span>
|
|
100
|
+
{category.name}{" "}
|
|
101
|
+
<span className="text-gray-500">
|
|
102
|
+
({category.items.length})
|
|
103
|
+
</span>
|
|
104
|
+
</span>
|
|
105
|
+
<span
|
|
106
|
+
className={`transform transition-transform duration-200 ${
|
|
107
|
+
expandedCategories[category.name] ? "rotate-90" : ""
|
|
108
|
+
}`}
|
|
109
|
+
>
|
|
110
|
+
▶
|
|
111
|
+
</span>
|
|
112
|
+
</h3>
|
|
113
|
+
{expandedCategories[category.name] && (
|
|
114
|
+
<div
|
|
115
|
+
className="grid gap-5"
|
|
116
|
+
style={{
|
|
117
|
+
gridTemplateColumns: `repeat(auto-fit, minmax(${defaultConfig.itemSize.width}, 1fr))`,
|
|
118
|
+
}}
|
|
119
|
+
>
|
|
120
|
+
{category.items.map((item) => (
|
|
121
|
+
<div
|
|
122
|
+
key={item.code}
|
|
123
|
+
className="border rounded-lg shadow-lg hover:shadow-xl cursor-pointer relative"
|
|
124
|
+
onClick={() => handleItemClick(item)}
|
|
125
|
+
style={{
|
|
126
|
+
backgroundImage: `url(${item.imageUrl})`,
|
|
127
|
+
backgroundSize: "cover",
|
|
128
|
+
backgroundPosition: "center",
|
|
129
|
+
width: defaultConfig.itemSize.width,
|
|
130
|
+
height: defaultConfig.itemSize.height,
|
|
131
|
+
}}
|
|
132
|
+
>
|
|
133
|
+
<div className="bg-white bg-opacity-80 p-2 rounded absolute top-0 left-0 right-0">
|
|
134
|
+
<h4 className="font-bold text-sm text-center">
|
|
135
|
+
{item.name}
|
|
136
|
+
</h4>
|
|
137
|
+
</div>
|
|
138
|
+
<div className="absolute bottom-0 left-0 bg-white bg-opacity-80 p-2 rounded">
|
|
139
|
+
<span className="text-xs">Stock: {item.stock}</span>
|
|
140
|
+
</div>
|
|
141
|
+
<div className="absolute bottom-0 right-0 bg-white bg-opacity-80 p-2 rounded">
|
|
142
|
+
<span className="text-xs">
|
|
143
|
+
{item.currencyPrefix}
|
|
144
|
+
{item.unitPrice}
|
|
145
|
+
</span>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
))}
|
|
149
|
+
</div>
|
|
150
|
+
)}
|
|
151
|
+
</div>
|
|
152
|
+
))
|
|
153
|
+
)}
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// Función para crear el modal
|
|
160
|
+
export const createItemModal = (
|
|
161
|
+
itemList: ItemCategory[],
|
|
162
|
+
config?: ItemModalProps["config"]
|
|
163
|
+
): Promise<Item | null> => {
|
|
164
|
+
return new Promise((resolve) => {
|
|
165
|
+
const handleClose = (selectedItem: Item | null) => {
|
|
166
|
+
resolve(selectedItem);
|
|
167
|
+
removeModal();
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const removeModal = () => {
|
|
171
|
+
document.body.removeChild(modalWrapper);
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const modalWrapper = document.createElement("div");
|
|
175
|
+
document.body.appendChild(modalWrapper);
|
|
176
|
+
|
|
177
|
+
const root = createRoot(modalWrapper);
|
|
178
|
+
root.render(
|
|
179
|
+
<ItemSelectionModal
|
|
180
|
+
itemList={itemList}
|
|
181
|
+
onClose={handleClose}
|
|
182
|
+
config={config}
|
|
183
|
+
/>
|
|
184
|
+
);
|
|
185
|
+
});
|
|
186
|
+
};
|
package/src/Modal/index.tsx
CHANGED
package/src/NavBar/NavBar.tsx
CHANGED
|
@@ -98,28 +98,15 @@ export const NavBar = ({
|
|
|
98
98
|
}: NavBarProps) => {
|
|
99
99
|
const color: ColorInterface = COLORS[selectedColor];
|
|
100
100
|
const [NavBarOpen, setNavBarOpen] = useState(false);
|
|
101
|
-
const [isDarkMode, setIsDarkMode] = useState(false);
|
|
102
101
|
const [currentVersion, setCurrentVersion] = useState(version);
|
|
103
102
|
const navigate = useNavigate();
|
|
104
103
|
|
|
105
|
-
useEffect(() => {
|
|
106
|
-
if (isDarkMode) {
|
|
107
|
-
document.documentElement.classList.add("dark");
|
|
108
|
-
} else {
|
|
109
|
-
document.documentElement.classList.remove("dark");
|
|
110
|
-
}
|
|
111
|
-
}, [isDarkMode]);
|
|
112
|
-
|
|
113
104
|
useEffect(() => {
|
|
114
105
|
if (version !== currentVersion) {
|
|
115
106
|
setCurrentVersion(version);
|
|
116
107
|
}
|
|
117
108
|
}, [version, currentVersion]);
|
|
118
109
|
|
|
119
|
-
const toggleDarkMode = () => {
|
|
120
|
-
setIsDarkMode(!isDarkMode);
|
|
121
|
-
};
|
|
122
|
-
|
|
123
110
|
const refreshPage = () => {
|
|
124
111
|
navigate(0);
|
|
125
112
|
};
|
|
@@ -167,15 +154,6 @@ export const NavBar = ({
|
|
|
167
154
|
<span>Cerrar sesión</span>
|
|
168
155
|
</div>
|
|
169
156
|
</Link>,
|
|
170
|
-
<button
|
|
171
|
-
className={`block w-full text-left px-4 py-3 text-sm text-gray-600 capitalize transition-colors duration-200 transform dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 dark:hover:text-white`}
|
|
172
|
-
onClick={toggleDarkMode}
|
|
173
|
-
>
|
|
174
|
-
<div className="mx-auto pt-2">
|
|
175
|
-
{isDarkMode ? "🌞" : "🌙"}
|
|
176
|
-
<span>{isDarkMode ? "Modo Claro" : "Modo Oscuro"}</span>
|
|
177
|
-
</div>
|
|
178
|
-
</button>,
|
|
179
157
|
]}
|
|
180
158
|
/>
|
|
181
159
|
)}
|
|
@@ -12,11 +12,11 @@ interface LoadingWindowProps {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export const LoadingWindow: React.FC<LoadingWindowProps> = ({
|
|
15
|
-
loadingItems,
|
|
15
|
+
loadingItems = [],
|
|
16
16
|
description = "Por favor, espere mientras se carga la página.",
|
|
17
17
|
}) => {
|
|
18
18
|
return (
|
|
19
|
-
<div className="min-h-screen bg-gray-
|
|
19
|
+
<div className="min-h-screen bg-gray-200 flex flex-col justify-center items-center">
|
|
20
20
|
<motion.div
|
|
21
21
|
initial={{ opacity: 0, y: -20 }}
|
|
22
22
|
animate={{ opacity: 1, y: 0 }}
|