import { useState, useEffect, useCallback } from 'react'; import { Outlet, NavLink, useNavigate } from 'react-router-dom'; import { useQuery } from '@tanstack/react-query'; import { useAuth } from '../auth/AuthContext'; import { UserAvatar } from '../components/UserAvatar'; import api from '../api/client'; import { useTheme } from '../theme/ThemeContext'; import styles from './AppLayout.module.css'; interface ExternalLink { id: string; label: string; url: string; sortOrder: number; customIcon?: string; } /** Favicon ueber Backend-Proxy laden (cached in Redis) */ function FaviconImg({ url, label, customIcon, }: { url: string; label: string; customIcon?: string; }) { const [faviconUrl, setFaviconUrl] = useState(null); const [failed, setFailed] = useState(false); const fetchFavicon = useCallback(async () => { try { new URL(url); const res = await api.get<{ faviconUrl: string | null }>( `/settings/favicon?url=${encodeURIComponent(url)}`, ); if (res.data.faviconUrl) { setFaviconUrl(res.data.faviconUrl); setFailed(false); } else { setFailed(true); } } catch { setFailed(true); } }, [url]); useEffect(() => { fetchFavicon(); }, [fetchFavicon]); // Eigenes Icon hat Vorrang if (customIcon) { return ( ); } if (failed || !faviconUrl) { return ( {label.charAt(0).toUpperCase()} ); } return ( setFailed(true)} /> ); } const THEME_OPTIONS = [ { value: 'light' as const, label: 'Hell', icon: '☀' }, { value: 'dark' as const, label: 'Dunkel', icon: '☾' }, { value: 'system' as const, label: 'System', icon: '⚙' }, ]; export function AppLayout() { const { user, logout } = useAuth(); const navigate = useNavigate(); const { mode, setMode } = useTheme(); const [crmOpen, setCrmOpen] = useState(true); const [appsOpen, setAppsOpen] = useState(true); const [collapsed, setCollapsed] = useState(() => { return localStorage.getItem('sidebar-collapsed') === 'true'; }); const toggleCollapsed = () => { setCollapsed((prev) => { const next = !prev; localStorage.setItem('sidebar-collapsed', String(next)); return next; }); }; const { data: externalLinks } = useQuery({ queryKey: ['settings', 'external-links'], queryFn: async () => { const res = await api.get('/settings/external-links'); return res.data; }, staleTime: 5 * 60 * 1000, }); const { data: branding } = useQuery<{ logo: string | null; sidebarColor: string | null; }>({ queryKey: ['settings', 'branding'], queryFn: async () => { const res = await api.get<{ logo: string | null; sidebarColor: string | null; }>('/settings/branding'); return res.data; }, staleTime: 5 * 60 * 1000, }); const handleLogout = async () => { await logout(); navigate('/login'); }; return (
{/* Sidebar */} {/* Main Content */}
); }