Files
ems/frontend/src/App.tsx
Dusan Vojacek 9ca4b4c577
Some checks failed
CI and deploy / migration-check (push) Failing after 10s
CI and deploy / deploy (push) Has been skipped
korkece fve predikce, grafy predikci
2026-04-22 19:26:46 +02:00

129 lines
4.4 KiB
TypeScript

import { Toaster } from 'sonner'
import { NavLink, Outlet, Route, Routes } from 'react-router-dom'
import { SiteSelectionProvider, useSiteSelection } from './context/SiteSelectionContext'
import { useWsLogErrorCount } from './hooks/useWsLogErrorCount'
import { Dashboard } from './pages/Dashboard'
import Economics from './pages/Economics'
import EnergyFlows from './pages/EnergyFlows'
import ForecastVsActual from './pages/ForecastVsActual'
import { Logs } from './pages/Logs'
import Planning from './pages/Planning'
import SiteConfiguration from './pages/SiteConfiguration'
import { Settings } from './pages/Settings'
function SiteCombo() {
const { sites, selectedSiteId, setSelectedSiteId, ready, error } = useSiteSelection()
if (!ready) {
return (
<span className="ml-auto text-xs text-slate-500" aria-live="polite">
Lokality
</span>
)
}
if (error != null || sites.length === 0) {
return (
<span className="ml-auto max-w-[12rem] truncate text-xs text-amber-500/90" title={error ?? undefined}>
{error ?? 'Žádná lokalita'}
</span>
)
}
return (
<label className="ml-auto flex min-w-0 items-center gap-2 text-sm text-slate-400">
<span className="hidden shrink-0 sm:inline">Lokalita</span>
<select
className="max-w-[11rem] cursor-pointer truncate rounded-lg border border-slate-700 bg-slate-900 px-2 py-1.5 text-slate-200 sm:max-w-[14rem]"
value={selectedSiteId ?? ''}
onChange={(e) => {
const v = Number.parseInt(e.target.value, 10)
if (Number.isFinite(v)) setSelectedSiteId(v)
}}
aria-label="Vybrat lokalitu"
>
{sites.map((s) => (
<option key={s.id} value={s.id}>
{s.code} {s.name}
</option>
))}
</select>
</label>
)
}
function AppLayout() {
const logErrors = useWsLogErrorCount(true)
const tabClass = ({ isActive }: { isActive: boolean }) =>
`rounded-lg px-3 py-2 text-sm font-medium transition ${
isActive ? 'bg-slate-800 text-white' : 'text-slate-400 hover:bg-slate-900 hover:text-slate-200'
}`
return (
<div className="min-h-screen bg-slate-950">
<nav className="sticky top-0 z-40 border-b border-slate-800/80 bg-slate-950/95 backdrop-blur">
<div className="mx-auto flex max-w-7xl flex-wrap items-center gap-1 gap-y-2 px-4 py-2 md:px-8">
<NavLink to="/" end className={tabClass}>
Přehled
</NavLink>
<NavLink to="/planning" className={tabClass}>
Plánování
</NavLink>
<NavLink to="/forecast-vs-actual" className={tabClass}>
Srovnání
</NavLink>
<NavLink to="/economics" className={tabClass}>
Ekonomika
</NavLink>
<NavLink to="/energy-flows" className={tabClass}>
Toky
</NavLink>
<NavLink to="/site-config" className={tabClass}>
Konfigurace
</NavLink>
<NavLink to="/settings" className={tabClass}>
Nastavení
</NavLink>
<a
href="/logs"
target="_blank"
rel="noopener noreferrer"
className="relative rounded-lg px-3 py-2 text-sm font-medium text-slate-400 transition hover:bg-slate-900 hover:text-slate-200"
>
Logy
{logErrors > 0 ? (
<span className="absolute -right-0.5 -top-0.5 flex h-5 min-w-5 items-center justify-center rounded-full bg-red-600 px-1 text-[10px] font-bold text-white">
{logErrors > 99 ? '99+' : logErrors}
</span>
) : null}
</a>
<SiteCombo />
</div>
</nav>
<Outlet />
<Toaster richColors position="top-right" theme="dark" />
</div>
)
}
export default function App() {
return (
<SiteSelectionProvider>
<Routes>
<Route element={<AppLayout />}>
<Route index element={<Dashboard />} />
<Route path="planning" element={<Planning />} />
<Route path="forecast-vs-actual" element={<ForecastVsActual />} />
<Route path="economics" element={<Economics />} />
<Route path="energy-flows" element={<EnergyFlows />} />
<Route path="site-config" element={<SiteConfiguration />} />
<Route path="settings" element={<Settings />} />
</Route>
<Route path="logs" element={<Logs />} />
</Routes>
</SiteSelectionProvider>
)
}