import React, { useEffect, useMemo, useState } from "react"; import type { ChargingSession } from "./types"; import { fetchSessions } from "./api"; type LoadState = "idle" | "loading" | "success" | "error"; type Screen = "home" | "battery"; const App: React.FC = () => { const [screen, setScreen] = useState("home"); const [sessions, setSessions] = useState([]); const [loadState, setLoadState] = useState("idle"); const [errorMessage, setErrorMessage] = useState(null); useEffect(() => { const load = async () => { setLoadState("loading"); setErrorMessage(null); try { const { sessions } = await fetchSessions(30); const sorted = [...sessions].sort( (a, b) => new Date(b.endedAt).getTime() - new Date(a.endedAt).getTime() ); setSessions(sorted); setLoadState("success"); } catch (err) { console.error(err); setErrorMessage("Could not load charging data."); setLoadState("error"); } }; load(); }, []); const latestSession = sessions[0] ?? null; const previousSession = sessions[1] ?? null; const sohChange = useMemo(() => { if (!latestSession || !previousSession) return null; return latestSession.sohEnd - previousSession.sohEnd; }, [latestSession, previousSession]); const now = new Date(); return (
{screen === "home" ? ( setScreen("battery")} /> ) : ( )} {screen === "battery" && }
); }; /* ---------- STATUS BAR ---------- */ interface StatusBarProps { now: Date; } const StatusBar: React.FC = ({ now }) => (
{now.toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit", })} -2Β°C
πŸš— 320 km
πŸ“Ά LTE πŸ”Š
); /* ---------- HOME SCREEN ---------- */ interface HomeScreenProps { onOpenBattery: () => void; } const HomeScreen: React.FC = ({ onOpenBattery }) => { return ( <>
Car Menu Select a function
{/* HOME CONTENT: VW-style icon grid */}
{/* Row 1 */}
); }; /* ---------- BATTERY SCREEN (existing screen) ---------- */ interface BatteryScreenProps { sessions: ChargingSession[]; loadState: LoadState; errorMessage: string | null; latestSession: ChargingSession | null; sohChange: number | null; } const BatteryScreen: React.FC = ({ sessions, loadState, errorMessage, latestSession, sohChange, }) => { const sohTrendPoints = useMemo(() => { return sessions .slice() .reverse() .map((s) => ({ label: new Date(s.endedAt).toLocaleDateString(undefined, { month: "short", day: "numeric", }), value: (s.sohStart + s.sohEnd) / 2, })); }, [sessions]); return ( <>
Battery Health Last 30 days
{loadState === "loading" && (
Loading charging history…
)} {loadState === "error" && (
{errorMessage ?? "Something went wrong."}
)} {loadState === "success" && sessions.length === 0 && (
No charging sessions found for the last 30 days.
)} {loadState === "success" && sessions.length > 0 && (
{/* Top section: current SoH + change */}

Current battery health (SoH)

Based on last completed charging session
SoH now {latestSession ? latestSession.sohEnd.toFixed(1) : "--"}% {latestSession && ( Session ended{" "} {new Date(latestSession.endedAt).toLocaleString(undefined, { weekday: "short", hour: "2-digit", minute: "2-digit", })} )}
Change since last session {sohChange !== null ? ( 0 ? "delta-up" : sohChange < 0 ? "delta-down" : "delta-flat") } > {sohChange > 0 ? "+" : ""} {sohChange.toFixed(2)} % ) : ( Not enough history yet )} {sohChange !== null && ( {sohChange > 0 ? "Slightly improved compared to previous session." : sohChange < 0 ? "Slightly decreased. Frequent fast-charging and heat may affect this." : "No visible change compared to previous session."} )}
{typeof latestSession?.energyAddedKWh === "number" && (
Energy added {latestSession.energyAddedKWh.toFixed(1)} kWh
)} {typeof latestSession?.avgTempC === "number" && (
Avg. pack temperature {latestSession.avgTempC.toFixed(1)} Β°C
)} {typeof latestSession?.socStart === "number" && typeof latestSession?.socEnd === "number" && (
SoC change {latestSession.socStart}% β†’ {latestSession.socEnd}%
)}
{/* Middle section: SoH trend */}

SoH trend (last 30 days)

Each point is one charging session
{/* Bottom section: session list */}

Charging sessions

{sessions.length} session {sessions.length !== 1 ? "s" : ""} in the last 30 days
{sessions.map((session, index) => { const prev = sessions[index + 1]; const delta = prev != null ? session.sohEnd - prev.sohEnd : undefined; return ( ); })}
)} ); }; /* ---------- BOTTOM NAV ---------- */ interface BottomNavProps { onChange: (screen: Screen) => void; } const BottomNav: React.FC = ({ onChange }) => { return ( ); }; /* ---------- CHART + SESSION ROW (unchanged) ---------- */ interface TrendPoint { label: string; value: number; } interface SimpleTrendChartProps { points: TrendPoint[]; } const SimpleTrendChart: React.FC = ({ points }) => { if (points.length === 0) { return
No data yet.
; } const values = points.map((p) => p.value); const min = Math.min(...values); const max = Math.max(...values); const padding = 4; const norm = (v: number) => { if (max === min) return 50; return padding + ((max - v) / (max - min)) * (100 - padding * 2); }; const stepX = points.length > 1 ? 100 / (points.length - 1) : 50; const pathD = points .map((p, i) => { const x = i * stepX; const y = norm(p.value); return `${i === 0 ? "M" : "L"} ${x} ${y}`; }) .join(" "); return (
{points.map((p, i) => { const x = i * stepX; const y = norm(p.value); return ( ); })}
{points.map((p, i) => ( {p.label} ))}
); }; interface SessionRowProps { session: ChargingSession; sohDelta?: number; } const SessionRow: React.FC = ({ session, sohDelta }) => { const end = new Date(session.endedAt); return (
{end.toLocaleDateString(undefined, { weekday: "short", month: "short", day: "numeric", })}{" "} Β·{" "} {end.toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit", })}
{session.locationName ?? "Unknown location"}
SoH {session.sohEnd.toFixed(1)}%
{typeof sohDelta === "number" && (
Ξ” vs prev 0 ? "delta-up" : sohDelta < 0 ? "delta-down" : "delta-flat") } > {sohDelta > 0 ? "+" : ""} {sohDelta.toFixed(2)}%
)} {typeof session.energyAddedKWh === "number" && (
Energy {session.energyAddedKWh.toFixed(1)} kWh
)}
); }; export default App;