From f7c96a0258afed4b94bb6c21dce7aa6d8f49de15 Mon Sep 17 00:00:00 2001 From: PhuocNTB Date: Thu, 9 Oct 2025 08:54:05 +0700 Subject: [PATCH 1/6] temp --- db.json | 8 ++++++++ src/apis/core/cart.api.ts | 9 +++++++++ src/apis/index.ts | 4 +++- src/types/cart.type.ts | 6 ++++++ 4 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 src/apis/core/cart.api.ts create mode 100644 src/types/cart.type.ts diff --git a/db.json b/db.json index 8529753..b76da92 100644 --- a/db.json +++ b/db.json @@ -176,5 +176,13 @@ "des": "

This is the initial content of the editor.

\n

", "isActive": true } + ], + "carts": [ + { + "id": "a", + "userId": "1", + "productId": "aaa", + "quantity": 10 + } ] } \ No newline at end of file diff --git a/src/apis/core/cart.api.ts b/src/apis/core/cart.api.ts new file mode 100644 index 0000000..5fb8134 --- /dev/null +++ b/src/apis/core/cart.api.ts @@ -0,0 +1,9 @@ +import axios from "axios"; + + +export const CartApi = { + getCartByUserId: async (userId: string) => { + let result = await axios.get(`${import.meta.env.VITE_SV_HOST}/carts?userId=${userId}`) + return result.data + } +} \ No newline at end of file diff --git a/src/apis/index.ts b/src/apis/index.ts index 8986f42..a91c4c8 100644 --- a/src/apis/index.ts +++ b/src/apis/index.ts @@ -1,3 +1,4 @@ +import { CartApi } from "./core/cart.api"; import { CategoryApi } from "./core/category.api"; import { CloudianryApi } from "./core/cloudinary.api"; import { ProductApi } from "./core/product.api"; @@ -7,5 +8,6 @@ export const Apis = { user: UserApi, category: CategoryApi, cloundInary: CloudianryApi, - product: ProductApi + product: ProductApi, + cart: CartApi } \ No newline at end of file diff --git a/src/types/cart.type.ts b/src/types/cart.type.ts new file mode 100644 index 0000000..99ce726 --- /dev/null +++ b/src/types/cart.type.ts @@ -0,0 +1,6 @@ +export interface CartItem { + id: string; + userId: string; + productId: string; + quantity: number +} \ No newline at end of file -- 2.43.0 From 4b11ee21804ad91503846255038ded1bf9f9be51 Mon Sep 17 00:00:00 2001 From: PhuocNTB Date: Thu, 9 Oct 2025 09:02:15 +0700 Subject: [PATCH 2/6] temp 3 --- src/App.tsx | 19 +++++++++++++++++-- src/RouterSetup.tsx | 5 ++++- src/pages/home/cart/Cart.tsx | 22 ++++++++++++++++++++++ src/stores/slices/user.slice.ts | 9 +++++++-- 4 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 src/pages/home/cart/Cart.tsx diff --git a/src/App.tsx b/src/App.tsx index e9a6ca8..b1f721c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,12 +1,27 @@ -import { createContext } from 'react' +import { createContext, useEffect } from 'react' import RouterSetup from './RouterSetup' -import { useSelector } from 'react-redux' +import { useDispatch, useSelector } from 'react-redux' import type { StoreType } from './stores' import Loading from './components/Loading' +import { Apis } from './apis' +import { userAction } from './stores/slices/user.slice' export default function App() { const userStore = useSelector((store: StoreType) => store.user) + const dipatch = useDispatch() + useEffect(() => { + if(userStore.data && !userStore.loading) { + try { + Apis.cart.getCartByUserId(userStore.data.id) + .then(res => { + dipatch(userAction.initCartData(res)) + }) + }catch(err) { + + } + } + }, [userStore.data, userStore.loading]) return ( <> { diff --git a/src/RouterSetup.tsx b/src/RouterSetup.tsx index 30796bb..7363b88 100644 --- a/src/RouterSetup.tsx +++ b/src/RouterSetup.tsx @@ -8,10 +8,13 @@ import UserManagement from './pages/admin/User/UserManagement' import CategoryManagement from './pages/admin/Category/CategoryManagement' import ProductManagement from './pages/admin/Product/ProductManagement' import CreateProductForm from './pages/admin/Product/components/CreateProductForm' +import Cart from './pages/home/cart/Cart' export default function RouterSetup() { return - }> + }> + }> + }> diff --git a/src/pages/home/cart/Cart.tsx b/src/pages/home/cart/Cart.tsx new file mode 100644 index 0000000..dc37455 --- /dev/null +++ b/src/pages/home/cart/Cart.tsx @@ -0,0 +1,22 @@ +import React from 'react' +import { useSelector } from 'react-redux' +import type { StoreType } from '../../../stores' +import type { CartItem } from '../../../types/cart.type' + +export default function Cart() { + const userStore = useSelector((store: StoreType) => store.user) + + + return ( +
+ Cart Page +
    + { + userStore.cart?.map((item: CartItem) => { + return
  • {item.productId} - {item.quantity}
  • + }) + } +
