/* ============================================================================ GC · APP — kollegans skal: header, flikrad, sidväxling ---------------------------------------------------------------------------- Hans app är en enkolumnsapp: max-w-2xl som växer till max-w-4xl, egen header med "Hem" + live-prick, sju flikar under en linje. Läge "Ny" renderar HANS skal i stället för vårt — vår topbar, sidopanel och korttokens är helt ur bilden här. Flikar som ännu inte är portade visar den vy vi redan har byggt mot backend, märkt som oportad. Den ser ut som vår gamla design, för det är vad den är — hellre det än en tom flik, och märkningen gör skillnaden tydlig. ========================================================================== */ /* Banderollen om oportad markup är borttagen 2026-08-16: den var till för oss under portningen, inte för användaren, och stod kvar på varje besök. Vilka vyer som är ritade i hans markup står i DESIGN_STATE.md §8. */ function GcLegacy({ children }){ return
{children}
; } function GcApp(props){ const { day, tomorrow, units, live, settings, history, comparison, savings, now, nowIdx, onRange, onSaved, onOldDesign, liveText, homeName, slots, evLoad, tomorrowPrelim, fcShow, evWList, evUnmet, evUnmetTotal, dataStatus, peak } = props; const [page, setPage] = React.useState(() => { try { return localStorage.getItem('gc.page') || 'oversikt'; } catch(e){ return 'oversikt'; } }); React.useEffect(() => { try { localStorage.setItem('gc.page', page); } catch(e){} }, [page]); const ported = { oversikt: !!window.GcOversikt, last: !!window.GcLast, schema: !!window.GcSchema, avancerat: !!window.GcAvancerat }; /* EvWeekCards renderar NULL utan laddboxar — en flik som går att klicka och sedan står tom är värre än en avstängd flik. */ const chargers = (window.evModel && window.evModel.normalize) ? window.evModel.normalize(settings) : []; const tabs = [ { id:'oversikt', txt:'Översikt', enabled:true }, { id:'schema', txt:'Schema', enabled:!!(window.GcSchema || window.ClockV2) }, { id:'bil', txt:'Bil', enabled:!!window.GcBil && chargers.length > 0 }, /* Beta-punkt i flikraden (André 7/9): märkningen måste synas INNAN man klickar sig in, inte bara på sidan man redan valt. */ { id:'last', txt:'Last', enabled:!!window.GcLast, beta:true }, /* Namnbyten 29/8 (André): "Statistik" bar både kWh och kr — HISTORIK är det ärliga namnet för allt som ligger bakåt i tiden. "Avancerat" sa inget om innehållet — PROGNOS är vad 48h-grafen är. Flik-ID:na behålls (localStorage gc.page + all sidväxlingslogik pekar på dem). */ { id:'statistik', txt:'Historik', enabled:!!window.StatsV2 }, { id:'avancerat', txt:'Prognos', enabled:!!window.GcAvancerat || (!!window.ForecastCard && !!(slots && slots.length)) }, { id:'installningar', txt:'Inställningar', enabled:!!window.GcInstallningar, href:'settings.html' }, /* Hjälpen är en LÄNK i flikraden, inte en flik: help.html är en egen sida (egen bundle med guider, flödesbilder, felsökning) och ska inte dras in i dashboardens React-träd. Samma utseende som flikarna så den hittas där man letar (André 31/8: "måste vara lättåtkomligt"). */ { id:'hjalp', txt:'Hjälp', enabled:true, href:'help.html' }, ]; /* href-flikar är LÄNKAR, inte sidor: de får aldrig bli `active`. Utan !t.href skulle ett gammalt localStorage-värde ('installningar' från när fliken var en sida) rendera driftvyn under en flikrad utan markering. */ const active = tabs.find((t) => t.id === page && t.enabled && !t.href) ? page : 'oversikt'; return (
{homeName || 'Hem'}
{liveText || 'Live · ansluten'}
{/* "Gammal design"-länken borttagen (André 29/8: "den nya fungerar bra, den gamla kan vi ta bort"). Gamla stacken finns kvar bakom ?design=gammal för felsökning, men får ingen väg dit i UI:t. */} {/* Gravio-chippen är en KNAPP hem till Översikt (André 1/9) — från vilken flik som helst, där energiflödet ligger. */}
{/* DATAVARNINGAR (André 29/8: "hur vet användaren via dashboard att flöden fryser eller slutat skicka data?"). Backend vaktar detta i /api/data-status: status/age_minutes säger om Homey-pingarna stannat, warnings[] bär sensorvarningar (nätsensor utan negativa värden, orimliga inställningar, m.m.). GAMLA designen visade dem — nya skalet tappade dem i portningen. Frusen data ser annars ut som en lugn dag: samma sol, samma nät, inga fel någonstans. */} {dataStatus && (() => { const stale = dataStatus.status && dataStatus.status !== 'connected'; const age = dataStatus.age_minutes; const staleTxt = stale ? (dataStatus.message || 'Ingen data tas emot') + (age != null && age > 0 ? ` — senaste mätvärdet är ${age} min gammalt` : '') : null; const ws = Array.isArray(dataStatus.warnings_structured) ? dataStatus.warnings_structured : Array.isArray(dataStatus.warnings) ? dataStatus.warnings : []; const items = ws.map((w) => typeof w === 'string' ? w : (w.text || w.message || '')).filter(Boolean); /* Effekttimmen som BANNER, inte kortrad (backend 31/8): "denna timme X av Y kWh" är åtgärdsrelevant precis när gränsen närmar sig — kortet är översikt. Tröskel 80 % av gränsen, och bara när peak shaving är aktiverat. */ const ph = peak?.current_hour, lim = peak?.peak_shaving?.max_grid_kw; if (peak?.peak_shaving?.enabled && ph && lim > 0 && (ph.accumulated_kwh || 0) >= lim * 0.8){ items.unshift(`Denna timme: ${window.gcNum(ph.accumulated_kwh, 1)} av ${window.gcNum(lim, 1)} kWh — närmar sig effektgränsen`); } if (!staleTxt && !items.length) return null; return (
{staleTxt && (
{staleTxt}. Dashboarden visar gamla värden — kontrollera Homey-flödet.
)} {items.map((t, i) => (
{t}
))}
); })()}
{tabs.map((t) => t.href ? ( {t.txt} ) : ( ))}
{!day && (
Väntar på schema från backend.
)} {day && active === 'oversikt' && ported.oversikt && ( {/* Peak-kortet: BARA när effektavgiftshanteringen är på — för alla andra är det brus (backend 31/8). ÖVER energiflödet (André 31/8): gränsen är det första man vill se när avgiften styr. */} {window.GcPeak && settings?.peak_shaving_enabled && peak && ( )} setPage('schema')}/> {/* Hans "Prognoser"-avsnitt längst ner på Översikt — sol och förbrukning för idag, imorgon och igår på ett ställe. */} {window.GcPrognoser && ( )} )} {day && active === 'schema' && ( )} {active === 'bil' && ( )} {active === 'installningar' && } {active === 'last' && ( )} {active === 'avancerat' && (day && window.GcAvancerat ? ( ) : ( ))} {active === 'statistik' && ( )}
); } Object.assign(window, { GcApp, GcLegacy });