ca-grow-ops-manager/frontend/src/components/Layout.tsx
fullsizemalt 5c86b98628
Some checks are pending
Test / backend-test (push) Waiting to run
Test / frontend-test (push) Waiting to run
feat: Pulse sensor integration with real-time WebSocket alerts
2026-01-05 20:09:39 -08:00

143 lines
7.8 KiB
TypeScript

import { useState } from 'react';
import { Outlet, useLocation } from 'react-router-dom';
import { motion, AnimatePresence } from 'framer-motion';
import { useAuth } from '../context/AuthContext';
import { Sidebar } from './layout/Sidebar';
import { MobileNav } from './layout/MobileNav';
import { MobileNavSheet } from './layout/MobileNavSheet';
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';
import { pageVariants } from '../lib/animations';
import { Search, Bell, Settings, Filter, ChevronDown } from 'lucide-react';
import ThemeToggle from './ThemeToggle';
import { UserMenu } from './layout/UserMenu';
import { NotificationBell } from './notifications/NotificationBell';
export default function Layout() {
const location = useLocation();
const [mobileSheetOpen, setMobileSheetOpen] = useState(false);
return (
<div className="flex h-screen bg-[var(--color-bg-primary)] text-[var(--color-text-primary)] overflow-hidden font-sans">
{/* Accessibility: Skip to main content */}
<a
href="#main-content"
className="absolute left-0 top-0 -translate-y-full bg-[var(--color-primary)] 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>
{/* Desktop Sidebar - Persistent on left */}
<aside className="hidden lg:flex flex-col w-[260px] border-r border-[var(--color-border-subtle)] bg-[var(--color-bg-secondary)] z-30">
<div className="h-16 flex items-center px-6 border-b border-[var(--color-border-subtle)]">
<div className="flex items-center gap-3">
<div className="w-9 h-9 rounded-xl bg-[var(--color-primary)] flex items-center justify-center text-[var(--color-text-inverse)] font-bold text-lg shadow-lg shadow-emerald-500/20">
V
</div>
<div className="flex flex-col">
<span className="text-sm font-bold tracking-tight leading-none text-[var(--color-text-primary)]">Veridian</span>
<span className="text-[10px] text-[var(--color-text-tertiary)] font-medium uppercase tracking-wider leading-none mt-0.5">Cultivation Platform</span>
</div>
</div>
</div>
<div className="flex-1 overflow-y-auto pt-4">
<Sidebar />
</div>
<div className="p-4 border-t border-[var(--color-border-subtle)]">
<UserMenu />
</div>
</aside>
{/* Main Content Area */}
<div className="flex-1 flex flex-col min-w-0 overflow-hidden relative">
{/* Topbar - Search, Global Filters, Vitals */}
<header className="h-16 flex items-center justify-between px-4 sm:px-6 lg:px-8 border-b border-[var(--color-border-subtle)] bg-[var(--color-bg-secondary)]/80 backdrop-blur-xl z-20">
<div className="flex items-center gap-4 flex-1">
{/* Facility Switcher / Filter */}
<div className="hidden md:flex items-center gap-2 px-3 py-1.5 rounded-full bg-[var(--color-bg-tertiary)] border border-[var(--color-border-subtle)] cursor-pointer hover:border-[var(--color-border-default)] transition-all">
<span className="text-xs font-semibold uppercase tracking-wider text-[var(--color-text-tertiary)]">Facility</span>
<span className="text-xs font-bold text-[var(--color-text-primary)]">NORCAL-01</span>
<ChevronDown size={14} className="text-[var(--color-text-tertiary)]" />
</div>
{/* Global Search */}
<div className="relative group max-w-sm w-full hidden sm:block">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-[var(--color-text-tertiary)] group-focus-within:text-[var(--color-primary)] transition-colors" size={16} />
<input
type="text"
placeholder="Search batches, rooms, tasks..."
className="w-full bg-[var(--color-bg-tertiary)] border border-[var(--color-border-subtle)] rounded-full pl-10 pr-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]/20 focus:border-[var(--color-primary)] transition-all"
/>
<div className="absolute right-3 top-1/2 -translate-y-1/2 flex items-center gap-1 opacity-50">
<kbd className="text-[10px] font-mono text-[var(--color-text-tertiary)]"></kbd>
<kbd className="text-[10px] font-mono text-[var(--color-text-tertiary)]">K</kbd>
</div>
</div>
</div>
<div className="flex items-center gap-3">
<div className="hidden md:flex items-center gap-4 mr-4 text-[11px] font-semibold uppercase tracking-wider text-[var(--color-text-tertiary)]">
<div className="flex items-center gap-1.5">
<div className="w-2 h-2 rounded-full bg-[var(--color-success)] animate-pulse" />
<span>Terminal Live</span>
</div>
<div className="flex items-center gap-1.5">
<div className="w-2 h-2 rounded-full bg-[var(--color-accent)]" />
<span>32 Sensors Active</span>
</div>
</div>
<ThemeToggle />
<NotificationBell />
<button className="lg:hidden p-2 text-[var(--color-text-tertiary)]" onClick={() => setMobileSheetOpen(true)}>
<Filter size={20} />
</button>
</div>
</header>
{/* Content Scroller */}
<main
id="main-content"
className="flex-1 overflow-y-auto custom-scrollbar relative"
role="main"
>
<PageTitleUpdater />
<AnnouncementBanner />
<div className="max-w-[1920px] mx-auto p-4 sm:p-6 lg:p-8">
<Breadcrumbs />
<AnimatePresence mode="wait">
<motion.div
key={location.pathname}
variants={pageVariants}
initial="initial"
animate="animate"
exit="exit"
className="w-full"
>
<Outlet />
</motion.div>
</AnimatePresence>
</div>
</main>
</div>
{/* Mobile Interface Components */}
<MobileNav onMoreClick={() => setMobileSheetOpen(true)} />
<MobileNavSheet
isOpen={mobileSheetOpen}
onClose={() => setMobileSheetOpen(false)}
/>
{/* System Utilities */}
<CommandPalette />
<SessionTimeoutWarning />
<DevTools />
</div>
);
}