TorbatYar/frontend/components/accounting/AccountingShell.tsx
Mortezakoohjani 12c8615615 Ship enterprise Accounting FE/API with CRUD parity and production wiring.
Adds accounting-service PATCH/archive, fiscal helpers, COA templates and setup status, plus SuperApp Accounting UI (DS, scoreboard, masters, vouchers, ledger, ops modules) with session refresh and HTTPS public API URLs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-24 15:26:43 +03:30

165 lines
6.6 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 Link from "next/link";
import { usePathname } from "next/navigation";
import {
LayoutDashboard,
BookOpen,
CalendarRange,
Coins,
Landmark,
FolderKanban,
FileText,
Wallet,
Users,
Building2,
Briefcase,
FileBarChart2,
ShieldCheck,
Settings,
Moon,
Sun,
ChevronRight,
Scale,
Wrench,
Truck,
Plus,
MoreHorizontal,
} from "lucide-react";
import { cn } from "@/lib/utils";
import { useColorMode } from "@/components/providers/ColorModeProvider";
import { Button } from "@/components/ds";
const NAV = [
{ href: "/accounting", label: "داشبورد", icon: LayoutDashboard },
{ href: "/accounting/chart-of-accounts", label: "دفتر حساب‌ها", icon: BookOpen },
{ href: "/accounting/fiscal", label: "سال و دوره مالی", icon: CalendarRange },
{ href: "/accounting/currencies", label: "ارزها", icon: Coins },
{ href: "/accounting/cost-centers", label: "مراکز هزینه", icon: Landmark },
{ href: "/accounting/projects", label: "پروژه‌ها", icon: FolderKanban },
{ href: "/accounting/vouchers", label: "اسناد و ثبت", icon: FileText },
{ href: "/accounting/ledger", label: "دفتر کل", icon: Scale },
{ href: "/accounting/treasury", label: "خزانه", icon: Wallet },
{ href: "/accounting/customers", label: "مشتریان", icon: Users },
{ href: "/accounting/suppliers", label: "تأمین‌کنندگان", icon: Truck },
{ href: "/accounting/assets", label: "دارایی‌ها", icon: Building2 },
{ href: "/accounting/payroll", label: "حقوق و دستمزد", icon: Briefcase },
{ href: "/accounting/reports", label: "گزارش‌ها", icon: FileBarChart2 },
{ href: "/accounting/audit", label: "حسابرسی", icon: ShieldCheck },
{ href: "/accounting/setup", label: "راه‌اندازی", icon: Wrench },
{ href: "/accounting/settings", label: "تنظیمات", icon: Settings },
];
const MOBILE_PRIMARY = [
{ href: "/accounting", label: "داشبورد", icon: LayoutDashboard },
{ href: "/accounting/vouchers", label: "اسناد", icon: FileText },
{ href: "/accounting/chart-of-accounts", label: "حساب‌ها", icon: BookOpen },
{ href: "/accounting/treasury", label: "خزانه", icon: Wallet },
{ href: "/accounting/setup", label: "بیشتر", icon: MoreHorizontal },
];
function isActive(pathname: string, href: string) {
if (href === "/accounting") return pathname === "/accounting";
return pathname === href || pathname.startsWith(`${href}/`);
}
export function AccountingShell({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
const { mode, toggle } = useColorMode();
return (
<div className="flex min-h-[calc(100vh-4rem)] bg-[var(--canvas)]">
<aside className="sticky top-16 hidden h-[calc(100vh-4rem)] w-64 shrink-0 overflow-y-auto border-l border-[var(--border)] bg-[var(--surface)] p-4 lg:block">
<div className="mb-4 flex items-center justify-between px-2">
<div>
<p className="text-xs text-[var(--muted)]">ماژول</p>
<p className="font-semibold text-secondary">حسابداری</p>
</div>
<Button type="button" variant="ghost" size="icon" onClick={toggle} aria-label="تغییر تم">
{mode === "dark" ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />}
</Button>
</div>
<nav className="space-y-0.5">
{NAV.map((item) => {
const active = isActive(pathname, item.href);
const Icon = item.icon;
return (
<Link
key={item.href}
href={item.href}
className={cn(
"flex items-center gap-3 rounded-xl px-3 py-2.5 text-sm font-medium transition-colors",
active
? "bg-primary text-white shadow-sm"
: "text-[var(--muted)] hover:bg-[var(--surface-muted)] hover:text-secondary"
)}
>
<Icon className="h-4 w-4 shrink-0" />
{item.label}
</Link>
);
})}
</nav>
<Link
href="/dashboard"
className="mt-6 flex items-center gap-2 px-3 text-xs text-[var(--muted)] hover:text-secondary"
>
<ChevronRight className="h-3.5 w-3.5" />
بازگشت به workspace
</Link>
</aside>
<div className="relative min-w-0 flex-1 pb-20 lg:pb-0">
<div className="hidden gap-2 overflow-x-auto border-b border-[var(--border)] bg-[var(--surface)] px-3 py-2 md:flex lg:hidden">
{NAV.map((item) => {
const active = isActive(pathname, item.href);
return (
<Link
key={item.href}
href={item.href}
className={cn(
"whitespace-nowrap rounded-lg px-3 py-1.5 text-xs font-medium",
active ? "bg-primary text-white" : "text-[var(--muted)] hover:bg-[var(--surface-muted)]"
)}
>
{item.label}
</Link>
);
})}
</div>
<div className="mx-auto max-w-7xl px-4 py-6 sm:px-6">{children}</div>
<nav className="fixed inset-x-0 bottom-0 z-40 border-t border-[var(--border)] bg-[var(--surface)]/95 backdrop-blur md:hidden">
<div className="mx-auto flex max-w-lg items-stretch justify-around gap-1 px-2 py-1.5 pe-16">
{MOBILE_PRIMARY.map((item) => {
const active = isActive(pathname, item.href);
const Icon = item.icon;
return (
<Link
key={item.href}
href={item.href}
className={cn(
"flex min-w-0 flex-1 flex-col items-center gap-0.5 rounded-lg px-1 py-1.5 text-[10px] font-medium",
active ? "text-primary" : "text-[var(--muted)]"
)}
>
<Icon className="h-5 w-5 shrink-0" />
<span className="truncate">{item.label}</span>
</Link>
);
})}
</div>
<Link
href="/accounting/vouchers/new"
className="absolute bottom-3 left-3 flex h-12 w-12 items-center justify-center rounded-full bg-primary text-white shadow-lg"
aria-label="سند جدید"
>
<Plus className="h-6 w-6" />
</Link>
</nav>
</div>
</div>
);
}