@zauru-sdk/components 2.0.119 → 2.0.120

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.
@@ -3,65 +3,72 @@ import { DownloadIconSVG, IdeaIconSVG } from "@zauru-sdk/icons";
3
3
  import { useState } from "react";
4
4
  import { useFormContext } from "react-hook-form";
5
5
  export const FileUploadField = (props) => {
6
- const { id, name, title, helpText, hint, onChange, disabled = false, readOnly = false, fileTypes = [], showAvailableTypes = false, className, defaultValue = undefined, download = false, required = false, } = props;
7
- const { register: tempRegister, formState: { errors }, } = useFormContext() || { formState: {} }; // Obtener el contexto solo si existe
8
- const error = errors ? errors[props.name ?? "-1"] : undefined;
9
- const register = tempRegister
10
- ? tempRegister(props.name ?? "-1", { required })
11
- : undefined; // Solo usar register si está disponible
6
+ const { id, name, title, helpText, hint, onChange, readOnly = false, fileTypes = [], showAvailableTypes = false, className, defaultValue, download = false, required = false, } = props;
7
+ const { register: tempRegister, formState: { errors }, } = useFormContext() || { formState: {} };
8
+ const error = errors ? errors[name] : undefined;
9
+ const register = tempRegister ? tempRegister(name, { required }) : undefined;
12
10
  const [showTooltip, setShowTooltip] = useState(false);
13
- if (typeof defaultValue == "string") {
11
+ // Para mostrar en el hint los tipos de archivo permitidos (opcional)
12
+ let hintMessage = hint;
13
+ if (showAvailableTypes && fileTypes.length > 0) {
14
+ hintMessage = `${hint || ""} Archivos permitidos: ${fileTypes.join(", ")}`;
15
+ }
16
+ // Clases de estilo basadas en si hay error (color rojo) o no (gris),
17
+ // pero ahora ignoramos el "disabled" y nos centramos en "readOnly".
18
+ const color = error ? "red" : "gray";
19
+ const isReadOnly = readOnly;
20
+ // En modo readOnly, puedes poner un fondo distinto, o dejarlo en blanco
21
+ const bgColor = isReadOnly ? "bg-gray-100" : `bg-${color}-50`;
22
+ const textColor = isReadOnly ? "text-gray-700" : `text-${color}-900`;
23
+ const borderColor = error ? "border-red-500" : `border-${color}-500`;
24
+ /**
25
+ * onChange normal del input.
26
+ * Sólo se llama cuando readOnly es false (porque si es true ni renderizamos el input).
27
+ */
28
+ const handleInputChange = (event) => {
29
+ onChange && onChange(event);
30
+ // Si usas register, la parte interna de react-hook-form también se encargará
31
+ // del cambio, no necesitas llamarlo manualmente.
32
+ };
33
+ /**
34
+ * Para el "preview" cuando `defaultValue` es string:
35
+ * - Si `download` es true, mostramos un icono de descarga.
36
+ * - Si `download` es false, mostramos la imagen en miniatura.
37
+ * El click abre la URL en nueva ventana.
38
+ */
39
+ function renderPreview(defaultValue) {
14
40
  if (download) {
15
- return (_jsxs("div", { role: "button", tabIndex: 0, onClick: () => {
16
- window.open(defaultValue, "_blank");
17
- }, onKeyDown: (event) => {
18
- // Permite que el evento se active con la tecla Enter
41
+ // Botón de descarga
42
+ return (_jsxs("div", { role: "button", tabIndex: 0, onClick: () => window.open(defaultValue, "_blank"), onKeyDown: (event) => {
19
43
  if (event.key === "Enter") {
20
44
  window.open(defaultValue, "_blank");
21
45
  }
22
- }, children: [title && (_jsx("label", { htmlFor: name, className: "block mb-1 text-sm font-medium text-gray-700", children: title })), " ", _jsx(DownloadIconSVG, {})] }));
46
+ }, className: "inline-flex items-center cursor-pointer", children: [_jsx(DownloadIconSVG, {}), _jsx("span", { className: "ml-1 text-blue-600 underline", children: "Descargar archivo" })] }));
23
47
  }
24
- return (_jsxs("div", { className: `col-span-6 sm:col-span-3 ${className}`, children: [title && (_jsxs("label", { htmlFor: name, className: `block mb-1 text-sm font-medium text-gray-700`, children: [title, required && _jsx("span", { className: "text-red-500", children: "*" })] })), " ", _jsx("div", { role: "button", tabIndex: 0, onClick: () => {
25
- if (register) {
26
- register.onChange({
27
- target: {
28
- value: defaultValue,
29
- },
30
- });
31
- }
48
+ else {
49
+ // Vista previa como imagen
50
+ return (_jsx("div", { role: "button", tabIndex: 0, onClick: () => window.open(defaultValue, "_blank"), onKeyDown: (event) => {
51
+ if (event.key === "Enter") {
32
52
  window.open(defaultValue, "_blank");
33
- }, onKeyDown: (event) => {
34
- // Permite que el evento se active con la tecla Enter
35
- if (event.key === "Enter") {
36
- if (register) {
37
- register.onChange({
38
- target: {
39
- value: defaultValue,
40
- },
41
- });
42
- }
43
- window.open(defaultValue, "_blank");
44
- }
45
- }, children: _jsx("img", { src: defaultValue, alt: name, className: `h-48 w-48 inline mr-1 pb-1`, style: {
46
- stroke: "currentColor",
47
- strokeWidth: 2,
48
- strokeLinecap: "round",
49
- strokeLinejoin: "round",
50
- fill: "none",
51
- backgroundColor: "transparent",
52
- } }) })] }));
53
+ }
54
+ }, className: "inline-block cursor-pointer", children: _jsx("img", { src: defaultValue, alt: name, className: "h-48 w-48 inline mr-1 pb-1", style: {
55
+ objectFit: "contain",
56
+ backgroundColor: "transparent",
57
+ } }) }));
58
+ }
53
59
  }
54
- const handleInputChange = (event) => {
55
- onChange && onChange(event);
56
- };
57
- let hintMessage = hint;
58
- if (showAvailableTypes && fileTypes.length > 0) {
59
- hintMessage = `${hint} Archivos permitidos: ${fileTypes.join(", ")}`;
60
+ /**
61
+ * 1) Si readOnly = true:
62
+ * - Si defaultValue es string -> Sólo mostramos el preview (descarga/imagen).
63
+ * - Si no hay defaultValue (o es File) -> mostramos "nada" o un texto de "Sin archivo".
64
+ */
65
+ if (readOnly) {
66
+ return (_jsxs("div", { className: `col-span-6 sm:col-span-3 ${className}`, children: [title && (_jsx("label", { htmlFor: name, className: `block mb-1 text-sm font-medium text-gray-700`, children: title })), typeof defaultValue === "string" && defaultValue ? (renderPreview(defaultValue)) : (_jsx("div", { className: "text-sm italic text-gray-400", children: "No hay archivo disponible" }))] }));
60
67
  }
61
- const color = error ? "red" : "gray";
62
- const isReadOnly = disabled || readOnly;
63
- const bgColor = isReadOnly ? "bg-gray-200" : `bg-${color}-50`;
64
- const textColor = isReadOnly ? "text-gray-500" : `text-${color}-900`;
65
- const borderColor = isReadOnly ? "border-gray-300" : `border-${color}-500`;
66
- return (_jsxs("div", { className: `col-span-6 sm:col-span-3 ${className}`, children: [title && (_jsx("label", { htmlFor: name, className: `block mb-1 text-sm font-medium text-${color}-700`, children: title })), _jsxs("div", { className: "flex relative items-center", children: [_jsx("input", { type: "file", name: name, id: id ?? name, disabled: disabled, readOnly: readOnly, accept: fileTypes.map((ft) => `.${ft}`).join(", "), onChange: handleInputChange, className: `block w-full rounded-md ${bgColor} ${borderColor} ${textColor} shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm` }), helpText && (_jsx("div", { className: "flex items-center relative ml-3", children: _jsxs("div", { className: "relative cursor-pointer", onMouseEnter: () => setShowTooltip(true), onMouseLeave: () => setShowTooltip(false), children: [_jsx(IdeaIconSVG, {}), showTooltip && (_jsx("div", { className: "absolute -left-48 top-0 mt-8 p-2 bg-white border rounded shadow text-black z-50", children: helpText }))] }) }))] }), error && (_jsxs("p", { className: `mt-2 text-sm text-${color}-600`, children: [_jsx("span", { className: "font-medium", children: "Oops!" }), " ", error.message?.toString()] })), !error && hintMessage && (_jsx("p", { className: `mt-2 italic text-sm text-${color}-500`, children: hintMessage }))] }));
68
+ /**
69
+ * 2) readOnly = false:
70
+ * - Si hay defaultValue y es string, mostramos la vista previa + el input
71
+ * - Si no hay defaultValue (o no es string) mostramos solo el input
72
+ */
73
+ return (_jsxs("div", { className: `col-span-6 sm:col-span-3 ${className}`, children: [title && (_jsxs("label", { htmlFor: name, className: `block mb-1 text-sm font-medium text-${color}-700`, children: [title, required && _jsx("span", { className: "text-red-500 ml-1", children: "*" })] })), typeof defaultValue === "string" && defaultValue && (_jsx("div", { className: "mb-2", children: renderPreview(defaultValue) })), _jsxs("div", { className: "flex relative items-center", children: [_jsx("input", { type: "file", id: id ?? name, accept: fileTypes.map((ft) => `.${ft}`).join(", "), className: `block w-full rounded-md ${bgColor} ${borderColor} ${textColor} shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm`, ...(register ?? {}), name: name, onChange: handleInputChange }), helpText && (_jsx("div", { className: "flex items-center relative ml-3", children: _jsxs("div", { className: "relative cursor-pointer", onMouseEnter: () => setShowTooltip(true), onMouseLeave: () => setShowTooltip(false), children: [_jsx(IdeaIconSVG, {}), showTooltip && (_jsx("div", { className: "absolute -left-48 top-0 mt-8 p-2 bg-white border rounded shadow text-black z-50", children: helpText }))] }) }))] }), error && (_jsxs("p", { className: `mt-2 text-sm text-red-600`, children: [_jsx("span", { className: "font-medium", children: "Oops!" }), " ", error.message?.toString()] })), !error && hintMessage && (_jsx("p", { className: `mt-2 italic text-sm text-${color}-500`, children: hintMessage }))] }));
67
74
  };
@@ -14,11 +14,19 @@ export const ReactZodForm = (props) => {
14
14
  const handleSubmit = (data, event) => {
15
15
  if (onSubmit) {
16
16
  onSubmit(data, event);
17
+ return;
17
18
  }
18
- else {
19
- // If no onSubmit is provided, use Remix's submit function
20
- submit(event?.target, { method });
19
+ const form = event?.target;
20
+ const nativeEvent = event?.nativeEvent;
21
+ // Armamos el FormData a partir del propio <form>
22
+ const formData = new FormData(form);
23
+ // Detectamos el botón que disparó el submit (submitter),
24
+ // en caso de que tenga un name/value, lo agregamos al formData
25
+ const submitter = nativeEvent.submitter;
26
+ if (submitter instanceof HTMLButtonElement && submitter.name) {
27
+ formData.append(submitter.name, submitter.value);
21
28
  }
29
+ submit(formData, { method, encType });
22
30
  };
23
31
  return (_jsx(FormProvider, { ...methods, children: _jsx(Form, { onSubmit: methods.handleSubmit(handleSubmit), method: method, id: id, className: className, encType: encType, children: children }) }));
24
32
  };
@@ -1,7 +1,7 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from "react";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState, useRef } from "react";
3
3
  import { createRoot } from "react-dom/client";
4
- const ItemSelectionModal = ({ itemList, onClose, config, }) => {
4
+ const ItemSelectionModal = ({ itemList, onClose, config, categoryViewMode = "text", }) => {
5
5
  const defaultConfig = {
6
6
  itemSize: {
7
7
  width: config?.itemSize?.width || "150px",
@@ -10,6 +10,18 @@ const ItemSelectionModal = ({ itemList, onClose, config, }) => {
10
10
  };
11
11
  const [searchTerm, setSearchTerm] = useState("");
12
12
  const [expandedCategories, setExpandedCategories] = useState({});
13
+ const [selectedItem, setSelectedItem] = useState(null);
14
+ const [quantity, setQuantity] = useState(1);
15
+ const [customPrice, setCustomPrice] = useState(null);
16
+ // Ref para forzar el scroll al tope cuando seleccionamos un ítem
17
+ const modalContentRef = useRef(null);
18
+ const filteredList = itemList
19
+ .map((category) => ({
20
+ ...category,
21
+ items: category.items.filter((item) => item.name.toLowerCase().includes(searchTerm.toLowerCase())),
22
+ }))
23
+ .filter((category) => category.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
24
+ category.items.length > 0);
13
25
  const toggleCategory = (categoryName) => {
14
26
  setExpandedCategories((prev) => ({
15
27
  ...prev,
@@ -17,27 +29,86 @@ const ItemSelectionModal = ({ itemList, onClose, config, }) => {
17
29
  }));
18
30
  };
19
31
  const handleItemClick = (item) => {
20
- onClose(item);
32
+ setSelectedItem(item);
33
+ setQuantity(1);
34
+ // Si el ítem tiene precio flexible, inicializamos el customPrice con su precio actual
35
+ setCustomPrice(item.flexiblePrice ? item.unitPrice : null);
36
+ // Forzar scroll a la parte superior del contenido, ya que antes se iniciaba el scroll desde donde se había quedado.
37
+ if (modalContentRef.current) {
38
+ modalContentRef.current.scrollTo({ top: 0, behavior: "smooth" });
39
+ }
21
40
  };
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))))] }) }));
41
+ const handleConfirmQuantity = () => {
42
+ if (selectedItem) {
43
+ // (5) Devolver el precio correcto
44
+ const finalPrice = selectedItem.flexiblePrice
45
+ ? // si el precio es flexible, tomar lo que el usuario haya ingresado (o fallback al precio original)
46
+ customPrice ?? selectedItem.unitPrice
47
+ : // si NO es flexible, solo usamos el precio que ya tenía
48
+ selectedItem.unitPrice;
49
+ onClose({
50
+ ...selectedItem,
51
+ quantity,
52
+ unitPrice: finalPrice,
53
+ });
54
+ }
55
+ };
56
+ const handleCancelQuantity = () => {
57
+ setSelectedItem(null);
58
+ setQuantity(1);
59
+ setCustomPrice(null);
60
+ };
61
+ const handleCloseModal = () => {
62
+ onClose(null);
63
+ };
64
+ return (_jsx("div", { className: "fixed inset-0 bg-black bg-opacity-50 flex justify-center items-center z-50", onClick: handleCloseModal, children: _jsxs("div", {
65
+ //Le ponemos ref para poder scrollear al tope
66
+ ref: modalContentRef, className: "bg-white p-4 rounded-lg shadow-lg w-11/12 max-w-4xl h-5/6 overflow-y-auto", onClick: (e) => e.stopPropagation(), children: [_jsxs("div", { className: "flex justify-between items-center mb-4", children: [_jsx("h2", { className: "text-2xl font-bold", children: selectedItem ? "Confirmar selección" : "Seleccionar un Ítem" }), _jsx("button", { className: "text-gray-500 hover:text-gray-800 text-3xl", onClick: handleCloseModal, children: "\u00D7" })] }), !selectedItem ? (
67
+ /* ==============================
68
+ PASO 1: SELECCIONAR ÍTEM
69
+ ============================== */
70
+ _jsxs(_Fragment, { children: [_jsxs("div", { className: "relative mb-4", children: [_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" }), searchTerm && (_jsx("button", { className: "absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-500 hover:text-gray-800", onClick: () => setSearchTerm(""), children: "\u00D7" }))] }), filteredList.length === 0 ? (_jsx("p", { className: "text-gray-500 text-center", children: "No se encontraron resultados." })) : (filteredList.map((category) => {
71
+ const isExpanded = expandedCategories[category.name];
72
+ // Estilos condicionales para "text" vs. "card"
73
+ const categoryContainerClasses = categoryViewMode === "card"
74
+ ? "w-full mb-2 px-4 py-3 bg-blue-50 rounded-md flex justify-between items-center cursor-pointer hover:bg-blue-100"
75
+ : "text-lg font-semibold mb-2 cursor-pointer flex justify-between items-center hover:text-blue-600";
76
+ return (_jsxs("div", { className: "mb-4", children: [_jsxs("div", { className: categoryContainerClasses, 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 ${isExpanded ? "rotate-90" : ""}`, children: "\u25B6" })] }), isExpanded && (_jsx("div", { className: "grid gap-5", style: {
77
+ gridTemplateColumns: `repeat(auto-fit, minmax(${defaultConfig.itemSize.width}, 1fr))`,
78
+ }, children: category.items.map((item) => {
79
+ // Verificamos si es un "paquete" usando item_id
80
+ const isPackage = item.item_id.startsWith("b");
81
+ return (_jsxs("div", { onClick: () => handleItemClick(item), className: `border rounded-lg shadow-lg hover:shadow-xl cursor-pointer relative ${isPackage ? "bg-yellow-50" : ""}`, style: {
82
+ backgroundImage: `url(${item.imageUrl})`,
83
+ backgroundSize: "cover",
84
+ backgroundPosition: "center",
85
+ width: defaultConfig.itemSize.width,
86
+ height: defaultConfig.itemSize.height,
87
+ }, children: [_jsx("div", { className: `p-2 rounded absolute top-0 left-0 right-0 ${isPackage
88
+ ? "bg-yellow-300 text-black"
89
+ : "bg-white bg-opacity-80"}`, children: _jsx("h4", { className: "font-semibold text-sm text-center", children: item.name }) }), _jsx("div", { className: `absolute bottom-0 left-0 p-2 rounded ${isPackage
90
+ ? "bg-yellow-300"
91
+ : "bg-white bg-opacity-80"}`, children: _jsxs("span", { className: "text-xs font-normal", children: ["Stock: ", item.stock] }) }), _jsx("div", { className: `absolute bottom-0 right-0 p-2 rounded ${isPackage
92
+ ? "bg-yellow-300"
93
+ : "bg-white bg-opacity-80"}`, children: _jsxs("span", { className: "text-xs font-normal", children: [item.currencyPrefix, item.priceText] }) })] }, item.code));
94
+ }) }))] }, category.name));
95
+ }))] })) : (
96
+ /* ==============================
97
+ PASO 2: CONFIRMAR SELECCIÓN
98
+ ============================== */
99
+ _jsxs("div", { className: "flex flex-col items-center justify-center", children: [_jsx("img", { src: selectedItem.imageUrl, alt: selectedItem.name, className: "w-40 h-40 object-cover rounded mb-4 shadow-md" }), _jsx("p", { className: "mb-2 text-xl font-bold", children: selectedItem.name }), _jsxs("p", { className: "mb-2 text-base font-semibold text-gray-700", children: ["Stock disponible:", _jsx("span", { className: "ml-1 font-normal text-gray-800", children: selectedItem.stock })] }), !selectedItem.flexiblePrice && (_jsxs("p", { className: "mb-4 text-base font-semibold text-gray-700", children: ["Precio unitario:", _jsxs("span", { className: "ml-1 font-normal text-gray-800", children: [selectedItem.currencyPrefix, selectedItem.priceText] })] })), _jsxs("div", { className: "mb-4 w-full max-w-sm flex flex-col items-center", children: [_jsx("p", { className: "text-base font-semibold text-gray-700 mb-2", children: "Seleccionar cantidad" }), _jsxs("div", { className: "flex items-center space-x-4", children: [_jsx("button", { className: "bg-gray-300 rounded-full w-12 h-12 flex justify-center items-center text-2xl font-bold hover:bg-gray-400", onClick: () => setQuantity((prev) => (prev > 1 ? prev - 1 : 1)), children: "-" }), _jsx("input", { className: "w-16 text-center border rounded text-lg", type: "number", min: 1, value: quantity, onChange: (e) => {
100
+ const val = parseInt(e.target.value, 10);
101
+ setQuantity(isNaN(val) || val < 1 ? 1 : val);
102
+ } }), _jsx("button", { className: "bg-gray-300 rounded-full w-12 h-12 flex justify-center items-center text-2xl font-bold hover:bg-gray-400", onClick: () => setQuantity((prev) => prev + 1), children: "+" })] })] }), selectedItem.flexiblePrice && (_jsxs("div", { className: "mb-4 w-full max-w-sm flex flex-col items-center", children: [_jsx("p", { className: "text-base font-semibold text-gray-700 mb-2", children: "Seleccionar precio" }), _jsxs("div", { className: "flex items-center space-x-4", children: [_jsx("button", { className: "bg-gray-300 rounded-full w-12 h-12 flex justify-center items-center text-2xl font-bold hover:bg-gray-400", onClick: () => setCustomPrice((prev) => {
103
+ const newVal = (prev ?? 0) - 1;
104
+ return newVal < 0 ? 0 : newVal;
105
+ }), children: "-" }), _jsx("input", { className: "w-16 text-center border rounded text-lg", type: "number", min: 0, value: customPrice ?? 0, onChange: (e) => {
106
+ const val = parseInt(e.target.value, 10);
107
+ setCustomPrice(isNaN(val) || val < 0 ? 0 : val);
108
+ } }), _jsx("button", { className: "bg-gray-300 rounded-full w-12 h-12 flex justify-center items-center text-2xl font-bold hover:bg-gray-400", onClick: () => setCustomPrice((prev) => (prev == null ? 1 : prev + 1)), children: "+" })] })] })), _jsxs("div", { className: "flex space-x-4 mt-6", children: [_jsx("button", { className: "bg-gray-300 px-4 py-2 rounded hover:bg-gray-400", onClick: handleCancelQuantity, children: "Volver" }), _jsx("button", { className: "bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700", onClick: handleConfirmQuantity, children: "Confirmar" })] })] }))] }) }));
38
109
  };
39
110
  // Función para crear el modal
40
- export const createItemModal = (itemList, config) => {
111
+ export const createItemModal = (itemList, config, categoryViewMode) => {
41
112
  return new Promise((resolve) => {
42
113
  const handleClose = (selectedItem) => {
43
114
  resolve(selectedItem);
@@ -49,6 +120,6 @@ export const createItemModal = (itemList, config) => {
49
120
  const modalWrapper = document.createElement("div");
50
121
  document.body.appendChild(modalWrapper);
51
122
  const root = createRoot(modalWrapper);
52
- root.render(_jsx(ItemSelectionModal, { itemList: itemList, onClose: handleClose, config: config }));
123
+ root.render(_jsx(ItemSelectionModal, { itemList: itemList, onClose: handleClose, config: config, categoryViewMode: categoryViewMode }));
53
124
  });
54
125
  };
@@ -2,13 +2,15 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  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
- import { Link, useNavigate } from "@remix-run/react";
5
+ import { Link, useNavigate, useLocation } from "@remix-run/react";
6
6
  import { useAppSelector } from "@zauru-sdk/redux";
7
7
  const OptionsDropDownButton = ({ color, options, name }) => {
8
8
  const [showOptionsMenu, setShowOptionsMenu] = useState(true);
9
9
  return (_jsx("div", { className: "nav-item ml-auto", children: _jsx("div", { className: "flex justify-center", children: _jsxs("div", { className: "relative inline-block", 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))) })] }) }) }));
10
10
  };
11
11
  const NavItem = ({ name, link, icon, selectedColor, childrens = [], reduxNotificationBadge, }) => {
12
+ const location = useLocation();
13
+ const isActive = location.pathname === link;
12
14
  const specialColor = selectedColor
13
15
  ? COLORS[selectedColor]
14
16
  : COLORS["slate"];
@@ -18,7 +20,15 @@ const NavItem = ({ name, link, icon, selectedColor, childrens = [], reduxNotific
18
20
  useEffect(() => {
19
21
  setNotificationBadge(relevantState);
20
22
  }, [relevantState]);
21
- return (_jsx("li", { className: "nav-item relative", children: childrens.length > 0 ? (_jsx(OptionsDropDownButton, { name: name, color: 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.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(Link, { className: "px-3 flex items-center text-xs leading-snug text-white uppercase hover:opacity-75 relative", to: link, children: [_jsxs("div", { className: "mx-auto pt-2", children: [icon, _jsx("span", { children: name })] }), notificationBadge !== undefined && (_jsx("span", { className: "absolute -top-2 -right-2 bg-red-500 text-white text-xs font-bold rounded-full flex items-center justify-center w-5 h-5", children: notificationBadge }))] }) })) }));
23
+ // Si este NavItem tiene elementos hijos, renderiza el botón desplegable:
24
+ if (childrens.length > 0) {
25
+ return (_jsx(OptionsDropDownButton, { name: name, color: 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))) }));
26
+ }
27
+ // Si NO tiene elementos hijos, se renderiza como un ítem simple:
28
+ return (_jsx("li", { className: "nav-item relative", children: _jsx("div", {
29
+ // Si está activo, usamos color de fondo más oscuro (bg900)
30
+ // De lo contrario, usamos el color normal (bg700)
31
+ className: `${isActive ? specialColor.bg900 : specialColor.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(Link, { className: "px-3 flex items-center text-xs leading-snug text-white uppercase hover:opacity-75 relative", to: link, children: [_jsxs("div", { className: "mx-auto pt-2", children: [icon, _jsx("span", { children: name })] }), notificationBadge !== undefined && (_jsx("span", { className: "absolute -top-2 -right-2 bg-red-500 text-white text-xs font-bold rounded-full flex items-center justify-center w-5 h-5", children: notificationBadge }))] }) }) }));
22
32
  };
23
33
  export const NavBar = ({ title, loggedIn, items, selectedColor, version, }) => {
24
34
  const color = COLORS[selectedColor];
@@ -39,7 +49,7 @@ export const NavBar = ({ title, loggedIn, items, selectedColor, version, }) => {
39
49
  }) }, index));
40
50
  }) }));
41
51
  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: [
42
- _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", children: _jsxs("div", { className: "mx-auto pt-2", children: [_jsx(LogoutDropDownSvgIcon, {}), _jsx("span", { children: "Cerrar sesi\u00F3n" })] }) }),
52
+ _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", children: _jsxs("div", { className: "mx-auto pt-2", children: [_jsx(LogoutDropDownSvgIcon, {}), _jsx("span", { children: "Cerrar sesi\u00F3n" })] }) }, "cerrar-sesion"),
43
53
  ] })) })] }));
44
- 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", 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 })] }) }));
54
+ 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: [_jsxs(Link, { className: "text-sm font-bold leading-relaxed inline-block mr-4 py-2 whitespace-nowrap uppercase text-white", to: "/home", 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 })] }) }));
45
55
  };
@@ -4,6 +4,7 @@ export const COLORS = {
4
4
  bg700: "bg-sky-700",
5
5
  bg600: "bg-sky-600",
6
6
  bg500: "bg-sky-500",
7
+ bg200: "bg-sky-200",
7
8
  ring600: "ring-sky-600",
8
9
  ring500: "ring-sky-500",
9
10
  },
@@ -12,6 +13,7 @@ export const COLORS = {
12
13
  bg700: "bg-purple-700",
13
14
  bg600: "bg-purple-600",
14
15
  bg500: "bg-purple-500",
16
+ bg200: "bg-purple-200",
15
17
  ring600: "ring-purple-600",
16
18
  ring500: "ring-purple-500",
17
19
  },
@@ -20,6 +22,7 @@ export const COLORS = {
20
22
  bg700: "bg-pink-700",
21
23
  bg600: "bg-pink-600",
22
24
  bg500: "bg-pink-500",
25
+ bg200: "bg-pink-200",
23
26
  ring600: "ring-pink-600",
24
27
  ring500: "ring-pink-500",
25
28
  },
@@ -28,6 +31,7 @@ export const COLORS = {
28
31
  bg700: "bg-slate-700",
29
32
  bg600: "bg-slate-600",
30
33
  bg500: "bg-slate-500",
34
+ bg200: "bg-slate-200",
31
35
  ring600: "ring-slate-600",
32
36
  ring500: "ring-slate-500",
33
37
  },
@@ -36,6 +40,7 @@ export const COLORS = {
36
40
  bg700: "bg-green-700",
37
41
  bg600: "bg-green-600",
38
42
  bg500: "bg-green-500",
43
+ bg200: "bg-green-200",
39
44
  ring600: "ring-green-600",
40
45
  ring500: "ring-green-500",
41
46
  },
@@ -44,6 +49,7 @@ export const COLORS = {
44
49
  bg700: "bg-yellow-700",
45
50
  bg600: "bg-yellow-600",
46
51
  bg500: "bg-yellow-500",
52
+ bg200: "bg-yellow-200",
47
53
  ring600: "ring-yellow-600",
48
54
  ring500: "ring-yellow-500",
49
55
  },
@@ -52,6 +58,7 @@ export const COLORS = {
52
58
  bg700: "bg-red-700",
53
59
  bg600: "bg-red-600",
54
60
  bg500: "bg-red-500",
61
+ bg200: "bg-red-200",
55
62
  ring600: "ring-red-600",
56
63
  ring500: "ring-red-500",
57
64
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zauru-sdk/components",
3
- "version": "2.0.119",
3
+ "version": "2.0.120",
4
4
  "description": "Componentes reutilizables en las WebApps de Zauru.",
5
5
  "main": "./dist/esm/index.js",
6
6
  "module": "./dist/esm/index.js",
@@ -34,7 +34,7 @@
34
34
  "@reduxjs/toolkit": "^2.2.1",
35
35
  "@remix-run/react": "^2.8.1",
36
36
  "@zauru-sdk/common": "^2.0.118",
37
- "@zauru-sdk/hooks": "^2.0.118",
37
+ "@zauru-sdk/hooks": "^2.0.120",
38
38
  "@zauru-sdk/icons": "^2.0.99",
39
39
  "@zauru-sdk/types": "^2.0.109",
40
40
  "@zauru-sdk/utils": "^2.0.119",
@@ -49,5 +49,5 @@
49
49
  "styled-components": "^5.3.5",
50
50
  "zod": "^3.23.8"
51
51
  },
52
- "gitHead": "56d56910cd5f8e311239d379369fd608dd697aea"
52
+ "gitHead": "05212e4cb104943ac0c6050ada75859622537c6a"
53
53
  }
@@ -43,6 +43,7 @@ export const Button = (props: Props) => {
43
43
  bg700: "bg-green-700",
44
44
  bg600: "bg-green-600",
45
45
  bg500: "bg-green-500",
46
+ bg200: "bg-green-200",
46
47
  ring600: "ring-green-600",
47
48
  ring500: "ring-green-500",
48
49
  },
@@ -51,6 +52,7 @@ export const Button = (props: Props) => {
51
52
  bg700: "bg-indigo-700",
52
53
  bg600: "bg-indigo-600",
53
54
  bg500: "bg-indigo-500",
55
+ bg200: "bg-indigo-200",
54
56
  ring600: "ring-indigo-600",
55
57
  ring500: "ring-indigo-500",
56
58
  },
@@ -59,6 +61,7 @@ export const Button = (props: Props) => {
59
61
  bg700: "bg-red-700",
60
62
  bg600: "bg-red-600",
61
63
  bg500: "bg-red-500",
64
+ bg200: "bg-red-200",
62
65
  ring600: "ring-red-600",
63
66
  ring500: "ring-red-500",
64
67
  },
@@ -67,6 +70,7 @@ export const Button = (props: Props) => {
67
70
  bg700: "bg-yellow-700",
68
71
  bg600: "bg-yellow-600",
69
72
  bg500: "bg-yellow-500",
73
+ bg200: "bg-yellow-200",
70
74
  ring600: "ring-yellow-600",
71
75
  ring500: "ring-yellow-500",
72
76
  },
@@ -84,9 +88,10 @@ export const Button = (props: Props) => {
84
88
 
85
89
  const buttonContent = (
86
90
  <>
87
- <input type="hidden" name="action" value={name} />
88
91
  <button
89
92
  type={type}
93
+ name={"action"}
94
+ value={name}
90
95
  disabled={
91
96
  loading || disabled || (enableFormErrorsValidation && formHasErrors)
92
97
  }