140 lines
5.9 KiB
TypeScript
140 lines
5.9 KiB
TypeScript
import { useState, useEffect, useRef } from 'react';
|
||
import { Link, useLocation } from 'react-router-dom';
|
||
import { Users, ShoppingCart, Tag, Settings, TrendingUp, FolderTree, FileText, BookOpen, Heart, Package, LayoutDashboard, Languages, Image, Video, PhoneCall, Sparkles, MessageSquareQuote, FlaskConical, Building2, Globe, DollarSign, Sliders, MessageSquare, Receipt, CreditCard } from 'lucide-react';
|
||
import api from '../services/api';
|
||
|
||
const menuGroups = [
|
||
{
|
||
title: 'اصلی',
|
||
items: [
|
||
{ icon: LayoutDashboard, label: 'داشبورد', path: '/' },
|
||
{ icon: TrendingUp, label: 'گزارشات', path: '/reports' },
|
||
]
|
||
},
|
||
{
|
||
title: 'فروشگاه و تخصصی',
|
||
items: [
|
||
{ icon: ShoppingCart, label: 'سفارشات', path: '/orders' },
|
||
{ icon: Receipt, label: 'تراکنشها و لاگ پرداخت', path: '/transactions' },
|
||
{ icon: Package, label: 'محصولات', path: '/products' },
|
||
{ icon: FolderTree, label: 'دستهبندیها', path: '/categories' },
|
||
{ icon: Tag, label: 'کدهای تخفیف', path: '/coupons' },
|
||
{ icon: Sparkles, label: 'مشاور هوشمند', path: '/smart-advisor' },
|
||
{ icon: FileText, label: 'نسخههای پزشکی', path: '/prescriptions' },
|
||
]
|
||
},
|
||
{
|
||
title: 'مدیریت و همکاران B2B',
|
||
items: [
|
||
{ icon: Users, label: 'کاربران', path: '/users' },
|
||
{ icon: Heart, label: 'حیوانات (Pets)', path: '/pets' },
|
||
{ icon: Building2, label: 'مدیریت B2B & عمده', path: '/b2b' },
|
||
{ icon: PhoneCall, label: 'تماس با ما & اطلاعات', path: '/contact' },
|
||
]
|
||
},
|
||
{
|
||
title: 'محتوا و دانشنامه',
|
||
items: [
|
||
{ icon: Image, label: 'بنرها و اسلایدرها', path: '/banners' },
|
||
{ icon: FlaskConical, label: 'دانشنامه ترکیبات', path: '/ingredients' },
|
||
{ icon: MessageSquareQuote, label: 'نظرات و گواهیها', path: '/testimonials' },
|
||
{ icon: FileText, label: 'وبلاگ', path: '/blogs' },
|
||
{ icon: BookOpen, label: 'دانشنامه عمومی', path: '/wiki' },
|
||
{ icon: Video, label: 'مدیریت ویدئوها', path: '/videos' },
|
||
]
|
||
},
|
||
{
|
||
title: 'سیستم و تنظیمات',
|
||
items: [
|
||
{ icon: Settings, label: 'تنظیمات کلی', path: '/settings' },
|
||
{ icon: MessageSquare, label: 'تنظیمات پیامک', path: '/settings/sms' },
|
||
{ icon: Globe, label: 'تنظیمات سئو', path: '/settings/seo' },
|
||
{ icon: DollarSign, label: 'تنظیمات مالی & ارسال', path: '/settings/financial' },
|
||
{ icon: Sliders, label: 'تنظیمات سیستمی', path: '/settings/system' },
|
||
{ icon: Languages, label: 'متون رابط کاربری', path: '/ui-texts' },
|
||
{ icon: Image, label: 'مدیریت رسانه', path: '/media' },
|
||
]
|
||
}
|
||
];
|
||
|
||
interface SidebarProps {
|
||
isOpen: boolean;
|
||
setIsOpen: (val: boolean) => void;
|
||
}
|
||
|
||
export default function Sidebar({ isOpen, setIsOpen }: SidebarProps) {
|
||
const location = useLocation();
|
||
const [newOrdersCount, setNewOrdersCount] = useState(0);
|
||
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
||
|
||
useEffect(() => {
|
||
const fetchOrdersCount = async () => {
|
||
try {
|
||
const response = await api.get('/admin/dashboard/stats');
|
||
if (response.data?.success) {
|
||
setNewOrdersCount(response.data.data.newOrders);
|
||
}
|
||
} catch (err) {
|
||
console.error('Failed to fetch stats in sidebar', err);
|
||
}
|
||
};
|
||
fetchOrdersCount();
|
||
intervalRef.current = setInterval(fetchOrdersCount, 60000);
|
||
return () => {
|
||
if (intervalRef.current) clearInterval(intervalRef.current);
|
||
};
|
||
}, []);
|
||
|
||
return (
|
||
<>
|
||
{/* Mobile Backdrop */}
|
||
{isOpen && (
|
||
<div
|
||
className="fixed inset-0 bg-black/50 z-40 lg:hidden backdrop-blur-sm"
|
||
onClick={() => setIsOpen(false)}
|
||
/>
|
||
)}
|
||
|
||
<aside className={`w-64 bg-white border-l border-gray-200 h-screen fixed lg:fixed lg:right-0 lg:top-0 lg:bottom-0 flex flex-col font-vazir shadow-sm z-50 transform transition-transform duration-300 ease-in-out ${isOpen ? 'translate-x-0' : 'translate-x-full lg:translate-x-0'
|
||
}`}>
|
||
<div className="h-16 flex items-center justify-center border-b border-gray-200 shrink-0">
|
||
<h1 className="text-xl font-black text-purple-600">کانینا | ادمینپنل</h1>
|
||
</div>
|
||
|
||
<nav className="flex-1 overflow-y-auto py-4 px-3 space-y-6">
|
||
{menuGroups.map((group, idx) => (
|
||
<div key={idx}>
|
||
<h3 className="px-3 mb-2 text-xs font-black text-gray-400 uppercase tracking-wider">{group.title}</h3>
|
||
<div className="space-y-1">
|
||
{group.items.map((item) => {
|
||
const isActive = location.pathname === item.path || (item.path !== '/' && location.pathname.startsWith(item.path));
|
||
const Icon = item.icon;
|
||
|
||
return (
|
||
<Link
|
||
key={item.path}
|
||
to={item.path}
|
||
className={`flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all font-bold ${isActive
|
||
? 'bg-purple-50 text-purple-600'
|
||
: 'text-gray-500 hover:bg-gray-50 hover:text-gray-900'
|
||
}`}
|
||
>
|
||
<Icon className="w-5 h-5" />
|
||
<span>{item.label}</span>
|
||
{item.label === 'سفارشات' && newOrdersCount > 0 && (
|
||
<span className="mr-auto bg-purple-600 text-white text-[11px] font-black px-2 py-0.5 rounded-full min-w-[20px] text-center">
|
||
{newOrdersCount}
|
||
</span>
|
||
)}
|
||
</Link>
|
||
);
|
||
})}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</nav>
|
||
</aside>
|
||
</>
|
||
);
|
||
}
|