feat: Graph-Toggle, Baum-Farben, PWA-Baumansicht

- Graph-Integration von feature/cytoscape-graph auf main portiert
  (anlagen.php + contact_anlagen.php: View-Mode, Toolbar, Container)
- Baum-Knoten farblich unterschieden: grün=Gebäude, blau=Equipment, orange=Endgerät
  (CSS border-left + Icon-Farbe je nach can_have_children/can_have_equipment)
- PWA: Kompletter Anlagen-Baum statt flache Liste
  (API liefert rekursiven Baum, Frontend mit aufklappbaren Knoten)
- PWA: Equipment-Container öffnen Editor, Strukturknoten klappen auf/zu
- Connection-URLs in contact_anlagen.php: contactid Parameter ergänzt

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Eduard Wisch 2026-02-26 12:09:13 +01:00
parent da4ed40ad2
commit 143ddcb958
9 changed files with 511 additions and 233 deletions

View file

@ -148,25 +148,10 @@ switch ($action) {
$db->free($resFields); $db->free($resFields);
} }
// Root-Anlagen ohne Kontaktzuweisung (Kunden-Ebene) // Kompletter Baum ohne Kontaktzuweisung (Kunden-Ebene)
$anlage = new Anlage($db); $anlage = new Anlage($db);
$anlagen = $anlage->fetchChildren(0, $customerId); $tree = $anlage->fetchTree($customerId, 0);
$result = pwaTreeToArray($tree, $fieldMeta);
$result = array();
foreach ($anlagen as $a) {
$item = array(
'id' => $a->id,
'label' => $a->label,
'type' => $a->type_label,
'has_editor' => !empty($a->schematic_editor_enabled)
);
// Feld-Badges hinzufügen
$fields = pwaGetAnlageFields($a, $fieldMeta);
if (!empty($fields)) {
$item['fields'] = $fields;
}
$result[] = $item;
}
// Kontakt-Adressen mit Anlagen laden // Kontakt-Adressen mit Anlagen laden
$contacts = array(); $contacts = array();
@ -229,22 +214,8 @@ switch ($action) {
} }
$anlage = new Anlage($db); $anlage = new Anlage($db);
$anlagen = $anlage->fetchChildrenByContact(0, $customerId, $contactId); $tree = $anlage->fetchTreeByContact($customerId, $contactId, 0);
$result = pwaTreeToArray($tree, $fieldMeta);
$result = array();
foreach ($anlagen as $a) {
$item = array(
'id' => $a->id,
'label' => $a->label,
'type' => $a->type_label,
'has_editor' => !empty($a->schematic_editor_enabled)
);
$fields = pwaGetAnlageFields($a, $fieldMeta);
if (!empty($fields)) {
$item['fields'] = $fields;
}
$result[] = $item;
}
$response['success'] = true; $response['success'] = true;
$response['anlagen'] = $result; $response['anlagen'] = $result;
@ -931,3 +902,37 @@ function pwaGetAnlageFields($anlage, $fieldMeta) {
} }
return $result; return $result;
} }
/**
* Anlagen-Baum rekursiv in JSON-Array umwandeln
*
* @param array $nodes Array von Anlage-Objekten mit ->children
* @param array $fieldMeta Feld-Metadaten [typeId][code] = {label, display, color}
* @return array JSON-serialisierbares Array mit children
*/
function pwaTreeToArray($nodes, $fieldMeta) {
$result = array();
foreach ($nodes as $node) {
$item = array(
'id' => $node->id,
'label' => $node->label,
'type' => $node->type_label,
'can_have_equipment' => !empty($node->type_can_have_equipment),
'can_have_children' => !empty($node->type_can_have_children),
);
// Feld-Badges
$fields = pwaGetAnlageFields($node, $fieldMeta);
if (!empty($fields)) {
$item['fields'] = $fields;
}
// Kinder rekursiv
if (!empty($node->children)) {
$item['children'] = pwaTreeToArray($node->children, $fieldMeta);
}
$result[] = $item;
}
return $result;
}

View file

@ -227,6 +227,8 @@ class Anlage extends CommonObject
$this->type_label = $obj->type_label; $this->type_label = $obj->type_label;
$this->type_short = $obj->type_short; $this->type_short = $obj->type_short;
$this->type_picto = $obj->type_picto; $this->type_picto = $obj->type_picto;
$this->type_can_have_children = isset($obj->type_can_have_children) ? (int) $obj->type_can_have_children : 0;
$this->type_can_have_equipment = isset($obj->type_can_have_equipment) ? (int) $obj->type_can_have_equipment : 0;
// System info // System info
$this->system_label = $obj->system_label; $this->system_label = $obj->system_label;
@ -400,6 +402,7 @@ class Anlage extends CommonObject
$results = array(); $results = array();
$sql = "SELECT a.*, t.label as type_label, t.label_short as type_short, t.picto as type_picto,"; $sql = "SELECT a.*, t.label as type_label, t.label_short as type_short, t.picto as type_picto,";
$sql .= " t.can_have_children as type_can_have_children, t.can_have_equipment as type_can_have_equipment,";
$sql .= " s.label as system_label, s.code as system_code,"; $sql .= " s.label as system_label, s.code as system_code,";
// Count images // Count images
$sql .= " (SELECT COUNT(*) FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_files f WHERE f.fk_anlage = a.rowid AND f.file_type = 'image') as image_count,"; $sql .= " (SELECT COUNT(*) FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_files f WHERE f.fk_anlage = a.rowid AND f.file_type = 'image') as image_count,";
@ -709,6 +712,7 @@ class Anlage extends CommonObject
$results = array(); $results = array();
$sql = "SELECT a.*, t.label as type_label, t.label_short as type_short, t.picto as type_picto,"; $sql = "SELECT a.*, t.label as type_label, t.label_short as type_short, t.picto as type_picto,";
$sql .= " t.can_have_children as type_can_have_children, t.can_have_equipment as type_can_have_equipment,";
$sql .= " s.label as system_label, s.code as system_code,"; $sql .= " s.label as system_label, s.code as system_code,";
// Count images // Count images
$sql .= " (SELECT COUNT(*) FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_files f WHERE f.fk_anlage = a.rowid AND f.file_type = 'image') as image_count,"; $sql .= " (SELECT COUNT(*) FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_files f WHERE f.fk_anlage = a.rowid AND f.file_type = 'image') as image_count,";

