'use client'; import { useEffect, useRef, useState } from 'react'; import { usePathname, useRouter } from 'next/navigation'; import { ChevronDown, Cloud, FileText, History, KeyRound, LayoutDashboard, LogOut, MapPin, Menu, NotebookText, Package, PencilLine, Shield, Sprout, Tractor, Truck, Upload, Construction, Wheat, X, type LucideIcon, } from 'lucide-react'; import { logout } from '@/lib/api'; type NavItem = { label: string; href: string; icon?: LucideIcon; match?: (pathname: string) => boolean; }; type NavGroup = { key: string; label: string; type: 'link' | 'group'; href?: string; icon?: LucideIcon; items?: NavItem[]; }; const matchesHref = (pathname: string, href: string) => pathname === href || pathname.startsWith(`${href}/`); const navGroups: NavGroup[] = [ { key: 'home', label: 'ホーム', type: 'link', href: '/dashboard', icon: LayoutDashboard, }, { key: 'planning', label: '計画', type: 'group', icon: Wheat, items: [ { label: '作付け計画', href: '/allocation', icon: Wheat }, { label: '施肥計画', href: '/fertilizer', icon: Sprout, match: (pathname) => matchesHref(pathname, '/fertilizer') && !matchesHref(pathname, '/fertilizer/spreading') && !matchesHref(pathname, '/fertilizer/masters'), }, { label: '田植え計画', href: '/rice-transplant', icon: Tractor }, { label: '運搬計画', href: '/distribution', icon: Truck }, ], }, { key: 'records', label: '実績', type: 'group', icon: NotebookText, items: [ { label: '散布実績', href: '/fertilizer/spreading', icon: PencilLine, }, { label: '畔塗記録', href: '/levee-work', icon: Construction }, { label: '作業記録', href: '/workrecords', icon: NotebookText }, ], }, { key: 'masters', label: 'マスター', type: 'group', icon: Package, items: [ { label: '圃場管理', href: '/fields', icon: MapPin }, { label: '資材マスタ', href: '/materials/masters', icon: Package, }, { label: '肥料マスタ', href: '/fertilizer/masters', icon: Sprout, }, ], }, { key: 'support', label: '帳票・連携', type: 'group', icon: FileText, items: [ { label: '在庫管理', href: '/materials', icon: Package, match: (pathname) => matchesHref(pathname, '/materials') && !matchesHref(pathname, '/materials/masters'), }, { label: '帳票出力', href: '/reports', icon: FileText }, { label: 'データ取込', href: '/import', icon: Upload }, { label: '気象', href: '/weather', icon: Cloud }, { label: 'メール履歴', href: '/mail/history', icon: History }, { label: 'メールルール', href: '/mail/rules', icon: Shield }, ], }, ]; const userActions: NavItem[] = [ { label: 'パスワード変更', href: '/settings/password', icon: KeyRound }, ]; export default function Navbar() { const router = useRouter(); const pathname = usePathname(); const navRef = useRef(null); const [openDesktopGroup, setOpenDesktopGroup] = useState(null); const [mobileMenuOpen, setMobileMenuOpen] = useState(false); const [openMobileGroups, setOpenMobileGroups] = useState([]); useEffect(() => { const handlePointerDown = (event: MouseEvent) => { if (!navRef.current?.contains(event.target as Node)) { setOpenDesktopGroup(null); } }; const handleKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape') { setOpenDesktopGroup(null); setMobileMenuOpen(false); } }; document.addEventListener('mousedown', handlePointerDown); document.addEventListener('keydown', handleKeyDown); return () => { document.removeEventListener('mousedown', handlePointerDown); document.removeEventListener('keydown', handleKeyDown); }; }, []); useEffect(() => { setOpenDesktopGroup(null); setMobileMenuOpen(false); setOpenMobileGroups((prev) => { const activeKey = getActiveGroupKey(pathname); if (!activeKey) return prev; return prev.includes(activeKey) ? prev : [activeKey]; }); }, [pathname]); const handleLogout = () => { logout(); }; const navigateTo = (href: string) => { setOpenDesktopGroup(null); setMobileMenuOpen(false); router.push(href); }; const toggleDesktopGroup = (key: string) => { setOpenDesktopGroup((prev) => (prev === key ? null : key)); }; const toggleMobileGroup = (key: string) => { setOpenMobileGroups((prev) => prev.includes(key) ? prev.filter((groupKey) => groupKey !== key) : [...prev, key] ); }; const toggleMobileMenu = () => { if (!mobileMenuOpen) { const activeKey = getActiveGroupKey(pathname); setOpenMobileGroups(activeKey ? [activeKey] : []); } setMobileMenuOpen((prev) => !prev); }; return ( ); } function DesktopLinkButton({ group, pathname, onNavigate, }: { group: NavGroup; pathname: string; onNavigate: (href: string) => void; }) { const active = isGroupActive(group, pathname); const Icon = group.icon; return ( ); } function DesktopGroupButton({ group, isOpen, pathname, onNavigate, onToggle, }: { group: NavGroup; isOpen: boolean; pathname: string; onNavigate: (href: string) => void; onToggle: (key: string) => void; }) { const active = isGroupActive(group, pathname); const Icon = group.icon; return (
{isOpen && group.items ? (
{group.items.map((item) => ( ))}
) : null}
); } function MobileLinkButton({ group, pathname, onNavigate, }: { group: NavGroup; pathname: string; onNavigate: (href: string) => void; }) { const active = isGroupActive(group, pathname); const Icon = group.icon; return ( ); } function MobileGroupButton({ group, isOpen, pathname, onNavigate, onToggle, }: { group: NavGroup; isOpen: boolean; pathname: string; onNavigate: (href: string) => void; onToggle: (key: string) => void; }) { const active = isGroupActive(group, pathname); const Icon = group.icon; return (
{isOpen && group.items ? (
{group.items.map((item) => ( ))}
) : null}
); } function isGroupActive(group: NavGroup, pathname: string) { if (group.type === 'link') { return group.href ? matchesHref(pathname, group.href) : false; } return group.items?.some((item) => isItemActive(item, pathname)) ?? false; } function isItemActive(item: NavItem, pathname: string) { if (item.match) { return item.match(pathname); } return matchesHref(pathname, item.href); } function getActiveGroupKey(pathname: string) { return navGroups.find((group) => isGroupActive(group, pathname))?.key ?? null; }