Rebranded for Thunderbird Portal (THBD): numbering prefix EL->TH, all "ELMI"/"ЭЛМИ" UI strings genericized, ELMI's real contract templates and company legal/bank details removed (not applicable to this company), fresh VAPID keypair and encryption key, fresh single-migration schema history. Same architecture as elmi-portal: Web Push notifications, unread-chat app badge, cookie-session auth. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gh2UXUQUVBWroWEnn1FLFG
101 lines
3.9 KiB
TypeScript
101 lines
3.9 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import Link from "next/link";
|
|
import { usePathname } from "next/navigation";
|
|
import { Inbox, Truck, MessageCircle, ListTodo, MoreHorizontal, X } from "lucide-react";
|
|
import { links, type Role } from "./nav-data";
|
|
|
|
// The 4 most-used sections get their own bottom-bar slot (bar's too narrow
|
|
// for all 8, and cramming more in defeats the point of a thumb-reachable
|
|
// tab bar); everything else lives behind "Ещё".
|
|
const PRIMARY_HREFS = ["/chat", "/orders", "/tasks", "/inquiries"];
|
|
const PRIMARY_ICONS: Record<string, typeof Inbox> = {
|
|
"/chat": MessageCircle,
|
|
"/orders": Truck,
|
|
"/tasks": ListTodo,
|
|
"/inquiries": Inbox,
|
|
};
|
|
|
|
export function MobileNav({ role, unread }: { role: Role; unread: number }) {
|
|
const pathname = usePathname();
|
|
const [moreOpen, setMoreOpen] = useState(false);
|
|
|
|
const visibleLinks = links.filter((link) => link.roles.includes(role));
|
|
const primary = PRIMARY_HREFS.map((href) => visibleLinks.find((l) => l.href === href)).filter((l) => l !== undefined);
|
|
const overflow = visibleLinks.filter((l) => !PRIMARY_HREFS.includes(l.href));
|
|
const overflowActive = overflow.some((l) => pathname.startsWith(l.href));
|
|
|
|
return (
|
|
<>
|
|
{moreOpen && (
|
|
<div className="fixed inset-0 z-40 bg-black/40 lg:hidden" onClick={() => setMoreOpen(false)}>
|
|
<div
|
|
className="absolute inset-x-0 bottom-0 rounded-t-xl border-t border-border bg-surface p-2 pb-[calc(0.5rem+env(safe-area-inset-bottom))]"
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
<div className="mb-1 flex items-center justify-between px-2 py-1.5">
|
|
<span className="text-sm font-semibold">Ещё</span>
|
|
<button onClick={() => setMoreOpen(false)} className="rounded-md p-1 text-text-faint hover:bg-surface-hover">
|
|
<X size={16} />
|
|
</button>
|
|
</div>
|
|
{overflow.map(({ href, label, icon: Icon }) => {
|
|
const active = pathname.startsWith(href);
|
|
return (
|
|
<Link
|
|
key={href}
|
|
href={href}
|
|
onClick={() => setMoreOpen(false)}
|
|
className={`flex items-center gap-3 rounded-md px-3 py-2.5 text-sm font-medium ${
|
|
active ? "bg-accent-soft text-accent-soft-text" : "text-text-muted hover:bg-surface-hover hover:text-text"
|
|
}`}
|
|
>
|
|
<Icon size={17} />
|
|
{label}
|
|
</Link>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<nav
|
|
className="app-mobile-nav fixed inset-x-0 bottom-0 z-30 flex border-t border-border bg-surface pb-[env(safe-area-inset-bottom)] lg:hidden"
|
|
aria-label="Основная навигация"
|
|
>
|
|
{primary.map(({ href, label, icon: FallbackIcon }) => {
|
|
const active = pathname.startsWith(href);
|
|
const Icon = PRIMARY_ICONS[href] ?? FallbackIcon;
|
|
return (
|
|
<Link
|
|
key={href}
|
|
href={href}
|
|
className={`relative flex flex-1 flex-col items-center gap-0.5 py-2 text-[11px] font-medium ${
|
|
active ? "text-accent" : "text-text-faint"
|
|
}`}
|
|
>
|
|
<Icon size={20} />
|
|
{label}
|
|
{href === "/chat" && unread > 0 && (
|
|
<span className="absolute right-[22%] top-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-danger px-1 text-[10px] font-semibold text-white">
|
|
{unread}
|
|
</span>
|
|
)}
|
|
</Link>
|
|
);
|
|
})}
|
|
<button
|
|
onClick={() => setMoreOpen(true)}
|
|
className={`flex flex-1 flex-col items-center gap-0.5 py-2 text-[11px] font-medium ${
|
|
overflowActive ? "text-accent" : "text-text-faint"
|
|
}`}
|
|
>
|
|
<MoreHorizontal size={20} />
|
|
Ещё
|
|
</button>
|
|
</nav>
|
|
</>
|
|
);
|
|
}
|