import { useEffect, useState } from 'react'
import { LayoutDashboard, BookOpen, FileText, Settings as SettingsIcon } from 'lucide-react'
import Dashboard from './pages/Dashboard'
import SettingsPage from './pages/Settings'
import LogsPage from './pages/Logs'
import AnleitungPage from './pages/Anleitung'
import FirstRunWizard from './components/FirstRunWizard'
import { api } from './lib/api'
import { EventStreamProvider, useStrom } from './lib/useEventStream'
import { BetriebProvider } from './lib/useBetrieb'
import { Fehlergrenze } from './components/Fehlergrenze'
type Page = 'dashboard' | 'anleitung' | 'logs' | 'settings'
/**
* Zeigt, ob der Live-Strom steht.
*
* Hier stand bis V2-3 ein fest verdrahtetes „ONLINE" mit pulsierendem Punkt —
* es leuchtete auch dann gruen, wenn die API tot war. Genau die Sorte
* Placebo-Anzeige, die in Etappe 19 schon einmal aufgeraeumt wurde
* („Fortschritt log, Auswurf tat nichts, Encoder wurden behauptet statt
* gemessen"). Jetzt sagt sie die Wahrheit.
*/
function VerbindungsAnzeige() {
const { verbunden, zuletzt } = useStrom()
if (verbunden) {
return (
LIVE
)
}
return (
VERBINDUNG WEG
)
}
function AppInhalt() {
const [currentPage, setCurrentPage] = useState('dashboard')
// First-Run: solange der Einrichtungs-Assistent nicht abgeschlossen ist, zeigen wir
// ihn statt des Dashboards. null = /setup noch nicht geprüft (kein Aufblitzen).
const [setupDone, setSetupDone] = useState(null)
/*
* ⚠️ Ein fehlgeschlagener Abruf ist KEINE Aussage über die Einrichtung.
*
* Hier stand `.catch(() => setSetupDone(true))` mit der Begründung
* „API/Setup nicht erreichbar → nicht blockieren". Die Folge hat der
* Commander am 28.08.2026 gemeldet: **Nach einer frischen Installation
* fehlte der Ersteinrichtungs-Assistent.**
*
* Und zwar zwangsläufig: Das Fenster geht unmittelbar nach dem Setup auf,
* der Dienst braucht ein bis zwei Sekunden bis zur ersten Antwort — der
* erste Abruf scheitert also fast immer. Aus „ich konnte nicht nachsehen"
* wurde „ist schon erledigt", und der Assistent war für immer weg.
*
* Dasselbe Prinzip steht nebenan in `useEventStream.tsx`: Wer nichts Neues
* weiß, behält, was er wusste — und behauptet nichts.
*
* Deshalb: nachfragen, bis der Server ANTWORTET. Erst eine Antwort
* entscheidet.
*/
useEffect(() => {
let abgemeldet = false
let versuche = 0
const fragen = () => {
api.get('/setup')
.then((r) => { if (!abgemeldet) setSetupDone(Boolean(r.data?.done)) })
.catch(() => {
if (abgemeldet) return
versuche += 1
// Zwei Minuten lang alle zwei Sekunden. Antwortet der Server bis
// dahin nicht, hat der Nutzer ein anderes Problem als den
// Assistenten — dann zeigen wir die Oberfläche, damit er die Logs
// sehen kann.
if (versuche < 60) setTimeout(fragen, 2000)
else setSetupDone(true)
})
}
fragen()
return () => { abgemeldet = true }
}, [])
const navItems: { id: Page; label: string; icon: any }[] = [
{ id: 'dashboard', label: 'Dashboard', icon: LayoutDashboard },
{ id: 'anleitung', label: 'Anleitung', icon: BookOpen },
{ id: 'logs', label: 'Logs', icon: FileText },
{ id: 'settings', label: 'Einstellungen', icon: SettingsIcon },
]
// Solange /setup noch nicht geantwortet hat: sagen, dass gewartet wird.
// Ein leeres Fenster sieht aus wie ein Absturz — und nach einer frischen
// Installation ist genau das der erste Eindruck.
if (setupDone === null) {
return (
◉
Rippy startet …
warte auf den Dienst
)
}
if (!setupDone) return setSetupDone(true)} />
return (
{/* Top Header Navigation Bar */}
{/* Main Content Viewport */}
{/* Je Seite eine eigene Grenze: Faellt eine aus, bleibt die
Navigation stehen und man kommt woanders hin. Ohne das war am
29.08.2026 nach einem Klick auf „Rippen starten" der ganze
Bildschirm leer — samt Kopfzeile. */}
{currentPage === 'dashboard' && }
{currentPage === 'anleitung' && }
{currentPage === 'logs' && }
{currentPage === 'settings' && }
)
}
export default function App() {
// Der Provider haelt GENAU EINE SSE-Verbindung fuer die ganze Anwendung.
// Ein Hook je Komponente waere eine Verbindung je Komponente — also das
// alte Problem in neuer Form.
// BetriebProvider ganz aussen: Was Rippy anzeigen DARF, entscheidet sich
// vor allem anderen. Ohne ihn zeigte das Windows-Fenster "Pruefen: docker
// compose ps" — einen Rat, den dort niemand befolgen kann.
return (
)
}