From 6da9f5b2457be52a3c953c5ab9b8baeef0e0f2e0 Mon Sep 17 00:00:00 2001 From: Janet Huacahuasi Date: Fri, 29 Aug 2025 18:32:49 -0500 Subject: [PATCH 1/4] feat: integration product detail with cart service --- .react-router/types/+register.ts | 1 + prisma/initial_data.ts | 2 +- src/routes/cart/add-item/index.tsx | 2 - src/routes/product/index.tsx | 110 ++++++++++++++++++----------- 4 files changed, 69 insertions(+), 46 deletions(-) diff --git a/.react-router/types/+register.ts b/.react-router/types/+register.ts index 8e951ae..5901e8b 100644 --- a/.react-router/types/+register.ts +++ b/.react-router/types/+register.ts @@ -29,4 +29,5 @@ type Params = { "/account/orders": {}; "/not-found": {}; "/verify-email": {}; + "/chat": {}; }; \ No newline at end of file diff --git a/prisma/initial_data.ts b/prisma/initial_data.ts index 3ab5380..7796283 100644 --- a/prisma/initial_data.ts +++ b/prisma/initial_data.ts @@ -30,9 +30,9 @@ export const categories = [ ]; export const variantAttributes = [ - { name: "no aplica" }, { name: "talla" }, { name: "dimensiones" }, + { name: "no aplica" }, ] export const products = [ diff --git a/src/routes/cart/add-item/index.tsx b/src/routes/cart/add-item/index.tsx index 06745d1..a8adce0 100644 --- a/src/routes/cart/add-item/index.tsx +++ b/src/routes/cart/add-item/index.tsx @@ -13,8 +13,6 @@ export async function action({ request }: Route.ActionArgs) { const session = await getSession(request.headers.get("Cookie")); const sessionCartId = session.get("sessionCartId"); const userId = session.get("userId"); - await addToCart(userId, sessionCartId, attributeValueId, quantity); - return redirect(redirectTo || "/cart"); } diff --git a/src/routes/product/index.tsx b/src/routes/product/index.tsx index 545544c..b8691c1 100644 --- a/src/routes/product/index.tsx +++ b/src/routes/product/index.tsx @@ -8,6 +8,21 @@ import { getProductById } from "@/services/product.service"; import NotFound from "../not-found"; import type { Route } from "./+types"; +const categoryStickerId =3 +const categoryTazaId = 2 +const initialVariantPosition = 1 + +const variantGroupLabel: { [key: number]: string } = { + 3: 'No aplica', + 1: 'Talla', + 2: 'Dimensiones' +} +const displayVariantSize: { [key: string]: string } = { + 'S': 'Small', + 'M': 'Medium', + 'L': 'Large' +} + export async function loader({ params }: Route.LoaderArgs) { try { @@ -20,50 +35,59 @@ export async function loader({ params }: Route.LoaderArgs) { export default function Product({ loaderData }: Route.ComponentProps) { const { product } = loaderData; - const navigation = useNavigation(); - const cartLoading = navigation.state === "submitting"; - const [selectedSize, setSelectedSize] = useState("Medium"); - - if (!product) { - return ; - } - - const showSizeSelector = product.categoryId === 1 || product.categoryId === 3; - - const getAttributeValueId = () => { // AQUI TRAER EL AttributeValueId con el cambio de SEBAS - if ( - !product.variantAttributeValues || - product.variantAttributeValues.length === 0 - ) { - return undefined; - } - // Devuelve el attributeId de la posición 0 - return product.variantAttributeValues[0].id; - }; const getSizeOptions = () => { - if (product.categoryId === 3) { - return { - label: "Dimensiones", - options: [ - { value: "Small", label: "3x3 cm" }, - { value: "Medium", label: "5x5 cm" }, - { value: "Large", label: "10x10 cm" }, - ], - }; - } else { + const options = product?.variantAttributeValues?.map((variantAttribute, index) => ({ + value: variantAttribute.id, + label: product.categoryId === categoryStickerId ? `${variantAttribute.value} cm` : displayVariantSize[variantAttribute.value], + price: variantAttribute.price, + selected: index===initialVariantPosition?true:false + })); + if (product?.categoryId === categoryTazaId) { return { - label: "Talla", - options: [ - { value: "Small", label: "Small" }, - { value: "Medium", label: "Medium" }, - { value: "Large", label: "Large" }, - ], - }; + label: '', + options: [{ + value: product?.variantAttributeValues?.[0].id, + label: product?.variantAttributeValues?.[0].value, + price: product?.variantAttributeValues?.[0].price, + selected: true + }] + } + } + return { + label: variantGroupLabel[product?.variantAttributeValues?.[0].attributeId as number], + options: options || [], } }; const sizeOptions = getSizeOptions(); + const navigation = useNavigation(); + const cartLoading = navigation.state === "submitting"; + const [data, setData] = useState(sizeOptions) + const showSizeSelector = product?.categoryId === 1 || product?.categoryId === 3; + const selectedDisplay = data.options.find((option) => option.selected === true); + + const onSelectedVariant = (id: number) => { + setData({ + ...data, + options: data.options.map((v) => { + if (v.value === id) { + return ({ + ...v, + selected: true + }) + } + return ({ + ...v, + selected: false + }) + }) + }) + } + + if (!product) { + return ; + } return ( <> @@ -84,15 +108,15 @@ export default function Product({ loaderData }: Route.ComponentProps) { {" "} ( { - sizeOptions.options.find( - (option) => option.value === selectedSize + data.options.find( + (option) => option.selected === true )?.label } ) )} -

S/{product.price}

+

S/ {selectedDisplay?.price}

{product.description}

@@ -107,10 +131,10 @@ export default function Product({ loaderData }: Route.ComponentProps) { @@ -128,7 +152,7 @@ export default function Product({ loaderData }: Route.ComponentProps) { - + ); } diff --git a/src/routes/category/index.tsx b/src/routes/category/index.tsx index 65ad74c..5066e1a 100644 --- a/src/routes/category/index.tsx +++ b/src/routes/category/index.tsx @@ -2,9 +2,8 @@ import { redirect } from "react-router"; import { Container } from "@/components/ui"; import { isValidCategorySlug, type Category } from "@/models/category.model"; -import type { Product } from "@/models/product.model"; import { getCategoryBySlug } from "@/services/category.service"; -import { getProductsByCategorySlug } from "@/services/product.service"; +import { filterByMinMaxPrice } from "@/services/product.service"; import { PriceFilter } from "./components/price-filter"; import { ProductCard } from "./components/product-card"; @@ -19,54 +18,25 @@ export async function loader({ params, request }: Route.LoaderArgs) { } const url = new URL(request.url); - const minPrice = url.searchParams.get("minPrice") || ""; - const maxPrice = url.searchParams.get("maxPrice") || ""; + const minPrice = url.searchParams.get("minPrice"); + const minValue = minPrice ? parseFloat(minPrice) : undefined; + const maxPrice = url.searchParams.get("maxPrice"); + const maxValue = maxPrice ? parseFloat(maxPrice) : undefined; + try { - const [category, products] = await Promise.all([ + const [category] = await Promise.all([ getCategoryBySlug(categorySlug), - getProductsByCategorySlug(categorySlug), ]); - - const filterProductsByPrice = ( - products: Product[], - minPrice: string, - maxPrice: string - ) => { - const min = minPrice ? parseFloat(minPrice) : 0; - const max = maxPrice ? parseFloat(maxPrice) : Infinity; - return products.filter( - (product) => { - const minProductPrice = product.minPrice||0 - const maxProductPrice = product.maxPrice ||0 - const productPrice = product.price || 0 - - if (min && max) { - return ((productPrice||minProductPrice) >= min) && ((productPrice||maxProductPrice) <= max) - } - - if (min) { - return (productPrice||minProductPrice) >= min - } - - if (max) { - return (productPrice||maxProductPrice) <= max - } - return true - }); - }; - - const filteredProducts = filterProductsByPrice( - products, - minPrice, - maxPrice - ); + console.log({categorySlug, minValue, maxValue}) + const finalProducts = await filterByMinMaxPrice(categorySlug, minValue, maxValue); return { category, - products: filteredProducts, - minPrice, - maxPrice, + products: finalProducts, + minPrice: minValue, + maxPrice: maxValue, + finalProducts }; } catch (e) { throw new Response("Error loading category: " + e, { status: 500 }); diff --git a/src/services/product.service.ts b/src/services/product.service.ts index add02b7..d4434a4 100644 --- a/src/services/product.service.ts +++ b/src/services/product.service.ts @@ -5,9 +5,9 @@ import type { VariantAttributeValue } from "@/models/variant-attribute.model"; import { getCategoryBySlug } from "./category.service"; -const formattedProduct = (product: ProductVariantValue) => { +const formattedProduct = (product:any ): ProductVariantValue => { const {variantAttributeValues, ...rest} = product - const prices = variantAttributeValues.map((v: VariantAttributeValue) => Number(v.price)) + const prices = variantAttributeValues.map((v: VariantAttributeValue) => v.price.toNumber()) const minPrice = Math.min(...prices) const maxPrice = Math.max(...prices) if (minPrice === maxPrice) { @@ -19,7 +19,7 @@ const formattedProduct = (product: ProductVariantValue) => { return { ...rest, minPrice, - maxPrice + maxPrice, } } @@ -37,7 +37,7 @@ export async function getProductsByCategorySlug( return products.map(formattedProduct) } -export async function getProductById(id: number): Promise { +export async function getProductById(id: number): Promise { const product = await prisma.product.findUnique({ where: { id }, include: { @@ -50,10 +50,10 @@ export async function getProductById(id: number): Promise { } const variants = product.variantAttributeValues.map((variant)=> ({ ...variant, - price: Number(variant.price) + price: variant.price.toNumber() })) -return {...product, variantAttributeValues: variants } as Product +return {...product, variantAttributeValues: variants } } export async function getAllProducts(): Promise { @@ -64,3 +64,36 @@ export async function getAllProducts(): Promise { }); return products.map(formattedProduct) } + +export async function filterByMinMaxPrice( + slug: string, + min?: number, + max?: number +): Promise { + const priceFilter: any = {}; + + if (min !== undefined) { + priceFilter.gte = min; + } + if (max !== undefined) { + priceFilter.lte = max; + } + + const result = await prisma.product.findMany({ + where: { + category: { + slug: slug as any, // si slug es enum + }, + variantAttributeValues: { + some: { + price: priceFilter, // 👈 el rango se aplica al mismo variant + }, + }, + }, + include: { + variantAttributeValues: true, + }, + }); + + return result.map(formattedProduct); +} From 027de23e6d4ae2c3044d7194176ba60b499fbc26 Mon Sep 17 00:00:00 2001 From: mike Date: Sun, 31 Aug 2025 22:24:07 -0500 Subject: [PATCH 4/4] feat: update variant attribute values and improve product pricing logic --- prisma/initial_data.ts | 102 ++++----- src/models/cart.model.ts | 3 + src/models/variant-attribute.model.ts | 7 +- .../components/product-card/index.tsx | 9 +- src/routes/product/index.tsx | 205 ++++++++---------- src/services/cart.service.ts | 2 + src/services/product.service.ts | 69 +++--- 7 files changed, 196 insertions(+), 201 deletions(-) diff --git a/prisma/initial_data.ts b/prisma/initial_data.ts index 7796283..a46b79b 100644 --- a/prisma/initial_data.ts +++ b/prisma/initial_data.ts @@ -357,74 +357,74 @@ export const products = [ export const variantAttributeValues = [ // --- POLOS (talla: S, M, L) --- - { attributeId: 1, productId: 1, value: "S", price: 20.0 }, - { attributeId: 1, productId: 1, value: "M", price: 20.0 }, - { attributeId: 1, productId: 1, value: "L", price: 20.0 }, + { attributeId: 1, productId: 1, value: "Small", price: 20.0 }, + { attributeId: 1, productId: 1, value: "Medium", price: 20.0 }, + { attributeId: 1, productId: 1, value: "Large", price: 20.0 }, - { attributeId: 1, productId: 2, value: "S", price: 20.0 }, - { attributeId: 1, productId: 2, value: "M", price: 20.0 }, - { attributeId: 1, productId: 2, value: "L", price: 20.0 }, + { attributeId: 1, productId: 2, value: "Small", price: 20.0 }, + { attributeId: 1, productId: 2, value: "Medium", price: 20.0 }, + { attributeId: 1, productId: 2, value: "Large", price: 20.0 }, - { attributeId: 1, productId: 3, value: "S", price: 20.0 }, - { attributeId: 1, productId: 3, value: "M", price: 20.0 }, - { attributeId: 1, productId: 3, value: "L", price: 20.0 }, + { attributeId: 1, productId: 3, value: "Small", price: 20.0 }, + { attributeId: 1, productId: 3, value: "Medium", price: 20.0 }, + { attributeId: 1, productId: 3, value: "Large", price: 20.0 }, - { attributeId: 1, productId: 4, value: "S", price: 20.0 }, - { attributeId: 1, productId: 4, value: "M", price: 20.0 }, - { attributeId: 1, productId: 4, value: "L", price: 20.0 }, + { attributeId: 1, productId: 4, value: "Small", price: 20.0 }, + { attributeId: 1, productId: 4, value: "Medium", price: 20.0 }, + { attributeId: 1, productId: 4, value: "Large", price: 20.0 }, - { attributeId: 1, productId: 5, value: "S", price: 25.0 }, - { attributeId: 1, productId: 5, value: "M", price: 25.0 }, - { attributeId: 1, productId: 5, value: "L", price: 25.0 }, + { attributeId: 1, productId: 5, value: "Small", price: 25.0 }, + { attributeId: 1, productId: 5, value: "Medium", price: 25.0 }, + { attributeId: 1, productId: 5, value: "Large", price: 25.0 }, - { attributeId: 1, productId: 6, value: "S", price: 25.0 }, - { attributeId: 1, productId: 6, value: "M", price: 25.0 }, - { attributeId: 1, productId: 6, value: "L", price: 25.0 }, + { attributeId: 1, productId: 6, value: "Small", price: 25.0 }, + { attributeId: 1, productId: 6, value: "Medium", price: 25.0 }, + { attributeId: 1, productId: 6, value: "Large", price: 25.0 }, - { attributeId: 1, productId: 7, value: "S", price: 25.0 }, - { attributeId: 1, productId: 7, value: "M", price: 25.0 }, - { attributeId: 1, productId: 7, value: "L", price: 25.0 }, + { attributeId: 1, productId: 7, value: "Small", price: 25.0 }, + { attributeId: 1, productId: 7, value: "Medium", price: 25.0 }, + { attributeId: 1, productId: 7, value: "Large", price: 25.0 }, - { attributeId: 1, productId: 8, value: "S", price: 15.0 }, - { attributeId: 1, productId: 8, value: "M", price: 15.0 }, - { attributeId: 1, productId: 8, value: "L", price: 15.0 }, + { attributeId: 1, productId: 8, value: "Small", price: 15.0 }, + { attributeId: 1, productId: 8, value: "Medium", price: 15.0 }, + { attributeId: 1, productId: 8, value: "Large", price: 15.0 }, - { attributeId: 1, productId: 9, value: "S", price: 15.0 }, - { attributeId: 1, productId: 9, value: "M", price: 15.0 }, - { attributeId: 1, productId: 9, value: "L", price: 15.0 }, + { attributeId: 1, productId: 9, value: "Small", price: 15.0 }, + { attributeId: 1, productId: 9, value: "Medium", price: 15.0 }, + { attributeId: 1, productId: 9, value: "Large", price: 15.0 }, // --- STICKERS (dimensiones: 3x3, 6x6, 9x9) --- - { attributeId: 2, productId: 10, value: "3x3", price: 2.99 }, - { attributeId: 2, productId: 10, value: "5x5", price: 3.99 }, - { attributeId: 2, productId: 10, value: "10x10", price: 4.99 }, + { attributeId: 2, productId: 10, value: "3x3 cm", price: 2.99 }, + { attributeId: 2, productId: 10, value: "5x5 cm", price: 3.99 }, + { attributeId: 2, productId: 10, value: "10x10 cm", price: 4.99 }, - { attributeId: 2, productId: 11, value: "3x3", price: 2.49 }, - { attributeId: 2, productId: 11, value: "5x5", price: 3.49 }, - { attributeId: 2, productId: 11, value: "10x10", price: 4.49 }, + { attributeId: 2, productId: 11, value: "3x3 cm", price: 2.49 }, + { attributeId: 2, productId: 11, value: "5x5 cm", price: 3.49 }, + { attributeId: 2, productId: 11, value: "10x10 cm", price: 4.49 }, - { attributeId: 2, productId: 12, value: "3x3", price: 3.99 }, - { attributeId: 2, productId: 12, value: "5x5", price: 4.99 }, - { attributeId: 2, productId: 12, value: "10x10", price: 5.99 }, + { attributeId: 2, productId: 12, value: "3x3 cm", price: 3.99 }, + { attributeId: 2, productId: 12, value: "5x5 cm", price: 4.99 }, + { attributeId: 2, productId: 12, value: "10x10 cm", price: 5.99 }, - { attributeId: 2, productId: 13, value: "3x3", price: 2.99 }, - { attributeId: 2, productId: 13, value: "5x5", price: 3.99 }, - { attributeId: 2, productId: 13, value: "10x10", price: 4.99 }, + { attributeId: 2, productId: 13, value: "3x3 cm", price: 2.99 }, + { attributeId: 2, productId: 13, value: "5x5 cm", price: 3.99 }, + { attributeId: 2, productId: 13, value: "10x10 cm", price: 4.99 }, - { attributeId: 2, productId: 14, value: "3x3", price: 2.49 }, - { attributeId: 2, productId: 14, value: "5x5", price: 3.49 }, - { attributeId: 2, productId: 14, value: "10x10", price: 4.49 }, + { attributeId: 2, productId: 14, value: "3x3 cm", price: 2.49 }, + { attributeId: 2, productId: 14, value: "5x5 cm", price: 3.49 }, + { attributeId: 2, productId: 14, value: "10x10 cm", price: 4.49 }, - { attributeId: 2, productId: 15, value: "3x3", price: 2.49 }, - { attributeId: 2, productId: 15, value: "5x5", price: 3.49 }, - { attributeId: 2, productId: 15, value: "10x10", price: 4.49 }, + { attributeId: 2, productId: 15, value: "3x3 cm", price: 2.49 }, + { attributeId: 2, productId: 15, value: "5x5 cm", price: 3.49 }, + { attributeId: 2, productId: 15, value: "10x10 cm", price: 4.49 }, - { attributeId: 2, productId: 16, value: "3x3", price: 2.99 }, - { attributeId: 2, productId: 16, value: "5x5", price: 3.99 }, - { attributeId: 2, productId: 16, value: "10x10", price: 4.99 }, + { attributeId: 2, productId: 16, value: "3x3 cm", price: 2.99 }, + { attributeId: 2, productId: 16, value: "5x5 cm", price: 3.99 }, + { attributeId: 2, productId: 16, value: "10x10 cm", price: 4.99 }, - { attributeId: 2, productId: 17, value: "3x3", price: 2.99 }, - { attributeId: 2, productId: 17, value: "5x5", price: 3.99 }, - { attributeId: 2, productId: 17, value: "10x10", price: .99 }, + { attributeId: 2, productId: 17, value: "3x3 cm", price: 2.99 }, + { attributeId: 2, productId: 17, value: "5x5 cm", price: 3.99 }, + { attributeId: 2, productId: 17, value: "10x10 cm", price: .99 }, // --- TAZAS (no aplica: Único) --- { attributeId: 3, productId: 18, value: "Único", price: 14.99 }, diff --git a/src/models/cart.model.ts b/src/models/cart.model.ts index 53333ce..5128ebd 100644 --- a/src/models/cart.model.ts +++ b/src/models/cart.model.ts @@ -3,6 +3,7 @@ import { type Product } from "./product.model"; import type { Cart as PrismaCart, CartItem as PrismaCartItem, + VariantAttributeValue } from "@/../generated/prisma/client"; export type CartItem = PrismaCartItem & { @@ -10,6 +11,7 @@ export type CartItem = PrismaCartItem & { Product, "id" | "title" | "imgSrc" | "alt" | "price" | "isOnSale" >; + variantAttributeValue?: VariantAttributeValue; }; export type Cart = PrismaCart; @@ -34,6 +36,7 @@ export type CartItemWithProduct = { product: CartProductInfo; quantity: number; attributeValueId: number; + variantAttributeValue?: VariantAttributeValue; }; // Tipo para el carrito con items y productos incluidos diff --git a/src/models/variant-attribute.model.ts b/src/models/variant-attribute.model.ts index 2c91027..acbda1b 100644 --- a/src/models/variant-attribute.model.ts +++ b/src/models/variant-attribute.model.ts @@ -1,2 +1,5 @@ -import type { VariantAttributeValue as PrismaVariantAttributeValue } from "@/../generated/prisma/client"; -export type VariantAttributeValue= PrismaVariantAttributeValue \ No newline at end of file +import type { VariantAttributeValue as PrismaVariantAttributeValue, VariantAttribute } from "@/../generated/prisma/client"; + +export type VariantAttributeValue = PrismaVariantAttributeValue & { + variantAttribute?: VariantAttribute; +}; \ No newline at end of file diff --git a/src/routes/category/components/product-card/index.tsx b/src/routes/category/components/product-card/index.tsx index 23c402b..6526ded 100644 --- a/src/routes/category/components/product-card/index.tsx +++ b/src/routes/category/components/product-card/index.tsx @@ -34,14 +34,19 @@ export function ProductCard({ product }: ProductCardProps) { {isSticker ? (

- Desde + Entre

S/{product.minPrice} - S/{product.maxPrice}

) : ( -

S/{product.price}

+
+

+ Precio +

+

S/{product.price}

+
)} {product.isOnSale && ( diff --git a/src/routes/product/index.tsx b/src/routes/product/index.tsx index 0713b03..b17ab76 100644 --- a/src/routes/product/index.tsx +++ b/src/routes/product/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useState, useEffect } from "react"; import { Form, useNavigation, useSearchParams } from "react-router"; import { Button, Container, Separator } from "@/components/ui"; @@ -8,21 +8,6 @@ import { getProductById } from "@/services/product.service"; import NotFound from "../not-found"; import type { Route } from "./+types"; -const categoryStickerId =3 -const categoryTazaId = 2 -const initialVariantPosition = 1 - -const variantGroupLabel: { [key: number]: string } = { - 3: 'No aplica', - 1: 'Talla', - 2: 'Dimensiones' -} -const displayVariantSize: { [key: string]: string } = { - 'S': 'Small', - 'M': 'Medium', - 'L': 'Large' -} - export async function loader({ params }: Route.LoaderArgs) { try { @@ -34,71 +19,55 @@ export async function loader({ params }: Route.LoaderArgs) { } export default function Product({ loaderData }: Route.ComponentProps) { -const [searchParams] = useSearchParams(); -const initialVariantId = searchParams.get("variantId") ? Number(searchParams.get("variantId")) : null; const { product } = loaderData; - - const getSizeOptions = () => { - const options = product?.variantAttributeValues?.map((variantAttribute, index) => ({ - value: variantAttribute.id, - label: product.categoryId === categoryStickerId ? `${variantAttribute.value} cm` : displayVariantSize[variantAttribute.value], - price: variantAttribute.price, - selected: index===initialVariantPosition?true:false - })); - if (product?.categoryId === categoryTazaId) { - return { - label: '', - options: [{ - value: product?.variantAttributeValues?.[0].id, - label: product?.variantAttributeValues?.[0].value, - price: product?.variantAttributeValues?.[0].price, - selected: true - }] - } - } - return { - label: variantGroupLabel[product?.variantAttributeValues?.[0].attributeId as number], - options: options || [], - } - }; - - const sizeOptions = getSizeOptions(); const navigation = useNavigation(); const cartLoading = navigation.state === "submitting"; - const [data, setData] = useState(sizeOptions) - const showSizeSelector = product?.categoryId === 1 || product?.categoryId === 3; - const selectedDisplay = data.options.find((option) => option.selected === true); - useEffect(() => { - if (initialVariantId) { - setData((prevData) => ({ - ...prevData, - options: prevData.options.map((option) => - option.value === initialVariantId - ? { ...option, selected: true } - : { ...option, selected: false } - ), - })); - } - }, [initialVariantId]); + const [searchParams] = useSearchParams(); + const initialVariantId = searchParams.get("variantId") ? Number(searchParams.get("variantId")) : null; - const onSelectedVariant = (id: number) => { - setData({ - ...data, - options: data.options.map((v) => { - if (v.value === id) { - return ({ - ...v, - selected: true - }) + // Estados para manejar variantes + const [selectedVariant, setSelectedVariant] = useState(initialVariantId); + const [currentPrice, setCurrentPrice] = useState(0); + + // Verificar si el producto tiene variantes + const hasVariants = product?.variantAttributeValues && product.variantAttributeValues.length > 0; + + // Verificar si debe mostrar selectores (solo polos y stickers) + const shouldShowVariants = hasVariants && (product?.categoryId === 1 || product?.categoryId === 3); + + // Agrupar variantes por atributo + const variantGroups = shouldShowVariants + ? product.variantAttributeValues!.reduce((groups, variant) => { + const attributeName = variant!.variantAttribute!.name; + if (!groups[attributeName]) { + groups[attributeName] = []; } - return ({ - ...v, - selected: false - }) - }) - }) - } + groups[attributeName].push(variant); + return groups; + }, {} as Record) + : {}; + + useEffect(() => { + if (!product) return; + + if (hasVariants && product.variantAttributeValues) { + const firstVariant = product.variantAttributeValues[selectedVariant ? product.variantAttributeValues.findIndex(v => v.id === selectedVariant) : 0]; + setSelectedVariant(firstVariant.id); + setCurrentPrice(Number(firstVariant.price)); + } else { + setCurrentPrice(Number(product.price || 0)); + } + }, [product, hasVariants, selectedVariant]); + + // Funciones después de los hooks + const handleVariantChange = (variantId: number) => { + setSelectedVariant(variantId); + const variant = product?.variantAttributeValues?.find(v => v.id === variantId); + if (variant) { + setCurrentPrice(Number(variant.price)); + } + }; if (!product) { return ; @@ -118,71 +87,75 @@ const initialVariantId = searchParams.get("variantId") ? Number(searchParams.get

{product.title} - {showSizeSelector && ( - - {" "} - ( - { - data.options.find( - (option) => option.selected === true - )?.label - } - ) - - )}

-

S/ {selectedDisplay?.price}

+ + {/* Precio dinámico */} +

+ S/{currentPrice.toFixed(2)} +

+

{product.description}

- {showSizeSelector && ( -
-

- {sizeOptions.label} -

-
- {sizeOptions.options.map((option) => ( - - ))} -
-
+ {/* Selectores de variantes dinámicos - solo para polos y stickers */} + {shouldShowVariants && ( + <> + {Object.entries(variantGroups).map(([attributeName, variants]) => ( +
+

+ {attributeName.charAt(0).toUpperCase() + attributeName.slice(1)} +

+
+ {variants!.map((variant) => ( + + ))} +
+
+ ))} + )} + {/* Formulario actualizado para enviar variante seleccionada */}
+ {/* Enviar la variante seleccionada si existe y debe mostrar variantes */} + {shouldShowVariants && selectedVariant && ( + + )}
- + - +

Características @@ -198,4 +171,4 @@ const initialVariantId = searchParams.get("variantId") ? Number(searchParams.get ); -} +} \ No newline at end of file diff --git a/src/services/cart.service.ts b/src/services/cart.service.ts index 417bcbb..a63a665 100644 --- a/src/services/cart.service.ts +++ b/src/services/cart.service.ts @@ -45,6 +45,8 @@ async function getCart( }, }); + console.log("DATA CART SERVICE", data?.items); + if (!data) return null; return { diff --git a/src/services/product.service.ts b/src/services/product.service.ts index d4434a4..bc58616 100644 --- a/src/services/product.service.ts +++ b/src/services/product.service.ts @@ -5,23 +5,25 @@ import type { VariantAttributeValue } from "@/models/variant-attribute.model"; import { getCategoryBySlug } from "./category.service"; -const formattedProduct = (product:any ): ProductVariantValue => { - const {variantAttributeValues, ...rest} = product - const prices = variantAttributeValues.map((v: VariantAttributeValue) => v.price.toNumber()) - const minPrice = Math.min(...prices) - const maxPrice = Math.max(...prices) - if (minPrice === maxPrice) { - return { - ...rest, - price: minPrice - } - } +const formattedProduct = (product: any): ProductVariantValue => { + const { variantAttributeValues, ...rest } = product; + const prices = variantAttributeValues.map((v: VariantAttributeValue) => + v.price.toNumber() + ); + const minPrice = Math.min(...prices); + const maxPrice = Math.max(...prices); + if (minPrice === maxPrice) { return { ...rest, - minPrice, - maxPrice, - } -} + price: minPrice, + }; + } + return { + ...rest, + minPrice, + maxPrice, + }; +}; export async function getProductsByCategorySlug( categorySlug: Category["slug"] @@ -30,39 +32,45 @@ export async function getProductsByCategorySlug( const products = await prisma.product.findMany({ where: { categoryId: category.id }, include: { - variantAttributeValues: true - } + variantAttributeValues: true, + }, }); - return products.map(formattedProduct) + return products.map(formattedProduct); } -export async function getProductById(id: number): Promise { +export async function getProductById(id: number): Promise { const product = await prisma.product.findUnique({ where: { id }, include: { - variantAttributeValues: true - } + variantAttributeValues: { + include: { + variantAttribute: true, + }, + }, + }, }); - if (!product) { throw new Error("Product not found"); } - const variants = product.variantAttributeValues.map((variant)=> ({ - ...variant, - price: variant.price.toNumber() - })) + const productWithParsedPrices = { + ...product, + variantAttributeValues: product.variantAttributeValues.map((variant) => ({ + ...variant, + price: variant.price.toNumber(), + })), + }; -return {...product, variantAttributeValues: variants } + return productWithParsedPrices as unknown as ProductVariantValue; } export async function getAllProducts(): Promise { const products = await prisma.product.findMany({ include: { - variantAttributeValues: true - } + variantAttributeValues: true, + }, }); - return products.map(formattedProduct) + return products.map(formattedProduct); } export async function filterByMinMaxPrice( @@ -97,3 +105,4 @@ export async function filterByMinMaxPrice( return result.map(formattedProduct); } +