diff --git a/src/App.tsx b/src/App.tsx index f7bb0d9..d199eff 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,16 +3,14 @@ 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); - const [activeTab] = useState<"home" | "battery" | "trips" | "settings">( - "battery" - ); - useEffect(() => { const load = async () => { setLoadState("loading"); @@ -21,7 +19,8 @@ const App: React.FC = () => { try { const { sessions } = await fetchSessions(30); const sorted = [...sessions].sort( - (a, b) => new Date(b.endedAt).getTime() - new Date(a.endedAt).getTime() + (a, b) => + new Date(b.endedAt).getTime() - new Date(a.endedAt).getTime() ); setSessions(sorted); setLoadState("success"); @@ -43,6 +42,159 @@ const App: React.FC = () => { 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() @@ -56,73 +208,15 @@ const App: React.FC = () => { })); }, [sessions]); - const now = new Date(); - return ( -
- {/* STATUS BAR – topmost, like Android Auto / CarPlay */} -
-
- - {now.toLocaleTimeString(undefined, { - hour: "2-digit", - minute: "2-digit", - })} - - -2Β°C -
-
- - πŸš— - 320 km - -
-
- πŸ“Ά - LTE - πŸ”Š -
-
- - {/* APP HEADER */} + <>
Battery Health Last 30 days
-
- - - {loadState === "loading" - ? "Syncing…" - : loadState === "error" - ? "Offline" - : "Connected"} - -
- {/* QUICK CONTROL STRIP – static toggles to feel β€œcar-like” */} -
- - - - -
- - {/* MAIN CONTENT */} {loadState === "loading" && (
Loading charging history…
)} @@ -156,14 +250,16 @@ const App: React.FC = () => { {latestSession ? latestSession.sohEnd.toFixed(1) : "--"}% - - Session ended{" "} - {new Date(latestSession!.endedAt).toLocaleString(undefined, { - weekday: "short", - hour: "2-digit", - minute: "2-digit", - })} - + {latestSession && ( + + Session ended{" "} + {new Date(latestSession.endedAt).toLocaleString(undefined, { + weekday: "short", + hour: "2-digit", + minute: "2-digit", + })} + + )}
@@ -254,9 +350,7 @@ const App: React.FC = () => { {sessions.map((session, index) => { const prev = sessions[index + 1]; const delta = - prev != null - ? session.sohEnd - prev.sohEnd - : undefined; + prev != null ? session.sohEnd - prev.sohEnd : undefined; return ( { )} - - {/* BOTTOM NAV – static icons / labels */} - -
+ ); }; +/* ---------- BOTTOM NAV ---------- */ + +interface BottomNavProps { + current: Screen; + onChange: (screen: Screen) => void; +} + +const BottomNav: React.FC = ({ current, onChange }) => { + return ( + + ); +}; + +/* ---------- CHART + SESSION ROW (unchanged) ---------- */ + interface TrendPoint { label: string; value: number; @@ -332,10 +431,7 @@ const SimpleTrendChart: React.FC = ({ points }) => { const norm = (v: number) => { if (max === min) return 50; - return ( - padding + - ((max - v) / (max - min)) * (100 - padding * 2) - ); + return padding + ((max - v) / (max - min)) * (100 - padding * 2); }; const stepX = points.length > 1 ? 100 / (points.length - 1) : 50; @@ -350,7 +446,11 @@ const SimpleTrendChart: React.FC = ({ points }) => { return (
- + {points.map((p, i) => { const x = i * stepX; diff --git a/src/styles.css b/src/styles.css index 4af551c..c0fe606 100644 --- a/src/styles.css +++ b/src/styles.css @@ -19,7 +19,6 @@ html, } /* MAIN SCREEN LAYOUT */ - .screen { width: 100%; height: 100%; @@ -30,7 +29,6 @@ html, padding: 4px 16px 8px; gap: 8px; - /* whole screen scrolls */ overflow-y: auto; -webkit-overflow-scrolling: touch; } @@ -40,7 +38,6 @@ html, align-items: center; justify-content: space-between; margin-top: 2px; - /* small tweak */ } .top-bar-left { @@ -89,19 +86,16 @@ html, color: #ff453a; } -/* CONTENT AREA – just layout, NO own scrolling now */ - +/* CONTENT AREA */ .content { display: grid; grid-template-rows: auto auto minmax(0, 1fr); gap: 10px; - /* small extra space above nav */ padding-bottom: 12px; } /* PANELS */ - .panel { background: rgba(9, 11, 22, 0.9); border-radius: 16px; @@ -133,7 +127,6 @@ html, } /* CURRENT SOH SECTION */ - .current-soh-row { display: flex; justify-content: space-between; @@ -215,7 +208,6 @@ html, } /* Chart */ - .panel-chart { display: flex; flex-direction: column; @@ -262,7 +254,6 @@ html, min-height: 0; } -/* IMPORTANT: remove its own scrolling, let whole screen handle it */ .session-list { margin-top: 6px; display: flex; @@ -307,7 +298,6 @@ html, } /* STATUS BAR */ - .status-bar { display: flex; align-items: center; @@ -354,41 +344,7 @@ html, opacity: 0.9; } -/* QUICK STRIP */ - -.quick-strip { - display: flex; - gap: 8px; - margin-bottom: 2px; -} - -.quick-btn { - flex: 1; - border: none; - border-radius: 999px; - padding: 4px 8px; - background: rgba(21, 24, 40, 0.9); - color: #e7e9f5; - display: flex; - align-items: center; - gap: 6px; - font-size: 0.8rem; -} - -.quick-btn-active { - background: linear-gradient(120deg, #2bcbff, #5e72eb); -} - -.quick-icon { - font-size: 1rem; -} - -.quick-label { - white-space: nowrap; -} - /* BOTTOM NAV – fixed overlay inside the car screen */ - .bottom-nav { position: sticky; bottom: 0; @@ -459,4 +415,127 @@ html, #root { width: 100%; height: 100%; +} + +/* VW-style HOME MENU GRID – fills the screen under the header */ + +.home-menu-content { + flex: 1; + min-height: 0; + + display: flex; + align-items: stretch; + justify-content: stretch; + + padding: 20px 0; +} + +.home-menu-grid { + flex: 1; + width: 100%; + height: 100%; + + display: grid; + + grid-template-columns: repeat(5, minmax(0, 1fr)); + grid-template-rows: repeat(2, minmax(0, 1fr)); + + gap: 20px; + + margin: 0; + padding: 0; +} + +.home-menu-tile { + border: none; + border-radius: 10px; + padding: 10px 12px; + background: radial-gradient(circle at top, #272b37, #141722 60%, #05060a); + box-shadow: + 0 6px 10px rgba(0, 0, 0, 0.6), + 0 0 0 1px rgba(255, 255, 255, 0.04); + color: #f5f5f5; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 8px; + text-align: center; + cursor: default; +} + +.home-menu-icon { + font-size: 1.6rem; +} + +.home-menu-label { + font-size: 0.85rem; + text-transform: uppercase; + letter-spacing: 0.08em; +} + +/* Battery tile – interactive, glow on hover */ +.home-menu-tile-battery { + cursor: pointer; + transition: + transform 0.25s ease, + box-shadow 0.25s ease, + background 0.25s ease, + filter 0.25s ease; +} + +.home-menu-tile-battery:hover { + transform: translateY(-3px) scale(1.04); + box-shadow: + 0 0 25px rgba(80, 220, 255, 0.8), + 0 8px 18px rgba(0, 0, 0, 0.8); + background: radial-gradient(circle at top, #2f3f6a, #111423 65%, #05060a); + filter: drop-shadow(0 0 12px rgba(80, 220, 255, 0.7)); +} + +.home-menu-tile-battery:active { + transform: translateY(-1px) scale(0.99); + box-shadow: + 0 0 14px rgba(80, 220, 255, 0.7), + 0 4px 10px rgba(0, 0, 0, 0.8); + filter: drop-shadow(0 0 8px rgba(80, 220, 255, 0.6)); +} + +/* Screen enter animation */ +.screen-battery-enter { + animation: battery-screen-in 300ms ease-out; + transform-origin: bottom center; +} + +@keyframes battery-screen-in { + from { + opacity: 0; + transform: translateY(16px) scale(0.98); + filter: blur(2px); + } + + to { + opacity: 1; + transform: translateY(0) scale(1); + filter: blur(0); + } +} + +.screen-home-enter { + animation: home-screen-in 200ms ease-out; + transform-origin: bottom center; +} + +@keyframes home-screen-in { + from { + opacity: 0; + transform: translateY(16px) scale(0.98); + filter: blur(2px); + } + + to { + opacity: 1; + transform: translateY(0) scale(1); + filter: blur(0); + } } \ No newline at end of file