frontend_nyla/src/layouts/MainLayout.tsx

139 lines
4.3 KiB
TypeScript

/**
* MainLayout
*
* Hauptlayout der Anwendung mit Sidebar und Content-Bereich.
* Enthält den FeatureProvider für das Multi-Tenant-System.
*/
import React, { useEffect, useState } from 'react';
import { Outlet, useLocation } from 'react-router-dom';
import { FeatureProvider, useFeatureStore } from '../stores/featureStore';
import { MandateNavigation } from '../components/Navigation/MandateNavigation';
import { UserSection } from '../components/Navigation/UserSection';
import { WorkspaceKeepAlive } from '../pages/views/workspace/WorkspaceKeepAlive';
import { CommcoachKeepAlive } from '../pages/views/commcoach/CommcoachKeepAlive';
import styles from './MainLayout.module.css';
const _WORKSPACE_ROUTE_RE = /\/mandates\/[^/]+\/workspace\/[^/]+\/dashboard/;
const _COMMCOACH_ROUTE_RE = /\/mandates\/[^/]+\/commcoach\/[^/]+\/(?:coaching|dossier)/;
// =============================================================================
// INNER LAYOUT (mit Zugriff auf Store)
// =============================================================================
const MainLayoutInner: React.FC = () => {
const { loadFeatures, initialized, loading, error } = useFeatureStore();
const location = useLocation();
const [isMobileSidebarOpen, setIsMobileSidebarOpen] = useState(false);
const isWorkspaceKeepAliveVisible = _WORKSPACE_ROUTE_RE.test(location.pathname);
const isCommcoachKeepAliveVisible = _COMMCOACH_ROUTE_RE.test(location.pathname);
const hideOutletShell = isWorkspaceKeepAliveVisible || isCommcoachKeepAliveVisible;
// Features laden beim Mount
useEffect(() => {
if (!initialized && !loading) {
loadFeatures();
}
}, [initialized, loading, loadFeatures]);
useEffect(() => {
setIsMobileSidebarOpen(false);
}, [location.pathname]);
useEffect(() => {
const handleResize = () => {
if (window.innerWidth > 1024) {
setIsMobileSidebarOpen(false);
}
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return (
<div className={styles.mainLayout}>
{isMobileSidebarOpen && (
<button
className={styles.mobileBackdrop}
onClick={() => setIsMobileSidebarOpen(false)}
aria-label="Navigation schliessen"
/>
)}
{/* Sidebar */}
<aside className={`${styles.sidebar} ${isMobileSidebarOpen ? styles.sidebarOpen : ''}`}>
<div className={styles.logoContainer}>
<img
src="/logos/poweron-logo.png"
alt="PowerOn"
className={styles.logoImage}
/>
</div>
<nav className={styles.navigation}>
{loading && (
<div className={styles.loadingNav}>
Lade Navigation...
</div>
)}
{error && (
<div className={styles.errorNav}>
Fehler: {error}
</div>
)}
{initialized && !loading && (
<MandateNavigation />
)}
</nav>
{/* User-Bereich am unteren Rand */}
<UserSection />
</aside>
{/* Content */}
<main className={styles.content}>
<div className={styles.mobileTopBar}>
<button
className={styles.mobileMenuButton}
onClick={() => setIsMobileSidebarOpen(true)}
aria-label="Navigation oeffnen"
>
</button>
<img
src="/logos/poweron-logo.png"
alt="PowerOn"
className={styles.mobileLogo}
/>
</div>
<WorkspaceKeepAlive isVisible={isWorkspaceKeepAliveVisible} />
<CommcoachKeepAlive isVisible={isCommcoachKeepAliveVisible} />
<div
className={styles.outletShell}
style={{ display: hideOutletShell ? 'none' : undefined }}
>
<Outlet />
</div>
</main>
</div>
);
};
// =============================================================================
// MAIN LAYOUT (mit Provider)
// =============================================================================
export const MainLayout: React.FC = () => {
return (
<FeatureProvider>
<MainLayoutInner />
</FeatureProvider>
);
};
export default MainLayout;