12/02/2026

This commit is contained in:
2026-02-12 17:14:04 +07:00
parent 2bc93383a0
commit 756cf6410c
35 changed files with 784 additions and 299 deletions

View File

@@ -0,0 +1,12 @@
import BuildPc from "@/components/buildpc"
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Xây Dựng Cấu Hình PC, Build PC Chuẩn Nhất✔Giá Rẻ",
description: "Xây dựng cấu hình máy tính PC chuyên nghiệp ✳️ máy tính đồ họa, máy tính làm việc giá rẻ ✳️ Build PC.",
};
export default function Home() {
return(
<BuildPc />
)
}

View File

@@ -0,0 +1,13 @@
import DealPage from "@/components/deal"
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Flash Sale Mỗi Ngày Cực Sốc - Hoàng Hà PC ",
description: "Flash Sale Mỗi Ngày Cực Sốc - Hoàng Hà PC",
};
export default function Home() {
return(
<DealPage />
)
}

View File

@@ -1,12 +1,26 @@
import { findBySlug } from "@/lib/slug/slugMap";
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import LayoutTypeSetter from "@/components/layout/LayoutTypeSetter"
import { findBySlug } from "@/lib/slug/slugMap";
import { metadataBySlug } from "@/app/[slug]/metadataBySlug";
import { SLUG_CONFIG } from "@/app/[slug]/slugConfig";
import { renderBySlug } from "@/app/[slug]/renderBySlug";
import LayoutTypeSetter from "@/components/layout/LayoutTypeSetter"
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}): Promise<Metadata> {
const { slug } = await params;
const result = await findBySlug(slug);
return metadataBySlug(result);
}
export default async function SlugPage({ params }: { params: { slug: string } }) {
console.log("designer-tool layout check");
const { slug } = await params;
const result = await findBySlug(slug);
if (!result) notFound();

View File

@@ -0,0 +1,37 @@
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { findBySlug } from "@/lib/slug/slugMap";
import { metadataBySlug } from "@/app/[slug]/metadataBySlug";
import { SLUG_CONFIG } from "@/app/[slug]/slugConfig";
import { renderBySlug } from "@/app/[slug]/renderBySlug";
import LayoutTypeSetter from "@/components/layout/LayoutTypeSetter"
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}): Promise<Metadata> {
const { slug } = await params;
const result = await findBySlug(slug);
return metadataBySlug(result);
}
export default async function SlugPage({ params }: { params: { slug: string } }) {
const { slug } = await params;
const result = await findBySlug(slug);
if (!result) notFound();
const config = SLUG_CONFIG[result.type];
if (!config) notFound();
return (
<LayoutTypeSetter layout="main">
{renderBySlug(result)}
</LayoutTypeSetter>
);
}

View File

@@ -0,0 +1,13 @@
import ProductSearch from "@/components/search"
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Danh sách tìm kiếm ",
description: "Danh sách kết quả thỏa mãn",
};
export default function Search() {
return(
<ProductSearch />
)
}

View File

