Spaces:
Running
Running
| import { type ReactNode, useEffect } from 'react'; | |
| import { NavLink } from 'react-router-dom'; | |
| import { useTranslation } from 'react-i18next'; | |
| import { useUIStore } from '../../store/uiStore'; | |
| import './AppShell.css'; | |
| interface Props { | |
| children: ReactNode; | |
| } | |
| const NAV_ITEMS = [ | |
| { path: '/', translationKey: 'appshell.nav.home', icon: <img src="/favicon.svg" alt="HTP" style={{ width: '1.2em', height: '1.2em', display: 'block' }} /> }, | |
| { path: '/data-collection', translationKey: 'appshell.nav.data_collection', icon: 'π' }, | |
| { path: '/potential-analysis', translationKey: 'appshell.nav.potential_analysis', icon: 'π' }, | |
| ]; | |
| export default function AppShell({ children }: Props) { | |
| const { t, i18n } = useTranslation(); | |
| const theme = useUIStore((s) => s.theme); | |
| const toggleTheme = useUIStore((s) => s.toggleTheme); | |
| const toggleLanguage = () => { | |
| const nextLang = i18n.language === 'en' ? 'de' : 'en'; | |
| i18n.changeLanguage(nextLang); | |
| localStorage.setItem('i18nextLng', nextLang); | |
| // Trigger Google Translate widget | |
| try { | |
| const selectField = document.querySelector('.goog-te-combo') as HTMLSelectElement; | |
| if (selectField) { | |
| selectField.value = nextLang; | |
| selectField.dispatchEvent(new Event('change')); | |
| } | |
| } catch (e) { | |
| console.error('Google Translate trigger failed:', e); | |
| } | |
| }; | |
| // Sync dark mode class to HTML element | |
| useEffect(() => { | |
| console.log('[HeatTransPlan] Current Theme:', theme); | |
| if (theme === 'dark') { | |
| document.documentElement.classList.add('dark'); | |
| } else { | |
| document.documentElement.classList.remove('dark'); | |
| } | |
| }, [theme]); | |
| return ( | |
| <div className="app-shell"> | |
| {/* Top Navigation */} | |
| <header className="top-nav"> | |
| <div className="nav-brand"> | |
| <span className="brand-text notranslate">HeatTransPlan</span> | |
| <span className="brand-version notranslate">v2.1.0</span> | |
| </div> | |
| <nav className="nav-links"> | |
| {NAV_ITEMS.map((item) => ( | |
| <NavLink | |
| key={item.path} | |
| to={item.path} | |
| className={({ isActive }) => | |
| `nav-item ${isActive ? 'active' : ''}` | |
| } | |
| end={item.path === '/'} | |
| title={t(item.translationKey)} | |
| > | |
| <span className="nav-icon">{item.icon}</span> | |
| <span className="nav-label">{t(item.translationKey)}</span> | |
| </NavLink> | |
| ))} | |
| </nav> | |
| <div className="nav-actions"> | |
| <button className="theme-toggle" onClick={toggleLanguage} title={i18n.language === 'en' ? 'Switch to German' : 'Switch to English'}> | |
| {i18n.language === 'en' ? 'π©πͺ' : 'π¬π§'} | |
| </button> | |
| <button className="theme-toggle" onClick={toggleTheme} title={theme === 'dark' ? t('appshell.theme.light') : t('appshell.theme.dark')}> | |
| {theme === 'dark' ? 'βοΈ' : 'π'} | |
| </button> | |
| </div> | |
| </header> | |
| {/* Main content */} | |
| <main className="main-content"> | |
| {children} | |
| </main> | |
| </div> | |
| ); | |
| } | |