Files
reui-blocks/blocks/dashboard12.tsx
T

1807 lines
90 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import DottedMap from "dotted-map/without-countries";
import { useMotionValueEvent, useSpring } from "framer-motion";
import {
ArrowDownRight,
ArrowUpRight,
BarChart3,
Box,
ChevronRight,
ChevronsUpDown,
ClipboardList,
Facebook,
Globe,
Instagram,
LayoutDashboard,
LogOut,
MessageSquare,
Package,
RotateCcw,
Search,
Settings,
Truck,
Twitter,
User,
Users,
Wallet,
} from "lucide-react";
import * as React from "react";
import type { TooltipProps } from "recharts";
import { Bar, BarChart, ReferenceLine, Tooltip, XAxis, YAxis } from "recharts";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import { type ChartConfig, ChartContainer } from "@/components/ui/chart";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { ScrollArea } from "@/components/ui/scroll-area";
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
SidebarInset,
SidebarProvider,
SidebarRail,
SidebarTrigger,
} from "@/components/ui/sidebar";
import { TooltipProvider } from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
// ============================================================================
// Color Palette
// ============================================================================
const mixBase = "var(--background)";
const palette = {
primary: "var(--primary)",
secondary: {
light: `color-mix(in oklch, var(--primary) 75%, ${mixBase})`,
dark: `color-mix(in oklch, var(--primary) 85%, ${mixBase})`,
},
tertiary: {
light: `color-mix(in oklch, var(--primary) 55%, ${mixBase})`,
dark: `color-mix(in oklch, var(--primary) 65%, ${mixBase})`,
},
quaternary: {
light: `color-mix(in oklch, var(--primary) 40%, ${mixBase})`,
dark: `color-mix(in oklch, var(--primary) 45%, ${mixBase})`,
},
quinary: {
light: `color-mix(in oklch, var(--primary) 25%, ${mixBase})`,
dark: `color-mix(in oklch, var(--primary) 30%, ${mixBase})`,
},
};
// ============================================================================
// Types
// ============================================================================
type NavItem = {
label: string;
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
href: string;
isActive?: boolean;
children?: NavItem[];
};
type NavGroup = {
title: string;
items: NavItem[];
defaultOpen?: boolean;
};
type UserData = {
name: string;
email: string;
avatar: string;
};
type SidebarData = {
logo: {
src: string;
alt: string;
title: string;
description: string;
};
navGroups: NavGroup[];
user?: UserData;
};
type KPIStat = {
title: string;
value: number;
change: number;
format: "currency" | "percent" | "number";
};
type CustomerSegment = {
name: string;
percentage: number;
customers: number;
change: number;
color: string;
};
type Channel = {
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
name: string;
count: number;
change: number;
};
type GeographyCountry = {
name: string;
flag: string;
lat: number;
lng: number;
users: number;
demographics: { men: number; women: number; other: number };
};
// ============================================================================
// Formatters
// ============================================================================
const currencyFormatter = new Intl.NumberFormat("fa-IR", {
style: "currency",
currency: "IRR",
maximumFractionDigits: 0,
});
const compactCurrencyFormatter = new Intl.NumberFormat("fa-IR", {
style: "currency",
currency: "IRR",
notation: "compact",
maximumFractionDigits: 1,
});
const numberFormatter = new Intl.NumberFormat("fa-IR");
const compactNumberFormatter = new Intl.NumberFormat("fa-IR", {
notation: "compact",
maximumFractionDigits: 2,
});
const percentFormatter = new Intl.NumberFormat("fa-IR", {
style: "percent",
maximumFractionDigits: 1,
});
const monthFormatter = new Intl.DateTimeFormat("fa-IR", { month: "short" });
const monthLabel = (monthIndex: number) =>
monthFormatter.format(new Date(2025, monthIndex, 1));
const formatChangePercent = (change: number, signed = true) => {
const formatted = Math.abs(change).toLocaleString("fa-IR", {
minimumFractionDigits: change % 1 !== 0 ? 1 : 0,
maximumFractionDigits: 1,
});
if (!signed) return `${formatted}٪`;
return `${change >= 0 ? "+" : "-"}${formatted}٪`;
};
type OrderStatus = "Delivered" | "Processing" | "Cancelled";
const orderStatusLabels: Record<OrderStatus, string> = {
Delivered: "تحویل‌شده",
Processing: "در حال پردازش",
Cancelled: "لغوشده",
};
// ============================================================================
// Hooks
// ============================================================================
function useHoverHighlight<T extends string | number>() {
const [active, setActive] = React.useState<T | null>(null);
const handleHover = React.useCallback((value: T | null) => {
setActive(value);
}, []);
return { active, handleHover };
}
// ============================================================================
// Mock Data
// ============================================================================
const sidebarData: SidebarData = {
logo: {
src: "/block-media/shadcnblocks/block/logos/NovaSales-logo.svg",
alt: "فروشگاه آکمه",
title: "ری یو آی Store",
description: "داشبورد",
},
navGroups: [
{
title: "اصلی",
defaultOpen: true,
items: [
{
label: "داشبورد",
icon: LayoutDashboard,
href: "#",
isActive: true,
},
{ label: "سفارش‌ها", icon: ClipboardList, href: "#" },
{ label: "مرجوعی‌ها", icon: RotateCcw, href: "#" },
],
},
{
title: "کاتالوگ",
defaultOpen: true,
items: [
{
label: "محصولات",
icon: Box,
href: "#",
children: [
{ label: "همه محصولات", icon: Package, href: "#" },
{ label: "دسته‌بندی‌ها", icon: Package, href: "#" },
{ label: "موجودی", icon: Package, href: "#" },
],
},
{ label: "ارسال", icon: Truck, href: "#" },
],
},
{
title: "مشتریان",
defaultOpen: false,
items: [
{ label: "همه مشتریان", icon: Users, href: "#" },
{ label: "پیام‌ها", icon: MessageSquare, href: "#" },
],
},
{
title: "تحلیل فروش",
defaultOpen: false,
items: [
{ label: "نمای کلی", icon: Globe, href: "#" },
{ label: "گزارش‌ها", icon: BarChart3, href: "#" },
{ label: "مالی", icon: Wallet, href: "#" },
],
},
{
title: "تنظیمات",
defaultOpen: false,
items: [{ label: "تنظیمات", icon: Settings, href: "#" }],
},
],
user: {
name: "علی رضایی",
email: "john@acmestore.com",
avatar:
"/block-media/shadcnblocks/block/avatar-6.webp",
},
};
const kpiStats: KPIStat[] = [
{ title: "درآمد کل", value: 485000, change: -12.5, format: "currency" },
{ title: "نرخ تبدیل", value: 18.5, change: -12.5, format: "percent" },
{ title: "کل سفارش‌ها", value: 92, change: 25, format: "number" },
{ title: "مشتریان فعال", value: 38, change: 25, format: "number" },
];
const monthlyRevenueData = [
{ month: monthLabel(0), revenue: 72000 },
{ month: monthLabel(1), revenue: 128000 },
{ month: monthLabel(2), revenue: 103000 },
{ month: monthLabel(3), revenue: 176000 },
{ month: monthLabel(4), revenue: 230000 },
{ month: monthLabel(5), revenue: 142000 },
{ month: monthLabel(6), revenue: 310000 },
{ month: monthLabel(7), revenue: 640000 },
{ month: monthLabel(8), revenue: 410000 },
{ month: monthLabel(9), revenue: 210000 },
{ month: monthLabel(10), revenue: 98000 },
{ month: monthLabel(11), revenue: 260000 },
];
const customerSegmentsData: CustomerSegment[] = [
{
name: "سازمانی",
percentage: 45,
customers: 1074,
color: palette.primary,
change: 3.2,
},
{
name: "کسب‌وکار متوسط",
percentage: 35,
customers: 836,
color: palette.secondary.light,
change: -1.5,
},
{
name: "شخصی",
percentage: 20,
customers: 478,
color: palette.tertiary.light,
change: 5.8,
},
];
const totalCustomers = 2388;
const totalCustomersChange = 4.2;
const channelsData: Channel[] = [
{ icon: Facebook, name: "فیس‌بوک", count: 2341, change: 25 },
{ icon: Twitter, name: "توییتر", count: 1231, change: -25 },
{ icon: Globe, name: "گوگل", count: 1123, change: -25 },
{ icon: Instagram, name: "اینستاگرام", count: 125, change: 25 },
];
const geographyCountries: GeographyCountry[] = [
{
name: "ایالات متحده",
flag: "\u{1F1FA}\u{1F1F8}",
lat: 38.9,
lng: -77.0,
users: 12400,
demographics: { men: 38, women: 54, other: 8 },
},
{
name: "بریتانیا",
flag: "\u{1F1EC}\u{1F1E7}",
lat: 51.5,
lng: -0.1,
users: 8200,
demographics: { men: 35, women: 57, other: 8 },
},
{
name: "هند",
flag: "\u{1F1EE}\u{1F1F3}",
lat: 20.6,
lng: 79.0,
users: 6500,
demographics: { men: 42, women: 50, other: 8 },
},
{
name: "برزیل",
flag: "\u{1F1E7}\u{1F1F7}",
lat: -14.2,
lng: -51.9,
users: 4200,
demographics: { men: 30, women: 62, other: 8 },
},
{
name: "ژاپن",
flag: "\u{1F1EF}\u{1F1F5}",
lat: 36.2,
lng: 139.7,
users: 3400,
demographics: { men: 32, women: 60, other: 8 },
},
];
const totalGeographyUsers = geographyCountries.reduce(
(sum, c) => sum + c.users,
0,
);
// Inline pre-computed dotted map grid (from dotted-map/without-countries)
// prettier-ignore
const mapJsonData = { "points": { "11;0": { "x": 11.5, "y": 0 }, "12;0": { "x": 12.5, "y": 0 }, "16;0": { "x": 16.5, "y": 0 }, "17;0": { "x": 17.5, "y": 0 }, "18;0": { "x": 18.5, "y": 0 }, "22;0": { "x": 22.5, "y": 0 }, "23;0": { "x": 23.5, "y": 0 }, "24;0": { "x": 24.5, "y": 0 }, "25;0": { "x": 25.5, "y": 0 }, "26;0": { "x": 26.5, "y": 0 }, "27;0": { "x": 27.5, "y": 0 }, "36;0": { "x": 36.5, "y": 0 }, "41;0": { "x": 41.5, "y": 0 }, "43;0": { "x": 43.5, "y": 0 }, "44;0": { "x": 44.5, "y": 0 }, "45;0": { "x": 45.5, "y": 0 }, "46;0": { "x": 46.5, "y": 0 }, "47;0": { "x": 47.5, "y": 0 }, "48;0": { "x": 48.5, "y": 0 }, "49;0": { "x": 49.5, "y": 0 }, "50;0": { "x": 50.5, "y": 0 }, "51;0": { "x": 51.5, "y": 0 }, "52;0": { "x": 52.5, "y": 0 }, "53;0": { "x": 53.5, "y": 0 }, "55;0": { "x": 55.5, "y": 0 }, "56;0": { "x": 56.5, "y": 0 }, "57;0": { "x": 57.5, "y": 0 }, "3;1": { "x": 3, "y": 0.8660254037844386 }, "4;1": { "x": 4, "y": 0.8660254037844386 }, "5;1": { "x": 5, "y": 0.8660254037844386 }, "6;1": { "x": 6, "y": 0.8660254037844386 }, "7;1": { "x": 7, "y": 0.8660254037844386 }, "8;1": { "x": 8, "y": 0.8660254037844386 }, "9;1": { "x": 9, "y": 0.8660254037844386 }, "10;1": { "x": 10, "y": 0.8660254037844386 }, "12;1": { "x": 12, "y": 0.8660254037844386 }, "13;1": { "x": 13, "y": 0.8660254037844386 }, "14;1": { "x": 14, "y": 0.8660254037844386 }, "15;1": { "x": 15, "y": 0.8660254037844386 }, "17;1": { "x": 17, "y": 0.8660254037844386 }, "19;1": { "x": 19, "y": 0.8660254037844386 }, "23;1": { "x": 23, "y": 0.8660254037844386 }, "24;1": { "x": 24, "y": 0.8660254037844386 }, "25;1": { "x": 25, "y": 0.8660254037844386 }, "26;1": { "x": 26, "y": 0.8660254037844386 }, "27;1": { "x": 27, "y": 0.8660254037844386 }, "35;1": { "x": 35, "y": 0.8660254037844386 }, "36;1": { "x": 36, "y": 0.8660254037844386 }, "37;1": { "x": 37, "y": 0.8660254037844386 }, "44;1": { "x": 44, "y": 0.8660254037844386 }, "45;1": { "x": 45, "y": 0.8660254037844386 }, "46;1": { "x": 46, "y": 0.8660254037844386 }, "47;1": { "x": 47, "y": 0.8660254037844386 }, "48;1": { "x": 48, "y": 0.8660254037844386 }, "49;1": { "x": 49, "y": 0.8660254037844386 }, "50;1": { "x": 50, "y": 0.8660254037844386 }, "51;1": { "x": 51, "y": 0.8660254037844386 }, "52;1": { "x": 52, "y": 0.8660254037844386 }, "53;1": { "x": 53, "y": 0.8660254037844386 }, "54;1": { "x": 54, "y": 0.8660254037844386 }, "55;1": { "x": 55, "y": 0.8660254037844386 }, "56;1": { "x": 56, "y": 0.8660254037844386 }, "57;1": { "x": 57, "y": 0.8660254037844386 }, "58;1": { "x": 58, "y": 0.8660254037844386 }, "59;1": { "x": 59, "y": 0.8660254037844386 }, "60;1": { "x": 60, "y": 0.8660254037844386 }, "61;1": { "x": 61, "y": 0.8660254037844386 }, "62;1": { "x": 62, "y": 0.8660254037844386 }, "0;2": { "x": 0.5, "y": 1.7320508075688772 }, "3;2": { "x": 3.5, "y": 1.7320508075688772 }, "4;2": { "x": 4.5, "y": 1.7320508075688772 }, "5;2": { "x": 5.5, "y": 1.7320508075688772 }, "6;2": { "x": 6.5, "y": 1.7320508075688772 }, "7;2": { "x": 7.5, "y": 1.7320508075688772 }, "8;2": { "x": 8.5, "y": 1.7320508075688772 }, "9;2": { "x": 9.5, "y": 1.7320508075688772 }, "10;2": { "x": 10.5, "y": 1.7320508075688772 }, "11;2": { "x": 11.5, "y": 1.7320508075688772 }, "12;2": { "x": 12.5, "y": 1.7320508075688772 }, "13;2": { "x": 13.5, "y": 1.7320508075688772 }, "14;2": { "x": 14.5, "y": 1.7320508075688772 }, "15;2": { "x": 15.5, "y": 1.7320508075688772 }, "16;2": { "x": 16.5, "y": 1.7320508075688772 }, "19;2": { "x": 19.5, "y": 1.7320508075688772 }, "22;2": { "x": 22.5, "y": 1.7320508075688772 }, "23;2": { "x": 23.5, "y": 1.7320508075688772 }, "24;2": { "x": 24.5, "y": 1.7320508075688772 }, "25;2": { "x": 25.5, "y": 1.7320508075688772 }, "34;2": { "x": 34.5, "y": 1.7320508075688772 }, "35;2": { "x": 35.5, "y": 1.7320508075688772 }, "36;2": { "x": 36.5, "y": 1.7320508075688772 }, "37;2": { "x": 37.5, "y": 1.7320508075688772 }, "38;2": { "x": 38.5, "y": 1.7320508075688772 }, "39;2": { "x": 39.5, "y": 1.7320508075688772 }, "40;2": { "x": 40.5, "y": 1.7320508075688772 }, "41;2": { "x": 41.5, "y": 1.7320508075688772 }, "42;2": { "x": 42.5, "y": 1.7320508075688772 }, "43;2": { "x": 43.5, "y": 1.7320508075688772 }, "45;2": { "x": 45.5, "y": 1.7320508075688772 }, "46;2": { "x": 46.5, "y": 1.7320508075688772 }, "47;2": { "x": 47.5, "y": 1.7320508075688772 }, "48;2": { "x": 48.5, "y": 1.7320508075688772 }, "49;2": { "x": 49.5, "y": 1.7320508075688772 }, "50;2": { "x": 50.5, "y": 1.7320508075688772 }, "51;2": { "x": 51.5, "y": 1.7320508075688772 }, "52;2": { "x": 52.5, "y": 1.7320508075688772 }, "53;2": { "x": 53.5, "y": 1.7320508075688772 }, "54;2": { "x": 54.5, "y": 1.7320508075688772 }, "55;2": { "x": 55.5, "y": 1.7320508075688772 }, "56;2": { "x": 56.5, "y": 1.7320508075688772 }, "57;2": { "x": 57.5, "y": 1.7320508075688772 }, "58;2": { "x": 58.5, "y": 1.7320508075688772 }, "59;2": { "x": 59.5, "y": 1.7320508075688772 }, "60;2": { "x": 60.5, "y": 1.7320508075688772 }, "61;2": { "x": 61.5, "y": 1.7320508075688772 }, "62;2": { "x": 62.5, "y": 1.7320508075688772 }, "1;3": { "x": 1, "y": 2.598076211353316 }, "2;3": { "x": 2, "y": 2.598076211353316 }, "3;3": { "x": 3, "y": 2.598076211353316 }, "4;3": { "x": 4, "y": 2.598076211353316 }, "5;3": { "x": 5, "y": 2.598076211353316 }, "6;3": { "x": 6, "y": 2.598076211353316 }, "7;3": { "x": 7, "y": 2.598076211353316 }, "8;3": { "x": 8, "y": 2.598076211353316 }, "9;3": { "x": 9, "y": 2.598076211353316 }, "10;3": { "x": 10, "y": 2.598076211353316 }, "11;3": { "x": 11, "y": 2.598076211353316 }, "12;3": { "x": 12, "y": 2.598076211353316 }, "13;3": { "x": 13, "y": 2.598076211353316 }, "14;3": { "x": 14, "y": 2.598076211353316 }, "15;3": { "x": 15, "y": 2.598076211353316 }, "16;3": { "x": 16, "y": 2.598076211353316 }, "19;3": { "x": 19, "y": 2.598076211353316 }, "20;3": { "x": 20, "y": 2.598076211353316 }, "23;3": { "x": 23, "y": 2.598076211353316 }, "24;3": { "x": 24, "y": 2.598076211353316 }, "28;3": { "x": 28, "y": 2.598076211353316 }, "34;3": { "x": 34, "y": 2.598076211353316 }, "35;3": { "x": 35, "y": 2.598076211353316 }, "36;3": { "x": 36, "y": 2.598076211353316 }, "37;3": { "x": 37, "y": 2.598076211353316 }, "39;3": { "x": 39, "y": 2.598076211353316 }, "40;3": { "x": 40, "y": 2.598076211353316 }, "41;3": { "x": 41, "y": 2.598076211353316 }, "42;3": { "x": 42, "y": 2.598076211353316 }, "43;3": { "x": 43, "y": 2.598076211353316 }, "44;3": { "x": 44, "y": 2.598076211353316 }, "45;3": { "x": 45, "y": 2.598076211353316 }, "46;3": { "x": 46, "y": 2.598076211353316 }, "47;3": { "x": 47, "y": 2.598076211353316 }, "48;3": { "x": 48, "y": 2.598076211353316 }, "49;3": { "x": 49, "y": 2.598076211353316 }, "50;3": { "x": 50, "y": 2.598076211353316 }, "51;3": { "x": 51, "y": 2.598076211353316 }, "52;3": { "x": 52, "y": 2.598076211353316 }, "53;3": { "x": 53, "y": 2.598076211353316 }, "54;3": { "x": 54, "y": 2.598076211353316 }, "55;3": { "x": 55, "y": 2.598076211353316 }, "56;3": { "x": 56, "y": 2.598076211353316 }, "57;3": { "x": 57, "y": 2.598076211353316 }, "58;3": { "x": 58, "y": 2.598076211353316 }, "59;3": { "x": 59, "y": 2.598076211353316 }, "60;3": { "x": 60, "y": 2.598076211353316 }, "61;3": { "x": 61, "y": 2.598076211353316 }, "62;3": { "x": 62, "y": 2.598076211353316 }, "1;4": { "x": 1.5, "y": 3.4641016151377544 }, "3;4": { "x": 3.5, "y": 3.4641016151377544 }, "4;4": { "x": 4.5, "y": 3.4641016151377544 }, "5;4": { "x": 5.5, "y": 3.4641016151377544 }, "6;4": { "x": 6.5, "y": 3.4641016151377544 }, "7;4": { "x": 7.5, "y": 3.4641016151377544 }, "8;4": { "x": 8.5, "y": 3.4641016151377544 }, "9;4": { "x": 9.5, "y": 3.4641016151377544 }, "10;4": { "x": 10.5, "y": 3.4641016151377544 }, "11;4": { "x": 11.5, "y": 3.4641016151377544 }, "12;4": { "x": 12.5, "y": 3.4641016151377544 }, "13;4": { "x": 13.5, "y": 3.4641016151377544 }, "14;4": { "x": 14.5, "y": 3.4641016151377544 }, "15;4": { "x": 15.5, "y": 3.4641016151377544 }, "16;4": { "x": 16.5, "y": 3.4641016151377544 }, "22;4": { "x": 22.5, "y": 3.4641016151377544 }, "23;4": { "x": 23.5, "y": 3.4641016151377544 }, "33;4": { "x": 33.5, "y": 3.4641016151377544 }, "34;4": { "x": 34.5, "y": 3.4641016151377544 }, "35;4": { "x": 35.5, "y": 3.4641016151377544 }, "36;4": { "x": 36.5, "y": 3.4641016151377544 }, "37;4": { "x": 37.5, "y": 3.4641016151377544 }, "38;4": { "x": 38.5, "y": 3.4641016151377544 }, "39;4": { "x": 39.5, "y": 3.4641016151377544 }, "40;4": { "x": 40.5, "y": 3.4641016151377544 }, "41;4": { "x": 41.5, "y": 3.4641016151377544 }, "42;4": { "x": 42.5, "y": 3.4641016151377544 }, "43;4": { "x": 43.5, "y": 3.4641016151377544 }, "44;4": { "x": 44.5, "y": 3.4641016151377544 }, "45;4": { "x": 45.5, "y": 3.4641016151377544 }, "46;4": { "x": 46.5, "y": 3.4641016151377544 }, "47;4": { "x": 47.5, "y": 3.4641016151377544 }, "48;4": { "x": 48.5, "y": 3.4641016151377544 }, "49;4": { "x": 49.5, "y": 3.4641016151377544 }, "50;4": { "x": 50.5, "y": 3.4641016151377544 }, "51;4": { "x": 51.5, "y": 3.4641016151377544 }, "52;4": { "x": 52.5, "y": 3.4641016151377544 }, "53;4": { "x": 53.5, "y": 3.4641016151377544 }, "54;4": { "x": 54.5, "y": 3.4641016151377544 }, "55;4": { "x": 55.5, "y": 3.4641016151377544 }, "56;4": { "x": 56.5, "y": 3.4641016151377544 }, "57;4": { "x": 57.5, "y": 3.4641016151377544 }, "58;4": { "x": 58.5, "y": 3.4641016151377544 }, "59;4": { "x": 59.5, "y": 3.4641016151377544 }, "60;4": { "x": 60.5, "y": 3.4641016151377544 }, "61;4": { "x": 61.5, "y": 3.4641016151377544 }, "62;4": { "x": 62.5, "y": 3.4641016151377544 }, "3;5": { "x": 3, "y": 4.330127018922193 }, "4;5": { "x": 4, "y": 4.330127018922193 }, "6;5": { "x": 6, "y": 4.330127018922193 }, "7;5": { "x": 7, "y": 4.330127018922193 }, "8;5": { "x": 8, "y": 4.330127018922193 }, "9;5": { "x": 9, "y": 4.330127018922193 }, "10;5": { "x": 10, "y": 4.330127018922193 }, "11;5": { "x": 11, "y": 4.330127018922193 }, "12;5": { "x": 12, "y": 4.330127018922193 }, "13;5": { "x": 13, "y": 4.330127018922193 }, "14;5": { "x": 14, "y": 4.330127018922193 }, "18;5": { "x": 18, "y": 4.330127018922193 }, "23;5": { "x": 23, "y": 4.330127018922193 }, "33;5": { "x": 33, "y": 4.330127018922193 }, "34;5": { "x": 34, "y": 4.330127018922193 }, "36;5": { "x": 36, "y": 4.330127018922193 }, "37;5": { "x": 37, "y": 4.330127018922193 }, "38;5": { "x": 38, "y": 4.330127018922193 }, "39;5": { "x": 39, "y": 4.330127018922193 }, "40;5": { "x": 40, "y": 4.330127018922193 }, "41;5": { "x": 41, "y": 4.330127018922193 }, "42;5": { "x": 42, "y": 4.330127018922193 }, "43;5": { "x": 43, "y": 4.330127018922193 }, "44;5": { "x": 44, "y": 4.330127018922193 }, "45;5": { "x": 45, "y": 4.330127018922193 }, "46;5": { "x": 46, "y": 4.330127018922193 }, "47;5": { "x": 47, "y": 4.330127018922193 }, "48;5": { "x": 48, "y": 4.330127018922193 }, "49;5": { "x": 49, "y": 4.330127018922193 }, "50;5": { "x": 50, "y": 4.330127018922193 }, "51;5": { "x": 51, "y": 4.330127018922193 }, "52;5": { "x": 52, "y": 4.330127018922193 }, "53;5": { "x": 53, "y": 4.330127018922193 }, "54;5": { "x": 54, "y": 4.330127018922193 }, "55;5": { "x": 55, "y": 4.330127018922193 }, "56;5": { "x": 56, "y": 4.330127018922193 }, "57;5": { "x": 57, "y": 4.330127018922193 }, "58;5": { "x": 58, "y": 4.330127018922193 }, "59;5": { "x": 59, "y": 4.330127018922193 }, "61;5": { "x": 61, "y": 4.330127018922193 }, "3;6": { "x": 3.5, "y": 5.196152422706632 }, "7;6": { "x": 7.5, "y": 5.196152422706632 }, "8;6": { "x": 8.5, "y": 5.196152422706632 }, "9;6": { "x": 9.5, "y": 5.196152422706632 }, "10;6": { "x": 10.5, "y": 5.196152422706632 }, "11;6": { "x": 11.5, "y": 5.196152422706632 }, "12;6": { "x": 12.5, "y": 5.196152422706632 }, "13;6": { "x": 13.5, "y": 5.196152422706632 }, "14;6": { "x": 14.5, "y": 5.196152422706632 }, "18;6": { "x": 18.5, "y": 5.196152422706632 }, "32;6": { "x": 32.5, "y": 5.196152422706632 }, "33;6": { "x": 33.5, "y": 5.196152422706632 }, "36;6": { "x": 36.5, "y": 5.196152422706632 }, "37;6": { "x": 37.5, "y": 5.196152422706632 }, "38;6": { "x": 38.5, "y": 5.196152422706632 }, "39;6": { "x": 39.5, "y": 5.196152422706632 }, "40;6": { "x": 40.5, "y": 5.196152422706632 }, "41;6": { "x": 41.5, "y": 5.196152422706632 }, "42;6": { "x": 42.5, "y": 5.196152422706632 }, "43;6": { "x": 43.5, "y": 5.196152422706632 }, "44;6": { "x": 44.5, "y": 5.196152422706632 }, "45;6": { "x": 45.5, "y": 5.196152422706632 }, "46;6": { "x": 46.5, "y": 5.196152422706632 }, "47;6": { "x": 47.5, "y": 5.196152422706632 }, "48;6": { "x": 48.5, "y": 5.196152422706632 }, "49;6": { "x": 49.5, "y": 5.196152422706632 }, "50;6": { "x": 50.5, "y": 5.196152422706632 }, "51;6": { "x": 51.5, "y": 5.196152422706632 }, "52;6": { "x": 52.5, "y": 5.196152422706632 }, "53;6": { "x": 53.5, "y": 5.196152422706632 }, "54;6": { "x": 54.5, "y": 5.196152422706632 }, "55;6": { "x": 55.5, "y": 5.196152422706632 }, "9;7": { "x": 9, "y": 6.06217782649107 }, "10;7": { "x": 10, "y": 6.06217782649107 }, "11;7": { "x": 11, "y": 6.06217782649107 }, "12;7": { "x": 12, "y": 6.06217782649107 }, "13;7": { "x": 13, "y": 6.06217782649107 }, "14;7": { "x": 14, "y": 6.06217782649107 }, "15;7": { "x": 15, "y": 6.06217782649107 }, "16;7": { "x": 16, "y": 6.06217782649107 }, "18;7": { "x": 18, "y": 6.06217782649107 }, "19;7": { "x": 19, "y": 6.06217782649107 }, "20;7": { "x": 20, "y": 6.06217782649107 }, "33;7": { "x": 33, "y": 6.06217782649107 }, "34;7": { "x": 34, "y": 6.06217782649107 }, "36;7": { "x": 36, "y": 6.06217782649107 }, "37;7": { "x": 37, "y": 6.06217782649107 }, "38;7": { "x": 38, "y": 6.06217782649107 }, "39;7": { "x": 39, "y": 6.06217782649107 }, "40;7": { "x": 40, "y": 6.06217782649107 }, "41;7": { "x": 41, "y": 6.06217782649107 }, "42;7": { "x": 42, "y": 6.06217782649107 }, "43;7": { "x": 43, "y": 6.06217782649107 }, "44;7": { "x": 44, "y": 6.06217782649107 }, "45;7": { "x": 45, "y": 6.06217782649107 }, "46;7": { "x": 46, "y": 6.06217782649107 }, "47;7": { "x": 47, "y": 6.06217782649107 }, "48;7": { "x": 48, "y": 6.06217782649107 }, "49;7": { "x": 49, "y": 6.06217782649107 }, "50;7": { "x": 50, "y": 6.06217782649107 }, "51;7": { "x": 51, "y": 6.06217782649107 }, "52;7": { "x": 52, "y": 6.06217782649107 }, "53;7": { "x": 53, "y": 6.06217782649107 }, "54;7": { "x": 54, "y": 6.06217782649107 }, "55;7": { "x": 55, "y": 6.06217782649107 }, "59;7": { "x": 59, "y": 6.06217782649107 }, "60;7": { "x": 60, "y": 6.06217782649107 }, "9;8": { "x": 9.5, "y": 6.928203230275509 }, "10;8": { "x": 10.5, "y": 6.928203230275509 }, "11;8": { "x": 11.5, "y": 6.928203230275509 }, "12;8": { "x": 12.5, "y": 6.928203230275509 }, "13;8": { "x": 13.5, "y": 6.928203230275509 }, "14;8": { "x": 14.5, "y": 6.928203230275509 }, "15;8": { "x": 15.5, "y": 6.928203230275509 }, "16;8": { "x": 16.5, "y": 6.928203230275509 }, "18;8": { "x": 18.5, "y": 6.928203230275509 }, "19;8": { "x": 19.5, "y": 6.928203230275509 }, "20;8": { "x": 20.5, "y": 6.928203230275509 }, "21;8": { "x": 21.5, "y": 6.928203230275509 }, "31;8": { "x": 31.5, "y": 6.928203230275509 }, "32;8": { "x": 32.5, "y": 6.928203230275509 }, "33;8": { "x": 33.5, "y": 6.928203230275509 }, "34;8": { "x": 34.5, "y": 6.928203230275509 }, "35;8": { "x": 35.5, "y": 6.928203230275509 }, "36;8": { "x": 36.5, "y": 6.928203230275509 }, "37;8": { "x": 37.5, "y": 6.928203230275509 }, "38;8": { "x": 38.5, "y": 6.928203230275509 }, "39;8": { "x": 39.5, "y": 6.928203230275509 }, "40;8": { "x": 40.5, "y": 6.928203230275509 }, "41;8": { "x": 41.5, "y": 6.928203230275509 }, "42;8": { "x": 42.5, "y": 6.928203230275509 }, "43;8": { "x": 43.5, "y": 6.928203230275509 }, "44;8": { "x": 44.5, "y": 6.928203230275509 }, "45;8": { "x": 45.5, "y": 6.928203230275509 }, "46;8": { "x": 46.5, "y": 6.928203230275509 }, "47;8": { "x": 47.5, "y": 6.928203230275509 }, "48;8": { "x": 48.5, "y": 6.928203230275509 }, "49;8": { "x": 49.5, "y": 6.928203230275509 }, "50;8": { "x": 50.5, "y": 6.928203230275509 }, "51;8": { "x": 51.5, "y": 6.928203230275509 }, "52;8": { "x": 52.5, "y": 6.928203230275509 }, "53;8": { "x": 53.5, "y": 6.928203230275509 }, "54;8": { "x": 54.5, "y": 6.928203230275509 }, "55;8": { "x": 55.5, "y": 6.928203230275509 }, "56;8": { "x": 56.5, "y": 6.928203230275509 }, "59;8": { "x": 59.5, "y": 6.928203230275509 }, "9;9": { "x": 9, "y": 7.794228634059947 }, "10;9": { "x": 10, "y": 7.794228634059947 }, "11;9": { "x": 11, "y": 7.794228634059947 }, "12;9": { "x": 12, "y": 7.794228634059947 }, "13;9": { "x": 13, "y": 7.794228634059947 }, "14;9": { "x": 14, "y": 7.794228634059947 }, "15;9": { "x": 15, "y": 7.794228634059947 }, "16;9": { "x": 16, "y": 7.794228634059947 }, "17;9": { "x": 17, "y": 7.794228634059947 }, "18;9": { "x": 18, "y": 7.794228634059947 }, "19;9": { "x": 19, "y": 7.794228634059947 }, "32;9": { "x": 32, "y": 7.794228634059947 }, "33;9": { "x": 33, "y": 7.794228634059947 }, "34;9": { "x": 34, "y": 7.794228634059947 }, "35;9": { "x": 35, "y": 7.794228634059947 }, "36;9": { "x": 36, "y": 7.794228634059947 }, "37;9": { "x": 37, "y": 7.794228634059947 }, "38;9": { "x": 38, "y": 7.794228634059947 }, "39;9": { "x": 39, "y": 7.794228634059947 }, "40;9": { "x": 40, "y": 7.794228634059947 }, "41;9": { "x": 41, "y": 7.794228634059947 }, "42;9": { "x": 42, "y": 7.794228634059947 }, "43;9": { "x": 43, "y": 7.794228634059947 }, "44;9": { "x": 44, "y": 7.794228634059947 }, "45;9": { "x": 45, "y": 7.794228634059947 }, "46;9": { "x": 46, "y": 7.794228634059947 }, "47;9": { "x": 47, "y": 7.794228634059947 }, "48;9": { "x": 48, "y": 7.794228634059947 }, "49;9": { "x": 49, "y": 7.794228634059947 }, "50;9": { "x": 50, "y": 7.794228634059947 }, "51;9": { "x": 51, "y": 7.794228634059947 }, "52;9": { "x": 52, "y": 7.794228634059947 }, "53;9": { "x": 53, "y": 7.794228634059947 }, "54;9": { "x": 54, "y": 7.794228634059947 }, "55;9": { "x": 55, "y": 7.794228634059947 }, "56;9": { "x": 56, "y": 7.794228634059947 }, "10;10": { "x": 10.5, "y": 8.660254037844386 }, "11;10": { "x": 11.5, "y": 8.660254037844386 }, "12;10": { "x": 12.5, "y": 8.660254037844386 }, "13;10": { "x": 13.5, "y": 8.660254037844386 }, "14;10": { "x": 14.5, "y": 8.660254037844386 }, "15;10": { "x": 15.5, "y": 8.660254037844386 }, "16;10": { "x": 16.5, "y": 8.660254037844386 }, "17;10": { "x": 17.5, "y": 8.660254037844386 }, "18;10": { "x": 18.5, "y": 8.660254037844386 }, "19;10": { "x": 19.5, "y": 8.660254037844386 }, "31;10": { "x": 31.5, "y": 8.660254037844386 }, "32;10": { "x": 32.5, "y": 8.660254037844386 }, "33;10": { "x": 33.5, "y": 8.660254037844386 }, "34;10": { "x": 34.5, "y": 8.660254037844386 }, "35;10": { "x": 35.5, "y": 8.660254037844386 }, "36;10": { "x": 36.5, "y": 8.660254037844386 }, "37;10": { "x": 37.5, "y": 8.660254037844386 }, "38;10": { "x": 38.5, "y": 8.660254037844386 }, "39;10": { "x": 39.5, "y": 8.660254037844386 }, "41;10": { "x": 41.5, "y": 8.660254037844386 }, "42;10": { "x": 42.5, "y": 8.660254037844386 }, "43;10": { "x": 43.5, "y": 8.660254037844386 }, "44;10": { "x": 44.5, "y": 8.660254037844386 }, "45;10": { "x": 45.5, "y": 8.660254037844386 }, "46;10": { "x": 46.5, "y": 8.660254037844386 }, "47;10": { "x": 47.5, "y": 8.660254037844386 }, "48;10": { "x": 48.5, "y": 8.660254037844386 }, "49;10": { "x": 49.5, "y": 8.660254037844386 }, "50;10": { "x": 50.5, "y": 8.660254037844386 }, "51;10": { "x": 51.5, "y": 8.660254037844386 }, "52;10": { "x": 52.5, "y": 8.660254037844386 }, "53;10": { "x": 53.5, "y": 8.660254037844386 }, "54;10": { "x": 54.5, "y": 8.660254037844386 }, "55;10": { "x": 55.5, "y": 8.660254037844386 }, "56;10": { "x": 56.5, "y": 8.660254037844386 }, "10;11": { "x": 10, "y": 9.526279441628825 }, "11;11": { "x": 11, "y": 9.526279441628825 }, "12;11": { "x": 12, "y": 9.526279441628825 }, "13;11": { "x": 13, "y": 9.526279441628825 }, "14;11": { "x": 14, "y": 9.526279441628825 }, "15;11": { "x": 15, "y": 9.526279441628825 }, "16;11": { "x": 16, "y": 9.526279441628825 }, "17;11": { "x": 17, "y": 9.526279441628825 }, "18;11": { "x": 18, "y": 9.526279441628825 }, "19;11": { "x": 19, "y": 9.526279441628825 }, "30;11": { "x": 30, "y": 9.526279441628825 }, "32;11": { "x": 32, "y": 9.526279441628825 }, "35;11": { "x": 35, "y": 9.526279441628825 }, "36;11": { "x": 36, "y": 9.526279441628825 }, "39;11": { "x": 39, "y": 9.526279441628825 }, "41;11": { "x": 41, "y": 9.526279441628825 }, "42;11": { "x": 42, "y": 9.526279441628825 }, "43;11": { "x": 43, "y": 9.526279441628825 }, "44;11": { "x": 44, "y": 9.526279441628825 }, "45;11": { "x": 45, "y": 9.526279441628825 }, "46;11": { "x": 46, "y": 9.526279441628825 }, "47;11": { "x": 47, "y": 9.526279441628825 }, "48;11": { "x": 48, "y": 9.526279441628825 }, "49;11": { "x": 49, "y": 9.526279441628825 }, "50;11": { "x": 50, "y": 9.526279441628825 }, "51;11": { "x": 51, "y": 9.526279441628825 }, "52;11": { "x": 52, "y": 9.526279441628825 }, "53;11": { "x": 53, "y": 9.526279441628825 }, "54;11": { "x": 54, "y": 9.526279441628825 }, "55;11": { "x": 55, "y": 9.526279441628825 }, "57;11": { "x": 57, "y": 9.526279441628825 }, "10;12": { "x": 10.5, "y": 10.392304845413264 }, "11;12": { "x": 11.5, "y": 10.392304845413264 }, "12;12": { "x": 12.5, "y": 10.392304845413264 }, "13;12": { "x": 13.5, "y": 10.392304845413264 }, "14;12": { "x": 14.5, "y": 10.392304845413264 }, "15;12": { "x": 15.5, "y": 10.392304845413264 }, "16;12": { "x": 16.5, "y": 10.392304845413264 }, "17;12": { "x": 17.5, "y": 10.392304845413264 }, "30;12": { "x": 30.5, "y": 10.392304845413264 }, "35;12": { "x": 35.5, "y": 10.392304845413264 }, "36;12": { "x": 36.5, "y": 10.392304845413264 }, "37;12": { "x": 37.5, "y": 10.392304845413264 }, "38;12": { "x": 38.5, "y": 10.392304845413264 }, "39;12": { "x": 39.5, "y": 10.392304845413264 }, "41;12": { "x": 41.5, "y": 10.392304845413264 }, "42;12": { "x": 42.5, "y": 10.392304845413264 }, "43;12": { "x": 43.5, "y": 10.392304845413264 }, "44;12": { "x": 44.5, "y": 10.392304845413264 }, "45;12": { "x": 45.5, "y": 10.392304845413264 }, "46;12": { "x": 46.5, "y": 10.392304845413264 }, "47;12": { "x": 47.5, "y": 10.392304845413264 }, "48;12": { "x": 48.5, "y": 10.392304845413264 }, "49;12": { "x": 49.5, "y": 10.392304845413264 }, "50;12": { "x": 50.5, "y": 10.392304845413264 }, "51;12": { "x": 51.5, "y": 10.392304845413264 }, "52;12": { "x": 52.5, "y": 10.392304845413264 }, "53;12": { "x": 53.5, "y": 10.392304845413264 }, "11;13": { "x": 11, "y": 11.258330249197702 }, "12;13": { "x": 12, "y": 11.258330249197702 }, "13;13": { "x": 13, "y": 11.258330249197702 }, "14;13": { "x": 14, "y": 11.258330249197702 }, "15;13": { "x": 15, "y": 11.258330249197702 }, "16;13": { "x": 16, "y": 11.258330249197702 }, "17;13": { "x": 17, "y": 11.258330249197702 }, "18;13": { "x": 18, "y": 11.258330249197702 }, "32;13": { "x": 32, "y": 11.258330249197702 }, "33;13": { "x": 33, "y": 11.258330249197702 }, "38;13": { "x": 38, "y": 11.258330249197702 }, "39;13": { "x": 39, "y": 11.258330249197702 }, "40;13": { "x": 40, "y": 11.258330249197702 }, "41;13": { "x": 41, "y": 11.258330249197702 }, "42;13": { "x": 42, "y": 11.258330249197702 }, "43;13": { "x": 43, "y": 11.258330249197702 }, "44;13": { "x": 44, "y": 11.258330249197702 }, "45;13": { "x": 45, "y": 11.258330249197702 }, "46;13": { "x": 46, "y": 11.258330249197702 }, "47;13": { "x": 47, "y": 11.258330249197702 }, "48;13": { "x": 48, "y": 11.258330249197702 }, "49;13": { "x": 49, "y": 11.258330249197702 }, "50;13": { "x": 50, "y": 11.258330249197702 }, "51;13": { "x": 51, "y": 11.258330249197702 }, "52;13": { "x": 52, "y": 11.258330249197702 }, "54;13": { "x": 54, "y": 11.258330249197702 }, "56;13": { "x": 56, "y": 11.258330249197702 }, "11;14": { "x": 11.5, "y": 12.12435565298214 }, "12;14": { "x": 12.5, "y": 12.12435565298214 }, "13;14": { "x": 13.5, "y": 12.12435565298214 }, "14;14": { "x": 14.5, "y": 12.12435565298214 }, "15;14": { "x": 15.5, "y": 12.12435565298214 }, "16;14": { "x": 16.5, "y": 12.12435565298214 }, "30;14": { "x": 30.5, "y": 12.12435565298214 }, "31;14": { "x": 31.5, "y": 12.12435565298214 }, "32;14": { "x": 32.5, "y": 12.12435565298214 }, "33;14": { "x": 33.5, "y": 12.12435565298214 }, "35;14": { "x": 35.5, "y": 12.12435565298214 }, "38;14": { "x": 38.5, "y": 12.12435565298214 }, "39;14": { "x": 39.5, "y": 12.12435565298214 }, "40;14": { "x": 40.5, "y": 12.12435565298214 }, "41;14": { "x": 41.5, "y": 12.12435565298214 }, "42;14": { "x": 42.5, "y": 12.12435565298214 }, "43;14": { "x": 43.5, "y": 12.12435565298214 }, "44;14": { "x": 44.5, "y": 12.12435565298214 }, "45;14": { "x": 45.5, "y": 12.12435565298214 }, "46;14": { "x": 46.5, "y": 12.12435565298214 }, "47;14": { "x": 47.5, "y": 12.12435565298214 }, "48;14": { "x": 48.5, "y": 12.12435565298214 }, "49;14": { "x": 49.5, "y": 12.12435565298214 }, "50;14": { "x": 50.5, "y": 12.12435565298214 }, "51;14": { "x": 51.5, "y": 12.12435565298214 }, "52;14": { "x": 52.5, "y": 12.12435565298214 }, "54;14": { "x": 54.5, "y": 12.12435565298214 }, "13;15": { "x": 13, "y": 12.990381056766578 }, "14;15": { "x": 14, "y": 12.990381056766578 }, "17;15": { "x": 17, "y": 12.990381056766578 }, "30;15": { "x": 30, "y": 12.990381056766578 }, "31;15": { "x": 31, "y": 12.990381056766578 }, "32;15": { "x": 32, "y": 12.990381056766578 }, "33;15": { "x": 33, "y": 12.990381056766578 }, "34;15": { "x": 34, "y": 12.990381056766578 }, "35;15": { "x": 35, "y": 12.990381056766578 }, "36;15": { "x": 36, "y": 12.990381056766578 }, "37;15": { "x": 37, "y": 12.990381056766578 }, "38;15": { "x": 38, "y": 12.990381056766578 }, "39;15": { "x": 39, "y": 12.990381056766578 }, "40;15": { "x": 40, "y": 12.990381056766578 }, "41;15": { "x": 41, "y": 12.990381056766578 }, "42;15": { "x": 42, "y": 12.990381056766578 }, "43;15": { "x": 43, "y": 12.990381056766578 }, "44;15": { "x": 44, "y": 12.990381056766578 }, "45;15": { "x": 45, "y": 12.990381056766578 }, "46;15": { "x": 46, "y": 12.990381056766578 }, "47;15": { "x": 47, "y": 12.990381056766578 }, "48;15": { "x": 48, "y": 12.990381056766578 }, "49;15": { "x": 49, "y": 12.990381056766578 }, "50;15": { "x": 50, "y": 12.990381056766578 }, "51;15": { "x": 51, "y": 12.990381056766578 }, "52;15": { "x": 52, "y": 12.990381056766578 }, "13;16": { "x": 13.5, "y": 13.856406460551018 }, "29;16": { "x": 29.5, "y": 13.856406460551018 }, "30;16": { "x": 30.5, "y": 13.856406460551018 }, "31;16": { "x": 31.5, "y": 13.856406460551018 }, "32;16": { "x": 32.5, "y": 13.856406460551018 }, "33;16": { "x": 33.5, "y": 13.856406460551018 }, "34;16": { "x": 34.5, "y": 13.856406460551018 }, "35;16": { "x": 35.5, "y": 13.856406460551018 }, "36;16": { "x": 36.5, "y": 13.856406460551018 }, "37;16": { "x": 37.5, "y": 13.856406460551018 }, "38;16": { "x": 38.5, "y": 13.856406460551018 }, "39;16": { "x": 39.5, "y": 13.856406460551018 }, "40;16": { "x": 40.5, "y": 13.856406460551018 }, "41;16": { "x": 41.5, "y": 13.856406460551018 }, "44;16": { "x": 44.5, "y": 13.856406460551018 }, "45;16": { "x": 45.5, "y": 13.856406460551018 }, "46;16": { "x": 46.5, "y": 13.856406460551018 }, "47;16": { "x": 47.5, "y": 13.856406460551018 }, "48;16": { "x": 48.5, "y": 13.856406460551018 }, "49;16": { "x": 49.5, "y": 13.856406460551018 }, "50;16": { "x": 50.5, "y": 13.856406460551018 }, "51;16": { "x": 51.5, "y": 13.856406460551018 }, "14;17": { "x": 14, "y": 14.722431864335457 }, "16;17": { "x": 16, "y": 14.722431864335457 }, "19;17": { "x": 19, "y": 14.722431864335457 }, "29;17": { "x": 29, "y": 14.722431864335457 }, "30;17": { "x": 30, "y": 14.722431864335457 }, "31;17": { "x": 31, "y": 14.722431864335457 }, "32;17": { "x": 32, "y": 14.722431864335457 }, "33;17": { "x": 33, "y": 14.722431864335457 }, "34;17": { "x": 34, "y": 14.722431864335457 }, "35;17": { "x": 35, "y": 14.722431864335457 }, "36;17": { "x": 36, "y": 14.722431864335457 }, "37;17": { "x": 37, "y": 14.722431864335457 }, "38;17": { "x": 38, "y": 14.722431864335457 }, "39;17": { "x": 39, "y": 14.722431864335457 }, "40;17": { "x": 40, "y": 14.722431864335457 }, "41;17": { "x": 41, "y": 14.722431864335457 }, "45;17": { "x": 45, "y": 14.722431864335457 }, "46;17": { "x": 46, "y": 14.722431864335457 }, "49;17": { "x": 49, "y": 14.722431864335457 }, "50;17": { "x": 50, "y": 14.722431864335457 }, "16;18": { "x": 16.5, "y": 15.588457268119894 }, "29;18": { "x": 29.5, "y": 15.588457268119894 }, "30;18": { "x": 30.5, "y": 15.588457268119894 }, "31;18": { "x": 31.5, "y": 15.588457268119894 }, "32;18": { "x": 32.5, "y": 15.588457268119894 }, "33;18": { "x": 33.5, "y": 15.588457268119894 }, "34;18": { "x": 34.5, "y": 15.588457268119894 }, "35;18": { "x": 35.5, "y": 15.588457268119894 }, "36;18": { "x": 36.5, "y": 15.588457268119894 }, "37;18": { "x": 37.5, "y": 15.588457268119894 }, "38;18": { "x": 38.5, "y": 15.588457268119894 }, "39;18": { "x": 39.5, "y": 15.588457268119894 }, "45;18": { "x": 45.5, "y": 15.588457268119894 }, "49;18": { "x": 49.5, "y": 15.588457268119894 }, "50;18": { "x": 50.5, "y": 15.588457268119894 }, "17;19": { "x": 17, "y": 16.454482671904334 }, "19;19": { "x": 19, "y": 16.454482671904334 }, "20;19": { "x": 20, "y": 16.454482671904334 }, "30;19": { "x": 30, "y": 16.454482671904334 }, "31;19": { "x": 31, "y": 16.454482671904334 }, "32;19": { "x": 32, "y": 16.454482671904334 }, "33;19": { "x": 33, "y": 16.454482671904334 }, "34;19": { "x": 34, "y": 16.454482671904334 }, "35;19": { "x": 35, "y": 16.454482671904334 }, "36;19": { "x": 36, "y": 16.454482671904334 }, "37;19": { "x": 37, "y": 16.454482671904334 }, "38;19": { "x": 38, "y": 16.454482671904334 }, "39;19": { "x": 39, "y": 16.454482671904334 }, "40;19": { "x": 40, "y": 16.454482671904334 }, "45;19": { "x": 45, "y": 16.454482671904334 }, "49;19": { "x": 49, "y": 16.454482671904334 }, "50;19": { "x": 50, "y": 16.454482671904334 }, "18;20": { "x": 18.5, "y": 17.32050807568877 }, "19;20": { "x": 19.5, "y": 17.32050807568877 }, "20;20": { "x": 20.5, "y": 17.32050807568877 }, "21;20": { "x": 21.5, "y": 17.32050807568877 }, "33;20": { "x": 33.5, "y": 17.32050807568877 }, "34;20": { "x": 34.5, "y": 17.32050807568877 }, "35;20": { "x": 35.5, "y": 17.32050807568877 }, "36;20": { "x": 36.5, "y": 17.32050807568877 }, "37;20": { "x": 37.5, "y": 17.32050807568877 }, "38;20": { "x": 38.5, "y": 17.32050807568877 }, "39;20": { "x": 39.5, "y": 17.32050807568877 }, "48;20": { "x": 48.5, "y": 17.32050807568877 }, "49;20": { "x": 49.5, "y": 17.32050807568877 }, "18;21": { "x": 18, "y": 18.186533479473212 }, "19;21": { "x": 19, "y": 18.186533479473212 }, "20;21": { "x": 20, "y": 18.186533479473212 }, "21;21": { "x": 21, "y": 18.186533479473212 }, "22;21": { "x": 22, "y": 18.186533479473212 }, "34;21": { "x": 34, "y": 18.186533479473212 }, "35;21": { "x": 35, "y": 18.186533479473212 }, "36;21": { "x": 36, "y": 18.186533479473212 }, "37;21": { "x": 37, "y": 18.186533479473212 }, "38;21": { "x": 38, "y": 18.186533479473212 }, "51;21": { "x": 51, "y": 18.186533479473212 }, "52;21": { "x": 52, "y": 18.186533479473212 }, "55;21": { "x": 55, "y": 18.186533479473212 }, "17;22": { "x": 17.5, "y": 19.05255888325765 }, "18;22": { "x": 18.5, "y": 19.05255888325765 }, "19;22": { "x": 19.5, "y": 19.05255888325765 }, "20;22": { "x": 20.5, "y": 19.05255888325765 }, "21;22": { "x": 21.5, "y": 19.05255888325765 }, "22;22": { "x": 22.5, "y": 19.05255888325765 }, "23;22": { "x": 23.5, "y": 19.05255888325765 }, "24;22": { "x": 24.5, "y": 19.05255888325765 }, "34;22": { "x": 34.5, "y": 19.05255888325765 }, "35;22": { "x": 35.5, "y": 19.05255888325765 }, "36;22": { "x": 36.5, "y": 19.05255888325765 }, "37;22": { "x": 37.5, "y": 19.05255888325765 }, "56;22": { "x": 56.5, "y": 19.05255888325765 }, "18;23": { "x": 18, "y": 19.918584287042087 }, "19;23": { "x": 19, "y": 19.918584287042087 }, "20;23": { "x": 20, "y": 19.918584287042087 }, "21;23": { "x": 21, "y": 19.918584287042087 }, "22;23": { "x": 22, "y": 19.918584287042087 }, "23;23": { "x": 23, "y": 19.918584287042087 }, "24;23": { "x": 24, "y": 19.918584287042087 }, "25;23": { "x": 25, "y": 19.918584287042087 }, "34;23": { "x": 34, "y": 19.918584287042087 }, "35;23": { "x": 35, "y": 19.918584287042087 }, "36;23": { "x": 36, "y": 19.918584287042087 }, "37;23": { "x": 37, "y": 19.918584287042087 }, "38;23": { "x": 38, "y": 19.918584287042087 }, "60;23": { "x": 60, "y": 19.918584287042087 }, "18;24": { "x": 18.5, "y": 20.784609690826528 }, "19;24": { "x": 19.5, "y": 20.784609690826528 }, "20;24": { "x": 20.5, "y": 20.784609690826528 }, "21;24": { "x": 21.5, "y": 20.784609690826528 }, "22;24": { "x": 22.5, "y": 20.784609690826528 }, "23;24": { "x": 23.5, "y": 20.784609690826528 }, "24;24": { "x": 24.5, "y": 20.784609690826528 }, "34;24": { "x": 34.5, "y": 20.784609690826528 }, "35;24": { "x": 35.5, "y": 20.784609690826528 }, "36;24": { "x": 36.5, "y": 20.784609690826528 }, "37;24": { "x": 37.5, "y": 20.784609690826528 }, "38;24": { "x": 38.5, "y": 20.784609690826528 }, "53;24": { "x": 53.5, "y": 20.784609690826528 }, "54;24": { "x": 54.5, "y": 20.784609690826528 }, "56;24": { "x": 56.5, "y": 20.784609690826528 }, "20;25": { "x": 20, "y": 21.650635094610966 }, "21;25": { "x": 21, "y": 21.650635094610966 }, "22;25": { "x": 22, "y": 21.650635094610966 }, "23;25": { "x": 23, "y": 21.650635094610966 }, "24;25": { "x": 24, "y": 21.650635094610966 }, "34;25": { "x": 34, "y": 21.650635094610966 }, "35;25": { "x": 35, "y": 21.650635094610966 }, "36;25": { "x": 36, "y": 21.650635094610966 }, "37;25": { "x": 37, "y": 21.650635094610966 }, "40;25": { "x": 40, "y": 21.650635094610966 }, "53;25": { "x": 53, "y": 21.650635094610966 }, "54;25": { "x": 54, "y": 21.650635094610966 }, "55;25": { "x": 55, "y": 21.650635094610966 }, "56;25": { "x": 56, "y": 21.650635094610966 }, "57;25": { "x": 57, "y": 21.650635094610966 }, "19;26": { "x": 19.5, "y": 22.516660498395403 }, "20;26": { "x": 20.5, "y": 22.516660498395403 }, "21;26": { "x": 21.5, "y": 22.516660498395403 }, "22;26": { "x": 22.5, "y": 22.516660498395403 }, "34;26": { "x": 34.5, "y": 22.516660498395403 }, "35;26": { "x": 35.5, "y": 22.516660498395403 }, "36;26": { "x": 36.5, "y": 22.516660498395403 }, "37;26": { "x": 37.5, "y": 22.516660498395403 }, "39;26": { "x": 39.5, "y": 22.516660498395403 }, "51;26": { "x": 51.5, "y": 22.516660498395403 }, "52;26": { "x": 52.5, "y": 22.516660498395403 }, "53;26": { "x": 53.5, "y": 22.516660498395403 }, "54;26": { "x": 54.5, "y": 22.516660498395403 }, "55;26": { "x": 55.5, "y": 22.516660498395403 }, "56;26": { "x": 56.5, "y": 22.516660498395403 }, "57;26": { "x": 57.5, "y": 22.516660498395403 }, "19;27": { "x": 19, "y": 23.38268590217984 }, "20;27": { "x": 20, "y": 23.38268590217984 }, "21;27": { "x": 21, "y": 23.38268590217984 }, "22;27": { "x": 22, "y": 23.38268590217984 }, "35;27": { "x": 35, "y": 23.38268590217984 }, "36;27": { "x": 36, "y": 23.38268590217984 }, "37;27": { "x": 37, "y": 23.38268590217984 }, "52;27": { "x": 52, "y": 23.38268590217984 }, "53;27": { "x": 53, "y": 23.38268590217984 }, "54;27": { "x": 54, "y": 23.38268590217984 }, "55;27": { "x": 55, "y": 23.38268590217984 }, "56;27": { "x": 56, "y": 23.38268590217984 }, "57;27": { "x": 57, "y": 23.38268590217984 }, "58;27": { "x": 58, "y": 23.38268590217984 }, "19;28": { "x": 19.5, "y": 24.24871130596428 }, "20;28": { "x": 20.5, "y": 24.24871130596428 }, "21;28": { "x": 21.5, "y": 24.24871130596428 }, "35;28": { "x": 35.5, "y": 24.24871130596428 }, "52;28": { "x": 52.5, "y": 24.24871130596428 }, "55;28": { "x": 55.5, "y": 24.24871130596428 }, "56;28": { "x": 56.5, "y": 24.24871130596428 }, "57;28": { "x": 57.5, "y": 24.24871130596428 }, "19;29": { "x": 19, "y": 25.11473670974872 }, "20;29": { "x": 20, "y": 25.11473670974872 }, "21;29": { "x": 21, "y": 25.11473670974872 }, "57;29": { "x": 57, "y": 25.11473670974872 }, "18;30": { "x": 18.5, "y": 25.980762113533157 }, "19;30": { "x": 19.5, "y": 25.980762113533157 }, "57;30": { "x": 57.5, "y": 25.980762113533157 }, "62;30": { "x": 62.5, "y": 25.980762113533157 }, "19;31": { "x": 19, "y": 26.846787517317598 }, "18;32": { "x": 18.5, "y": 27.712812921102035 }, "19;32": { "x": 19.5, "y": 27.712812921102035 }, "19;33": { "x": 19, "y": 28.578838324886473 }, "19;34": { "x": 19.5, "y": 29.444863728670914 } }, "X_MIN": -19926188.85199597, "Y_MIN": -7558415.656081783, "X_MAX": 19926188.85199597, "Y_MAX": 11402272.159297233, "X_RANGE": 39852377.70399194, "Y_RANGE": 18960687.815379016, "region": { "lat": { "min": -56, "max": 71 }, "lng": { "min": -179, "max": 179 } }, "grid": "diagonal", "height": 30, "width": 63, "ystep": 0.8660254037844386 };
// Pre-compute dotted map data at module scope
const dottedMap = new DottedMap({ map: mapJsonData as any });
const geographyPinPositions = geographyCountries.map((country) =>
dottedMap.addPin({
lat: country.lat,
lng: country.lng,
svgOptions: { color: "transparent", radius: 0 },
}),
);
const mapBasePoints = dottedMap.getPoints();
const SPOTLIGHT_RADIUS = 4;
const spotlightMaps: Map<number, number>[] = geographyPinPositions.map(
(pin) => {
const dotMap = new Map<number, number>();
mapBasePoints.forEach((dot, dotIndex) => {
const dx = dot.x - pin.x;
const dy = dot.y - pin.y;
const distSq = dx * dx + dy * dy;
const radiusSq = SPOTLIGHT_RADIUS * SPOTLIGHT_RADIUS;
if (distSq <= radiusSq) {
const dist = Math.sqrt(distSq);
const t = dist / SPOTLIGHT_RADIUS;
dotMap.set(dotIndex, 0.35 - t * (0.35 - 0.12));
}
});
return dotMap;
},
);
const orderStatuses = {
Delivered: "text-emerald-600 bg-emerald-600/10",
Processing: "text-amber-500 bg-amber-500/10",
Cancelled: "text-red-600 bg-red-600/10",
};
const recentOrders = [
{
customer: { name: "سارا احمدی", avatar: "سا" },
orderId: "#ORD-7821",
product: "هدفون بی‌سیم",
status: "Delivered" as const,
amount: 129.99,
date: "۲۳ دقیقه پیش",
},
{
customer: { name: "محمد کریمی", avatar: "مک" },
orderId: "#ORD-7820",
product: "کفش دویدن",
status: "Processing" as const,
amount: 89.95,
date: "۱ ساعت پیش",
},
{
customer: { name: "نازنین موسوی", avatar: "نم" },
orderId: "#ORD-7819",
product: "کرم صورت ارگانیک",
status: "Delivered" as const,
amount: 34.5,
date: "۲ ساعت پیش",
},
{
customer: { name: "رضا نوری", avatar: "رن" },
orderId: "#ORD-7818",
product: "اسپیکر بلوتوث",
status: "Cancelled" as const,
amount: 199.0,
date: "۳ ساعت پیش",
},
{
customer: { name: "فاطمه رحیمی", avatar: "فر" },
orderId: "#ORD-7817",
product: "مت یوگا حرفه‌ای",
status: "Delivered" as const,
amount: 45.0,
date: "۵ ساعت پیش",
},
];
const secondaryNavigation = [
{ name: "نمای کلی", href: "#", current: true },
{ name: "سفارش‌ها", href: "#", current: false },
{ name: "محصولات", href: "#", current: false },
{ name: "مشتریان", href: "#", current: false },
{ name: "تحلیل فروش", href: "#", current: false },
];
// ============================================================================
// Chart Configs
// ============================================================================
const revenueChartConfig = {
revenue: {
label: "درآمد",
color: palette.secondary.light,
},
} satisfies ChartConfig;
// ============================================================================
// Sidebar Components
// ============================================================================
const SidebarLogo = ({ logo }: { logo: SidebarData["logo"] }) => {
return (
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton size="lg" tooltip={logo.title}>
<div className="flex aspect-square size-8 items-center justify-center bg-primary">
<img
src={logo.src}
alt={logo.alt}
width={24}
height={24}
className="size-6 text-primary-foreground invert dark:invert-0"
/>
</div>
<div className="flex flex-col gap-0.5 leading-none">
<span className="font-medium">{logo.title}</span>
<span className="text-xs text-muted-foreground">
{logo.description}
</span>
</div>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
);
};
const NavMenuItem = ({ item }: { item: NavItem }) => {
const Icon = item.icon;
const hasChildren = item.children && item.children.length > 0;
if (!hasChildren) {
return (
<SidebarMenuItem>
<SidebarMenuButton
asChild
isActive={item.isActive}
tooltip={item.label}
>
<a href={item.href}>
<Icon className="size-4" aria-hidden="true" />
<span>{item.label}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
);
}
return (
<Collapsible asChild defaultOpen className="group/collapsible">
<SidebarMenuItem>
<CollapsibleTrigger asChild>
<SidebarMenuButton isActive={item.isActive} tooltip={item.label}>
<Icon className="size-4" aria-hidden="true" />
<span>{item.label}</span>
<ChevronRight
className="ms-auto size-4 transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90 rtl:rotate-180"
aria-hidden="true"
/>
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
{item.children!.map((child) => (
<SidebarMenuSubItem key={child.label}>
<SidebarMenuSubButton
asChild
isActive={child.isActive}
className={
child.isActive
? "relative overflow-visible before:absolute before:top-0 before:-start-2.5 before:z-10 before:h-full before:w-px before:bg-primary before:content-['']"
: ""
}
>
<a href={child.href}>{child.label}</a>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
))}
</SidebarMenuSub>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
);
};
const NavUser = ({ user }: { user: UserData }) => {
const initials = user.name
.split(" ")
.map((n) => n[0])
.join("");
const userAvatar = (
<Avatar className="size-8 rounded-none">
<AvatarImage src={user.avatar} alt={user.name} />
<AvatarFallback className="rounded-none">{initials}</AvatarFallback>
</Avatar>
);
const userInfo = (
<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>
);
return (
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton
size="lg"
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
{userAvatar}
{userInfo}
<ChevronsUpDown className="ms-auto size-4" aria-hidden="true" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-none"
side="bottom"
align="end"
sideOffset={4}
>
<DropdownMenuLabel className="p-0 font-normal">
<div className="flex items-center gap-2 px-1 py-1.5 text-start text-sm">
{userAvatar}
{userInfo}
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem>
<User className="me-2 size-4" aria-hidden="true" />
حساب کاربری
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<LogOut className="me-2 size-4" aria-hidden="true" />
خروج
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
);
};
const AppSidebar = ({ ...props }: React.ComponentProps<typeof Sidebar>) => {
return (
<Sidebar side="right" variant="inset" collapsible="icon" {...props}>
<SidebarHeader>
<div className="flex items-center gap-2 group-data-[collapsible=icon]:flex-col">
<SidebarLogo logo={sidebarData.logo} />
<SidebarTrigger className="ms-auto group-data-[collapsible=icon]:ms-0" />
</div>
</SidebarHeader>
<SidebarContent>
<ScrollArea className="h-full">
{sidebarData.navGroups.map((group) => (
<SidebarGroup key={group.title}>
<SidebarGroupLabel>{group.title}</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{group.items.map((item) => (
<NavMenuItem key={item.label} item={item} />
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
))}
</ScrollArea>
</SidebarContent>
<SidebarFooter>
{sidebarData.user && <NavUser user={sidebarData.user} />}
</SidebarFooter>
<SidebarRail />
</Sidebar>
);
};
// ============================================================================
// Dashboard Header
// ============================================================================
const DashboardHeader = () => {
return (
<header className="sticky top-0 z-40 flex h-16 shrink-0 items-center gap-4 border-b bg-background px-4 sm:px-6">
<div className="flex flex-1 gap-4 self-stretch">
<form className="grid flex-1 grid-cols-1">
<input
name="search"
type="search"
placeholder="جستجو…"
aria-label="جستجوی داشبورد"
autoComplete="off"
className="col-start-1 row-start-1 block size-full bg-transparent ps-9 text-sm text-foreground outline-hidden placeholder:text-muted-foreground"
/>
<Search
aria-hidden="true"
className="pointer-events-none col-start-1 row-start-1 size-4 self-center justify-self-start ms-3 text-muted-foreground"
/>
</form>
</div>
</header>
);
};
// ============================================================================
// Secondary Navigation
// ============================================================================
const SecondaryNav = () => {
return (
<nav className="flex overflow-x-auto border-b bg-background py-4">
<ul
role="list"
className="flex min-w-full flex-none gap-x-6 px-4 text-sm/6 font-semibold text-muted-foreground sm:px-6"
>
{secondaryNavigation.map((item) => (
<li key={item.name}>
<a href={item.href} className={item.current ? "text-primary" : ""}>
{item.name}
</a>
</li>
))}
</ul>
</nav>
);
};
// ============================================================================
// Dashboard Heading
// ============================================================================
const DashboardHeading = () => {
return (
<div className="border-b px-4 py-4 sm:px-6">
<h1 className="flex gap-x-3 text-base/7">
<span className="font-semibold text-foreground">ری یو آی Store</span>
<span className="text-muted-foreground/60">/</span>
<span className="font-semibold text-foreground">نمای کلی</span>
</h1>
<p className="mt-2 text-xs/6 text-muted-foreground">
تحلیل فروشگاه آنلاین و شاخص‌های عملکرد
</p>
</div>
);
};
// ============================================================================
// Dashboard Content Components
// ============================================================================
// Growth section component (vertical layout for SalesRevenueCard header)
const GrowthSection = ({
label,
change,
}: {
label: string;
change: number;
}) => {
const isPositive = change >= 0;
return (
<div className="flex items-center gap-1.5 border bg-muted/30 px-2 py-1">
<span className="text-xs text-muted-foreground">{label}</span>
<span
className={cn(
"font-mono",
"flex items-center gap-0.5 text-xs font-medium",
isPositive ? "text-emerald-600" : "text-red-600",
)}
>
{isPositive ? (
<ArrowUpRight className="size-3" aria-hidden="true" />
) : (
<ArrowDownRight className="size-3" aria-hidden="true" />
)}
{formatChangePercent(change)}
</span>
</div>
);
};
// KPI Stat Card
const KPIStatCard = ({ stat }: { stat: KPIStat }) => {
const isPositive = stat.change >= 0;
const formatValue = (value: number, format: KPIStat["format"]) => {
switch (format) {
case "currency":
return currencyFormatter.format(value);
case "percent":
return percentFormatter.format(value / 100);
case "number":
return numberFormatter.format(value);
}
};
return (
<div className="flex h-full flex-col gap-2 border-b bg-card px-4 py-6 sm:px-6">
<span className="text-xs text-muted-foreground">{stat.title}</span>
<div className="flex items-baseline gap-2">
<span className={cn("font-mono", "text-xl font-semibold")}>
{formatValue(stat.value, stat.format)}
</span>
</div>
<div className="flex items-center gap-1 text-xs">
{isPositive ? (
<ArrowUpRight
className="size-3.5 text-emerald-600"
aria-hidden="true"
/>
) : (
<ArrowDownRight
className="size-3.5 text-red-600"
aria-hidden="true"
/>
)}
<span
className={cn(
"font-mono",
isPositive ? "text-emerald-600" : "text-red-600",
)}
>
{formatChangePercent(stat.change)}
</span>
<span className="text-muted-foreground">نسبت به ماه قبل</span>
</div>
</div>
);
};
// KPI Stats Row
const KPIStatsRow = () => {
return (
<div className="grid grid-cols-2 sm:grid-cols-4 [&>*]:border-e [&>*:last-child]:border-e-0">
{kpiStats.map((stat) => (
<KPIStatCard key={stat.title} stat={stat} />
))}
</div>
);
};
// ============================================================================
// Animated Reference Line Components
// ============================================================================
interface CustomReferenceLabelProps {
viewBox?: { x?: number; y?: number };
value: number;
}
const CustomReferenceLabel: React.FC<CustomReferenceLabelProps> = (props) => {
const { viewBox, value } = props;
const y = viewBox?.y ?? 0;
const width = React.useMemo(() => {
const characterWidth = 8;
const padding = 10;
return value.toString().length * characterWidth + padding;
}, [value]);
return (
<>
<rect
x={0}
y={y - 9}
width={width}
height={18}
fill="var(--secondary-foreground)"
rx={0}
/>
<text
className="font-mono"
fontWeight={600}
x={6}
y={y + 4}
fill="var(--primary-foreground)"
>
{compactCurrencyFormatter.format(value)}
</text>
</>
);
};
// Revenue Bar Chart Tooltip
function RevenueBarTooltip({
active,
payload,
label,
}: any) {
if (!active || !payload?.length) return null;
const value = payload[0]?.value || 0;
return (
<div className="border border-border bg-popover p-2 shadow-lg sm:p-3">
<p className="mb-1.5 text-xs font-medium text-foreground sm:mb-2 sm:text-sm">
{label}
</p>
<div className="flex items-center gap-1.5 sm:gap-2">
<div className="size-2 rounded-full bg-primary sm:size-2.5" />
<span className="text-[10px] text-muted-foreground sm:text-sm">
درآمد:
</span>
<span
className={cn(
"font-mono",
"text-[10px] font-medium text-foreground sm:text-sm",
)}
>
{currencyFormatter.format(Number(value))}
</span>
</div>
</div>
);
}
// Custom gradient bar shape for Sales Revenue chart
const CustomGradientBar = (
props: React.SVGProps<SVGRectElement> & {
index?: number;
activeIndex: number | undefined;
setActiveIndex: (index: number | undefined) => void;
maxValueIndex: number;
},
) => {
const {
fill,
x,
y,
width,
height,
index,
activeIndex,
setActiveIndex,
maxValueIndex,
} = props;
const highlightedIndex =
activeIndex !== undefined ? activeIndex : maxValueIndex;
const isActive = index === highlightedIndex;
const gradientId = `revenue-gradient-${index}`;
const barFill = isActive ? palette.secondary.light : palette.tertiary.light;
return (
<g onMouseEnter={() => setActiveIndex(index)}>
<defs>
<linearGradient id={gradientId} x1="0" y1="0" x2="0" y2="1">
<stop
offset="0%"
stopColor={barFill}
stopOpacity={isActive ? 0.6 : 0.15}
/>
<stop offset="100%" stopColor={barFill} stopOpacity={0} />
</linearGradient>
</defs>
{/* Gradient body */}
<rect
x={x}
y={y}
width={width}
height={height}
fill={`url(#${gradientId})`}
stroke="none"
/>
{/* 2px solid cap at top */}
<rect x={x} y={y} width={width} height={2} fill={barFill} stroke="none" />
</g>
);
};
// Sales Revenue Card with Animated Value Line Bar Chart
const SalesRevenueCard = () => {
const changePercent = 8.2;
// Track which bar is being hovered
const [activeIndex, setActiveIndex] = React.useState<number | undefined>(
undefined,
);
// Calculate the currently tracked value (hovered bar or max bar)
const maxValueData = React.useMemo(() => {
if (activeIndex !== undefined) {
return {
index: activeIndex,
value: monthlyRevenueData[activeIndex].revenue,
};
}
return monthlyRevenueData.reduce(
(max, data, index) =>
data.revenue > max.value ? { index, value: data.revenue } : max,
{ index: 0, value: 0 },
);
}, [activeIndex]);
const isPositive = changePercent >= 0;
// Spring animation for the reference line value
const valueSpring = useSpring(maxValueData.value, {
stiffness: 100,
damping: 20,
});
const [springyValue, setSpringyValue] = React.useState(maxValueData.value);
useMotionValueEvent(valueSpring, "change", (latest) => {
setSpringyValue(Number(latest.toFixed(0)));
});
React.useEffect(() => {
valueSpring.set(maxValueData.value);
}, [maxValueData.value, valueSpring]);
return (
<div className="flex min-w-0 flex-1 flex-col gap-4 bg-card p-4 sm:gap-5 sm:p-5">
<div className="flex items-start justify-between gap-4">
{/* Left: Title + Value + Change */}
<div className="flex flex-col gap-1">
<span className="text-xs text-muted-foreground">درآمد فروش</span>
<span
className={cn(
"font-mono",
"text-xl font-semibold tabular-nums sm:text-3xl",
)}
>
{currencyFormatter.format(maxValueData.value)}
</span>
<div className="flex items-center gap-1">
<span
className={cn(
"font-mono",
"text-sm font-medium",
isPositive ? "text-emerald-600" : "text-red-600",
)}
>
{formatChangePercent(changePercent)}
</span>
{isPositive ? (
<ArrowUpRight
className="size-3.5 text-emerald-600"
aria-hidden="true"
/>
) : (
<ArrowDownRight
className="size-3.5 text-red-600"
aria-hidden="true"
/>
)}
<span className="text-sm text-muted-foreground">نسبت به ماه قبل</span>
</div>
</div>
{/* Right: Growth sections */}
<div className="flex flex-wrap gap-2">
<GrowthSection label="۴ هفته" change={11.6} />
<GrowthSection label="۱۳ هفته" change={7.9} />
<GrowthSection label="۱۲ ماه" change={-3.4} />
</div>
</div>
<div className="h-[180px] w-full min-w-0 sm:h-[220px]">
<ChartContainer
config={revenueChartConfig}
className="aspect-auto h-full w-full"
>
<BarChart
data={monthlyRevenueData}
onMouseLeave={() => setActiveIndex(undefined)}
>
<XAxis
dataKey="month"
axisLine={false}
tickLine={false}
tick={{ fontSize: 10 }}
dy={8}
/>
<YAxis hide />
<Tooltip content={<RevenueBarTooltip />} cursor={false} />
<Bar
dataKey="revenue"
radius={0}
shape={
<CustomGradientBar
activeIndex={activeIndex}
setActiveIndex={setActiveIndex}
maxValueIndex={maxValueData.index}
/>
}
/>
<ReferenceLine
opacity={0.4}
y={springyValue}
stroke="var(--secondary-foreground)"
strokeWidth={1}
strokeDasharray="3 3"
label={<CustomReferenceLabel value={maxValueData.value} />}
/>
</BarChart>
</ChartContainer>
</div>
</div>
);
};
// Proportional Bubble Chart (Pure SVG)
const ProportionalBubbleChart = ({
activeIndex,
onHover,
}: {
activeIndex: number | null;
onHover: (index: number | null) => void;
}) => {
const maxPct = Math.max(...customerSegmentsData.map((r) => r.percentage));
const maxRadius = 70;
const positions = [
{ cx: 180, cy: 105 },
{ cx: 75, cy: 130 },
{ cx: 100, cy: 55 },
];
return (
<svg
viewBox="0 0 280 200"
preserveAspectRatio="xMidYMid meet"
className="h-full w-full font-mono"
onMouseLeave={() => onHover(null)}
>
{customerSegmentsData.map((segment, i) => {
const baseR = Math.sqrt(segment.percentage / maxPct) * maxRadius;
const isActive = activeIndex === i;
const isDimmed = activeIndex !== null && activeIndex !== i;
const r = isActive ? baseR * 1.08 : baseR;
const { cx, cy } = positions[i];
return (
<g
key={segment.name}
onMouseEnter={() => onHover(i)}
style={{ transition: "opacity 150ms ease-out" }}
>
<circle
cx={cx}
cy={cy}
r={r}
fill={segment.color}
fillOpacity={isActive ? 0.4 : isDimmed ? 0.12 : 0.25}
stroke={segment.color}
strokeWidth={1}
strokeOpacity={isActive ? 0.7 : isDimmed ? 0.2 : 0.4}
style={{ transition: "all 150ms ease-out" }}
/>
<text
x={cx}
y={isActive ? cy - 8 : cy}
textAnchor="middle"
dominantBaseline="central"
fill="currentColor"
fontSize={14}
fontWeight={600}
style={{
opacity: isDimmed ? 0.4 : 1,
transition: "all 150ms ease-out",
}}
>
{segment.percentage.toLocaleString("fa-IR")}٪
</text>
{isActive && (
<text
x={cx}
y={cy + 10}
textAnchor="middle"
dominantBaseline="central"
fill="currentColor"
fontSize={10}
style={{
opacity: 0.6,
transition: "opacity 150ms ease-out",
}}
>
{segment.name}
</text>
)}
</g>
);
})}
</svg>
);
};
// Customer Segments Card
const CustomerSegmentsCard = () => {
const isPositive = totalCustomersChange >= 0;
const [activeIndex, setActiveIndex] = React.useState<number | null>(null);
return (
<div
className="flex min-w-0 flex-col gap-4 bg-card p-4 sm:gap-5 sm:p-5"
onMouseLeave={() => setActiveIndex(null)}
>
{/* Header - matches SalesRevenueCard pattern */}
<div className="flex items-start justify-between gap-4">
{/* Left: Title + Value + Change */}
<div className="flex flex-col gap-1">
<span className="text-xs text-muted-foreground">کل مشتریان</span>
<span
className={cn("font-mono", "text-xl font-semibold tabular-nums")}
>
{numberFormatter.format(totalCustomers)}
</span>
<div className="flex items-center gap-1">
<span
className={cn(
"font-mono",
"text-sm font-medium",
isPositive ? "text-emerald-600" : "text-red-600",
)}
>
{formatChangePercent(totalCustomersChange)}
</span>
{isPositive ? (
<ArrowUpRight
className="size-3.5 text-emerald-600"
aria-hidden="true"
/>
) : (
<ArrowDownRight
className="size-3.5 text-red-600"
aria-hidden="true"
/>
)}
</div>
</div>
{/* Right: Details button */}
<Button variant="outline" size="sm" className="h-7 px-2 text-xs">
جزئیات
</Button>
</div>
{/* Bubble chart */}
<div className="h-[160px]">
<ProportionalBubbleChart
activeIndex={activeIndex}
onHover={setActiveIndex}
/>
</div>
{/* Legend rows */}
<div className="flex flex-col gap-3">
{customerSegmentsData.map((segment, i) => {
const segmentPositive = segment.change >= 0;
const isActive = activeIndex === i;
const isDimmed = activeIndex !== null && activeIndex !== i;
return (
<div
key={segment.name}
className="flex items-center justify-between"
style={{
opacity: isDimmed ? 0.4 : 1,
transition: "opacity 150ms ease-out",
}}
onMouseEnter={() => setActiveIndex(i)}
onMouseLeave={() => setActiveIndex(null)}
>
<div className="flex items-center gap-2">
<div
className="h-2 w-2 rounded-full"
style={{ backgroundColor: segment.color }}
/>
<span
className={cn(
"text-sm text-muted-foreground",
isActive && "font-medium text-foreground",
)}
style={{ transition: "all 150ms ease-out" }}
>
{segment.name}
</span>
</div>
<div className="flex items-center gap-4">
<span
className={cn(
"font-mono",
"text-sm font-medium tabular-nums",
)}
>
{numberFormatter.format(segment.customers)}
</span>
<div
className={cn(
"font-mono",
"flex items-center gap-0.5 text-xs tabular-nums",
segmentPositive ? "text-emerald-600" : "text-red-600",
)}
>
{segmentPositive ? (
<ArrowUpRight className="size-3" aria-hidden="true" />
) : (
<ArrowDownRight className="size-3" aria-hidden="true" />
)}
{formatChangePercent(segment.change)}
</div>
</div>
</div>
);
})}
</div>
</div>
);
};
// Dotted World Map (Pure SVG)
const DottedWorldMap = ({
activeIndex,
onSelect,
hoveredIndex,
onHover,
}: {
activeIndex: number | null;
onSelect: (index: number | null) => void;
hoveredIndex: number | null;
onHover: (index: number | null) => void;
}) => {
const maxUsers = Math.max(...geographyCountries.map((c) => c.users));
const minRadius = 1.2;
const maxRadius = 2.8;
return (
<svg
viewBox="0 0 63 30"
preserveAspectRatio="xMidYMid meet"
className="h-full w-full"
onMouseLeave={() => onHover(null)}
>
<defs>
<style>{`
@keyframes mapMarkerPulse {
0% { opacity: 1.0; }
50% { opacity: 0.65; }
100% { opacity: 1.0; }
}
`}</style>
</defs>
{mapBasePoints.map((point, i) => {
let dotOpacity = 0.12;
if (hoveredIndex !== null) {
const boosted = spotlightMaps[hoveredIndex].get(i);
dotOpacity = boosted !== undefined ? boosted : 0.12;
}
return (
<circle
key={i}
cx={point.x}
cy={point.y}
r={0.35}
fill="currentColor"
opacity={dotOpacity}
style={{ transition: "opacity 300ms ease-out" }}
/>
);
})}
{geographyPinPositions.map((pin, i) => {
const isActive = activeIndex === i;
const isHovered = hoveredIndex === i;
const isDimmed =
(activeIndex !== null || hoveredIndex !== null) &&
!isActive &&
!isHovered;
const ratio = geographyCountries[i].users / maxUsers;
const baseR = minRadius + ratio * (maxRadius - minRadius);
const r = isActive ? baseR * 1.15 : isHovered ? baseR * 1.08 : baseR;
const markerFill = isActive
? palette.primary
: isHovered
? palette.secondary.light
: palette.tertiary.light;
const markerOpacity = isActive
? 0.7
: isHovered
? 0.6
: isDimmed
? 0.2
: 0.5;
const isIdle = !isActive && !isHovered && !isDimmed;
return (
<g
key={geographyCountries[i].name}
onClick={() => onSelect(isActive ? null : i)}
onMouseEnter={() => onHover(i)}
style={{
cursor: "pointer",
animation: isIdle
? `mapMarkerPulse 3s ease-in-out ${i * 0.6}s infinite`
: "none",
}}
>
{isActive && (
<circle
cx={pin.x}
cy={pin.y}
r={r * 2.5}
fill={palette.primary}
fillOpacity={0.08}
stroke={palette.primary}
strokeWidth={0.3}
strokeOpacity={0.15}
style={{ transition: "all 150ms ease-out" }}
/>
)}
{isHovered && !isActive && (
<circle
cx={pin.x}
cy={pin.y}
r={r * 2.0}
fill={palette.tertiary.light}
fillOpacity={0.06}
stroke={palette.tertiary.light}
strokeWidth={0.2}
strokeOpacity={0.12}
style={{ transition: "all 150ms ease-out" }}
/>
)}
<circle
cx={pin.x}
cy={pin.y}
r={r}
fill={markerFill}
fillOpacity={markerOpacity}
stroke={markerFill}
strokeWidth={0.4}
strokeOpacity={
isActive ? 0.9 : isHovered ? 0.6 : isDimmed ? 0.1 : 0.4
}
style={{ transition: "all 150ms ease-out" }}
/>
</g>
);
})}
</svg>
);
};
// Geography Card
const GeographyCard = () => {
const [activeIndex, setActiveIndex] = React.useState<number | null>(null);
const [hoveredIndex, setHoveredIndex] = React.useState<number | null>(null);
const selected =
activeIndex !== null ? geographyCountries[activeIndex] : null;
const hovered =
hoveredIndex !== null ? geographyCountries[hoveredIndex] : null;
const previewCountry = hovered ?? selected;
const globalDemographics = {
men: Math.round(
geographyCountries.reduce((s, c) => s + c.demographics.men * c.users, 0) /
totalGeographyUsers,
),
women: Math.round(
geographyCountries.reduce(
(s, c) => s + c.demographics.women * c.users,
0,
) / totalGeographyUsers,
),
other: Math.round(
geographyCountries.reduce(
(s, c) => s + c.demographics.other * c.users,
0,
) / totalGeographyUsers,
),
};
const displayUsers = previewCountry
? previewCountry.users
: totalGeographyUsers;
const displayDemographics = previewCountry
? previewCountry.demographics
: globalDemographics;
const displayLabel = previewCountry
? { flag: previewCountry.flag, name: previewCountry.name }
: null;
const tooltipCountry = hovered;
const tooltipPin =
hoveredIndex !== null ? geographyPinPositions[hoveredIndex] : null;
const viewBoxW = 63;
const viewBoxH = 30;
let tooltipLeft = 0;
let tooltipTop = 0;
let tooltipAbove = false;
if (tooltipPin) {
tooltipLeft = (tooltipPin.x / viewBoxW) * 100;
tooltipTop = (tooltipPin.y / viewBoxH) * 100;
tooltipAbove = tooltipPin.y > viewBoxH / 2;
}
const tooltipTranslateX =
tooltipLeft < 20 ? "0%" : tooltipLeft > 80 ? "-100%" : "-50%";
return (
<div className="flex min-w-0 flex-col gap-4 bg-card p-4 sm:gap-5 sm:p-5">
<div className="flex items-start justify-between gap-4">
<div className="flex flex-col gap-1">
<span className="text-xs text-muted-foreground">جغرافیا</span>
<div className="flex items-baseline gap-2">
<span
className={cn("font-mono", "text-xl font-semibold tabular-nums")}
>
{numberFormatter.format(displayUsers)}
</span>
{displayLabel && (
<span className="flex items-center gap-1 border bg-muted/30 px-1.5 py-0.5 text-xs text-muted-foreground">
{displayLabel.flag} {displayLabel.name}
</span>
)}
</div>
</div>
<Button variant="outline" size="sm" className="h-7 px-2 text-xs">
جزئیات
</Button>
</div>
<div className="flex items-center gap-3">
<div className="flex h-1.5 flex-1 overflow-hidden">
<div
style={{
width: `${displayDemographics.men}%`,
backgroundColor: palette.primary,
transition: "width 150ms ease-out",
}}
className="h-full"
/>
<div
style={{
width: `${displayDemographics.women}%`,
backgroundColor: palette.tertiary.light,
transition: "width 150ms ease-out",
}}
className="h-full"
/>
<div
style={{
width: `${displayDemographics.other}%`,
backgroundColor: palette.quinary.light,
transition: "width 150ms ease-out",
}}
className="h-full"
/>
</div>
</div>
<div className="flex items-center gap-3 text-xs text-muted-foreground">
<span className="flex items-center gap-1">
<span
className="inline-block h-1.5 w-1.5 rounded-full"
style={{ backgroundColor: palette.primary }}
/>
مرد {displayDemographics.men.toLocaleString("fa-IR")}٪
</span>
<span className="flex items-center gap-1">
<span
className="inline-block h-1.5 w-1.5 rounded-full"
style={{ backgroundColor: palette.tertiary.light }}
/>
زن {displayDemographics.women.toLocaleString("fa-IR")}٪
</span>
<span className="flex items-center gap-1">
<span
className="inline-block h-1.5 w-1.5 rounded-full"
style={{ backgroundColor: palette.quinary.light }}
/>
سایر {displayDemographics.other.toLocaleString("fa-IR")}٪
</span>
</div>
<div className="relative h-[200px]">
<DottedWorldMap
activeIndex={activeIndex}
onSelect={setActiveIndex}
hoveredIndex={hoveredIndex}
onHover={setHoveredIndex}
/>
{tooltipCountry && tooltipPin && (
<div
className="pointer-events-none absolute z-10 flex flex-col gap-1.5 border border-border bg-popover px-3 py-2 shadow-lg"
style={{
left: `${tooltipLeft}%`,
top: tooltipAbove ? `${tooltipTop}%` : `${tooltipTop}%`,
transform: `${tooltipTranslateX !== "-50%" ? `translateX(${tooltipTranslateX})` : "translateX(-50%)"} ${tooltipAbove ? "translateY(calc(-100% - 12px))" : "translateY(12px)"}`,
opacity: 1,
transition: "opacity 150ms ease-out, transform 150ms ease-out",
}}
>
<div className="flex items-center gap-1.5 text-xs font-medium text-foreground">
<span>{tooltipCountry.flag}</span>
<span>{tooltipCountry.name}</span>
</div>
<span
className={cn(
"font-mono",
"text-sm font-semibold text-foreground tabular-nums",
)}
>
{numberFormatter.format(tooltipCountry.users)}
</span>
<div className="flex h-1 w-full overflow-hidden">
<div
style={{
width: `${tooltipCountry.demographics.men}%`,
backgroundColor: palette.primary,
}}
className="h-full"
/>
<div
style={{
width: `${tooltipCountry.demographics.women}%`,
backgroundColor: palette.tertiary.light,
}}
className="h-full"
/>
<div
style={{
width: `${tooltipCountry.demographics.other}%`,
backgroundColor: palette.quinary.light,
}}
className="h-full"
/>
</div>
<div className="flex items-center gap-2 text-[10px] text-muted-foreground">
<span>م {tooltipCountry.demographics.men.toLocaleString("fa-IR")}٪</span>
<span>ز {tooltipCountry.demographics.women.toLocaleString("fa-IR")}٪</span>
<span>س {tooltipCountry.demographics.other.toLocaleString("fa-IR")}٪</span>
</div>
</div>
)}
</div>
</div>
);
};
// Recent Orders Card (activity table)
function RecentOrdersCard() {
return (
<div className="flex flex-col bg-card">
<div className="px-4 pt-5 sm:px-6">
<h2 className="text-sm font-medium text-foreground">سفارش‌های اخیر</h2>
</div>
<div className="mt-4">
<table className="w-full text-start">
<colgroup>
<col className="w-full sm:w-4/12" />
<col className="sm:w-2/12" />
<col className="sm:w-2/12" />
<col className="sm:w-2/12" />
<col className="sm:w-2/12" />
</colgroup>
<thead className="border-b border-border text-xs text-muted-foreground">
<tr>
<th scope="col" className="py-2 pe-3 ps-4 font-medium sm:ps-6">
مشتری
</th>
<th
scope="col"
className="hidden px-3 py-2 font-medium sm:table-cell"
>
سفارش
</th>
<th scope="col" className="px-3 py-2 font-medium">
وضعیت
</th>
<th scope="col" className="px-3 py-2 text-end font-medium">
مبلغ
</th>
<th
scope="col"
className="hidden py-2 pe-4 ps-3 text-end font-medium sm:table-cell sm:pe-6"
>
زمان
</th>
</tr>
</thead>
<tbody className="divide-y divide-border">
{recentOrders.map((order) => (
<tr key={order.orderId}>
<td className="py-3 pe-3 ps-4 sm:ps-6">
<div className="flex items-center gap-x-3">
<div className="flex size-8 items-center justify-center rounded-full bg-muted text-xs font-medium text-muted-foreground">
{order.customer.avatar}
</div>
<span className="text-sm font-medium text-foreground">
{order.customer.name}
</span>
</div>
</td>
<td className="hidden px-3 py-3 sm:table-cell">
<span className="font-mono text-xs text-muted-foreground">
{order.orderId}
</span>
</td>
<td className="px-3 py-3">
<span
className={`inline-flex items-center gap-x-1.5 rounded-full px-2 py-0.5 text-xs font-medium ${orderStatuses[order.status]}`}
>
<span className="size-1.5 rounded-full bg-current" />
{orderStatusLabels[order.status]}
</span>
</td>
<td className="px-3 py-3 text-end">
<span className="font-mono text-sm text-foreground">
{currencyFormatter.format(order.amount)}
</span>
</td>
<td className="hidden py-3 pe-4 ps-3 text-end sm:table-cell sm:pe-6">
<span className="text-xs text-muted-foreground">
{order.date}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
}
// Main Dashboard Content
const DashboardContent = () => {
return (
<div id="dashboard-main" className="flex w-full flex-1 flex-col bg-card">
{/* Heading */}
<DashboardHeading />
{/* KPI Stats Row */}
<KPIStatsRow />
{/* Middle Row: Sales Revenue + Segmentation */}
<div className="grid lg:grid-cols-3">
<div className="border-b lg:col-span-2 lg:border-e">
<SalesRevenueCard />
</div>
<div className="border-b">
<CustomerSegmentsCard />
</div>
</div>
{/* Bottom Row: Geography + Recent Orders */}
<div className="grid lg:grid-cols-3">
<div className="border-b lg:border-e">
<GeographyCard />
</div>
<div className="border-b lg:col-span-2">
<RecentOrdersCard />
</div>
</div>
</div>
);
};
// ============================================================================
// Main Dashboard Component
// ============================================================================
const Dashboard12 = ({ className }: { className?: string }) => {
return (
<TooltipProvider>
<SidebarProvider
dir="rtl"
lang="fa"
className={cn("min-h-svh w-full overflow-hidden bg-sidebar", className)}
style={{ "--sidebar-width": "18rem" } as React.CSSProperties}
>
<a
href="#dashboard-main"
className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:start-4 focus:z-50 focus:rounded-md focus:bg-background focus:px-3 focus:py-2 focus:text-sm focus:text-foreground focus:ring-2 focus:ring-ring"
>
رفتن به محتوای اصلی
</a>
<AppSidebar />
<SidebarInset className="min-h-svh min-w-0 w-full overflow-auto md:peer-data-[variant=inset]:m-0 md:peer-data-[variant=inset]:rounded-none md:peer-data-[variant=inset]:shadow-none md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ms-0 md:peer-data-[variant=inset]:peer-data-[state=collapsed]:me-0">
<DashboardHeader />
<SecondaryNav />
<DashboardContent />
</SidebarInset>
</SidebarProvider>
</TooltipProvider>
);
};
export { Dashboard12 };