View file

@ -336,6 +336,31 @@ body.kundenkarte-drag-active * {
font-weight: normal !important; font-weight: normal !important;
} }
/* Tree - Typ-Kategorie Farben */
/* Gebäudeteile (Struktur): grüner Akzent links */
.node-structure > .kundenkarte-tree-item {
border-left: 3px solid #4caf50 !important;
}
.node-structure > .kundenkarte-tree-item .kundenkarte-tree-icon {
color: #4caf50 !important;
}
/* Geräte-Container (can_have_equipment): blauer Akzent links */
.node-equipment > .kundenkarte-tree-item {
border-left: 3px solid #2196f3 !important;
}
.node-equipment > .kundenkarte-tree-item .kundenkarte-tree-icon {
color: #2196f3 !important;
}
/* Endgeräte/Blätter: oranger Akzent links */
.node-leaf > .kundenkarte-tree-item {
border-left: 3px solid #ff9800 !important;
}
.node-leaf > .kundenkarte-tree-item .kundenkarte-tree-icon {
color: #ff9800 !important;
}
/* Tree - File Indicators */ /* Tree - File Indicators */
.kundenkarte-tree-files { .kundenkarte-tree-files {
display: inline-flex !important; display: inline-flex !important;

View file

@ -438,14 +438,14 @@ body {
} }
/* ============================================ /* ============================================
ANLAGEN GRID ANLAGEN BAUM
============================================ */ ============================================ */
.anlagen-grid { .anlagen-grid {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 6px; gap: 0;
padding: 12px; padding: 8px 0;
} }
/* Trennlabel Kunden-Adresse */ /* Trennlabel Kunden-Adresse */
@ -453,83 +453,132 @@ body {
font-size: 12px; font-size: 12px;
font-weight: 600; font-weight: 600;
color: var(--colortextmuted); color: var(--colortextmuted);
padding: 8px 4px 2px; padding: 8px 12px 2px;
border-top: 1px solid var(--colorborder); border-top: 1px solid var(--colorborder);
margin-top: 4px; margin-top: 4px;
} }
.anlage-card { /* Baum-Knoten */
.pwa-tree-node {
/* Container für Knoten + Kinder */
}
.pwa-tree-row {
display: flex; display: flex;
flex-direction: row;
align-items: center; align-items: center;
gap: 12px; gap: 8px;
padding: 14px; padding: 10px 12px;
background: var(--colorbackline); border-bottom: 1px solid var(--colorborder);
border: 1px solid var(--colorborder);
border-radius: 8px;
cursor: pointer; cursor: pointer;
transition: all 0.2s; transition: background 0.15s;
} }
.anlage-card:active { .pwa-tree-row:active {
background: var(--colorbackinput); background: var(--colorbackinput);
transform: scale(0.98);
} }
.anlage-card-icon { /* Toggle-Chevron */
width: 44px; .pwa-tree-toggle {
height: 44px; width: 20px;
background: var(--success); height: 20px;
border-radius: 10px; fill: var(--colortextmuted);
flex-shrink: 0;
transition: transform 0.2s;
}
.pwa-tree-toggle-spacer {
width: 20px;
flex-shrink: 0;
}
.pwa-tree-node.expanded > .pwa-tree-row > .pwa-tree-toggle {
transform: rotate(90deg);
}
/* Icon je nach Typ */
.pwa-tree-icon {
width: 32px;
height: 32px;
border-radius: 6px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
flex-shrink: 0; flex-shrink: 0;
} }
.anlage-card-icon svg { .pwa-tree-icon svg {
width: 24px; width: 18px;
height: 24px; height: 18px;
fill: #fff; fill: #fff;
} }
.anlage-card-title { .pwa-tree-icon.node-structure {
font-size: 15px; background: #4caf50;
}
.pwa-tree-icon.node-equipment {
background: #2196f3;
}
.pwa-tree-icon.node-leaf {
background: #ff9800;
}
/* Inhalt */
.pwa-tree-content {
flex: 1;
min-width: 0;
}
.pwa-tree-label {
font-size: 14px;
font-weight: 600; font-weight: 600;
word-break: break-word; word-break: break-word;
line-height: 1.3; line-height: 1.3;
} }
.anlage-card-content { .pwa-tree-type {
flex: 1; font-size: 11px;
min-width: 0;
}
.anlage-card-type {
font-size: 12px;
color: var(--colortextmuted); color: var(--colortextmuted);
margin-top: 2px; margin-top: 1px;
} }
.anlage-card-arrow { /* Editor-Pfeil für Equipment-Container */
width: 20px; .pwa-tree-open {
height: 20px; width: 24px;
height: 24px;
fill: var(--colortextmuted); fill: var(--colortextmuted);
flex-shrink: 0; flex-shrink: 0;
} }
.node-equipment > .pwa-tree-row .pwa-tree-open {
fill: #2196f3;
}
/* Kinder (eingeklappt) */
.pwa-tree-children {
display: none;
border-left: 2px solid var(--colorborder);
margin-left: 22px;
}
.pwa-tree-node.expanded > .pwa-tree-children {
display: block;
}
/* Feld-Badges (werden wiederverwendet) */
.anlage-card-fields { .anlage-card-fields {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 4px; gap: 4px;
margin-top: 6px; margin-top: 4px;
} }
.anlage-field-badge { .anlage-field-badge {
font-size: 11px; font-size: 10px;
font-weight: 600; font-weight: 600;
padding: 2px 8px; padding: 1px 6px;
border-radius: 4px; border-radius: 3px;
background: var(--colorbackinput); background: var(--colorbackinput);
color: #fff; color: #fff;
white-space: nowrap; white-space: nowrap;
@ -1508,7 +1557,7 @@ body {
.te-btn, .te-btn,
.list-item, .list-item,
.contact-group-header, .contact-group-header,
.anlage-card { .pwa-tree-row {
min-height: 48px; min-height: 48px;
} }

138
js/pwa.js
View file

@ -157,7 +157,7 @@
if (e.state && e.state.screen) { if (e.state && e.state.screen) {
showScreen(e.state.screen, true); showScreen(e.state.screen, true);
// Anlagen-Liste nachladen falls leer (z.B. nach Seiten-Refresh) // Anlagen-Liste nachladen falls leer (z.B. nach Seiten-Refresh)
if (e.state.screen === 'anlagen' && App.customerId && !$('#anlagen-list').children('.anlage-card, .contact-group').length) { if (e.state.screen === 'anlagen' && App.customerId && !$('#anlagen-list').children('.pwa-tree-node, .contact-list, .contact-group').length) {
reloadAnlagen(); reloadAnlagen();
} }
} else { } else {
@ -172,7 +172,7 @@
// Customer/Anlage selection // Customer/Anlage selection
$('#customer-list').on('click', '.list-item', handleCustomerSelect); $('#customer-list').on('click', '.list-item', handleCustomerSelect);
$('#anlagen-list').on('click', '.anlage-card', handleAnlageSelect); $('#anlagen-list').on('click', '.pwa-tree-row', handleTreeNodeClick);
$('#anlagen-list').on('click', '.contact-group-header', handleContactGroupClick); $('#anlagen-list').on('click', '.contact-group-header', handleContactGroupClick);
// Editor actions // Editor actions
@ -469,14 +469,12 @@
html += '</div>'; html += '</div>';
} }
// Kunden-Anlagen (ohne Kontaktzuweisung) darunter // Kunden-Anlagen (ohne Kontaktzuweisung) als Baum darunter
if (anlagen && anlagen.length) { if (anlagen && anlagen.length) {
if (contacts && contacts.length && App.customerAddress) { if (contacts && contacts.length && App.customerAddress) {
html += `<div class="anlagen-section-label">${escapeHtml(App.customerName)} ${escapeHtml(App.customerAddress)}</div>`; html += `<div class="anlagen-section-label">${escapeHtml(App.customerName)} ${escapeHtml(App.customerAddress)}</div>`;
} }
anlagen.forEach(a => { html += renderTreeNodes(anlagen, 0);
html += renderAnlageCard(a);
});
} }
if (!html) { if (!html) {
@ -487,35 +485,105 @@
$('#anlagen-list').html(html); $('#anlagen-list').html(html);
} }
function renderAnlageCard(a) { // Baum-Knoten rekursiv rendern
let fieldsHtml = ''; function renderTreeNodes(nodes, level) {
if (a.fields && a.fields.length) { let html = '';
fieldsHtml = '<div class="anlage-card-fields">'; nodes.forEach(a => {
a.fields.forEach(f => { const hasChildren = a.children && a.children.length > 0;
const style = f.color ? ` style="background:${f.color}"` : ''; const isEquipment = a.can_have_equipment;
fieldsHtml += `<span class="anlage-field-badge"${style}>${escapeHtml(f.value)}</span>`; const isStructure = a.can_have_children && !isEquipment;
});
fieldsHtml += '</div>'; // Typ-Klasse für farbliche Unterscheidung
} let typeClass = 'node-leaf';
return ` if (isEquipment) typeClass = 'node-equipment';
<div class="anlage-card" data-id="${a.id}"> else if (isStructure) typeClass = 'node-structure';
<div class="anlage-card-icon">
<svg viewBox="0 0 24 24"><path d="M21 3H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h18v14zM9 7H7v2h2V7zm0 4H7v2h2v-2zm0 4H7v2h2v-2zm8-8h-6v2h6V7zm0 4h-6v2h6v-2zm0 4h-6v2h6v-2z"/></svg> // Feld-Badges
</div> let fieldsHtml = '';
<div class="anlage-card-content"> if (a.fields && a.fields.length) {
<div class="anlage-card-title">${escapeHtml(a.label || 'Anlage ' + a.id)}</div> fieldsHtml = '<div class="anlage-card-fields">';
${a.type ? '<div class="anlage-card-type">' + escapeHtml(a.type) + '</div>' : ''} a.fields.forEach(f => {
${fieldsHtml} const style = f.color ? ` style="background:${f.color}"` : '';
</div> fieldsHtml += `<span class="anlage-field-badge"${style}>${escapeHtml(f.value)}</span>`;
<svg class="anlage-card-arrow" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg> });
</div> fieldsHtml += '</div>';
`; }
// Icons je nach Typ
let iconSvg;
if (isEquipment) {
// Schaltschrank/Verteiler
iconSvg = '<svg viewBox="0 0 24 24"><path d="M21 3H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h18v14zM9 7H7v2h2V7zm0 4H7v2h2v-2zm0 4H7v2h2v-2zm8-8h-6v2h6V7zm0 4h-6v2h6v-2zm0 4h-6v2h6v-2z"/></svg>';
} else if (isStructure) {
// Gebäude/Raum
iconSvg = '<svg viewBox="0 0 24 24"><path d="M12 3L2 12h3v8h6v-6h2v6h6v-8h3L12 3zm0 2.84L18 12v7h-2v-6H8v6H6v-7l6-6.16z"/></svg>';
} else {
// Endgerät
iconSvg = '<svg viewBox="0 0 24 24"><path d="M20 18c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2H0v2h24v-2h-4zM4 6h16v10H4V6z"/></svg>';
}
html += `<div class="pwa-tree-node ${typeClass}${hasChildren ? ' has-children' : ''}" data-id="${a.id}" data-level="${level}">`;
html += `<div class="pwa-tree-row" style="padding-left:${12 + level * 20}px">`;
// Toggle-Chevron (nur bei Kindern)
if (hasChildren) {
html += '<svg class="pwa-tree-toggle" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>';
} else {
html += '<span class="pwa-tree-toggle-spacer"></span>';
}
// Icon
html += `<div class="pwa-tree-icon ${typeClass}">${iconSvg}</div>`;
// Inhalt
html += '<div class="pwa-tree-content">';
html += `<div class="pwa-tree-label">${escapeHtml(a.label || 'Anlage ' + a.id)}</div>`;
if (a.type) html += `<div class="pwa-tree-type">${escapeHtml(a.type)}</div>`;
html += fieldsHtml;
html += '</div>';
// Editor-Pfeil nur bei Equipment-Containern
if (isEquipment) {
html += '<svg class="pwa-tree-open" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>';
}
html += '</div>'; // pwa-tree-row
// Kinder (eingeklappt)
if (hasChildren) {
html += '<div class="pwa-tree-children">';
html += renderTreeNodes(a.children, level + 1);
html += '</div>';
}
html += '</div>'; // pwa-tree-node
});
return html;
} }
async function handleAnlageSelect() { // Baum-Knoten aufklappen/zuklappen
const id = $(this).data('id'); function handleTreeNodeClick(e) {
const name = $(this).find('.anlage-card-title').text(); const $node = $(this).closest('.pwa-tree-node');
// Bei Klick auf Editor-Pfeil → Editor öffnen
if ($(e.target).closest('.pwa-tree-open').length) {
openAnlageEditor($node.data('id'), $node.find('> .pwa-tree-row .pwa-tree-label').first().text());
return;
}
// Bei Equipment-Containern: Klick auf Content öffnet Editor
if ($node.hasClass('node-equipment') && !$(e.target).closest('.pwa-tree-toggle').length) {
openAnlageEditor($node.data('id'), $node.find('> .pwa-tree-row .pwa-tree-label').first().text());
return;
}
// Toggle Kinder
if ($node.hasClass('has-children')) {
$node.toggleClass('expanded');
}
}
async function openAnlageEditor(id, name) {
App.anlageId = id; App.anlageId = id;
App.anlageName = name; App.anlageName = name;
$('#anlage-name').text(name); $('#anlage-name').text(name);
@ -551,11 +619,7 @@
}); });
if (response.success && response.anlagen && response.anlagen.length) { if (response.success && response.anlagen && response.anlagen.length) {
let html = ''; $list.html(renderTreeNodes(response.anlagen, 0));
response.anlagen.forEach(a => {
html += renderAnlageCard(a);
});
$list.html(html);
} else { } else {
$list.html('<div class="list-empty small">Keine Anlagen</div>'); $list.html('<div class="list-empty small">Keine Anlagen</div>');
} }

