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);
|
$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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,";
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
129
css/pwa.css
129
css/pwa.css
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
120
js/pwa.js
120
js/pwa.js
|
|
@ -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,7 +485,20 @@
|
||||||
$('#anlagen-list').html(html);
|
$('#anlagen-list').html(html);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderAnlageCard(a) {
|
// 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 = '';
|
let fieldsHtml = '';
|
||||||
if (a.fields && a.fields.length) {
|
if (a.fields && a.fields.length) {
|
||||||
fieldsHtml = '<div class="anlage-card-fields">';
|
fieldsHtml = '<div class="anlage-card-fields">';
|
||||||
|
|
@ -497,25 +508,82 @@
|
||||||
});
|
});
|
||||||
fieldsHtml += '</div>';
|
fieldsHtml += '</div>';
|
||||||
}
|
}
|
||||||
return `
|
|
||||||
<div class="anlage-card" data-id="${a.id}">
|
// Icons je nach Typ
|
||||||
<div class="anlage-card-icon">
|
let 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>
|
if (isEquipment) {
|
||||||
</div>
|
// Schaltschrank/Verteiler
|
||||||
<div class="anlage-card-content">
|
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>';
|
||||||
<div class="anlage-card-title">${escapeHtml(a.label || 'Anlage ' + a.id)}</div>
|
} else if (isStructure) {
|
||||||
${a.type ? '<div class="anlage-card-type">' + escapeHtml(a.type) + '</div>' : ''}
|
// Gebäude/Raum
|
||||||
${fieldsHtml}
|
iconSvg = '<svg viewBox="0 0 24 24"><path d="M12 3L2 12h3v8h6v-6h2v6h6v-8h3L12 3zm0 2.84L18 12v7h-2v-6H8v6H6v-7l6-6.16z"/></svg>';
|
||||||
</div>
|
} else {
|
||||||
<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>
|
// Endgerät
|
||||||
</div>
|
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>';
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleAnlageSelect() {
|
html += `<div class="pwa-tree-node ${typeClass}${hasChildren ? ' has-children' : ''}" data-id="${a.id}" data-level="${level}">`;
|
||||||
const id = $(this).data('id');
|
html += `<div class="pwa-tree-row" style="padding-left:${12 + level * 20}px">`;
|
||||||
const name = $(this).find('.anlage-card-title').text();
|
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.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>');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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="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
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.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 = [
|
||||||
|
|
|
||||||
|
|
@ -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,18 +456,27 @@ 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) {
|
||||||
|
$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 '<div class="kundenkarte-tree-controls">';
|
||||||
// Compact mode toggle (visible on mobile)
|
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 '<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 '<i class="fa fa-compress"></i> <span>Kompakt</span>';
|
||||||
print '</button>';
|
print '</button>';
|
||||||
|
|
@ -471,9 +494,22 @@ if ($isTreeView) {
|
||||||
}
|
}
|
||||||
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,6 +1036,19 @@ if (empty($customerSystems)) {
|
||||||
print '</div>';
|
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 {
|
||||||
|
// Baumansicht (klassisch)
|
||||||
|
|
||||||
// Load tree
|
// Load tree
|
||||||
$tree = $anlage->fetchTree($id, $systemId);
|
$tree = $anlage->fetchTree($id, $systemId);
|
||||||
|
|
||||||
|
|
@ -1039,6 +1088,7 @@ if (empty($customerSystems)) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
print '</div>';
|
print '</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) {
|
||||||
|
|
|
||||||
|
|
@ -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,18 +454,27 @@ 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) {
|
||||||
|
$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 '<div class="kundenkarte-tree-controls">';
|
||||||
// Compact mode toggle (visible on mobile)
|
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 '<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 '<i class="fa fa-compress"></i> <span>Kompakt</span>';
|
||||||
print '</button>';
|
print '</button>';
|
||||||
|
|
@ -470,9 +492,22 @@ if ($isTreeView) {
|
||||||
}
|
}
|
||||||
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,6 +1034,19 @@ if (empty($customerSystems)) {
|
||||||
print '</div>';
|
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 {
|
||||||
|
// Baumansicht (klassisch)
|
||||||
|
|
||||||
// Load tree for this contact
|
// Load tree for this contact
|
||||||
$tree = $anlage->fetchTreeByContact($object->socid, $id, $systemId);
|
$tree = $anlage->fetchTreeByContact($object->socid, $id, $systemId);
|
||||||
|
|
||||||
|
|
@ -1038,6 +1086,7 @@ if (empty($customerSystems)) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
print '</div>';
|
print '</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) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue