@zauru-sdk/components 2.0.118 → 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.
- package/CHANGELOG.md +16 -0
- package/dist/DynamicTable/GenericDynamicTable.d.ts +48 -4
- package/dist/Form/DynamicBaculoForm/index.d.ts +10 -1
- package/dist/Form/FileUpload/index.d.ts +0 -1
- package/dist/Modal/ItemModal.d.ts +16 -5
- package/dist/NavBar/NavBar.types.d.ts +2 -1
- package/dist/NavBar/NavBar.utils.d.ts +7 -0
- package/dist/esm/Buttons/Button.js +11 -7
- package/dist/esm/DynamicTable/GenericDynamicTable.js +78 -19
- package/dist/esm/Form/DynamicBaculoForm/index.js +29 -5
- package/dist/esm/Form/FileUpload/index.js +59 -52
- package/dist/esm/Form/ReactZodForm/index.js +11 -3
- package/dist/esm/Modal/ItemModal.js +93 -22
- package/dist/esm/NavBar/NavBar.js +14 -4
- package/dist/esm/NavBar/NavBar.utils.js +7 -0
- package/package.json +4 -4
- package/src/Buttons/Button.tsx +6 -1
- package/src/DynamicTable/GenericDynamicTable.tsx +140 -39
- package/src/Form/DynamicBaculoForm/index.tsx +32 -6
- package/src/Form/FileUpload/index.tsx +100 -77
- package/src/Form/ReactZodForm/index.tsx +16 -3
- package/src/Modal/ItemModal.tsx +351 -96
- package/src/NavBar/NavBar.tsx +53 -48
- package/src/NavBar/NavBar.types.ts +9 -1
- package/src/NavBar/NavBar.utils.ts +7 -0
package/src/Modal/ItemModal.tsx
CHANGED
|
@@ -1,28 +1,46 @@
|
|
|
1
|
-
import React, { useState } from "react";
|
|
1
|
+
import React, { useState, useRef } from "react";
|
|
2
2
|
import { createRoot } from "react-dom/client";
|
|
3
3
|
|
|
4
4
|
// Tipos de datos
|
|
5
5
|
type Item = {
|
|
6
|
+
item_id: string;
|
|
6
7
|
name: string;
|
|
7
8
|
code: string;
|
|
8
9
|
unitPrice: number;
|
|
9
10
|
stock: number;
|
|
10
11
|
currencyPrefix: string;
|
|
11
12
|
imageUrl: string;
|
|
13
|
+
quantity: number;
|
|
14
|
+
flexiblePrice: boolean;
|
|
15
|
+
priceText: string;
|
|
12
16
|
};
|
|
13
17
|
|
|
14
|
-
type
|
|
18
|
+
export type ItemModalCategory = { name: string; items: Item[] };
|
|
19
|
+
|
|
20
|
+
type OnCloseType = (selectedItem: Item | null) => void;
|
|
15
21
|
|
|
16
22
|
type ItemModalProps = {
|
|
17
|
-
itemList:
|
|
18
|
-
onClose:
|
|
19
|
-
config?: {
|
|
23
|
+
itemList: ItemModalCategory[];
|
|
24
|
+
onClose: OnCloseType;
|
|
25
|
+
config?: {
|
|
26
|
+
itemSize?: {
|
|
27
|
+
width: string;
|
|
28
|
+
height: string;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Controla cómo se muestran las categorías.
|
|
33
|
+
* - "text": Estilo por defecto (un simple título con flecha).
|
|
34
|
+
* - "card": Tarjeta más grande y ancha, útil para pantallas táctiles (tablet).
|
|
35
|
+
*/
|
|
36
|
+
categoryViewMode?: "text" | "card";
|
|
20
37
|
};
|
|
21
38
|
|
|
22
39
|
const ItemSelectionModal: React.FC<ItemModalProps> = ({
|
|
23
40
|
itemList,
|
|
24
41
|
onClose,
|
|
25
42
|
config,
|
|
43
|
+
categoryViewMode = "text",
|
|
26
44
|
}) => {
|
|
27
45
|
const defaultConfig = {
|
|
28
46
|
itemSize: {
|
|
@@ -35,17 +53,12 @@ const ItemSelectionModal: React.FC<ItemModalProps> = ({
|
|
|
35
53
|
const [expandedCategories, setExpandedCategories] = useState<{
|
|
36
54
|
[key: string]: boolean;
|
|
37
55
|
}>({});
|
|
56
|
+
const [selectedItem, setSelectedItem] = useState<Item | null>(null);
|
|
57
|
+
const [quantity, setQuantity] = useState<number>(1);
|
|
58
|
+
const [customPrice, setCustomPrice] = useState<number | null>(null);
|
|
38
59
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
...prev,
|
|
42
|
-
[categoryName]: !prev[categoryName],
|
|
43
|
-
}));
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const handleItemClick = (item: Item) => {
|
|
47
|
-
onClose(item);
|
|
48
|
-
};
|
|
60
|
+
// Ref para forzar el scroll al tope cuando seleccionamos un ítem
|
|
61
|
+
const modalContentRef = useRef<HTMLDivElement>(null);
|
|
49
62
|
|
|
50
63
|
const filteredList = itemList
|
|
51
64
|
.map((category) => ({
|
|
@@ -60,96 +73,336 @@ const ItemSelectionModal: React.FC<ItemModalProps> = ({
|
|
|
60
73
|
category.items.length > 0
|
|
61
74
|
);
|
|
62
75
|
|
|
76
|
+
const toggleCategory = (categoryName: string) => {
|
|
77
|
+
setExpandedCategories((prev) => ({
|
|
78
|
+
...prev,
|
|
79
|
+
[categoryName]: !prev[categoryName],
|
|
80
|
+
}));
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const handleItemClick = (item: Item) => {
|
|
84
|
+
setSelectedItem(item);
|
|
85
|
+
setQuantity(1);
|
|
86
|
+
// Si el ítem tiene precio flexible, inicializamos el customPrice con su precio actual
|
|
87
|
+
setCustomPrice(item.flexiblePrice ? item.unitPrice : null);
|
|
88
|
+
|
|
89
|
+
// Forzar scroll a la parte superior del contenido, ya que antes se iniciaba el scroll desde donde se había quedado.
|
|
90
|
+
if (modalContentRef.current) {
|
|
91
|
+
modalContentRef.current.scrollTo({ top: 0, behavior: "smooth" });
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const handleConfirmQuantity = () => {
|
|
96
|
+
if (selectedItem) {
|
|
97
|
+
// (5) Devolver el precio correcto
|
|
98
|
+
const finalPrice = selectedItem.flexiblePrice
|
|
99
|
+
? // si el precio es flexible, tomar lo que el usuario haya ingresado (o fallback al precio original)
|
|
100
|
+
customPrice ?? selectedItem.unitPrice
|
|
101
|
+
: // si NO es flexible, solo usamos el precio que ya tenía
|
|
102
|
+
selectedItem.unitPrice;
|
|
103
|
+
|
|
104
|
+
onClose({
|
|
105
|
+
...selectedItem,
|
|
106
|
+
quantity,
|
|
107
|
+
unitPrice: finalPrice,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const handleCancelQuantity = () => {
|
|
113
|
+
setSelectedItem(null);
|
|
114
|
+
setQuantity(1);
|
|
115
|
+
setCustomPrice(null);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const handleCloseModal = () => {
|
|
119
|
+
onClose(null);
|
|
120
|
+
};
|
|
121
|
+
|
|
63
122
|
return (
|
|
64
|
-
<div
|
|
65
|
-
|
|
123
|
+
<div
|
|
124
|
+
className="fixed inset-0 bg-black bg-opacity-50 flex justify-center items-center z-50"
|
|
125
|
+
onClick={handleCloseModal}
|
|
126
|
+
>
|
|
127
|
+
<div
|
|
128
|
+
//Le ponemos ref para poder scrollear al tope
|
|
129
|
+
ref={modalContentRef}
|
|
130
|
+
className="bg-white p-4 rounded-lg shadow-lg w-11/12 max-w-4xl h-5/6 overflow-y-auto"
|
|
131
|
+
onClick={(e) => e.stopPropagation()}
|
|
132
|
+
>
|
|
66
133
|
{/* Header */}
|
|
67
|
-
<div className="flex
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
</
|
|
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
|
-
/>
|
|
134
|
+
<div className="flex justify-between items-center mb-4">
|
|
135
|
+
<h2 className="text-2xl font-bold">
|
|
136
|
+
{selectedItem ? "Confirmar selección" : "Seleccionar un Ítem"}
|
|
137
|
+
</h2>
|
|
138
|
+
<button
|
|
139
|
+
className="text-gray-500 hover:text-gray-800 text-3xl"
|
|
140
|
+
onClick={handleCloseModal}
|
|
141
|
+
>
|
|
142
|
+
×
|
|
143
|
+
</button>
|
|
85
144
|
</div>
|
|
86
145
|
|
|
87
|
-
{/*
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
}}
|
|
146
|
+
{/*
|
|
147
|
+
Render condicional:
|
|
148
|
+
- Si NO hay item seleccionado => mostramos la búsqueda + listado de ítems.
|
|
149
|
+
- Si YA hay un item seleccionado => mostramos selector de cantidad y (si aplica) selector de precio.
|
|
150
|
+
*/}
|
|
151
|
+
{!selectedItem ? (
|
|
152
|
+
/* ==============================
|
|
153
|
+
PASO 1: SELECCIONAR ÍTEM
|
|
154
|
+
============================== */
|
|
155
|
+
<>
|
|
156
|
+
{/* Barra de búsqueda */}
|
|
157
|
+
<div className="relative mb-4">
|
|
158
|
+
<input
|
|
159
|
+
type="text"
|
|
160
|
+
placeholder="Buscar por nombre o categoría..."
|
|
161
|
+
value={searchTerm}
|
|
162
|
+
onChange={(e) => setSearchTerm(e.target.value)}
|
|
163
|
+
className="p-2 border rounded-lg w-full"
|
|
164
|
+
/>
|
|
165
|
+
{searchTerm && (
|
|
166
|
+
<button
|
|
167
|
+
className="absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-500 hover:text-gray-800"
|
|
168
|
+
onClick={() => setSearchTerm("")}
|
|
119
169
|
>
|
|
120
|
-
|
|
170
|
+
×
|
|
171
|
+
</button>
|
|
172
|
+
)}
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
{/* Lista de categorías e ítems */}
|
|
176
|
+
{filteredList.length === 0 ? (
|
|
177
|
+
<p className="text-gray-500 text-center">
|
|
178
|
+
No se encontraron resultados.
|
|
179
|
+
</p>
|
|
180
|
+
) : (
|
|
181
|
+
filteredList.map((category) => {
|
|
182
|
+
const isExpanded = expandedCategories[category.name];
|
|
183
|
+
|
|
184
|
+
// Estilos condicionales para "text" vs. "card"
|
|
185
|
+
const categoryContainerClasses =
|
|
186
|
+
categoryViewMode === "card"
|
|
187
|
+
? "w-full mb-2 px-4 py-3 bg-blue-50 rounded-md flex justify-between items-center cursor-pointer hover:bg-blue-100"
|
|
188
|
+
: "text-lg font-semibold mb-2 cursor-pointer flex justify-between items-center hover:text-blue-600";
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
<div key={category.name} className="mb-4">
|
|
192
|
+
{/* Cabecera de la categoría (texto o card) */}
|
|
121
193
|
<div
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
}}
|
|
194
|
+
className={categoryContainerClasses}
|
|
195
|
+
onClick={() => toggleCategory(category.name)}
|
|
132
196
|
>
|
|
133
|
-
<
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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}
|
|
197
|
+
<span>
|
|
198
|
+
{category.name}{" "}
|
|
199
|
+
<span className="text-gray-500">
|
|
200
|
+
({category.items.length})
|
|
145
201
|
</span>
|
|
146
|
-
</
|
|
202
|
+
</span>
|
|
203
|
+
<span
|
|
204
|
+
className={`transform transition-transform duration-200 ${
|
|
205
|
+
isExpanded ? "rotate-90" : ""
|
|
206
|
+
}`}
|
|
207
|
+
>
|
|
208
|
+
▶
|
|
209
|
+
</span>
|
|
147
210
|
</div>
|
|
148
|
-
|
|
211
|
+
|
|
212
|
+
{/* Ítems dentro de la categoría */}
|
|
213
|
+
{isExpanded && (
|
|
214
|
+
<div
|
|
215
|
+
className="grid gap-5"
|
|
216
|
+
style={{
|
|
217
|
+
gridTemplateColumns: `repeat(auto-fit, minmax(${defaultConfig.itemSize.width}, 1fr))`,
|
|
218
|
+
}}
|
|
219
|
+
>
|
|
220
|
+
{category.items.map((item) => {
|
|
221
|
+
// Verificamos si es un "paquete" usando item_id
|
|
222
|
+
const isPackage = item.item_id.startsWith("b");
|
|
223
|
+
|
|
224
|
+
return (
|
|
225
|
+
<div
|
|
226
|
+
key={item.code}
|
|
227
|
+
onClick={() => handleItemClick(item)}
|
|
228
|
+
className={`border rounded-lg shadow-lg hover:shadow-xl cursor-pointer relative ${
|
|
229
|
+
isPackage ? "bg-yellow-50" : ""
|
|
230
|
+
}`}
|
|
231
|
+
style={{
|
|
232
|
+
backgroundImage: `url(${item.imageUrl})`,
|
|
233
|
+
backgroundSize: "cover",
|
|
234
|
+
backgroundPosition: "center",
|
|
235
|
+
width: defaultConfig.itemSize.width,
|
|
236
|
+
height: defaultConfig.itemSize.height,
|
|
237
|
+
}}
|
|
238
|
+
>
|
|
239
|
+
{/* Franja superior (nombre) */}
|
|
240
|
+
<div
|
|
241
|
+
className={`p-2 rounded absolute top-0 left-0 right-0 ${
|
|
242
|
+
isPackage
|
|
243
|
+
? "bg-yellow-300 text-black"
|
|
244
|
+
: "bg-white bg-opacity-80"
|
|
245
|
+
}`}
|
|
246
|
+
>
|
|
247
|
+
<h4 className="font-semibold text-sm text-center">
|
|
248
|
+
{item.name}
|
|
249
|
+
</h4>
|
|
250
|
+
</div>
|
|
251
|
+
<div
|
|
252
|
+
className={`absolute bottom-0 left-0 p-2 rounded ${
|
|
253
|
+
isPackage
|
|
254
|
+
? "bg-yellow-300"
|
|
255
|
+
: "bg-white bg-opacity-80"
|
|
256
|
+
}`}
|
|
257
|
+
>
|
|
258
|
+
<span className="text-xs font-normal">
|
|
259
|
+
Stock: {item.stock}
|
|
260
|
+
</span>
|
|
261
|
+
</div>
|
|
262
|
+
<div
|
|
263
|
+
className={`absolute bottom-0 right-0 p-2 rounded ${
|
|
264
|
+
isPackage
|
|
265
|
+
? "bg-yellow-300"
|
|
266
|
+
: "bg-white bg-opacity-80"
|
|
267
|
+
}`}
|
|
268
|
+
>
|
|
269
|
+
<span className="text-xs font-normal">
|
|
270
|
+
{item.currencyPrefix}
|
|
271
|
+
{item.priceText}
|
|
272
|
+
</span>
|
|
273
|
+
</div>
|
|
274
|
+
</div>
|
|
275
|
+
);
|
|
276
|
+
})}
|
|
277
|
+
</div>
|
|
278
|
+
)}
|
|
279
|
+
</div>
|
|
280
|
+
);
|
|
281
|
+
})
|
|
282
|
+
)}
|
|
283
|
+
</>
|
|
284
|
+
) : (
|
|
285
|
+
/* ==============================
|
|
286
|
+
PASO 2: CONFIRMAR SELECCIÓN
|
|
287
|
+
============================== */
|
|
288
|
+
<div className="flex flex-col items-center justify-center">
|
|
289
|
+
<img
|
|
290
|
+
src={selectedItem.imageUrl}
|
|
291
|
+
alt={selectedItem.name}
|
|
292
|
+
className="w-40 h-40 object-cover rounded mb-4 shadow-md"
|
|
293
|
+
/>
|
|
294
|
+
<p className="mb-2 text-xl font-bold">{selectedItem.name}</p>
|
|
295
|
+
<p className="mb-2 text-base font-semibold text-gray-700">
|
|
296
|
+
Stock disponible:
|
|
297
|
+
<span className="ml-1 font-normal text-gray-800">
|
|
298
|
+
{selectedItem.stock}
|
|
299
|
+
</span>
|
|
300
|
+
</p>
|
|
301
|
+
|
|
302
|
+
{/* Si el ítem NO es flexible, mostramos el precio fijo */}
|
|
303
|
+
{!selectedItem.flexiblePrice && (
|
|
304
|
+
<p className="mb-4 text-base font-semibold text-gray-700">
|
|
305
|
+
Precio unitario:
|
|
306
|
+
<span className="ml-1 font-normal text-gray-800">
|
|
307
|
+
{selectedItem.currencyPrefix}
|
|
308
|
+
{selectedItem.priceText}
|
|
309
|
+
</span>
|
|
310
|
+
</p>
|
|
311
|
+
)}
|
|
312
|
+
|
|
313
|
+
{/*
|
|
314
|
+
Selector de cantidad
|
|
315
|
+
*/}
|
|
316
|
+
<div className="mb-4 w-full max-w-sm flex flex-col items-center">
|
|
317
|
+
<p className="text-base font-semibold text-gray-700 mb-2">
|
|
318
|
+
Seleccionar cantidad
|
|
319
|
+
</p>
|
|
320
|
+
<div className="flex items-center space-x-4">
|
|
321
|
+
<button
|
|
322
|
+
className="bg-gray-300 rounded-full w-12 h-12 flex justify-center items-center text-2xl font-bold hover:bg-gray-400"
|
|
323
|
+
onClick={() =>
|
|
324
|
+
setQuantity((prev) => (prev > 1 ? prev - 1 : 1))
|
|
325
|
+
}
|
|
326
|
+
>
|
|
327
|
+
-
|
|
328
|
+
</button>
|
|
329
|
+
<input
|
|
330
|
+
className="w-16 text-center border rounded text-lg"
|
|
331
|
+
type="number"
|
|
332
|
+
min={1}
|
|
333
|
+
value={quantity}
|
|
334
|
+
onChange={(e) => {
|
|
335
|
+
const val = parseInt(e.target.value, 10);
|
|
336
|
+
setQuantity(isNaN(val) || val < 1 ? 1 : val);
|
|
337
|
+
}}
|
|
338
|
+
/>
|
|
339
|
+
<button
|
|
340
|
+
className="bg-gray-300 rounded-full w-12 h-12 flex justify-center items-center text-2xl font-bold hover:bg-gray-400"
|
|
341
|
+
onClick={() => setQuantity((prev) => prev + 1)}
|
|
342
|
+
>
|
|
343
|
+
+
|
|
344
|
+
</button>
|
|
345
|
+
</div>
|
|
346
|
+
</div>
|
|
347
|
+
|
|
348
|
+
{/*
|
|
349
|
+
Si el ítem SÍ es flexible, ponemos un selector de precio
|
|
350
|
+
*/}
|
|
351
|
+
{selectedItem.flexiblePrice && (
|
|
352
|
+
<div className="mb-4 w-full max-w-sm flex flex-col items-center">
|
|
353
|
+
<p className="text-base font-semibold text-gray-700 mb-2">
|
|
354
|
+
Seleccionar precio
|
|
355
|
+
</p>
|
|
356
|
+
<div className="flex items-center space-x-4">
|
|
357
|
+
<button
|
|
358
|
+
className="bg-gray-300 rounded-full w-12 h-12 flex justify-center items-center text-2xl font-bold hover:bg-gray-400"
|
|
359
|
+
onClick={() =>
|
|
360
|
+
setCustomPrice((prev) => {
|
|
361
|
+
const newVal = (prev ?? 0) - 1;
|
|
362
|
+
return newVal < 0 ? 0 : newVal;
|
|
363
|
+
})
|
|
364
|
+
}
|
|
365
|
+
>
|
|
366
|
+
-
|
|
367
|
+
</button>
|
|
368
|
+
<input
|
|
369
|
+
className="w-16 text-center border rounded text-lg"
|
|
370
|
+
type="number"
|
|
371
|
+
min={0}
|
|
372
|
+
value={customPrice ?? 0}
|
|
373
|
+
onChange={(e) => {
|
|
374
|
+
const val = parseInt(e.target.value, 10);
|
|
375
|
+
setCustomPrice(isNaN(val) || val < 0 ? 0 : val);
|
|
376
|
+
}}
|
|
377
|
+
/>
|
|
378
|
+
<button
|
|
379
|
+
className="bg-gray-300 rounded-full w-12 h-12 flex justify-center items-center text-2xl font-bold hover:bg-gray-400"
|
|
380
|
+
onClick={() =>
|
|
381
|
+
setCustomPrice((prev) => (prev == null ? 1 : prev + 1))
|
|
382
|
+
}
|
|
383
|
+
>
|
|
384
|
+
+
|
|
385
|
+
</button>
|
|
149
386
|
</div>
|
|
150
|
-
|
|
387
|
+
</div>
|
|
388
|
+
)}
|
|
389
|
+
|
|
390
|
+
{/* Botones de Confirmar y Volver */}
|
|
391
|
+
<div className="flex space-x-4 mt-6">
|
|
392
|
+
<button
|
|
393
|
+
className="bg-gray-300 px-4 py-2 rounded hover:bg-gray-400"
|
|
394
|
+
onClick={handleCancelQuantity}
|
|
395
|
+
>
|
|
396
|
+
Volver
|
|
397
|
+
</button>
|
|
398
|
+
<button
|
|
399
|
+
className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700"
|
|
400
|
+
onClick={handleConfirmQuantity}
|
|
401
|
+
>
|
|
402
|
+
Confirmar
|
|
403
|
+
</button>
|
|
151
404
|
</div>
|
|
152
|
-
|
|
405
|
+
</div>
|
|
153
406
|
)}
|
|
154
407
|
</div>
|
|
155
408
|
</div>
|
|
@@ -158,8 +411,9 @@ const ItemSelectionModal: React.FC<ItemModalProps> = ({
|
|
|
158
411
|
|
|
159
412
|
// Función para crear el modal
|
|
160
413
|
export const createItemModal = (
|
|
161
|
-
itemList:
|
|
162
|
-
config?: ItemModalProps["config"]
|
|
414
|
+
itemList: ItemModalCategory[],
|
|
415
|
+
config?: ItemModalProps["config"],
|
|
416
|
+
categoryViewMode?: ItemModalProps["categoryViewMode"]
|
|
163
417
|
): Promise<Item | null> => {
|
|
164
418
|
return new Promise((resolve) => {
|
|
165
419
|
const handleClose = (selectedItem: Item | null) => {
|
|
@@ -180,6 +434,7 @@ export const createItemModal = (
|
|
|
180
434
|
itemList={itemList}
|
|
181
435
|
onClose={handleClose}
|
|
182
436
|
config={config}
|
|
437
|
+
categoryViewMode={categoryViewMode}
|
|
183
438
|
/>
|
|
184
439
|
);
|
|
185
440
|
});
|
package/src/NavBar/NavBar.tsx
CHANGED
|
@@ -12,7 +12,7 @@ import type {
|
|
|
12
12
|
NavBarItem,
|
|
13
13
|
NavBarProps,
|
|
14
14
|
} from "./NavBar.types.js";
|
|
15
|
-
import { Link, useNavigate } from "@remix-run/react";
|
|
15
|
+
import { Link, useNavigate, useLocation } from "@remix-run/react";
|
|
16
16
|
import { useAppSelector } from "@zauru-sdk/redux";
|
|
17
17
|
|
|
18
18
|
const OptionsDropDownButton = ({ color, options, name }: EntityProps) => {
|
|
@@ -52,6 +52,9 @@ const NavItem = ({
|
|
|
52
52
|
childrens = [],
|
|
53
53
|
reduxNotificationBadge,
|
|
54
54
|
}: NavBarItem) => {
|
|
55
|
+
const location = useLocation();
|
|
56
|
+
const isActive = location.pathname === link;
|
|
57
|
+
|
|
55
58
|
const specialColor: ColorInterface = selectedColor
|
|
56
59
|
? COLORS[selectedColor]
|
|
57
60
|
: COLORS["slate"];
|
|
@@ -69,43 +72,51 @@ const NavItem = ({
|
|
|
69
72
|
setNotificationBadge(relevantState);
|
|
70
73
|
}, [relevantState]);
|
|
71
74
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
<Link
|
|
80
|
-
key={index}
|
|
81
|
-
to={x.link}
|
|
82
|
-
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`}
|
|
83
|
-
>
|
|
84
|
-
{x.name}
|
|
85
|
-
</Link>
|
|
86
|
-
))}
|
|
87
|
-
/>
|
|
88
|
-
) : (
|
|
89
|
-
<div
|
|
90
|
-
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`}
|
|
91
|
-
>
|
|
75
|
+
// Si este NavItem tiene elementos hijos, renderiza el botón desplegable:
|
|
76
|
+
if (childrens.length > 0) {
|
|
77
|
+
return (
|
|
78
|
+
<OptionsDropDownButton
|
|
79
|
+
name={name}
|
|
80
|
+
color={specialColor}
|
|
81
|
+
options={childrens.map((x, index) => (
|
|
92
82
|
<Link
|
|
93
|
-
|
|
94
|
-
to={link}
|
|
83
|
+
key={index}
|
|
84
|
+
to={x.link}
|
|
85
|
+
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`}
|
|
95
86
|
>
|
|
96
|
-
|
|
97
|
-
{icon}
|
|
98
|
-
<span>{name}</span>
|
|
99
|
-
</div>
|
|
100
|
-
{/* Badge de notificaciones */}
|
|
101
|
-
{notificationBadge !== undefined && (
|
|
102
|
-
<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">
|
|
103
|
-
{notificationBadge}
|
|
104
|
-
</span>
|
|
105
|
-
)}
|
|
87
|
+
{x.name}
|
|
106
88
|
</Link>
|
|
107
|
-
|
|
108
|
-
|
|
89
|
+
))}
|
|
90
|
+
/>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Si NO tiene elementos hijos, se renderiza como un ítem simple:
|
|
95
|
+
return (
|
|
96
|
+
<li className="nav-item relative">
|
|
97
|
+
<div
|
|
98
|
+
// Si está activo, usamos color de fondo más oscuro (bg900)
|
|
99
|
+
// De lo contrario, usamos el color normal (bg700)
|
|
100
|
+
className={`${
|
|
101
|
+
isActive ? specialColor.bg900 : specialColor.bg700
|
|
102
|
+
} 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`}
|
|
103
|
+
>
|
|
104
|
+
<Link
|
|
105
|
+
className="px-3 flex items-center text-xs leading-snug text-white uppercase hover:opacity-75 relative"
|
|
106
|
+
to={link}
|
|
107
|
+
>
|
|
108
|
+
<div className="mx-auto pt-2">
|
|
109
|
+
{icon}
|
|
110
|
+
<span>{name}</span>
|
|
111
|
+
</div>
|
|
112
|
+
{/* Badge de notificaciones */}
|
|
113
|
+
{notificationBadge !== undefined && (
|
|
114
|
+
<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">
|
|
115
|
+
{notificationBadge}
|
|
116
|
+
</span>
|
|
117
|
+
)}
|
|
118
|
+
</Link>
|
|
119
|
+
</div>
|
|
109
120
|
</li>
|
|
110
121
|
);
|
|
111
122
|
};
|
|
@@ -165,6 +176,7 @@ export const NavBar = ({
|
|
|
165
176
|
color={color}
|
|
166
177
|
options={[
|
|
167
178
|
<Link
|
|
179
|
+
key="cerrar-sesion"
|
|
168
180
|
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`}
|
|
169
181
|
to="/logout"
|
|
170
182
|
>
|
|
@@ -187,19 +199,12 @@ export const NavBar = ({
|
|
|
187
199
|
<Link
|
|
188
200
|
className="text-sm font-bold leading-relaxed inline-block mr-4 py-2 whitespace-nowrap uppercase text-white"
|
|
189
201
|
to={"/home"}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
alt="logo-zauru"
|
|
197
|
-
/>
|
|
198
|
-
</div>
|
|
199
|
-
{title}
|
|
200
|
-
</>
|
|
201
|
-
}
|
|
202
|
-
/>
|
|
202
|
+
>
|
|
203
|
+
<div className="inline-block mr-2 mb-2 align-middle">
|
|
204
|
+
<img className="w-auto h-7" src="/logo.png" alt="logo-zauru" />
|
|
205
|
+
</div>
|
|
206
|
+
{title}
|
|
207
|
+
</Link>
|
|
203
208
|
{version !== currentVersion && (
|
|
204
209
|
<button
|
|
205
210
|
className={`ml-2 px-2 py-1 text-xs text-white ${color.bg700} rounded-full hover:${color.bg900} transition-colors duration-200`}
|
|
@@ -3,7 +3,14 @@ export type NavBarItem = {
|
|
|
3
3
|
link: string;
|
|
4
4
|
loggedIn: boolean;
|
|
5
5
|
icon?: any;
|
|
6
|
-
selectedColor?:
|
|
6
|
+
selectedColor?:
|
|
7
|
+
| "pink"
|
|
8
|
+
| "purple"
|
|
9
|
+
| "slate"
|
|
10
|
+
| "green"
|
|
11
|
+
| "yellow"
|
|
12
|
+
| "red"
|
|
13
|
+
| "sky";
|
|
7
14
|
color?: ColorInterface;
|
|
8
15
|
childrens?: Exclude<NavBarItem, "loggedIn">[];
|
|
9
16
|
reduxNotificationBadge?: (state: any) => string | number;
|
|
@@ -35,6 +42,7 @@ export type ColorInterface = {
|
|
|
35
42
|
bg700: string;
|
|
36
43
|
bg600: string;
|
|
37
44
|
bg500: string;
|
|
45
|
+
bg200: string;
|
|
38
46
|
ring600: string;
|
|
39
47
|
ring500: string;
|
|
40
48
|
};
|