ca-grow-ops-manager/frontend/src/components/Layout.tsx
fullsizemalt 15b50a74c6
Some checks are pending
Deploy to Production / deploy (push) Waiting to run
Test / backend-test (push) Waiting to run
Test / frontend-test (push) Waiting to run
feat: global breadcrumbs + walkthrough photo upload
Breadcrumbs:
- Added Breadcrumbs to main Layout (appears on ALL pages)
- Dynamic route support (/batches/:id, /rooms/:id)
- Proper navigation hierarchy

Daily Walkthrough:
- Enhanced layout with progress bar
- Photo capture from camera or file upload
- Notes fields for each check
- Improved touch targets and mobile UX

Removed inline breadcrumbs from individual pages since
they now come from the global Layout.
2025-12-12 21:22:01 -08:00

166 lines
7.1 KiB
TypeScript

import { useState } from 'react';
import { Outlet, Link } from 'react-router-dom';
import { Menu, X, Command } from 'lucide-react';
import { useAuth } from '../context/AuthContext';
import { Sidebar } from './layout/Sidebar';
import { MobileNav } from './layout/MobileNav';
import { MobileNavSheet } from './layout/MobileNavSheet';
import { UserMenu } from './layout/UserMenu';
import ThemeToggle from './ThemeToggle';
import { CommandPalette } from './ui/CommandPalette';
import { SessionTimeoutWarning } from './ui/SessionTimeoutWarning';
import { PageTitleUpdater } from '../hooks/usePageTitle';
import AnnouncementBanner from './AnnouncementBanner';
import { DevTools } from './dev/DevTools';
import { Breadcrumbs } from './ui/Breadcrumbs';
export default function Layout() {
const { user } = useAuth();
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [mobileSheetOpen, setMobileSheetOpen] = useState(false);
return (
<div className="flex flex-col h-screen bg-primary">
{/* Skip to main content link (accessibility) */}
<a
href="#main-content"
className="absolute left-0 top-0 -translate-y-full bg-accent text-white px-4 py-2 rounded-br-lg font-medium focus:translate-y-0 z-50 transition-transform duration-fast"
>
Skip to main content
</a>
{/* Mobile Top Header */}
<header className="md:hidden bg-elevated border-b border-default px-4 py-3 flex items-center justify-between sticky top-0 z-40">
<Link to="/" className="flex items-center gap-3">
<img
src="/assets/logo-777-wolfpack.jpg"
alt="777 Wolfpack"
className="w-8 h-8 rounded-lg"
/>
<div>
<h1 className="text-sm font-semibold text-primary leading-tight tracking-tight">
777 Wolfpack
</h1>
<p className="text-[10px] text-tertiary">
Operations
</p>
</div>
</Link>
<button
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
className="p-2 rounded-md hover:bg-tertiary transition-colors duration-fast"
aria-label={mobileMenuOpen ? 'Close menu' : 'Open menu'}
>
{mobileMenuOpen ? (
<X size={20} className="text-primary" />
) : (
<Menu size={20} className="text-primary" />
)}
</button>
</header>
{/* Mobile Dropdown Menu */}
{mobileMenuOpen && (
<div
className="md:hidden fixed inset-0 top-[53px] z-30 bg-black/40 animate-fade-in"
onClick={() => setMobileMenuOpen(false)}
>
<div
className="bg-elevated border-b border-default shadow-xl max-h-[60vh] overflow-y-auto animate-slide-up"
onClick={e => e.stopPropagation()}
>
<div className="p-4">
<Sidebar onItemClick={() => setMobileMenuOpen(false)} />
</div>
</div>
</div>
)}
<div className="flex flex-1 overflow-hidden">
{/* Desktop Sidebar */}
<aside
className="hidden md:flex w-60 lg:w-64 bg-secondary border-r border-default flex-col"
role="navigation"
aria-label="Main navigation"
>
{/* Logo */}
<div className="p-4 border-b border-default">
<Link to="/" className="flex items-center gap-3 group">
<div className="relative">
<img
src="/assets/logo-777-wolfpack.jpg"
alt="777 Wolfpack"
className="w-9 h-9 rounded-lg transition-transform duration-fast group-hover:scale-105"
/>
{/* Status indicator */}
<div className="absolute -bottom-0.5 -right-0.5 w-2.5 h-2.5 bg-success rounded-full border-2 border-secondary" />
</div>
<div>
<h1 className="text-sm font-semibold text-primary leading-tight tracking-tight">
777 Wolfpack
</h1>
<p className="text-xs text-tertiary">
Operations
</p>
</div>
</Link>
</div>
{/* Search hint */}
<div className="px-4 py-3 border-b border-subtle">
<button
onClick={() => dispatchEvent(new KeyboardEvent('keydown', { key: 'k', metaKey: true }))}
className="w-full flex items-center gap-2 px-3 py-2 text-sm text-tertiary bg-tertiary/50 hover:bg-tertiary rounded-md transition-colors duration-fast"
>
<Command size={14} />
<span>Search...</span>
<kbd className="ml-auto text-[10px] font-mono bg-primary px-1.5 py-0.5 rounded">K</kbd>
</button>
</div>
{/* Navigation */}
<Sidebar />
{/* Footer */}
<div className="mt-auto p-3 border-t border-default">
<UserMenu />
</div>
</aside>
{/* Main Content */}
<main
id="main-content"
className="flex-1 overflow-auto pb-20 md:pb-6 custom-scrollbar bg-primary"
role="main"
>
<PageTitleUpdater />
<AnnouncementBanner />
<div className="p-4 md:p-6 lg:p-8">
{/* Global Breadcrumbs - appears on all pages */}
<Breadcrumbs />
<Outlet />
</div>
</main>
</div>
{/* Mobile Bottom Navigation */}
<MobileNav onMoreClick={() => setMobileSheetOpen(true)} />
{/* Mobile Navigation Sheet */}
<MobileNavSheet
isOpen={mobileSheetOpen}
onClose={() => setMobileSheetOpen(false)}
/>
{/* Command Palette */}
<CommandPalette />
{/* Session Timeout Warning */}
<SessionTimeoutWarning />
{/* Dev Tools */}
<DevTools />
</div>
);
}