ui-nyla/src/layouts/MainLayout.tsx
2026-05-06 23:28:15 +02:00

151 lines
5.1 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 { GraphicalEditorKeepAlive } from '../pages/views/graphicalEditor/GraphicalEditorKeepAlive';
import { AdminLanguagesKeepAlive } from '../pages/admin/AdminLanguagesKeepAlive';
import styles from './MainLayout.module.css';
import { useLanguage } from '../providers/language/LanguageContext';
const _WORKSPACE_ROUTE_RE = /\/mandates\/[^/]+\/workspace\/[^/]+\/dashboard/;
const _COMMCOACH_ROUTE_RE = /\/mandates\/[^/]+\/commcoach\/[^/]+\/session/;
const _GE_EDITOR_ROUTE_RE = /\/mandates\/[^/]+\/graphicalEditor\/[^/]+\/editor/;
const _ADMIN_LANGUAGES_RE = /\/admin\/languages(?:$|\/)/;
// =============================================================================
// INNER LAYOUT (mit Zugriff auf Store)
// =============================================================================
const MainLayoutInner: React.FC = () => {
const { t } = useLanguage();
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 isGEEditorKeepAliveVisible = _GE_EDITOR_ROUTE_RE.test(location.pathname);
const isLanguagesKeepAliveVisible = _ADMIN_LANGUAGES_RE.test(location.pathname);
const hideOutletShell = isWorkspaceKeepAliveVisible || isCommcoachKeepAliveVisible || isGEEditorKeepAliveVisible || isLanguagesKeepAliveVisible;
// 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={t('Navigation schließen')}
/>
)}
{/* 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}>
{t('Lade Navigation…')}
</div>
)}
{error && (
<div className={styles.errorNav}>
{t('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={t('Navigation öffnen')}
>
</button>
<img
src="/logos/poweron-logo.png"
alt="PowerOn"
className={styles.mobileLogo}
/>
</div>
<WorkspaceKeepAlive isVisible={isWorkspaceKeepAliveVisible} />
<CommcoachKeepAlive isVisible={isCommcoachKeepAliveVisible} />
<GraphicalEditorKeepAlive isVisible={isGEEditorKeepAliveVisible} />
<AdminLanguagesKeepAlive isVisible={isLanguagesKeepAliveVisible} />
<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;