/* ==========================================================================
Reiter "Übersicht": die Energiefluss-Übersicht an dieses Haus anpassen.
Links die Übersicht selbst im Bearbeitungsmodus (energiefluss.js) mit
Beispielwerten, rechts je Kreis Schalter und Name, beim PV-Kreis die
Flächen. Verschoben wird in der Vorschau. Gespeichert wird der ganze Stand
auf einmal (restricted/energiefluss.php hält davon nur die Abweichungen).
Nutzt textSchuetzen() aus settings.js.
========================================================================== */
(function () {
"use strict";
// Beispielwerte: ein sonniger Nachmittag, Auto lädt im EG, Heizstab läuft.
const BEISPIEL = {
"solarManager/P_PV": "8400", "solarManager/P_PVn": JSON.stringify([2600, 1300, 130, 130, 140, 140, 130, 130, 135, 135, 130, 130, 120, 120,
155, 150, 152, 158, 150, 149, 150, 154, 151, 150, 153, 155, 150, 92, 150, 152, 151, 150, 149, 0]),
"solarManager/P_Grid": "-900", "solarManager/i_l1evu": "-1.3", "solarManager/i_l2evu": "-1.4", "solarManager/i_l3evu": "-1.2",
"solarManager/P_Akku": "-2100", "solarManager/SOC": "64", "solarManager/crgMaxPct": "100",
"solarManager/autarky": "100", "solarManager/eff": "96",
"solarManager/P_Load": "-5400", "solarManager/og": "-350", "solarManager/eg": "3720", "solarManager/ug": "270",
"solarManager/pHeat": "1000", "solarManager/t_buffT": "61", "solarManager/t_buffM": "48", "solarManager/t_buffB": "35",
"solarManager/heatMode": "eco", "solarManager/heatOG": "1", "solarManager/heatEG": "0",
"solarManager/evPower": "3.2", "solarManager/evSOC": "78", "solarManager/evFuel": "0", "solarManager/evMode": "Eco",
"solarManager/evPlug": "1", "solarManager/evLock": "1", "solarManager/carRemChrg": "95",
"solarManager/evPowerOG": "0", "solarManager/evPlugOG": "0", "solarManager/evModeOG": "Eco",
};
let bild = null;
let gespeichert = {}; // Stand der Datenbank
let stand = {}; // Stand der Maske
let geladen = false;
const $ = id => document.getElementById(id);
const katalog = () => ENERGIEFLUSS_KATALOG.knoten;
const kopie = o => JSON.parse(JSON.stringify(o));
function hinweis(text, klasse) {
const ziel = $("energieflussHinweis");
if (!ziel) return;
ziel.className = "small " + (klasse || "text-body-secondary");
ziel.textContent = text;
}
function geaendert() {
const sauber = s => JSON.stringify(Object.keys(s).sort().map(k => [k, s[k]]));
const anders = sauber(normalisiert(stand)) !== sauber(normalisiert(gespeichert));
hinweis(anders ? "Nicht gespeichert." : "", anders ? "text-warning" : "");
}
/** Nur echte Abweichungen, in fester Form - zum Vergleichen und Schicken. */
function normalisiert(s) {
const aus = {};
for (const k of katalog()) {
const e = s[k.id] || {};
const eintrag = {
aktiv: k.pflicht ? true : e.aktiv !== false,
name: (e.name || "").trim() || null,
x: e.x ?? null, y: e.y ?? null,
optionen: e.optionen || null,
};
if (eintrag.x === k.x && eintrag.y === k.y) { eintrag.x = null; eintrag.y = null; }
if (!eintrag.aktiv || eintrag.name || eintrag.x !== null || eintrag.optionen) aus[k.id] = eintrag;
}
return aus;
}
function vorschauZeichnen() {
if (!bild) {
bild = new Energiefluss($("energieflussVorschau"), {
bearbeiten: true,
einstellungen: stand,
onVerschoben(id, x, y) {
stand[id] = Object.assign({}, stand[id], { x, y });
listeZeichnen();
geaendert();
},
});
}
bild.setzeEinstellungen(normalisiert(stand));
bild.zeichnen();
bild.aktualisieren(BEISPIEL);
}
/* ------------------------------------------------------------ Liste */
function listeZeichnen() {
const ziel = $("energieflussListe");
const wirksam = bild ? bild.nachId : {};
ziel.innerHTML = katalog().map(k => {
const e = stand[k.id] || {};
const aktiv = k.pflicht || e.aktiv !== false;
const w = wirksam[k.id];
const eltern = w && w.elternWirksam;
const elternName = eltern ? (stand[eltern.id] && stand[eltern.id].name) || eltern.titel || eltern.name : null;
const verschoben = e.x != null && (e.x !== k.x || e.y !== k.y);
const flaechen = k.id === "pv" && aktiv ? flaechenZeichnen(k, e) : "";
return `
`;
}).join("");
}
function flaechenZeichnen(k, e) {
const liste = (e.optionen && e.optionen.flaechen) || k.optionen.flaechen;
const eigen = !!(e.optionen && e.optionen.flaechen);
return `
`;
}
function flaechenLesen(zeile) {
return [...zeile.querySelectorAll(".enf-e-flaeche")].map(r => ({
name: r.querySelector('[data-flaeche="name"]').value.trim(),
von: Math.max(0, parseInt(r.querySelector('[data-flaeche="von"]').value, 10) || 0),
bis: Math.max(0, parseInt(r.querySelector('[data-flaeche="bis"]').value, 10) || 0),
farbe: r.querySelector('[data-flaeche="farbe"]').value,
}));
}
function flaechenSetzen(id, flaechen) {
const e = stand[id] = Object.assign({}, stand[id]);
e.optionen = Object.assign({}, e.optionen, { flaechen });
}
/* --------------------------------------------------------- Ereignisse */
function binden() {
const liste = $("energieflussListe");
liste.addEventListener("change", ev => {
const zeile = ev.target.closest(".enf-e-zeile");
if (!zeile) return;
const id = zeile.dataset.id;
if (ev.target.dataset.feld === "aktiv") {
stand[id] = Object.assign({}, stand[id], { aktiv: ev.target.checked });
vorschauZeichnen();
listeZeichnen();
} else if (ev.target.dataset.flaeche) {
flaechenSetzen(id, flaechenLesen(zeile));
vorschauZeichnen();
}
geaendert();
});
liste.addEventListener("input", ev => {
const zeile = ev.target.closest(".enf-e-zeile");
if (!zeile || ev.target.dataset.feld !== "name") return;
stand[zeile.dataset.id] = Object.assign({}, stand[zeile.dataset.id], { name: ev.target.value });
clearTimeout(binden._uhr);
binden._uhr = setTimeout(vorschauZeichnen, 300);
geaendert();
});
liste.addEventListener("click", ev => {
const knopf = ev.target.closest("[data-aktion]");
if (!knopf) return;
const zeile = knopf.closest(".enf-e-zeile");
const id = zeile.dataset.id;
switch (knopf.dataset.aktion) {
case "lage":
stand[id] = Object.assign({}, stand[id], { x: null, y: null });
break;
case "flaeche-neu": {
const f = flaechenLesen(zeile);
const bis = f.length ? f[f.length - 1].bis + 1 : 0;
f.push({ name: "Fläche " + (f.length + 1), von: bis, bis, farbe: "#f7c000" });
flaechenSetzen(id, f);
break;
}
case "flaeche-weg": {
const f = flaechenLesen(zeile);
f.splice(Number(knopf.closest(".enf-e-flaeche").dataset.index), 1);
flaechenSetzen(id, f);
break;
}
case "flaechen-zurueck":
if (stand[id] && stand[id].optionen) {
const o = Object.assign({}, stand[id].optionen);
delete o.flaechen;
stand[id].optionen = Object.keys(o).length ? o : null;
}
break;
default:
return;
}
vorschauZeichnen();
listeZeichnen();
geaendert();
});
$("energieflussSpeichern").addEventListener("click", speichern);
$("energieflussNeuLaden").addEventListener("click", () => {
stand = kopie(gespeichert);
vorschauZeichnen(); listeZeichnen(); geaendert();
});
$("energieflussZuruecksetzen").addEventListener("click", () => {
stand = {};
vorschauZeichnen(); listeZeichnen(); geaendert();
});
}
async function speichern() {
const knopf = $("energieflussSpeichern");
knopf.disabled = true;
try {
const n = normalisiert(stand);
const knoten = Object.keys(n).map(id => Object.assign({ id }, n[id]));
const antwort = await fetch("ajax/settings.php?action=energiefluss-save", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ knoten }),
});
const d = await antwort.json();
if (!antwort.ok || d.error) throw new Error(d.error || "HTTP " + antwort.status);
gespeichert = d.einstellungen || {};
stand = kopie(gespeichert);
vorschauZeichnen(); listeZeichnen();
hinweis("Gespeichert – die Solar-Seite zeigt es beim nächsten Laden.", "text-success");
} catch (e) {
hinweis(e.message, "text-danger");
} finally {
knopf.disabled = false;
}
}
async function laden() {
if (geladen) return;
geladen = true;
try {
const antwort = await fetch("ajax/settings.php?action=energiefluss", { cache: "no-store" });
const d = await antwort.json();
if (!antwort.ok || d.error) throw new Error(d.error || "HTTP " + antwort.status);
gespeichert = d.einstellungen || {};
stand = kopie(gespeichert);
binden();
vorschauZeichnen();
listeZeichnen();
} catch (e) {
geladen = false;
$("energieflussListe").innerHTML = 'Übersicht nicht abrufbar: '
+ textSchuetzen(e.message) + "
";
}
}
document.addEventListener("DOMContentLoaded", () => {
if (!$("energieflussVorschau") || typeof Energiefluss === "undefined") return;
// Erst zeichnen, wenn der Reiter offen ist - verborgen hat die Vorschau keine Größe.
const knopf = $("einstellung-energiefluss-tab");
if (knopf) knopf.addEventListener("shown.bs.tab", laden);
if ($("einstellung-energiefluss").classList.contains("active")) laden();
// Ein Anker in der Adresse öffnet den Reiter erst nach DOMContentLoaded.
setTimeout(() => { if ($("einstellung-energiefluss").classList.contains("active")) laden(); }, 0);
});
})();