@@ -1,16 +1,13 @@
import type { ReactNode } from 'react';
import LayoutTypeSetter from "@/components/layout/LayoutTypeSetter";
import StaticStyleLoader from "@/components/layout/StaticStyleLoader";
// import '@/styles/static_page.css';
// import '@/styles/tuyen_dung.css';
import '@/styles/static_page.css';
import '@/styles/tuyen_dung.css';
export default function StaticLayout({ children }: { children: ReactNode }) {
return (
<>
<StaticStyleLoader />
<LayoutTypeSetter layout="static">
{children}
</LayoutTypeSetter>

View File

@@ -69,6 +69,15 @@ export function metadataBySlug(result: any): Metadata {
}
};
case "designer_detail":
return {
title: result.data.title,
description: result.data.meta_description,
openGraph: {
type: 'website',
}
};
default:
return {

View File

@@ -1,4 +1,3 @@
import { cache } from "react";
import { renderBySlug } from "./renderBySlug";
import { metadataBySlug } from "./metadataBySlug";
import { findBySlug } from "@/lib/slug/slugMap";
@@ -15,7 +14,7 @@ export async function generateMetadata({
}): Promise<Metadata> {
const { slug } = await params;
const result = await findBySlug(slug);
if (!result) return { title: "Local PC" };
return metadataBySlug(result);
}

View File

@@ -266,13 +266,13 @@ export default function Info() {
<img src="/images/static-about-us-pic-4.png" alt="customer-image" />
</div>
<ul className="ul customer-list text-20">
<li><span> Doanh nghiệp nhà nước </span></li>
<li><span> Tập đoàn lớn </span></li>
<li><span> Doanh nghiệp nhân vừa nhỏ </span></li>
<li><span> Tổ chức phi chính phủ </span></li>
<li><span> Doanh nghiệp vốn đu nước ngoài </span></li>
<li><span> Trường học, bệnh viện </span></li>
<li><span> Team Youtube, MMO </span></li>
<li><span/> Doanh nghiệp nhà nước </li>
<li><span/> Tập đoàn lớn </li>
<li><span/> Doanh nghiệp nhân vừa nhỏ </li>
<li><span/> Tổ chức phi chính phủ </li>
<li><span/> Doanh nghiệp vốn đu nước ngoài </li>
<li><span/> Trường học, bệnh viện </li>
<li><span/> Team Youtube, MMO </li>
</ul>
</div>
</div>

View File

@@ -0,0 +1,3 @@
export default function BuildPc() {
return(<> cpn buildpc </>)
}

View File

@@ -0,0 +1,93 @@
'use client';
import Link from "next/link";
import parse from "html-react-parser";
import { formatPrice } from "@/lib/utils";
import { useDealItem } from "@/hooks/useDealItem"
import { useCart } from '@/hooks/useCart';
import { DealCountdown } from "@/lib/times"
export default function DealItem( {item}: any ) {
const deal = useDealItem(item);
if (!deal) return null;
const { addToCart, isInCart } = useCart();
const {
productInfo,
price,
marketPrice,
discount,
remain,
saleRemainPercent,
specialOffer,
} = deal;
const discountView = discount > 0 ? (<>
<del>{formatPrice(marketPrice)} đ</del>
<span className="deal-discount">-{discount}%</span>
</>
) : null;
const checkIncart = isInCart(productInfo.id);
return(
<div className="deal-item js-p-item js-deal-item p-[10px]">
<Link href={ productInfo.productUrl } className="deal-img">
<img
src={ productInfo.productImage.large }
alt={ productInfo.productName }
width={200}
height={200}
className="fit-img"
/>
</Link>
<div className="deal-text">
<Link href={ productInfo.productUrl } className="deal-name"> { item.title } </Link>
<div className="deal-price-holder">
<div>
<p className="deal-price"> {formatPrice(price)} đ </p>
{discountView}
</div>
<button className={`deal-btn bx ${checkIncart ? 'bx-check' : 'bx-plus' }`}
style={{ background: `${checkIncart ? '#ccc' : ''}` }}
disabled={checkIncart}
type="button" aria-label="Mua"
onClick={() => addToCart(productInfo.id)}
></button>
</div>
<div className="deal-count">
<i className="deal-line" style={{ width: saleRemainPercent + '%' }}></i>
<span>
{ Number(item.sale_quantity) < Number(item.quantity)
? `Còn ${remain}/${item.quantity} sản phẩm`
: 'Hết DEAL'
}
</span>
</div>
<div className="deal-time-container my-2 flex flex-wrap justify-content-between items-center text-13 gap-1">
<p className="m-0"> Kết thúc sau: </p>
<div className="deal-time-holder js-deal-time" data-time-left={item.to_time}>
<DealCountdown endTime={item.to_time} />
</div>
</div>
{specialOffer && (
<div className="deal-offer">
<span className="text-[#BE1F2D]"> Quà tặng: </span>
{parse(specialOffer)}
</div>
)}
</div>
</div>
)
}

View File

@@ -0,0 +1,46 @@
'use client';
import { dealList } from "@/data/deals"
import { useMemo } from "react";
import { usePagination } from "@/hooks/usePagination";
import ButtonShowMore from "@/components/shared/ProductShowMore";
import DealItemType2 from "@/components/deal/DealItemType2"
export default function DealPage() {
const data = useMemo(() => {
return dealList.flatMap((item: any) => item.list);
}, []);
const {
currentData,
hasMore,
loadMore,
total
} = usePagination(data);
return (
<div className="deal-page container">
<div className="text-center mb-4">
<img src="https://hoanghapccdn.com/media/lib/09-07-2024/hoanghapc-deal-hot.jpg" alt="DEAL" width={1600} height={490} className="deal-featured-image" style={{ width: '100%', borderRadius: 12 }} />
</div>
<div className="product-holder grid grid-cols-2 gap-3 lg:grid-cols-5 lg:gap-x-5 lg:gap-y-8 mb-6 relative min-h-[300px]">
{currentData.map((item: any) => (
<DealItemType2 key={item.id} item={item} />
))}
</div>
{hasMore &&
<ButtonShowMore
onClick={loadMore}
displayCount={currentData.length}
total={total}
/>
}
</div>
)
}

View File

@@ -1,37 +0,0 @@
export default function DesignerItem({ item }: any) {
console.log('DesignerItem: ', item)
return (
<>
<div className="p-item">
<a href="{{ _item.productUrl }}" className="p-img">
<img src="{{ _item.productImage.large }}" alt="{{ _item.productName }}" width={250} height={250} />
</a>
<div className="p-text">
<a href="{{ _item.productUrl }}" className="p-name">
<h3> {'{'}{'{'} _item.productName {'}'}{'}'} </h3>
</a>
<div className="p-price-group">
<p className="p-price">
{'{'}% if _item.price &gt; 0 %{'}'} {'{'}{'{'} _item.price | format_price {'}'}{'}'} đ
{'{'}% else %{'}'} Liên hệ
{'{'}% endif %{'}'}
</p>
{'{'}% if _item.price_off &gt; 0 %{'}'}
<del>{'{'}{'{'} _item.marketPrice | format_price {'}'}{'}'} đ</del>
<span className="p-discount">-{'{'}{'{'} _item.price_off {'}'}{'}'}%</span>
{'{'}% endif %{'}'}
</div>
{'{'}% if _item.productSummary %{'}'}
<div className="p-summary">
{'{'}% assign _summary = _item.productSummary | get_line %{'}'}
{'{'}% for _item in _summary | limit: 5 %{'}'}
<div className="item-circle"> {'{'}{'{'} _item {'}'}{'}'} </div>
{'{'}% endfor %{'}'}
</div>
{'{'}% endif %{'}'}
</div>
</div>
</>
)
}

View File

@@ -1,18 +1,41 @@
'use client';
import { usePathname } from 'next/navigation';
import parse from "html-react-parser";
import { useState } from 'react';
import { useSearchParams } from 'next/navigation';
import Link from "next/link";
import FAQ from "./Faq";
import DesignerItem from "@/components/shared/DesignerItem";
import { usePagination } from "@/hooks/usePagination";
import ButtonShowMore from "@/components/shared/ProductShowMore";
export default function Detail({ slug }: any) {
// console.log('DesignerDetail: ', slug)
const title = usePathname().includes('device=laptop') ? 'Laptop' : 'PC';
const deviceTitle = useSearchParams().get('device') == 'laptop' ? 'Laptop' : 'PC';
const ignoreKeys = ["Loại máy", "Loại máy tính"];
const {
attribute_list,
product_list
} = slug
const filteredAttribute = slug.attribute_list.filter(
(item: any) => !ignoreKeys.includes(item.name)
const ignoreKeys = ["loại máy", "loại máy tính"];
const filteredAttribute = attribute_list.filter(
(item: any) =>
!ignoreKeys.map((key: string) =>
key.toLowerCase()).includes(item.name.toLowerCase())
);
const [attribute, setAttribute] = useState(0)
const result = product_list.filter((item: any) =>
attribute === 0 || item.attribute?.includes(attribute)
);
const {
currentData,
hasMore,
loadMore,
total
} = usePagination(result, 24);
return (
<>
@@ -45,40 +68,56 @@ export default function Detail({ slug }: any) {
<div className="container">
<h1 className="text-center text-[#004BA4] font-600 text-24 leading-[30px] lg:text-[32px] lg:leading-10 mb-4">
{title} dành cho {slug.item_info.name}
{deviceTitle} dành cho {slug.item_info.name}
</h1>
{filteredAttribute &&
{filteredAttribute.length > 0 &&
<div className="text-center tool-btn-list mb-4 lg:mb-6">
<div className="inline-flex items-center justify-center p-1 border border-[#DFE4EC] bg-[#F5F8FF] overflow-auto whitespace-nowrap no-scroll gap-1 leading-9 font-500 text-[#5D6776] text-14 rounded-[8px]">
{
filteredAttribute.map((item: any) =>
<button
key={item.id}
className="js-attribute-btn px-9"
data-id="{{ _item.id }}"
data-current="current"
<div className="inline-flex items-center justify-center p-1 border border-[#DFE4EC] bg-[#F5F8FF] overflow-auto whitespace-nowrap no-scroll gap-1 leading-9 font-500 text-[#5D6776] rounded-[8px]">
{filteredAttribute.map((item: any) =>
item.list.map((list: any) =>
<button className={`js-attribute-btn px-9
${list.id === attribute ? 'current' : ""}
`}
key={list.id}
onClick={() => setAttribute(list.id)}
>
{item.name}
{list.name}
</button>
)
)}
</div>
</div>
}
<div className="mb-8 lg:mb-12">
{currentData.length == 0
? parse(` <div class="text-center py-20">
<p class="text-20 font-700">Sản phẩm đang được cập nhật ...!</p>
<a href="/" class="color-main text-18"> Quay lại trang chủ </a>
</div>
`) : (
<div className="tool-product-holder grid grid-cols-2 lg:grid-cols-4 gap-x-3 gap-y-4 lg:gap-x-4 lg:gap-y-6" id="js-product-holder">
{currentData.map((item: any) =>
<DesignerItem
key={item.id}
item={item}
/>
)}
</div>
)
}
<div className="text-center mt-12" id="js-paging-holder">
<button type="button" className="mb-3 bg-btn text-white rounded-[30px] h-10 font-500 text-16 table max-w-[240px] w-full m-auto mb-3" aria-label="Xem thêm"> TẢI THÊM </button>
<p className="text-14 leading-[18px] m-0" id="js-paging-count"> </p>
{hasMore &&
<ButtonShowMore
onClick={loadMore}
displayCount={currentData.length}
total={total}
/>
}
</div>
</div>
</div>
{slug.item_info.description &&
<div className="designer-summary-container bg-white py-8 lg:py-12 px-3">
<div className="js-static-container static-container leading-[135%] m-auto max-w-[924px]">

View File

@@ -1,9 +1,9 @@
'use client';
import Link from "next/link";
import { useEffect, useState, useMemo } from "react";
import { useState, useMemo } from "react";
import { DesignerToolData } from "@/data/designer-tool"
import { convertToSlug } from "@/lib/utils";
import FAQ from "./Faq";
import { convertToSlug } from "@/lib/utils"
export default function DesignerTool() {
@@ -21,13 +21,12 @@ export default function DesignerTool() {
const [ device, setDevice ] = useState("desktop");
useEffect(() => {
document.body.style.background = '#FFFFFF';
console.log('device: ', device)
}, [device]);
return (
<>
<div style={{
background : "#FFFFFF",
marginTop : "-20px",
paddingTop : "20px"
}}>
<div className="global-breadcrumb">
<div className="container">
<ol itemScope itemType="http://schema.org/BreadcrumbList" className="ul clearfix">
@@ -58,10 +57,11 @@ export default function DesignerTool() {
<div className="w-full lg:w-[520px] flex flex-wrap items-center justify-between p-[6px] pl-5 bg-white rounded-[30px]">
<input
type="text"
placeholder="Nhập phần mềm cần tìm"
className="w-[calc(100%_-_36px)] pr-3 placeholder:!text-[#5F5F5F] placeholder:!text-[14px] h-9"
value={keyword}
onChange={(e) => setKeyword(e.target.value)}
placeholder="Nhập phần mềm cần tìm"
className="w-[calc(100%_-_36px)] pr-3 placeholder:!text-[#5F5F5F] placeholder:!text-[14px] h-9" />
/>
<button type="button" aria-label="button" className="bg-linear rounded-full w-9 h-9">
<i className="block !w-full !h-full icons icon-search" />
@@ -96,10 +96,10 @@ export default function DesignerTool() {
<div className="software-list grid grid-cols-4 lg:grid-cols-7 gap-x-[16px] gap-y-[24px] lg:gap-x-[32px] lg:gap-y-[48px] text-center mb-8">
{filteredSoftware.map((item: any) =>
<button
<Link
key={item.id}
data-id={item.url_index}
className="js-software-item item text-12 leading-4 lg:text-[14px] lg:leading-[18px] font-500 hover:text-[#0676DA] flex flex-col"
href={`/designer-tool/${item.url_index}?device=${device}`}
className="item text-12 leading-4 lg:text-[14px] lg:leading-[18px] font-500 hover:text-[#0676DA] flex flex-col"
>
<span className="img block mb-2 lg:mb-3 relative pb-[100%]">
<img src={item.image}
@@ -108,8 +108,9 @@ export default function DesignerTool() {
height={1}
className="block absolute w-full h-full object-cover rounded-[24px]" />
</span>
<span className="text"> {item.name} </span>
</button>
</Link>
)}
</div>
</div>
@@ -128,6 +129,6 @@ export default function DesignerTool() {
</div>
</div>
</div>
</>
</div>
)
}

View File

@@ -1,14 +0,0 @@
// components/layout/StaticStyleLoader.tsx
'use client';
import { useEffect } from 'react';
export default function StaticStyleLoader() {
useEffect(() => {
// Import CSS chỉ khi component được mount
import('@/styles/static_page.css');
import('@/styles/tuyen_dung.css');
}, []);
return null;
}

View File

@@ -66,7 +66,7 @@ export default function Header() {
</div>
</div>
<div className="bg-white shadow-[0px_4px_20px_0px_#004AA11A] text-16 font-500 mb-5">
<div className="bg-white shadow-[0px_4px_20px_0px_#004AA11A] text-16 font-500 mb-5 relative">
<div className="container flex items-center justify-between leading-[20px] py-[17px]">
<Link href="/buildpc" className="flex items-center gap-2 hover:text-[#0678DB]">
<i className="icons icon-buildpc"></i>

View File

@@ -6,13 +6,13 @@ export default function otherHeader() {
<div>
<div className="global-header-container global-another-header">
<div className="container d-flex align-items-center justify-content-between position-relative">
<Link href="/" className="logo-header">
<a href="/" className="logo-header">
<img src="/images/logo.png"
alt="HoangHaPc"
width={247}
height={96}
className="d-block h-auto w-auto m-auto" />
</Link>
</a>
<div className="another-header-right">
<Link href="/he-thong-cua-hang" className="header-item font-700"> Hệ thống Showroom </Link>

View File

@@ -1,40 +1,47 @@
'use client';
import { useState } from "react";
import parse from "html-react-parser";
import { productList } from "@/data/products/productList";
import ProductItem from "@/components/shared/ProductItem";
const PRODUCT_PER_PAGE = 30;
import { usePagination } from "@/hooks/usePagination";
import ButtonShowMore from "@/components/shared/ProductShowMore";
export default function ProductList( {id} : any) {
const productsFilter = productList.find(item => item.id === id)?.list || [];
const total = productsFilter?.length;
export default function ProductList({ id }: any) {
const [page, setPage] = useState(1);
const displayCount = page * PRODUCT_PER_PAGE;
const hasMore = displayCount < total;
const products = productsFilter.slice(0, displayCount);
const data = productList.find(item => item.id === id)?.list || [];
const {
currentData,
hasMore,
loadMore,
total
} = usePagination(data);
return (
<>
{currentData.length == 0
? parse(`
<div class="text-center py-20">
<p class="text-20 font-700">Sản phẩm đang được cập nhật ...!</p>
<a href="/" class="color-main text-18"> Quay lại trang chủ </a>
</div>
`)
: (
<div className="product-holder grid grid-cols-4 gap-x-5 gap-y-8 mb-6">
{products.map((item: any) => (
{currentData.map((item: any) => (
<ProductItem key={item.id} item={item} />
))}
</div>
)
}
{ hasMore &&
<div className="text-center mt-12">
<button type="button" aria-label="Xem thêm"
className="mb-3 bg-btn text-white rounded-[30px] h-10 font-500 text-16 table max-w-[240px] w-full m-auto mb-3"
onClick={() => setPage(prev => prev + 1)}
>
TẢI THÊM
</button>
<p className="text-14 leading-[18px] m-0">
Hiển thị {Math.min(displayCount, total)} trên tổng số {total} sản phẩm
</p>
</div>
{hasMore &&
<ButtonShowMore
onClick={loadMore}
displayCount={currentData.length}
total={total}
/>
}
</>
)

View File

@@ -35,6 +35,8 @@ export default async function ProductDetail({ slug }: any) {
quantity : slug.quantity
}
console.log('aaaaaaaa: ', priceData)
return (
<>
<div className="product-detail-page container">

View File

@@ -7,8 +7,8 @@ export default function DealPrice( {item} : any ) {
const normal_price = item.sale_rules.normal_price;
const discount = Math.ceil(((normal_price - price) / normal_price) * 100);
const sale_quantity = item.deal_list[0].sale_quantity;
const quantity = item.deal_list[0].quantity;
const sale_quantity = Number(item.deal_list[0].sale_quantity);
const quantity = Number(item.deal_list[0].quantity);
const saleRemainPercent = 100 - (sale_quantity / quantity) * 100;
return (

View File

@@ -1,6 +1,7 @@
'use client';
import { useEffect, useState } from 'react'
import { renderSummary } from "@/lib/utils"
export default function ProductSummary({ item }: any) {
const [mounted, setMounted] = useState(false)
@@ -22,33 +23,4 @@ export default function ProductSummary({ item }: any) {
}
function renderSummary(data: any) {
if (!data) return null;
if (typeof data === 'string' && data.includes('<')) {
const parser = new DOMParser()
const doc = parser.parseFromString(data, 'text/html')
return Array.from(doc.body.childNodes)
.filter(
node =>
node.nodeType === 1 &&
node.textContent &&
node.textContent.trim() !== ''
)
.map((node, index) => (
<div key={index} className="item-circle">
{node.textContent!.trim()}
</div>
))
}
return data
.split(/\r?\n/)
.filter((line: string) => line.trim() !== '')
.map((line: string, index: number) => (
<div key={index} className="item-circle">
{line.trim()}
</div>
))
}

View File

@@ -5,8 +5,6 @@ import parse from "html-react-parser"
import { JobData } from "@/data/articles/Job";
import RecruitForm from "./Form";
import '@/styles/static_page.css';
import '@/styles/tuyen_dung.css';
export default function JobDetail({ slug }: any) {

View File

@@ -0,0 +1,96 @@
'use client';
import parse from "html-react-parser";
import { useSearchParams } from 'next/navigation';
import { productCategory } from "@/data/products/productCategory";
import { productList } from "@/data/products/productList";
import { usePagination } from "@/hooks/usePagination";
import ButtonShowMore from "@/components/shared/ProductShowMore";
import ProductFilter from "@/components/product/category/filter";
import SortByCollection from "@/components/product/category/sort";
import ProductItem from "@/components/shared/ProductItem";
import { useMemo } from "react";
export default function ProductSearch() {
const searchParams = useSearchParams();
const search_query = searchParams.get('q') || "";
const {
sort_by_collection,
} = productCategory.current_category;
const totalProduct = useMemo(() => {
return productList.flatMap((item: any) => item.list);
}, []);
const filterData = useMemo(() => {
return totalProduct.filter((item: any) =>
item.productName?.trim()
.toLowerCase()
.includes(search_query.trim().toLowerCase())
);
}, [totalProduct, search_query]);
const {
currentData,
hasMore,
loadMore,
total
} = usePagination(filterData);
return (
<div className="product-page container">
<h1 className="text-[#004BA4] text-[32px] leading-10 mb-4 font-600">
Tìm kiếm sản phẩm: "{search_query}"
</h1>
{currentData.length == 0
? parse(`
<div class="text-center py-20">
<p class="text-22 font-500">
Rất tiếc, chúng tôi không tìm thấy kết quả của "${search_query}"
</p>
<div class="text-left" style="border:solid 1px #ccc; max-width:500px; margin:auto; padding:20px;">
<p class="text-center"><b>Để tìm được kết quả chính xác hơn, xin vui lòng</b></p>
<ul>
<li>Kiểm tra lại chính tả của từ khóa đã nhập</li>
<li>Thử lại bằng từ khóa khác</li>
<li>Thử lại bằng các từ khóa tổng quát hơn</li>
<li>Thử lại bằng các từ khóa ngắn gọn hơn</li>
</ul>
</div>
</div>
`) : (
<div className="product-page-content flex flex-wrap items-start gap-4 mb-5">
<div className="col-left-group w-[264px] rounded-[16px] bg-white p-4 pb-6">
<ProductFilter data={productCategory} />
</div>
<div className="col-right-group w-[968px]">
<div className="box-item rounded-[24px] bg-white px-6 pt-4 pb-8 mb-4">
<SortByCollection sort={sort_by_collection} total={total} />
<div className="product-holder grid grid-cols-4 gap-x-5 gap-y-8 mb-6">
{currentData.map((item: any) => (
<ProductItem key={item.id} item={item} />
))}
</div>
{hasMore &&
<ButtonShowMore
onClick={loadMore}
displayCount={currentData.length}
total={total}
/>
}
</div>
</div>
</div>
)}
</div>
)
}

View File

@@ -63,9 +63,11 @@ export default function DealItem( {item} : any) {
<div className="deal-count">
<i className="deal-line" style={{ width: saleRemainPercent + '%' }}></i>
<span> Còn:
{remain}/{item.quantity}
sản phẩm
<span>
{ Number(item.sale_quantity) < Number(item.quantity)
? `Còn ${remain}/${item.quantity} sản phẩm`
: 'Hết DEAL'
}
</span>
</div>

View File

@@ -0,0 +1,43 @@
import { formatPrice, renderSummary } from "@/lib/utils";
export default function DesignerItem({ item }: any) {
return (
<div className="p-item">
<a href={item.productUrl} className="p-img">
<img src={item.productImage.large}
alt={item.productName}
width={250}
height={250}
/>
</a>
<div className="p-text">
<a href={item.productUrl} className="p-name">
<h3> {item.productName} </h3>
</a>
<div className="p-price-group">
<p className="p-price">
{item.price > 0
? formatPrice(item.price) + 'đ'
: 'Liên hệ'
}
</p>
{Number(item.price_off) > 0 &&
<>
<del> {formatPrice(item.marketPrice)} đ </del>
<span className="p-discount"> -{item.price_off}% </span>
</>
}
</div>
{item.productSummary &&
<div className="p-summary">
{renderSummary(item.productSummary)}
</div>
}
</div>
</div>
)
}

View File

@@ -0,0 +1,35 @@
interface ButtonShowMoreProps {
onClick: () => void;
label?: string;
loading?: boolean;
displayCount: number;
total: number;
}
export default function ButtonShowMore({
onClick,
label = "TẢI THÊM",
loading = false,
displayCount,
total
}: ButtonShowMoreProps) {
return (
<div className="text-center mt-12">
<button
type="button"
disabled={loading}
className="mb-3 bg-btn text-white rounded-[30px] h-10 font-500 text-16 table max-w-[240px] w-full m-auto"
aria-label="Xem thêm"
onClick={onClick}
>
{loading ? "ĐANG TẢI..." : label}
<i className="bx bx-chevron-down text-20 align-middle mt-[-3px]" />
</button>
<p className="text-14 leading-[18px] m-0">
Hiển thị {displayCount} trên tổng số {total} sản phẩm
</p>
</div>
);
}

View File

@@ -14,13 +14,13 @@ export const dealList = [
"quantity": "10",
"min_purchase": "1",
"max_purchase": "10",
"to_time": "02-02-2026, 8:30 am",
"to_time": "22-02-2026, 8:30 am",
"is_featured": "0",
"last_update": "1767576809",
"sale_order": "0",
"deal_time_happen": 796220,
"deal_time_left": 1624780,
"sale_quantity": "0",
"sale_quantity": "10",
"request_path": "\/deal\/342",
"is_start": 1,
"is_end": 0,
@@ -79,7 +79,7 @@ export const dealList = [
"quantity": "10",
"min_purchase": "1",
"max_purchase": "10",
"to_time": "02-02-2026, 8:30 am",
"to_time": "02-02-2027, 8:30 am",
"is_featured": "0",
"last_update": "1767576805",
"sale_order": "0",
@@ -144,7 +144,7 @@ export const dealList = [
"quantity": "10",
"min_purchase": "1",
"max_purchase": "10",
"to_time": "02-02-2026, 8:30 am",
"to_time": "02-02-2027, 8:30 am",
"is_featured": "0",
"last_update": "1767576799",
"sale_order": "0",
@@ -180,7 +180,7 @@ export const dealList = [
"quantity": "10",
"min_purchase": "1",
"max_purchase": "10",
"to_time": "02-02-2026, 8:30 am",
"to_time": "02-02-2027, 8:30 am",
"is_featured": "0",
"last_update": "1767576954",
"sale_order": "0",
@@ -216,7 +216,7 @@ export const dealList = [
"quantity": "10",
"min_purchase": "1",
"max_purchase": "10",
"to_time": "02-02-2026, 8:30 am",
"to_time": "02-02-2027, 8:30 am",
"is_featured": "0",
"last_update": "1767576950",
"sale_order": "0",
@@ -252,7 +252,7 @@ export const dealList = [
"quantity": "10",
"min_purchase": "1",
"max_purchase": "10",
"to_time": "02-02-2026, 8:30 am",
"to_time": "02-02-2027, 8:30 am",
"is_featured": "0",
"last_update": "1767576946",
"sale_order": "0",
@@ -288,7 +288,7 @@ export const dealList = [
"quantity": "10",
"min_purchase": "1",
"max_purchase": "10",
"to_time": "02-02-2026, 8:30 am",
"to_time": "02-02-2027, 8:30 am",
"is_featured": "0",
"last_update": "1767577069",
"sale_order": "0",
@@ -324,7 +324,7 @@ export const dealList = [
"quantity": "10",
"min_purchase": "1",
"max_purchase": "10",
"to_time": "02-02-2026, 8:30 am",
"to_time": "02-02-2027, 8:30 am",
"is_featured": "0",
"last_update": "1767576941",
"sale_order": "0",
@@ -360,7 +360,7 @@ export const dealList = [
"quantity": "10",
"min_purchase": "1",
"max_purchase": "10",
"to_time": "02-02-2026, 8:30 am",
"to_time": "02-02-2027, 8:30 am",
"is_featured": "0",
"last_update": "1767576936",
"sale_order": "0",
@@ -396,7 +396,7 @@ export const dealList = [
"quantity": "10",
"min_purchase": "1",
"max_purchase": "10",
"to_time": "02-02-2026, 8:30 am",
"to_time": "02-02-2027, 8:30 am",
"is_featured": "0",
"last_update": "1767576932",
"sale_order": "0",
@@ -442,7 +442,7 @@ export const dealList = [
"quantity": "10",
"min_purchase": "1",
"max_purchase": "10",
"to_time": "02-02-2026, 8:30 am",
"to_time": "02-02-2027, 8:30 am",
"is_featured": "0",
"last_update": "1768017144",
"sale_order": "0",
@@ -507,7 +507,7 @@ export const dealList = [
"quantity": "10",
"min_purchase": "1",
"max_purchase": "10",
"to_time": "02-02-2026, 8:30 am",
"to_time": "02-02-2027, 8:30 am",
"is_featured": "0",
"last_update": "1768017107",
"sale_order": "0",
@@ -543,7 +543,7 @@ export const dealList = [
"quantity": "10",
"min_purchase": "1",
"max_purchase": "10",
"to_time": "02-02-2026, 8:30 am",
"to_time": "02-02-2027, 8:30 am",
"is_featured": "0",
"last_update": "1767853933",
"sale_order": "1",
@@ -579,7 +579,7 @@ export const dealList = [
"quantity": "10",
"min_purchase": "1",
"max_purchase": "10",
"to_time": "02-02-2026, 8:30 am",
"to_time": "02-02-2027, 8:30 am",
"is_featured": "0",
"last_update": "1767585250",
"sale_order": "2",
@@ -615,7 +615,7 @@ export const dealList = [
"quantity": "10",
"min_purchase": "1",
"max_purchase": "10",
"to_time": "02-02-2026, 8:30 am",
"to_time": "02-02-2027, 8:30 am",
"is_featured": "0",
"last_update": "1767576873",
"sale_order": "0",
@@ -651,7 +651,7 @@ export const dealList = [
"quantity": "10",
"min_purchase": "1",
"max_purchase": "10",
"to_time": "02-02-2026, 8:30 am",
"to_time": "02-02-2027, 8:30 am",
"is_featured": "0",
"last_update": "1767576869",
"sale_order": "0",
@@ -687,7 +687,7 @@ export const dealList = [
"quantity": "10",
"min_purchase": "1",
"max_purchase": "10",
"to_time": "02-02-2026, 8:30 am",
"to_time": "02-02-2027, 8:30 am",
"is_featured": "0",
"last_update": "1767576982",
"sale_order": "0",
@@ -723,7 +723,7 @@ export const dealList = [
"quantity": "10",
"min_purchase": "1",
"max_purchase": "10",
"to_time": "02-02-2026, 8:30 am",
"to_time": "02-02-2027, 8:30 am",
"is_featured": "0",
"last_update": "1767576860",
"sale_order": "0",
@@ -759,7 +759,7 @@ export const dealList = [
"quantity": "10",
"min_purchase": "1",
"max_purchase": "10",
"to_time": "02-02-2026, 8:30 am",
"to_time": "02-02-2027, 8:30 am",
"is_featured": "0",
"last_update": "1767576852",
"sale_order": "0",
@@ -795,7 +795,7 @@ export const dealList = [
"quantity": "10",
"min_purchase": "1",
"max_purchase": "10",
"to_time": "02-02-2026, 8:30 am",
"to_time": "02-02-2027, 8:30 am",
"is_featured": "0",
"last_update": "1767576848",
"sale_order": "0",

View File

@@ -25,13 +25,13 @@ export const DesignerToolDetail = [
"id": 5975,
"productId": 5975,
"marketPrice": 50000000,
"price": 47740000,
"price_off": "5",
"price": 48230000,
"price_off": "4",
"warranty": `theo từng linh kiện`,
"productName": `HHPC CORE i7 14700KF | 32GB DDR5 | NVIDIA RTX 5060 Ti 16G`,
"productSummary": `<p>CPU : INTEL CORE i7 14700KF UP 5.6GHz | 20 CORES | 28 THREADS</p>
"productSummary": `<p>CPU : INTEL CORE i7 14700KF UP 5.6GHz | 20 CORES | 28 THREADS</p>
<p>RAM : DDR5 32GB 6000 MHz (2x16G)</p>
<p>VGA: NVIDIA RTX 5060 Ti 16G GDDR7 - 2 FAN</p>`,
<p>VGA: NVIDIA RTX 5060 Ti 16G GDDR7 - 2 FAN</p>`,
"productUrl": `/pc-i7-14700kf-rtx-5060-ti-16g`,
"productImage": {
"small": "https://hoanghapccdn.com/media/product/75_6518_pc_anubis_astrobeat_kh__ng_logo_h2.jpg",
@@ -39,15 +39,16 @@ export const DesignerToolDetail = [
"original": "",
},
"sale_rules": {
"price": 47740000,
"normal_price": 47740000,
"price": 48230000,
"normal_price": 48230000,
"min_purchase": 1,
"max_purchase": 1,
"remain_quantity": 1,
"from_time": 0,
"to_time": 0,
"type": "component"
}
},
"attribute": [25, 26]
},
{
@@ -58,9 +59,9 @@ export const DesignerToolDetail = [
"price_off": "6",
"warranty": `theo từng linh kiện`,
"productName": `HHPC CORE i5 14600K | 32G | NVIDIA RTX 5060 Ti 16G`,
"productSummary": `<p>CPU : INTEL CORE i5 14600K UP 5.3GHz | 14 CORES | 20 THREADS</p>
<p>RAM : DDR4 32GB (2x16G) 3200 MHz</p>
<p>VGA: NVIDIA RTX 5060 Ti 16G GDDR7 - 3 FAN</p>`,
"productSummary": `<p>CPU : INTEL CORE i5 14600K UP 5.3GHz | 14 CORES | 20 THREADS</p>
<p>RAM : DDR4 32GB (2x16G) 3200 MHz</p>
<p>VGA: NVIDIA RTX 5060 Ti 16G GDDR7 - 3 FAN</p>`,
"productUrl": `/hhpc-core-i5-14600k-32g-rtx-5060-ti-16g`,
"productImage": {
"small": "https://hoanghapccdn.com/media/product/75_5989_pc_gaming_x_ii_a620.jpg",
@@ -76,7 +77,8 @@ export const DesignerToolDetail = [
"from_time": 0,
"to_time": 0,
"type": "component"
}
},
"attribute": [24, 26]
},
{
@@ -89,7 +91,7 @@ export const DesignerToolDetail = [
"productName": `HHPC CORE i9 14900KF | 32G | RX 7800 XT 16G`,
"productSummary": `<p>CPU : INTEL CORE i9 14900KF UP 6.0GHz | 24 CORES | 32 THREADS</p>
<p>RAM : DDR4 32GB 3200 MHz (2x16G)</p>
<p>VGA: RADEON RX 7800 XT 16G GDDR6</p>`,
<p>VGA: RADEON RX 7800 XT 16G GDDR6</p>`,
"productUrl": `/hhpc-core-i9-14900kf-32g-rx-7800-xt-16g`,
"productImage": {
"small": "https://hoanghapccdn.com/media/product/75_6011_pc_anubis_le360_k_logo_ha3.jpg",
@@ -105,7 +107,8 @@ export const DesignerToolDetail = [
"from_time": 0,
"to_time": 0,
"type": "component"
}
},
"attribute": [24, 26]
},
{
@@ -116,9 +119,9 @@ export const DesignerToolDetail = [
"price_off": "6",
"warranty": `theo từng linh kiện`,
"productName": `HHPC CORE i7 14700KF | 32GB | RX 7800 XT 16G`,
"productSummary": `<p>CPU : INTEL CORE i7 14700KF up 5.6GHz | 20 CORE | 28 THREAD</p>
<p>RAM : DDR4 32GB (2x16G) 3200 MHz</p>
<p>VGA: RADEON RX 7800 XT 16G GDDR6</p>`,
"productSummary": `<p>CPU : INTEL CORE i7 14700KF up 5.6GHz | 20 CORE | 28 THREAD</p>
<p>RAM : DDR4 32GB (2x16G) 3200 MHz</p>
<p>VGA: RADEON RX 7800 XT 16G GDDR6</p>`,
"productUrl": `/pc-core-i7-14700kf-32gb-rx-7800-xt-16g`,
"productImage": {
"small": "https://hoanghapccdn.com/media/product/75_6012_pc_anubis_le360_k_logo_ha1.jpg",
@@ -134,7 +137,8 @@ export const DesignerToolDetail = [
"from_time": 0,
"to_time": 0,
"type": "component"
}
},
"attribute": [24, 26]
},
{
@@ -145,9 +149,9 @@ export const DesignerToolDetail = [
"price_off": "5",
"warranty": `theo từng linh kiện`,
"productName": `HHPC GAMING CORE i5 14600K | 16GB | NVIDIA RTX 5070 Ti 16G`,
"productSummary": `<p>CPU : INTEL CORE i5 14600K UP 5.3GHz | 14 CORES | 20 THREADS</p>
<p>RAM : DDR4 16GB (1x16G) 3200 MHz</p>
<p>VGA : NVIDIA RTX 5070 Ti 16G GDDR7</p>`,
"productSummary": `<p>CPU : INTEL CORE i5 14600K UP 5.3GHz | 14 CORES | 20 THREADS</p>
<p>RAM : DDR4 16GB (1x16G) 3200 MHz</p>
<p>VGA : NVIDIA RTX 5070 Ti 16G GDDR7</p>`,
"productUrl": `/pc-gaming-core-i5-14600kf-16gb-nvidia-rtx-5070-ti`,
"productImage": {
"small": "https://hoanghapccdn.com/media/product/75_6045_pc_blast_m_ha1.jpg",
@@ -163,7 +167,8 @@ export const DesignerToolDetail = [
"from_time": 0,
"to_time": 0,
"type": "component"
}
},
"attribute": [24, 26]
},
{
@@ -174,9 +179,9 @@ export const DesignerToolDetail = [
"price_off": "6",
"warranty": `theo từng linh kiện`,
"productName": `HHPC GAMING CORE i5 14600K | 16GB | RX 7800 XT 16G`,
"productSummary": `<p>CPU : INTEL CORE i5 14600K UP 5.3GHz | 14 CORES | 20 THREADS</p>
<p>RAM : DDR4 16GB (1x16G) 3200 MHz</p>
<p>VGA: RADEON RX 7800 XT 16G GDDR6</p>`,
"productSummary": `<p>CPU : INTEL CORE i5 14600K UP 5.3GHz | 14 CORES | 20 THREADS</p>
<p>RAM : DDR4 16GB (1x16G) 3200 MHz</p>
<p>VGA: RADEON RX 7800 XT 16G GDDR6</p>`,
"productUrl": `/hhpc-gaming-core-i5-14600k-16gb-rx-7800-xt`,
"productImage": {
"small": "https://hoanghapccdn.com/media/product/75_6046_pc_blast_m_ha1.jpg",
@@ -192,7 +197,8 @@ export const DesignerToolDetail = [
"from_time": 0,
"to_time": 0,
"type": "component"
}
},
"attribute": [24, 27]
},
{
@@ -203,9 +209,9 @@ export const DesignerToolDetail = [
"price_off": "3",
"warranty": `theo từng linh kiện`,
"productName": `HHPC CORE i5 14600K | 32G | NVIDIA RTX 5060 8G`,
"productSummary": `<p>CPU : INTEL CORE i5 14600K UP 5.3GHz | 14 CORES | 20 THREADS</p>
<p>RAM : DDR4 32GB (2x16G) 3200 MHz</p>
<p>VGA: NVIDIA RTX 5060 8G GDDR7</p>`,
"productSummary": `<p>CPU : INTEL CORE i5 14600K UP 5.3GHz | 14 CORES | 20 THREADS</p>
<p>RAM : DDR4 32GB (2x16G) 3200 MHz</p>
<p>VGA: NVIDIA RTX 5060 8G GDDR7</p>`,
"productUrl": `/pc-core-i5-14600k-32g-rtx-5060-8g`,
"productImage": {
"small": "https://hoanghapccdn.com/media/product/75_6139_pc_gaming_x_ii_a620.jpg",
@@ -221,7 +227,8 @@ export const DesignerToolDetail = [
"from_time": 0,
"to_time": 0,
"type": "component"
}
},
"attribute": [24, 27]
},
{
@@ -232,9 +239,9 @@ export const DesignerToolDetail = [
"price_off": "",
"warranty": `theo từng linh kiện`,
"productName": `HHPC CORE i7 14700KF | 32GB | NVIDIA RTX 5060 8G`,
"productSummary": `<p>CPU : INTEL CORE i7 14700KF up 5.6GHz | 20 CORE | 28 THREAD</p>
<p>RAM : DDR4 32GB (2x16G) 3200 MHz</p>
<p>VGA: NVIDIA RTX 5060 8G GDDR7</p>`,
"productSummary": `<p>CPU : INTEL CORE i7 14700KF up 5.6GHz | 20 CORE | 28 THREAD</p>
<p>RAM : DDR4 32GB (2x16G) 3200 MHz</p>
<p>VGA: NVIDIA RTX 5060 8G GDDR7</p>`,
"productUrl": `/pc-i7-14700-32g-rtx-5060-8g`,
"productImage": {
"small": "https://hoanghapccdn.com/media/product/75_6140_pc_anubis_lt720_ram_d4_14900k_ha6ss.jpg",
@@ -250,7 +257,8 @@ export const DesignerToolDetail = [
"from_time": 1767574800,
"to_time": 1772415000,
"type": "deal"
}
},
"attribute": [24, 27]
},
{
@@ -261,9 +269,9 @@ export const DesignerToolDetail = [
"price_off": "0",
"warranty": `theo từng linh kiện`,
"productName": `HHPC CORE i5 14600K | 16G | NVIDIA QUADRO P400 2GB`,
"productSummary": `<p>CPU : INTEL CORE i5 14600K UP 5.3GHz | 14 CORES | 20 THREADS</p>
<p>RAM : DDR4 16GB (1x16G) 3200 MHz</p>
<p>VGA : NVIDIA QUADRO P400 2GB GDDR5</p>`,
"productSummary": `<p>CPU : INTEL CORE i5 14600K UP 5.3GHz | 14 CORES | 20 THREADS</p>
<p>RAM : DDR4 16GB (1x16G) 3200 MHz</p>
<p>VGA : NVIDIA QUADRO P400 2GB GDDR5</p>`,
"productUrl": `/pc-core-i5-14600k-16g-quadro-p400-2gb`,
"productImage": {
"small": "https://hoanghapccdn.com/media/product/75_6240_pc_gaming_x_ii_a620_ha1.jpg",
@@ -279,7 +287,8 @@ export const DesignerToolDetail = [
"from_time": 0,
"to_time": 0,
"type": "component"
}
},
"attribute": [24, 27]
},
{
@@ -292,8 +301,8 @@ export const DesignerToolDetail = [
"productName": `HHPC CORE i9 14900KF | 64G DDR5 | NVIDIA RTX 5090 32G`,
"productSummary": `<p>CPU : INTEL CORE i9 14900KF UP 6.0GHz | 24 CORES | 32 THREADS</p>
<p>RAM : DDR5 64GB 6000 MHz (2x32G)</p>
<p>SSD: 1TB M.2 2280 PCIe 4.0 NVMe</p>
<p>VGA: NVIDIA RTX 5090 32G GDDR7</p>`,
<p>SSD: 1TB M.2 2280 PCIe 4.0 NVMe</p>
<p>VGA: NVIDIA RTX 5090 32G GDDR7</p>`,
"productUrl": `/pc-core-i9-14900kf-rtx-5090-32g`,
"productImage": {
"small": "https://hoanghapccdn.com/media/product/75_6095_pc_anubis_le360_k_logo_ha2.jpg",
@@ -309,7 +318,8 @@ export const DesignerToolDetail = [
"from_time": 0,
"to_time": 0,
"type": "component"
}
},
"attribute": [24, 26]
},
{
@@ -320,9 +330,9 @@ export const DesignerToolDetail = [
"price_off": "6",
"warranty": `theo từng linh kiện`,
"productName": `HHPC ULTRA 9 285 | 32GB DDR5 | NVIDIA RTX 5070 Ti 16G`,
"productSummary": `<p>CPU : INTEL CORE ULTRA 9 285 UP 5.6GHz | 24 CORE | 24 THREAD</p>
"productSummary": `<p>CPU : INTEL CORE ULTRA 9 285 UP 5.6GHz | 24 CORE | 24 THREAD</p>
<p>RAM : DDR5 32GB 6000 MHz (2x16G)</p>
<p>VGA: NVIDIA RTX 5070 Ti 16G GDDR7</p>`,
<p>VGA: NVIDIA RTX 5070 Ti 16G GDDR7</p>`,
"productUrl": `/pc-ultra-9-285-32gb-d5-rtx-5070-ti-16g`,
"productImage": {
"small": "https://hoanghapccdn.com/media/product/75_6127_pc_anubis_le360_k_logo_ha3.jpg",
@@ -338,7 +348,8 @@ export const DesignerToolDetail = [
"from_time": 0,
"to_time": 0,
"type": "component"
}
},
"attribute": [24, 26]
},
{
@@ -351,7 +362,7 @@ export const DesignerToolDetail = [
"productName": `HHPC CORE i9 14900KF | 64G DDR5 | NVIDIA RTX 5070 Ti 16G`,
"productSummary": `<p>CPU : INTEL CORE i9 14900KF UP 6.0GHz | 24 CORES | 32 THREADS</p>
<p>RAM : DDR5 64GB 6000 MHz (2x32G)</p>
<p>VGA : NVIDIA RTX 5070 Ti 16G GDDR7</p>`,
<p>VGA : NVIDIA RTX 5070 Ti 16G GDDR7</p>`,
"productUrl": `/pc-core-i9-14900kf-rtx-5070-ti-16g`,
"productImage": {
"small": "https://hoanghapccdn.com/media/product/75_5997_pc_anubis_le360_k_logo_ha3.jpg",
@@ -367,7 +378,8 @@ export const DesignerToolDetail = [
"from_time": 0,
"to_time": 0,
"type": "component"
}
},
"attribute": [24, 26]
},
{
@@ -378,10 +390,10 @@ export const DesignerToolDetail = [
"price_off": "",
"warranty": `theo từng linh kiện`,
"productName": `HHPC ULTRA 9 285K | 32GB DDR5 | NVIDIA RTX 5070 Ti 16G`,
"productSummary": `<p>CPU : INTEL CORE ULTRA 9 285K UP 5.7GHz | 24 CORES | 24 THREADS</p>
"productSummary": `<p>CPU : INTEL CORE ULTRA 9 285K UP 5.7GHz | 24 CORES | 24 THREADS</p>
<p>RAM : DDR5 32GB 6000 MHz (2x16G)</p>
<p>SSD: 1TB M.2 2280 PCIe 4.0 NVMe</p>
<p>VGA : NVIDIA RTX 5070 Ti 16G GDDR7</p>`,
<p>SSD: 1TB M.2 2280 PCIe 4.0 NVMe</p>
<p>VGA : NVIDIA RTX 5070 Ti 16G GDDR7</p>`,
"productUrl": `/hhpc-ultra-9-285k-32gb-ddr5-nvidia-rtx-5070-ti-16g`,
"productImage": {
"small": "https://hoanghapccdn.com/media/product/75_5877_pc_view_290_tg_lc360_sale.jpg",
@@ -397,7 +409,8 @@ export const DesignerToolDetail = [
"from_time": 1767574800,
"to_time": 1772415000,
"type": "deal"
}
},
"attribute": [24, 26]
},
{
@@ -408,9 +421,9 @@ export const DesignerToolDetail = [
"price_off": "5",
"warranty": `theo từng linh kiện`,
"productName": `HHPC RYZEN 9 9950X | 32G DDR5 | NVIDIA RTX 5070 Ti 16G`,
"productSummary": `<p>CPU : AMD RYZEN 9 9950X up to 5.7 GHz 16 CORE | 32 THREAD</p>
"productSummary": `<p>CPU : AMD RYZEN 9 9950X up to 5.7 GHz 16 CORE | 32 THREAD</p>
<p>RAM : DDR5 32GB 6000Mhz (2x16GB)</p>
<p>VGA : NVIDIA RTX 5070 Ti 16G GDDR7</p>`,
<p>VGA : NVIDIA RTX 5070 Ti 16G GDDR7</p>`,
"productUrl": `/pc-ryzen-9-9950x-32g-d5-rtx-5070-ti-16g`,
"productImage": {
"small": "https://hoanghapccdn.com/media/product/75_6349_pc_view_290_tg_argb_ha3.jpg",
@@ -426,7 +439,8 @@ export const DesignerToolDetail = [
"from_time": 0,
"to_time": 0,
"type": "component"
}
},
"attribute": [24, 26]
},
{
@@ -437,9 +451,9 @@ export const DesignerToolDetail = [
"price_off": "3",
"warranty": `theo từng linh kiện`,
"productName": `HHPC ULTRA 7 265KF | 32GB DDR5 | NVIDIA RTX 5070 12G`,
"productSummary": `<p>CPU : INTEL CORE ULTRA 7 265KF UP 5.5GHz | 20 CORES | 20 THREADS</p>
"productSummary": `<p>CPU : INTEL CORE ULTRA 7 265KF UP 5.5GHz | 20 CORES | 20 THREADS</p>
<p>RAM : DDR5 32GB 6000 MHz (2x16G)</p>
<p>VGA : NVIDIA RTX 5070 12G GDDR7</p>`,
<p>VGA : NVIDIA RTX 5070 12G GDDR7</p>`,
"productUrl": `/hhpc-ultra-7-265kf-5070-12g`,
"productImage": {
"small": "https://hoanghapccdn.com/media/product/75_5866_pc_gaming_x_ii_a620_do_hoa.jpg",
@@ -455,7 +469,8 @@ export const DesignerToolDetail = [
"from_time": 0,
"to_time": 0,
"type": "component"
}
},
"attribute": [24, 27]
},
{
@@ -466,10 +481,10 @@ export const DesignerToolDetail = [
"price_off": "",
"warranty": `theo từng linh kiện`,
"productName": `HHPC ULTRA 9 285K | 32GB DDR5 | NVIDIA RTX 5070 12G`,
"productSummary": `<p>CPU : INTEL CORE ULTRA 9 285K UP 5.7GHz | 24 CORE | 24 THREAD</p>
"productSummary": `<p>CPU : INTEL CORE ULTRA 9 285K UP 5.7GHz | 24 CORE | 24 THREAD</p>
<p>RAM : DDR5 32GB 6000 MHz (2x16G)</p>
<p>SSD: 1TB M.2 2280 PCIe 4.0 NVMe</p>
<p>VGA : NVIDIA RTX 5070 12G GDDR7</p>`,
<p>SSD: 1TB M.2 2280 PCIe 4.0 NVMe</p>
<p>VGA : NVIDIA RTX 5070 12G GDDR7</p>`,
"productUrl": `/hhpc-ultra-9-285k-32gb-ddr5-nvidia-rtx-5070-12g`,
"productImage": {
"small": "https://hoanghapccdn.com/media/product/75_5867_pc_anubis_le360_d5_sale_2.jpg",
@@ -485,7 +500,8 @@ export const DesignerToolDetail = [
"from_time": 1767574800,
"to_time": 1772415000,
"type": "deal"
}
},
"attribute": [24, 27]
},
{
@@ -496,9 +512,9 @@ export const DesignerToolDetail = [
"price_off": "6",
"warranty": `theo từng linh kiện`,
"productName": `HHPC RYZEN 9 9950X | 32G DDR5 | RX 7800 XT 16G`,
"productSummary": `<p>CPU : AMD RYZEN 9 9950X up to 5.7 GHz 16 CORE | 32 THREAD</p>
"productSummary": `<p>CPU : AMD RYZEN 9 9950X up to 5.7 GHz 16 CORE | 32 THREAD</p>
<p>RAM : DDR5 32GB 6000Mhz (2x16GB)</p>
<p>VGA: RADEON RX 7800 XT 16G GDDR6</p>`,
<p>VGA: RADEON RX 7800 XT 16G GDDR6</p>`,
"productUrl": `/hhpc-ryzen-9-9950x-32g-ddr5-rx-7800-xt-16g`,
"productImage": {
"small": "https://hoanghapccdn.com/media/product/75_6007_pc_anubis_le360_k_logo_ha3.jpg",
@@ -514,7 +530,8 @@ export const DesignerToolDetail = [
"from_time": 0,
"to_time": 0,
"type": "component"
}
},
"attribute": [24, 27]
},
{
@@ -525,10 +542,10 @@ export const DesignerToolDetail = [
"price_off": "7",
"warranty": `theo từng linh kiện`,
"productName": `HHPC ULTRA 9 285K | 32GB DDR5 | RX 7800 XT 16G`,
"productSummary": `<p>CPU : INTEL CORE ULTRA 9 285K UP 5.7GHz | 24 CORE | 24 THREAD</p>
"productSummary": `<p>CPU : INTEL CORE ULTRA 9 285K UP 5.7GHz | 24 CORE | 24 THREAD</p>
<p>RAM : DDR5 32GB 6000 MHz (2x16G)</p>
<p>SSD: 1TB M.2 2280 PCIe 4.0 NVMe</p>
<p>VGA: RADEON RX 7800 XT 16G GDDR6</p>`,
<p>SSD: 1TB M.2 2280 PCIe 4.0 NVMe</p>
<p>VGA: RADEON RX 7800 XT 16G GDDR6</p>`,
"productUrl": `/hhpc-ultra-9-285k-32gb-ddr5-rx-7800-xt-16g`,
"productImage": {
"small": "https://hoanghapccdn.com/media/product/75_6009_pc_anubis_le360_k_logo_ha2.jpg",
@@ -544,7 +561,8 @@ export const DesignerToolDetail = [
"from_time": 0,
"to_time": 0,
"type": "component"
}
},
"attribute": [24, 27]
},
{
@@ -555,9 +573,9 @@ export const DesignerToolDetail = [
"price_off": "5",
"warranty": `theo từng linh kiện`,
"productName": `HHPC ULTRA 7 265KF | 32GB DDR5 | RX 7800 XT 16G`,
"productSummary": `<p>CPU : INTEL CORE ULTRA 7 265KF UP 5.5GHz | 20 CORES | 20 THREADS</p>
"productSummary": `<p>CPU : INTEL CORE ULTRA 7 265KF UP 5.5GHz | 20 CORES | 20 THREADS</p>
<p>RAM : DDR5 32GB 6000 MHz (2x16G)</p>
<p>VGA: RADEON RX 7800 XT 16G GDDR6</p>`,
<p>VGA: RADEON RX 7800 XT 16G GDDR6</p>`,
"productUrl": `/hhpc-ultra-7-265kf-32gb-d5-rx-7800-xt-16g`,
"productImage": {
"small": "https://hoanghapccdn.com/media/product/75_6010_pc_gaming_x_ii_a620.jpg",
@@ -573,7 +591,8 @@ export const DesignerToolDetail = [
"from_time": 0,
"to_time": 0,
"type": "component"
}
},
"attribute": [24, 27]
},
{
@@ -584,9 +603,9 @@ export const DesignerToolDetail = [
"price_off": "7",
"warranty": `theo từng linh kiện`,
"productName": `HHPC ULTRA 9 285 | 32GB DDR5 | NVIDIA RTX 5070 12G`,
"productSummary": `<p>CPU : INTEL CORE ULTRA 9 285 UP 5.6GHz | 24 CORE | 24 THREAD</p>
"productSummary": `<p>CPU : INTEL CORE ULTRA 9 285 UP 5.6GHz | 24 CORE | 24 THREAD</p>
<p>RAM : DDR5 32GB 6000 MHz (2x16G)</p>
<p>VGA: NVIDIA RTX 5070 12G GDDR7</p>`,
<p>VGA: NVIDIA RTX 5070 12G GDDR7</p>`,
"productUrl": `/hhpc-ultra-9-285-32gb-ddr5-nvidia-rtx-5070-12g`,
"productImage": {
"small": "https://hoanghapccdn.com/media/product/75_6126_pc_anubis_le360_k_logo_ha2.jpg",
@@ -602,7 +621,8 @@ export const DesignerToolDetail = [
"from_time": 0,
"to_time": 0,
"type": "component"
}
},
"attribute": [24, 27]
},
{
@@ -613,9 +633,9 @@ export const DesignerToolDetail = [
"price_off": "4",
"warranty": `theo từng linh kiện`,
"productName": `HHPC CORE i7 14700KF | 32GB DDR5 | NVIDIA RTX 5080 16G`,
"productSummary": `<p>CPU : INTEL CORE i7 14700KF up 5.6GHz | 20 CORE | 28 THREAD</p>
"productSummary": `<p>CPU : INTEL CORE i7 14700KF up 5.6GHz | 20 CORE | 28 THREAD</p>
<p>RAM : DDR5 32GB 6000 MHz (2x16G)</p>
<p>VGA: NVIDIA RTX 5080 16G GDDR7</p>`,
<p>VGA: NVIDIA RTX 5080 16G GDDR7</p>`,
"productUrl": `/pc-core-i7-14700kf-32gb-d5-rtx-5080-16g`,
"productImage": {
"small": "https://hoanghapccdn.com/media/product/75_6189_pc_anubis_le360_k_logo_ha3.jpg",
@@ -631,7 +651,8 @@ export const DesignerToolDetail = [
"from_time": 0,
"to_time": 0,
"type": "component"
}
},
"attribute": [24, 27]
},
],

File diff suppressed because one or more lines are too long

View File

@@ -9,8 +9,8 @@ export function useDealItem(item: DealItemProps) {
const marketPrice = Number(productInfo.price);
const discount = calculateDiscount(price, marketPrice);
const remain = item.quantity - item.sale_quantity;
const saleRemainPercent = 100 - (item.sale_quantity / item.quantity) * 100;
const remain = Number(item.quantity) - Number(item.sale_quantity);
const saleRemainPercent = 100 - (Number(item.sale_quantity) / Number(item.quantity)) * 100;
const specialOffer = productInfo?.specialOffer?.all?.[0]?.title ?? '';

View File

@@ -0,0 +1,35 @@
'use client'
import { useState, useMemo, useEffect } from "react";
export function usePagination<T>(data: any, perPage: number = 30) {
const [page, setPage] = useState(1);
const total = data.length;
const totalPage = Math.ceil(total / perPage);
useEffect(() => {
setPage(1); // reset khi data đổi
}, [data]);
const currentData = useMemo(() => {
return data.slice(0, page * perPage);
// nếu muốn paging thật thì đổi thành:
// const start = (page - 1) * perPage;
// return data.slice(start, start + perPage);
}, [data, page, perPage]);
const hasMore = page < totalPage;
const loadMore = () => setPage(prev => prev + 1);
return {
currentData,
hasMore,
loadMore,
page,
total,
totalPage
};
}

View File

@@ -1,19 +1,36 @@
'use client';
import { useEffect, useState } from "react";
import { useEffect, useState, useMemo } from "react";
export function DealCountdown({ endTime }: { endTime: any }) {
const formatTime = useMemo(() => {
if (!endTime) return 0;
if (typeof endTime === "string") {
const parsed = new Date(endTime).getTime();
return isNaN(parsed) ? 0 : Math.floor(parsed / 1000);
}
// Nếu là milliseconds (13 số)
if (endTime > 9999999999) {
return Math.floor(endTime / 1000);
}
return endTime;
}, [endTime]);
export function DealCountdown({ endTime }: { endTime: number }) {
const [mounted, setMounted] = useState(false);
const [timeLeft, setTimeLeft] = useState(getTimeLeft(endTime));
const [timeLeft, setTimeLeft] = useState(getTimeLeft(formatTime));
useEffect(() => {
setMounted(true);
const timer = setInterval(() => {
setTimeLeft(getTimeLeft(endTime));
setTimeLeft(getTimeLeft(formatTime));
}, 1000);
return () => clearInterval(timer);
}, [endTime]);
}, [formatTime]);
if (!mounted) return null;
@@ -37,20 +54,20 @@ export function getTimeLeft(endTime: number) {
if (distance <= 0) {
return {
total : 0,
days : '00',
hours : '00',
minutes : '00',
seconds : '00'
total: 0,
days: '00',
hours: '00',
minutes: '00',
seconds: '00'
};
}
return {
total : distance,
days : String(Math.floor(distance / 86400)).padStart(2, '0'),
hours : String(Math.floor((distance % 86400) / 3600)).padStart(2, '0'),
minutes : String(Math.floor((distance % 3600) / 60)).padStart(2, '0'),
seconds : String(distance % 60).padStart(2, '0'),
total: distance,
days: String(Math.floor(distance / 86400)).padStart(2, '0'),
hours: String(Math.floor((distance % 86400) / 3600)).padStart(2, '0'),
minutes: String(Math.floor((distance % 3600) / 60)).padStart(2, '0'),
seconds: String(distance % 60).padStart(2, '0'),
};
}

View File

@@ -38,6 +38,37 @@ export function formatTextList(
.join('');
}
export function renderSummary(data: any) {
if (!data) return null;
// Nếu là HTML string
if (typeof data === 'string' && data.includes('<')) {
// Bỏ toàn bộ tag HTML
const textOnly = data.replace(/<[^>]*>/g, '\n');
return textOnly
.split(/\r?\n/)
.filter((line: string) => line.trim() !== '')
.map((line: string, index: number) => (
<div key={index} className="item-circle">
{line.trim()}
</div>
));
}
// Nếu là text thường
return data
.split(/\r?\n/)
.filter((line: string) => line.trim() !== '')
.map((line: string, index: number) => (
<div key={index} className="item-circle">
{line.trim()}
</div>
));
}
// Format giá
export function formatPrice(amount: number) {
return amount.toLocaleString('vi-VN');
@@ -114,3 +145,4 @@ export function convertToSlug(text: string) {
.trim()
.replace(/ +/g, "-");
}