This commit is contained in:
2025-10-09 10:06:25 +07:00
parent b237658bb9
commit 31d25529c5
6 changed files with 127 additions and 36 deletions

View File

@@ -79,7 +79,7 @@
"id": "c06c", "id": "c06c",
"productId": "d317", "productId": "d317",
"userId": "1", "userId": "1",
"quantity": 2 "quantity": 48
} }
] ]
} }

View File

@@ -21,7 +21,7 @@ export default function App() {
} }
} }
}, [userStore.data, userStore.loading]) }, [userStore.data, userStore.loading, userStore.reloadCart])
return ( return (
<> <>
{ {

View File

@@ -14,12 +14,12 @@ export const CartApi = {
let resultExisted = await axios.get(`${import.meta.env.VITE_SV_HOST}/carts?productId=${data.productId}`) let resultExisted = await axios.get(`${import.meta.env.VITE_SV_HOST}/carts?productId=${data.productId}`)
if (!resultExisted.data[0]) { if (!resultExisted.data[0]) {
/* thêm mới */ /* thêm mới */
await axios.post(`${import.meta.env.VITE_SV_HOST}/carts`, { let addRes = await axios.post(`${import.meta.env.VITE_SV_HOST}/carts`, {
...data, ...data,
quantity: 1 quantity: 1
}) })
return return addRes.data
} }
/* cập nhật */ /* cập nhật */
@@ -27,6 +27,6 @@ export const CartApi = {
quantity: resultExisted.data[0].quantity + 1 quantity: resultExisted.data[0].quantity + 1
}) })
return true return resultUpdate.data
} }
} }

View File

@@ -1,4 +1,4 @@
import React from 'react' import React, { useEffect, useState } from 'react'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import type { StoreType } from '../../../stores' import type { StoreType } from '../../../stores'
import type { CartItem } from '../../../types/cart.type' import type { CartItem } from '../../../types/cart.type'
@@ -7,33 +7,98 @@ import type { Product } from '../../../types/product.type'
export default function Cart() { export default function Cart() {
const userStore = useSelector((store: StoreType) => store.user) const userStore = useSelector((store: StoreType) => store.user)
const [products, setProducts] = useState<Product[]>([])
// // Lấy thông tin product cho từng item trong giỏ
//đây là api lấy product id let data = Apis.product.findProductById(productId) useEffect(() => {
// data có interface là Product async function fetchProducts() {
// cho bạn interface product luôn let temsArr: Product[] = []
/* for (const item of userStore.cart || []) {
export interface Product { const res = await Apis.product.findProductById(item.productId)
id: string; temsArr.push(res)
categoryId: string;
price: number;
iconUrl: string;
isActive: boolean;
name: string;
des: string;
}
*/
return (
<div>
Cart Page
<ul>
{
userStore.cart?.map((item: CartItem) => {
return <li key={item.id}>{item.productId} - {item.quantity}</li>
})
} }
setProducts(temsArr)
}
fetchProducts()
}, [userStore.cart])
// Tính tổng tiền
const total = userStore.cart?.reduce((sum, item) => {
const p = products[item.productId]
return sum + (p ? p.price * item.quantity : 0)
}, 0) || 0
return (
<div className="max-w-4xl mx-auto p-6">
<h1 className="text-2xl font-bold mb-6 text-center">🛒 Giỏ hàng của bạn</h1>
{userStore.cart?.length === 0 ? (
<p className="text-center text-gray-500">Giỏ hàng trống.</p>
) : (
<div className="bg-white shadow-md rounded-2xl p-4">
<ul className="divide-y divide-gray-200">
{userStore.cart.map((item: CartItem) => {
const product = products.find(pro => pro.id == item.productId)
return (
<li
key={item.id}
className="flex items-center gap-4 py-4"
>
{/* Ảnh sản phẩm */}
<div className="w-16 h-16 flex-shrink-0 overflow-hidden rounded-lg border">
{product ? (
<img
src={product.iconUrl}
alt={product.name}
className="w-full h-full object-cover"
/>
) : (
<div className="w-full h-full bg-gray-200 animate-pulse" />
)}
</div>
{/* Thông tin */}
<div className="flex-1">
<h2 className="font-semibold text-gray-800">
{product ? product.name : 'Đang tải...'}
</h2>
<p className="text-sm text-gray-500">
{product ? product.price.toLocaleString() + ' đ' : ''}
</p>
</div>
{/* Số lượng & tổng */}
<div className="text-right">
<p className="text-sm text-gray-700">SL: {item.quantity}</p>
{product && (
<p className="font-semibold text-gray-800">
{(product.price * item.quantity).toLocaleString()} đ
</p>
)}
</div>
</li>
)
})}
</ul> </ul>
{/* Tổng tiền */}
<div className="mt-6 border-t pt-4 text-right">
<p className="text-lg font-semibold">
Tổng cộng:{' '}
<span className="text-blue-600">{total.toLocaleString()} đ</span>
</p>
</div>
{/* Nút hành động */}
<div className="mt-6 text-right">
<button className="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-xl transition">
Thanh toán
</button>
</div>
</div>
)}
</div> </div>
) )
} }

View File

@@ -3,13 +3,15 @@ import { useParams } from 'react-router'
import { Apis } from '../../../apis' import { Apis } from '../../../apis'
import type { Product } from '../../../types/product.type' import type { Product } from '../../../types/product.type'
import { Button } from 'antd' import { Button } from 'antd'
import { useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import type { StoreType } from '../../../stores' import type { StoreType } from '../../../stores'
import { userAction } from '../../../stores/slices/user.slice'
export default function ProductDetail() { export default function ProductDetail() {
const { productId } = useParams() const { productId } = useParams()
const [product, setProduct] = useState<Product | null>(null) const [product, setProduct] = useState<Product | null>(null)
const userStore = useSelector((store: StoreType) => store.user) const userStore = useSelector((store: StoreType) => store.user)
const dipatch = useDispatch()
useEffect(() => { useEffect(() => {
if (!productId) return if (!productId) return
@@ -35,7 +37,9 @@ export default function ProductDetail() {
productId: product.id, productId: product.id,
userId: userStore.data?.id userId: userStore.data?.id
}) })
alert("thành công") console.log("result", result)
dipatch(userAction.changeLoad())
} catch (err) { } catch (err) {
} }

View File

@@ -7,13 +7,15 @@ import type { CartItem } from "../../types/cart.type";
interface UserState { interface UserState {
data: User | null, data: User | null,
loading: boolean; loading: boolean;
cart: CartItem[] cart: CartItem[],
reloadCart: boolean
} }
const InitUserState: UserState = { const InitUserState: UserState = {
data: null, data: null,
loading: false, loading: false,
cart: [] cart: [],
reloadCart: false
} }
@@ -23,7 +25,27 @@ const userSlice = createSlice({
reducers: { reducers: {
initCartData: (state, action) =>{ initCartData: (state, action) =>{
state.cart = action.payload state.cart = action.payload
},
changeLoad: (state) => {
state.reloadCart = !state.reloadCart
} }
// addToCart: (state, action) => {
// let existedItem = state.cart.find(item => item.productId == action.payload.productId)
// if(existedItem) {
// state.cart = state.cart.map(item => {
// if(item.id == existedItem.id) {
// return {
// ...existedItem,
// quantity: action.payload.quantity
// }
// }
// return item
// })
// }else {
// state.cart.push(action.payload)
// }
// }
}, },
extraReducers: (bd) => { extraReducers: (bd) => {
bd.addCase(fetchUserData.pending, (state, action) => { bd.addCase(fetchUserData.pending, (state, action) => {