Files
CRM_ENVR/src/components/layout/app-shell.tsx
T
2026-06-26 16:38:18 +02:00

115 lines
5.2 KiB
TypeScript

"use client"
import { useState, useEffect } from "react"
import { usePathname } from "next/navigation"
import { motion, AnimatePresence } from "framer-motion"
import { Sidebar } from "./sidebar"
import { Topbar } from "./topbar"
import { SystemMonitor } from "./system-monitor"
interface AppShellProps {
children: React.ReactNode
}
export function AppShell({ children }: AppShellProps) {
const [sidebarCollapsed, setSidebarCollapsed] = useState(false)
const [mobileOpen, setMobileOpen] = useState(false)
const pathname = usePathname()
// Close mobile sidebar on route change
useEffect(() => {
setMobileOpen(false)
}, [pathname])
// Persist sidebar state
useEffect(() => {
const saved = localStorage.getItem("sidebar-collapsed")
if (saved) setSidebarCollapsed(saved === "true")
}, [])
const toggleSidebar = () => {
const next = !sidebarCollapsed
setSidebarCollapsed(next)
localStorage.setItem("sidebar-collapsed", String(next))
}
return (
<div className="min-h-screen bg-[#F8F8F8] dark:bg-[#0A0A0A] relative overflow-hidden"
style={{
backgroundImage: "radial-gradient(circle, #CC000010 1px, transparent 1px), radial-gradient(circle, #0033CC06 1px, transparent 1px)",
backgroundSize: "28px 28px, 14px 14px",
backgroundPosition: "0 0, 7px 7px",
}}
>
{/* Spider-Man top gradient bar */}
<div className="fixed top-0 left-0 right-0 h-[3px] w-full bg-gradient-to-r from-[#CC0000] via-[#FFFFFF] to-[#0033CC] dark:from-[#FF1111] dark:via-[#FFFFFF] dark:to-[#1144FF] z-50" />
{/* Corner spider webs */}
<div className="hidden lg:block fixed top-0 left-0 pointer-events-none z-0 opacity-[0.07] dark:opacity-[0.12]">
<svg width="180" height="180" viewBox="0 0 180 180" fill="none" xmlns="http://www.w3.org/2000/svg">
<line x1="0" y1="0" x2="180" y2="0" stroke="#CC0000" strokeWidth="0.8"/>
<line x1="0" y1="0" x2="180" y2="60" stroke="#CC0000" strokeWidth="0.8"/>
<line x1="0" y1="0" x2="180" y2="120" stroke="#CC0000" strokeWidth="0.8"/>
<line x1="0" y1="0" x2="180" y2="180" stroke="#CC0000" strokeWidth="0.8"/>
<line x1="0" y1="0" x2="120" y2="180" stroke="#CC0000" strokeWidth="0.8"/>
<line x1="0" y1="0" x2="60" y2="180" stroke="#CC0000" strokeWidth="0.8"/>
<line x1="0" y1="0" x2="0" y2="180" stroke="#CC0000" strokeWidth="0.8"/>
<path d="M0,30 Q30,30 30,0" stroke="#CC0000" strokeWidth="0.8" fill="none"/>
<path d="M0,60 Q60,60 60,0" stroke="#CC0000" strokeWidth="0.8" fill="none"/>
<path d="M0,90 Q90,90 90,0" stroke="#CC0000" strokeWidth="0.8" fill="none"/>
<path d="M0,120 Q120,120 120,0" stroke="#CC0000" strokeWidth="0.8" fill="none"/>
<path d="M0,150 Q150,150 150,0" stroke="#CC0000" strokeWidth="0.8" fill="none"/>
<path d="M0,180 Q180,180 180,0" stroke="#CC0000" strokeWidth="0.8" fill="none"/>
</svg>
</div>
<div className="hidden lg:block fixed top-0 right-0 pointer-events-none z-0 opacity-[0.07] dark:opacity-[0.12]">
<svg width="180" height="180" viewBox="0 0 180 180" fill="none" xmlns="http://www.w3.org/2000/svg" style={{ transform: "scaleX(-1)" }}>
<line x1="0" y1="0" x2="180" y2="0" stroke="#CC0000" strokeWidth="0.8"/>
<line x1="0" y1="0" x2="180" y2="60" stroke="#CC0000" strokeWidth="0.8"/>
<line x1="0" y1="0" x2="180" y2="120" stroke="#CC0000" strokeWidth="0.8"/>
<line x1="0" y1="0" x2="180" y2="180" stroke="#CC0000" strokeWidth="0.8"/>
<line x1="0" y1="0" x2="120" y2="180" stroke="#CC0000" strokeWidth="0.8"/>
<line x1="0" y1="0" x2="60" y2="180" stroke="#CC0000" strokeWidth="0.8"/>
<line x1="0" y1="0" x2="0" y2="180" stroke="#CC0000" strokeWidth="0.8"/>
<path d="M0,30 Q30,30 30,0" stroke="#CC0000" strokeWidth="0.8" fill="none"/>
<path d="M0,60 Q60,60 60,0" stroke="#CC0000" strokeWidth="0.8" fill="none"/>
<path d="M0,90 Q90,90 90,0" stroke="#CC0000" strokeWidth="0.8" fill="none"/>
<path d="M0,120 Q120,120 120,0" stroke="#CC0000" strokeWidth="0.8" fill="none"/>
<path d="M0,150 Q150,150 150,0" stroke="#CC0000" strokeWidth="0.8" fill="none"/>
<path d="M0,180 Q180,180 180,0" stroke="#CC0000" strokeWidth="0.8" fill="none"/>
</svg>
</div>
<SystemMonitor />
<Sidebar
collapsed={sidebarCollapsed}
onToggle={toggleSidebar}
mobileOpen={mobileOpen}
onMobileClose={() => setMobileOpen(false)}
/>
<div className={cn("transition-all duration-300", sidebarCollapsed ? "lg:ml-16" : "lg:ml-64")}>
<Topbar onMenuClick={() => setMobileOpen(true)} />
<main className="flex-1 p-4 lg:p-6">
<AnimatePresence mode="wait">
<motion.div
key={pathname}
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -8 }}
transition={{ duration: 0.2 }}
>
{children}
</motion.div>
</AnimatePresence>
</main>
</div>
</div>
)
}
function cn(...classes: (string | boolean | undefined | null)[]) {
return classes.filter(Boolean).join(" ")
}