category api - dashboard page
This commit is contained in:
@@ -3,9 +3,11 @@ import React, { useState, useEffect, useRef } from 'react';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import { ShoppingCart, Trash2 } from "lucide-react";
|
||||
|
||||
import { Category } from '@/public/src/types/categories';
|
||||
|
||||
import { ShoppingCart, Trash2, Search, X, ChevronDown } from "lucide-react";
|
||||
import { useCart } from './context/cartcontext';
|
||||
import { Search, X } from 'lucide-react';
|
||||
import { products } from '@/lib/data';
|
||||
import '@/public/src/css/header.css';
|
||||
|
||||
@@ -16,13 +18,16 @@ const topBarLinks = [
|
||||
|
||||
const mainNavLinks = [
|
||||
{ label: "صفحه اصلی", href: "/" },
|
||||
{ label: "خرید بلبرینگ", href: "/products" },
|
||||
{ label: "محصولات", href: "/products" },
|
||||
{ label: "مقالات", href: "/blog" },
|
||||
{ label: "درباره ما", href: "/about" },
|
||||
{ label: "تماس با ما", href: "/contact" },
|
||||
];
|
||||
|
||||
export function Header() {
|
||||
// 1. استخراج دستهبندیهای منحصر به فرد از محصولات
|
||||
const uniqueCategories = Array.from(new Set(products.map(p => p.category)));
|
||||
|
||||
export function Header({ categories }: { categories: Category[] }) {
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
const pathname = usePathname();
|
||||
const { cart, removeFromCart } = useCart();
|
||||
@@ -31,8 +36,8 @@ export function Header() {
|
||||
const [filteredProducts, setFilteredProducts] = useState<any[]>([]);
|
||||
const searchRef = useRef<HTMLDivElement>(null);
|
||||
const router = useRouter();
|
||||
|
||||
// محاسبه قیمت کل برای سبد خرید (اصلاح شده برای حذف ویرگولهای احتمالی)
|
||||
const safeCategories = categories || [];
|
||||
const rootCategories = safeCategories.filter(cat => cat.parent === null);
|
||||
const parsePrice = (priceStr?: string | null) => {
|
||||
if (!priceStr) return 0;
|
||||
return Number(priceStr.toString().replace(/,/g, ''));
|
||||
@@ -40,13 +45,6 @@ export function Header() {
|
||||
|
||||
const totalPrice = cart.reduce((total, item) => total + (parsePrice(item.price) * item.quantity), 0);
|
||||
|
||||
const isLinkActive = (href: string): boolean => {
|
||||
if (href === "/") {
|
||||
return pathname === "/";
|
||||
}
|
||||
return pathname === href;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (searchRef.current && !searchRef.current.contains(event.target as Node)) {
|
||||
@@ -57,7 +55,6 @@ export function Header() {
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||
}, []);
|
||||
|
||||
// فیلتر کردن محصولات در لحظه تایپ
|
||||
useEffect(() => {
|
||||
if (searchTerm.trim().length > 1) {
|
||||
const results = products.filter(p =>
|
||||
@@ -65,7 +62,7 @@ export function Header() {
|
||||
p.brand?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
p.d?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
p.l?.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
).slice(0, 6); // نمایش فقط ۶ نتیجه اول در دراپداون
|
||||
).slice(0, 6);
|
||||
setFilteredProducts(results);
|
||||
setShowResults(true);
|
||||
} else {
|
||||
@@ -82,10 +79,12 @@ export function Header() {
|
||||
}
|
||||
};
|
||||
|
||||
// ... (بقیه کدهای شما از اینجا تا بخش <nav> دست نخورده باقی میماند)
|
||||
// ... (بخشهای top bar, main bar, search, cart, etc. بدون تغییر هستند)
|
||||
return (
|
||||
<header>
|
||||
<div>
|
||||
|
||||
{/* ... کدهای مربوط به mobile slider, top bar, main bar ... */}
|
||||
{/* overlay */}
|
||||
{menuOpen && (
|
||||
<div
|
||||
@@ -244,48 +243,30 @@ export function Header() {
|
||||
<div className="absolute top-full left-0 right-0 mt-2 bg-white border border-gray-200 rounded-2xl shadow-xl z-[100] overflow-hidden animate-in fade-in slide-in-from-top-2 duration-200">
|
||||
<div className="p-2 flex flex-col">
|
||||
{filteredProducts.map((product) => {
|
||||
// تبدیل نام محصول به اسلاگ (جایگزینی فاصلهها با خط تیره)
|
||||
const productSlug = product.title.replace(/\s+/g, '-');
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={product.id}
|
||||
// اصلاح URL برای تطابق با /products/[id]/[slug]
|
||||
href={`/products/${product.id}/${encodeURIComponent(productSlug)}`}
|
||||
onClick={() => setShowResults(false)}
|
||||
className="flex items-center gap-3 p-2 hover:bg-gray-50 rounded-xl transition group"
|
||||
>
|
||||
<div className="bg-gray-100 p-1 rounded-lg shrink-0 w-10 h-10 flex items-center justify-center">
|
||||
<Image
|
||||
src={product.image}
|
||||
alt={product.title}
|
||||
width={32}
|
||||
height={32}
|
||||
className="object-contain"
|
||||
/>
|
||||
<Image src={product.image} alt={product.title} width={32} height={32} className="object-contain" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0 text-right">
|
||||
<p className="text-[11px] font-bold text-gray-800 truncate group-hover:text-blue-600 transition-colors">
|
||||
{product.title}
|
||||
</p>
|
||||
<p className="text-[11px] font-bold text-gray-800 truncate group-hover:text-blue-600 transition-colors">{product.title}</p>
|
||||
<p className="text-[9px] text-gray-500">{product.brand}</p>
|
||||
</div>
|
||||
<div className="text-[10px] font-bold text-gray-700 bg-gray-50 px-2 py-1 rounded-md shrink-0">
|
||||
{/* اعمال منطق حذف کاما و تبدیل به عدد در صورت رشته بودن قیمت */}
|
||||
{product.price
|
||||
? `${Number(product.price.toString().replace(/,/g, '')).toLocaleString('fa-IR')} ت`
|
||||
: 'استعلام'}
|
||||
{product.price ? `${Number(product.price.toString().replace(/,/g, '')).toLocaleString('fa-IR')} ت` : 'استعلام'}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* دکمه مشاهده همه نتایج */}
|
||||
<button
|
||||
onClick={handleSearch}
|
||||
className="w-full bg-gray-50 py-2.5 text-[11px] text-blue-600 font-bold border-t border-gray-100 hover:bg-blue-50 transition-colors"
|
||||
>
|
||||
<button onClick={handleSearch} className="w-full bg-gray-50 py-2.5 text-[11px] text-blue-600 font-bold border-t border-gray-100 hover:bg-blue-50 transition-colors">
|
||||
مشاهده تمام نتایج برای "{searchTerm}"
|
||||
</button>
|
||||
</div>
|
||||
@@ -300,22 +281,16 @@ export function Header() {
|
||||
|
||||
<Link href="/cart" className="flex items-center justify-center p-2.5 bg-white border border-gray-300/60 rounded-xl text-gray-700 hover:bg-gray-50 transition relative z-10">
|
||||
<ShoppingCart className="w-4 h-4 text-gray-500" strokeWidth={1.8} />
|
||||
|
||||
{/* بج (Badge) تعداد محصولات */}
|
||||
{cart.length > 0 && (
|
||||
<span className="absolute -top-1.5 -right-1.5 bg-[#f92a35] text-white text-[10px] font-bold w-4 h-4 flex items-center justify-center rounded-full shadow-sm">
|
||||
{cart.reduce((total, item) => total + item.quantity, 0)}
|
||||
</span>
|
||||
|
||||
)}
|
||||
</Link>
|
||||
|
||||
{/* یک پل نامرئی برای جلوگیری از قطع شدن هاور */}
|
||||
<div className="absolute top-full left-0 w-full h-3 bg-transparent hidden group-hover:block"></div>
|
||||
|
||||
{/* منوی کشویی سبد خرید */}
|
||||
<div className="absolute top-[calc(100%+12px)] left-0 w-80 bg-white border border-gray-200 rounded-2xl shadow-xl hidden group-hover:flex flex-col overflow-hidden z-50">
|
||||
|
||||
{cart.length === 0 ? (
|
||||
<div className="p-6 text-center text-sm text-gray-500 flex flex-col items-center gap-2">
|
||||
<ShoppingCart className="w-8 h-8 text-gray-200" />
|
||||
@@ -325,46 +300,25 @@ export function Header() {
|
||||
<>
|
||||
<div className="flex justify-between items-center p-4 border-b border-gray-100 bg-gray-50/50">
|
||||
<span className="text-xs font-semibold text-gray-500">{cart.length} کالا</span>
|
||||
<Link href="/cart" className="text-xs text-blue-500 hover:text-blue-700 font-medium transition">
|
||||
مشاهده سبد خرید
|
||||
</Link>
|
||||
<Link href="/cart" className="text-xs text-blue-500 hover:text-blue-700 font-medium transition">مشاهده سبد خرید</Link>
|
||||
</div>
|
||||
|
||||
<div className="max-h-64 overflow-y-auto p-2 flex flex-col gap-1 custom-scrollbar">
|
||||
{cart.slice(0, 3).map((item) => {
|
||||
// محاسبه قیمت بر اساس تعداد (قیمت واحد × تعداد)
|
||||
const itemTotal = item.price
|
||||
? (Number(item.price.toString().replace(/,/g, '')) * item.quantity).toLocaleString('fa-IR')
|
||||
: null;
|
||||
|
||||
const itemTotal = item.price ? (Number(item.price.toString().replace(/,/g, '')) * item.quantity).toLocaleString('fa-IR') : null;
|
||||
return (
|
||||
<div key={item.id} className="flex items-center gap-3 p-2 hover:bg-gray-50 rounded-xl transition group/item relative">
|
||||
<div className="bg-white border border-gray-100 p-1 rounded-lg shrink-0">
|
||||
<Image src={item.image} alt={item.title} width={40} height={40} className="object-contain w-10 h-10" />
|
||||
</div>
|
||||
|
||||
<div className="flex-1 min-w-0">
|
||||
<h4 className="text-xs font-semibold text-gray-800 truncate">{item.title}</h4>
|
||||
|
||||
{/* بخش جدید: نمایش قیمت و تعداد در کنار هم */}
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
<span className="text-[10px] text-gray-500 font-medium">
|
||||
{itemTotal ? `${itemTotal} تومان` : 'استعلام'}
|
||||
</span>
|
||||
|
||||
{/* بج نمایش تعداد محصول */}
|
||||
<span className="text-[9px] font-bold text-blue-600 bg-blue-50 px-1.5 py-0.5 rounded-md border border-blue-100">
|
||||
{item.quantity} عدد
|
||||
</span>
|
||||
<span className="text-[10px] text-gray-500 font-medium">{itemTotal ? `${itemTotal} تومان` : 'استعلام'}</span>
|
||||
<span className="text-[9px] font-bold text-blue-600 bg-blue-50 px-1.5 py-0.5 rounded-md border border-blue-100">{item.quantity} عدد</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
removeFromCart(item.id);
|
||||
}}
|
||||
onClick={(e) => { e.preventDefault(); e.stopPropagation(); removeFromCart(item.id); }}
|
||||
className="text-gray-300 hover:text-red-500 transition p-1.5 opacity-0 group-hover/item:opacity-100"
|
||||
title="حذف"
|
||||
>
|
||||
@@ -373,49 +327,70 @@ export function Header() {
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
||||
{cart.length > 3 && (
|
||||
<p className="text-center text-[10px] text-gray-400 py-2 border-t border-gray-50 mt-1">
|
||||
و {cart.length - 3} کالای دیگر...
|
||||
</p>
|
||||
)}
|
||||
{cart.length > 3 && (<p className="text-center text-[10px] text-gray-400 py-2 border-t border-gray-50 mt-1"> و {cart.length - 3} کالای دیگر...</p>)}
|
||||
</div>
|
||||
|
||||
|
||||
<div className="p-4 bg-white border-t border-gray-100">
|
||||
<div className="flex justify-between items-center mb-3">
|
||||
<span className="text-xs text-gray-500">مبلغ قابل پرداخت:</span>
|
||||
<span className="text-sm font-bold text-gray-800">
|
||||
{totalPrice > 0 ? `${totalPrice.toLocaleString('fa-IR')} تومان` : 'استعلام'}
|
||||
</span>
|
||||
<span className="text-sm font-bold text-gray-800">{totalPrice > 0 ? `${totalPrice.toLocaleString('fa-IR')} تومان` : 'استعلام'}</span>
|
||||
</div>
|
||||
<Link href="/cart" className="flex items-center justify-center w-full py-2 bg-[#ffb900] hover:bg-[#e6a600] text-black text-xs font-semibold rounded-xl transition-colors">
|
||||
ثبت سفارش
|
||||
</Link>
|
||||
<Link href="/cart" className="flex items-center justify-center w-full py-2 bg-[#ffb900] hover:bg-[#e6a600] text-black text-xs font-semibold rounded-xl transition-colors">ثبت سفارش</Link>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button className="flex items-center gap-2 px-3 py-2.5 bg-white border border-gray-300/60 rounded-xl text-xs text-gray-700 hover:bg-gray-50 transition">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" className="w-4 h-4 text-gray-500">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.5 20.25a7.5 7.5 0 0115 0" />
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" className="w-4 h-4 text-gray-500"><path strokeLinecap="round" strokeLinejoin="round" d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.5 20.25a7.5 7.5 0 0115 0" /></svg>
|
||||
<span>ورود / عضویت</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* nav */}
|
||||
{/* nav -- این بخش تغییر کرده است */}
|
||||
<div className='justify-center flex'>
|
||||
<div className='flex max-w-6xl container mx-auto px-4 justify-between py-2 items-center'>
|
||||
<nav className="flex space-x-6 rtl:space-x-4 text-gray-700 text-xs w-full lg:w-auto lg:order-none">
|
||||
{mainNavLinks.map((link) => (
|
||||
<a key={link.label} href={link.href} className="hover:text-blue-600 transition duration-200 font-medium hidden md:block">
|
||||
<nav className="hidden md:flex items-center space-x-6 rtl:space-x-4 text-gray-700 text-xs w-full lg:w-auto lg:order-none">
|
||||
|
||||
{/* لینک صفحه اصلی */}
|
||||
<Link href="/" className="hover:text-blue-600 transition duration-200 font-medium">صفحه اصلی</Link>
|
||||
|
||||
<div className="relative group">
|
||||
<div className="flex items-center gap-1 cursor-pointer hover:text-blue-600 transition duration-200 font-medium">
|
||||
<span>دسته بندی محصولات</span>
|
||||
<ChevronDown size={14} className="transition-transform duration-200 group-hover:rotate-180" />
|
||||
</div>
|
||||
|
||||
<div className="absolute top-full right-0 w-full h-4 bg-transparent hidden group-hover:block"></div>
|
||||
|
||||
<div className="absolute top-[calc(100%+16px)] right-0 w-56 bg-white border border-gray-200 rounded-xl shadow-lg p-2 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 flex flex-col gap-1 z-50">
|
||||
|
||||
{rootCategories.length > 0 ? (
|
||||
rootCategories.map((category) => (
|
||||
<Link
|
||||
key={category.id}
|
||||
href={`/category/${category.slug}`}
|
||||
className="block w-full text-right px-3 py-2 text-xs font-medium text-gray-600 hover:bg-gray-100 hover:text-blue-600 rounded-md transition-colors"
|
||||
>
|
||||
{category.name}
|
||||
</Link>
|
||||
))
|
||||
) : (
|
||||
<div className="text-center text-xs text-gray-500 py-2">
|
||||
در حال بارگذاری...
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* نمایش بقیه لینکها */}
|
||||
{mainNavLinks.filter(link => link.href !== "/").map((link) => (
|
||||
<Link key={link.label} href={link.href} className="hover:text-blue-600 transition duration-200 font-medium">
|
||||
{link.label}
|
||||
</a>
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { Phone, Plus, ShoppingCart, Minus, Trash2 } from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import Link from 'next/link';
|
||||
import { useCart, Product } from "./context/cartcontext"; // مسیر ایمپورت را با پروژه خود چک کنید
|
||||
import { useCart, Product } from "./context/cartcontext";
|
||||
|
||||
interface ProductCardProps {
|
||||
product: Product;
|
||||
@@ -77,50 +77,59 @@ export default function ProductCard({ product }: ProductCardProps) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* بخش قیمت و دکمه خرید */}
|
||||
{product.price ? (
|
||||
<div className="flex justify-between mt-3 items-center min-h-[32px]">
|
||||
<span className="text-[0.85em] font-bold">
|
||||
<span className="text-[0.85em] font-bold text-gray-800">
|
||||
{formattedPrice} تومان
|
||||
</span>
|
||||
|
||||
{quantity > 0 ? (
|
||||
<div
|
||||
className="flex items-center gap-3 bg-white border border-gray-200 rounded-lg py-1 px-2 shadow-sm"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{/* بررسی موجودی برای نمایش دکمه سبد خرید */}
|
||||
{product.stock ? (
|
||||
quantity > 0 ? (
|
||||
<div
|
||||
className="flex items-center gap-3 bg-white border border-gray-200 rounded-lg py-1 px-2 shadow-sm"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<button
|
||||
onClick={handleIncrease}
|
||||
className="text-gray-500 hover:text-green-600 transition-colors"
|
||||
>
|
||||
<Plus size={16} />
|
||||
</button>
|
||||
|
||||
<span className="text-xs font-bold text-gray-800 w-3 text-center">
|
||||
{quantity}
|
||||
</span>
|
||||
|
||||
<button
|
||||
onClick={handleDecrease}
|
||||
className="text-gray-500 hover:text-red-500 transition-colors"
|
||||
>
|
||||
{quantity === 1 ? <Trash2 size={16} /> : <Minus size={16} />}
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
onClick={handleIncrease}
|
||||
className="text-gray-500 hover:text-green-600 transition-colors"
|
||||
className="rounded-lg border border-[#e6e6e6] bg-gray-50 flex items-center p-2 text-gray-600 hover:bg-gray-200 transition-colors"
|
||||
title="افزودن به سبد خرید"
|
||||
>
|
||||
<Plus size={16} />
|
||||
<ShoppingCart size={16} />
|
||||
</button>
|
||||
|
||||
<span className="text-xs font-bold text-gray-800 w-3 text-center">
|
||||
{quantity}
|
||||
</span>
|
||||
|
||||
<button
|
||||
onClick={handleDecrease}
|
||||
className="text-gray-500 hover:text-red-500 transition-colors"
|
||||
>
|
||||
{quantity === 1 ? <Trash2 size={16} /> : <Minus size={16} />}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<button
|
||||
onClick={handleIncrease}
|
||||
className="rounded-lg border border-[#e6e6e6] bg-gray-50 flex items-center p-2 text-gray-600 hover:bg-gray-200 transition-colors"
|
||||
title="افزودن به سبد خرید"
|
||||
>
|
||||
<ShoppingCart size={16} />
|
||||
</button>
|
||||
// جایگزین دکمه خرید وقتی محصول ناموجود است
|
||||
<span className="text-[10px] text-red-500 font-medium bg-red-50 px-2 py-1 rounded-md">
|
||||
عدم موجودی
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<span className="flex justify-between mt-3 text-[0.85em] font-bold items-center gap-1 min-h-[32px]">
|
||||
<span className="flex justify-between mt-3 text-[0.85em] font-bold items-center gap-1 min-h-[32px] text-gray-700">
|
||||
استعلام
|
||||
<Phone className="mb-1" size={13} />
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user