diff --git a/src/app/[slug]/page.tsx b/src/app/[slug]/page.tsx index 4b1eb41..fef9db2 100644 --- a/src/app/[slug]/page.tsx +++ b/src/app/[slug]/page.tsx @@ -1,7 +1,7 @@ 'use client'; import { useParams } from 'next/navigation'; -import CategoryPage from '@/components/product/Category'; -import ProductDetailPage from '@/components/product/ProductDetail'; +import CategoryPage from '@/components/Product/Category'; +import ProductDetailPage from '@/components/Product/ProductDetail'; import { resolvePageType } from '@/lib/resolvePageType'; @@ -14,7 +14,7 @@ export default function DynamicPage() { switch (pageType) { case 'category': return ; - case 'product': + case 'product-detail': return ; default: return
404 Không tìm thấy
; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index cac05e5..da438be 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -5,10 +5,10 @@ import 'swiper/css'; import 'swiper/css/navigation'; import 'swiper/css/pagination'; import '@styles/globals.css'; -import Header from '@/components/other/Header'; -import Footer from '@/components/other/Footer'; +import Header from '@/components/Other/Header'; +import Footer from '@/components/Other/Footer'; -import PreLoader from '@components/common/PreLoader'; +import PreLoader from '@/components/Common/PreLoader'; export default function RootLayout({ children, diff --git a/src/app/page.tsx b/src/app/page.tsx index 1622776..2f98f95 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import Home from '@/components/home'; +import Home from '@/components/Home'; import { Metadata } from 'next'; export const metadata: Metadata = { diff --git a/src/components/home/Deal/CounDown/index.tsx b/src/components/common/CounDown/index.tsx similarity index 84% rename from src/components/home/Deal/CounDown/index.tsx rename to src/components/common/CounDown/index.tsx index e3aa8c6..c711c77 100644 --- a/src/components/home/Deal/CounDown/index.tsx +++ b/src/components/common/CounDown/index.tsx @@ -1,16 +1,23 @@ 'use client'; import React, { useState, useEffect } from 'react'; -const CounDown: React.FC = () => { +interface CountDownProps { + deadline: Date | string; +} + +const CounDown: React.FC = ({ deadline }) => { const [days, setDays] = useState(0); const [hours, setHours] = useState(0); const [minutes, setMinutes] = useState(0); const [seconds, setSeconds] = useState(0); - const deadline: Date = new globalThis.Date('2025-12-31'); - const getTime = () => { - const time = deadline.getTime() - Date.now(); + let time: number; + if (deadline instanceof Date) { + time = deadline.getTime() - Date.now(); + } else { + time = Number(deadline) * 1000 - Date.now(); + } setDays(Math.floor(time / (1000 * 60 * 60 * 24))); setHours(Math.floor((time / (1000 * 60 * 60)) % 24)); diff --git a/src/components/home/Category/index.tsx b/src/components/home/Category/index.tsx index 466d61b..f04f85f 100644 --- a/src/components/home/Category/index.tsx +++ b/src/components/home/Category/index.tsx @@ -4,11 +4,11 @@ import Link from 'next/link'; import { FaCaretDown } from 'react-icons/fa'; import { Swiper, SwiperSlide } from 'swiper/react'; import { Autoplay, Navigation, Pagination } from 'swiper/modules'; -import ItemProduct from '@/components/common/ItemProduct'; +import ItemProduct from '@/components/Common/ItemProduct'; import { InfoCategory } from '@/types'; -import { menuData } from '@/components/other/Header/menuData'; +import { menuData } from '@/components/Other/Header/menuData'; import { productData } from './productData'; const BoxListCategory: React.FC = () => { diff --git a/src/components/home/CategoryFeature/index.tsx b/src/components/home/CategoryFeature/index.tsx index 4eb47cd..b2da803 100644 --- a/src/components/home/CategoryFeature/index.tsx +++ b/src/components/home/CategoryFeature/index.tsx @@ -1,6 +1,6 @@ 'use client'; import React from 'react'; -import { menuData } from '../../other/Header/menuData'; +import { menuData } from '../../Other/Header/menuData'; import ItemCategory from './ItemCategory'; import { InfoCategory } from '@/types'; diff --git a/src/components/home/Deal/index.tsx b/src/components/home/Deal/index.tsx index 295b9a5..e8244f0 100644 --- a/src/components/home/Deal/index.tsx +++ b/src/components/home/Deal/index.tsx @@ -7,7 +7,7 @@ import { FaCaretRight } from 'react-icons/fa'; import { productDealData } from './productDealData'; -import CounDown from './CounDown'; +import CounDown from '../../Common/CounDown'; import ProductItem from './ProductItem'; const BoxProductDeal: React.FC = () => { @@ -19,7 +19,7 @@ const BoxProductDeal: React.FC = () => {

Giá tốt mỗi ngày

Kết thúc sau
- +
diff --git a/src/components/other/Header/HeaderMid/index.tsx b/src/components/other/Header/HeaderMid/index.tsx index b20f13d..3a9d1e0 100644 --- a/src/components/other/Header/HeaderMid/index.tsx +++ b/src/components/other/Header/HeaderMid/index.tsx @@ -3,7 +3,7 @@ import React, { useState } from 'react'; import Image from 'next/image'; import Link from 'next/link'; import { FaMapMarkerAlt, FaBars } from 'react-icons/fa'; -import BoxShowroom from '@components/common/BoxShowroom'; +import BoxShowroom from '@/components/Common/BoxShowroom'; const HeaderMid: React.FC = () => { const PopupAddress = () => { diff --git a/src/components/product/Category/index.tsx b/src/components/product/Category/index.tsx index 40203ff..11d59a4 100644 --- a/src/components/product/Category/index.tsx +++ b/src/components/product/Category/index.tsx @@ -6,12 +6,12 @@ import { productCategoryData } from '@/data/product/category'; import { findCategoryBySlug } from '@/lib/product/category'; // box -import { Breadcrumb } from '@components/common/Breadcrumb'; +import { Breadcrumb } from '@/components/Common/Breadcrumb'; import BannerCategory from './BannerCategory'; import ItemCategoryChild from './ItemCategoryChild'; import BoxFilter from './BoxFilter'; import BoxSort from './BoxSort'; -import ItemProduct from '@/components/common/ItemProduct'; +import ItemProduct from '@/components/Common/ItemProduct'; interface CategoryPageProps { slug: string; // khai báo prop slug diff --git a/src/components/product/ProductDetail/BoxInfoRight/BoxBought/index.tsx b/src/components/product/ProductDetail/BoxInfoRight/BoxBought/index.tsx new file mode 100644 index 0000000..b04be1b --- /dev/null +++ b/src/components/product/ProductDetail/BoxInfoRight/BoxBought/index.tsx @@ -0,0 +1,86 @@ +import { FaCheckSquare } from 'react-icons/fa'; +import { Swiper, SwiperSlide } from 'swiper/react'; +import { Autoplay, Navigation, Pagination, Thumbs } from 'swiper/modules'; + +export const BoxBought = () => { + return ( +
+ + + + + + +
+ + +
+

+ Khách hàng Anh Tuấn (036 856 xxxx) +

+

Đã mua hàng 2 giờ trước

+
+
+ +
+

+ Khách hàng Quốc Trung (035 348 xxxx) +

+

Đã mua hàng 1 giờ trước

+
+
+ +
+

+ Khách hàng Quang Ngọc (097 478 xxxx) +

+

Đã mua hàng 30 phút trước

+
+
+ +
+

+ Khách hàng Mạnh Lực (037 204 xxxx) +

+

Đã mua hàng 25 phút trước

+
+
+ +
+

+ Khách hàng Hiếu (096 859 xxxx) +

+

Đã mua hàng 20 phút trước

+
+
+
+
+
+ ); +}; diff --git a/src/components/product/ProductDetail/BoxInfoRight/BoxPrice/index.tsx b/src/components/product/ProductDetail/BoxInfoRight/BoxPrice/index.tsx index d6ce7f9..9ecbabf 100644 --- a/src/components/product/ProductDetail/BoxInfoRight/BoxPrice/index.tsx +++ b/src/components/product/ProductDetail/BoxInfoRight/BoxPrice/index.tsx @@ -1,4 +1,6 @@ import type { ProductDetailData } from '@/types'; +import CounDown from '@/components/Common/CounDown'; +import { formatCurrency } from '@/lib/formatPrice'; export const BoxPrice = (item: ProductDetailData) => { return ( @@ -9,10 +11,70 @@ export const BoxPrice = (item: ProductDetailData) => {

flash sale

-
-
-
+
+
+
+
+

+ Còn{' '} + {(() => { + const deal = item.product_info.deal_list[0]; + return Number(deal.quantity) - deal.sale_order; + })()} + /{item.product_info.deal_list[0].quantity} sản phẩm +

+ +
+ +
+ {(() => { + const deal = item.product_info.deal_list[0]; + const percentRemaining = + ((Number(deal.quantity) - deal.sale_order) / Number(deal.quantity)) * 100; + + return ( + <> +

+ + ); + })()} +
+
+
+
+ )} + {/* giá */} + + {item.product_info.marketPrice > '0' && item.product_info.sale_rules.type == 'deal' && ( +
+

+ {item.product_info.price !== '0' + ? `${formatCurrency(item.product_info.price)}đ` + : 'Liên hệ'} +

+ {item.product_info.marketPrice > '0' && ( + <> + + {formatCurrency(item.product_info.marketPrice)}₫ + +
+ Tiết kiệm + {(() => { + return formatCurrency( + Number(item.product_info.marketPrice) - Number(item.product_info.price), + ); + })()} + đ +
+ + )}
)} diff --git a/src/components/product/ProductDetail/BoxInfoRight/TimeDeal/index.tsx b/src/components/product/ProductDetail/BoxInfoRight/TimeDeal/index.tsx deleted file mode 100644 index e69de29..0000000 diff --git a/src/components/product/ProductDetail/BoxInfoRight/index.tsx b/src/components/product/ProductDetail/BoxInfoRight/index.tsx index 7eedc6f..52df2d5 100644 --- a/src/components/product/ProductDetail/BoxInfoRight/index.tsx +++ b/src/components/product/ProductDetail/BoxInfoRight/index.tsx @@ -2,11 +2,14 @@ import type { ProductDetailData } from '@/types'; import Link from 'next/link'; import { BoxPrice } from './BoxPrice'; +import { BoxBought } from './BoxBought'; export const BoxInfoRight = (item: ProductDetailData) => { return ( <> -

{item.product_info.productName}

+

+ {item.product_info.productName} +

@@ -125,6 +128,31 @@ export const BoxInfoRight = (item: ProductDetailData) => {
)} + + {/* yên tâm mua hàng */} +
+

Yên tâm mua hàng

+
+
+ +

Cam kết giá tốt nhất thị trường.

+
+
+ +

Sản phẩm mới 100%.

+
+
+ +

Lỗi 1 đổi 1 ngay lập tức.

+
+
+ +

Hỗ trợ trả góp - Thủ tục nhanh gọn.

+
+
+
+ + ); }; diff --git a/src/components/product/ProductDetail/index.tsx b/src/components/product/ProductDetail/index.tsx index 2deee7b..64da303 100644 --- a/src/components/product/ProductDetail/index.tsx +++ b/src/components/product/ProductDetail/index.tsx @@ -4,9 +4,9 @@ import Link from 'next/link'; import type { ProductDetailData } from '@/types'; import { productDetailData } from '@/data/product/detail'; import { findProductDetailBySlug } from '@/lib/product/productdetail'; -import { ErrorLink } from '@components/common/error'; +import { ErrorLink } from '@/components/Common/error'; -import { Breadcrumb } from '@components/common/Breadcrumb'; +import { Breadcrumb } from '@/components/Common/Breadcrumb'; import { ImageProduct } from './ImageProduct'; import { ProductSummary } from './ProductSummary'; import { ComboSetBox } from './ComboSet'; diff --git a/src/data/product/detail/index.ts b/src/data/product/detail/index.ts index 7627f72..c56ffd1 100644 --- a/src/data/product/detail/index.ts +++ b/src/data/product/detail/index.ts @@ -320,7 +320,7 @@ export const productDetailData = [ max_purchase: '0', remain_quantity: '1', from_time: '1766106000', - to_time: '1766716200', + to_time: '1766975400', type: 'deal', type_id: '565', }, @@ -388,6 +388,8 @@ export const productDetailData = [ from_time: '1766106000', to_time: '1766716200', is_started: '1', + sale_order: 2, + sale_quantity: 2, }, ], pricing_traces: [ @@ -1351,9 +1353,9 @@ export const productDetailData = [ min_purchase: '1', max_purchase: '1', remain_quantity: '1', - from_time: '0', - to_time: '0', - type: '', + from_time: '19-12-2025, 8:00 am', + to_time: '29-12-2025, 9:30 am', + type: 'deal', }, categoryInfo: [ { diff --git a/src/lib/resolvePageType.ts b/src/lib/resolvePageType.ts index ac0a8fc..ee3cacd 100644 --- a/src/lib/resolvePageType.ts +++ b/src/lib/resolvePageType.ts @@ -8,7 +8,7 @@ export function resolvePageType(slug: string) { } // kiểm tra sản phẩm if (productDetailData.some((c) => c.product_info.productUrl == slug)) { - return 'product'; + return 'product-detail'; } return '404'; } diff --git a/src/styles/globals.css b/src/styles/globals.css index ef7daaf..73b4ce3 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -2236,6 +2236,7 @@ textarea::placeholder { justify-content: center; font-weight: 600; } + .btn-product, .page-hompage .box-product-deal .btn-deal { color: #105fbd; @@ -3284,15 +3285,22 @@ textarea::placeholder { border-top: 64px solid transparent; border-right: 30px solid #fff; } -.page-product-detail .box-content-product-detail .box-flash-sale .item-time b { - border-radius: 5px; +.page-product-detail .box-content-product-detail .box-flash-sale .global-time-deal p { background: #001644; - padding: 5px; - font-size: 16px; color: #fff; - margin-bottom: 4px; - display: block; + border-radius: 3px; + justify-content: center; + align-items: center; + width: 30px; + height: 28px; + font-size: 16px; + font-weight: 600; + display: flex; } +.page-product-detail .global-time-deal .flex.items-center.gap-2 { + gap: calc(var(--spacing) * 1); +} + .page-product-detail .box-content-product-detail .box-flash-sale .box-middle span { margin-top: 4px; } @@ -6016,3 +6024,30 @@ textarea::placeholder { background-size: 100% 100%; z-index: 1; } + +.pro-customer-bought { + position: relative; + padding-left: 30px; + margin: 8px 0; +} +.pro-customer-bought .pcb-icon { + position: absolute; + top: 4px; + left: 0; +} +.pro-customer-bought .pcb-slider { + font-size: 14px; + line-height: 24px; +} +.pro-customer-bought .pcb-slider .swiper-slide p:first-child { + font-size: 15px; +} + +@media (max-width: 768px) { + .pro-customer-bought .pcb-slider { + font-size: 13px; + } + .pro-customer-bought .pcb-slider .swiper-slide p:first-child { + font-size: 14px; + } +} diff --git a/src/types/product/detail/index.ts b/src/types/product/detail/index.ts index 256a4a0..4105278 100644 --- a/src/types/product/detail/index.ts +++ b/src/types/product/detail/index.ts @@ -88,6 +88,8 @@ interface Deal { from_time: string; to_time: string; is_started: string; + sale_order: number; + sale_quantity: number; } // Pricing Trace diff --git a/tsconfig.json b/tsconfig.json index e8e3e3e..a5ff0c3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,7 +23,7 @@ "@components/*": ["./src/components/*"], "@types/*": ["./src/types/*"], "@styles/*": ["./src/styles/*"], - "@Common/*": ["./src/components/Common/*"] + "@Common/*": ["src/components/Common/*"] } }, "include": [