Skip to content

Commit 35dddc6

Browse files
authored
Merge pull request #267 from codeableorg/feat/update-product-variant-ui
Refactor: product.service & UI and chatbot update
2 parents f796181 + 937f49a commit 35dddc6

File tree

5 files changed

+257
-148
lines changed

5 files changed

+257
-148
lines changed

prisma/initial_data.ts

Lines changed: 52 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ export const categories = [
3030
];
3131

3232
export const variantAttributes = [
33-
{ name: "no aplica" },
3433
{ name: "talla" },
3534
{ name: "dimensiones" },
35+
{ name: "no aplica" },
3636
]
3737

3838
export const products = [
@@ -357,74 +357,74 @@ export const products = [
357357

358358
export const variantAttributeValues = [
359359
// --- POLOS (talla: S, M, L) ---
360-
{ attributeId: 1, productId: 1, value: "S", price: 20.0 },
361-
{ attributeId: 1, productId: 1, value: "M", price: 20.0 },
362-
{ attributeId: 1, productId: 1, value: "L", price: 20.0 },
360+
{ attributeId: 1, productId: 1, value: "Small", price: 20.0 },
361+
{ attributeId: 1, productId: 1, value: "Medium", price: 20.0 },
362+
{ attributeId: 1, productId: 1, value: "Large", price: 20.0 },
363363

364-
{ attributeId: 1, productId: 2, value: "S", price: 20.0 },
365-
{ attributeId: 1, productId: 2, value: "M", price: 20.0 },
366-
{ attributeId: 1, productId: 2, value: "L", price: 20.0 },
364+
{ attributeId: 1, productId: 2, value: "Small", price: 20.0 },
365+
{ attributeId: 1, productId: 2, value: "Medium", price: 20.0 },
366+
{ attributeId: 1, productId: 2, value: "Large", price: 20.0 },
367367

368-
{ attributeId: 1, productId: 3, value: "S", price: 20.0 },
369-
{ attributeId: 1, productId: 3, value: "M", price: 20.0 },
370-
{ attributeId: 1, productId: 3, value: "L", price: 20.0 },
368+
{ attributeId: 1, productId: 3, value: "Small", price: 20.0 },
369+
{ attributeId: 1, productId: 3, value: "Medium", price: 20.0 },
370+
{ attributeId: 1, productId: 3, value: "Large", price: 20.0 },
371371

372-
{ attributeId: 1, productId: 4, value: "S", price: 20.0 },
373-
{ attributeId: 1, productId: 4, value: "M", price: 20.0 },
374-
{ attributeId: 1, productId: 4, value: "L", price: 20.0 },
372+
{ attributeId: 1, productId: 4, value: "Small", price: 20.0 },
373+
{ attributeId: 1, productId: 4, value: "Medium", price: 20.0 },
374+
{ attributeId: 1, productId: 4, value: "Large", price: 20.0 },
375375

376-
{ attributeId: 1, productId: 5, value: "S", price: 25.0 },
377-
{ attributeId: 1, productId: 5, value: "M", price: 25.0 },
378-
{ attributeId: 1, productId: 5, value: "L", price: 25.0 },
376+
{ attributeId: 1, productId: 5, value: "Small", price: 25.0 },
377+
{ attributeId: 1, productId: 5, value: "Medium", price: 25.0 },
378+
{ attributeId: 1, productId: 5, value: "Large", price: 25.0 },
379379

380-
{ attributeId: 1, productId: 6, value: "S", price: 25.0 },
381-
{ attributeId: 1, productId: 6, value: "M", price: 25.0 },
382-
{ attributeId: 1, productId: 6, value: "L", price: 25.0 },
380+
{ attributeId: 1, productId: 6, value: "Small", price: 25.0 },
381+
{ attributeId: 1, productId: 6, value: "Medium", price: 25.0 },
382+
{ attributeId: 1, productId: 6, value: "Large", price: 25.0 },
383383

384-
{ attributeId: 1, productId: 7, value: "S", price: 25.0 },
385-
{ attributeId: 1, productId: 7, value: "M", price: 25.0 },
386-
{ attributeId: 1, productId: 7, value: "L", price: 25.0 },
384+
{ attributeId: 1, productId: 7, value: "Small", price: 25.0 },
385+
{ attributeId: 1, productId: 7, value: "Medium", price: 25.0 },
386+
{ attributeId: 1, productId: 7, value: "Large", price: 25.0 },
387387

388-
{ attributeId: 1, productId: 8, value: "S", price: 15.0 },
389-
{ attributeId: 1, productId: 8, value: "M", price: 15.0 },
390-
{ attributeId: 1, productId: 8, value: "L", price: 15.0 },
388+
{ attributeId: 1, productId: 8, value: "Small", price: 15.0 },
389+
{ attributeId: 1, productId: 8, value: "Medium", price: 15.0 },
390+
{ attributeId: 1, productId: 8, value: "Large", price: 15.0 },
391391

392-
{ attributeId: 1, productId: 9, value: "S", price: 15.0 },
393-
{ attributeId: 1, productId: 9, value: "M", price: 15.0 },
394-
{ attributeId: 1, productId: 9, value: "L", price: 15.0 },
392+
{ attributeId: 1, productId: 9, value: "Small", price: 15.0 },
393+
{ attributeId: 1, productId: 9, value: "Medium", price: 15.0 },
394+
{ attributeId: 1, productId: 9, value: "Large", price: 15.0 },
395395

396396
// --- STICKERS (dimensiones: 3x3, 6x6, 9x9) ---
397-
{ attributeId: 2, productId: 10, value: "3x3", price: 2.99 },
398-
{ attributeId: 2, productId: 10, value: "5x5", price: 3.99 },
399-
{ attributeId: 2, productId: 10, value: "10x10", price: 4.99 },
397+
{ attributeId: 2, productId: 10, value: "3x3 cm", price: 2.99 },
398+
{ attributeId: 2, productId: 10, value: "5x5 cm", price: 3.99 },
399+
{ attributeId: 2, productId: 10, value: "10x10 cm", price: 4.99 },
400400

401-
{ attributeId: 2, productId: 11, value: "3x3", price: 2.49 },
402-
{ attributeId: 2, productId: 11, value: "5x5", price: 3.49 },
403-
{ attributeId: 2, productId: 11, value: "10x10", price: 4.49 },
401+
{ attributeId: 2, productId: 11, value: "3x3 cm", price: 2.49 },
402+
{ attributeId: 2, productId: 11, value: "5x5 cm", price: 3.49 },
403+
{ attributeId: 2, productId: 11, value: "10x10 cm", price: 4.49 },
404404

405-
{ attributeId: 2, productId: 12, value: "3x3", price: 3.99 },
406-
{ attributeId: 2, productId: 12, value: "5x5", price: 4.99 },
407-
{ attributeId: 2, productId: 12, value: "10x10", price: 5.99 },
405+
{ attributeId: 2, productId: 12, value: "3x3 cm", price: 3.99 },
406+
{ attributeId: 2, productId: 12, value: "5x5 cm", price: 4.99 },
407+
{ attributeId: 2, productId: 12, value: "10x10 cm", price: 5.99 },
408408

409-
{ attributeId: 2, productId: 13, value: "3x3", price: 2.99 },
410-
{ attributeId: 2, productId: 13, value: "5x5", price: 3.99 },
411-
{ attributeId: 2, productId: 13, value: "10x10", price: 4.99 },
409+
{ attributeId: 2, productId: 13, value: "3x3 cm", price: 2.99 },
410+
{ attributeId: 2, productId: 13, value: "5x5 cm", price: 3.99 },
411+
{ attributeId: 2, productId: 13, value: "10x10 cm", price: 4.99 },
412412

413-
{ attributeId: 2, productId: 14, value: "3x3", price: 2.49 },
414-
{ attributeId: 2, productId: 14, value: "5x5", price: 3.49 },
415-
{ attributeId: 2, productId: 14, value: "10x10", price: 4.49 },
413+
{ attributeId: 2, productId: 14, value: "3x3 cm", price: 2.49 },
414+
{ attributeId: 2, productId: 14, value: "5x5 cm", price: 3.49 },
415+
{ attributeId: 2, productId: 14, value: "10x10 cm", price: 4.49 },
416416

417-
{ attributeId: 2, productId: 15, value: "3x3", price: 2.49 },
418-
{ attributeId: 2, productId: 15, value: "5x5", price: 3.49 },
419-
{ attributeId: 2, productId: 15, value: "10x10", price: 4.49 },
417+
{ attributeId: 2, productId: 15, value: "3x3 cm", price: 2.49 },
418+
{ attributeId: 2, productId: 15, value: "5x5 cm", price: 3.49 },
419+
{ attributeId: 2, productId: 15, value: "10x10 cm", price: 4.49 },
420420

421-
{ attributeId: 2, productId: 16, value: "3x3", price: 2.99 },
422-
{ attributeId: 2, productId: 16, value: "5x5", price: 3.99 },
423-
{ attributeId: 2, productId: 16, value: "10x10", price: 4.99 },
421+
{ attributeId: 2, productId: 16, value: "3x3 cm", price: 2.99 },
422+
{ attributeId: 2, productId: 16, value: "5x5 cm", price: 3.99 },
423+
{ attributeId: 2, productId: 16, value: "10x10 cm", price: 4.99 },
424424

425-
{ attributeId: 2, productId: 17, value: "3x3", price: 2.99 },
426-
{ attributeId: 2, productId: 17, value: "5x5", price: 3.99 },
427-
{ attributeId: 2, productId: 17, value: "10x10", price: .99 },
425+
{ attributeId: 2, productId: 17, value: "3x3 cm", price: 2.99 },
426+
{ attributeId: 2, productId: 17, value: "5x5 cm", price: 3.99 },
427+
{ attributeId: 2, productId: 17, value: "10x10 cm", price: 4.99 },
428428

429429
// --- TAZAS (no aplica: Único) ---
430430
{ attributeId: 3, productId: 18, value: "Único", price: 14.99 },

src/routes/category/components/product-card/index.tsx

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,25 @@ export function ProductCard({ product }: ProductCardProps) {
3131
<div className="flex grow flex-col gap-2 p-4">
3232
<h2 className="text-sm font-medium">{product.title}</h2>
3333
<p className="text-sm text-muted-foreground">{product.description}</p>
34-
{isSticker ? (
35-
<div className="text-xs text-muted-foreground">
36-
<p className="text-base font-semibold text-accent-foreground">
37-
Desde
38-
</p>
39-
<p className="font-medium text-foreground text-base">
40-
S/{product.minPrice} - S/{product.maxPrice}
41-
</p>
42-
</div>
34+
{product.categoryId === 3 ? (
35+
<div className="text-xs text-muted-foreground">
36+
<p className="text-base font-semibold text-accent-foreground">
37+
Desde
38+
</p>
39+
<p className="font-medium text-foreground text-base">
40+
S/{product.minPrice} - S/{product.maxPrice}
41+
</p>
42+
</div>
4343
) : (
44-
<p className="mt-auto text-base font-medium">S/{product.price}</p>
45-
)}
44+
<div className="text-xs text-muted-foreground">
45+
<p className="text-base font-semibold text-accent-foreground">
46+
Precio
47+
</p>
48+
<p className="font-medium text-foreground text-base">
49+
S/{product.price}
50+
</p>
51+
</div>
52+
)}
4653
</div>
4754
{product.isOnSale && (
4855
<span className="absolute top-0 right-0 rounded-bl-xl bg-primary px-2 py-1 text-sm font-medium text-primary-foreground">

src/routes/product/index.tsx

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState } from "react";
1+
import { useState, useEffect } from "react";
22
import { Form, useNavigation } from "react-router";
33

44
import { Button, Container, Separator } from "@/components/ui";
@@ -22,14 +22,57 @@ export default function Product({ loaderData }: Route.ComponentProps) {
2222
const { product } = loaderData;
2323
const navigation = useNavigation();
2424
const cartLoading = navigation.state === "submitting";
25-
const [selectedSize, setSelectedSize] = useState<string>("Medium");
25+
26+
// Estados para manejar variantes
27+
const [selectedVariant, setSelectedVariant] = useState<number | null>(null);
28+
const [currentPrice, setCurrentPrice] = useState<number>(0);
2629

2730
if (!product) {
2831
return <NotFound />;
2932
}
3033

3134
const showSizeSelector = product.categoryId === 1 || product.categoryId === 3;
3235

36+
// Verificar si el producto tiene variantes
37+
const hasVariants = product.variantAttributeValues && product.variantAttributeValues.length > 0;
38+
39+
// Verificar si debe mostrar selectores (solo polos y stickers)
40+
const shouldShowVariants = hasVariants && (product.categoryId === 1 || product.categoryId === 3);
41+
42+
// Agrupar variantes por atributo (en caso de que un producto tenga múltiples tipos de atributos)
43+
const variantGroups = shouldShowVariants
44+
? product.variantAttributeValues.reduce((groups, variant) => {
45+
const attributeName = variant.variantAttribute.name;
46+
if (!groups[attributeName]) {
47+
groups[attributeName] = [];
48+
}
49+
groups[attributeName].push(variant);
50+
return groups;
51+
}, {} as Record<string, typeof product.variantAttributeValues>)
52+
: {};
53+
54+
// Inicializar precio y variante seleccionada
55+
useEffect(() => {
56+
if (hasVariants) {
57+
// Seleccionar la primera variante por defecto
58+
const firstVariant = product.variantAttributeValues[0];
59+
setSelectedVariant(firstVariant.id);
60+
setCurrentPrice(firstVariant.price);
61+
} else {
62+
// Si no hay variantes, usar el precio base del producto (asumiendo que existe)
63+
setCurrentPrice(product.price || 0);
64+
}
65+
}, [product]);
66+
67+
// Manejar cambio de variante
68+
const handleVariantChange = (variantId: number) => {
69+
setSelectedVariant(variantId);
70+
const variant = product.variantAttributeValues.find(v => v.id === variantId);
71+
if (variant) {
72+
setCurrentPrice(variant.price);
73+
}
74+
};
75+
3376
const getAttributeValueId = () => { // AQUI TRAER EL AttributeValueId con el cambio de SEBAS
3477
if (
3578
!product.variantAttributeValues ||
@@ -41,29 +84,6 @@ export default function Product({ loaderData }: Route.ComponentProps) {
4184
return product.variantAttributeValues[0].id;
4285
};
4386

44-
const getSizeOptions = () => {
45-
if (product.categoryId === 3) {
46-
return {
47-
label: "Dimensiones",
48-
options: [
49-
{ value: "Small", label: "3x3 cm" },
50-
{ value: "Medium", label: "5x5 cm" },
51-
{ value: "Large", label: "10x10 cm" },
52-
],
53-
};
54-
} else {
55-
return {
56-
label: "Talla",
57-
options: [
58-
{ value: "Small", label: "Small" },
59-
{ value: "Medium", label: "Medium" },
60-
{ value: "Large", label: "Large" },
61-
],
62-
};
63-
}
64-
};
65-
66-
const sizeOptions = getSizeOptions();
6787

6888
return (
6989
<>

0 commit comments

Comments
 (0)