fix(pwa): Terminal-Ausrichtung und Block-Darstellung

- Gebündelte Terminals: Pfeil jetzt in Zeile 2/4 (wie normale Terminals)
  statt in Label-Zeile - sitzt direkt am Equipment-Block
- Terminal-Punkte mit CSS-Klassen terminal-row-top/bottom für korrekte
  Ausrichtung am Equipment
- Equipment-Block-Value (B16A etc.) auf 8px verkleinert
- Grid gap auf 0 für kompaktere Darstellung

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Eduard Wisch 2026-03-02 15:03:50 +01:00
parent 619d14e8d5
commit 01626be22d
5 changed files with 65 additions and 46 deletions

View file

@ -76,7 +76,7 @@ class modKundenKarte extends DolibarrModules
$this->editor_squarred_logo = ''; // Must be image filename into the module/img directory followed with @modulename. Example: 'myimage.png@kundenkarte' $this->editor_squarred_logo = ''; // Must be image filename into the module/img directory followed with @modulename. Example: 'myimage.png@kundenkarte'
// Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z' // Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z'
$this->version = '7.5'; $this->version = '8.3';
// Url to the file with your last numberversion of this module // Url to the file with your last numberversion of this module
//$this->url_last_version = 'http://www.example.com/versionmodule.txt'; //$this->url_last_version = 'http://www.example.com/versionmodule.txt';

View file

@ -831,10 +831,10 @@ body {
Zeile 3: Output-Terminals (Abgänge) */ Zeile 3: Output-Terminals (Abgänge) */
.carrier-content { .carrier-content {
display: grid; display: grid;
gap: 2px; gap: 0;
padding: 5px; padding: 5px;
grid-template-rows: auto auto auto; /* 5 Zeilen: Labels oben, Terminals oben, Equipment, Terminals unten, Labels unten */
align-items: stretch; grid-template-rows: auto auto auto auto auto;
} }
/* Equipment Block (Zeile 2) - Sicherungsautomat-Optik */ /* Equipment Block (Zeile 2) - Sicherungsautomat-Optik */
@ -869,7 +869,7 @@ body {
} }
.equipment-block-value { .equipment-block-value {
font-size: 11px; font-size: 8px;
font-weight: bold; font-weight: bold;
color: #fff; color: #fff;
line-height: 1.1; line-height: 1.1;
@ -913,12 +913,18 @@ body {
min-width: 0; min-width: 0;
} }
.terminal-point.terminal-input { /* Zeile 2 (obere Terminals): direkt am Equipment (margin-bottom negativ) */
align-self: end; .terminal-point.terminal-row-top {
margin-bottom: -2px;
position: relative;
z-index: 1;
} }
.terminal-point.terminal-output { /* Zeile 4 (untere Terminals): direkt am Equipment (margin-top negativ) */
align-self: start; .terminal-point.terminal-row-bottom {
margin-top: -2px;
position: relative;
z-index: 1;
} }
.terminal-point:active { .terminal-point:active {
@ -1038,7 +1044,16 @@ body {
.terminal-label-cell.bundled-label { .terminal-label-cell.bundled-label {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; }
/* Oben: Am unteren Rand ausrichten (zum Equipment hin) */
.terminal-label-cell.bundled-label.label-row-top {
align-items: flex-end;
}
/* Unten: Am oberen Rand ausrichten (zum Equipment hin) */
.terminal-label-cell.bundled-label.label-row-bottom {
align-items: flex-start;
} }
/* Gebündeltes Label mit Pfeil */ /* Gebündeltes Label mit Pfeil */

View file

@ -856,25 +856,17 @@
const bundledTop = eqTopOutputs.find(o => o.bundled_terminals === 'all'); const bundledTop = eqTopOutputs.find(o => o.bundled_terminals === 'all');
if (bundledTop && widthTe > 1) { if (bundledTop && widthTe > 1) {
// Gebündeltes Label mit Pfeil: Über alle Spalten des Equipment spannen // Gebündeltes Label (nur Text, OHNE Pfeil) in Zeile 1
const gridColStyle = posTe > 0 const gridColStyle = posTe > 0
? `grid-row:1; grid-column: ${posTe} / span ${widthTe}` ? `grid-row:1; grid-column: ${posTe} / span ${widthTe}`
: `grid-row:1; grid-column: span ${widthTe}`; : `grid-row:1; grid-column: span ${widthTe}`;
const cableInfo = buildCableInfo(bundledTop); const cableInfo = buildCableInfo(bundledTop);
const phaseColor = bundledTop.color || getPhaseColor(bundledTop.connection_type); html += `<span class="terminal-label-cell label-row-top bundled-label" style="${gridColStyle}" data-connection-id="${bundledTop.id}" data-equipment-id="${eq.id}" data-direction="output">`;
html += `<span class="terminal-label-cell label-row-top bundled-label bundled-with-arrow" style="${gridColStyle}" data-connection-id="${bundledTop.id}" data-equipment-id="${eq.id}" data-direction="output">`;
// Container für vertikale Anordnung: Label oben, Pfeil unten
html += `<span class="bundled-content">`;
if (bundledTop.output_label) { if (bundledTop.output_label) {
html += `<span class="terminal-label">${escapeHtml(bundledTop.output_label)}`; html += `<span class="terminal-label">${escapeHtml(bundledTop.output_label)}`;
if (cableInfo) html += `<span class="cable-info">${escapeHtml(cableInfo)}</span>`; if (cableInfo) html += `<span class="cable-info">${escapeHtml(cableInfo)}</span>`;
html += `</span>`; html += `</span>`;
} }
html += `<span class="bundled-arrow">`;
html += `<span class="terminal-arrow terminal-arrow-up" style="--arrow-color:${phaseColor}"></span>`;
if (bundledTop.connection_type) html += `<span class="terminal-phase">${escapeHtml(bundledTop.connection_type)}</span>`;
html += `</span>`;
html += `</span>`;
html += `</span>`; html += `</span>`;
} else { } else {
// Normale einzelne Labels pro Terminal - nur für tatsächliche Terminals // Normale einzelne Labels pro Terminal - nur für tatsächliche Terminals
@ -925,24 +917,34 @@
const topOut = bundledTop || eqTopOutputs[t] || null; const topOut = bundledTop || eqTopOutputs[t] || null;
if (bundledTop && widthTe > 1) { if (bundledTop && widthTe > 1) {
// Gebündelter Abgang: Pfeil ist bereits im Label (Zeile 1) - hier nur leere Zelle // Gebündelter Abgang: Pfeil nur beim ersten Terminal, Rest leer
html += `<span class="terminal-point terminal-empty bundled-placeholder" style="${style}"></span>`; if (t === 0) {
const phaseColor = bundledTop.color || getPhaseColor(bundledTop.connection_type);
const bundledStyle = posTe > 0
? `grid-row:2; grid-column: ${posTe} / span ${topTerminalCount}`
: `grid-row:2; grid-column: span ${topTerminalCount}`;
html += `<span class="terminal-point terminal-output terminal-row-top bundled-output" data-equipment-id="${eq.id}" data-direction="output" data-terminal-position="top" data-connection-id="${bundledTop.id}" style="${bundledStyle}">`;
html += `<span class="terminal-arrow terminal-arrow-up" style="--arrow-color:${phaseColor}"></span>`;
html += `<span class="terminal-phase">${escapeHtml(bundledTop.connection_type || '')}</span>`;
html += `</span>`;
}
// Restliche Terminals überspringen (grid-column: span hat sie schon)
} else if (topOut && (!topOut.bundled_terminals || widthTe <= 1)) { } else if (topOut && (!topOut.bundled_terminals || widthTe <= 1)) {
// Normaler Top-Output ODER bundled bei 1 TE (Bundle macht bei 1 TE keinen Unterschied) // Normaler Top-Output ODER bundled bei 1 TE (Bundle macht bei 1 TE keinen Unterschied)
const phaseColor = topOut.color || getPhaseColor(topOut.connection_type); const phaseColor = topOut.color || getPhaseColor(topOut.connection_type);
html += `<span class="terminal-point terminal-output" data-equipment-id="${eq.id}" data-direction="output" data-terminal-position="top" data-connection-id="${topOut.id}" style="${style}">`; html += `<span class="terminal-point terminal-output terminal-row-top" data-equipment-id="${eq.id}" data-direction="output" data-terminal-position="top" data-connection-id="${topOut.id}" style="${style}">`;
html += `<span class="terminal-arrow terminal-arrow-up" style="--arrow-color:${phaseColor}"></span>`; html += `<span class="terminal-arrow terminal-arrow-up" style="--arrow-color:${phaseColor}"></span>`;
html += `<span class="terminal-phase">${escapeHtml(topOut.connection_type || '')}</span>`; html += `<span class="terminal-phase">${escapeHtml(topOut.connection_type || '')}</span>`;
html += `</span>`; html += `</span>`;
} else if (inp) { } else if (inp) {
const phaseColor = inp.color || getPhaseColor(inp.connection_type); const phaseColor = inp.color || getPhaseColor(inp.connection_type);
html += `<span class="terminal-point terminal-input" data-equipment-id="${eq.id}" data-direction="input" data-terminal-position="top" data-connection-id="${inp.id}" style="${style}">`; html += `<span class="terminal-point terminal-input terminal-row-top" data-equipment-id="${eq.id}" data-direction="input" data-terminal-position="top" data-connection-id="${inp.id}" style="${style}">`;
html += `<span class="terminal-dot" style="background:${phaseColor}"></span>`; html += `<span class="terminal-dot" style="background:${phaseColor}"></span>`;
html += `<span class="terminal-phase">${escapeHtml(inp.connection_type || '')}</span>`; html += `<span class="terminal-phase">${escapeHtml(inp.connection_type || '')}</span>`;
html += `</span>`; html += `</span>`;
} else { } else {
// Leerer Terminal - neutral, Position "top" // Leerer Terminal - neutral, Position "top"
html += `<span class="terminal-point terminal-empty" data-equipment-id="${eq.id}" data-terminal-position="top" data-connection-id="" style="${style}">`; html += `<span class="terminal-point terminal-empty terminal-row-top" data-equipment-id="${eq.id}" data-terminal-position="top" data-connection-id="" style="${style}">`;
html += `<span class="terminal-dot terminal-dot-empty"></span>`; html += `<span class="terminal-dot terminal-dot-empty"></span>`;
html += `</span>`; html += `</span>`;
} }
@ -951,7 +953,7 @@
for (let t = topTerminalCount; t < widthTe; t++) { for (let t = topTerminalCount; t < widthTe; t++) {
const colPos = posTe > 0 ? posTe + t : 0; const colPos = posTe > 0 ? posTe + t : 0;
const style = `grid-row:2;${colPos > 0 ? ' grid-column:' + colPos : ''}`; const style = `grid-row:2;${colPos > 0 ? ' grid-column:' + colPos : ''}`;
html += `<span class="terminal-point terminal-empty no-terminal" style="${style}"></span>`; html += `<span class="terminal-point terminal-empty no-terminal terminal-row-top" style="${style}"></span>`;
} }
}); });
@ -1034,24 +1036,34 @@
const inp = eqBottomInputs[t] || null; const inp = eqBottomInputs[t] || null;
if (bundledBottom && widthTe > 1) { if (bundledBottom && widthTe > 1) {
// Gebündelter Abgang: Pfeil ist im Label (Zeile 5) - hier nur leere Zelle // Gebündelter Abgang: Pfeil nur beim ersten Terminal, Rest leer
html += `<span class="terminal-point terminal-empty bundled-placeholder" style="${style}"></span>`; if (t === 0) {
const phaseColor = bundledBottom.color || getPhaseColor(bundledBottom.connection_type);
const bundledStyle = posTe > 0
? `grid-row:4; grid-column: ${posTe} / span ${bottomTerminalCount}`
: `grid-row:4; grid-column: span ${bottomTerminalCount}`;
html += `<span class="terminal-point terminal-output terminal-row-bottom bundled-output" data-equipment-id="${eq.id}" data-direction="output" data-terminal-position="bottom" data-connection-id="${bundledBottom.id}" style="${bundledStyle}">`;
html += `<span class="terminal-arrow terminal-arrow-down" style="--arrow-color:${phaseColor}"></span>`;
html += `<span class="terminal-phase">${escapeHtml(bundledBottom.connection_type || '')}</span>`;
html += `</span>`;
}
// Restliche Terminals überspringen (grid-column: span hat sie schon)
} else if (out && (!out.bundled_terminals || widthTe <= 1)) { } else if (out && (!out.bundled_terminals || widthTe <= 1)) {
// Normaler Abgang ODER bundled bei 1 TE (Bundle macht bei 1 TE keinen Unterschied) // Normaler Abgang ODER bundled bei 1 TE (Bundle macht bei 1 TE keinen Unterschied)
const phaseColor = out.color || getPhaseColor(out.connection_type); const phaseColor = out.color || getPhaseColor(out.connection_type);
html += `<span class="terminal-point terminal-output" data-equipment-id="${eq.id}" data-direction="output" data-terminal-position="bottom" data-connection-id="${out.id}" style="${style}">`; html += `<span class="terminal-point terminal-output terminal-row-bottom" data-equipment-id="${eq.id}" data-direction="output" data-terminal-position="bottom" data-connection-id="${out.id}" style="${style}">`;
html += `<span class="terminal-arrow terminal-arrow-down" style="--arrow-color:${phaseColor}"></span>`; html += `<span class="terminal-arrow terminal-arrow-down" style="--arrow-color:${phaseColor}"></span>`;
html += `<span class="terminal-phase">${escapeHtml(out.connection_type || '')}</span>`; html += `<span class="terminal-phase">${escapeHtml(out.connection_type || '')}</span>`;
html += `</span>`; html += `</span>`;
} else if (inp) { } else if (inp) {
const phaseColor = inp.color || getPhaseColor(inp.connection_type); const phaseColor = inp.color || getPhaseColor(inp.connection_type);
html += `<span class="terminal-point terminal-input" data-equipment-id="${eq.id}" data-direction="input" data-terminal-position="bottom" data-connection-id="${inp.id}" style="${style}">`; html += `<span class="terminal-point terminal-input terminal-row-bottom" data-equipment-id="${eq.id}" data-direction="input" data-terminal-position="bottom" data-connection-id="${inp.id}" style="${style}">`;
html += `<span class="terminal-dot" style="background:${phaseColor}"></span>`; html += `<span class="terminal-dot" style="background:${phaseColor}"></span>`;
html += `<span class="terminal-phase">${escapeHtml(inp.connection_type || '')}</span>`; html += `<span class="terminal-phase">${escapeHtml(inp.connection_type || '')}</span>`;
html += `</span>`; html += `</span>`;
} else { } else {
// Leerer Terminal - neutral, Position "bottom" // Leerer Terminal - neutral, Position "bottom"
html += `<span class="terminal-point terminal-empty" data-equipment-id="${eq.id}" data-terminal-position="bottom" data-connection-id="" style="${style}">`; html += `<span class="terminal-point terminal-empty terminal-row-bottom" data-equipment-id="${eq.id}" data-terminal-position="bottom" data-connection-id="" style="${style}">`;
html += `<span class="terminal-dot terminal-dot-empty"></span>`; html += `<span class="terminal-dot terminal-dot-empty"></span>`;
html += `</span>`; html += `</span>`;
} }
@ -1060,7 +1072,7 @@
for (let t = bottomTerminalCount; t < widthTe; t++) { for (let t = bottomTerminalCount; t < widthTe; t++) {
const colPos = posTe > 0 ? posTe + t : 0; const colPos = posTe > 0 ? posTe + t : 0;
const style = `grid-row:4;${colPos > 0 ? ' grid-column:' + colPos : ''}`; const style = `grid-row:4;${colPos > 0 ? ' grid-column:' + colPos : ''}`;
html += `<span class="terminal-point terminal-empty no-terminal" style="${style}"></span>`; html += `<span class="terminal-point terminal-empty no-terminal terminal-row-bottom" style="${style}"></span>`;
} }
}); });
@ -1078,26 +1090,18 @@
const bundledBottom = eqBottomOutputs.find(o => o.bundled_terminals === 'all'); const bundledBottom = eqBottomOutputs.find(o => o.bundled_terminals === 'all');
if (bundledBottom && widthTe > 1) { if (bundledBottom && widthTe > 1) {
// Gebündeltes Label mit Pfeil: Über alle Spalten des Equipment spannen // Gebündeltes Label (nur Text, OHNE Pfeil) in Zeile 5
const gridColStyle = posTe > 0 const gridColStyle = posTe > 0
? `grid-row:5; grid-column: ${posTe} / span ${widthTe}` ? `grid-row:5; grid-column: ${posTe} / span ${widthTe}`
: `grid-row:5; grid-column: span ${widthTe}`; : `grid-row:5; grid-column: span ${widthTe}`;
const cableInfo = buildCableInfo(bundledBottom); const cableInfo = buildCableInfo(bundledBottom);
const phaseColor = bundledBottom.color || getPhaseColor(bundledBottom.connection_type); html += `<span class="terminal-label-cell label-row-bottom bundled-label" style="${gridColStyle}" data-connection-id="${bundledBottom.id}" data-equipment-id="${eq.id}" data-direction="output">`;
html += `<span class="terminal-label-cell label-row-bottom bundled-label bundled-with-arrow" style="${gridColStyle}" data-connection-id="${bundledBottom.id}" data-equipment-id="${eq.id}" data-direction="output">`;
// Container für vertikale Anordnung: Pfeil oben, Label unten
html += `<span class="bundled-content bundled-content-bottom">`;
html += `<span class="bundled-arrow">`;
html += `<span class="terminal-arrow terminal-arrow-down" style="--arrow-color:${phaseColor}"></span>`;
if (bundledBottom.connection_type) html += `<span class="terminal-phase">${escapeHtml(bundledBottom.connection_type)}</span>`;
html += `</span>`;
if (bundledBottom.output_label) { if (bundledBottom.output_label) {
html += `<span class="terminal-label">${escapeHtml(bundledBottom.output_label)}`; html += `<span class="terminal-label">${escapeHtml(bundledBottom.output_label)}`;
if (cableInfo) html += `<span class="cable-info">${escapeHtml(cableInfo)}</span>`; if (cableInfo) html += `<span class="cable-info">${escapeHtml(cableInfo)}</span>`;
html += `</span>`; html += `</span>`;
} }
html += `</span>`; html += `</span>`;
html += `</span>`;
} else { } else {
// Normale einzelne Labels pro Terminal - nur für tatsächliche Terminals // Normale einzelne Labels pro Terminal - nur für tatsächliche Terminals
for (let t = 0; t < bottomTerminalCount; t++) { for (let t = 0; t < bottomTerminalCount; t++) {

View file

@ -44,7 +44,7 @@ $themeColor = getDolGlobalString('THEME_ELDY_TOPMENU_BACK1', '#3498db');
<link rel="manifest" href="manifest.json"> <link rel="manifest" href="manifest.json">
<link rel="icon" type="image/png" sizes="192x192" href="img/pwa-icon-192.png"> <link rel="icon" type="image/png" sizes="192x192" href="img/pwa-icon-192.png">
<link rel="apple-touch-icon" href="img/pwa-icon-192.png"> <link rel="apple-touch-icon" href="img/pwa-icon-192.png">
<link rel="stylesheet" href="css/pwa.css?v=4.4"> <link rel="stylesheet" href="css/pwa.css?v=5.3">
<style>:root { --primary: <?php echo $themeColor; ?>; }</style> <style>:root { --primary: <?php echo $themeColor; ?>; }</style>
</head> </head>
<body> <body>
@ -374,6 +374,6 @@ $themeColor = getDolGlobalString('THEME_ELDY_TOPMENU_BACK1', '#3498db');
window.DOLIBARR_URL = '<?php echo DOL_URL_ROOT; ?>'; window.DOLIBARR_URL = '<?php echo DOL_URL_ROOT; ?>';
window.MODULE_URL = '<?php echo DOL_URL_ROOT; ?>/custom/kundenkarte'; window.MODULE_URL = '<?php echo DOL_URL_ROOT; ?>/custom/kundenkarte';
</script> </script>
<script src="js/pwa.js?v=4.4"></script> <script src="js/pwa.js?v=5.1"></script>
</body> </body>
</html> </html>

4
sw.js
View file

@ -3,8 +3,8 @@
* Offline-First für Schaltschrank-Dokumentation * Offline-First für Schaltschrank-Dokumentation
*/ */
const CACHE_NAME = 'kundenkarte-pwa-v5.2'; const CACHE_NAME = 'kundenkarte-pwa-v6.1';
const OFFLINE_CACHE = 'kundenkarte-offline-v5.2'; const OFFLINE_CACHE = 'kundenkarte-offline-v6.1';
// Statische Assets die immer gecached werden (ohne Query-String) // Statische Assets die immer gecached werden (ohne Query-String)
const STATIC_ASSETS = [ const STATIC_ASSETS = [