DI2-роутер регистрируется перед основным dashboard.routes.ts и перехватывал GET /api/dashboard (403 без finance.manage и неверный JSON — live-регрессия главного дашборда CRM). Роут переименован, в DI2_PREFIXES /api/dashboard заменён на /api/di2, клиент client-di2 ходит на новый путь (fetch + queryKey + prefetch в app-sidebar). Форматы старого модуля финансов не тронуты.
165 lines
6.4 KiB
TypeScript
165 lines
6.4 KiB
TypeScript
import { useCallback } from "react";
|
||
import { useLocation } from "wouter";
|
||
import { queryClient } from "@/lib/queryClient";
|
||
import {
|
||
Sidebar,
|
||
SidebarContent,
|
||
SidebarGroup,
|
||
SidebarGroupContent,
|
||
SidebarGroupLabel,
|
||
SidebarMenu,
|
||
SidebarMenuButton,
|
||
SidebarMenuItem,
|
||
SidebarHeader,
|
||
} from "@/components/ui/sidebar";
|
||
import { LayoutDashboard, TrendingUp, Wallet, AlertCircle, Settings, Database, Handshake, Calculator, CalendarRange, Banknote, ShieldCheck, Grid3X3 } from "lucide-react";
|
||
|
||
const settingsItems = [
|
||
{ title: "Настройки", url: "/settings", icon: Settings },
|
||
];
|
||
|
||
export function AppSidebar() {
|
||
const [location] = useLocation();
|
||
|
||
const prefetch = useCallback((keys: string[]) => {
|
||
keys.forEach(key => {
|
||
queryClient.prefetchQuery({ queryKey: [key] });
|
||
});
|
||
}, []);
|
||
|
||
const isIncomeSection = location.startsWith("/income");
|
||
const isExpenseSection = location.startsWith("/expenses");
|
||
|
||
return (
|
||
<Sidebar>
|
||
<SidebarHeader className="px-4 py-5 border-b border-sidebar-border">
|
||
<div className="flex items-center gap-2.5">
|
||
<div className="w-8 h-8 rounded-md bg-primary flex items-center justify-center">
|
||
<Database className="w-4 h-4 text-primary-foreground" />
|
||
</div>
|
||
<div>
|
||
<div className="font-semibold text-sm text-sidebar-foreground">ОмегаПром</div>
|
||
<div className="text-xs text-muted-foreground">Финансы</div>
|
||
</div>
|
||
</div>
|
||
</SidebarHeader>
|
||
<SidebarContent>
|
||
<SidebarGroup>
|
||
<SidebarGroupLabel>Аналитика</SidebarGroupLabel>
|
||
<SidebarGroupContent>
|
||
<SidebarMenu>
|
||
<SidebarMenuItem>
|
||
<SidebarMenuButton asChild isActive={location === "/"}>
|
||
<a href="/" data-testid="nav-dashboard" onMouseEnter={() => prefetch(["/api/di2/dashboard"])}>
|
||
<LayoutDashboard className="w-4 h-4" />
|
||
<span>Дашборд</span>
|
||
</a>
|
||
</SidebarMenuButton>
|
||
</SidebarMenuItem>
|
||
|
||
<SidebarMenuItem>
|
||
<SidebarMenuButton asChild isActive={isIncomeSection}>
|
||
<a href="/income" data-testid="nav-income" onMouseEnter={() => prefetch(["/api/income/tree-summary"])}>
|
||
<TrendingUp className="w-4 h-4" />
|
||
<span>Доходы</span>
|
||
</a>
|
||
</SidebarMenuButton>
|
||
</SidebarMenuItem>
|
||
|
||
<SidebarMenuItem>
|
||
<SidebarMenuButton asChild isActive={location === "/profitability"}>
|
||
<a href="/profitability" data-testid="nav-profitability">
|
||
<Handshake className="w-4 h-4" />
|
||
<span>Прибыльность</span>
|
||
</a>
|
||
</SidebarMenuButton>
|
||
</SidebarMenuItem>
|
||
|
||
<SidebarMenuItem>
|
||
<SidebarMenuButton asChild isActive={isExpenseSection}>
|
||
<a href="/expenses" data-testid="nav-expenses" onMouseEnter={() => prefetch(["/api/expense-categories"])}>
|
||
<Wallet className="w-4 h-4" />
|
||
<span>Расходы</span>
|
||
</a>
|
||
</SidebarMenuButton>
|
||
</SidebarMenuItem>
|
||
|
||
<SidebarMenuItem>
|
||
<SidebarMenuButton asChild isActive={location === "/debt"}>
|
||
<a href="/debt" data-testid="nav-debt" onMouseEnter={() => prefetch(["/api/debt/filters", "/api/debt/analytics"])}>
|
||
<AlertCircle className="w-4 h-4" />
|
||
<span>Дебиторская задолженность</span>
|
||
</a>
|
||
</SidebarMenuButton>
|
||
</SidebarMenuItem>
|
||
|
||
<SidebarMenuItem>
|
||
<SidebarMenuButton asChild isActive={location === "/calculations"}>
|
||
<a href="/calculations" data-testid="nav-calculations">
|
||
<Calculator className="w-4 h-4" />
|
||
<span>Расчеты</span>
|
||
</a>
|
||
</SidebarMenuButton>
|
||
</SidebarMenuItem>
|
||
|
||
<SidebarMenuItem>
|
||
<SidebarMenuButton asChild isActive={location === "/planning"}>
|
||
<a href="/planning" data-testid="nav-planning">
|
||
<CalendarRange className="w-4 h-4" />
|
||
<span>Планирование</span>
|
||
</a>
|
||
</SidebarMenuButton>
|
||
</SidebarMenuItem>
|
||
|
||
<SidebarMenuItem>
|
||
<SidebarMenuButton asChild isActive={location === "/salary"}>
|
||
<a href="/salary" data-testid="nav-salary">
|
||
<Banknote className="w-4 h-4" />
|
||
<span>Зарплата</span>
|
||
</a>
|
||
</SidebarMenuButton>
|
||
</SidebarMenuItem>
|
||
|
||
<SidebarMenuItem>
|
||
<SidebarMenuButton asChild isActive={location === "/audit"}>
|
||
<a href="/audit" data-testid="nav-audit">
|
||
<ShieldCheck className="w-4 h-4" />
|
||
<span>Аудит данных</span>
|
||
</a>
|
||
</SidebarMenuButton>
|
||
</SidebarMenuItem>
|
||
|
||
<SidebarMenuItem>
|
||
<SidebarMenuButton asChild isActive={location === "/heatmap"}>
|
||
<a href="/heatmap" data-testid="nav-heatmap">
|
||
<Grid3X3 className="w-4 h-4" />
|
||
<span>Тепловая карта</span>
|
||
</a>
|
||
</SidebarMenuButton>
|
||
</SidebarMenuItem>
|
||
</SidebarMenu>
|
||
</SidebarGroupContent>
|
||
</SidebarGroup>
|
||
|
||
<SidebarGroup>
|
||
<SidebarGroupLabel>Настройки</SidebarGroupLabel>
|
||
<SidebarGroupContent>
|
||
<SidebarMenu>
|
||
{settingsItems.map((item) => (
|
||
<SidebarMenuItem key={item.title}>
|
||
<SidebarMenuButton asChild isActive={location === item.url}>
|
||
<a href={item.url} data-testid="nav-settings">
|
||
<item.icon className="w-4 h-4" />
|
||
<span>{item.title}</span>
|
||
</a>
|
||
</SidebarMenuButton>
|
||
</SidebarMenuItem>
|
||
))}
|
||
</SidebarMenu>
|
||
</SidebarGroupContent>
|
||
</SidebarGroup>
|
||
</SidebarContent>
|
||
</Sidebar>
|
||
);
|
||
}
|