+
+ ) +} diff --git a/src/stores/slices/user.slice.ts b/src/stores/slices/user.slice.ts index 892210c..cf0df4c 100644 --- a/src/stores/slices/user.slice.ts +++ b/src/stores/slices/user.slice.ts @@ -1,16 +1,19 @@ import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; import type { User } from "../../types/user.type"; import { Apis } from "../../apis"; +import type { CartItem } from "../../types/cart.type"; interface UserState { data: User | null, loading: boolean; + cart: CartItem[] | [] } const InitUserState: UserState = { data: null, - loading: false + loading: false, + cart: [] } @@ -18,7 +21,9 @@ const userSlice = createSlice({ name: "user", initialState: InitUserState, reducers: { - + initCartData: (state, action) =>{ + state.cart = action.payload + } }, extraReducers: (bd) => { bd.addCase(fetchUserData.pending, (state, action) => { -- 2.43.0 From c746369daa5e05a9aa6bad5fcddb81b854b4b14c Mon Sep 17 00:00:00 2001 From: PhuocNTB Date: Thu, 9 Oct 2025 09:10:27 +0700 Subject: [PATCH 3/6] temp 4 --- src/pages/home/components/Header/Header.tsx | 13 +++++++++++-- src/stores/slices/user.slice.ts | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/pages/home/components/Header/Header.tsx b/src/pages/home/components/Header/Header.tsx index 8234030..4fd4b46 100644 --- a/src/pages/home/components/Header/Header.tsx +++ b/src/pages/home/components/Header/Header.tsx @@ -6,6 +6,7 @@ import logo from '../../../../assets/img/logo.png' import Search from 'antd/es/input/Search' import { useSelector } from 'react-redux' import type { StoreType } from '../../../../stores' +import type { CartItem } from '../../../../types/cart.type' export default function Header() { const navigate = useNavigate() const menu = [ @@ -28,6 +29,12 @@ export default function Header() { const userStore = useSelector((store: StoreType) => { return store.user }) + + function countCartItem() { + return userStore.cart?.reduce((cur: number, next: CartItem) => { + return cur + next.quantity + }, 0) + } return (
@@ -52,9 +59,11 @@ export default function Header() { ) }) } -
+
{ + window.location.href = "/cart" + }} className='item'> -

0

+

{countCartItem()}

Giỏ

Hàng

diff --git a/src/stores/slices/user.slice.ts b/src/stores/slices/user.slice.ts index cf0df4c..c9f0bbc 100644 --- a/src/stores/slices/user.slice.ts +++ b/src/stores/slices/user.slice.ts @@ -7,7 +7,7 @@ import type { CartItem } from "../../types/cart.type"; interface UserState { data: User | null, loading: boolean; - cart: CartItem[] | [] + cart: CartItem[] } const InitUserState: UserState = { -- 2.43.0 From 55e49096c59990ad77d25e1e4a05749487ef204c Mon Sep 17 00:00:00 2001 From: PhuocNTB Date: Thu, 9 Oct 2025 09:40:24 +0700 Subject: [PATCH 4/6] tt --- db.json | 129 +++------------------- src/RouterSetup.tsx | 8 +- src/apis/core/cart.api.ts | 24 ++++ src/apis/core/product.api.ts | 10 +- src/pages/home/HomeContent.tsx | 31 ++++++ src/pages/home/collections/Collection.tsx | 48 ++++++++ src/pages/home/product/ProductDetail.tsx | 75 +++++++++++++ 7 files changed, 207 insertions(+), 118 deletions(-) create mode 100644 src/pages/home/HomeContent.tsx create mode 100644 src/pages/home/collections/Collection.tsx create mode 100644 src/pages/home/product/ProductDetail.tsx diff --git a/db.json b/db.json index b76da92..ccc7a3b 100644 --- a/db.json +++ b/db.json @@ -46,36 +46,6 @@ "title": "danh mục 2", "iconUrl": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTLx0GLJkUHO42P1w4qBVzajal81_-N7oP-6w&s", "isActive": false - }, - { - "id": "cafa", - "title": "Danh mục 3", - "iconUrl": "https://res.cloudinary.com/dusw32tsq/image/upload/v1759887991/gtsjxafutf5nu140skar.png", - "isActive": true - }, - { - "id": "20d1", - "title": "Điện Thoại", - "iconUrl": "https://res.cloudinary.com/dusw32tsq/image/upload/v1759888235/mehgtcrul6kmfp9oklko.png", - "isActive": true - }, - { - "id": "545a", - "title": "Mèo", - "iconUrl": "https://res.cloudinary.com/dusw32tsq/image/upload/v1759888284/ewzw5idmjosvqzk4c2cn.png", - "isActive": true - }, - { - "id": "37f1", - "title": "Mèo 12121", - "iconUrl": "https://res.cloudinary.com/dusw32tsq/image/upload/v1759888337/whofwib0nrquknf4v5b6.png", - "isActive": true - }, - { - "id": "2d29", - "title": "Mèo 12121", - "iconUrl": "https://res.cloudinary.com/dusw32tsq/image/upload/v1759888364/oululvqiwuwpn8se3dww.png", - "isActive": true } ], "products": [ @@ -89,91 +59,12 @@ "des": "asasasa" }, { - "id": "f52d", - "name": "Sản Phẩm Mới", - "price": 10000, - "iconUrl": "https://res.cloudinary.com/dusw32tsq/image/upload/v1759972232/vako1ivmnk8oqfjvxu5e.png", - "categoryId": "37f1", - "des": "" - }, - { - "id": "d33f", - "name": "asdas", - "price": 12312312, - "iconUrl": "https://res.cloudinary.com/dusw32tsq/image/upload/v1759972302/om1v3jypr8kilajpyjrq.png", - "categoryId": "bb", - "des": "" - }, - { - "id": "89fb", - "name": "asdas", - "price": 13212312, - "iconUrl": "https://res.cloudinary.com/dusw32tsq/image/upload/v1759972392/xkighdvy507fbpcuwvft.png", - "categoryId": "20d1", - "des": "", - "isActive": true - }, - { - "id": "293d", - "name": "asdas", - "price": 13212312, - "iconUrl": "https://res.cloudinary.com/dusw32tsq/image/upload/v1759972446/cykgd7rr2biamil4wmbp.png", - "categoryId": "20d1", - "des": "

This is the initial content of the easdasdasditor.

", - "isActive": true - }, - { - "id": "d14d", - "name": "asdas131231", - "price": 13212312, - "iconUrl": "https://res.cloudinary.com/dusw32tsq/image/upload/v1759972577/qhcajdypn0hkbpj8wngn.png", - "categoryId": "37f1", - "des": "

This is the initial content of the easdasdasditor.adasdasdasd

\n

asdasd

\n

asdas

\n

asdas

\n

asda

\n

 

\n

 

\n

", - "isActive": true - }, - { - "id": "0f28", - "name": "Yasdasdassd", - "price": 121212, - "iconUrl": "https://res.cloudinary.com/dusw32tsq/image/upload/v1759972822/vuwgc9qc4ibywllovgfi.png", - "categoryId": "37f1", - "des": "

This is the initial content of the editor.

", - "isActive": true - }, - { - "id": "1d6b", - "name": "Yasdasdassd", - "price": 121212, - "iconUrl": "https://res.cloudinary.com/dusw32tsq/image/upload/v1759972847/tyb4pyhwre1mhwcean5v.png", - "categoryId": "37f1", - "des": "

This is the initial content of the editor.

", - "isActive": true - }, - { - "id": "e718", - "name": "Yasdasdassd", - "price": 121212, - "iconUrl": "https://res.cloudinary.com/dusw32tsq/image/upload/v1759972882/f1ycf1xtlevvo4jwqrwh.png", - "categoryId": "37f1", - "des": "

This is the initial content of the editor.

", - "isActive": true - }, - { - "id": "b4c9", - "name": "assda", - "price": 1231231, - "iconUrl": "https://res.cloudinary.com/dusw32tsq/image/upload/v1759973036/zf8euabhj88wvgqpa1n4.png", - "categoryId": "bb", - "des": "

This is the initial content of the editor.

\n

 

\n

", - "isActive": true - }, - { - "id": "8d17", - "name": "s1231 56786786", - "price": 12321312, - "iconUrl": "https://res.cloudinary.com/dusw32tsq/image/upload/v1759973083/whwafvfupldclzjxn9bs.png", - "categoryId": "545a", - "des": "

This is the initial content of the editor.

\n

", + "id": "d317", + "name": "Sản Phẩm Vip", + "price": 100000, + "iconUrl": "https://res.cloudinary.com/dusw32tsq/image/upload/v1759976982/z7kus1kwxqfahi6bcwwo.png", + "categoryId": "aa", + "des": "
\n

Thông tin sản phẩm

\n
\n
\n
\n
\n

 

\n

Đánh giá chi tiết màn hình Viewsonic VA2432A-H 24\" IPS 120Hz viền mỏng

\n

Với tần số quét 120Hz và tấm nền IPS, màn hình Viewsonic VA2432A-H 24\" là một lựa chọn tuyệt vời cho cả game thủ và những người làm việc đồ họa. Chiếc màn hình này hứa hẹn sẽ đem lại cho bạn những hình ảnh sinh động và mượt mà, hỗ trợ nâng cao năng suất hoạt động.

\n

Hình ảnh sắc nét với tần số quét 120Hz, tốc độ phản hồi 1ms

\n

Tần số quét 120Hz của màn hình ViewSonic VA2432A-H giúp hình ảnh chuyển động mượt mà hơn gấp đôi so với màn hình máy tính 60Hz thông thường. Bạn sẽ không còn bỏ lỡ bất kỳ chi tiết nào trong những pha hành động nhanh của game FPS. Song song đó với thời gian phản hồi 1ms (MPRT) siêu nhanh giúp loại bỏ hiện tượng bóng mờ, mang đến trải nghiệm chơi game mượt mà và không bị giật lag.

\n

\"Màn

\n

Ngoại hình hiện đại, tinh tế với ba cạnh không viền

\n

Màn hình Viewsonic VA2432A-H có thiết kế hiện đại và tối giản với viền màn hình  siêu mỏng, tạo cảm giác màn hình tràn viền, giúp bạn tập trung vào nội dung hiển thị mà không bị phân tán bởi các chi tiết thừa. Phần chân đế thường được làm từ chất liệu nhựa cao cấp mang lại cảm giác chắc chắn và bền bỉ.

\n
\n
\n
", "isActive": true } ], @@ -182,7 +73,13 @@ "id": "a", "userId": "1", "productId": "aaa", - "quantity": 10 + "quantity": 11 + }, + { + "id": "c06c", + "productId": "d317", + "userId": "1", + "quantity": 1 } ] } \ No newline at end of file diff --git a/src/RouterSetup.tsx b/src/RouterSetup.tsx index 7363b88..cb9be1e 100644 --- a/src/RouterSetup.tsx +++ b/src/RouterSetup.tsx @@ -9,11 +9,17 @@ import CategoryManagement from './pages/admin/Category/CategoryManagement' import ProductManagement from './pages/admin/Product/ProductManagement' import CreateProductForm from './pages/admin/Product/components/CreateProductForm' import Cart from './pages/home/cart/Cart' +import HomeContent from './pages/home/HomeContent' +import Collection from './pages/home/collections/Collection' +import ProductDetail from './pages/home/product/ProductDetail' export default function RouterSetup() { return }> - }> + }> + }> + }> + }> }> { let result = await axios.get(`${import.meta.env.VITE_SV_HOST}/carts?userId=${userId}`) return result.data + }, + addToCart: async (data: AddToCartDTO) => { + let resultExisted = await axios.get(`${import.meta.env.VITE_SV_HOST}/carts?productId=${data.productId}`) + console.log("resultExisted", resultExisted.data) + if (!resultExisted.data[0]) { + /* thêm mới */ + await axios.post(`${import.meta.env.VITE_SV_HOST}/carts`, { + ...data, + quantity: 1 + }) + + return + } + + /* cập nhật */ + let resultUpdate = await axios.patch(`${import.meta.env.VITE_SV_HOST}/carts/${resultExisted.data[0].id}`, { + quantity: resultExisted.data[0].quantity + 1 + }) + + console.log("resultUpdate", resultUpdate.data) } } \ No newline at end of file diff --git a/src/apis/core/product.api.ts b/src/apis/core/product.api.ts index 254f2fa..0b52e74 100644 --- a/src/apis/core/product.api.ts +++ b/src/apis/core/product.api.ts @@ -19,5 +19,13 @@ export const ProductApi = { isActive: true }) return result.data - } + }, + findListWithCategoryId: async (categoryId: string) => { + let result = await axios.get(`${import.meta.env.VITE_SV_HOST}/products?categoryId=${categoryId}`) + return result.data + }, + findProductById: async (productId: string) => { + let result = await axios.get(`${import.meta.env.VITE_SV_HOST}/products/` + productId) + return result.data + }, } \ No newline at end of file diff --git a/src/pages/home/HomeContent.tsx b/src/pages/home/HomeContent.tsx new file mode 100644 index 0000000..e196bee --- /dev/null +++ b/src/pages/home/HomeContent.tsx @@ -0,0 +1,31 @@ +import React, { useEffect, useState } from 'react' +import type { Category } from '../../types/category.type' +import { Apis } from '../../apis' +import { Link } from 'react-router' + +export default function HomeContent() { + const [categories, setCategories] = useState([]) + + useEffect(() => { + Apis.category.getAll() + .then(res => { + setCategories(res) + }) + }, []) + return ( +
+ HomeContent +
    + { + categories.map(item => { + return ( +
  • + {item.title} +
  • + ) + }) + } +
+
+ ) +} diff --git a/src/pages/home/collections/Collection.tsx b/src/pages/home/collections/Collection.tsx new file mode 100644 index 0000000..76176b8 --- /dev/null +++ b/src/pages/home/collections/Collection.tsx @@ -0,0 +1,48 @@ +import React, { useEffect, useState } from 'react' +import { useParams } from 'react-router' +import { Apis } from '../../../apis' +import type { Product } from '../../../types/product.type' + +export default function Collection() { + const { categoryId } = useParams() + const [productList, setProductList] = useState([]) + + + useEffect(() => { + Apis.product.findListWithCategoryId((categoryId)) + .then(res => { + setProductList(res) + }) + }, [categoryId]) + return ( +
+

+ Danh sách sản phẩm (Danh mục: {categoryId}) +

+ +
+ {productList.map((item) => ( +
{ + window.location.href = "/product/" + item.id + }} + key={item.id} + className="border rounded-xl p-3 shadow hover:shadow-lg transition bg-white" + > + {item.name} +

{item.name}

+

Giá: {item.price.toLocaleString()}₫

+

+ {item.isActive ? 'Đang bán' : 'Ngừng bán'} +

+
+ ))} +
+
+ ) +} diff --git a/src/pages/home/product/ProductDetail.tsx b/src/pages/home/product/ProductDetail.tsx new file mode 100644 index 0000000..e575754 --- /dev/null +++ b/src/pages/home/product/ProductDetail.tsx @@ -0,0 +1,75 @@ +import React, { useEffect, useState } from 'react' +import { useParams } from 'react-router' +import { Apis } from '../../../apis' +import type { Product } from '../../../types/product.type' +import { Button } from 'antd' +import { useSelector } from 'react-redux' +import type { StoreType } from '../../../stores' + +export default function ProductDetail() { + const { productId } = useParams() + const [product, setProduct] = useState(null) + const userStore = useSelector((store: StoreType) => store.user) + + useEffect(() => { + if (!productId) return + Apis.product.findProductById(productId) + .then(res => { + setProduct(res) + }) + }, [productId]) + + if (!product) { + return ( +
+
+
+ ) + } + + return ( +
+ +
+ {/* Hình ảnh sản phẩm */} +
+ {product.name} +
+ + {/* Thông tin sản phẩm */} +
+

{product.name}

+

+ {product.price.toLocaleString()} ₫ +

+ + {product.isActive ? 'Đang bán' : 'Ngừng kinh doanh'} + + +
+

Mô tả sản phẩm

+
+
+
+
+
+ ) +} -- 2.43.0 From b237658bb93f7e563362b2540c763ed41e20bc4b Mon Sep 17 00:00:00 2001 From: PhuocNTB Date: Thu, 9 Oct 2025 09:45:37 +0700 Subject: [PATCH 5/6] zzz --- db.json | 2 +- src/apis/core/cart.api.ts | 3 +-- src/pages/home/cart/Cart.tsx | 19 ++++++++++++++++++- src/pages/home/product/ProductDetail.tsx | 20 ++++++++++++-------- 4 files changed, 32 insertions(+), 12 deletions(-) diff --git a/db.json b/db.json index ccc7a3b..f3d29c6 100644 --- a/db.json +++ b/db.json @@ -79,7 +79,7 @@ "id": "c06c", "productId": "d317", "userId": "1", - "quantity": 1 + "quantity": 2 } ] } \ No newline at end of file diff --git a/src/apis/core/cart.api.ts b/src/apis/core/cart.api.ts index 102731f..21b79bb 100644 --- a/src/apis/core/cart.api.ts +++ b/src/apis/core/cart.api.ts @@ -12,7 +12,6 @@ export const CartApi = { }, addToCart: async (data: AddToCartDTO) => { let resultExisted = await axios.get(`${import.meta.env.VITE_SV_HOST}/carts?productId=${data.productId}`) - console.log("resultExisted", resultExisted.data) if (!resultExisted.data[0]) { /* thêm mới */ await axios.post(`${import.meta.env.VITE_SV_HOST}/carts`, { @@ -28,6 +27,6 @@ export const CartApi = { quantity: resultExisted.data[0].quantity + 1 }) - console.log("resultUpdate", resultUpdate.data) + return true } } \ No newline at end of file diff --git a/src/pages/home/cart/Cart.tsx b/src/pages/home/cart/Cart.tsx index dc37455..950a7a8 100644 --- a/src/pages/home/cart/Cart.tsx +++ b/src/pages/home/cart/Cart.tsx @@ -2,11 +2,28 @@ import React from 'react' import { useSelector } from 'react-redux' import type { StoreType } from '../../../stores' import type { CartItem } from '../../../types/cart.type' +import { Apis } from '../../../apis' +import type { Product } from '../../../types/product.type' export default function Cart() { const userStore = useSelector((store: StoreType) => store.user) - + // + //đây là api lấy product id let data = Apis.product.findProductById(productId) + // data có interface là Product + // cho bạn interface product luôn + /* + export interface Product { + id: string; + categoryId: string; + price: number; + iconUrl: string; + isActive: boolean; + name: string; + des: string; +} + + */ return (
Cart Page diff --git a/src/pages/home/product/ProductDetail.tsx b/src/pages/home/product/ProductDetail.tsx index e575754..9b5c34b 100644 --- a/src/pages/home/product/ProductDetail.tsx +++ b/src/pages/home/product/ProductDetail.tsx @@ -29,11 +29,16 @@ export default function ProductDetail() { return (
-
{/* Hình ảnh sản phẩm */} @@ -52,11 +57,10 @@ export default function ProductDetail() { {product.price.toLocaleString()} ₫

{product.isActive ? 'Đang bán' : 'Ngừng kinh doanh'} -- 2.43.0 From 31d25529c566566bf2312448649ba222c223c836 Mon Sep 17 00:00:00 2001 From: PhuocNTB Date: Thu, 9 Oct 2025 10:06:25 +0700 Subject: [PATCH 6/6] tt --- db.json | 2 +- src/App.tsx | 2 +- src/apis/core/cart.api.ts | 6 +- src/pages/home/cart/Cart.tsx | 119 ++++++++++++++++++----- src/pages/home/product/ProductDetail.tsx | 8 +- src/stores/slices/user.slice.ts | 26 ++++- 6 files changed, 127 insertions(+), 36 deletions(-) diff --git a/db.json b/db.json index f3d29c6..1b8bd90 100644 --- a/db.json +++ b/db.json @@ -79,7 +79,7 @@ "id": "c06c", "productId": "d317", "userId": "1", - "quantity": 2 + "quantity": 48 } ] } \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index b1f721c..755225d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -21,7 +21,7 @@ export default function App() { } } - }, [userStore.data, userStore.loading]) + }, [userStore.data, userStore.loading, userStore.reloadCart]) return ( <> { diff --git a/src/apis/core/cart.api.ts b/src/apis/core/cart.api.ts index 21b79bb..1ecd369 100644 --- a/src/apis/core/cart.api.ts +++ b/src/apis/core/cart.api.ts @@ -14,12 +14,12 @@ export const CartApi = { let resultExisted = await axios.get(`${import.meta.env.VITE_SV_HOST}/carts?productId=${data.productId}`) if (!resultExisted.data[0]) { /* 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, quantity: 1 }) - return + return addRes.data } /* cập nhật */ @@ -27,6 +27,6 @@ export const CartApi = { quantity: resultExisted.data[0].quantity + 1 }) - return true + return resultUpdate.data } } \ No newline at end of file diff --git a/src/pages/home/cart/Cart.tsx b/src/pages/home/cart/Cart.tsx index 950a7a8..eea7b23 100644 --- a/src/pages/home/cart/Cart.tsx +++ b/src/pages/home/cart/Cart.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useEffect, useState } from 'react' import { useSelector } from 'react-redux' import type { StoreType } from '../../../stores' import type { CartItem } from '../../../types/cart.type' @@ -6,34 +6,99 @@ import { Apis } from '../../../apis' import type { Product } from '../../../types/product.type' export default function Cart() { - const userStore = useSelector((store: StoreType) => store.user) + const userStore = useSelector((store: StoreType) => store.user) + const [products, setProducts] = useState([]) + + // Lấy thông tin product cho từng item trong giỏ + useEffect(() => { + async function fetchProducts() { + let temsArr: Product[] = [] + for (const item of userStore.cart || []) { + const res = await Apis.product.findProductById(item.productId) + temsArr.push(res) + } + 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 - // - //đây là api lấy product id let data = Apis.product.findProductById(productId) - // data có interface là Product - // cho bạn interface product luôn - /* - export interface Product { - id: string; - categoryId: string; - price: number; - iconUrl: string; - isActive: boolean; - name: string; - des: string; -} - - */ return ( -
- Cart Page -
    - { - userStore.cart?.map((item: CartItem) => { - return
  • {item.productId} - {item.quantity}
  • - }) - } -
+
+

🛒 Giỏ hàng của bạn

+ + {userStore.cart?.length === 0 ? ( +

Giỏ hàng trống.

+ ) : ( +
+
    + {userStore.cart.map((item: CartItem) => { + const product = products.find(pro => pro.id == item.productId) + return ( +
  • + {/* Ảnh sản phẩm */} +
    + {product ? ( + {product.name} + ) : ( +
    + )} +
    + + {/* Thông tin */} +
    +

    + {product ? product.name : 'Đang tải...'} +

    +

    + {product ? product.price.toLocaleString() + ' đ' : ''} +

    +
    + + {/* Số lượng & tổng */} +
    +

    SL: {item.quantity}

    + {product && ( +

    + {(product.price * item.quantity).toLocaleString()} đ +

    + )} +
    +
  • + ) + })} +
+ + {/* Tổng tiền */} +
+

+ Tổng cộng:{' '} + {total.toLocaleString()} đ +

+
+ + {/* Nút hành động */} +
+ +
+
+ )}
) } diff --git a/src/pages/home/product/ProductDetail.tsx b/src/pages/home/product/ProductDetail.tsx index 9b5c34b..8d53c75 100644 --- a/src/pages/home/product/ProductDetail.tsx +++ b/src/pages/home/product/ProductDetail.tsx @@ -3,13 +3,15 @@ import { useParams } from 'react-router' import { Apis } from '../../../apis' import type { Product } from '../../../types/product.type' import { Button } from 'antd' -import { useSelector } from 'react-redux' +import { useDispatch, useSelector } from 'react-redux' import type { StoreType } from '../../../stores' +import { userAction } from '../../../stores/slices/user.slice' export default function ProductDetail() { const { productId } = useParams() const [product, setProduct] = useState(null) const userStore = useSelector((store: StoreType) => store.user) + const dipatch = useDispatch() useEffect(() => { if (!productId) return @@ -35,7 +37,9 @@ export default function ProductDetail() { productId: product.id, userId: userStore.data?.id }) - alert("thành công") + console.log("result", result) + + dipatch(userAction.changeLoad()) } catch (err) { } diff --git a/src/stores/slices/user.slice.ts b/src/stores/slices/user.slice.ts index c9f0bbc..668c23a 100644 --- a/src/stores/slices/user.slice.ts +++ b/src/stores/slices/user.slice.ts @@ -7,13 +7,15 @@ import type { CartItem } from "../../types/cart.type"; interface UserState { data: User | null, loading: boolean; - cart: CartItem[] + cart: CartItem[], + reloadCart: boolean } const InitUserState: UserState = { data: null, loading: false, - cart: [] + cart: [], + reloadCart: false } @@ -23,7 +25,27 @@ const userSlice = createSlice({ reducers: { initCartData: (state, action) =>{ 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) => { bd.addCase(fetchUserData.pending, (state, action) => { -- 2.43.0