Files
iistwin/client-di2/src/components/app-sidebar.tsx
Ильяс Султанов 49578ab442 DI2 фикс коллизии: дашборд DI2 переименован в /api/di2/dashboard
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). Форматы старого модуля финансов не тронуты.
2026-09-06 19:58:24 +03:00

165 lines
6.4 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.

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>
);
}