From 241229659bc03124d3637552a16f028c443d1557 Mon Sep 17 00:00:00 2001 From: data Date: Thu, 26 Feb 2026 14:31:43 +0100 Subject: [PATCH] feat(pwa): Equipment-Detail mit Feldlabels, Typ-Kategorien MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - API: field_meta mit Labels/Typ/Optionen pro Equipment-Typ hinzugefügt - Detail-Sheet zeigt jetzt Feld-Labels statt DB-Codes (z.B. "Nennstrom (A)" statt "ampere") - Felder in konfigurierter Reihenfolge (position) angezeigt - Typ-Auswahl nach Kategorien gruppiert (Leitungsschutz, Schutzgeräte, etc.) - Alle Systeme laden statt nur Elektro (fetchAllBySystem(0)) - fieldMeta wird im Offline-Cache mitgespeichert Co-Authored-By: Claude Opus 4.6 --- ajax/pwa_api.php | 36 ++++++++++++++++++++++--- css/pwa.css | 16 +++++++++++ js/pwa.js | 69 +++++++++++++++++++++++++++++++++++++----------- 3 files changed, 103 insertions(+), 18 deletions(-) diff --git a/ajax/pwa_api.php b/ajax/pwa_api.php index eb2535f..62ed4a1 100644 --- a/ajax/pwa_api.php +++ b/ajax/pwa_api.php @@ -279,9 +279,9 @@ switch ($action) { } } - // Equipment-Typen laden (benötigt für Terminal-Position-Auflösung) + // Equipment-Typen laden (benötigt für Terminal-Position-Auflösung + Typ-Auswahl) $eqType = new EquipmentType($db); - $types = $eqType->fetchAllBySystem(1, 1); // System 1 = Elektro, nur aktive + $types = $eqType->fetchAllBySystem(0, 1); // Alle Systeme, nur aktive // Abgänge laden (Connections mit fk_target IS NULL = Ausgänge) $outputsData = array(); @@ -383,10 +383,39 @@ switch ($action) { 'label' => $t->label, 'label_short' => $t->label_short, 'width_te' => $t->width_te, - 'color' => $t->color + 'color' => $t->color, + 'category' => $t->category ); } + // Feld-Metadaten pro Typ laden (Labels, Typ, Optionen) + $fieldMetaData = array(); + $usedTypeIds = array_unique(array_map(function($e) { return (int) $e['fk_equipment_type']; }, $equipmentData)); + if (!empty($usedTypeIds)) { + $sql = "SELECT fk_equipment_type, field_code, field_label, field_type, field_options, show_on_block"; + $sql .= " FROM ".MAIN_DB_PREFIX."kundenkarte_equipment_type_field"; + $sql .= " WHERE fk_equipment_type IN (".implode(',', $usedTypeIds).")"; + $sql .= " AND active = 1"; + $sql .= " ORDER BY position ASC"; + $resql = $db->query($sql); + if ($resql) { + while ($obj = $db->fetch_object($resql)) { + $typeId = (int) $obj->fk_equipment_type; + if (!isset($fieldMetaData[$typeId])) { + $fieldMetaData[$typeId] = array(); + } + $fieldMetaData[$typeId][] = array( + 'code' => $obj->field_code, + 'label' => $obj->field_label, + 'type' => $obj->field_type, + 'options' => $obj->field_options, + 'show_on_block' => (int) $obj->show_on_block + ); + } + $db->free($resql); + } + } + $response['success'] = true; $response['panels'] = $panelsData; $response['carriers'] = $carriersData; @@ -394,6 +423,7 @@ switch ($action) { $response['outputs'] = $outputsData; $response['inputs'] = $inputsData; $response['types'] = $typesData; + $response['field_meta'] = $fieldMetaData; break; // ============================================ diff --git a/css/pwa.css b/css/pwa.css index 543433d..ddb6e68 100644 --- a/css/pwa.css +++ b/css/pwa.css @@ -1277,6 +1277,22 @@ body { text-align: center; } +.type-grid-category { + grid-column: 1 / -1; + font-size: 11px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--colortextlink); + padding: 8px 0 2px; + border-bottom: 1px solid var(--colorborder); + margin-bottom: -2px; +} + +.type-grid-category:first-child { + padding-top: 0; +} + .te-grid { display: grid; grid-template-columns: repeat(2, 1fr); diff --git a/js/pwa.js b/js/pwa.js index a62233c..141acc5 100644 --- a/js/pwa.js +++ b/js/pwa.js @@ -29,6 +29,7 @@ equipmentTypes: [], outputs: [], inputs: [], + fieldMeta: {}, // Offline queue offlineQueue: [], @@ -653,6 +654,7 @@ App.equipmentTypes = response.types || []; App.outputs = response.outputs || []; App.inputs = response.inputs || []; + App.fieldMeta = response.field_meta || {}; // Cache for offline localStorage.setItem('kundenkarte_data_' + App.anlageId, JSON.stringify({ @@ -661,7 +663,8 @@ equipment: App.equipment, types: App.equipmentTypes, outputs: App.outputs, - inputs: App.inputs + inputs: App.inputs, + fieldMeta: App.fieldMeta })); renderEditor(); @@ -677,6 +680,7 @@ App.equipmentTypes = data.types || []; App.outputs = data.outputs || []; App.inputs = data.inputs || []; + App.fieldMeta = data.fieldMeta || {}; renderEditor(); showToast('Offline - Zeige gecachte Daten', 'warning'); } else { @@ -830,14 +834,34 @@ } function renderTypeGrid() { - let html = ''; + const categoryLabels = { + 'automat': 'Leitungsschutz', + 'schutz': 'Schutzgeräte', + 'steuerung': 'Steuerung & Sonstiges', + 'klemme': 'Klemmen' + }; + const categoryOrder = ['automat', 'schutz', 'steuerung', 'klemme']; + + // Typen nach Kategorie gruppieren + const groups = {}; App.equipmentTypes.forEach(type => { - html += ` - - `; + const cat = type.category || 'steuerung'; + if (!groups[cat]) groups[cat] = []; + groups[cat].push(type); + }); + + let html = ''; + categoryOrder.forEach(cat => { + if (!groups[cat] || !groups[cat].length) return; + html += `
${escapeHtml(categoryLabels[cat] || cat)}
`; + groups[cat].forEach(type => { + html += ` + + `; + }); }); $('#type-grid').html(html); } @@ -1461,17 +1485,32 @@ // Body zusammenbauen let html = ''; - // Feldwerte + // Feldwerte mit Labels aus Feld-Metadaten if (eq.field_values && Object.keys(eq.field_values).length) { + const typeMeta = App.fieldMeta ? App.fieldMeta[eq.fk_equipment_type] : null; html += '
'; html += '
Werte
'; html += '
'; - for (const [key, val] of Object.entries(eq.field_values)) { - if (val === '' || val === null || val === undefined) continue; - html += `
- ${escapeHtml(key)} - ${escapeHtml(String(val))} -
`; + + if (typeMeta && typeMeta.length) { + // Felder in der konfigurierten Reihenfolge anzeigen + typeMeta.forEach(function(fm) { + const val = eq.field_values[fm.code]; + if (val === '' || val === null || val === undefined) return; + html += `
+ ${escapeHtml(fm.label)} + ${escapeHtml(String(val))} +
`; + }); + } else { + // Fallback: Code als Label + for (const [key, val] of Object.entries(eq.field_values)) { + if (val === '' || val === null || val === undefined) continue; + html += `
+ ${escapeHtml(key)} + ${escapeHtml(String(val))} +
`; + } } html += '
'; }