View file

@ -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.7"> <link rel="stylesheet" href="css/pwa.css?v=2.8">
<style>:root { --primary: <?php echo $themeColor; ?>; }</style> <style>:root { --primary: <?php echo $themeColor; ?>; }</style>
</head> </head>
<body> <body>
@ -324,6 +324,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.7"></script> <script src="js/pwa.js?v=2.8"></script>
</body> </body>
</html> </html>

4
sw.js
View file

@ -3,8 +3,8 @@
* Offline-First für Schaltschrank-Dokumentation * Offline-First für Schaltschrank-Dokumentation
*/ */
const CACHE_NAME = 'kundenkarte-pwa-v2.7'; const CACHE_NAME = 'kundenkarte-pwa-v2.8';
const OFFLINE_CACHE = 'kundenkarte-offline-v2.7'; const OFFLINE_CACHE = 'kundenkarte-offline-v2.8';
// Statische Assets die immer gecached werden // Statische Assets die immer gecached werden
const STATIC_ASSETS = [ const STATIC_ASSETS = [

View file

@ -365,7 +365,21 @@ if ($action == 'togglepin' && $permissiontoadd) {
// Use Dolibarr standard button classes // Use Dolibarr standard button classes
$title = $langs->trans('TechnicalInstallations').' - '.$object->name; $title = $langs->trans('TechnicalInstallations').' - '.$object->name;
llxHeader('', $title, '', '', 0, 0, array('/kundenkarte/js/pathfinding.min.js', '/kundenkarte/js/kundenkarte.js?v='.time()), array('/kundenkarte/css/kundenkarte.css?v='.time()));
// Ansichtsmodus: URL-Parameter hat Vorrang, sonst Admin-Setting
$defaultView = getDolGlobalString('KUNDENKARTE_DEFAULT_VIEW', 'tree');
$viewMode = GETPOST('view', 'aZ09');
if (!in_array($viewMode, array('tree', 'graph'))) {
$viewMode = $defaultView;
}
dol_include_once('/kundenkarte/lib/graph_view.lib.php');
$jsFiles = array('/kundenkarte/js/kundenkarte.js?v='.time());
$cssFiles = array('/kundenkarte/css/kundenkarte.css?v='.time());
kundenkarte_graph_add_assets($jsFiles, $cssFiles, $viewMode);
llxHeader('', $title, '', '', 0, 0, $jsFiles, $cssFiles);
// Prepare tabs // Prepare tabs
$head = societe_prepare_head($object); $head = societe_prepare_head($object);
@ -442,38 +456,60 @@ if ($permissiontoadd) {
// Get systems not yet enabled for this customer // Get systems not yet enabled for this customer
$availableSystems = array_diff_key($allSystems, $customerSystems); $availableSystems = array_diff_key($allSystems, $customerSystems);
if (!empty($availableSystems)) { if (!empty($availableSystems)) {
print '<button type="button" class="button small" style="margin-left:auto;" onclick="document.getElementById(\'add-system-form\').style.display=\'block\';">'; print '<button type="button" class="button small kundenkarte-add-system-btn" onclick="document.getElementById(\'add-system-form\').style.display=\'block\';">';
print '<i class="fa fa-plus"></i> '.$langs->trans('AddSystem'); print '<i class="fa fa-plus"></i> '.$langs->trans('AddSystem');
print '</button>'; print '</button>';
} }
} }
print '</div>'; print '</div>';
// Expand/Collapse buttons (only in tree view, not in create/edit/view/copy) // Steuerungs-Buttons (nur wenn kein Formular aktiv)
$isTreeView = !in_array($action, array('create', 'edit', 'view', 'copy')); $isTreeView = !in_array($action, array('create', 'edit', 'view', 'copy'));
if ($isTreeView) { if ($isTreeView) {
print '<div class="kundenkarte-tree-controls">'; $toggleView = ($viewMode === 'graph') ? 'tree' : 'graph';
// Compact mode toggle (visible on mobile) $toggleUrl = $_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'&view='.$toggleView;
print '<button type="button" class="kundenkarte-view-toggle" id="btn-compact-mode" title="Kompakte Ansicht">'; $toggleIcon = ($viewMode === 'graph') ? 'fa-list' : 'fa-sitemap';
print '<i class="fa fa-compress"></i> <span>Kompakt</span>'; $toggleLabel = ($viewMode === 'graph') ? $langs->trans('TreeView') : $langs->trans('GraphView');
print '</button>';
print '<button type="button" class="button small" id="btn-expand-all" title="'.$langs->trans('ExpandAll').'">'; if ($viewMode !== 'graph') {
print '<i class="fa fa-expand"></i> '.$langs->trans('ExpandAll'); // Baumansicht: Controls auf gleicher Zeile wie System-Tabs (im Wrapper)
print '</button>'; print '<div class="kundenkarte-tree-controls">';
print '<button type="button" class="button small" id="btn-collapse-all" title="'.$langs->trans('CollapseAll').'">'; print '<a class="button small" href="'.$toggleUrl.'" title="'.$toggleLabel.'">';
print '<i class="fa fa-compress"></i> '.$langs->trans('CollapseAll'); print '<i class="fa '.$toggleIcon.'"></i> '.$toggleLabel;
print '</button>';
if ($systemId > 0) {
$exportUrl = dol_buildpath('/kundenkarte/ajax/export_tree_pdf.php', 1).'?socid='.$id.'&system='.$systemId;
print '<a class="button small" href="'.$exportUrl.'" title="'.$langs->trans('ExportPDF').'" target="_blank">';
print '<i class="fa fa-file-pdf-o"></i> PDF Export';
print '</a>'; print '</a>';
print '<button type="button" class="kundenkarte-view-toggle" id="btn-compact-mode" title="Kompakte Ansicht">';
print '<i class="fa fa-compress"></i> <span>Kompakt</span>';
print '</button>';
print '<button type="button" class="button small" id="btn-expand-all" title="'.$langs->trans('ExpandAll').'">';
print '<i class="fa fa-expand"></i> '.$langs->trans('ExpandAll');
print '</button>';
print '<button type="button" class="button small" id="btn-collapse-all" title="'.$langs->trans('CollapseAll').'">';
print '<i class="fa fa-compress"></i> '.$langs->trans('CollapseAll');
print '</button>';
if ($systemId > 0) {
$exportUrl = dol_buildpath('/kundenkarte/ajax/export_tree_pdf.php', 1).'?socid='.$id.'&system='.$systemId;
print '<a class="button small" href="'.$exportUrl.'" title="'.$langs->trans('ExportPDF').'" target="_blank">';
print '<i class="fa fa-file-pdf-o"></i> PDF Export';
print '</a>';
}
print '</div>';
} }
print '</div>';
} }
print '</div>'; // End kundenkarte-system-tabs-wrapper print '</div>'; // End kundenkarte-system-tabs-wrapper
// Graph-Toolbar: UNTER der System-Tab-Borderlinie
if ($isTreeView && $viewMode === 'graph') {
kundenkarte_graph_print_toolbar(array(
'socid' => $id,
'contactid' => 0,
'systemid' => $systemId,
'viewMode' => $viewMode,
'permissiontoadd' => $permissiontoadd,
'pageUrl' => $_SERVER['PHP_SELF'],
));
}
// Add system form (hidden by default) // Add system form (hidden by default)
if ($permissiontoadd && !empty($availableSystems)) { if ($permissiontoadd && !empty($availableSystems)) {
print '<div id="add-system-form" class="kundenkarte-add-system-form" style="display:none;margin-bottom:15px;">'; print '<div id="add-system-form" class="kundenkarte-add-system-form" style="display:none;margin-bottom:15px;">';
@ -991,8 +1027,8 @@ if (empty($customerSystems)) {
print '</div>'; print '</div>';
} else { } else {
// Tree view // Listenansicht (Baum oder Graph)
if ($permissiontoadd) { if ($permissiontoadd && $viewMode !== 'graph') {
print '<div style="margin-bottom:15px;">'; print '<div style="margin-bottom:15px;">';
print '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'&action=create">'; print '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'&action=create">';
print '<i class="fa fa-plus"></i> '.$langs->trans('AddElement'); print '<i class="fa fa-plus"></i> '.$langs->trans('AddElement');
@ -1000,42 +1036,56 @@ if (empty($customerSystems)) {
print '</div>'; print '</div>';
} }
// Load tree if ($viewMode === 'graph' && $isTreeView) {
$tree = $anlage->fetchTree($id, $systemId); // Graph-Ansicht: Container rendern, Daten werden per AJAX geladen
kundenkarte_graph_print_container(array(
// Pre-load all type fields for tooltip and tree display 'socid' => $id,
$typeFieldsMap = array(); 'contactid' => 0,
$sql = "SELECT f.*, f.fk_anlage_type FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_type_field f WHERE f.active = 1 ORDER BY f.position ASC"; 'systemid' => $systemId,
$resql = $db->query($sql); 'permissiontoadd' => $permissiontoadd,
if ($resql) { 'permissiontodelete' => $permissiontodelete,
while ($obj = $db->fetch_object($resql)) { 'pageUrl' => $_SERVER['PHP_SELF'],
if (!isset($typeFieldsMap[$obj->fk_anlage_type])) { ));
$typeFieldsMap[$obj->fk_anlage_type] = array();
}
$typeFieldsMap[$obj->fk_anlage_type][] = $obj;
}
$db->free($resql);
}
// Pre-load all connections for this customer/system
dol_include_once('/kundenkarte/class/anlageconnection.class.php');
$connObj = new AnlageConnection($db);
$allConnections = $connObj->fetchBySociete($id, $systemId);
// Index by target_id for quick lookup (connection shows ABOVE the target element)
$connectionsByTarget = array();
foreach ($allConnections as $conn) {
if (!isset($connectionsByTarget[$conn->fk_target])) {
$connectionsByTarget[$conn->fk_target] = array();
}
$connectionsByTarget[$conn->fk_target][] = $conn;
}
if (!empty($tree)) {
print '<div class="kundenkarte-tree" data-system="'.$systemId.'" data-socid="'.$id.'">';
printTree($tree, $id, $systemId, $permissiontoadd, $permissiontodelete, $langs, 0, $typeFieldsMap, $connectionsByTarget);
print '</div>';
} else { } else {
print '<div class="opacitymedium">'.$langs->trans('NoInstallations').'</div>'; // Baumansicht (klassisch)
// Load tree
$tree = $anlage->fetchTree($id, $systemId);
// Pre-load all type fields for tooltip and tree display
$typeFieldsMap = array();
$sql = "SELECT f.*, f.fk_anlage_type FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_type_field f WHERE f.active = 1 ORDER BY f.position ASC";
$resql = $db->query($sql);
if ($resql) {
while ($obj = $db->fetch_object($resql)) {
if (!isset($typeFieldsMap[$obj->fk_anlage_type])) {
$typeFieldsMap[$obj->fk_anlage_type] = array();
}
$typeFieldsMap[$obj->fk_anlage_type][] = $obj;
}
$db->free($resql);
}
// Pre-load all connections for this customer/system
dol_include_once('/kundenkarte/class/anlageconnection.class.php');
$connObj = new AnlageConnection($db);
$allConnections = $connObj->fetchBySociete($id, $systemId);
// Index by target_id for quick lookup (connection shows ABOVE the target element)
$connectionsByTarget = array();
foreach ($allConnections as $conn) {
if (!isset($connectionsByTarget[$conn->fk_target])) {
$connectionsByTarget[$conn->fk_target] = array();
}
$connectionsByTarget[$conn->fk_target][] = $conn;
}
if (!empty($tree)) {
print '<div class="kundenkarte-tree" data-system="'.$systemId.'" data-socid="'.$id.'">';
printTree($tree, $id, $systemId, $permissiontoadd, $permissiontodelete, $langs, 0, $typeFieldsMap, $connectionsByTarget);
print '</div>';
} else {
print '<div class="opacitymedium">'.$langs->trans('NoInstallations').'</div>';
}
} }
} }
} }
@ -1160,11 +1210,18 @@ function printTree($nodes, $socid, $systemId, $canEdit, $canDelete, $langs, $lev
} }
} }
// CSS class based on whether node has its own cable connection // CSS class basierend auf Kabel-Verbindung und Typ-Kategorie
$nodeClass = 'kundenkarte-tree-node'; $nodeClass = 'kundenkarte-tree-node';
if (!$hasConnection && $level > 0) { if (!$hasConnection && $level > 0) {
$nodeClass .= ' no-cable'; // durchgeschleift - kein eigenes Kabel $nodeClass .= ' no-cable'; // durchgeschleift - kein eigenes Kabel
} }
if ($node->type_can_have_equipment) {
$nodeClass .= ' node-equipment'; // Geräte-Container (Schaltschrank, Verteiler)
} elseif ($node->type_can_have_children) {
$nodeClass .= ' node-structure'; // Gebäudeteil (Gebäude, Etage, Raum)
} else {
$nodeClass .= ' node-leaf'; // Endgerät
}
print '<div class="'.$nodeClass.'">'; print '<div class="'.$nodeClass.'">';
print '<div class="kundenkarte-tree-item" data-anlage-id="'.$node->id.'">'; print '<div class="kundenkarte-tree-item" data-anlage-id="'.$node->id.'">';
@ -1427,7 +1484,16 @@ function printTreeWithCableLines($nodes, $socid, $systemId, $canEdit, $canDelete
print '<span class="'.$lineClass.'"'.$inlineStyle.' data-line="'.$i.'"></span>'; print '<span class="'.$lineClass.'"'.$inlineStyle.' data-line="'.$i.'"></span>';
} }
print '<div class="kundenkarte-tree-node-content">'; // Typ-Kategorie als CSS-Klasse
$nodeContentClass = 'kundenkarte-tree-node-content';
if ($node->type_can_have_equipment) {
$nodeContentClass .= ' node-equipment';
} elseif ($node->type_can_have_children) {
$nodeContentClass .= ' node-structure';
} else {
$nodeContentClass .= ' node-leaf';
}
print '<div class="'.$nodeContentClass.'">';
print '<div class="kundenkarte-tree-item" data-anlage-id="'.$node->id.'">'; print '<div class="kundenkarte-tree-item" data-anlage-id="'.$node->id.'">';
if ($hasChildren) { if ($hasChildren) {

View file

@ -364,7 +364,20 @@ if ($action == 'togglepin' && $permissiontoadd) {
*/ */
$title = $langs->trans('TechnicalInstallations').' - '.$object->getFullName($langs); $title = $langs->trans('TechnicalInstallations').' - '.$object->getFullName($langs);
llxHeader('', $title, '', '', 0, 0, array('/kundenkarte/js/pathfinding.min.js', '/kundenkarte/js/kundenkarte.js?v='.time()), array('/kundenkarte/css/kundenkarte.css?v='.time()));
// Ansichtsmodus: URL-Parameter hat Vorrang, sonst Admin-Setting
dol_include_once('/kundenkarte/lib/graph_view.lib.php');
$defaultView = getDolGlobalString('KUNDENKARTE_DEFAULT_VIEW', 'tree');
$viewMode = GETPOST('view', 'aZ09');
if (!in_array($viewMode, array('tree', 'graph'))) {
$viewMode = $defaultView;
}
$jsFiles = array('/kundenkarte/js/kundenkarte.js?v='.time());
$cssFiles = array('/kundenkarte/css/kundenkarte.css?v='.time());
kundenkarte_graph_add_assets($jsFiles, $cssFiles, $viewMode);
llxHeader('', $title, '', '', 0, 0, $jsFiles, $cssFiles);
// Prepare tabs // Prepare tabs
$head = contact_prepare_head($object); $head = contact_prepare_head($object);
@ -441,38 +454,60 @@ if ($permissiontoadd) {
// Get systems not yet enabled for this contact // Get systems not yet enabled for this contact
$availableSystems = array_diff_key($allSystems, $customerSystems); $availableSystems = array_diff_key($allSystems, $customerSystems);
if (!empty($availableSystems)) { if (!empty($availableSystems)) {
print '<button type="button" class="button small" style="margin-left:auto;" onclick="document.getElementById(\'add-system-form\').style.display=\'block\';">'; print '<button type="button" class="button small kundenkarte-add-system-btn" onclick="document.getElementById(\'add-system-form\').style.display=\'block\';">';
print '<i class="fa fa-plus"></i> '.$langs->trans('AddSystem'); print '<i class="fa fa-plus"></i> '.$langs->trans('AddSystem');
print '</button>'; print '</button>';
} }
} }
print '</div>'; print '</div>';
// Expand/Collapse buttons (only in tree view, not in create/edit/view/copy) // Steuerungs-Buttons (nur wenn kein Formular aktiv)
$isTreeView = !in_array($action, array('create', 'edit', 'view', 'copy')); $isTreeView = !in_array($action, array('create', 'edit', 'view', 'copy'));
if ($isTreeView) { if ($isTreeView) {
print '<div class="kundenkarte-tree-controls">'; $toggleView = ($viewMode === 'graph') ? 'tree' : 'graph';
// Compact mode toggle (visible on mobile) $toggleUrl = $_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'&view='.$toggleView;
print '<button type="button" class="kundenkarte-view-toggle" id="btn-compact-mode" title="Kompakte Ansicht">'; $toggleIcon = ($viewMode === 'graph') ? 'fa-list' : 'fa-sitemap';
print '<i class="fa fa-compress"></i> <span>Kompakt</span>'; $toggleLabel = ($viewMode === 'graph') ? $langs->trans('TreeView') : $langs->trans('GraphView');
print '</button>';
print '<button type="button" class="button small" id="btn-expand-all" title="'.$langs->trans('ExpandAll').'">'; if ($viewMode !== 'graph') {
print '<i class="fa fa-expand"></i> '.$langs->trans('ExpandAll'); // Baumansicht: Controls auf gleicher Zeile wie System-Tabs (im Wrapper)
print '</button>'; print '<div class="kundenkarte-tree-controls">';
print '<button type="button" class="button small" id="btn-collapse-all" title="'.$langs->trans('CollapseAll').'">'; print '<a class="button small" href="'.$toggleUrl.'" title="'.$toggleLabel.'">';
print '<i class="fa fa-compress"></i> '.$langs->trans('CollapseAll'); print '<i class="fa '.$toggleIcon.'"></i> '.$toggleLabel;
print '</button>';
if ($systemId > 0) {
$exportUrl = dol_buildpath('/kundenkarte/ajax/export_tree_pdf.php', 1).'?socid='.$object->socid.'&contactid='.$id.'&system='.$systemId;
print '<a class="button small" href="'.$exportUrl.'" title="'.$langs->trans('ExportPDF').'" target="_blank">';
print '<i class="fa fa-file-pdf-o"></i> PDF Export';
print '</a>'; print '</a>';
print '<button type="button" class="kundenkarte-view-toggle" id="btn-compact-mode" title="Kompakte Ansicht">';
print '<i class="fa fa-compress"></i> <span>Kompakt</span>';
print '</button>';
print '<button type="button" class="button small" id="btn-expand-all" title="'.$langs->trans('ExpandAll').'">';
print '<i class="fa fa-expand"></i> '.$langs->trans('ExpandAll');
print '</button>';
print '<button type="button" class="button small" id="btn-collapse-all" title="'.$langs->trans('CollapseAll').'">';
print '<i class="fa fa-compress"></i> '.$langs->trans('CollapseAll');
print '</button>';
if ($systemId > 0) {
$exportUrl = dol_buildpath('/kundenkarte/ajax/export_tree_pdf.php', 1).'?socid='.$object->socid.'&contactid='.$id.'&system='.$systemId;
print '<a class="button small" href="'.$exportUrl.'" title="'.$langs->trans('ExportPDF').'" target="_blank">';
print '<i class="fa fa-file-pdf-o"></i> PDF Export';
print '</a>';
}
print '</div>';
} }
print '</div>';
} }
print '</div>'; // End kundenkarte-system-tabs-wrapper print '</div>'; // End kundenkarte-system-tabs-wrapper
// Graph-Toolbar: UNTER der System-Tab-Borderlinie
if ($isTreeView && $viewMode === 'graph') {
kundenkarte_graph_print_toolbar(array(
'socid' => $object->socid,
'contactid' => $id,
'systemid' => $systemId,
'viewMode' => $viewMode,
'permissiontoadd' => $permissiontoadd,
'pageUrl' => $_SERVER['PHP_SELF'],
));
}
// Add system form (hidden by default) // Add system form (hidden by default)
if ($permissiontoadd && !empty($availableSystems)) { if ($permissiontoadd && !empty($availableSystems)) {
print '<div id="add-system-form" class="kundenkarte-add-system-form" style="display:none;margin-bottom:15px;">'; print '<div id="add-system-form" class="kundenkarte-add-system-form" style="display:none;margin-bottom:15px;">';
@ -990,8 +1025,8 @@ if (empty($customerSystems)) {
print '</div>'; print '</div>';
} else { } else {
// Tree view // Listenansicht (Baum oder Graph)
if ($permissiontoadd) { if ($permissiontoadd && $viewMode !== 'graph') {
print '<div style="margin-bottom:15px;">'; print '<div style="margin-bottom:15px;">';
print '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'&action=create">'; print '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'&action=create">';
print '<i class="fa fa-plus"></i> '.$langs->trans('AddElement'); print '<i class="fa fa-plus"></i> '.$langs->trans('AddElement');
@ -999,42 +1034,56 @@ if (empty($customerSystems)) {
print '</div>'; print '</div>';
} }
// Load tree for this contact if ($viewMode === 'graph' && $isTreeView) {
$tree = $anlage->fetchTreeByContact($object->socid, $id, $systemId); // Graph-Ansicht: Container rendern, Daten werden per AJAX geladen
kundenkarte_graph_print_container(array(
// Pre-load all type fields for tooltip and tree display 'socid' => $object->socid,
$typeFieldsMap = array(); 'contactid' => $id,
$sql = "SELECT f.*, f.fk_anlage_type FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_type_field f WHERE f.active = 1 ORDER BY f.position ASC"; 'systemid' => $systemId,
$resql = $db->query($sql); 'permissiontoadd' => $permissiontoadd,
if ($resql) { 'permissiontodelete' => $permissiontodelete,
while ($obj = $db->fetch_object($resql)) { 'pageUrl' => $_SERVER['PHP_SELF'],
if (!isset($typeFieldsMap[$obj->fk_anlage_type])) { ));
$typeFieldsMap[$obj->fk_anlage_type] = array();
}
$typeFieldsMap[$obj->fk_anlage_type][] = $obj;
}
$db->free($resql);
}
// Pre-load all connections for this contact/system
dol_include_once('/kundenkarte/class/anlageconnection.class.php');
$connObj = new AnlageConnection($db);
$allConnections = $connObj->fetchBySociete($object->socid, $systemId);
// Index by target_id for quick lookup (connection shows ABOVE the target element)
$connectionsByTarget = array();
foreach ($allConnections as $conn) {
if (!isset($connectionsByTarget[$conn->fk_target])) {
$connectionsByTarget[$conn->fk_target] = array();
}
$connectionsByTarget[$conn->fk_target][] = $conn;
}
if (!empty($tree)) {
print '<div class="kundenkarte-tree" data-system="'.$systemId.'" data-socid="'.$object->socid.'">';
printTree($tree, $id, $systemId, $permissiontoadd, $permissiontodelete, $langs, 0, $typeFieldsMap, $connectionsByTarget);
print '</div>';
} else { } else {
print '<div class="opacitymedium">'.$langs->trans('NoInstallations').'</div>'; // Baumansicht (klassisch)
// Load tree for this contact
$tree = $anlage->fetchTreeByContact($object->socid, $id, $systemId);
// Pre-load all type fields for tooltip and tree display
$typeFieldsMap = array();
$sql = "SELECT f.*, f.fk_anlage_type FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_type_field f WHERE f.active = 1 ORDER BY f.position ASC";
$resql = $db->query($sql);
if ($resql) {
while ($obj = $db->fetch_object($resql)) {
if (!isset($typeFieldsMap[$obj->fk_anlage_type])) {
$typeFieldsMap[$obj->fk_anlage_type] = array();
}
$typeFieldsMap[$obj->fk_anlage_type][] = $obj;
}
$db->free($resql);
}
// Pre-load all connections for this contact/system
dol_include_once('/kundenkarte/class/anlageconnection.class.php');
$connObj = new AnlageConnection($db);
$allConnections = $connObj->fetchBySociete($object->socid, $systemId);
// Index by target_id for quick lookup (connection shows ABOVE the target element)
$connectionsByTarget = array();
foreach ($allConnections as $conn) {
if (!isset($connectionsByTarget[$conn->fk_target])) {
$connectionsByTarget[$conn->fk_target] = array();
}
$connectionsByTarget[$conn->fk_target][] = $conn;
}
if (!empty($tree)) {
print '<div class="kundenkarte-tree" data-system="'.$systemId.'" data-socid="'.$object->socid.'">';
printTree($tree, $id, $systemId, $permissiontoadd, $permissiontodelete, $langs, 0, $typeFieldsMap, $connectionsByTarget);
print '</div>';
} else {
print '<div class="opacitymedium">'.$langs->trans('NoInstallations').'</div>';
}
} }
} }
} }
@ -1146,7 +1195,7 @@ function printTree($nodes, $contactid, $systemId, $canEdit, $canDelete, $langs,
$mainText = $conn->label ? $conn->label : $cableInfo; $mainText = $conn->label ? $conn->label : $cableInfo;
$badgeText = $conn->label ? $cableInfo : ''; $badgeText = $conn->label ? $cableInfo : '';
$connEditUrl = dol_buildpath('/kundenkarte/anlage_connection.php', 1).'?id='.$conn->id.'&socid='.$node->fk_soc.'&system_id='.$systemId; $connEditUrl = dol_buildpath('/kundenkarte/anlage_connection.php', 1).'?id='.$conn->id.'&socid='.$node->fk_soc.'&contactid='.$id.'&system_id='.$systemId;
print '<a href="'.$connEditUrl.'" class="kundenkarte-tree-conn">'; print '<a href="'.$connEditUrl.'" class="kundenkarte-tree-conn">';
print '<span class="conn-icon"><i class="fa fa-plug"></i></span>'; print '<span class="conn-icon"><i class="fa fa-plug"></i></span>';
if ($mainText) { if ($mainText) {
@ -1159,11 +1208,18 @@ function printTree($nodes, $contactid, $systemId, $canEdit, $canDelete, $langs,
} }
} }
// CSS class based on whether node has its own cable connection // CSS class basierend auf Kabel-Verbindung und Typ-Kategorie
$nodeClass = 'kundenkarte-tree-node'; $nodeClass = 'kundenkarte-tree-node';
if (!$hasConnection && $level > 0) { if (!$hasConnection && $level > 0) {
$nodeClass .= ' no-cable'; // durchgeschleift - kein eigenes Kabel $nodeClass .= ' no-cable'; // durchgeschleift - kein eigenes Kabel
} }
if ($node->type_can_have_equipment) {
$nodeClass .= ' node-equipment'; // Geräte-Container (Schaltschrank, Verteiler)
} elseif ($node->type_can_have_children) {
$nodeClass .= ' node-structure'; // Gebäudeteil (Gebäude, Etage, Raum)
} else {
$nodeClass .= ' node-leaf'; // Endgerät
}
print '<div class="'.$nodeClass.'">'; print '<div class="'.$nodeClass.'">';
print '<div class="kundenkarte-tree-item" data-anlage-id="'.$node->id.'">'; print '<div class="kundenkarte-tree-item" data-anlage-id="'.$node->id.'">';
@ -1394,7 +1450,7 @@ function printTreeWithCableLines($nodes, $contactid, $systemId, $canEdit, $canDe
$mainText = $conn->label ? $conn->label : $cableInfo; $mainText = $conn->label ? $conn->label : $cableInfo;
$badgeText = $conn->label ? $cableInfo : ''; $badgeText = $conn->label ? $cableInfo : '';
$connEditUrl = dol_buildpath('/kundenkarte/anlage_connection.php', 1).'?id='.$conn->id.'&socid='.$node->fk_soc.'&system_id='.$systemId; $connEditUrl = dol_buildpath('/kundenkarte/anlage_connection.php', 1).'?id='.$conn->id.'&socid='.$node->fk_soc.'&contactid='.$id.'&system_id='.$systemId;
print '<div class="kundenkarte-tree-row">'; print '<div class="kundenkarte-tree-row">';
// Draw vertical line columns (for cables passing through) // Draw vertical line columns (for cables passing through)
@ -1458,7 +1514,16 @@ function printTreeWithCableLines($nodes, $contactid, $systemId, $canEdit, $canDe
print '<span class="'.$lineClass.'"'.$inlineStyle.' data-line="'.$i.'"></span>'; print '<span class="'.$lineClass.'"'.$inlineStyle.' data-line="'.$i.'"></span>';
} }
print '<div class="kundenkarte-tree-node-content">'; // Typ-Kategorie als CSS-Klasse
$nodeContentClass = 'kundenkarte-tree-node-content';
if ($node->type_can_have_equipment) {
$nodeContentClass .= ' node-equipment';
} elseif ($node->type_can_have_children) {
$nodeContentClass .= ' node-structure';
} else {
$nodeContentClass .= ' node-leaf';
}
print '<div class="'.$nodeContentClass.'">';
print '<div class="kundenkarte-tree-item" data-anlage-id="'.$node->id.'">'; print '<div class="kundenkarte-tree-item" data-anlage-id="'.$node->id.'">';
if ($hasChildren) { if ($hasChildren) {