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

View File

@@ -1,12 +1,17 @@
export default function Promotion() {
export default function Promotion({ total }: any) {
return (
<div className="buildpc-info-group">
<p>
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>
<div className="buildpc-promotion-content js-buildpc-promotion-content"> {/* // Khuyến mại buildpc */} </div>
<div className="buildpc-promotion-content" />
</div>
)
);
}

View File

@@ -1,35 +1,22 @@
'use client';
import { useEffect, useState } from "react";
import { Fancybox } from "@fancyapps/ui/dist/fancybox/";
import { categoryDetail } from "@/data/buildpc/categoryDetail";
import ModalContent from "../modal";
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 [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(() => {
if (selectedCategory) {
const filterCategory = categoryDetail.find(
@@ -39,15 +26,36 @@ export default function BuildPCCategories({ categories }: any) {
}
}, [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 newData = buildData.filter((b: any) => b.rowId !== rowId);
localStorage.setItem(storageKey, JSON.stringify(newData));
localStorage.setItem(getStorageKey(), JSON.stringify(newData));
setBuildData(newData);
};
// Đổi số lượng
// ==============================
// QUANTITY
// ==============================
const handleQuantityChange = (rowId: number, quantity: number) => {
const newData = buildData.map((b: any) => {
if (b.rowId === rowId) {
return {
@@ -63,7 +71,7 @@ export default function BuildPCCategories({ categories }: any) {
return b;
});
localStorage.setItem(storageKey, JSON.stringify(newData));
localStorage.setItem(getStorageKey(), JSON.stringify(newData));
setBuildData(newData);
};
@@ -83,24 +91,34 @@ export default function BuildPCCategories({ categories }: any) {
</p>
<div className="item-drive-info">
{product ? (
{ product
? (
<SelectedItemRow
product={product}
rowId={item.id}
onRemove={handleRemove}
onQuantityChange={handleQuantityChange}
onEdit={(rowId) => setSelectedCategory(rowId)}
onEdit={(rowId: number) => setSelectedCategory(rowId)}
/>
) : (
<a
href="#js-modal-popup"
data-fancybox=""
<button
type="button"
className="open-selection"
onClick={() => setSelectedCategory(item.id)}
onClick={(e) => {
e.preventDefault();
setSelectedCategory(item.id);
Fancybox.show([
{
src: "#js-modal-popup",
type: "inline",
},
]);
}}
>
<i className="bx bx-plus" /> Chọn {item.name}
</a>
</button>
)}
</div>
@@ -114,7 +132,10 @@ export default function BuildPCCategories({ categories }: any) {
id="js-modal-popup"
style={{ display: 'none', padding: 0 }}
>
<ModalContent item={categoryInfo} />
<ModalContent
item={categoryInfo}
onSave={handleSaveProduct}
/>
</div>
</>
);

View File

@@ -1,7 +1,8 @@
'use client';
import Link from "next/link";
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 Content from "./Content";
@@ -10,16 +11,48 @@ import BuildPcPopups from "./Popups";
import Promotion from "./Promotion";
import Buttons from "./Buttons";
export default function BuildPc() {
const [fancyboxRef] = useFancybox({});
const [activeTab, setActiveTab] = useState(1);
const [buildData, setBuildData] = useState<any[]>([]);
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) => {
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 (
<>
@@ -60,7 +93,7 @@ export default function BuildPc() {
</Link>
</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) => (
<button
@@ -76,21 +109,26 @@ export default function BuildPc() {
</div>
<div className="buildpc-detail-group gap-4">
<Promotion />
<Promotion total={totalPrice} />
<Link
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"
>
LÀM MỚI <i className="bx bx-rotate-ccw" />
</Link>
</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">
<Promotion />
<Promotion total={totalPrice} />
<Buttons />
</div>
@@ -100,7 +138,9 @@ export default function BuildPc() {
<Content />
<BuildPcPopups />
<BuildPcPopups
onRebuild={handleRebuild}
/>
</div>
</>
)

View File

@@ -1,15 +1,9 @@
'use client';
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 = () => {
if (typeof window === "undefined") return;
const storageKey = "buildpc";
const oldData = localStorage.getItem(storageKey);
const parsed = oldData ? JSON.parse(oldData) : [];
const handleSelect = () => {
const productData = {
id : item.productId,
@@ -22,24 +16,7 @@ export default function ProductItem({ item, rowId }: any) {
warranty : item.warranty || ''
};
const buildIndex = parsed.findIndex((b: any) => b.rowId === rowId);
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();
onSelect(productData);
};
@@ -89,7 +66,7 @@ export default function ProductItem({ item, rowId }: any) {
</div>
<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"
/>
</div>

View File

@@ -1,5 +1,5 @@
'use client';
import { Fancybox } from "@fancyapps/ui/dist/fancybox/";
interface Props {
product: any;
rowId: number;
@@ -17,7 +17,6 @@ export default function SelectedItemRow({
}: Props) {
return (
<div className="js-item-row">
<div className="contain-item-drive">
<a href={product.url} target="_blank" className="item-img">
@@ -52,8 +51,8 @@ export default function SelectedItemRow({
<input
type="number"
value={product.quantity}
className="item-quantity"
value={product.quantity}
min={1}
max={50}
onChange={(e) =>
@@ -70,11 +69,21 @@ export default function SelectedItemRow({
</div>
<div className="item-button-group">
<a href="#js-modal-popup" data-fancybox
<button
type="button"
title="Thay đổi"
className="btn-action_seclect show-popup_select bx bx-edit"
onClick={() => onEdit(rowId)}
onClick={(e) => {
e.preventDefault();
onEdit(rowId);
Fancybox.show([
{
src: "#js-modal-popup",
type: "inline",
},
]);
}}
/>
<button
@@ -88,6 +97,5 @@ export default function SelectedItemRow({
</div>
</div>
</div>
</div>
);
}

View File

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

View File

@@ -15,10 +15,6 @@ 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);
}, []);
@@ -70,7 +66,10 @@ export default function ProductSearch() {
<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} />
<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">
{currentData.map((item: any) => (
@@ -89,8 +88,8 @@ export default function ProductSearch() {
</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 > b {width: calc(50% - 65px)}
.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 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}