2026-07-11 21:21:29 +03:30
|
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
|
|
import { motion } from "framer-motion";
|
|
|
|
|
|
import {
|
|
|
|
|
|
BadgeCheck,
|
|
|
|
|
|
Bell,
|
|
|
|
|
|
Bookmark,
|
|
|
|
|
|
ChevronDown,
|
|
|
|
|
|
ClipboardCheck,
|
|
|
|
|
|
Clock,
|
|
|
|
|
|
CreditCard,
|
|
|
|
|
|
FileText,
|
|
|
|
|
|
Folder,
|
|
|
|
|
|
Hash,
|
|
|
|
|
|
Inbox,
|
|
|
|
|
|
Layers,
|
|
|
|
|
|
LayoutGrid,
|
|
|
|
|
|
LogOut,
|
|
|
|
|
|
MessageSquare,
|
|
|
|
|
|
MessageSquareText,
|
|
|
|
|
|
MessagesSquare,
|
|
|
|
|
|
PhoneCall,
|
|
|
|
|
|
Pin,
|
|
|
|
|
|
Settings,
|
|
|
|
|
|
Sparkles,
|
|
|
|
|
|
UserPlus,
|
|
|
|
|
|
Users,
|
|
|
|
|
|
} from "lucide-react";
|
|
|
|
|
|
import * as React from "react";
|
|
|
|
|
|
|
|
|
|
|
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
|
|
|
|
import { Badge } from "@/components/ui/badge";
|
|
|
|
|
|
import {
|
|
|
|
|
|
Breadcrumb,
|
|
|
|
|
|
BreadcrumbItem,
|
|
|
|
|
|
BreadcrumbLink,
|
|
|
|
|
|
BreadcrumbList,
|
|
|
|
|
|
BreadcrumbPage,
|
|
|
|
|
|
BreadcrumbSeparator,
|
|
|
|
|
|
} from "@/components/ui/breadcrumb";
|
|
|
|
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
|
|
import {
|
|
|
|
|
|
Drawer,
|
|
|
|
|
|
DrawerContent,
|
|
|
|
|
|
DrawerHeader,
|
|
|
|
|
|
DrawerTitle,
|
|
|
|
|
|
} from "@/components/ui/drawer";
|
|
|
|
|
|
import {
|
|
|
|
|
|
DropdownMenu,
|
|
|
|
|
|
DropdownMenuContent,
|
|
|
|
|
|
DropdownMenuGroup,
|
|
|
|
|
|
DropdownMenuItem,
|
|
|
|
|
|
DropdownMenuLabel,
|
|
|
|
|
|
DropdownMenuPortal,
|
|
|
|
|
|
DropdownMenuSeparator,
|
|
|
|
|
|
DropdownMenuShortcut,
|
|
|
|
|
|
DropdownMenuSub,
|
|
|
|
|
|
DropdownMenuSubContent,
|
|
|
|
|
|
DropdownMenuSubTrigger,
|
|
|
|
|
|
DropdownMenuTrigger,
|
|
|
|
|
|
} from "@/components/ui/dropdown-menu";
|
|
|
|
|
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
|
|
|
|
import {
|
|
|
|
|
|
Sheet,
|
|
|
|
|
|
SheetContent,
|
|
|
|
|
|
SheetHeader,
|
|
|
|
|
|
SheetTitle,
|
|
|
|
|
|
SheetTrigger,
|
|
|
|
|
|
} from "@/components/ui/sheet";
|
|
|
|
|
|
import {
|
|
|
|
|
|
Sidebar,
|
|
|
|
|
|
SidebarContent,
|
|
|
|
|
|
SidebarFooter,
|
|
|
|
|
|
SidebarGroup,
|
|
|
|
|
|
SidebarGroupContent,
|
|
|
|
|
|
SidebarGroupLabel,
|
|
|
|
|
|
SidebarHeader,
|
|
|
|
|
|
SidebarInset,
|
|
|
|
|
|
SidebarMenu,
|
|
|
|
|
|
SidebarMenuBadge,
|
|
|
|
|
|
SidebarMenuButton,
|
|
|
|
|
|
SidebarMenuItem,
|
|
|
|
|
|
SidebarProvider,
|
|
|
|
|
|
SidebarTrigger,
|
|
|
|
|
|
useSidebar,
|
|
|
|
|
|
} from "@/components/ui/sidebar";
|
|
|
|
|
|
import { cn } from "@/lib/utils";
|
|
|
|
|
|
|
|
|
|
|
|
type ModuleId = "messages" | "spaces" | "threads" | "calls" | "files";
|
|
|
|
|
|
|
|
|
|
|
|
type NavItem = {
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
|
|
|
|
|
href: string;
|
|
|
|
|
|
badge?: string;
|
|
|
|
|
|
active?: boolean;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
type NavSection = {
|
|
|
|
|
|
title?: string;
|
|
|
|
|
|
items: NavItem[];
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
type NavModule = {
|
|
|
|
|
|
id: ModuleId;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
icon: NavItem["icon"];
|
|
|
|
|
|
sections: NavSection[];
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
function getItemKey(moduleId: ModuleId, item: NavItem) {
|
|
|
|
|
|
return `${moduleId}:${item.label}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function getDefaultItemKey(module: NavModule) {
|
|
|
|
|
|
const items = module.sections.flatMap((section) => section.items);
|
|
|
|
|
|
const activeItem = items.find((item) => item.active) ?? items[0];
|
|
|
|
|
|
return activeItem ? getItemKey(module.id, activeItem) : `${module.id}:`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function getActiveItemLabel(module: NavModule, activeItemKey: string) {
|
|
|
|
|
|
const items = module.sections.flatMap((section) => section.items);
|
|
|
|
|
|
const match = items.find(
|
|
|
|
|
|
(item) => getItemKey(module.id, item) === activeItemKey,
|
|
|
|
|
|
);
|
|
|
|
|
|
return match?.label ?? module.sections[0]?.items[0]?.label ?? module.label;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function getInitials(name: string) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
name
|
|
|
|
|
|
.split(" ")
|
|
|
|
|
|
.filter(Boolean)
|
|
|
|
|
|
.slice(0, 2)
|
|
|
|
|
|
.map((part) => part[0]?.toUpperCase())
|
|
|
|
|
|
.join("") || "U"
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const navModules: NavModule[] = [
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "messages",
|
|
|
|
|
|
label: "پیامها",
|
|
|
|
|
|
icon: MessagesSquare,
|
|
|
|
|
|
sections: [
|
|
|
|
|
|
{
|
|
|
|
|
|
title: "صندوق ورودی",
|
|
|
|
|
|
items: [
|
|
|
|
|
|
{ label: "همه پیامها", icon: Inbox, href: "#", active: true },
|
|
|
|
|
|
{ label: "ارجاعها", icon: UserPlus, href: "#", badge: "۴" },
|
|
|
|
|
|
{ label: "اعلانها", icon: Bell, href: "#", badge: "۱۲" },
|
|
|
|
|
|
{ label: "ذخیرهشده", icon: Bookmark, href: "#" },
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: "نماها",
|
|
|
|
|
|
items: [
|
|
|
|
|
|
{ label: "خواندهنشده", icon: Bell, href: "#" },
|
|
|
|
|
|
{ label: "زمانبندیشده", icon: Clock, href: "#" },
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "spaces",
|
|
|
|
|
|
label: "فضاها",
|
|
|
|
|
|
icon: LayoutGrid,
|
|
|
|
|
|
sections: [
|
|
|
|
|
|
{
|
|
|
|
|
|
title: "فضاها",
|
|
|
|
|
|
items: [
|
|
|
|
|
|
{ label: "بهروزرسانی محصول", icon: Hash, href: "#", active: true },
|
|
|
|
|
|
{ label: "موفقیت مشتری", icon: Users, href: "#", badge: "۲" },
|
|
|
|
|
|
{ label: "عملیات انتشار", icon: Layers, href: "#" },
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: "مستقیم",
|
|
|
|
|
|
items: [
|
|
|
|
|
|
{ label: "Kai", icon: Users, href: "#", badge: "۱" },
|
|
|
|
|
|
{ label: "Mina", icon: Users, href: "#" },
|
|
|
|
|
|
{ label: "Diego", icon: Users, href: "#" },
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "threads",
|
|
|
|
|
|
label: "گفتگوها",
|
|
|
|
|
|
icon: MessageSquareText,
|
|
|
|
|
|
sections: [
|
|
|
|
|
|
{
|
|
|
|
|
|
title: "گفتگوها",
|
|
|
|
|
|
items: [
|
|
|
|
|
|
{ label: "دنبالشده", icon: Bookmark, href: "#", active: true },
|
|
|
|
|
|
{ label: "محولشده", icon: Users, href: "#" },
|
|
|
|
|
|
{ label: "بستهشده", icon: MessageSquareText, href: "#" },
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: "برجستهها",
|
|
|
|
|
|
items: [
|
|
|
|
|
|
{ label: "ارجاعها", icon: UserPlus, href: "#", badge: "۳" },
|
|
|
|
|
|
{ label: "سنجاقشده", icon: Bookmark, href: "#" },
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "calls",
|
|
|
|
|
|
label: "تماسها",
|
|
|
|
|
|
icon: PhoneCall,
|
|
|
|
|
|
sections: [
|
|
|
|
|
|
{
|
|
|
|
|
|
title: "جلسات",
|
|
|
|
|
|
items: [
|
|
|
|
|
|
{ label: "پیشرو", icon: Clock, href: "#", active: true },
|
|
|
|
|
|
{ label: "ضبطها", icon: FileText, href: "#" },
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: "دستگاهها",
|
|
|
|
|
|
items: [
|
|
|
|
|
|
{ label: "صدا و تصویر", icon: PhoneCall, href: "#" },
|
|
|
|
|
|
{ label: "اعلانها", icon: Bell, href: "#" },
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "files",
|
|
|
|
|
|
label: "فایلها",
|
|
|
|
|
|
icon: Folder,
|
|
|
|
|
|
sections: [
|
|
|
|
|
|
{
|
|
|
|
|
|
title: "مرور",
|
|
|
|
|
|
items: [
|
|
|
|
|
|
{ label: "اخیر", icon: Clock, href: "#", active: true },
|
|
|
|
|
|
{ label: "اشتراکی با من", icon: Users, href: "#" },
|
|
|
|
|
|
{ label: "بارگذاریها", icon: Folder, href: "#" },
|
|
|
|
|
|
{ label: "مجموعهها", icon: Layers, href: "#" },
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: "فیلترهای سریع",
|
|
|
|
|
|
items: [
|
|
|
|
|
|
{ label: "اسناد", icon: FileText, href: "#" },
|
|
|
|
|
|
{ label: "نظرات خواندهنشده", icon: Bell, href: "#", badge: "۵" },
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const utilities: NavItem[] = [
|
|
|
|
|
|
{ label: "اعلانها", icon: Bell, href: "#" },
|
|
|
|
|
|
{ label: "تنظیمات", icon: Settings, href: "#" },
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const data = {
|
|
|
|
|
|
user: {
|
|
|
|
|
|
name: "علی رضایی",
|
|
|
|
|
|
email: "ali@example.com",
|
|
|
|
|
|
avatar:
|
|
|
|
|
|
"/block-media/shadcnblocks/block/avatar/avatar3.webp",
|
|
|
|
|
|
},
|
|
|
|
|
|
participants: [
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "you",
|
|
|
|
|
|
name: "علی رضایی",
|
|
|
|
|
|
title: "شما",
|
|
|
|
|
|
status: "online",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "riley",
|
|
|
|
|
|
name: "ریحانه Park",
|
|
|
|
|
|
title: "محصول",
|
|
|
|
|
|
status: "online",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "sam",
|
|
|
|
|
|
name: "Sam Rivera",
|
|
|
|
|
|
title: "طراحی",
|
|
|
|
|
|
status: "away",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "noah",
|
|
|
|
|
|
name: "Noah Patel",
|
|
|
|
|
|
title: "مهندسی",
|
|
|
|
|
|
status: "offline",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "maya",
|
|
|
|
|
|
name: "نگار Singh",
|
|
|
|
|
|
title: "پشتیبانی",
|
|
|
|
|
|
status: "online",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "jules",
|
|
|
|
|
|
name: "Jules Tan",
|
|
|
|
|
|
title: "عملیات",
|
|
|
|
|
|
status: "offline",
|
|
|
|
|
|
},
|
|
|
|
|
|
] as const,
|
|
|
|
|
|
activity: [
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "a1",
|
|
|
|
|
|
icon: MessageSquare,
|
|
|
|
|
|
title: "گفتگوی جدید شروع شد",
|
|
|
|
|
|
detail: "Riley در بهروزرسانی محصول پست گذاشت.",
|
|
|
|
|
|
time: "۲ دقیقه",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "a2",
|
|
|
|
|
|
icon: UserPlus,
|
|
|
|
|
|
title: "ارجاع جدید",
|
|
|
|
|
|
detail: "سام شما را به تیکت پشتیبانی مشتری ارجاع داد.",
|
|
|
|
|
|
time: "۱۸ دقیقه",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "a3",
|
|
|
|
|
|
icon: Pin,
|
|
|
|
|
|
title: "پیام سنجاق شد",
|
|
|
|
|
|
detail: "Maya «چکلیست انتشار نسخه ۳» را سنجاق کرد.",
|
|
|
|
|
|
time: "۱ ساعت",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "a4",
|
|
|
|
|
|
icon: ClipboardCheck,
|
|
|
|
|
|
title: "وظیفه تکمیل شد",
|
|
|
|
|
|
detail: "Noah «پاکسازی API» را انجامشده علامت زد.",
|
|
|
|
|
|
time: "امروز",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "a5",
|
|
|
|
|
|
icon: PhoneCall,
|
|
|
|
|
|
title: "تماس پایان یافت",
|
|
|
|
|
|
detail: "همگامسازی هفتگی • ۲۴ دقیقه",
|
|
|
|
|
|
time: "دیروز",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "a6",
|
|
|
|
|
|
icon: FileText,
|
|
|
|
|
|
title: "فایل بارگذاری شد",
|
|
|
|
|
|
detail: "Jules «incident-notes.pdf» را به اشتراک گذاشت.",
|
|
|
|
|
|
time: "دیروز",
|
|
|
|
|
|
},
|
|
|
|
|
|
] as const,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
interface ShellBreadcrumbProps {
|
|
|
|
|
|
className?: string;
|
|
|
|
|
|
moduleLabel: string;
|
|
|
|
|
|
itemLabel: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function ShellBreadcrumb({
|
|
|
|
|
|
className,
|
|
|
|
|
|
moduleLabel,
|
|
|
|
|
|
itemLabel,
|
|
|
|
|
|
}: ShellBreadcrumbProps) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<Breadcrumb className={cn(className)}>
|
|
|
|
|
|
<BreadcrumbList>
|
|
|
|
|
|
<BreadcrumbItem className="hidden md:block">
|
|
|
|
|
|
<BreadcrumbLink href="#">{moduleLabel}</BreadcrumbLink>
|
|
|
|
|
|
</BreadcrumbItem>
|
|
|
|
|
|
<BreadcrumbSeparator className="hidden md:block" />
|
|
|
|
|
|
<BreadcrumbItem>
|
|
|
|
|
|
<BreadcrumbPage>{itemLabel}</BreadcrumbPage>
|
|
|
|
|
|
</BreadcrumbItem>
|
|
|
|
|
|
</BreadcrumbList>
|
|
|
|
|
|
</Breadcrumb>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface ModulePanelProps {
|
|
|
|
|
|
className?: string;
|
|
|
|
|
|
module: NavModule;
|
|
|
|
|
|
activeItemKey: string;
|
|
|
|
|
|
onSelectItem: (key: string) => void;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function ModulePanel({
|
|
|
|
|
|
className,
|
|
|
|
|
|
module,
|
|
|
|
|
|
activeItemKey,
|
|
|
|
|
|
onSelectItem,
|
|
|
|
|
|
}: ModulePanelProps) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className={cn("space-y-4 px-4 py-3 text-start text-sm", className)}>
|
|
|
|
|
|
{module.sections.map((section, index) => (
|
|
|
|
|
|
<div className="space-y-2" key={section.title ?? index}>
|
|
|
|
|
|
{section.title && (
|
|
|
|
|
|
<SidebarGroupLabel className="mb-1 text-xs font-medium tracking-wide text-muted-foreground">
|
|
|
|
|
|
{section.title}
|
|
|
|
|
|
</SidebarGroupLabel>
|
|
|
|
|
|
)}
|
|
|
|
|
|
<SidebarMenu>
|
|
|
|
|
|
{section.items.map((item) => {
|
|
|
|
|
|
const Icon = item.icon;
|
|
|
|
|
|
const itemKey = getItemKey(module.id, item);
|
|
|
|
|
|
return (
|
|
|
|
|
|
<SidebarMenuItem
|
|
|
|
|
|
key={item.label}
|
|
|
|
|
|
className="text-muted-foreground hover:text-foreground"
|
|
|
|
|
|
>
|
|
|
|
|
|
<SidebarMenuButton
|
|
|
|
|
|
asChild
|
|
|
|
|
|
isActive={itemKey === activeItemKey}
|
|
|
|
|
|
className="px-2"
|
|
|
|
|
|
>
|
|
|
|
|
|
<a
|
|
|
|
|
|
href={item.href}
|
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
onSelectItem(itemKey);
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Icon className="size-4" />
|
|
|
|
|
|
{item.badge && (
|
|
|
|
|
|
<SidebarMenuBadge>{item.badge}</SidebarMenuBadge>
|
|
|
|
|
|
)}
|
|
|
|
|
|
<span>{item.label}</span>
|
|
|
|
|
|
</a>
|
|
|
|
|
|
</SidebarMenuButton>
|
|
|
|
|
|
</SidebarMenuItem>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
|
|
|
|
|
</SidebarMenu>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
|
|
|
|
|
|
<div className="border-t pt-3">
|
|
|
|
|
|
<SidebarGroupLabel className="mb-2 text-xs font-medium tracking-wide text-muted-foreground">
|
|
|
|
|
|
ترجیحات
|
|
|
|
|
|
</SidebarGroupLabel>
|
|
|
|
|
|
<SidebarMenu className="text-sm">
|
|
|
|
|
|
{utilities.map((item) => {
|
|
|
|
|
|
const Icon = item.icon;
|
|
|
|
|
|
return (
|
|
|
|
|
|
<SidebarMenuItem key={item.label}>
|
|
|
|
|
|
<SidebarMenuButton
|
|
|
|
|
|
asChild
|
|
|
|
|
|
className="gap-2.5 px-2 text-muted-foreground hover:text-foreground"
|
|
|
|
|
|
>
|
|
|
|
|
|
<a href={item.href}>
|
|
|
|
|
|
<Icon className="size-4" />
|
|
|
|
|
|
<span>{item.label}</span>
|
|
|
|
|
|
</a>
|
|
|
|
|
|
</SidebarMenuButton>
|
|
|
|
|
|
</SidebarMenuItem>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
|
|
|
|
|
</SidebarMenu>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface AppSidebarProps extends Omit<
|
|
|
|
|
|
React.ComponentProps<typeof Sidebar>,
|
|
|
|
|
|
"className"
|
|
|
|
|
|
> {
|
|
|
|
|
|
className?: string;
|
|
|
|
|
|
activeKey: ModuleId;
|
|
|
|
|
|
activeItemKey: string;
|
|
|
|
|
|
onNavChange: (key: ModuleId) => void;
|
|
|
|
|
|
onItemChange: (key: string) => void;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function AppSidebar({
|
|
|
|
|
|
className,
|
|
|
|
|
|
activeKey,
|
|
|
|
|
|
activeItemKey,
|
|
|
|
|
|
onNavChange,
|
|
|
|
|
|
onItemChange,
|
|
|
|
|
|
...props
|
|
|
|
|
|
}: AppSidebarProps) {
|
|
|
|
|
|
const { state, setOpen, isMobile } = useSidebar();
|
|
|
|
|
|
const isCollapsed = state === "collapsed";
|
|
|
|
|
|
const showFooter = isCollapsed || isMobile;
|
|
|
|
|
|
|
|
|
|
|
|
const activeModule =
|
|
|
|
|
|
navModules.find((module) => module.id === activeKey) ?? navModules[0];
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<Sidebar
|
|
|
|
|
|
side="right"
|
|
|
|
|
|
collapsible="icon"
|
|
|
|
|
|
className={cn(
|
|
|
|
|
|
"overflow-hidden *:data-[sidebar=sidebar]:flex-row",
|
|
|
|
|
|
className,
|
|
|
|
|
|
)}
|
|
|
|
|
|
{...props}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Sidebar
|
|
|
|
|
|
collapsible="none"
|
|
|
|
|
|
className="w-[calc(var(--sidebar-width-icon)+1px)]! border-e"
|
|
|
|
|
|
>
|
|
|
|
|
|
<SidebarHeader className="h-14 border-b">
|
|
|
|
|
|
<SidebarMenu className="my-auto">
|
|
|
|
|
|
<SidebarMenuItem>
|
|
|
|
|
|
<SidebarMenuButton size="lg" asChild className="md:h-8 md:p-0">
|
|
|
|
|
|
<a href="#">
|
|
|
|
|
|
<div className="flex aspect-square size-8 items-center justify-center rounded-sm bg-primary">
|
|
|
|
|
|
<img
|
2026-07-15 20:22:14 +02:00
|
|
|
|
src="/block-media/shadcnblocks/block/logos/NovaSales-logo.svg"
|
2026-07-11 21:21:29 +03:30
|
|
|
|
alt="شادسیان بلاکس"
|
|
|
|
|
|
className="size-6 text-primary-foreground invert dark:invert-0"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span className="text-lg font-semibold tracking-tighter">
|
|
|
|
|
|
ری یو آی
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</a>
|
|
|
|
|
|
</SidebarMenuButton>
|
|
|
|
|
|
</SidebarMenuItem>
|
|
|
|
|
|
</SidebarMenu>
|
|
|
|
|
|
</SidebarHeader>
|
|
|
|
|
|
|
|
|
|
|
|
<SidebarContent>
|
|
|
|
|
|
<SidebarGroup>
|
|
|
|
|
|
<SidebarGroupContent className="px-1.5 md:px-0">
|
|
|
|
|
|
<SidebarMenu>
|
|
|
|
|
|
{" "}
|
|
|
|
|
|
<SidebarMenuItem>
|
|
|
|
|
|
<motion.div
|
|
|
|
|
|
key={isCollapsed ? "h-collapsed" : "h-expanded"}
|
|
|
|
|
|
className={cn(
|
|
|
|
|
|
isCollapsed
|
|
|
|
|
|
? "hidden items-center justify-center sm:flex"
|
|
|
|
|
|
: "hidden",
|
|
|
|
|
|
)}
|
|
|
|
|
|
initial={{ opacity: 0 }}
|
|
|
|
|
|
animate={{ opacity: 1 }}
|
|
|
|
|
|
transition={{ duration: 0.8 }}
|
|
|
|
|
|
>
|
|
|
|
|
|
<SidebarTrigger />
|
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
</SidebarMenuItem>
|
|
|
|
|
|
{navModules.map((module) => {
|
|
|
|
|
|
const Icon = module.icon;
|
|
|
|
|
|
return (
|
|
|
|
|
|
<SidebarMenuItem key={module.id}>
|
|
|
|
|
|
<SidebarMenuButton
|
|
|
|
|
|
tooltip={{
|
|
|
|
|
|
children: module.label,
|
|
|
|
|
|
hidden: isMobile,
|
|
|
|
|
|
}}
|
|
|
|
|
|
onClick={() => {
|
|
|
|
|
|
onNavChange(module.id);
|
|
|
|
|
|
setOpen(true);
|
|
|
|
|
|
}}
|
|
|
|
|
|
isActive={activeKey === module.id}
|
|
|
|
|
|
aria-label={module.label}
|
|
|
|
|
|
className="px-2.5 md:px-2"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Icon className="size-4" />
|
|
|
|
|
|
<span>{module.label}</span>
|
|
|
|
|
|
</SidebarMenuButton>
|
|
|
|
|
|
</SidebarMenuItem>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
|
|
|
|
|
</SidebarMenu>
|
|
|
|
|
|
</SidebarGroupContent>
|
|
|
|
|
|
</SidebarGroup>
|
|
|
|
|
|
</SidebarContent>
|
|
|
|
|
|
|
|
|
|
|
|
<SidebarFooter className={cn(showFooter ? "flex" : "hidden")}>
|
|
|
|
|
|
<SidebarGroup className="p-0">
|
|
|
|
|
|
<SidebarGroupContent className="px-1.5 md:px-0">
|
|
|
|
|
|
<SidebarMenu>
|
|
|
|
|
|
{utilities.map((item) => {
|
|
|
|
|
|
const Icon = item.icon;
|
|
|
|
|
|
return (
|
|
|
|
|
|
<SidebarMenuItem key={item.label}>
|
|
|
|
|
|
<SidebarMenuButton
|
|
|
|
|
|
asChild
|
|
|
|
|
|
tooltip={{
|
|
|
|
|
|
children: item.label,
|
|
|
|
|
|
hidden: isMobile,
|
|
|
|
|
|
}}
|
|
|
|
|
|
className="px-2.5 md:px-2"
|
|
|
|
|
|
aria-label={item.label}
|
|
|
|
|
|
>
|
|
|
|
|
|
<a href={item.href}>
|
|
|
|
|
|
<Icon className="size-4" />
|
|
|
|
|
|
<span>{item.label}</span>
|
|
|
|
|
|
</a>
|
|
|
|
|
|
</SidebarMenuButton>
|
|
|
|
|
|
</SidebarMenuItem>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
|
|
|
|
|
</SidebarMenu>{" "}
|
|
|
|
|
|
</SidebarGroupContent>
|
|
|
|
|
|
</SidebarGroup>
|
|
|
|
|
|
</SidebarFooter>
|
|
|
|
|
|
</Sidebar>
|
|
|
|
|
|
|
|
|
|
|
|
<Sidebar collapsible="none" className="hidden flex-1 md:flex">
|
|
|
|
|
|
<SidebarHeader
|
|
|
|
|
|
className={cn(
|
|
|
|
|
|
"flex h-14 border-b px-2",
|
|
|
|
|
|
isCollapsed
|
|
|
|
|
|
? "flex-row items-center justify-between gap-y-4 md:flex-col md:items-center md:justify-start"
|
|
|
|
|
|
: "flex-row items-center justify-between",
|
|
|
|
|
|
)}
|
|
|
|
|
|
>
|
|
|
|
|
|
<SidebarMenu>
|
|
|
|
|
|
<SidebarMenuItem>
|
|
|
|
|
|
<DropdownMenu>
|
|
|
|
|
|
<div className="flex w-full items-center px-2 group-data-[collapsible=icon]:justify-center">
|
|
|
|
|
|
<DropdownMenuTrigger asChild>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
variant="ghost"
|
|
|
|
|
|
className="flex h-auto items-center gap-1.5 p-0! hover:bg-transparent"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span className="font-semibold">ری یو آی</span>
|
|
|
|
|
|
<ChevronDown className="size-3 text-muted-foreground" />
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</DropdownMenuTrigger>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<DropdownMenuContent
|
|
|
|
|
|
className="min-w-60 rounded-lg"
|
|
|
|
|
|
side="bottom"
|
|
|
|
|
|
align="center"
|
|
|
|
|
|
>
|
|
|
|
|
|
<DropdownMenuGroup>
|
|
|
|
|
|
<DropdownMenuItem asChild>
|
|
|
|
|
|
<a href="#">
|
|
|
|
|
|
تنظیمات فضای کار
|
|
|
|
|
|
<DropdownMenuShortcut>⌥⇧S</DropdownMenuShortcut>
|
|
|
|
|
|
</a>
|
|
|
|
|
|
</DropdownMenuItem>
|
|
|
|
|
|
<DropdownMenuSub>
|
|
|
|
|
|
<DropdownMenuSubTrigger>
|
|
|
|
|
|
دعوت همتیمیها
|
|
|
|
|
|
</DropdownMenuSubTrigger>
|
|
|
|
|
|
<DropdownMenuPortal>
|
|
|
|
|
|
<DropdownMenuSubContent className="min-w-64 rounded-lg">
|
|
|
|
|
|
<DropdownMenuLabel>دعوت به Pulse</DropdownMenuLabel>
|
|
|
|
|
|
<DropdownMenuSeparator />
|
|
|
|
|
|
<DropdownMenuItem className="flex items-center gap-2">
|
|
|
|
|
|
<div className="flex aspect-square size-6 items-center justify-center rounded bg-blue-500 text-[10px] font-semibold text-white">
|
|
|
|
|
|
EM
|
|
|
|
|
|
</div>
|
|
|
|
|
|
دعوت با ایمیل
|
|
|
|
|
|
</DropdownMenuItem>
|
|
|
|
|
|
<DropdownMenuItem className="flex items-center gap-2">
|
|
|
|
|
|
<div className="flex aspect-square size-6 items-center justify-center rounded bg-emerald-500 text-[10px] font-semibold text-white">
|
|
|
|
|
|
LK
|
|
|
|
|
|
</div>
|
|
|
|
|
|
کپی لینک دعوت
|
|
|
|
|
|
<DropdownMenuShortcut>⌘C</DropdownMenuShortcut>
|
|
|
|
|
|
</DropdownMenuItem>
|
|
|
|
|
|
<DropdownMenuSeparator />
|
|
|
|
|
|
<DropdownMenuItem>دعوتهای در انتظار</DropdownMenuItem>
|
|
|
|
|
|
<DropdownMenuItem>نقشها و دسترسی</DropdownMenuItem>
|
|
|
|
|
|
</DropdownMenuSubContent>
|
|
|
|
|
|
</DropdownMenuPortal>
|
|
|
|
|
|
</DropdownMenuSub>
|
|
|
|
|
|
</DropdownMenuGroup>
|
|
|
|
|
|
<DropdownMenuSeparator />
|
|
|
|
|
|
|
|
|
|
|
|
<DropdownMenuItem>
|
|
|
|
|
|
خروج
|
|
|
|
|
|
<DropdownMenuShortcut>⌥⇧Q</DropdownMenuShortcut>
|
|
|
|
|
|
</DropdownMenuItem>
|
|
|
|
|
|
</DropdownMenuContent>
|
|
|
|
|
|
</DropdownMenu>
|
|
|
|
|
|
</SidebarMenuItem>
|
|
|
|
|
|
</SidebarMenu>
|
|
|
|
|
|
<motion.div
|
|
|
|
|
|
key={isCollapsed ? "h-collapsed" : "h-expanded"}
|
|
|
|
|
|
className={cn(
|
|
|
|
|
|
"flex items-center gap-2",
|
|
|
|
|
|
isCollapsed ? "flex-row md:flex-col-reverse" : "flex-row",
|
|
|
|
|
|
)}
|
|
|
|
|
|
initial={{ opacity: 0 }}
|
|
|
|
|
|
animate={{ opacity: 1 }}
|
|
|
|
|
|
transition={{ duration: 0.8 }}
|
|
|
|
|
|
>
|
|
|
|
|
|
<SidebarTrigger />
|
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
</SidebarHeader>
|
|
|
|
|
|
|
|
|
|
|
|
<SidebarContent className="overflow-hidden">
|
|
|
|
|
|
<ScrollArea className="min-h-0 flex-1">
|
|
|
|
|
|
<SidebarGroup className="px-0">
|
|
|
|
|
|
<SidebarGroupContent>
|
|
|
|
|
|
<ModulePanel
|
|
|
|
|
|
module={activeModule}
|
|
|
|
|
|
activeItemKey={activeItemKey}
|
|
|
|
|
|
onSelectItem={onItemChange}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</SidebarGroupContent>
|
|
|
|
|
|
</SidebarGroup>
|
|
|
|
|
|
</ScrollArea>
|
|
|
|
|
|
</SidebarContent>
|
|
|
|
|
|
</Sidebar>
|
|
|
|
|
|
</Sidebar>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface NavUserProps {
|
|
|
|
|
|
className?: string;
|
|
|
|
|
|
user: {
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
email: string;
|
|
|
|
|
|
avatar: string;
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function NavUser({ className, user }: NavUserProps) {
|
|
|
|
|
|
const { isMobile } = useSidebar();
|
|
|
|
|
|
const initials = getInitials(user.name);
|
|
|
|
|
|
return (
|
|
|
|
|
|
<SidebarMenu className={cn("w-full", className)}>
|
|
|
|
|
|
<SidebarMenuItem className="w-full">
|
|
|
|
|
|
<DropdownMenu>
|
|
|
|
|
|
<DropdownMenuTrigger asChild>
|
|
|
|
|
|
<SidebarMenuButton
|
|
|
|
|
|
size="default"
|
|
|
|
|
|
className="h-10 w-full data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Avatar className="h-7 w-7">
|
|
|
|
|
|
<AvatarImage src={user.avatar} alt={user.name} />
|
|
|
|
|
|
<AvatarFallback className="bg-sidebar-primary text-xs font-medium text-sidebar-primary-foreground">
|
|
|
|
|
|
{initials}
|
|
|
|
|
|
</AvatarFallback>
|
|
|
|
|
|
</Avatar>
|
|
|
|
|
|
<span className="min-w-0 flex-1 truncate text-sm font-medium">
|
|
|
|
|
|
{user.name}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<ChevronDown className="ms-auto size-4 text-muted-foreground" />
|
|
|
|
|
|
</SidebarMenuButton>
|
|
|
|
|
|
</DropdownMenuTrigger>
|
|
|
|
|
|
<DropdownMenuContent
|
|
|
|
|
|
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
|
|
|
|
|
|
side={isMobile ? "bottom" : "left"}
|
|
|
|
|
|
align="start"
|
|
|
|
|
|
sideOffset={4}
|
|
|
|
|
|
>
|
|
|
|
|
|
<DropdownMenuLabel className="p-0 font-normal">
|
|
|
|
|
|
<div className="flex items-center gap-2 px-1 py-1.5 text-start text-sm">
|
|
|
|
|
|
<Avatar className="h-8 w-8">
|
|
|
|
|
|
<AvatarImage src={user.avatar} alt={user.name} />
|
|
|
|
|
|
<AvatarFallback className="bg-sidebar-primary font-medium text-sidebar-primary-foreground">
|
|
|
|
|
|
{initials}
|
|
|
|
|
|
</AvatarFallback>
|
|
|
|
|
|
</Avatar>
|
|
|
|
|
|
<div className="grid flex-1 text-start text-sm leading-tight">
|
|
|
|
|
|
<span className="truncate font-medium">{user.name}</span>
|
|
|
|
|
|
<span className="truncate text-xs text-muted-foreground">
|
|
|
|
|
|
{user.email}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</DropdownMenuLabel>
|
|
|
|
|
|
<DropdownMenuSeparator />
|
|
|
|
|
|
<DropdownMenuGroup>
|
|
|
|
|
|
<DropdownMenuItem>
|
|
|
|
|
|
<Sparkles className="me-2 size-4" />
|
|
|
|
|
|
ارتقا به حرفهای
|
|
|
|
|
|
</DropdownMenuItem>
|
|
|
|
|
|
</DropdownMenuGroup>
|
|
|
|
|
|
<DropdownMenuSeparator />
|
|
|
|
|
|
<DropdownMenuGroup>
|
|
|
|
|
|
<DropdownMenuItem>
|
|
|
|
|
|
<BadgeCheck className="me-2 size-4" />
|
|
|
|
|
|
حساب کاربری
|
|
|
|
|
|
</DropdownMenuItem>
|
|
|
|
|
|
<DropdownMenuItem>
|
|
|
|
|
|
<CreditCard className="me-2 size-4" />
|
|
|
|
|
|
صورتحساب
|
|
|
|
|
|
</DropdownMenuItem>
|
|
|
|
|
|
<DropdownMenuItem>
|
|
|
|
|
|
<Bell className="me-2 size-4" />
|
|
|
|
|
|
اعلانها
|
|
|
|
|
|
</DropdownMenuItem>
|
|
|
|
|
|
</DropdownMenuGroup>
|
|
|
|
|
|
<DropdownMenuSeparator />
|
|
|
|
|
|
<DropdownMenuItem>
|
|
|
|
|
|
<LogOut className="me-2 size-4" />
|
|
|
|
|
|
خروج
|
|
|
|
|
|
</DropdownMenuItem>
|
|
|
|
|
|
</DropdownMenuContent>
|
|
|
|
|
|
</DropdownMenu>
|
|
|
|
|
|
</SidebarMenuItem>
|
|
|
|
|
|
</SidebarMenu>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface SidebarRightProps extends Omit<
|
|
|
|
|
|
React.ComponentProps<typeof Sidebar>,
|
|
|
|
|
|
"className"
|
|
|
|
|
|
> {
|
|
|
|
|
|
className?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function RightPanelContent() {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="space-y-6 p-4 text-start">
|
|
|
|
|
|
<section className="space-y-3">
|
|
|
|
|
|
<div className="flex items-center justify-between gap-2">
|
|
|
|
|
|
<SidebarGroupLabel className="text-xs font-medium tracking-wide text-muted-foreground">
|
|
|
|
|
|
شرکتکنندگان
|
|
|
|
|
|
</SidebarGroupLabel>
|
|
|
|
|
|
<Button variant="outline" size="sm" className="h-8 gap-2">
|
|
|
|
|
|
<UserPlus className="size-4" />
|
|
|
|
|
|
دعوت
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
|
{data.participants.map((participant) => {
|
|
|
|
|
|
const statusClassName =
|
|
|
|
|
|
participant.status === "online"
|
|
|
|
|
|
? "bg-emerald-500"
|
|
|
|
|
|
: participant.status === "away"
|
|
|
|
|
|
? "bg-amber-500"
|
|
|
|
|
|
: "bg-muted-foreground/50";
|
|
|
|
|
|
const initials = getInitials(participant.name);
|
|
|
|
|
|
const isYou = participant.id === "you";
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<button
|
|
|
|
|
|
key={participant.id}
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
className="flex w-full items-center gap-3 rounded-md px-2 py-1.5 text-start hover:bg-accent"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span
|
|
|
|
|
|
className={cn(
|
|
|
|
|
|
"mt-0.5 size-2 shrink-0 rounded-full",
|
|
|
|
|
|
statusClassName,
|
|
|
|
|
|
)}
|
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<Avatar className="h-7 w-7">
|
|
|
|
|
|
<AvatarFallback className="bg-primary text-xs font-medium text-primary-foreground">
|
|
|
|
|
|
{initials}
|
|
|
|
|
|
</AvatarFallback>
|
|
|
|
|
|
</Avatar>
|
|
|
|
|
|
<div className="min-w-0 flex-1">
|
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
|
<span className="truncate text-sm font-medium">
|
|
|
|
|
|
{participant.name}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
{isYou && (
|
|
|
|
|
|
<Badge
|
|
|
|
|
|
variant="secondary"
|
|
|
|
|
|
className="h-5 px-1.5 text-[10px]"
|
|
|
|
|
|
>
|
|
|
|
|
|
شما
|
|
|
|
|
|
</Badge>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="truncate text-xs text-muted-foreground">
|
|
|
|
|
|
{participant.title}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section className="space-y-3">
|
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
|
<SidebarGroupLabel className="text-xs font-medium tracking-wide text-muted-foreground">
|
2026-07-15 20:22:14 +02:00
|
|
|
|
فعالیت تیمها
|
2026-07-11 21:21:29 +03:30
|
|
|
|
</SidebarGroupLabel>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
variant="ghost"
|
|
|
|
|
|
size="sm"
|
|
|
|
|
|
className="h-8 px-2 text-muted-foreground hover:text-foreground"
|
|
|
|
|
|
>
|
|
|
|
|
|
مشاهده همه
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
|
{data.activity.map((event) => {
|
|
|
|
|
|
const Icon = event.icon;
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div
|
|
|
|
|
|
key={event.id}
|
|
|
|
|
|
className="flex gap-3 rounded-md p-2 hover:bg-accent/50"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-md bg-accent/50">
|
|
|
|
|
|
<Icon className="size-4 text-foreground" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="min-w-0 flex-1">
|
|
|
|
|
|
<div className="flex items-start justify-between gap-2">
|
|
|
|
|
|
<div className="truncate text-sm font-medium">
|
|
|
|
|
|
{event.title}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="shrink-0 text-xs text-muted-foreground">
|
|
|
|
|
|
{event.time}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="mt-0.5 text-xs text-muted-foreground">
|
|
|
|
|
|
{event.detail}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function SidebarRight({ className, ...props }: SidebarRightProps) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<Sidebar
|
|
|
|
|
|
collapsible="none"
|
|
|
|
|
|
side="left"
|
|
|
|
|
|
className={cn("sticky top-0 hidden h-svh border-s lg:flex", className)}
|
|
|
|
|
|
{...props}
|
|
|
|
|
|
>
|
|
|
|
|
|
<SidebarHeader className="flex h-14 items-center border-b px-2">
|
|
|
|
|
|
<NavUser user={data.user} />
|
|
|
|
|
|
</SidebarHeader>
|
|
|
|
|
|
<SidebarContent className="p-0">
|
|
|
|
|
|
<ScrollArea className="h-[calc(100svh-3.5rem)]">
|
|
|
|
|
|
<RightPanelContent />
|
|
|
|
|
|
</ScrollArea>
|
|
|
|
|
|
</SidebarContent>
|
|
|
|
|
|
</Sidebar>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface ApplicationShell7Props {
|
|
|
|
|
|
className?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function ApplicationShell7({ className }: ApplicationShell7Props) {
|
|
|
|
|
|
const [activeKey, setActiveKey] = React.useState<ModuleId>(navModules[0].id);
|
|
|
|
|
|
const [isMobilePanelOpen, setIsMobilePanelOpen] = React.useState(false);
|
|
|
|
|
|
const [activeItemKey, setActiveItemKey] = React.useState(() =>
|
|
|
|
|
|
getDefaultItemKey(navModules[0]),
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const activeModule =
|
|
|
|
|
|
navModules.find((module) => module.id === activeKey) ?? navModules[0];
|
|
|
|
|
|
const activeItemLabel = getActiveItemLabel(activeModule, activeItemKey);
|
|
|
|
|
|
|
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
|
|
setActiveItemKey(getDefaultItemKey(activeModule));
|
|
|
|
|
|
}, [activeModule]);
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<SidebarProvider
|
|
|
|
|
|
dir="rtl"
|
|
|
|
|
|
lang="fa"
|
|
|
|
|
|
style={
|
|
|
|
|
|
{
|
|
|
|
|
|
"--sidebar-width": "350px",
|
|
|
|
|
|
} as React.CSSProperties
|
|
|
|
|
|
}
|
|
|
|
|
|
className={cn(className)}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="hidden md:block">
|
|
|
|
|
|
<AppSidebar
|
|
|
|
|
|
activeKey={activeKey}
|
|
|
|
|
|
activeItemKey={activeItemKey}
|
|
|
|
|
|
onNavChange={setActiveKey}
|
|
|
|
|
|
onItemChange={setActiveItemKey}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<SidebarInset>
|
|
|
|
|
|
<header className="sticky top-0 flex h-14 shrink-0 items-center gap-2 border-b bg-background px-3">
|
|
|
|
|
|
<a href="#" className="md:hidden">
|
|
|
|
|
|
<div className="flex aspect-square size-8 items-center justify-center rounded-sm bg-primary">
|
|
|
|
|
|
<img
|
2026-07-15 20:22:14 +02:00
|
|
|
|
src="/block-media/shadcnblocks/block/logos/NovaSales-logo.svg"
|
2026-07-11 21:21:29 +03:30
|
|
|
|
alt="شادسیان بلاکس"
|
|
|
|
|
|
className="size-6 text-primary-foreground invert dark:invert-0"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</a>
|
|
|
|
|
|
<div className="flex flex-1 items-center gap-2">
|
|
|
|
|
|
<ShellBreadcrumb
|
|
|
|
|
|
moduleLabel={activeModule.label}
|
|
|
|
|
|
itemLabel={activeItemLabel}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<Sheet>
|
|
|
|
|
|
<SheetTrigger asChild>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
variant="ghost"
|
|
|
|
|
|
size="icon"
|
|
|
|
|
|
className="lg:hidden"
|
|
|
|
|
|
aria-label="تیم"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Users className="size-5" />
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</SheetTrigger>
|
|
|
|
|
|
<SheetContent side="left" dir="rtl" className="w-80 p-0 text-start">
|
|
|
|
|
|
<SheetHeader className="px-4 pt-4">
|
|
|
|
|
|
<SheetTitle className="text-start">تیم</SheetTitle>
|
|
|
|
|
|
</SheetHeader>
|
|
|
|
|
|
<ScrollArea className="min-h-0 flex-1">
|
|
|
|
|
|
<RightPanelContent />
|
|
|
|
|
|
</ScrollArea>
|
|
|
|
|
|
</SheetContent>
|
|
|
|
|
|
</Sheet>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
<div className="flex flex-1 flex-col gap-4 p-4">
|
|
|
|
|
|
<div className="h-[50vh] w-full rounded-xl bg-muted/50" />
|
|
|
|
|
|
<div className="h-[100vh] w-full rounded-xl bg-muted/50" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</SidebarInset>
|
|
|
|
|
|
|
|
|
|
|
|
<SidebarRight />
|
|
|
|
|
|
|
|
|
|
|
|
<Drawer
|
|
|
|
|
|
open={isMobilePanelOpen}
|
|
|
|
|
|
onOpenChange={setIsMobilePanelOpen}
|
|
|
|
|
|
dismissible
|
|
|
|
|
|
>
|
|
|
|
|
|
<DrawerContent dir="rtl" className="text-start md:hidden">
|
|
|
|
|
|
<DrawerHeader>
|
|
|
|
|
|
<DrawerTitle className="text-start">{activeModule.label}</DrawerTitle>
|
|
|
|
|
|
</DrawerHeader>
|
|
|
|
|
|
<div className="pb-6">
|
|
|
|
|
|
<ModulePanel
|
|
|
|
|
|
module={activeModule}
|
|
|
|
|
|
activeItemKey={activeItemKey}
|
|
|
|
|
|
onSelectItem={(key) => {
|
|
|
|
|
|
setActiveItemKey(key);
|
|
|
|
|
|
setIsMobilePanelOpen(false);
|
|
|
|
|
|
}}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</DrawerContent>
|
|
|
|
|
|
</Drawer>
|
|
|
|
|
|
|
|
|
|
|
|
<nav
|
|
|
|
|
|
aria-label="ناوبری پایین موبایل"
|
|
|
|
|
|
className="fixed inset-x-0 bottom-0 z-40 border-t bg-background/95 backdrop-blur md:hidden"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="grid grid-cols-5">
|
|
|
|
|
|
{navModules.map((module) => {
|
|
|
|
|
|
const Icon = module.icon;
|
|
|
|
|
|
const isActive = module.id === activeKey;
|
|
|
|
|
|
return (
|
|
|
|
|
|
<button
|
|
|
|
|
|
key={module.id}
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={() => {
|
|
|
|
|
|
setActiveKey(module.id);
|
|
|
|
|
|
setIsMobilePanelOpen(true);
|
|
|
|
|
|
}}
|
|
|
|
|
|
className={`flex flex-col items-center justify-center py-3 text-xs ${isActive
|
|
|
|
|
|
? "text-foreground"
|
|
|
|
|
|
: "text-muted-foreground hover:text-foreground"
|
|
|
|
|
|
}`}
|
|
|
|
|
|
aria-label={module.label}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Icon className="h-5 w-5" />
|
|
|
|
|
|
<span className="sr-only">{module.label}</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</nav>
|
|
|
|
|
|
</SidebarProvider>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|