feat(pwa): Equipment-Detail Bottom-Sheet
Tipp auf Equipment-Block zeigt jetzt Detail-Ansicht statt direkt den Bearbeiten-Dialog zu öffnen. Bottom-Sheet mit: - Typ-Badge + Bezeichnung - Alle Feldwerte - Abgänge mit Phasenfarben und Medium-Info - Einspeisungen - Position (Hutschiene + TE) - "Bearbeiten"-Button öffnet Edit-Dialog Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4fb9d8e472
commit
dcd00fe844
4 changed files with 352 additions and 11 deletions
203
css/pwa.css
203
css/pwa.css
|
|
@ -1491,6 +1491,209 @@ body {
|
||||||
color: #000;
|
color: #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
EQUIPMENT DETAIL BOTTOM-SHEET
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
.bottom-sheet {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 1100;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-sheet.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sheet-overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
animation: fadeIn 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sheet-content {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
max-height: 80vh;
|
||||||
|
background: var(--colorbackbody);
|
||||||
|
border-top-left-radius: 16px;
|
||||||
|
border-top-right-radius: 16px;
|
||||||
|
overflow-y: auto;
|
||||||
|
animation: slideUp 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideUp {
|
||||||
|
from { transform: translateY(100%); }
|
||||||
|
to { transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.sheet-handle {
|
||||||
|
width: 40px;
|
||||||
|
height: 4px;
|
||||||
|
background: var(--colorborder);
|
||||||
|
border-radius: 2px;
|
||||||
|
margin: 10px auto 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sheet-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
border-bottom: 1px solid var(--colorborder);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-type-badge {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #fff;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sheet-header-text {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sheet-header-text h2 {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 0;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-subtitle {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--colortextmuted);
|
||||||
|
margin: 2px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sheet-body {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-section {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-section-title {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
color: var(--colortextmuted);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-field-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-field-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: baseline;
|
||||||
|
padding: 6px 10px;
|
||||||
|
background: var(--colorbackinput);
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-field-label {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--colortextmuted);
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-field-value {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-align: right;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-field-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Verbindungsliste */
|
||||||
|
.detail-conn-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-conn-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
background: var(--colorbackinput);
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-conn-dot {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-conn-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-conn-label {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-conn-meta {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--colortextmuted);
|
||||||
|
margin-top: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-conn-arrow {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--colortextmuted);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sheet-footer {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 16px;
|
||||||
|
border-top: 1px solid var(--colorborder);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sheet-footer .btn {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* ============================================
|
/* ============================================
|
||||||
UTILITIES
|
UTILITIES
|
||||||
============================================ */
|
============================================ */
|
||||||
|
|
|
||||||
130
js/pwa.js
130
js/pwa.js
|
|
@ -194,6 +194,11 @@
|
||||||
$('#btn-cancel-equipment').on('click', () => closeModal('add-equipment'));
|
$('#btn-cancel-equipment').on('click', () => closeModal('add-equipment'));
|
||||||
$('#btn-delete-equipment').on('click', handleDeleteEquipmentConfirm);
|
$('#btn-delete-equipment').on('click', handleDeleteEquipmentConfirm);
|
||||||
|
|
||||||
|
// Equipment Detail Bottom-Sheet
|
||||||
|
$('#btn-detail-edit').on('click', openEditFromDetail);
|
||||||
|
$('#btn-detail-close').on('click', () => $('#sheet-equipment-detail').removeClass('active'));
|
||||||
|
$('#sheet-equipment-detail .sheet-overlay').on('click', () => $('#sheet-equipment-detail').removeClass('active'));
|
||||||
|
|
||||||
// Terminal/Connection - Klick auf einzelne Klemme
|
// Terminal/Connection - Klick auf einzelne Klemme
|
||||||
$('#editor-content').on('click', '.terminal-point', handleTerminalClick);
|
$('#editor-content').on('click', '.terminal-point', handleTerminalClick);
|
||||||
$('#btn-save-connection').on('click', handleSaveConnection);
|
$('#btn-save-connection').on('click', handleSaveConnection);
|
||||||
|
|
@ -1432,30 +1437,141 @@
|
||||||
App.editEquipmentId = null;
|
App.editEquipmentId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleEquipmentClick() {
|
function handleEquipmentClick() {
|
||||||
const eqId = $(this).data('equipment-id');
|
const eqId = $(this).data('equipment-id');
|
||||||
const eq = App.equipment.find(e => e.id == eqId);
|
const eq = App.equipment.find(e => e.id == eqId);
|
||||||
if (!eq) return;
|
if (!eq) return;
|
||||||
|
|
||||||
|
showEquipmentDetail(eq);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Equipment-Detail Bottom-Sheet anzeigen
|
||||||
|
*/
|
||||||
|
function showEquipmentDetail(eq) {
|
||||||
|
const type = App.equipmentTypes.find(t => t.id == eq.fk_equipment_type);
|
||||||
|
const typeLabel = type?.label || type?.ref || 'Equipment';
|
||||||
|
const typeLabelShort = type?.label_short || type?.ref || '?';
|
||||||
|
const typeColor = eq.block_color || type?.color || '#3498db';
|
||||||
|
|
||||||
|
// Header
|
||||||
|
$('#detail-type-badge').css('background', typeColor).text(typeLabelShort);
|
||||||
|
$('#detail-title').text(eq.label || 'Automat ' + eq.id);
|
||||||
|
$('#detail-type-name').text(typeLabel);
|
||||||
|
|
||||||
|
// Body zusammenbauen
|
||||||
|
let html = '';
|
||||||
|
|
||||||
|
// Feldwerte
|
||||||
|
if (eq.field_values && Object.keys(eq.field_values).length) {
|
||||||
|
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>`;
|
||||||
|
}
|
||||||
|
html += '</div></div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Abgänge (Outputs)
|
||||||
|
const outputs = App.outputs ? App.outputs.filter(o => o.fk_source == eq.id) : [];
|
||||||
|
if (outputs.length) {
|
||||||
|
html += '<div class="detail-section">';
|
||||||
|
html += '<div class="detail-section-title">Abgänge</div>';
|
||||||
|
html += '<div class="detail-conn-list">';
|
||||||
|
outputs.forEach(o => {
|
||||||
|
const color = o.color || getPhaseColor(o.connection_type);
|
||||||
|
const label = o.output_label || o.connection_type || 'Abgang';
|
||||||
|
const meta = [o.medium_type, o.medium_spec, o.medium_length].filter(Boolean).join(' · ');
|
||||||
|
const arrow = o.is_top ? '▲' : '▼';
|
||||||
|
html += `<div class="detail-conn-item">
|
||||||
|
<span class="detail-conn-dot" style="background:${color}"></span>
|
||||||
|
<div class="detail-conn-info">
|
||||||
|
<div class="detail-conn-label">${escapeHtml(label)}</div>
|
||||||
|
${meta ? '<div class="detail-conn-meta">' + escapeHtml(meta) + '</div>' : ''}
|
||||||
|
</div>
|
||||||
|
<span class="detail-conn-arrow">${arrow}</span>
|
||||||
|
</div>`;
|
||||||
|
});
|
||||||
|
html += '</div></div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Einspeisungen (Inputs)
|
||||||
|
const inputs = App.inputs ? App.inputs.filter(i => i.fk_target == eq.id) : [];
|
||||||
|
if (inputs.length) {
|
||||||
|
html += '<div class="detail-section">';
|
||||||
|
html += '<div class="detail-section-title">Einspeisungen</div>';
|
||||||
|
html += '<div class="detail-conn-list">';
|
||||||
|
inputs.forEach(i => {
|
||||||
|
const color = i.color || getPhaseColor(i.connection_type);
|
||||||
|
const label = i.output_label || i.connection_type || 'Einspeisung';
|
||||||
|
html += `<div class="detail-conn-item">
|
||||||
|
<span class="detail-conn-dot" style="background:${color}"></span>
|
||||||
|
<div class="detail-conn-info">
|
||||||
|
<div class="detail-conn-label">${escapeHtml(label)}</div>
|
||||||
|
</div>
|
||||||
|
<span class="detail-conn-arrow">▼</span>
|
||||||
|
</div>`;
|
||||||
|
});
|
||||||
|
html += '</div></div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Position-Info
|
||||||
|
const carrier = App.carriers.find(c => c.id == eq.fk_carrier);
|
||||||
|
html += '<div class="detail-section">';
|
||||||
|
html += '<div class="detail-section-title">Position</div>';
|
||||||
|
html += '<div class="detail-field-list">';
|
||||||
|
if (carrier) {
|
||||||
|
html += `<div class="detail-field-row">
|
||||||
|
<span class="detail-field-label">Hutschiene</span>
|
||||||
|
<span class="detail-field-value">${escapeHtml(carrier.label || 'Hutschiene')}</span>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
html += `<div class="detail-field-row">
|
||||||
|
<span class="detail-field-label">TE-Position</span>
|
||||||
|
<span class="detail-field-value">${eq.position_te || '–'} (${eq.width_te || 1} TE breit)</span>
|
||||||
|
</div>`;
|
||||||
|
html += '</div></div>';
|
||||||
|
|
||||||
|
if (!html) {
|
||||||
|
html = '<p class="text-muted text-center">Keine Details vorhanden</p>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#detail-body').html(html);
|
||||||
|
|
||||||
|
// Bearbeiten-Button: Equipment-ID merken
|
||||||
|
App.detailEquipmentId = eq.id;
|
||||||
|
|
||||||
|
$('#sheet-equipment-detail').addClass('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detail-Sheet schließen und Edit-Modal öffnen
|
||||||
|
*/
|
||||||
|
async function openEditFromDetail() {
|
||||||
|
const eqId = App.detailEquipmentId;
|
||||||
|
$('#sheet-equipment-detail').removeClass('active');
|
||||||
|
|
||||||
|
const eq = App.equipment.find(e => e.id == eqId);
|
||||||
|
if (!eq) return;
|
||||||
|
|
||||||
const type = App.equipmentTypes.find(t => t.id == eq.fk_equipment_type);
|
const type = App.equipmentTypes.find(t => t.id == eq.fk_equipment_type);
|
||||||
|
|
||||||
// Edit-Modus setzen
|
|
||||||
App.editEquipmentId = eqId;
|
App.editEquipmentId = eqId;
|
||||||
App.currentCarrierId = eq.fk_carrier;
|
App.currentCarrierId = eq.fk_carrier;
|
||||||
App.selectedTypeId = eq.fk_equipment_type;
|
App.selectedTypeId = eq.fk_equipment_type;
|
||||||
|
|
||||||
// Titel + Buttons anpassen
|
|
||||||
$('#eq-fields-title').text(type?.label_short || type?.label || 'Bearbeiten');
|
$('#eq-fields-title').text(type?.label_short || type?.label || 'Bearbeiten');
|
||||||
$('#btn-save-equipment').text('Aktualisieren');
|
$('#btn-save-equipment').text('Aktualisieren');
|
||||||
$('#btn-delete-equipment').removeClass('hidden');
|
$('#btn-delete-equipment').removeClass('hidden');
|
||||||
|
|
||||||
// Label befüllen
|
|
||||||
$('#equipment-label').val(eq.label || '');
|
$('#equipment-label').val(eq.label || '');
|
||||||
|
|
||||||
// Felder vom Server laden (mit bestehenden Werten)
|
|
||||||
await loadTypeFields(eq.fk_equipment_type, eqId);
|
await loadTypeFields(eq.fk_equipment_type, eqId);
|
||||||
|
|
||||||
// Direkt Schritt 2 zeigen (kein Typ-Wechsel beim Edit)
|
|
||||||
showEquipmentStep('fields');
|
showEquipmentStep('fields');
|
||||||
openModal('add-equipment');
|
openModal('add-equipment');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
26
pwa.php
26
pwa.php
|
|
@ -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=2.8">
|
<link rel="stylesheet" href="css/pwa.css?v=2.9">
|
||||||
<style>:root { --primary: <?php echo $themeColor; ?>; }</style>
|
<style>:root { --primary: <?php echo $themeColor; ?>; }</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
@ -313,6 +313,28 @@ $themeColor = getDolGlobalString('THEME_ELDY_TOPMENU_BACK1', '#3498db');
|
||||||
</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 -->
|
<!-- Toast Notifications -->
|
||||||
<div id="toast" class="toast"></div>
|
<div id="toast" class="toast"></div>
|
||||||
|
|
||||||
|
|
@ -324,6 +346,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=2.8"></script>
|
<script src="js/pwa.js?v=2.9"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
4
sw.js
4
sw.js
|
|
@ -3,8 +3,8 @@
|
||||||
* Offline-First für Schaltschrank-Dokumentation
|
* Offline-First für Schaltschrank-Dokumentation
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const CACHE_NAME = 'kundenkarte-pwa-v2.8';
|
const CACHE_NAME = 'kundenkarte-pwa-v2.9';
|
||||||
const OFFLINE_CACHE = 'kundenkarte-offline-v2.8';
|
const OFFLINE_CACHE = 'kundenkarte-offline-v2.9';
|
||||||
|
|
||||||
// Statische Assets die immer gecached werden
|
// Statische Assets die immer gecached werden
|
||||||
const STATIC_ASSETS = [
|
const STATIC_ASSETS = [
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue