@zauru-sdk/components 2.0.202 → 2.0.204

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.
Files changed (33) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/Containers/ButtonSectionContainer.d.ts +0 -1
  3. package/dist/DynamicTable/DynamicPrintTable.d.ts +0 -1
  4. package/dist/Form/DynamicBaculoForm/index.d.ts +2 -0
  5. package/dist/Form/FileUpload/index.d.ts +2 -1
  6. package/dist/Form/TimePicker/index.d.ts +0 -1
  7. package/dist/Form/YesNo/index.d.ts +0 -1
  8. package/dist/Modal/ItemModal.d.ts +1 -1
  9. package/dist/esm/BlockUI/BlockUI.js +1 -1
  10. package/dist/esm/Buttons/Button.js +7 -8
  11. package/dist/esm/Containers/ButtonSectionContainer.js +2 -2
  12. package/dist/esm/Containers/OutletContainer.js +2 -2
  13. package/dist/esm/DynamicTable/DynamicPrintTable.js +13 -14
  14. package/dist/esm/DynamicTable/GenericDynamicTable.js +1 -1
  15. package/dist/esm/Form/DynamicBaculoForm/index.js +21 -57
  16. package/dist/esm/Form/FileUpload/index.js +197 -103
  17. package/dist/esm/Layouts/errorLayout/index.js +3 -5
  18. package/dist/esm/Modal/ItemModal.js +21 -10
  19. package/dist/esm/Skeletons/LoadingWindow.js +8 -0
  20. package/package.json +6 -6
  21. package/src/BlockUI/BlockUI.tsx +2 -4
  22. package/src/Buttons/Button.tsx +28 -32
  23. package/src/Containers/ButtonSectionContainer.tsx +2 -5
  24. package/src/Containers/OutletContainer.tsx +3 -3
  25. package/src/DynamicTable/DynamicPrintTable.tsx +1 -4
  26. package/src/DynamicTable/GenericDynamicTable.tsx +1 -1
  27. package/src/Form/DynamicBaculoForm/index.tsx +55 -66
  28. package/src/Form/FileUpload/index.tsx +365 -150
  29. package/src/Form/TimePicker/index.tsx +0 -1
  30. package/src/Form/YesNo/index.tsx +0 -1
  31. package/src/Layouts/errorLayout/index.tsx +4 -11
  32. package/src/Modal/ItemModal.tsx +44 -13
  33. package/src/Skeletons/LoadingWindow.tsx +11 -1
@@ -1,18 +1,85 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { DownloadIconSVG, IdeaIconSVG } from "@zauru-sdk/icons";
3
3
  import { useState, useEffect, useRef } from "react";
4
4
  import { useFormContext } from "react-hook-form";
5
5
  export const FileUploadField = (props) => {
6
- const { id, name, title, helpText, hint, onChange, readOnly = false, fileTypes = [], showAvailableTypes = false, className, defaultValue, download = false, required = false, optimizeImages = true, zauruBaseURL = "https://zauru.herokuapp.com", } = props;
6
+ const { id, name, title, helpText, hint, onChange, readOnly = false, fileTypes = [], showAvailableTypes = false, className, defaultValue, download = false, required = false, optimizeImages = true, zauruBaseURL = "https://zauru.herokuapp.com", setProcessing, signature = false, } = props;
7
7
  const { register: tempRegister, setValue, formState: { errors }, } = useFormContext() || { formState: {} };
8
8
  const error = errors ? errors[name] : undefined;
9
9
  const register = tempRegister ? tempRegister(name, { required }) : undefined;
10
10
  const [showTooltip, setShowTooltip] = useState(false);
11
11
  const [previewSrc, setPreviewSrc] = useState(null);
12
- const [fileDeleted, setFileDeleted] = useState(false);
13
12
  const [uploading, setUploading] = useState(false);
14
13
  const [uploadProgress, setUploadProgress] = useState(0);
14
+ const [openSignatureModal, setOpenSignatureModal] = useState(false);
15
+ // Estados para la optimización de imagen
16
+ const [optimizing, setOptimizing] = useState(false);
17
+ const [optimizationProgress, setOptimizationProgress] = useState(0);
15
18
  const fileInputRef = useRef(null);
19
+ // For signature drawing mode
20
+ const canvasRef = useRef(null);
21
+ const isDrawing = useRef(false);
22
+ const lastX = useRef(0);
23
+ const lastY = useRef(0);
24
+ const handlePointerDown = (e) => {
25
+ e.preventDefault();
26
+ const canvas = canvasRef.current;
27
+ if (!canvas)
28
+ return;
29
+ const rect = canvas.getBoundingClientRect();
30
+ lastX.current = e.clientX - rect.left;
31
+ lastY.current = e.clientY - rect.top;
32
+ isDrawing.current = true;
33
+ };
34
+ const handlePointerMove = (e) => {
35
+ e.preventDefault();
36
+ if (!isDrawing.current)
37
+ return;
38
+ const canvas = canvasRef.current;
39
+ if (!canvas)
40
+ return;
41
+ const ctx = canvas.getContext("2d");
42
+ if (!ctx)
43
+ return;
44
+ const rect = canvas.getBoundingClientRect();
45
+ const currentX = e.clientX - rect.left;
46
+ const currentY = e.clientY - rect.top;
47
+ ctx.beginPath();
48
+ ctx.moveTo(lastX.current, lastY.current);
49
+ ctx.lineTo(currentX, currentY);
50
+ ctx.strokeStyle = "black";
51
+ ctx.lineWidth = 2;
52
+ ctx.stroke();
53
+ lastX.current = currentX;
54
+ lastY.current = currentY;
55
+ };
56
+ const handlePointerUp = (e) => {
57
+ e?.preventDefault();
58
+ isDrawing.current = false;
59
+ };
60
+ const clearCanvas = () => {
61
+ const canvas = canvasRef.current;
62
+ if (!canvas)
63
+ return;
64
+ const ctx = canvas.getContext("2d");
65
+ if (!ctx)
66
+ return;
67
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
68
+ };
69
+ const handleSignatureConfirm = () => {
70
+ const canvas = canvasRef.current;
71
+ if (!canvas)
72
+ return;
73
+ canvas.toBlob(async (blob) => {
74
+ if (blob) {
75
+ const file = new File([blob], "signature.png", { type: "image/png" });
76
+ const objectUrl = URL.createObjectURL(file);
77
+ setPreviewSrc(objectUrl);
78
+ await processFile(file);
79
+ setOpenSignatureModal(false);
80
+ }
81
+ }, "image/png");
82
+ };
16
83
  useEffect(() => {
17
84
  return () => {
18
85
  if (previewSrc) {
@@ -29,6 +96,102 @@ export const FileUploadField = (props) => {
29
96
  const bgColor = isReadOnly ? "bg-gray-100" : `bg-${color}-50`;
30
97
  const textColor = isReadOnly ? "text-gray-700" : `text-${color}-900`;
31
98
  const borderColor = error ? "border-red-500" : `border-${color}-500`;
99
+ const processFile = async (file, event) => {
100
+ let processedFile = file;
101
+ /*
102
+ if (file && file.type.startsWith("image/") && optimizeImages) {
103
+ try {
104
+ setOptimizing(true);
105
+ setOptimizationProgress(0);
106
+ const options: Options = {
107
+ fileType: "image/webp",
108
+ initialQuality: 0.7,
109
+ maxSizeMB: 1,
110
+ maxWidthOrHeight: 1920,
111
+ useWebWorker: true,
112
+ onProgress: (progress: number) => {
113
+ setOptimizationProgress(progress);
114
+ },
115
+ };
116
+ const compressedFile = await imageCompression(file, options);
117
+ setOptimizing(false);
118
+ processedFile = new File(
119
+ [compressedFile],
120
+ file.name.replace(/\.[^.]+$/, ".webp"),
121
+ { type: "image/webp" }
122
+ );
123
+ const objectUrl = URL.createObjectURL(processedFile);
124
+ setPreviewSrc(objectUrl);
125
+ } catch (error) {
126
+ console.error("Error al convertir la imagen a WebP:", error);
127
+ setOptimizing(false);
128
+ const objectUrl = URL.createObjectURL(file);
129
+ setPreviewSrc(objectUrl);
130
+ }
131
+ } else
132
+ */
133
+ if (file && file.type.startsWith("image/")) {
134
+ const objectUrl = URL.createObjectURL(file);
135
+ setPreviewSrc(objectUrl);
136
+ }
137
+ else {
138
+ setPreviewSrc(null);
139
+ }
140
+ if (event && event.target) {
141
+ const dataTransfer = new DataTransfer();
142
+ dataTransfer.items.add(processedFile);
143
+ event.target.files = dataTransfer.files;
144
+ }
145
+ // Proceso de subida mediante DirectUpload
146
+ import("@rails/activestorage")
147
+ .then(({ DirectUpload }) => {
148
+ const uploadUrl = `${zauruBaseURL}/api/direct_uploads`;
149
+ setProcessing && setProcessing(true);
150
+ setUploading(true);
151
+ setUploadProgress(0);
152
+ const directUpload = new DirectUpload(processedFile, uploadUrl, {
153
+ directUploadWillStoreFileWithXHR: (xhr) => {
154
+ xhr.upload.addEventListener("progress", (event) => {
155
+ if (event.lengthComputable) {
156
+ const progress = Math.round((event.loaded / event.total) * 100);
157
+ setUploadProgress(progress);
158
+ }
159
+ });
160
+ },
161
+ });
162
+ directUpload.create((error, blob) => {
163
+ setUploading(false);
164
+ setProcessing && setProcessing(false);
165
+ if (error) {
166
+ console.error("Error al subir el archivo:", error);
167
+ }
168
+ else {
169
+ setValue(name, blob.signed_id);
170
+ setValue(`${name}_file_type`, blob.content_type);
171
+ const hiddenField = document.createElement("input");
172
+ hiddenField.setAttribute("type", "hidden");
173
+ hiddenField.setAttribute("value", blob.signed_id);
174
+ hiddenField.setAttribute("name", name);
175
+ const typeHiddenField = document.createElement("input");
176
+ typeHiddenField.setAttribute("type", "hidden");
177
+ typeHiddenField.setAttribute("value", blob.content_type);
178
+ typeHiddenField.setAttribute("name", `${name}_file_type`);
179
+ const formElement = document.querySelector("form");
180
+ if (formElement) {
181
+ formElement.appendChild(hiddenField);
182
+ formElement.appendChild(typeHiddenField);
183
+ }
184
+ if (fileInputRef.current) {
185
+ fileInputRef.current.removeAttribute("name");
186
+ }
187
+ }
188
+ });
189
+ })
190
+ .catch((err) => {
191
+ setProcessing && setProcessing(false);
192
+ console.error("Error al cargar DirectUpload:", err);
193
+ });
194
+ };
32
195
  /**
33
196
  * Función que se dispara cuando el usuario selecciona un archivo.
34
197
  * Si optimizeImages está activo y se trata de una imagen, la convierte a WebP
@@ -36,100 +199,9 @@ export const FileUploadField = (props) => {
36
199
  */
37
200
  const handleInputChange = async (event) => {
38
201
  if (event.target.files && event.target.files.length > 0) {
39
- let file = event.target.files[0];
40
- // Si se activa la optimización y es imagen, se convierte a WebP
41
- if (file && file.type.startsWith("image/") && optimizeImages) {
42
- try {
43
- /*
44
- const options: Options = {
45
- fileType: "image/webp", // Especificamos el formato WebP
46
- initialQuality: 0.7, // Calidad inicial (puedes ajustar este valor)
47
- maxSizeMB: 1, // Tamaño máximo (opcional)
48
- maxWidthOrHeight: 1920, // Dimensión máxima (opcional)
49
- useWebWorker: true,
50
- };
51
- // Después de la compresión, renombramos el archivo:
52
- const compressedFile = await imageCompression(file, options);
53
- const newFile = new File(
54
- [compressedFile],
55
- file.name.replace(/\.[^.]+$/, ".webp"),
56
- {
57
- type: file.type,
58
- }
59
- );
60
- file = newFile;
61
- */
62
- console.log("Archivo convertido:", file.name, file.type);
63
- const objectUrl = URL.createObjectURL(file);
64
- setPreviewSrc(objectUrl);
65
- }
66
- catch (error) {
67
- console.error("Error al convertir la imagen a WebP:", error);
68
- const objectUrl = URL.createObjectURL(file);
69
- setPreviewSrc(objectUrl);
70
- }
71
- }
72
- else if (file && file.type.startsWith("image/")) {
73
- const objectUrl = URL.createObjectURL(file);
74
- setPreviewSrc(objectUrl);
75
- }
76
- else {
77
- setPreviewSrc(null);
78
- }
79
- // Actualizamos el input para que contenga el archivo convertido
80
- const dataTransfer = new DataTransfer();
81
- dataTransfer.items.add(file);
82
- event.target.files = dataTransfer.files;
83
- // Importamos dinámicamente DirectUpload solo en el cliente
84
- import("@rails/activestorage")
85
- .then(({ DirectUpload }) => {
86
- const uploadUrl = `${zauruBaseURL}/api/direct_uploads`;
87
- setUploading(true);
88
- setUploadProgress(0);
89
- const directUpload = new DirectUpload(file, uploadUrl, {
90
- directUploadWillStoreFileWithXHR: (xhr) => {
91
- xhr.upload.addEventListener("progress", (event) => {
92
- if (event.lengthComputable) {
93
- const progress = Math.round((event.loaded / event.total) * 100);
94
- setUploadProgress(progress);
95
- }
96
- });
97
- },
98
- });
99
- directUpload.create((error, blob) => {
100
- setUploading(false);
101
- if (error) {
102
- console.error("Error al subir el archivo:", error);
103
- }
104
- else {
105
- setValue(name, blob.signed_id);
106
- setValue(`${name}_file_type`, blob.content_type);
107
- // Input hidden para el signed_id
108
- const hiddenField = document.createElement("input");
109
- hiddenField.setAttribute("type", "hidden");
110
- hiddenField.setAttribute("value", blob.signed_id);
111
- hiddenField.setAttribute("name", name);
112
- // Input hidden para el content_type
113
- const typeHiddenField = document.createElement("input");
114
- typeHiddenField.setAttribute("type", "hidden");
115
- typeHiddenField.setAttribute("value", blob.content_type);
116
- typeHiddenField.setAttribute("name", `${name}_file_type`);
117
- const formElement = document.querySelector("form");
118
- if (formElement) {
119
- formElement.appendChild(hiddenField);
120
- formElement.appendChild(typeHiddenField);
121
- }
122
- // Removemos el atributo "name" del input file para evitar enviar el archivo
123
- if (fileInputRef.current) {
124
- fileInputRef.current.removeAttribute("name");
125
- }
126
- }
127
- });
128
- })
129
- .catch((err) => console.error("Error al cargar DirectUpload:", err));
202
+ await processFile(event.target.files[0], event);
130
203
  }
131
204
  onChange && onChange(event);
132
- setFileDeleted(false);
133
205
  };
134
206
  /**
135
207
  * Función para eliminar el archivo. Además de limpiar la vista previa,
@@ -137,7 +209,6 @@ export const FileUploadField = (props) => {
137
209
  */
138
210
  const deleteFile = () => {
139
211
  setPreviewSrc(null);
140
- setFileDeleted(true);
141
212
  if (fileInputRef.current) {
142
213
  fileInputRef.current.value = "";
143
214
  }
@@ -167,16 +238,39 @@ export const FileUploadField = (props) => {
167
238
  * - Si defaultValue es string, se muestra el preview (descarga/imagen).
168
239
  * - Si no hay defaultValue (o es File), se muestra "Sin archivo".
169
240
  */
170
- if (readOnly) {
171
- 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" }))] }));
241
+ if (signature) {
242
+ if (readOnly) {
243
+ 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 firma disponible" }))] }));
244
+ }
245
+ else {
246
+ 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: "*" })] })), previewSrc ? (_jsxs("div", { children: [renderPreview(previewSrc), _jsx("button", { type: "button", onClick: () => {
247
+ setPreviewSrc(null);
248
+ }, className: "ml-2 text-red-600 underline text-sm", children: "Eliminar firma" })] })) : (_jsx("div", { children: _jsx("button", { type: "button", onClick: () => setOpenSignatureModal(true), className: "px-4 py-2 bg-blue-600 text-white rounded", children: "Firmar" }) })), openSignatureModal && (_jsxs("div", { className: "fixed inset-0 z-50 flex flex-col bg-white", children: [_jsx("div", { className: "flex-grow flex items-center justify-center", children: _jsx("canvas", { ref: canvasRef, width: 400, height: 200, style: {
249
+ border: "1px solid #ccc",
250
+ backgroundColor: "transparent",
251
+ touchAction: "none",
252
+ }, onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp, onPointerLeave: handlePointerUp }) }), _jsxs("div", { className: "p-4 flex justify-center space-x-4", children: [_jsx("button", { type: "button", onClick: clearCanvas, className: "text-blue-600 underline text-sm", children: "Reset" }), _jsx("button", { type: "button", onClick: handleSignatureConfirm, className: "text-blue-600 underline text-sm", children: "Ok" }), _jsx("button", { type: "button", onClick: () => setOpenSignatureModal(false), className: "text-gray-600 underline text-sm", children: "Cancelar" })] })] }))] }));
253
+ }
172
254
  }
173
255
  /**
174
256
  * 2) Modo editable (readOnly = false):
257
+ * - Se muestra siempre el recuadro de vista previa, ya sea con la imagen cargada o con un placeholder.
175
258
  * - Si se ha seleccionado una imagen o existe defaultValue y no se ha eliminado,
176
- * se muestra la vista previa generada junto con un botón para eliminar el archivo.
177
- * - Si se elimina el archivo o no hay ninguno, se muestra el input para cargar uno.
259
+ * se muestra la imagen y un botón para eliminar el archivo.
178
260
  */
179
- 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: "*" })] })), !fileDeleted &&
180
- (previewSrc ? (_jsxs("div", { className: "mb-2 flex items-center", children: [renderPreview(previewSrc), _jsx("button", { type: "button", onClick: deleteFile, className: "ml-2 text-red-600 underline text-sm", children: "Eliminar archivo" })] })) : (typeof defaultValue === "string" &&
181
- defaultValue && (_jsxs("div", { className: "mb-2 flex items-center", children: [renderPreview(defaultValue), _jsx("button", { type: "button", onClick: deleteFile, className: "ml-2 text-red-600 underline text-sm", children: "Eliminar archivo" })] })))), _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 ?? {}), ref: fileInputRef, 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 }))] }) }))] }), uploading && (_jsxs("div", { className: "mt-2", children: [_jsx("progress", { value: uploadProgress, max: "100", className: "w-full" }), _jsxs("p", { className: "text-sm text-blue-600 mt-1", children: ["Subiendo archivo: ", uploadProgress, "%"] })] })), error && (_jsxs("p", { className: "mt-2 text-sm text-red-600", children: [_jsx("span", { className: "font-medium", children: "\u00A1Oops!" }), " ", error.message?.toString()] })), !error && hintMessage && (_jsx("p", { className: `mt-2 italic text-sm text-${color}-500`, children: hintMessage }))] }));
261
+ if (readOnly) {
262
+ 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" }))] }));
263
+ }
264
+ 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: "*" })] })), _jsxs("div", { className: "mb-2 flex items-center", children: [_jsx("div", { role: "button", tabIndex: 0, onClick: () => {
265
+ const srcToOpen = previewSrc || (typeof defaultValue === "string" && defaultValue);
266
+ if (srcToOpen)
267
+ window.open(srcToOpen, "_blank");
268
+ }, onKeyDown: (event) => {
269
+ const srcToOpen = previewSrc || (typeof defaultValue === "string" && defaultValue);
270
+ if (event.key === "Enter" && srcToOpen)
271
+ window.open(srcToOpen, "_blank");
272
+ }, className: "cursor-pointer border border-gray-300 h-48 w-48 flex items-center justify-center", children: previewSrc || (typeof defaultValue === "string" && defaultValue) ? (_jsx("img", { src: previewSrc ??
273
+ (typeof defaultValue === "string" && defaultValue
274
+ ? defaultValue
275
+ : undefined), alt: name, className: "h-48 w-48", style: { objectFit: "contain" } })) : (_jsx("span", { className: "text-gray-400", children: "No image" })) }), (previewSrc || (typeof defaultValue === "string" && defaultValue)) && (_jsx("button", { type: "button", onClick: deleteFile, className: "ml-2 text-red-600 underline text-sm", children: "Eliminar archivo" }))] }), _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 ?? {}), ref: fileInputRef, 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 }))] }) }))] }), _jsx("div", { className: "mt-2 min-h-[60px] flex items-center justify-center border border-dashed border-gray-200 rounded", children: optimizing || uploading ? (_jsxs(_Fragment, { children: [optimizing && (_jsxs("div", { className: "w-full", children: [_jsx("progress", { value: optimizationProgress, max: "100", className: "w-full" }), _jsxs("p", { className: "text-sm text-blue-600 mt-1 text-center", children: ["Optimizando imagen a webp: ", optimizationProgress, "%"] })] })), uploading && (_jsxs("div", { className: "w-full", children: [_jsx("progress", { value: uploadProgress, max: "100", className: "w-full" }), _jsxs("p", { className: "text-sm text-blue-600 mt-1 text-center", children: ["Subiendo archivo: ", uploadProgress, "%"] })] }))] })) : (_jsx("span", { className: "text-sm text-gray-500", children: "Informaci\u00F3n de progreso" })) }), error && (_jsxs("p", { className: "mt-2 text-sm text-red-600", children: [_jsx("span", { className: "font-medium", children: "\u00A1Oops!" }), " ", error.message?.toString()] })), !error && hintMessage && (_jsx("p", { className: `mt-2 italic text-sm text-${color}-500`, children: hintMessage }))] }));
182
276
  };
