/* ============================================================================ GC · LADDHISTORIK — hans sjudagarsblock, uppmätt data ---------------------------------------------------------------------------- GravioApp.jsx BilView avslutas med "Laddhistorik · 7 dagar". Hans rader kom ur rnd(); våra kommer ur GET /api/ev/history?days=7 (live 16/8), som mäter ur ev_actual. Skillnader mot hans block, alla för att kontraktet säger något annat: 1. INGEN BIL, BARA BOX. car_id finns inte i svaret — vi visar laddboxens namn. Hans "C40"/"G9P" är bilnamn. 2. TRE KÄLLOR, INTE TVÅ. Svaret skiljer solar_kwh, battery_kwh och grid_kwh. Hans block har bara sol och nät; en laddning ur hembatteriet fanns inte i hans värld. 3. KOSTNADEN ÄR BACKENDENS. cost_kr = nätdelen × köppris, solar_value_kr = soldelen × säljpris (alltså vad solen hade gett vid försäljning — hans "alt."). Vi räknar inte om dem. 4. within_plan och unknown_quarters syns. En laddning utanför styrningen är inte samma sak som en planerad, och kvartar utan mätvärde ska inte tyst räknas som noll. ========================================================================== */ const GH_SRC_COL = { sol:'#facc15', 'nät':'#60a5fa', batteri:'#fb923c', blandat:'#a3e635' }; function ghDayLabel(iso, today){ if (!iso) return '—'; const d = new Date(iso + 'T12:00:00'); if (isNaN(d.getTime())) return iso; const t = today ? new Date(today + 'T12:00:00') : null; if (t){ const diff = Math.round((t - d) / 86400000); if (diff === 0) return 'I dag'; if (diff === 1) return 'I går'; } return d.toLocaleDateString('sv-SE', { weekday:'short', day:'numeric', month:'short' }); } function GcLaddhistorik({ settings }){ const [state, setState] = React.useState({ loading:true, data:null, err:null }); React.useEffect(() => { if (!window.hems || !window.hems.getEvHistory){ setState({ loading:false, data:null, err:'saknas' }); return; } /* Samma mount-race som Prognoser hade: kortet monteras före bootHems satt nyckeln, och ett kast med noKeyYet är övergående. gcFetchReady väntar. */ return window.gcFetchReady(() => window.hems.getEvHistory(7), { // null = endpointen svarade inte (t.ex. inte deployad på den rev // sessionen träffar). Blocket ritas inte alls då — inget felmeddelande // om något användaren inte kan påverka. onOk: (d) => setState({ loading:false, data:d, err: d ? null : 'saknas' }), onFail: (e) => setState({ loading:false, data:null, err: String(e && e.message || e) }), }); }, []); const boxName = React.useCallback((id) => { const list = (window.evModel && window.evModel.normalize) ? window.evModel.normalize(settings) : []; // Kanoniskt id är ev_1, ev_2 … alltså POSITIONEN. Matcha på id först, annars // på ordningen — samma regel som ev_power_list och ev_per_car. const byId = list.findIndex(c => String(c.id) === String(id)); const m = /^ev_(\d+)$/.exec(String(id || '')); const k = byId >= 0 ? byId : (m ? Number(m[1]) - 1 : -1); const c = k >= 0 ? list[k] : null; return c ? ((window.evModel && window.evModel.displayName) ? window.evModel.displayName(c, k) : (c.name || 'Laddbox ' + (k+1))) : String(id || 'Laddbox'); }, [settings]); if (state.loading) { return (
Ingen laddning uppmätt de senaste dygnen.
) : (Uppmätt laddning, inte plan. Kostnaden är nätdelen gånger köppriset; solvärdet är vad soldelen hade gett vid försäljning. Kvartar utan mätvärde räknas inte med. {sessions.some(r => r.unknown_quarters > 0) && ' Några kvartar saknar mätvärde i perioden.'}