DI2: удаление старого модуля финансов — пул БД и аудит перенесены в finance-di2

- server/finance/ (19 файлов) удалён: пул финансовой БД перенесён в server/finance-di2/db-core.ts, db-client.ts теперь реэкспортирует из ./db-core
- Шедулер аудита перенесён в server/finance-di2/audit-scheduler.ts (03:00 МСК, setInterval, без node-cron), запуск из registerDi2Routes с защитой от повторного старта
- История аудита DI2 теперь персистентна: загрузка/запись data/finance-audit-history.json в audit-agent.ts (порт из удалённого server/finance/audit.ts)
- registerFinanceRoutes убран из server/routes/index.ts, registerDi2Routes остался на его месте
- Клиент: удалены pages/finance/, components/finance/, hooks/useFinance.ts, hooks/useColumnLabels.ts, lib/format.ts, pages/salary.tsx; роуты /finance*, /income*, /expenses*, /salary и lazy-импорты убраны из App.tsx
- Навигация: «Финансы» в Sidebar и MobileBottomNav ведёт на субапп /di2 (внешняя ссылка), гейт moduleAccess.finance + finance.view сохранён
- index.css: удалён блок .hover-elevate/.hover-elevate-2/.active-elevate (использовался только старым модульным DataTable)
- FinanceBottomNav.tsx сохранён как референс для этапа 3 (.superpowers/sdd/di2-restyle/FinanceBottomNav.reference.tsx)
- Не тронуто: client-di2/, сиды прав finance.view/manage, finance_access (module-access)
This commit is contained in:
2026-09-06 21:53:47 +03:00
parent 6bcf4dfdfc
commit 66f4e5ef53
68 changed files with 212 additions and 14471 deletions

View File

@@ -0,0 +1,144 @@
import { Link, useLocation } from "wouter";
import { cn } from "@/lib/utils";
import {
LayoutDashboard,
Wallet,
Calculator,
ShieldCheck,
Tags,
TrendingUp,
TrendingDown,
Settings,
Menu,
ExternalLink,
} from "lucide-react";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import type { FinanceTab } from "./FinanceNav";
interface FinanceBottomNavProps {
active: FinanceTab;
onChange: (tab: FinanceTab) => void;
}
export function FinanceBottomNav({ active, onChange }: FinanceBottomNavProps) {
const [location, setLocation] = useLocation();
// путь в wouter может включать search-параметр
const onDashboard = location.split("?")[0] === "/finance";
const goDashboardTab = (tab: FinanceTab) => {
if (onDashboard) {
onChange(tab);
} else {
setLocation(tab === "dashboard" ? "/finance" : `/finance?tab=${tab}`);
}
};
const btnClass = (isActive: boolean) =>
cn(
"flex flex-col items-center justify-center gap-0.5 flex-1 h-full rounded-lg transition-colors",
isActive ? "text-primary" : "text-muted-foreground hover:text-foreground"
);
const sectionsActive =
active === "debt" ||
active === "calculations" ||
active === "audit" ||
active === "income" ||
active === "expenses" ||
active === "income-categories" ||
active === "expense-categories";
return (
<div className="fixed bottom-0 left-0 right-0 bg-background border-t border-border z-50 md:hidden safe-area-bottom">
<nav className="flex items-stretch justify-around h-14 px-2" aria-label="Навигация по финансам">
<button
onClick={() => goDashboardTab("dashboard")}
aria-label="Обзор"
className={btnClass(active === "dashboard")}
>
<LayoutDashboard className="w-5 h-5" aria-hidden="true" />
<span className="text-[10px] font-medium">Обзор</span>
</button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
aria-label="Разделы"
className={cn(btnClass(false), "relative", sectionsActive && "text-primary")}
>
<Menu className="w-5 h-5" aria-hidden="true" />
<span className="text-[10px] font-medium">Разделы</span>
{sectionsActive && (
<span
aria-hidden="true"
className="absolute top-1 right-1/2 translate-x-4 w-1.5 h-1.5 rounded-full bg-primary"
/>
)}
</button>
</DropdownMenuTrigger>
<DropdownMenuContent side="top" align="center" className="w-56 mb-2">
<DropdownMenuItem className="text-xs cursor-pointer" onSelect={() => goDashboardTab("income")}>
<TrendingUp className="w-3.5 h-3.5 mr-2" />
Доходы
</DropdownMenuItem>
<DropdownMenuItem className="text-xs cursor-pointer" onSelect={() => goDashboardTab("expenses")}>
<TrendingDown className="w-3.5 h-3.5 mr-2" />
Расходы
</DropdownMenuItem>
<DropdownMenuSeparator />
<Link href="/finance/debt">
<DropdownMenuItem className="text-xs cursor-pointer">
<Wallet className="w-3.5 h-3.5 mr-2" />
Дебиторка
</DropdownMenuItem>
</Link>
<Link href="/finance/calculations">
<DropdownMenuItem className="text-xs cursor-pointer">
<Calculator className="w-3.5 h-3.5 mr-2" />
Расчёты
</DropdownMenuItem>
</Link>
<Link href="/finance/audit">
<DropdownMenuItem className="text-xs cursor-pointer">
<ShieldCheck className="w-3.5 h-3.5 mr-2" />
Аудит
</DropdownMenuItem>
</Link>
<DropdownMenuSeparator />
<DropdownMenuItem className="text-xs cursor-pointer" onSelect={() => goDashboardTab("income-categories")}>
<Tags className="w-3.5 h-3.5 mr-2" />
Категории доходов
</DropdownMenuItem>
<DropdownMenuItem className="text-xs cursor-pointer" onSelect={() => goDashboardTab("expense-categories")}>
<Tags className="w-3.5 h-3.5 mr-2" />
Категории расходов
</DropdownMenuItem>
<DropdownMenuSeparator />
{/* Внешняя ссылка на субапп Data-Insight2 — полная версия финансов */}
<a href="/di2" target="_blank" rel="noopener">
<DropdownMenuItem className="text-xs cursor-pointer">
<ExternalLink className="w-3.5 h-3.5 mr-2" />
Data-Insight (полная версия)
</DropdownMenuItem>
</a>
</DropdownMenuContent>
</DropdownMenu>
<Link
href="/finance/settings"
aria-label="Настройки"
className={btnClass(active === "settings")}
>
<Settings className="w-5 h-5" aria-hidden="true" />
<span className="text-[10px] font-medium">Настройки</span>
</Link>
</nav>
</div>
);
}