feat: Refactor session fetching logic
This commit is contained in:
21
src/App.tsx
21
src/App.tsx
@@ -62,9 +62,7 @@ const App: React.FC = () => {
|
|||||||
sohChange={sohChange}
|
sohChange={sohChange}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{screen === "battery" && (
|
{screen === "battery" && <BottomNav onChange={setScreen} />}
|
||||||
<BottomNav current={screen} onChange={setScreen} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -371,28 +369,17 @@ const BatteryScreen: React.FC<BatteryScreenProps> = ({
|
|||||||
/* ---------- BOTTOM NAV ---------- */
|
/* ---------- BOTTOM NAV ---------- */
|
||||||
|
|
||||||
interface BottomNavProps {
|
interface BottomNavProps {
|
||||||
current: Screen;
|
|
||||||
onChange: (screen: Screen) => void;
|
onChange: (screen: Screen) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BottomNav: React.FC<BottomNavProps> = ({ current, onChange }) => {
|
const BottomNav: React.FC<BottomNavProps> = ({ onChange }) => {
|
||||||
return (
|
return (
|
||||||
<nav className="bottom-nav">
|
<nav className="bottom-nav">
|
||||||
<button
|
<button className="nav-button" onClick={() => onChange("home")}>
|
||||||
className={
|
|
||||||
"nav-button " + (current === "home" ? "nav-button-active" : "")
|
|
||||||
}
|
|
||||||
onClick={() => onChange("home")}
|
|
||||||
>
|
|
||||||
<span className="nav-icon">🏠</span>
|
<span className="nav-icon">🏠</span>
|
||||||
<span className="nav-label">Home</span>
|
<span className="nav-label">Home</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button className="nav-button">
|
||||||
className={
|
|
||||||
"nav-button " + (current === "battery" ? "nav-button-active" : "")
|
|
||||||
}
|
|
||||||
onClick={() => onChange("battery")}
|
|
||||||
>
|
|
||||||
<span className="nav-icon">🚗</span>
|
<span className="nav-icon">🚗</span>
|
||||||
<span className="nav-label">Car</span>
|
<span className="nav-label">Car</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
74
src/api.ts
74
src/api.ts
@@ -1,9 +1,59 @@
|
|||||||
import type { SessionsResponse } from "./types";
|
import type { SessionsResponse, ChargingSession } from "./types";
|
||||||
import { generateMockSessions } from "./mocks/mockSessions";
|
import { generateMockSessions } from "./mocks/mockSessions";
|
||||||
|
|
||||||
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL ?? "https://example.com";
|
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL ?? "https://example.com";
|
||||||
const USE_MOCK = import.meta.env.VITE_USE_MOCK_API === "true";
|
const USE_MOCK = import.meta.env.VITE_USE_MOCK_API === "true";
|
||||||
|
|
||||||
|
interface BackendSession {
|
||||||
|
id: string;
|
||||||
|
started_at: string;
|
||||||
|
ended_at: string | null;
|
||||||
|
soc_start: number | null;
|
||||||
|
soc_end: number | null;
|
||||||
|
energy_added_kwh: number | null;
|
||||||
|
avg_temp_c: number | null;
|
||||||
|
location_name: string | null;
|
||||||
|
soh: number | null;
|
||||||
|
health_grade: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeSoc(value: number | null | undefined): number | undefined {
|
||||||
|
if (value == null) return undefined;
|
||||||
|
if (value <= 1) {
|
||||||
|
return value * 100;
|
||||||
|
}
|
||||||
|
if (value > 100) {
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeSoh(value: number | null): number {
|
||||||
|
if (value == null) return 0;
|
||||||
|
if (value <= 1) {
|
||||||
|
return value * 100;
|
||||||
|
}
|
||||||
|
if (value > 100) {
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapBackendSession(raw: BackendSession): ChargingSession {
|
||||||
|
return {
|
||||||
|
id: raw.id,
|
||||||
|
startedAt: raw.started_at,
|
||||||
|
endedAt: raw.ended_at ?? raw.started_at,
|
||||||
|
sohStart: normalizeSoh(raw.soh),
|
||||||
|
sohEnd: normalizeSoh(raw.soh),
|
||||||
|
socStart: normalizeSoc(raw.soc_start),
|
||||||
|
socEnd: normalizeSoc(raw.soc_end),
|
||||||
|
energyAddedKWh: raw.energy_added_kwh ?? undefined,
|
||||||
|
avgTempC: raw.avg_temp_c ?? undefined,
|
||||||
|
locationName: raw.location_name ?? undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export async function fetchSessions(days: number): Promise<SessionsResponse> {
|
export async function fetchSessions(days: number): Promise<SessionsResponse> {
|
||||||
if (USE_MOCK) {
|
if (USE_MOCK) {
|
||||||
await delay(400);
|
await delay(400);
|
||||||
@@ -11,17 +61,33 @@ export async function fetchSessions(days: number): Promise<SessionsResponse> {
|
|||||||
return { sessions };
|
return { sessions };
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await fetch(`${API_BASE_URL}/api/sessions?days=${days}`, {
|
const res = await fetch(`${API_BASE_URL}/charge_sessions`, {
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw new Error(`HTTP ${res.status}`);
|
throw new Error(`Error ${res.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (await res.json()) as SessionsResponse;
|
const rawSessions = (await res.json()) as BackendSession[];
|
||||||
|
|
||||||
|
const mapped: ChargingSession[] = rawSessions.map(mapBackendSession);
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const cutoff = new Date(now.getTime() - days * 24 * 60 * 60 * 1000);
|
||||||
|
|
||||||
|
const filtered = mapped
|
||||||
|
.filter((s) => {
|
||||||
|
const end = new Date(s.endedAt);
|
||||||
|
return !isNaN(end.getTime()) && end >= cutoff;
|
||||||
|
})
|
||||||
|
.sort(
|
||||||
|
(a, b) => new Date(a.endedAt).getTime() - new Date(b.endedAt).getTime()
|
||||||
|
);
|
||||||
|
|
||||||
|
return { sessions: filtered };
|
||||||
}
|
}
|
||||||
|
|
||||||
function delay(ms: number): Promise<void> {
|
function delay(ms: number): Promise<void> {
|
||||||
|
|||||||
Reference in New Issue
Block a user