feat(pwa): Equipment-Detail mit Feldlabels, Typ-Kategorien
- 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 <noreply@anthropic.com>
This commit is contained in:
parent
5b4fd16b32
commit
241229659b
3 changed files with 103 additions and 18 deletions
|
|
@ -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;
|
||||
|
||||
// ============================================
|
||||
|
|
|
|||
16
css/pwa.css
16
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);
|
||||
|
|
|
|||
69
js/pwa.js
69
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 += `
|
||||
<button class="type-btn" data-type-id="${type.id}" data-width="${type.width_te || 1}">
|
||||
<div class="type-btn-icon" style="color:${type.color || '#3498db'}">⚡</div>
|
||||
<div class="type-btn-label">${escapeHtml(type.label_short || type.ref || type.label)}</div>
|
||||
</button>
|
||||
`;
|
||||
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 += `<div class="type-grid-category">${escapeHtml(categoryLabels[cat] || cat)}</div>`;
|
||||
groups[cat].forEach(type => {
|
||||
html += `
|
||||
<button class="type-btn" data-type-id="${type.id}" data-width="${type.width_te || 1}">
|
||||
<div class="type-btn-icon" style="color:${type.color || '#3498db'}">⚡</div>
|
||||
<div class="type-btn-label">${escapeHtml(type.label_short || type.ref || type.label)}</div>
|
||||
</button>
|
||||
`;
|
||||
});
|
||||
});
|
||||
$('#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 += '<div class="detail-section">';
|
||||
html += '<div class="detail-section-title">Werte</div>';
|
||||
html += '<div class="detail-field-list">';
|
||||
for (const [key, val] of Object.entries(eq.field_values)) {
|
||||
if (val === '' || val === null || val === undefined) continue;
|
||||
html += `<div class="detail-field-row">
|
||||
<span class="detail-field-label">${escapeHtml(key)}</span>
|
||||
<span class="detail-field-value">${escapeHtml(String(val))}</span>
|
||||
</div>`;
|
||||
|
||||
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 += `<div class="detail-field-row">
|
||||
<span class="detail-field-label">${escapeHtml(fm.label)}</span>
|
||||
<span class="detail-field-value">${escapeHtml(String(val))}</span>
|
||||
</div>`;
|
||||
});
|
||||
} else {
|
||||
// Fallback: Code als Label
|
||||
for (const [key, val] of Object.entries(eq.field_values)) {
|
||||
if (val === '' || val === null || val === undefined) continue;
|
||||
html += `<div class="detail-field-row">
|
||||
<span class="detail-field-label">${escapeHtml(key)}</span>
|
||||
<span class="detail-field-value">${escapeHtml(String(val))}</span>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
html += '</div></div>';
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue