Skip to content

Commit e7ad0bf

Browse files
committed
feat: update data models and cart logic to support variant attribute values in orders and cart items
1 parent 13a759a commit e7ad0bf

File tree

9 files changed

+207
-126
lines changed

9 files changed

+207
-126
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
Warnings:
3+
4+
- You are about to drop the column `product_id` on the `order_items` table. All the data in the column will be lost.
5+
- Added the required column `attribute_value_id` to the `order_items` table without a default value. This is not possible if the table is not empty.
6+
7+
*/
8+
-- DropForeignKey
9+
ALTER TABLE "order_items" DROP CONSTRAINT "order_items_product_id_fkey";
10+
11+
-- AlterTable
12+
ALTER TABLE "order_items" DROP COLUMN "product_id",
13+
ADD COLUMN "attribute_value_id" INTEGER NOT NULL;
14+
15+
-- AddForeignKey
16+
ALTER TABLE "order_items" ADD CONSTRAINT "order_items_attribute_value_id_fkey" FOREIGN KEY ("attribute_value_id") REFERENCES "variants_attributes_values"("id") ON DELETE CASCADE ON UPDATE CASCADE;

prisma/schema.prisma

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ model Product {
6464
6565
category Category? @relation(fields: [categoryId], references: [id], onDelete: SetNull)
6666
67-
orderItems OrderItem[]
6867
variantAttributeValues VariantAttributeValue[]
6968
7069
@@map("products")
@@ -93,7 +92,8 @@ model VariantAttributeValue {
9392
variantAttribute VariantAttribute @relation(fields: [attributeId], references: [id])
9493
product Product @relation(fields: [productId], references: [id])
9594
96-
CartItem CartItem[]
95+
CartItem CartItem[]
96+
OrderItem OrderItem[]
9797
9898
@@unique([attributeId, productId, value], name: "unique_attribute_product_value")
9999
@@map("variants_attributes_values")
@@ -152,18 +152,18 @@ model Order {
152152
}
153153

154154
model OrderItem {
155-
id Int @id @default(autoincrement())
156-
orderId Int @map("order_id")
157-
productId Int? @map("product_id")
158-
quantity Int
159-
title String
160-
price Decimal @db.Decimal(10, 2)
161-
imgSrc String? @map("img_src")
162-
createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(0)
163-
updatedAt DateTime @default(now()) @map("updated_at") @db.Timestamp(0)
164-
165-
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
166-
product Product? @relation(fields: [productId], references: [id], onDelete: SetNull)
155+
id Int @id @default(autoincrement())
156+
orderId Int @map("order_id")
157+
attributeValueId Int @map("attribute_value_id")
158+
quantity Int
159+
title String
160+
price Decimal @db.Decimal(10, 2)
161+
imgSrc String? @map("img_src")
162+
createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(0)
163+
updatedAt DateTime @default(now()) @map("updated_at") @db.Timestamp(0)
164+
165+
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
166+
variantAttributeValue VariantAttributeValue @relation(fields: [attributeValueId], references: [id], onDelete: Cascade)
167167
168168
@@map("order_items")
169169
}

src/lib/cart.ts

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { CartItem, CartItemInput } from "@/models/cart.model";
1+
import type { CartItem, CartItemWithProduct } from "@/models/cart.model";
22
// import { type Product } from "@/models/product.model";
33
import { type VariantAttributeValue } from "@/models/variant-attribute.model";
44
import {
@@ -55,18 +55,30 @@ export async function removeFromCart(
5555
}
5656
}
5757

58-
export function calculateTotal(items: CartItem[]): number;
59-
export function calculateTotal(items: CartItemInput[]): number;
58+
// Función para CartItem
59+
function calculateCartItemTotal(items: CartItem[]): number {
60+
return items.reduce((total, item) => {
61+
const price = item.variantAttributeValue ? Number(item.variantAttributeValue.price) || 0 : 0;
62+
return total + (price * item.quantity);
63+
}, 0);
64+
}
6065

61-
export function calculateTotal(items: CartItem[] | CartItemInput[]): number {
66+
// Función para CartItemWithProduct
67+
function calculateCartItemWithProductTotal(items: CartItemWithProduct[]): number {
6268
return items.reduce((total, item) => {
63-
// Type guard to determine which type we're working with
64-
if ("product" in item) {
65-
// CartItem - has a product property
66-
return total + Number(item.product.price) * item.quantity;
67-
} else {
68-
// CartItemInput - has price directly
69-
return total + Number(item.price) * item.quantity;
70-
}
69+
const price = typeof item.product.price === 'number' ? item.product.price : 0;
70+
return total + (price * item.quantity);
7171
}, 0);
7272
}
73+
74+
export function calculateTotal(items: CartItem[]): number;
75+
export function calculateTotal(items: CartItemWithProduct[]): number;
76+
77+
export function calculateTotal(items: CartItem[] | CartItemWithProduct[]): number {
78+
// Verificar si es CartItemWithProduct comprobando la estructura
79+
if (items.length > 0 && 'product' in items[0]) {
80+
return calculateCartItemWithProductTotal(items as CartItemWithProduct[]);
81+
} else {
82+
return calculateCartItemTotal(items as CartItem[]);
83+
}
84+
}

src/models/cart.model.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ export interface CartItemInput {
2525
}
2626

2727
// Tipo para representar un producto simplificado en el carrito
28-
2928
export type CartProductInfo = Pick<
3029
Product,
3130
"id" | "title" | "imgSrc" | "alt" | "price" | "isOnSale"

src/models/order.model.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type {
22
Order as PrismaOrder,
33
OrderItem as PrismaOrderItem,
4+
VariantAttributeValue
45
} from "@/../generated/prisma/client";
56

67
export type OrderDetails = Pick<
@@ -19,6 +20,7 @@ export type OrderDetails = Pick<
1920

2021
export type OrderItem = Omit<PrismaOrderItem, "price"> & {
2122
price: number;
23+
variantAttributeValue?: VariantAttributeValue;
2224
};
2325

2426
export type Order = Omit<PrismaOrder, "totalAmount"> & {
@@ -28,7 +30,7 @@ export type Order = Omit<PrismaOrder, "totalAmount"> & {
2830
};
2931

3032
export interface OrderItemInput {
31-
productId: number;
33+
attributeValueId: number;
3234
quantity: number;
3335
title: string;
3436
price: number;

src/routes/account/orders/index.tsx

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export async function loader({ request }: Route.LoaderArgs) {
1313
try {
1414
const orders = await getOrdersByUser(request);
1515

16+
// console.log("ORDERS", orders[0].items[0].variantAttributeValue?.value);
1617
console.log("ORDERS", orders[0].items);
1718

1819
orders.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
@@ -89,37 +90,43 @@ export default function Orders({ loaderData }: Route.ComponentProps) {
8990
</tr>
9091
</thead>
9192
<tbody className="border-t border-b border-border">
92-
{order.items.map((item) => (
93-
<tr key={item.productId}>
94-
<td className="py-6 pl-6">
95-
<div className="flex items-center gap-2">
96-
<div className="w-16 rounded-xl bg-muted">
97-
<img
98-
src={item.imgSrc || undefined}
99-
alt={item.title}
100-
/>
101-
</div>
102-
<div>
103-
<div className="font-medium text-foreground">
104-
{item.title}
93+
{order.items.map((item) => {
94+
const productTitle = item.variantAttributeValue?.value
95+
? `${item.title} (${item.variantAttributeValue.value})`
96+
: item.title;
97+
98+
return (
99+
<tr key={item.id}>
100+
<td className="py-6 pl-6">
101+
<div className="flex items-center gap-2">
102+
<div className="w-16 rounded-xl bg-muted">
103+
<img
104+
src={item.imgSrc || undefined}
105+
alt={productTitle}
106+
/>
105107
</div>
106-
<div className="mt-1 sm:hidden">
107-
{item.quantity} × S/{item.price.toFixed(2)}
108+
<div>
109+
<div className="font-medium text-foreground">
110+
{productTitle}
111+
</div>
112+
<div className="mt-1 sm:hidden">
113+
{item.quantity} × S/{item.price.toFixed(2)}
114+
</div>
108115
</div>
109116
</div>
110-
</div>
111-
</td>
112-
<td className="py-6 pr-8 text-center hidden sm:table-cell">
113-
S/{item.price.toFixed(2)}
114-
</td>
115-
<td className="py-6 pr-8 text-center hidden sm:table-cell">
116-
{item.quantity}
117-
</td>
118-
<td className="py-6 pr-8 whitespace-nowrap text-center font-medium text-foreground">
119-
S/{(item.price * item.quantity).toFixed(2)}
120-
</td>
121-
</tr>
122-
))}
117+
</td>
118+
<td className="py-6 pr-8 text-center hidden sm:table-cell">
119+
S/{item.price.toFixed(2)}
120+
</td>
121+
<td className="py-6 pr-8 text-center hidden sm:table-cell">
122+
{item.quantity}
123+
</td>
124+
<td className="py-6 pr-8 whitespace-nowrap text-center font-medium text-foreground">
125+
S/{(item.price * item.quantity).toFixed(2)}
126+
</td>
127+
</tr>
128+
);
129+
})}
123130
</tbody>
124131
</table>
125132
</div>

src/routes/cart/index.tsx

Lines changed: 56 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -30,65 +30,71 @@ export default function Cart({ loaderData }: Route.ComponentProps) {
3030
Carrito de compras
3131
</h1>
3232
<div className="border-solid border rounded-xl flex flex-col">
33-
{cart?.items?.map(({ product, quantity, id, variantAttributeValue
34-
}) => (
35-
<div key={variantAttributeValue?.id} className="flex gap-7 p-6 border-b">
36-
<div className="w-20 rounded-xl bg-muted">
37-
<img
38-
src={product.imgSrc}
39-
alt={product.alt || product.title}
40-
className="w-full aspect-[2/3] object-contain"
41-
/>
42-
</div>
43-
<div className="flex grow flex-col justify-between">
44-
<div className="flex gap-4 justify-between items-center">
45-
<h2 className="text-sm">{product.title} ({variantAttributeValue?.value})</h2>
46-
<Form method="post" action="/cart/remove-item">
47-
<Button
48-
size="sm-icon"
49-
variant="outline"
50-
name="itemId"
51-
value={id}
52-
>
53-
<Trash2 />
54-
</Button>
55-
</Form>
33+
{cart?.items?.map(
34+
({ product, quantity, id, variantAttributeValue }) => (
35+
<div
36+
key={variantAttributeValue?.id}
37+
className="flex gap-7 p-6 border-b"
38+
>
39+
<div className="w-20 rounded-xl bg-muted">
40+
<img
41+
src={product.imgSrc}
42+
alt={product.alt || product.title}
43+
className="w-full aspect-[2/3] object-contain"
44+
/>
5645
</div>
57-
<div className="flex flex-col gap-2 md:flex-row md:justify-between md:items-center">
58-
<p className="text-sm font-medium">
59-
${product.price!.toFixed(2)}
60-
</p>
61-
<div className="flex gap-4 items-center">
62-
<Form method="post" action="/cart/add-item">
63-
<input type="hidden" name="quantity" value="-1" />
46+
<div className="flex grow flex-col justify-between">
47+
<div className="flex gap-4 justify-between items-center">
48+
<h2 className="text-sm">
49+
{product.title} ({variantAttributeValue?.value})
50+
</h2>
51+
<Form method="post" action="/cart/remove-item">
6452
<Button
65-
name="attributeValueId"
66-
value={variantAttributeValue?.id}
67-
variant="outline"
6853
size="sm-icon"
69-
disabled={quantity === 1}
70-
>
71-
<Minus />
72-
</Button>
73-
</Form>
74-
<span className="h-8 w-8 flex justify-center items-center border rounded-md py-2 px-4">
75-
{quantity}
76-
</span>
77-
<Form method="post" action="/cart/add-item">
78-
<Button
7954
variant="outline"
80-
size="sm-icon"
81-
name="attributeValueId"
82-
value={variantAttributeValue?.id}
55+
name="itemId"
56+
value={id}
8357
>
84-
<Plus />
58+
<Trash2 />
8559
</Button>
8660
</Form>
8761
</div>
62+
<div className="flex flex-col gap-2 md:flex-row md:justify-between md:items-center">
63+
<p className="text-sm font-medium">
64+
${product.price!.toFixed(2)}
65+
</p>
66+
<div className="flex gap-4 items-center">
67+
<Form method="post" action="/cart/add-item">
68+
<input type="hidden" name="quantity" value="-1" />
69+
<Button
70+
name="attributeValueId"
71+
value={variantAttributeValue?.id}
72+
variant="outline"
73+
size="sm-icon"
74+
disabled={quantity === 1}
75+
>
76+
<Minus />
77+
</Button>
78+
</Form>
79+
<span className="h-8 w-8 flex justify-center items-center border rounded-md py-2 px-4">
80+
{quantity}
81+
</span>
82+
<Form method="post" action="/cart/add-item">
83+
<Button
84+
variant="outline"
85+
size="sm-icon"
86+
name="attributeValueId"
87+
value={variantAttributeValue?.id}
88+
>
89+
<Plus />
90+
</Button>
91+
</Form>
92+
</div>
93+
</div>
8894
</div>
8995
</div>
90-
</div>
91-
))}
96+
)
97+
)}
9298
<div className="flex justify-between p-6 text-base font-medium border-b">
9399
<p>Total</p>
94100
<p>S/{total.toFixed(2)}</p>
@@ -107,3 +113,4 @@ export default function Cart({ loaderData }: Route.ComponentProps) {
107113
</Section>
108114
);
109115
}
116+

src/routes/checkout/index.tsx

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
type CulqiInstance,
2020
} from "@/hooks/use-culqui";
2121
import { calculateTotal, getCart } from "@/lib/cart";
22-
import { type CartItem } from "@/models/cart.model";
22+
import { type CartItem, type CartItemWithProduct } from "@/models/cart.model";
2323
import { getCurrentUser } from "@/services/auth.service";
2424
import { deleteRemoteCart } from "@/services/cart.service";
2525
import { createOrder } from "@/services/order.service";
@@ -103,12 +103,18 @@ export async function action({ request }: Route.ActionArgs) {
103103

104104
const chargeData = await response.json();
105105

106-
const items = cartItems.map((item) => ({
107-
productId: item.product.id,
106+
const items: CartItemWithProduct[] = cartItems.map((item) => ({
107+
product: {
108+
id: item.product.id,
109+
title: item.product.title,
110+
imgSrc: item.product.imgSrc,
111+
alt: item.product.alt,
112+
price: item.product.price ?? 0,
113+
isOnSale: item.product.isOnSale,
114+
},
108115
quantity: item.quantity,
109-
title: item.product.title,
110-
price: item.product.price,
111-
imgSrc: item.product.imgSrc,
116+
attributeValueId: item.attributeValueId,
117+
variantAttributeValue: item.variantAttributeValue,
112118
}));
113119

114120
const { id: orderId } = await createOrder(

0 commit comments

Comments
 (0)