@@ -6,12 +6,10 @@ export const ErrorLayout = ({ from, isRootLevel = true, error: parentError, }) =
6
6
  const error = useRouteError();
7
7
  const [showDetails, setShowDetails] = useState(!!parentError);
8
8
  const baseError = (_jsxs("div", { 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 en request: ${error.status}: ${error.statusText} - PATH: ${error.data.path} - MESSAGE: ${error.data.message}`
9
+ ? `Error ${error.status}: ${error.statusText}`
10
10
  : error instanceof Error
11
- ? `Error: ${error.message} - NAME: ${error.name} - STACK: ${error.stack?.slice(0, 100)} - CAUSE: ${error.cause}`
12
- : `Ha ocurrido un error inesperado: ${error}` }), from && (_jsxs("p", { className: "text-lg text-gray-400 mb-4 text-center", children: [isRootLevel
13
- ? "(*) Error lanzado desde: "
14
- : "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
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
15
13
  ? parentError.message
16
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." }) })] }));
17
15
  if (!isRootLevel) {
@@ -1,6 +1,17 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useState, useRef } from "react";
3
3
  import { createRoot } from "react-dom/client";
4
+ const ExpandableText = ({ text, threshold = 30, }) => {
5
+ const [expanded, setExpanded] = useState(false);
6
+ const toggleExpanded = (e) => {
7
+ e.stopPropagation();
8
+ setExpanded(!expanded);
9
+ };
10
+ if (text.length <= threshold) {
11
+ return _jsx("span", { children: text });
12
+ }
13
+ return (_jsx("span", { onClick: toggleExpanded, style: { cursor: "pointer" }, children: expanded ? (_jsxs("span", { className: "text-base font-semibold", children: [text, " ", _jsx("span", { className: "ml-1", children: "\u25B2" })] })) : (_jsxs("span", { className: "text-sm font-semibold", children: [text.substring(0, threshold), "...", " ", _jsx("span", { className: "ml-1", children: "\u25BC" })] })) }));
14
+ };
4
15
  const ItemSelectionModal = ({ itemList, onClose, config, categoryViewMode = "text", }) => {
5
16
  const defaultConfig = {
6
17
  itemSize: {
@@ -32,7 +43,7 @@ const ItemSelectionModal = ({ itemList, onClose, config, categoryViewMode = "tex
32
43
  setSelectedItem(item);
33
44
  setQuantity(1);
34
45
  // Si el ítem tiene precio flexible, inicializamos el customPrice con su precio actual
35
- setCustomPrice(item.flexiblePrice ? item.unitPrice : null);
46
+ setCustomPrice(item.flexiblePrice ? item.unit_price : null);
36
47
  // Forzar scroll a la parte superior del contenido, ya que antes se iniciaba el scroll desde donde se había quedado.
37
48
  if (modalContentRef.current) {
38
49
  modalContentRef.current.scrollTo({ top: 0, behavior: "smooth" });
@@ -43,13 +54,13 @@ const ItemSelectionModal = ({ itemList, onClose, config, categoryViewMode = "tex
43
54
  // (5) Devolver el precio correcto
44
55
  const finalPrice = selectedItem.flexiblePrice
45
56
  ? // si el precio es flexible, tomar lo que el usuario haya ingresado (o fallback al precio original)
46
- customPrice ?? selectedItem.unitPrice
57
+ customPrice ?? selectedItem.unit_price
47
58
  : // si NO es flexible, solo usamos el precio que ya tenía
48
- selectedItem.unitPrice;
59
+ selectedItem.unit_price;
49
60
  onClose({
50
61
  ...selectedItem,
51
62
  quantity,
52
- unitPrice: finalPrice,
63
+ unit_price: finalPrice,
53
64
  });
54
65
  }
55
66
  };
@@ -67,7 +78,7 @@ const ItemSelectionModal = ({ itemList, onClose, config, categoryViewMode = "tex
67
78
  /* ==============================
68
79
  PASO 1: SELECCIONAR ÍTEM
69
80
  ============================== */
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) => {
81
+ _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, index) => {
71
82
  const isExpanded = expandedCategories[category.name];
72
83
  // Estilos condicionales para "text" vs. "card"
73
84
  const categoryContainerClasses = categoryViewMode === "card"
@@ -86,23 +97,23 @@ const ItemSelectionModal = ({ itemList, onClose, config, categoryViewMode = "tex
86
97
  height: defaultConfig.itemSize.height,
87
98
  }, children: [_jsx("div", { className: `p-2 rounded absolute top-0 left-0 right-0 ${isPackage
88
99
  ? "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
100
+ : "bg-white bg-opacity-80"}`, children: _jsx(ExpandableText, { text: item.name, threshold: 30 }) }), _jsx("div", { className: `absolute bottom-0 left-0 p-2 rounded ${isPackage
90
101
  ? "bg-yellow-300"
91
102
  : "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
103
  ? "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));
104
+ : "bg-white bg-opacity-80"}`, children: _jsxs("span", { className: "text-xs font-normal", children: [item.currencyPrefix, item.priceText] }) })] }, item.item_id));
105
+ }) }))] }, index));
95
106
  }))] })) : (
96
107
  /* ==============================
97
108
  PASO 2: CONFIRMAR SELECCIÓN
98
109
  ============================== */
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) => {
110
+ _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-20 text-center border rounded text-lg", type: "number", min: 1, value: quantity, onChange: (e) => {
100
111
  const val = parseInt(e.target.value, 10);
101
112
  setQuantity(isNaN(val) || val < 1 ? 1 : val);
102
113
  } }), _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
114
  const newVal = (prev ?? 0) - 1;
104
115
  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) => {
116
+ }), children: "-" }), _jsx("input", { className: "w-24 text-center border rounded text-lg", type: "number", min: 0, value: customPrice ?? 0, onChange: (e) => {
106
117
  const val = parseInt(e.target.value, 10);
107
118
  setCustomPrice(isNaN(val) || val < 0 ? 0 : val);
108
119
  } }), _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" })] })] }))] }) }));
@@ -1,6 +1,14 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect } from "react";
2
3
  import { motion } from "framer-motion";
3
4
  export const LoadingWindow = ({ loadingItems = [], description = "Por favor, espere mientras se carga la página.", }) => {
5
+ const [hasMounted, setHasMounted] = useState(false);
6
+ useEffect(() => {
7
+ setHasMounted(true);
8
+ }, []);
9
+ if (!hasMounted) {
10
+ return null;
11
+ }
4
12
  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
13
  };
6
14
  export default LoadingWindow;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zauru-sdk/components",
3
- "version": "2.0.202",
3
+ "version": "2.0.204",
4
4
  "description": "Componentes reutilizables en las WebApps de Zauru.",
5
5
  "main": "./dist/esm/index.js",
6
6
  "module": "./dist/esm/index.js",
@@ -35,11 +35,11 @@
35
35
  "@rails/activestorage": "^8.0.200",
36
36
  "@reduxjs/toolkit": "^2.2.1",
37
37
  "@remix-run/react": "^2.8.1",
38
- "@zauru-sdk/common": "^2.0.198",
39
- "@zauru-sdk/hooks": "^2.0.198",
38
+ "@zauru-sdk/common": "^2.0.204",
39
+ "@zauru-sdk/hooks": "^2.0.204",
40
40
  "@zauru-sdk/icons": "^2.0.188",
41
- "@zauru-sdk/types": "^2.0.197",
42
- "@zauru-sdk/utils": "^2.0.202",
41
+ "@zauru-sdk/types": "^2.0.204",
42
+ "@zauru-sdk/utils": "^2.0.204",
43
43
  "browser-image-compression": "^2.0.2",
44
44
  "framer-motion": "^11.7.0",
45
45
  "jsonwebtoken": "^9.0.2",
@@ -52,5 +52,5 @@
52
52
  "styled-components": "^5.3.5",
53
53
  "zod": "^3.23.8"
54
54
  },
55
- "gitHead": "02024107088737551a72e04666cfd3d297547c95"
55
+ "gitHead": "1d4f8ef72286e9f7575109be154b09f53c3107f4"
56
56
  }
@@ -16,12 +16,11 @@ export const BlockUI = (props: Props) => {
16
16
  return (
17
17
  <div>
18
18
  <div className="relative">
19
- <div className="absolute bg-gray-100 bg-opacity-20 z-10 h-full w-full flex items-center justify-center">
19
+ <div className="absolute bg-gray-100 bg-opacity-80 z-10 h-full w-full flex items-center justify-center">
20
20
  <div className="flex items-center">
21
21
  <span className="text-3xl mr-4">{loadingText}</span>
22
- {/* <!-- loading icon --> */}
23
22
  <svg
24
- className="animate-spin h-5 w-5 text-gray-600"
23
+ className="animate-spin h-36 w-36 text-gray-600"
25
24
  xmlns="http://www.w3.org/2000/svg"
26
25
  fill="none"
27
26
  viewBox="0 0 24 24"
@@ -40,7 +39,6 @@ export const BlockUI = (props: Props) => {
40
39
  d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
41
40
  ></path>
42
41
  </svg>
43
- {/* <!-- end loading icon --> */}
44
42
  </div>
45
43
  </div>
46
44
  {children}
@@ -78,8 +78,6 @@ export const Button = (props: Props) => {
78
78
 
79
79
  const color: ColorInterface = COLORS[selectedColor];
80
80
 
81
- const inside = children ?? title;
82
-
83
81
  const errorMessage = formHasErrors
84
82
  ? Object.values(formErrors)
85
83
  .map((error) => error?.message?.toString())
@@ -87,36 +85,34 @@ export const Button = (props: Props) => {
87
85
  : "";
88
86
 
89
87
  const buttonContent = (
90
- <>
91
- <button
92
- type={type}
93
- name={"action"}
94
- value={name}
95
- disabled={
96
- loading || disabled || (enableFormErrorsValidation && formHasErrors)
97
- }
98
- onClick={onClickSave}
99
- className={`ml-2 ${
100
- loading || disabled || (enableFormErrorsValidation && formHasErrors)
101
- ? " bg-opacity-25 "
102
- : ""
103
- } ${
104
- loading
105
- ? " cursor-progress"
106
- : `${
107
- disabled || (enableFormErrorsValidation && formHasErrors)
108
- ? ""
109
- : `hover:${color.bg700}`
110
- }`
111
- } inline-flex justify-center rounded-md border border-transparent ${
112
- color.bg600
113
- } py-2 px-4 text-sm font-medium text-white shadow-sm focus:outline-none focus:ring-2 focus:${
114
- color.ring500
115
- } focus:ring-offset-2 ${className}`}
116
- >
117
- {loading ? loadingText : inside}
118
- </button>
119
- </>
88
+ <button
89
+ type={type}
90
+ name={"action"}
91
+ value={name}
92
+ disabled={
93
+ loading || disabled || (enableFormErrorsValidation && formHasErrors)
94
+ }
95
+ onClick={onClickSave}
96
+ className={`${
97
+ loading || disabled || (enableFormErrorsValidation && formHasErrors)
98
+ ? " bg-opacity-25 "
99
+ : ""
100
+ } ${
101
+ loading
102
+ ? " cursor-progress"
103
+ : `${
104
+ disabled || (enableFormErrorsValidation && formHasErrors)
105
+ ? ""
106
+ : `hover:${color.bg700}`
107
+ }`
108
+ } inline-flex justify-center rounded-md border border-transparent ${
109
+ color.bg600
110
+ } py-2 px-4 text-sm font-medium text-white shadow-sm focus:outline-none focus:ring-2 focus:${
111
+ color.ring500
112
+ } focus:ring-offset-2 ${className}`}
113
+ >
114
+ {loading ? children ?? loadingText : children ?? title}
115
+ </button>
120
116
  );
121
117
 
122
118
  return (