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:
parent
da4ed40ad2
commit
143ddcb958
9 changed files with 511 additions and 233 deletions
|
|
@ -148,25 +148,10 @@ switch ($action) {
|
|||
$db->free($resFields);
|
||||
}
|
||||
|
||||
// Root-Anlagen ohne Kontaktzuweisung (Kunden-Ebene)
|
||||
// Kompletter Baum ohne Kontaktzuweisung (Kunden-Ebene)
|
||||
$anlage = new Anlage($db);
|
||||
$anlagen = $anlage->fetchChildren(0, $customerId);
|
||||
|
||||
$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;
|
||||
}
|
||||
$tree = $anlage->fetchTree($customerId, 0);
|
||||
$result = pwaTreeToArray($tree, $fieldMeta);
|
||||
|
||||
// Kontakt-Adressen mit Anlagen laden
|
||||
$contacts = array();
|
||||
|
|
@ -229,22 +214,8 @@ switch ($action) {
|
|||
}
|
||||
|
||||
$anlage = new Anlage($db);
|
||||
$anlagen = $anlage->fetchChildrenByContact(0, $customerId, $contactId);
|
||||
|
||||
$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;
|
||||
}
|
||||
$tree = $anlage->fetchTreeByContact($customerId, $contactId, 0);
|
||||
$result = pwaTreeToArray($tree, $fieldMeta);
|
||||
|
||||
$response['success'] = true;
|
||||
$response['anlagen'] = $result;
|
||||
|
|
@ -931,3 +902,37 @@ function pwaGetAnlageFields($anlage, $fieldMeta) {
|
|||
}
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -227,6 +227,8 @@ class Anlage extends CommonObject
|
|||
$this->type_label = $obj->type_label;
|
||||
$this->type_short = $obj->type_short;
|
||||
$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
|
||||
$this->system_label = $obj->system_label;
|
||||
|
|
@ -400,6 +402,7 @@ class Anlage extends CommonObject
|
|||
$results = array();
|
||||
|
||||
$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,";
|
||||
// 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,";
|
||||
|
|
@ -709,6 +712,7 @@ class Anlage extends CommonObject
|
|||
$results = array();
|
||||
|
||||
$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,";
|
||||
// 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,";
|
||||
|
|
|
|||
|
|
@ -336,6 +336,31 @@ body.kundenkarte-drag-active * {
|
|||
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 */
|
||||
.kundenkarte-tree-files {
|
||||
display: inline-flex !important;
|
||||
|
|
|
|||
129
css/pwa.css
129
css/pwa.css
|
|
@ -438,14 +438,14 @@ body {
|
|||
}
|
||||
|
||||
/* ============================================
|
||||
ANLAGEN GRID
|
||||
ANLAGEN BAUM
|
||||
============================================ */
|
||||
|
||||
.anlagen-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding: 12px;
|
||||
gap: 0;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
/* Trennlabel Kunden-Adresse */
|
||||
|
|
@ -453,83 +453,132 @@ body {
|
|||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--colortextmuted);
|
||||
padding: 8px 4px 2px;
|
||||
padding: 8px 12px 2px;
|
||||
border-top: 1px solid var(--colorborder);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.anlage-card {
|
||||
/* Baum-Knoten */
|
||||
.pwa-tree-node {
|
||||
/* Container für Knoten + Kinder */
|
||||
}
|
||||
|
||||
.pwa-tree-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 14px;
|
||||
background: var(--colorbackline);
|
||||
border: 1px solid var(--colorborder);
|
||||
border-radius: 8px;
|
||||
gap: 8px;
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid var(--colorborder);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.anlage-card:active {
|
||||
.pwa-tree-row:active {
|
||||
background: var(--colorbackinput);
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.anlage-card-icon {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
background: var(--success);
|
||||
border-radius: 10px;
|
||||
/* Toggle-Chevron */
|
||||
.pwa-tree-toggle {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
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;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.anlage-card-icon svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
.pwa-tree-icon svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.anlage-card-title {
|
||||
font-size: 15px;
|
||||
.pwa-tree-icon.node-structure {
|
||||
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;
|
||||
word-break: break-word;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.anlage-card-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.anlage-card-type {
|
||||
font-size: 12px;
|
||||
.pwa-tree-type {
|
||||
font-size: 11px;
|
||||
color: var(--colortextmuted);
|
||||
margin-top: 2px;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.anlage-card-arrow {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
/* Editor-Pfeil für Equipment-Container */
|
||||
.pwa-tree-open {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
fill: var(--colortextmuted);
|
||||
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 {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
margin-top: 6px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.anlage-field-badge {
|
||||
font-size: 11px;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
padding: 1px 6px;
|
||||
border-radius: 3px;
|
||||
background: var(--colorbackinput);
|
||||
color: #fff;
|
||||
white-space: nowrap;
|
||||
|
|
@ -1508,7 +1557,7 @@ body {
|
|||
.te-btn,
|
||||
.list-item,
|
||||
.contact-group-header,
|
||||
.anlage-card {
|
||||
.pwa-tree-row {
|
||||
min-height: 48px;
|
||||
}
|
||||
|
||||
|
|
|
|||
138
js/pwa.js
138
js/pwa.js
|
|
@ -157,7 +157,7 @@
|
|||
if (e.state && e.state.screen) {
|
||||
showScreen(e.state.screen, true);
|
||||
// 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();
|
||||
}
|
||||
} else {
|
||||
|
|
@ -172,7 +172,7 @@
|
|||
|
||||
// Customer/Anlage selection
|
||||
$('#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);
|
||||
|
||||
// Editor actions
|
||||
|
|
@ -469,14 +469,12 @@
|
|||
html += '</div>';
|
||||
}
|
||||
|
||||
// Kunden-Anlagen (ohne Kontaktzuweisung) darunter
|
||||
// Kunden-Anlagen (ohne Kontaktzuweisung) als Baum darunter
|
||||
if (anlagen && anlagen.length) {
|
||||
if (contacts && contacts.length && App.customerAddress) {
|
||||
html += `<div class="anlagen-section-label">${escapeHtml(App.customerName)} – ${escapeHtml(App.customerAddress)}</div>`;
|
||||
}
|
||||
anlagen.forEach(a => {
|
||||
html += renderAnlageCard(a);
|
||||
});
|
||||
html += renderTreeNodes(anlagen, 0);
|
||||
}
|
||||
|
||||
if (!html) {
|
||||
|
|
@ -487,35 +485,105 @@
|
|||
$('#anlagen-list').html(html);
|
||||
}
|
||||
|
||||
function renderAnlageCard(a) {
|
||||
let fieldsHtml = '';
|
||||
if (a.fields && a.fields.length) {
|
||||
fieldsHtml = '<div class="anlage-card-fields">';
|
||||
a.fields.forEach(f => {
|
||||
const style = f.color ? ` style="background:${f.color}"` : '';
|
||||
fieldsHtml += `<span class="anlage-field-badge"${style}>${escapeHtml(f.value)}</span>`;
|
||||
});
|
||||
fieldsHtml += '</div>';
|
||||
}
|
||||
return `
|
||||
<div class="anlage-card" data-id="${a.id}">
|
||||
<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>
|
||||
</div>
|
||||
<div class="anlage-card-content">
|
||||
<div class="anlage-card-title">${escapeHtml(a.label || 'Anlage ' + a.id)}</div>
|
||||
${a.type ? '<div class="anlage-card-type">' + escapeHtml(a.type) + '</div>' : ''}
|
||||
${fieldsHtml}
|
||||
</div>
|
||||
<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>
|
||||
`;
|
||||
// Baum-Knoten rekursiv rendern
|
||||
function renderTreeNodes(nodes, level) {
|
||||
let html = '';
|
||||
nodes.forEach(a => {
|
||||
const hasChildren = a.children && a.children.length > 0;
|
||||
const isEquipment = a.can_have_equipment;
|
||||
const isStructure = a.can_have_children && !isEquipment;
|
||||
|
||||
// Typ-Klasse für farbliche Unterscheidung
|
||||
let typeClass = 'node-leaf';
|
||||
if (isEquipment) typeClass = 'node-equipment';
|
||||
else if (isStructure) typeClass = 'node-structure';
|
||||
|
||||
// Feld-Badges
|
||||
let fieldsHtml = '';
|
||||
if (a.fields && a.fields.length) {
|
||||
fieldsHtml = '<div class="anlage-card-fields">';
|
||||
a.fields.forEach(f => {
|
||||
const style = f.color ? ` style="background:${f.color}"` : '';
|
||||
fieldsHtml += `<span class="anlage-field-badge"${style}>${escapeHtml(f.value)}</span>`;
|
||||
});
|
||||
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() {
|
||||
const id = $(this).data('id');
|
||||
const name = $(this).find('.anlage-card-title').text();
|
||||
// Baum-Knoten aufklappen/zuklappen
|
||||
function handleTreeNodeClick(e) {
|
||||
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.anlageName = name;
|
||||
$('#anlage-name').text(name);
|
||||
|
|
@ -551,11 +619,7 @@
|
|||
});
|
||||
|
||||
if (response.success && response.anlagen && response.anlagen.length) {
|
||||
let html = '';
|
||||
response.anlagen.forEach(a => {
|
||||
html += renderAnlageCard(a);
|
||||
});
|
||||
$list.html(html);
|
||||
$list.html(renderTreeNodes(response.anlagen, 0));
|
||||
} else {
|
||||
$list.html('<div class="list-empty small">Keine Anlagen</div>');
|
||||
}
|
||||
|
|
|
|||
4
pwa.php
4
pwa.php
|
|
@ -44,7 +44,7 @@ $themeColor = getDolGlobalString('THEME_ELDY_TOPMENU_BACK1', '#3498db');
|
|||
<link rel="manifest" href="manifest.json">
|
||||
<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="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>
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -324,6 +324,6 @@ $themeColor = getDolGlobalString('THEME_ELDY_TOPMENU_BACK1', '#3498db');
|
|||
window.DOLIBARR_URL = '<?php echo DOL_URL_ROOT; ?>';
|
||||
window.MODULE_URL = '<?php echo DOL_URL_ROOT; ?>/custom/kundenkarte';
|
||||
</script>
|
||||
<script src="js/pwa.js?v=2.7"></script>
|
||||
<script src="js/pwa.js?v=2.8"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
4
sw.js
4
sw.js
|
|
@ -3,8 +3,8 @@
|
|||
* Offline-First für Schaltschrank-Dokumentation
|
||||
*/
|
||||
|
||||
const CACHE_NAME = 'kundenkarte-pwa-v2.7';
|
||||
const OFFLINE_CACHE = 'kundenkarte-offline-v2.7';
|
||||
const CACHE_NAME = 'kundenkarte-pwa-v2.8';
|
||||
const OFFLINE_CACHE = 'kundenkarte-offline-v2.8';
|
||||
|
||||
// Statische Assets die immer gecached werden
|
||||
const STATIC_ASSETS = [
|
||||
|
|
|
|||
182
tabs/anlagen.php
182
tabs/anlagen.php
|
|
@ -365,7 +365,21 @@ if ($action == 'togglepin' && $permissiontoadd) {
|
|||
// Use Dolibarr standard button classes
|
||||
|
||||
$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
|
||||
$head = societe_prepare_head($object);
|
||||
|
|
@ -442,38 +456,60 @@ if ($permissiontoadd) {
|
|||
// Get systems not yet enabled for this customer
|
||||
$availableSystems = array_diff_key($allSystems, $customerSystems);
|
||||
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 '</button>';
|
||||
}
|
||||
}
|
||||
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'));
|
||||
if ($isTreeView) {
|
||||
print '<div class="kundenkarte-tree-controls">';
|
||||
// Compact mode toggle (visible on mobile)
|
||||
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';
|
||||
$toggleView = ($viewMode === 'graph') ? 'tree' : 'graph';
|
||||
$toggleUrl = $_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'&view='.$toggleView;
|
||||
$toggleIcon = ($viewMode === 'graph') ? 'fa-list' : 'fa-sitemap';
|
||||
$toggleLabel = ($viewMode === 'graph') ? $langs->trans('TreeView') : $langs->trans('GraphView');
|
||||
|
||||
if ($viewMode !== 'graph') {
|
||||
// Baumansicht: Controls auf gleicher Zeile wie System-Tabs (im Wrapper)
|
||||
print '<div class="kundenkarte-tree-controls">';
|
||||
print '<a class="button small" href="'.$toggleUrl.'" title="'.$toggleLabel.'">';
|
||||
print '<i class="fa '.$toggleIcon.'"></i> '.$toggleLabel;
|
||||
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
|
||||
|
||||
// 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)
|
||||
if ($permissiontoadd && !empty($availableSystems)) {
|
||||
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>';
|
||||
|
||||
} else {
|
||||
// Tree view
|
||||
if ($permissiontoadd) {
|
||||
// Listenansicht (Baum oder Graph)
|
||||
if ($permissiontoadd && $viewMode !== 'graph') {
|
||||
print '<div style="margin-bottom:15px;">';
|
||||
print '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'&action=create">';
|
||||
print '<i class="fa fa-plus"></i> '.$langs->trans('AddElement');
|
||||
|
|
@ -1000,42 +1036,56 @@ if (empty($customerSystems)) {
|
|||
print '</div>';
|
||||
}
|
||||
|
||||
// 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>';
|
||||
if ($viewMode === 'graph' && $isTreeView) {
|
||||
// Graph-Ansicht: Container rendern, Daten werden per AJAX geladen
|
||||
kundenkarte_graph_print_container(array(
|
||||
'socid' => $id,
|
||||
'contactid' => 0,
|
||||
'systemid' => $systemId,
|
||||
'permissiontoadd' => $permissiontoadd,
|
||||
'permissiontodelete' => $permissiontodelete,
|
||||
'pageUrl' => $_SERVER['PHP_SELF'],
|
||||
));
|
||||
} 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';
|
||||
if (!$hasConnection && $level > 0) {
|
||||
$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="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 '<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.'">';
|
||||
|
||||
if ($hasChildren) {
|
||||
|
|
|
|||
|
|
@ -364,7 +364,20 @@ if ($action == 'togglepin' && $permissiontoadd) {
|
|||
*/
|
||||
|
||||
$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
|
||||
$head = contact_prepare_head($object);
|
||||
|
|
@ -441,38 +454,60 @@ if ($permissiontoadd) {
|
|||
// Get systems not yet enabled for this contact
|
||||
$availableSystems = array_diff_key($allSystems, $customerSystems);
|
||||
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 '</button>';
|
||||
}
|
||||
}
|
||||
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'));
|
||||
if ($isTreeView) {
|
||||
print '<div class="kundenkarte-tree-controls">';
|
||||
// Compact mode toggle (visible on mobile)
|
||||
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';
|
||||
$toggleView = ($viewMode === 'graph') ? 'tree' : 'graph';
|
||||
$toggleUrl = $_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'&view='.$toggleView;
|
||||
$toggleIcon = ($viewMode === 'graph') ? 'fa-list' : 'fa-sitemap';
|
||||
$toggleLabel = ($viewMode === 'graph') ? $langs->trans('TreeView') : $langs->trans('GraphView');
|
||||
|
||||
if ($viewMode !== 'graph') {
|
||||
// Baumansicht: Controls auf gleicher Zeile wie System-Tabs (im Wrapper)
|
||||
print '<div class="kundenkarte-tree-controls">';
|
||||
print '<a class="button small" href="'.$toggleUrl.'" title="'.$toggleLabel.'">';
|
||||
print '<i class="fa '.$toggleIcon.'"></i> '.$toggleLabel;
|
||||
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
|
||||
|
||||
// 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)
|
||||
if ($permissiontoadd && !empty($availableSystems)) {
|
||||
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>';
|
||||
|
||||
} else {
|
||||
// Tree view
|
||||
if ($permissiontoadd) {
|
||||
// Listenansicht (Baum oder Graph)
|
||||
if ($permissiontoadd && $viewMode !== 'graph') {
|
||||
print '<div style="margin-bottom:15px;">';
|
||||
print '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'&action=create">';
|
||||
print '<i class="fa fa-plus"></i> '.$langs->trans('AddElement');
|
||||
|
|
@ -999,42 +1034,56 @@ if (empty($customerSystems)) {
|
|||
print '</div>';
|
||||
}
|
||||
|
||||
// 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>';
|
||||
if ($viewMode === 'graph' && $isTreeView) {
|
||||
// Graph-Ansicht: Container rendern, Daten werden per AJAX geladen
|
||||
kundenkarte_graph_print_container(array(
|
||||
'socid' => $object->socid,
|
||||
'contactid' => $id,
|
||||
'systemid' => $systemId,
|
||||
'permissiontoadd' => $permissiontoadd,
|
||||
'permissiontodelete' => $permissiontodelete,
|
||||
'pageUrl' => $_SERVER['PHP_SELF'],
|
||||
));
|
||||
} 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;
|
||||
$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 '<span class="conn-icon"><i class="fa fa-plug"></i></span>';
|
||||
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';
|
||||
if (!$hasConnection && $level > 0) {
|
||||
$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="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;
|
||||
$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">';
|
||||
|
||||
// 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 '<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.'">';
|
||||
|
||||
if ($hasChildren) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue