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);
|
$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)
|
// Abgänge laden (Connections mit fk_target IS NULL = Ausgänge)
|
||||||
$outputsData = array();
|
$outputsData = array();
|
||||||
|
|
@ -383,10 +383,39 @@ switch ($action) {
|
||||||
'label' => $t->label,
|
'label' => $t->label,
|
||||||
'label_short' => $t->label_short,
|
'label_short' => $t->label_short,
|
||||||
'width_te' => $t->width_te,
|
'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['success'] = true;
|
||||||
$response['panels'] = $panelsData;
|
$response['panels'] = $panelsData;
|
||||||
$response['carriers'] = $carriersData;
|
$response['carriers'] = $carriersData;
|
||||||
|
|
@ -394,6 +423,7 @@ switch ($action) {
|
||||||
$response['outputs'] = $outputsData;
|
$response['outputs'] = $outputsData;
|
||||||
$response['inputs'] = $inputsData;
|
$response['inputs'] = $inputsData;
|
||||||
$response['types'] = $typesData;
|
$response['types'] = $typesData;
|
||||||
|
$response['field_meta'] = $fieldMetaData;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
|
||||||
16
css/pwa.css
16
css/pwa.css
|
|
@ -1277,6 +1277,22 @@ body {
|
||||||
text-align: center;
|
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 {
|
.te-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, 1fr);
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
|
|
||||||
45
js/pwa.js
45
js/pwa.js
|
|
@ -29,6 +29,7 @@
|
||||||
equipmentTypes: [],
|
equipmentTypes: [],
|
||||||
outputs: [],
|
outputs: [],
|
||||||
inputs: [],
|
inputs: [],
|
||||||
|
fieldMeta: {},
|
||||||
|
|
||||||
// Offline queue
|
// Offline queue
|
||||||
offlineQueue: [],
|
offlineQueue: [],
|
||||||
|
|
@ -653,6 +654,7 @@
|
||||||
App.equipmentTypes = response.types || [];
|
App.equipmentTypes = response.types || [];
|
||||||
App.outputs = response.outputs || [];
|
App.outputs = response.outputs || [];
|
||||||
App.inputs = response.inputs || [];
|
App.inputs = response.inputs || [];
|
||||||
|
App.fieldMeta = response.field_meta || {};
|
||||||
|
|
||||||
// Cache for offline
|
// Cache for offline
|
||||||
localStorage.setItem('kundenkarte_data_' + App.anlageId, JSON.stringify({
|
localStorage.setItem('kundenkarte_data_' + App.anlageId, JSON.stringify({
|
||||||
|
|
@ -661,7 +663,8 @@
|
||||||
equipment: App.equipment,
|
equipment: App.equipment,
|
||||||
types: App.equipmentTypes,
|
types: App.equipmentTypes,
|
||||||
outputs: App.outputs,
|
outputs: App.outputs,
|
||||||
inputs: App.inputs
|
inputs: App.inputs,
|
||||||
|
fieldMeta: App.fieldMeta
|
||||||
}));
|
}));
|
||||||
|
|
||||||
renderEditor();
|
renderEditor();
|
||||||
|
|
@ -677,6 +680,7 @@
|
||||||
App.equipmentTypes = data.types || [];
|
App.equipmentTypes = data.types || [];
|
||||||
App.outputs = data.outputs || [];
|
App.outputs = data.outputs || [];
|
||||||
App.inputs = data.inputs || [];
|
App.inputs = data.inputs || [];
|
||||||
|
App.fieldMeta = data.fieldMeta || {};
|
||||||
renderEditor();
|
renderEditor();
|
||||||
showToast('Offline - Zeige gecachte Daten', 'warning');
|
showToast('Offline - Zeige gecachte Daten', 'warning');
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -830,8 +834,27 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderTypeGrid() {
|
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 => {
|
App.equipmentTypes.forEach(type => {
|
||||||
|
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 += `
|
html += `
|
||||||
<button class="type-btn" data-type-id="${type.id}" data-width="${type.width_te || 1}">
|
<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-icon" style="color:${type.color || '#3498db'}">⚡</div>
|
||||||
|
|
@ -839,6 +862,7 @@
|
||||||
</button>
|
</button>
|
||||||
`;
|
`;
|
||||||
});
|
});
|
||||||
|
});
|
||||||
$('#type-grid').html(html);
|
$('#type-grid').html(html);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1461,11 +1485,25 @@
|
||||||
// Body zusammenbauen
|
// Body zusammenbauen
|
||||||
let html = '';
|
let html = '';
|
||||||
|
|
||||||
// Feldwerte
|
// Feldwerte mit Labels aus Feld-Metadaten
|
||||||
if (eq.field_values && Object.keys(eq.field_values).length) {
|
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">';
|
||||||
html += '<div class="detail-section-title">Werte</div>';
|
html += '<div class="detail-section-title">Werte</div>';
|
||||||
html += '<div class="detail-field-list">';
|
html += '<div class="detail-field-list">';
|
||||||
|
|
||||||
|
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)) {
|
for (const [key, val] of Object.entries(eq.field_values)) {
|
||||||
if (val === '' || val === null || val === undefined) continue;
|
if (val === '' || val === null || val === undefined) continue;
|
||||||
html += `<div class="detail-field-row">
|
html += `<div class="detail-field-row">
|
||||||
|
|
@ -1473,6 +1511,7 @@
|
||||||
<span class="detail-field-value">${escapeHtml(String(val))}</span>
|
<span class="detail-field-value">${escapeHtml(String(val))}</span>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
html += '</div></div>';
|
html += '</div></div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue