Files
Smart-Dashboard/restricted/home.php
T
adminandClaude Opus 5 93c34bf069 Automatismen als Zeitleiste des Tages, Liste bleibt umschaltbar
Die Uebersicht auf der Startseite zeigt die Automatiken jetzt als
Zeitleiste: eine Bahn je Geraeteart, die Spur ist der Tag. Der Umschalter
"Zeitleiste | Liste" oben in der Karte fuehrt zur bisherigen Tabelle
zurueck und wird im Browser gemerkt.

Jede Automatik erscheint genau einmal, nach festen Regeln
(restricted/zeitleiste.php), auch kuenftige:
- feste Uhrzeit oder Sonnenstand: Punkt; Zeitfenster mit Messwert: Balken;
  Kette: hinter ihrem Ausloeser, als gestrichelter Bogen verbunden
- ohne jede Zeit: Band "Jederzeit"; pausiert: Band "Pausiert"
- an diesem Tag nicht dran (Wochentag, Ferien, Feiertag, Vorabend, wie im
  Runner gerechnet): gestrichelt mit Grund, nie ausgeblendet
- Bahn aus den geschalteten Geraeten ueber bedienform(), Mehrheit gewinnt;
  Bewaesserung am Geraetetyp
- Zaehler "x von y" im Kopf

Dazu: Tag vor/zurueck, Etagen einzeln einblendbar (gemerkt), Jetzt-Linie
und Nachtschatten aus solarLog.daylight (spaetere Tage vom selben
Kalendertag eines Vorjahres), gelaufene Eintraege mit Uhrzeit aus
automation_log, Klick oeffnet den Editor. Schmal nur Symbole in den Bahnen
und Start bei der aktuellen Uhrzeit.

Auf einer Probeseite mit echten Daten geprueft: nichts ragt ueber, keine
ueberlappenden Etiketten, alle 22 Automatiken genau einmal, Filter,
Tageswechsel und Umschalter.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 13:12:15 +02:00

285 lines
15 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
require_once(__DIR__ . "/rooms.php");
require_once(__DIR__ . "/reiter.php");
?>
<!--begin::App Main-->
<main class="app-main">
<!--begin::App Content Header-->
<div class="app-content-header">
<!--begin::Container-->
<div class="container-fluid">
<!--begin::Row-->
<div class="row">
<div class="col-sm-12">
<h3 class="mb-0">Home</h3>
</div>
</div>
<!--end::Row-->
</div>
<!--end::Container-->
</div>
<!--end::App Content Header-->
<!--begin::App Content-->
<div class="app-content">
<!--begin::Container-->
<div class="container-fluid">
<!--begin::Row-->
<div class="row">
<!-- Start col -->
<div class="col-xxl-12 col-xl-12 col-lg-12 col-md-12 d-flex align-items-stretch">
<div class="card p-0 mb-4" style="width:100%">
<div class="card-header pb-0 pt-1">
<h3 class="card-title">Home</h3>
<div class="card-tools">
<button type="button" class="btn btn-tool" data-lte-toggle="card-remove">
<i class="bi bi-x-lg"></i>
</button>
</div>
</div>
<div class="card-body pb-1">
<?php if (!floorsWithPlan()): ?>
<!-- Eine neue Installation: noch keine Etage mit platzierten
Raeumen. Statt einer leeren Zeichenflaeche der Weg dorthin,
wo sich das aendert. -->
<div class="alert alert-secondary mb-3">
<?php if (!grundrissDa()): ?>
Etagen und Räume lassen sich nicht lesen die Datenbank
<code>homeMesh</code> ist nicht erreichbar, oder
<code>homeMesh_grundriss.sql</code> ist noch nicht eingespielt.
<?php else: ?>
Noch kein Grundriss. Unter
<a href="?action=settings#grundriss">Einstellungen → Grundriss</a>
werden Etagen angelegt, ihre Grundrissbilder hochgeladen und die
Kacheln der Räume gesetzt.
<?php endif; ?>
</div>
<?php else: ?>
<div lass="img-overlay-wrap">
<svg viewBox="0 0 400 300" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<defs>
<g id="heater" filter="url(#shadow)" transform="translate(1.5,1),scale(0.2,0.2)" fill="#993311" stroke="#FF2211">
<path clip-path="M-22 2.24h42V22h-42z"
d="M16.543 8.028c-.023 1.503-.523 3.538-2.867 4.327.734-1.746.846-3.417.326-4.979-.695-2.097-3.014-3.735-4.557-4.627-.527-.306-1.203.074-1.193.683.02 1.112-.318 2.737-1.959 4.378C4.107 9.994 3 12.251 3 14.517 3 17.362 5 21 9 21c-4.041-4.041-1-7.483-1-7.483C8.846 19.431 12.988 21 15 21c1.711 0 5-1.25 5-6.448 0-3.133-1.332-5.511-2.385-6.899-.347-.458-1.064-.198-1.072.375" />
</g>
<g id="buffer" filter="url(#shadow)" transform="translate(18,1),scale(0.3,0.3)" fill="#ffffff">
<path d="M5 12.5a1.5 1.5 0 1 1-2-1.415V2.5a.5.5 0 0 1 1 0v8.585A1.5 1.5 0 0 1 5 12.5"/>
<path d="M1 2.5a2.5 2.5 0 0 1 5 0v7.55a3.5 3.5 0 1 1-5 0zM3.5 1A1.5 1.5 0 0 0 2 2.5v7.987l-.167.15a2.5 2.5 0 1 0 3.333 0L5 10.486V2.5A1.5 1.5 0 0 0 3.5 1m5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-1 0v-1a.5.5 0 0 1 .5-.5m4.243 1.757a.5.5 0 0 1 0 .707l-.707.708a.5.5 0 1 1-.708-.708l.708-.707a.5.5 0 0 1 .707 0M8 5.5a.5.5 0 0 1 .5-.5 3 3 0 1 1 0 6 .5.5 0 0 1 0-1 2 2 0 0 0 0-4 .5.5 0 0 1-.5-.5M12.5 8a.5.5 0 0 1 .5-.5h1a.5.5 0 1 1 0 1h-1a.5.5 0 0 1-.5-.5m-1.172 2.828a.5.5 0 0 1 .708 0l.707.708a.5.5 0 0 1-.707.707l-.708-.707a.5.5 0 0 1 0-.708M8.5 12a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-1 0v-1a.5.5 0 0 1 .5-.5"/>
</g>
</defs>
<g>
<?php
/*
* Ein Bild je Etage, dazu fuer jede Etage eine Blende.
* Ausgeschrieben waren das bei drei Etagen neun Zeilen
* und bei vier schon sechzehn - und jede neue Etage
* haette alle anderen mit angefasst.
*
* floorsWithPlan() liefert nur Etagen, deren Raeume
* Koordinaten haben. Eine Etage ohne angeklickte
* Kachelpositionen taucht hier also gar nicht erst auf.
*/
$etagen = floorsWithPlan();
?>
<?php /* Von unten nach oben gestapelt, wie bisher: waehrend der Blende
sind zwei Bilder gleichzeitig halb sichtbar, und dann soll das
obere Geschoss obenauf liegen. */ ?>
<?php foreach (array_reverse($etagen) as $etage): ?>
<g id="<?= $etage ?>img" <?php if ($_GET["floor"] != $etage) echo "opacity='0'"; ?> >
<?php foreach ($etagen as $andere): ?>
<animate begin="<?= $andere ?>.click" attributetype="CSS" attributeName="opacity"
to=<?= $andere === $etage ? "1.0" : "0.0" ?> dur="0.7s"
repeatCount="1" calcMode="spline" keyTimes="0;1" keySplines=".1,0,.27,1" fill="freeze" />
<?php endforeach; ?>
<image href="<?= htmlspecialchars((string)etageBild($etage)) ?>" height="100%" width="100%" x=0 y=0 />
</g>
<?php endforeach; ?>
<?php
/*
* Groesse der Etagenschalter. Der Kreis ist im eigenen
* Koordinatensystem 88 Einheiten breit und reicht von 28
* bis 116 unter die Oberkante seiner Gruppe; alles andere
* folgt daraus. Sie waren einmal doppelt so gross und
* beherrschten damit die linke Seite - und das Bild des
* Aussengelaendes musste umso weiter einruecken, um sie
* freizuhalten.
*/
$mass = 0.38;
$kreisUnten = 116 * $mass;
/*
* Bei drei Etagen bleibt es bei den gewohnten 100
* Einheiten Abstand; ab der vierten ruecken sie zusammen,
* sonst faellt die unterste aus der 300 hohen viewBox.
*/
$abstand = count($etagen) > 1
? min(100, (295 - $kreisUnten) / (count($etagen) - 1))
: 0;
?>
<?php foreach ($etagen as $i => $etage): ?>
<g id="<?= $etage ?>" class="floorBtn<?= $_GET["floor"] == $etage ? " active" : "" ?>"
onclick="switchFloor('<?= $etage ?>')"
transform="translate(0,<?= round($i * $abstand, 1) ?>), scale(<?= $mass ?>)">
<!-- Die Beschriftung sitzt ueber text-anchor und
dominant-baseline im Kreismittelpunkt. Vorher stand
je Etage ein von Hand gesuchter Versatz im Markup
(62, 63, 64), und eine neue Etage brauchte einen
neuen. Aussehen und Zustand stehen in css/solar.css. -->
<circle cx="50" cy="72" r="44" />
<text x="50" y="72" text-anchor="middle"
dominant-baseline="central"><?= $etage ?></text>
</g>
<?php endforeach; ?>
<?php foreach (floorsWithPlan() as $floor): ?>
<g id="<?= $floor ?>_Info" <?php if ($floor != standardEtage()) echo 'opacity="0" '; ?><?php if($_GET["floor"] != $floor){echo "display='none'";} ?> >
<?php foreach (array_map("mitWerten", roomsOnFloor($floor)) as $room): ?>
<?php
/*
* Grundlinien je nach Anzahl der Werte. Die Kachel ist 19 hoch; mit
* drei Werten sind es dieselben Zeilen wie eh und je (klein, gross,
* klein), mit weniger ruecken sie in die Mitte.
*/
$zeilen = [1 => [12], 2 => [9, 16], 3 => [5, 11.5, 16.5]];
$y = $zeilen[count($room["werte"])] ?? [];
?>
<g transform="translate(<?= $room["x"] ?>,<?= $room["y"] ?>)" onclick="openRoomView(<?= $room["nr"] ?>);" cursor="pointer">
<rect width="24" height="19" rx="5" fill="#0006" />
<?php if ($room["heizung"]): ?>
<use id="<?= $floor ?>_<?= $room["id"] ?>_heater" href="#heater" display="none" />
<use id="<?= $floor ?>_<?= $room["id"] ?>_buffer" href="#buffer" display="none" />
<?php endif; ?>
<text fill="#fff" style="font: 4px sans-serif;">
<?php if (!$room["werte"]): ?>
<?php
/*
* Kein Messwert: die Kachel traegt ihren Namen und oeffnet den Raum.
* Ein leeres Rechteck saehe aus wie ein Fehler.
*
* Lange Namen brechen am letzten Leerzeichen um. Die Kachel ist 24
* Einheiten breit, und "Terrasse OG" stand einzeilig links und rechts
* darueber hinaus. Aufgefallen ist das erst mit dem Aussengelaende:
* dort besteht jeder Raumname aus zwei Woertern.
*/
$luecke = strrpos($room["mqtt"], " ");
$teile = (strlen($room["mqtt"]) > 8 && $luecke !== false)
? [substr($room["mqtt"], 0, $luecke), substr($room["mqtt"], $luecke + 1)]
: [$room["mqtt"]];
$namenszeilen = count($teile) === 2 ? [9, 15.5] : [11.5];
?>
<?php foreach ($teile as $i => $teil): ?>
<tspan x="12" y="<?= $namenszeilen[$i] ?>" font-size="1.2em" text-anchor="middle"><?= htmlspecialchars($teil) ?></tspan>
<?php endforeach; ?>
<?php endif; ?>
<?php foreach ($room["werte"] as $i => $wert): ?>
<?php $zeichen = kachelIcon($wert["icon"] ?? ""); ?>
<?php if ($zeichen !== ""): ?>
<!-- Ein Symbol vor der Zahl. "0 W" allein sagt nicht,
ob geladen, erzeugt oder verbraucht wird; die
Einheit beantwortet das nicht. Die Zahl rueckt
dafuer nach rechts, damit Symbol und Wert
zusammen mittig stehen. -->
<tspan x="4" y="<?= $y[$i] ?>" text-anchor="middle" opacity=".8"
style="font-family:bootstrap-icons"><?= $zeichen ?></tspan>
<?php endif; ?>
<!-- Der Platzhalter ist nur ein Strich: bevor der
erste Wert da ist, soll hier keine Einheit
stehen, die vielleicht gar nicht passt. -->
<tspan id="<?= $floor ?>_w<?= $i ?>_<?= $room["id"] ?>"
x="<?= $zeichen !== "" ? 15 : 12 ?>" y="<?= $y[$i] ?>"
<?= empty($wert["gross"]) ? "" : 'font-size="1.6em"' ?> text-anchor="middle">&ndash;</tspan>
<?php endforeach; ?>
</text>
</g>
<?php endforeach; ?>
</g>
<?php endforeach; ?>
</g>
</svg>
</div>
<?php endif; ?>
</div>
</div>
<!-- /.card -->
</div>
<div class="col-xxl-12 col-xl-12 col-lg-12 col-md-12 d-flex align-items-stretch">
<div class="card p-0 mb-4" style="width:100%">
<div class="card-header pb-1 pt-1 d-flex align-items-center gap-2 flex-wrap">
<h3 class="card-title me-auto">Automatismen</h3>
<!-- Zwei Ansichten derselben Automatiken. Die Zeitleiste zeigt den
Tag (js/solar/zeitleiste.js), die Liste ist die bisherige
Tabelle je Etage. Welche offen ist, merkt sich der Browser. -->
<div class="btn-group btn-group-sm zl-ansicht" role="group" aria-label="Ansicht">
<button type="button" class="btn btn-outline-secondary" data-ansicht="zeitleiste">
<i class="bi bi-bar-chart-steps me-1"></i>Zeitleiste</button>
<button type="button" class="btn btn-outline-secondary" data-ansicht="liste">
<i class="bi bi-list-ul me-1"></i>Liste</button>
</div>
<button type="button" class="btn btn-sm btn-success zl-neu" title="Neue Automatik">
<i class="bi bi-plus-lg"></i></button>
</div>
<div class="card-body pb-2">
<div id="zeitleiste" class="zl" hidden></div>
<div id="automatikListe">
<?php
/*
* Eine Etage, ein Reiter. Gezeichnet von reiterLeiste() aus
* restricted/reiter.php - dieselbe Bauart wie im Raum-Modal und
* auf der Einstellungsseite. Frueher stand hier ein eigenes
* Markup samt eigenem switchTab() in homeMQTT.js; umgeschaltet
* wird jetzt von Bootstrap wie ueberall sonst.
*
* Der Name "actions" haelt die Element-Ids, an denen
* autoActionFuncs.js haengt: actions-OG, actions-OG-list.
*/
$autoReiter = array_map(function ($etage) {
return ["id" => $etage, "titel" => $etage,
"titel2" => floorLabel($etage)];
}, $floors);
$autoOpt = ["name" => "actions", "aktiv" => $_GET["floor"],
"klasse" => "mb-2"];
$autoOpt["aktiv"] = reiterAktiv($autoReiter, $autoOpt);
?>
<?= reiterLeiste($autoReiter, $autoOpt) ?>
<div class="tab-content" id="actions-tabContent">
<?php foreach ($floors as $etage): ?>
<?= reiterFlaecheAuf($etage, $autoOpt) ?>
<!-- Inhalt kommt aus ajax/AutoAction.php?action=list, gefuellt von
refreshAutomations() in js/solar/autoActionFuncs.js -->
<div id="actions-<?= $etage ?>-list">Wird geladen...</div>
<button type="button" class="btn btn-success" title="Neue Automatik"
onclick="openAutoActionModal('?action=editor&floor=<?= $etage ?>')"><i class="bi bi-plus-square"></i></button>
<?= reiterFlaecheZu() ?>
<?php endforeach; ?>
</div>
</div><!-- /automatikListe -->
</div>
</div>
</div>
<!-- /.Start col -->
</div>
<!-- /.row (main row) -->
</div>
<!--end::Container-->
</div>
<!--end::App Content-->
</main>
<!--end::App Main-->
<script>
// Raumtabelle aus restricted/rooms.php - homeMQTT.js aktualisiert darueber
// die SVG-Kacheln, ohne die Raeume noch einmal aufzufuehren. In "werte"
// steht, welche Messwerte eine Kachel zeigt; homeTopics sind die Zweige,
// die der Browser dafuer beim Broker anmelden muss.
const homeRooms = <?= json_encode(roomsWithTile(), JSON_UNESCAPED_UNICODE) ?>;
const homeTopics = <?= json_encode(tileTopics(), JSON_UNESCAPED_UNICODE) ?>;
const homeStandardEtage = <?= json_encode(standardEtage()) ?>;
<?php if ($_GET["floor"] != standardEtage() && in_array($_GET["floor"], floorsWithPlan())): ?>
document.addEventListener('readystatechange', function () {
if (event.target.readyState === 'complete') {
switchFloor('<?= $_GET["floor"] ?>');
}
});
<?php endif; ?>
</script>