This commit is contained in:
2026-02-26 17:14:40 +07:00
parent a8df344207
commit b567aef3d8
9 changed files with 216 additions and 156 deletions

View File

@@ -1,4 +1,5 @@
export default function Popups() { export default function Popups({ onRebuild }: any) {
return (<> return (<>
{/* Rebuild */} {/* Rebuild */}
<div className="buildpc-popup-container buildpc-popup-rebuild text-black" id="popup-rebuild_config"> <div className="buildpc-popup-container buildpc-popup-rebuild text-black" id="popup-rebuild_config">
@@ -13,7 +14,9 @@ export default function Popups() {
<button <button
className="btn-red" className="btn-red"
style={{ background: '#FA354A', color: '#fff' }} style={{ background: '#FA354A', color: '#fff' }}
onClick={() => { /* reBuild(); */ }}> onClick={() => {
onRebuild();
}}>
Xác nhận Xác nhận
</button> </button>
</div> </div>

View File

@@ -1,12 +1,17 @@
export default function Promotion() { export default function Promotion({ total }: any) {
return ( return (
<div className="buildpc-info-group"> <div className="buildpc-info-group">
<p> <p>
Chi phí dự tính: Chi phí dự tính:
<span className="js-config-summary font-600" style={{ color: '#FF4E2A' }}> 0đ </span> <span
className="font-600"
style={{ color: '#FF4E2A' }}
>
{total.toLocaleString()} đ
</span>
</p> </p>
<div className="buildpc-promotion-content js-buildpc-promotion-content"> {/* // Khuyến mại buildpc */} </div> <div className="buildpc-promotion-content" />
</div> </div>
) );
} }

View File

@@ -1,35 +1,22 @@
'use client'; 'use client';
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Fancybox } from "@fancyapps/ui/dist/fancybox/";
import { categoryDetail } from "@/data/buildpc/categoryDetail"; import { categoryDetail } from "@/data/buildpc/categoryDetail";
import ModalContent from "../modal"; import ModalContent from "../modal";
import SelectedItemRow from "../modal/SelectedItemRow"; import SelectedItemRow from "../modal/SelectedItemRow";
export default function BuildPCCategories({ categories }: any) { export default function BuildPCCategories({
categories,
activeTab,
buildData,
setBuildData
}: any) {
const [selectedCategory, setSelectedCategory] = useState<any>(null); const [selectedCategory, setSelectedCategory] = useState<any>(null);
const [categoryInfo, setCategoryInfo] = useState<any>(null); const [categoryInfo, setCategoryInfo] = useState<any>(null);
const [buildData, setBuildData] = useState<any[]>([]);
const storageKey = "buildpc"; const getStorageKey = () => `buildpc_tab_${activeTab}`;
// Load khi mount
useEffect(() => {
const oldData = localStorage.getItem(storageKey);
setBuildData(oldData ? JSON.parse(oldData) : []);
}, []);
// Nghe event update
useEffect(() => {
const handleUpdate = () => {
const oldData = localStorage.getItem(storageKey);
setBuildData(oldData ? JSON.parse(oldData) : []);
};
window.addEventListener("buildpcUpdated", handleUpdate);
return () => window.removeEventListener("buildpcUpdated", handleUpdate);
}, []);
// Set category info khi mở modal
useEffect(() => { useEffect(() => {
if (selectedCategory) { if (selectedCategory) {
const filterCategory = categoryDetail.find( const filterCategory = categoryDetail.find(
@@ -39,15 +26,36 @@ export default function BuildPCCategories({ categories }: any) {
} }
}, [selectedCategory]); }, [selectedCategory]);
// Xoá sản phẩm const handleSaveProduct = (rowId: number, product: any) => {
const newData = [
...buildData.filter((b: any) => b.rowId !== rowId), // loại bỏ row cũ
{
rowId,
info: [product]
}
];
localStorage.setItem(getStorageKey(), JSON.stringify(newData));
setBuildData(newData);
window.dispatchEvent(new Event("buildpcUpdated"));
};
// ==============================
// REMOVE
// ==============================
const handleRemove = (rowId: number) => { const handleRemove = (rowId: number) => {
const newData = buildData.filter((b: any) => b.rowId !== rowId); const newData = buildData.filter((b: any) => b.rowId !== rowId);
localStorage.setItem(storageKey, JSON.stringify(newData)); localStorage.setItem(getStorageKey(), JSON.stringify(newData));
setBuildData(newData); setBuildData(newData);
}; };
// Đổi số lượng // ==============================
// QUANTITY
// ==============================
const handleQuantityChange = (rowId: number, quantity: number) => { const handleQuantityChange = (rowId: number, quantity: number) => {
const newData = buildData.map((b: any) => { const newData = buildData.map((b: any) => {
if (b.rowId === rowId) { if (b.rowId === rowId) {
return { return {
@@ -63,7 +71,7 @@ export default function BuildPCCategories({ categories }: any) {
return b; return b;
}); });
localStorage.setItem(storageKey, JSON.stringify(newData)); localStorage.setItem(getStorageKey(), JSON.stringify(newData));
setBuildData(newData); setBuildData(newData);
}; };
@@ -83,24 +91,34 @@ export default function BuildPCCategories({ categories }: any) {
</p> </p>
<div className="item-drive-info"> <div className="item-drive-info">
{ product
{product ? ( ? (
<SelectedItemRow <SelectedItemRow
product={product} product={product}
rowId={item.id} rowId={item.id}
onRemove={handleRemove} onRemove={handleRemove}
onQuantityChange={handleQuantityChange} onQuantityChange={handleQuantityChange}
onEdit={(rowId) => setSelectedCategory(rowId)} onEdit={(rowId: number) => setSelectedCategory(rowId)}
/> />
) : ( ) : (
<a <button
href="#js-modal-popup" type="button"
data-fancybox="" className="open-selection"
className="open-selection" onClick={(e) => {
onClick={() => setSelectedCategory(item.id)} e.preventDefault();
setSelectedCategory(item.id);
Fancybox.show([
{
src: "#js-modal-popup",
type: "inline",
},
]);
}}
> >
<i className="bx bx-plus" /> Chọn {item.name} <i className="bx bx-plus" /> Chọn {item.name}
</a> </button>
)} )}
</div> </div>
@@ -114,7 +132,10 @@ export default function BuildPCCategories({ categories }: any) {
id="js-modal-popup" id="js-modal-popup"
style={{ display: 'none', padding: 0 }} style={{ display: 'none', padding: 0 }}
> >
<ModalContent item={categoryInfo} /> <ModalContent
item={categoryInfo}
onSave={handleSaveProduct}
/>
</div> </div>
</> </>
); );

View File

@@ -1,7 +1,8 @@
'use client'; 'use client';
import Link from "next/link"; import Link from "next/link";
import useFancybox from '@/hooks/useFancyBox'; import useFancybox from '@/hooks/useFancyBox';
import { useState } from "react"; import { Fancybox } from "@fancyapps/ui/dist/fancybox/";
import { useState, useEffect, useMemo } from "react";
import { buildPcData } from "@/data/buildpc"; import { buildPcData } from "@/data/buildpc";
import Content from "./Content"; import Content from "./Content";
@@ -10,16 +11,48 @@ import BuildPcPopups from "./Popups";
import Promotion from "./Promotion"; import Promotion from "./Promotion";
import Buttons from "./Buttons"; import Buttons from "./Buttons";
export default function BuildPc() { export default function BuildPc() {
const [fancyboxRef] = useFancybox({}); const [fancyboxRef] = useFancybox({});
const [activeTab, setActiveTab] = useState(1);
const [buildData, setBuildData] = useState<any[]>([]);
const [activeTab, setActiveTab] = useState(1); const getStorageKey = (tab: number) => `buildpc_tab_${tab}`;
// Load data khi đổi tab
useEffect(() => {
const oldData = localStorage.getItem(getStorageKey(activeTab));
setBuildData(oldData ? JSON.parse(oldData) : []);
}, [activeTab]);
const handleTabChange = (tabIndex: number) => { const handleTabChange = (tabIndex: number) => {
setActiveTab(tabIndex); setActiveTab(tabIndex);
};
}
const handleRebuild = () => {
const storageKey = getStorageKey(activeTab);
localStorage.removeItem(storageKey);
setBuildData([]);
Fancybox.close();
};
const totalPrice = useMemo(() => {
return buildData.reduce((total, item) => {
const product = item?.info?.[0];
if (!product) return total;
const price = Number(product.price) || 0;
const quantity = Number(product.quantity) || 1;
return total + (price * quantity);
}, 0);
}, [buildData]);
return ( return (
<> <>
@@ -60,7 +93,7 @@ export default function BuildPc() {
</Link> </Link>
</div> </div>
<div className="btn-buildpc-group mb-6" id="js-buildpc-tab"> <div className="btn-buildpc-group mb-6">
{ {
[1, 2, 3, 4, 5].map((item) => ( [1, 2, 3, 4, 5].map((item) => (
<button <button
@@ -76,21 +109,26 @@ export default function BuildPc() {
</div> </div>
<div className="buildpc-detail-group gap-4"> <div className="buildpc-detail-group gap-4">
<Promotion /> <Promotion total={totalPrice} />
<Link <Link
href="#popup-rebuild_config" href="#popup-rebuild_config"
data-fancybox data-fancybox=""
className="flex items-center gap-3 bg-btn text-white rounded-[30px] leading-10 text-16 font-500 px-6" className="flex items-center gap-3 bg-btn text-white rounded-[30px] leading-10 text-16 font-500 px-6"
> >
LÀM MỚI <i className="bx bx-rotate-ccw" /> LÀM MỚI <i className="bx bx-rotate-ccw" />
</Link> </Link>
</div> </div>
<BuildPCCategories categories={buildPcData.category_config} /> <BuildPCCategories
categories={ buildPcData.category_config }
activeTab={ activeTab }
buildData={ buildData }
setBuildData={ setBuildData }
/>
<div className="flex flex-wrap items-center justify-between text-18 font-500 leading-6"> <div className="flex flex-wrap items-center justify-between text-18 font-500 leading-6">
<Promotion /> <Promotion total={totalPrice} />
<Buttons /> <Buttons />
</div> </div>
@@ -100,7 +138,9 @@ export default function BuildPc() {
<Content /> <Content />
<BuildPcPopups /> <BuildPcPopups
onRebuild={handleRebuild}
/>
</div> </div>
</> </>
) )

View File

@@ -1,15 +1,9 @@
'use client'; 'use client';
import Link from "next/link"; import Link from "next/link";
import { Fancybox } from "@fancyapps/ui/dist/fancybox/";
export default function ProductItem({ item, rowId }: any) { export default function ProductItem({ item, rowId, onSelect }: any) {
const handleBuy = () => { const handleSelect = () => {
if (typeof window === "undefined") return;
const storageKey = "buildpc";
const oldData = localStorage.getItem(storageKey);
const parsed = oldData ? JSON.parse(oldData) : [];
const productData = { const productData = {
id : item.productId, id : item.productId,
@@ -22,24 +16,7 @@ export default function ProductItem({ item, rowId }: any) {
warranty : item.warranty || '' warranty : item.warranty || ''
}; };
const buildIndex = parsed.findIndex((b: any) => b.rowId === rowId); onSelect(productData);
if (buildIndex !== -1) {
parsed[buildIndex].info = [productData];
} else {
parsed.push({
rowId: rowId,
info: [productData]
});
}
localStorage.setItem(storageKey, JSON.stringify(parsed));
// báo cho component cha
window.dispatchEvent(new Event("buildpcUpdated"));
// đóng popup
Fancybox.close();
}; };
@@ -89,7 +66,7 @@ export default function ProductItem({ item, rowId }: any) {
</div> </div>
<button <button
onClick={handleBuy} onClick={handleSelect}
className="btn-buy p-btn bx bx-plus bg-btn text-white rounded-full w-9 h-9 text-20" className="btn-buy p-btn bx bx-plus bg-btn text-white rounded-full w-9 h-9 text-20"
/> />
</div> </div>

View File

@@ -1,5 +1,5 @@
'use client'; 'use client';
import { Fancybox } from "@fancyapps/ui/dist/fancybox/";
interface Props { interface Props {
product: any; product: any;
rowId: number; rowId: number;
@@ -17,75 +17,83 @@ export default function SelectedItemRow({
}: Props) { }: Props) {
return ( return (
<div className="js-item-row"> <div className="contain-item-drive">
<div className="contain-item-drive">
<a href={product.url} target="_blank" className="item-img"> <a href={product.url} target="_blank" className="item-img">
<img src={product.image} alt={product.name} /> <img src={product.image} alt={product.name} />
</a> </a>
<div className="item-text"> <div className="item-text">
<div className="item-left"> <div className="item-left">
<a href={product.url} target="_blank" className="item-name"> <a href={product.url} target="_blank" className="item-name">
{product.name} {product.name}
</a> </a>
<p> <p>
<span className="font-500">- Kho hàng:</span> <span className="font-500">- Kho hàng:</span>
{product.quantity > 0 ? "Còn hàng" : "Hết hàng"} {product.quantity > 0 ? "Còn hàng" : "Hết hàng"}
</p> </p>
<p> <p>
<span className="font-500">- Bảo hành:</span> <span className="font-500">- Bảo hành:</span>
{product.warranty || "—"} {product.warranty || "—"}
</p> </p>
</div> </div>
<div className="item-right"> <div className="item-right">
<div className="item-quantity-group"> <div className="item-quantity-group">
<b>{product.price.toLocaleString()}</b> <b>{product.price.toLocaleString()}</b>
<div className="flex items-center" style={{ display: "flex", gap: "10px" }}> <div className="flex items-center" style={{ display: "flex", gap: "10px" }}>
<span>x</span> <span>x</span>
<input <input
type="number" type="number"
value={product.quantity} className="item-quantity"
className="item-quantity" value={product.quantity}
min={1} min={1}
max={50} max={50}
onChange={(e) => onChange={(e) =>
onQuantityChange(rowId, Number(e.target.value)) onQuantityChange(rowId, Number(e.target.value))
} }
/>
<span>=</span>
</div>
<b className="item-price">
{(product.price * product.quantity).toLocaleString()}
</b>
</div>
<div className="item-button-group">
<a href="#js-modal-popup" data-fancybox
type="button"
title="Thay đổi"
className="btn-action_seclect show-popup_select bx bx-edit"
onClick={() => onEdit(rowId)}
/> />
<button <span>=</span>
type="button"
title="Xóa"
className="btn-action_seclect delete_select bx bx-trash remove-item"
onClick={() => onRemove(rowId)}
/>
</div> </div>
<b className="item-price">
{(product.price * product.quantity).toLocaleString()}
</b>
</div> </div>
<div className="item-button-group">
<button
type="button"
className="btn-action_seclect show-popup_select bx bx-edit"
onClick={(e) => {
e.preventDefault();
onEdit(rowId);
Fancybox.show([
{
src: "#js-modal-popup",
type: "inline",
},
]);
}}
/>
<button
type="button"
title="Xóa"
className="btn-action_seclect delete_select bx bx-trash remove-item"
onClick={() => onRemove(rowId)}
/>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,9 +1,13 @@
import { Fancybox } from "@fancyapps/ui/dist/fancybox/";
import Filter from "./Filter"; import Filter from "./Filter";
import ProductItem from "./Products" import ProductItem from "./Products"
import Sort from "./Sort"; import Sort from "./Sort";
import Paing from "./Paging"; import Paing from "./Paging";
export default function ModalContent({ item }: any) { export default function ModalContent({
item,
onSave
}: any) {
if (!item) return null; if (!item) return null;
const { const {
@@ -69,15 +73,18 @@ export default function ModalContent({ item }: any) {
</div> </div>
<div className="popup-product-list"> <div className="popup-product-list">
{ {product_list.map((product: any) => (
product_list.map((item: any) => ( <ProductItem
<ProductItem key={product.id}
rowId={id} rowId={id}
key={item.id} item={product}
item={item} onSelect={(selectedProduct: any) => {
/> onSave(id, selectedProduct);
))
} Fancybox.close()
}}
/>
))}
</div> </div>
</div> </div>
</> </>

View File

@@ -15,10 +15,6 @@ export default function ProductSearch() {
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const search_query = searchParams.get('q') || ""; const search_query = searchParams.get('q') || "";
const {
sort_by_collection,
} = productCategory.current_category;
const totalProduct = useMemo(() => { const totalProduct = useMemo(() => {
return productList.flatMap((item: any) => item.list); return productList.flatMap((item: any) => item.list);
}, []); }, []);
@@ -70,7 +66,10 @@ export default function ProductSearch() {
<div className="col-right-group w-[968px]"> <div className="col-right-group w-[968px]">
<div className="box-item rounded-[24px] bg-white px-6 pt-4 pb-8 mb-4"> <div className="box-item rounded-[24px] bg-white px-6 pt-4 pb-8 mb-4">
<SortByCollection sort={sort_by_collection} total={total} /> <SortByCollection
sort={productCategory.current_category.sort_by_collection}
total={total}
/>
<div className="product-holder grid grid-cols-4 gap-x-5 gap-y-8 mb-6"> <div className="product-holder grid grid-cols-4 gap-x-5 gap-y-8 mb-6">
{currentData.map((item: any) => ( {currentData.map((item: any) => (
@@ -89,8 +88,8 @@ export default function ProductSearch() {
</div> </div>
</div> </div>
)} )
}
</div> </div>
) )
} }

View File

@@ -38,7 +38,7 @@
.buildpc-page .contain-item-drive .item-quantity-group > span{text-align:center;padding:0 5px} .buildpc-page .contain-item-drive .item-quantity-group > span{text-align:center;padding:0 5px}
.buildpc-page .contain-item-drive .item-quantity-group > b {width: calc(50% - 65px)} .buildpc-page .contain-item-drive .item-quantity-group > b {width: calc(50% - 65px)}
.buildpc-page .contain-item-drive .item-quantity-group .item-price{color:#f71400} .buildpc-page .contain-item-drive .item-quantity-group .item-price{color:#f71400}
.buildpc-page .contain-item-drive .btn-action_seclect{cursor: pointer;border: 1px solid #DFE4EC;border-radius: 50%;width: 40px;height: 40px;line-height: 39px;font-size: 20px;color: #0678DB;background: #EAF1FF;} .buildpc-page .contain-item-drive .btn-action_seclect{cursor: pointer;border: 1px solid #DFE4EC;border-radius: 50%;width: 40px;height: 40px;line-height: 39px;font-size: 20px;color: #0678DB;background: #EAF1FF;text-align: center;}
.buildpc-page .contain-item-drive .delete_select{border-color:#E7D9D9;background: #F8F3F3;color: #BE1F2D} .buildpc-page .contain-item-drive .delete_select{border-color:#E7D9D9;background: #F8F3F3;color: #BE1F2D}
.buildpc-page .contain-item-drive input::-webkit-outer-spin-button,.buildpc-page .contain-item-drive input::-webkit-inner-spin-button{-webkit-appearance:none} .buildpc-page .contain-item-drive input::-webkit-outer-spin-button,.buildpc-page .contain-item-drive input::-webkit-inner-spin-button{-webkit-appearance:none}
.buildpc-page .contain-item-drive input[type=number]{-moz-appearance:textfield} .buildpc-page .contain-item-drive input[type=number]{-moz-appearance:textfield}