kundenkarte/pwa.php
data 01626be22d 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>
2026-03-02 15:03:50 +01:00

379 lines
14 KiB
PHP
Raw Permalink 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
/* Copyright (C) 2026 Alles Watt lauft
*
* KundenKarte PWA - Mobile Schaltschrank-Dokumentation
* Offline-fähig für Elektriker vor Ort
*/
// Kein Dolibarr-Login erforderlich - eigenes Token-System
if (!defined('NOLOGIN')) {
define('NOLOGIN', '1');
}
if (!defined('NOREQUIREMENU')) {
define('NOREQUIREMENU', '1');
}
if (!defined('NOREQUIREHTML')) {
define('NOREQUIREHTML', '1');
}
// Load Dolibarr environment
$res = 0;
if (!$res && file_exists("../main.inc.php")) {
$res = @include "../main.inc.php";
}
if (!$res && file_exists("../../main.inc.php")) {
$res = @include "../../main.inc.php";
}
if (!$res) {
die("Dolibarr konnte nicht geladen werden");
}
// Theme color from Dolibarr
$themeColor = getDolGlobalString('THEME_ELDY_TOPMENU_BACK1', '#3498db');
?><!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<meta name="theme-color" content="<?php echo $themeColor; ?>">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="KundenKarte">
<title>KundenKarte</title>
<link rel="manifest" href="manifest.json">
<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="stylesheet" href="css/pwa.css?v=5.3">
<style>:root { --primary: <?php echo $themeColor; ?>; }</style>
</head>
<body>
<div id="app" class="app">
<!-- Login Screen -->
<div id="screen-login" class="screen active">
<div class="login-container">
<div class="login-logo">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 14h-2v-4H6v-2h4V7h2v4h4v2h-4v4z"/>
</svg>
</div>
<h1 class="login-title">KundenKarte</h1>
<p class="login-subtitle">Schaltschrank-Dokumentation</p>
<form id="login-form" class="login-form">
<div class="form-group">
<label>Benutzername</label>
<input type="text" id="login-user" autocomplete="username" required>
</div>
<div class="form-group">
<label>Passwort</label>
<input type="password" id="login-pass" autocomplete="current-password" required>
</div>
<button type="submit" class="btn btn-primary btn-large">
Anmelden
</button>
<p id="login-error" class="error-text"></p>
</form>
</div>
</div>
<!-- Kundensuche Screen -->
<div id="screen-search" class="screen">
<header class="header">
<h1>Kunde wählen</h1>
<button id="btn-logout" class="btn-icon" title="Abmelden">
<svg viewBox="0 0 24 24"><path d="M17 7l-1.41 1.41L18.17 11H8v2h10.17l-2.58 2.58L17 17l5-5zM4 5h8V3H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h8v-2H4V5z"/></svg>
</button>
</header>
<div class="search-container">
<div class="search-box">
<svg viewBox="0 0 24 24"><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>
<input type="text" id="search-customer" placeholder="Kunde suchen...">
</div>
</div>
<div id="customer-list" class="list">
<!-- Kunden werden hier geladen -->
</div>
<!-- Zuletzt bearbeitete Kunden -->
<div id="recent-customers" class="recent-section">
<h3 class="recent-title">Zuletzt bearbeitet</h3>
<div id="recent-list" class="list">
<!-- Wird per JS gefüllt -->
</div>
</div>
</div>
<!-- Anlagen Screen -->
<div id="screen-anlagen" class="screen">
<header class="header">
<button id="btn-back-search" class="btn-icon">
<svg viewBox="0 0 24 24"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>
</button>
<h1 id="customer-name">Kunde</h1>
<div class="header-spacer"></div>
</header>
<div id="anlagen-list" class="list anlagen-grid">
<!-- Anlagen werden hier geladen -->
</div>
</div>
<!-- Editor Screen -->
<div id="screen-editor" class="screen">
<header class="header">
<button id="btn-back-anlagen" class="btn-icon">
<svg viewBox="0 0 24 24"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>
</button>
<h1 id="anlage-name">Anlage</h1>
<button id="btn-sync" class="btn-icon sync-btn">
<svg viewBox="0 0 24 24"><path d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z"/></svg>
<span id="sync-badge" class="sync-badge hidden">0</span>
</button>
</header>
<div id="editor-content" class="editor-content">
<!-- Felder und Hutschienen -->
</div>
<div class="fab-container">
<button id="btn-add-panel" class="fab">
<svg viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
<span>Feld</span>
</button>
</div>
</div>
<!-- Automat hinzufügen/bearbeiten Modal -->
<div id="modal-add-equipment" class="modal">
<div class="modal-content">
<!-- Schritt 1: Typ wählen -->
<div id="eq-step-type">
<div class="modal-header">
<h2 id="equipment-modal-title">Automat hinzufügen</h2>
<button class="modal-close">&times;</button>
</div>
<div class="modal-body">
<p class="step-label">Typ wählen:</p>
<div id="type-grid" class="type-grid">
<!-- Typen werden geladen -->
</div>
</div>
</div>
<!-- Schritt 2: Felder ausfüllen -->
<div id="eq-step-fields" class="hidden">
<div class="modal-header">
<button id="btn-eq-back" class="btn-icon btn-back-modal">
<svg viewBox="0 0 24 24"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>
</button>
<h2 id="eq-fields-title">Werte</h2>
<button class="modal-close">&times;</button>
</div>
<div class="modal-body">
<div id="eq-dynamic-fields">
<!-- Dynamische Felder vom Server -->
</div>
<div class="form-group">
<label>Bezeichnung</label>
<input type="text" id="equipment-label" placeholder="Leer = automatisch (z.B. R1.3)">
</div>
<!-- FI/RCD-Schutz Zuordnung -->
<div id="eq-protection-fields" class="protection-section">
<div class="form-group">
<label><svg viewBox="0 0 24 24" class="icon-small"><path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11v8.8z"/></svg> Schutzgerät (FI/RCD)</label>
<select id="equipment-protection" class="form-select">
<option value="">-- Keins --</option>
</select>
</div>
</div>
</div>
<div class="modal-footer">
<button id="btn-delete-equipment" class="btn btn-danger hidden">Löschen</button>
<div class="modal-footer-right">
<button id="btn-cancel-equipment" class="btn btn-secondary">Abbrechen</button>
<button id="btn-save-equipment" class="btn btn-primary">Speichern</button>
</div>
</div>
</div>
</div>
</div>
<!-- Bestätigungsdialog -->
<div id="modal-confirm" class="modal">
<div class="modal-content modal-small">
<div class="modal-header">
<h2 id="confirm-title">Löschen?</h2>
<button class="modal-close">&times;</button>
</div>
<div class="modal-body">
<p id="confirm-message">Diesen Automaten wirklich löschen?</p>
</div>
<div class="modal-footer">
<button class="btn btn-secondary modal-close">Abbrechen</button>
<button id="btn-confirm-ok" class="btn btn-danger">Löschen</button>
</div>
</div>
</div>
<!-- Verbindung (Abgang/Anschlusspunkt) Modal -->
<div id="modal-connection" class="modal">
<div class="modal-content modal-small">
<div class="modal-header">
<h2 id="connection-modal-title">Verbindung bearbeiten</h2>
<button class="modal-close">&times;</button>
</div>
<div class="modal-body">
<div class="form-row">
<div class="form-group form-group-grow">
<label>Typ</label>
<select id="conn-type" class="form-select">
<!-- Wird dynamisch befüllt -->
</select>
</div>
<div class="form-group form-group-color">
<label>Farbe</label>
<input type="color" id="conn-color" class="form-color" value="#3498db">
</div>
</div>
<div class="form-group">
<label>Bezeichnung</label>
<input type="text" id="conn-label" class="form-input" placeholder="z.B. Küche Steckdosen">
</div>
<!-- Anschlussseite: Immer sichtbar (Automaten haben keine feste Richtung) -->
<div id="conn-side-fields" class="form-group">
<label>Anschlussseite</label>
<div id="conn-side-grid" class="side-grid">
<button type="button" class="side-btn selected" data-side="bottom">&#9660; Unten</button>
<button type="button" class="side-btn" data-side="top">&#9650; Oben</button>
</div>
</div>
<!-- Bundle-Option: Nur bei Abgängen + breitem Equipment sichtbar -->
<div id="conn-bundle-fields" class="form-group hidden">
<label class="checkbox-label">
<input type="checkbox" id="conn-bundle-all">
<span>Alle Terminals belegen (Drehstrom-Verbraucher)</span>
</label>
<p class="hint-text">Für E-Herd, Durchlauferhitzer u.ä. ein Abgang belegt alle Klemmen</p>
</div>
<!-- Medium-Felder: Nur bei Abgängen sichtbar -->
<div id="conn-output-fields">
<div class="form-group">
<label>Kabeltyp</label>
<select id="conn-medium-type" class="form-select">
<option value="">-- Auswählen --</option>
<!-- Wird per JS gefüllt -->
</select>
</div>
<div class="form-group">
<label>Querschnitt</label>
<select id="conn-medium-spec" class="form-select">
<option value="">-- Zuerst Kabeltyp wählen --</option>
</select>
</div>
<div class="form-group">
<label>Länge</label>
<input type="text" id="conn-medium-length" class="form-input" placeholder="z.B. 5m">
</div>
</div>
</div>
<div class="modal-footer">
<button id="btn-delete-connection" class="btn btn-danger hidden">Löschen</button>
<div class="modal-footer-right">
<button class="btn btn-secondary modal-close">Abbrechen</button>
<button id="btn-save-connection" class="btn btn-primary">Speichern</button>
</div>
</div>
</div>
</div>
<!-- Hutschiene hinzufügen/bearbeiten Modal -->
<div id="modal-add-carrier" class="modal">
<div class="modal-content modal-small">
<div class="modal-header">
<h2 id="carrier-modal-title">Hutschiene</h2>
<button class="modal-close">&times;</button>
</div>
<div class="modal-body">
<p class="step-label">Größe wählen:</p>
<div class="te-grid">
<button class="te-btn" data-te="12">12 TE</button>
<button class="te-btn" data-te="18">18 TE</button>
<button class="te-btn" data-te="24">24 TE</button>
<button class="te-btn" data-te="36">36 TE</button>
</div>
<div class="form-group" style="margin-top:16px;">
<label>Bezeichnung (optional)</label>
<input type="text" id="carrier-label" placeholder="z.B. Hutschiene 1">
</div>
</div>
<div class="modal-footer">
<button id="btn-delete-carrier" class="btn btn-danger hidden">Löschen</button>
<div class="modal-footer-right">
<button id="btn-save-carrier" class="btn btn-primary">Hinzufügen</button>
</div>
</div>
</div>
</div>
<!-- Feld hinzufügen Modal -->
<div id="modal-add-panel" class="modal">
<div class="modal-content modal-small">
<div class="modal-header">
<h2>Neues Feld</h2>
<button class="modal-close">&times;</button>
</div>
<div class="modal-body">
<div class="form-group">
<label>Bezeichnung</label>
<input type="text" id="panel-label" placeholder="z.B. Feld 1">
</div>
</div>
<div class="modal-footer">
<div class="modal-footer-right">
<button class="btn btn-secondary modal-close">Abbrechen</button>
<button id="btn-save-panel" class="btn btn-primary">Hinzufügen</button>
</div>
</div>
</div>
</div>
<!-- Equipment Detail Bottom-Sheet -->
<div id="sheet-equipment-detail" class="bottom-sheet">
<div class="sheet-overlay"></div>
<div class="sheet-content">
<div class="sheet-handle"></div>
<div class="sheet-header">
<div id="detail-type-badge" class="detail-type-badge"></div>
<div class="sheet-header-text">
<h2 id="detail-title">Equipment</h2>
<p id="detail-type-name" class="detail-subtitle"></p>
</div>
</div>
<div id="detail-body" class="sheet-body">
<!-- Dynamischer Inhalt -->
</div>
<div class="sheet-footer">
<button id="btn-detail-edit" class="btn btn-primary">Bearbeiten</button>
<button id="btn-detail-close" class="btn btn-secondary">Schließen</button>
</div>
</div>
</div>
<!-- Toast Notifications -->
<div id="toast" class="toast"></div>
</div>
<script src="<?php echo DOL_URL_ROOT; ?>/includes/jquery/js/jquery.min.js"></script>
<script>
// Dolibarr URL für AJAX
window.DOLIBARR_URL = '<?php echo DOL_URL_ROOT; ?>';
window.MODULE_URL = '<?php echo DOL_URL_ROOT; ?>/custom/kundenkarte';
</script>
<script src="js/pwa.js?v=5.1"></script>
</body>
</html>