Version 5.1.0 - Graph-Feldanzeige & Shared Library
- Felder nach position sortiert (nicht JSON-Reihenfolge) - show_in_tree/show_in_hover Filterung auf Graph-Nodes und Tooltip - Badge-Werte im Graph mit Feldbezeichnung (Label: Wert) - Tooltip: Farbige Badge-Kaesten, Typ/System entfernt (redundant) - Shared Library lib/graph_view.lib.php (Toolbar, Container, Legende) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e6b28fe85e
commit
f7f84228ad
15 changed files with 473 additions and 186 deletions
17
ChangeLog.md
17
ChangeLog.md
|
|
@ -1,5 +1,22 @@
|
|||
# CHANGELOG MODULE KUNDENKARTE FOR [DOLIBARR ERP CRM](https://www.dolibarr.org)
|
||||
|
||||
## 5.1.0 (2026-02)
|
||||
|
||||
### Verbesserungen
|
||||
- **Graph-Ansicht: Intelligente Feldanzeige**
|
||||
- Felder nach `position` sortiert (nicht mehr nach JSON-Reihenfolge)
|
||||
- Nur Felder mit `show_in_tree=1` werden auf den Graph-Nodes angezeigt
|
||||
- Nur Felder mit `show_in_hover=1` erscheinen im Tooltip
|
||||
- Badge-Werte im Graph mit Feldbezeichnung (z.B. "Hersteller: ABB")
|
||||
- Tooltip: Typ/System entfernt (redundant mit Graph-Node)
|
||||
- Tooltip: Farbige Badge-Kaesten wie in der Baumansicht
|
||||
- Shared Library `lib/graph_view.lib.php` fuer Toolbar/Container/Legende
|
||||
|
||||
### Neue Dateien
|
||||
- `lib/graph_view.lib.php` - Gemeinsame Graph-Funktionen (Toolbar, Container, Legende)
|
||||
|
||||
---
|
||||
|
||||
## 5.0.0 (2026-02)
|
||||
|
||||
### Neue Features
|
||||
|
|
|
|||
104
ajax/graph_data.php
Normal file → Executable file
104
ajax/graph_data.php
Normal file → Executable file
|
|
@ -43,8 +43,9 @@ if ($socId <= 0) {
|
|||
}
|
||||
|
||||
// Feld-Metadaten laden (field_code → Label, Display-Modus, Badge-Farbe pro Typ)
|
||||
$fieldMeta = array(); // [fk_anlage_type][field_code] = {label, display_mode, badge_color}
|
||||
$sqlFields = "SELECT fk_anlage_type, field_code, field_label, field_type, tree_display_mode, badge_color";
|
||||
// Sortiert nach position → Reihenfolge wird in der Graph-Ansicht beibehalten
|
||||
$fieldMeta = array(); // [fk_anlage_type][field_code] = {label, display_mode, badge_color, show_in_tree, show_in_hover}
|
||||
$sqlFields = "SELECT fk_anlage_type, field_code, field_label, field_type, tree_display_mode, badge_color, show_in_tree, show_in_hover";
|
||||
$sqlFields .= " FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_type_field WHERE active = 1 ORDER BY position";
|
||||
$resFields = $db->query($sqlFields);
|
||||
if ($resFields) {
|
||||
|
|
@ -55,6 +56,8 @@ if ($resFields) {
|
|||
'display' => $fObj->tree_display_mode ?: 'badge',
|
||||
'color' => $fObj->badge_color ?: '',
|
||||
'type' => $fObj->field_type,
|
||||
'show_in_tree' => (int) $fObj->show_in_tree,
|
||||
'show_in_hover' => (int) $fObj->show_in_hover,
|
||||
);
|
||||
}
|
||||
$db->free($resFields);
|
||||
|
|
@ -87,15 +90,29 @@ $sql .= " ORDER BY a.fk_parent, a.rang, a.rowid";
|
|||
|
||||
$elements = array('nodes' => array(), 'edges' => array());
|
||||
$nodeIds = array();
|
||||
// Zwischenspeicher: rowid → isBuilding (für Compound-Entscheidung)
|
||||
$nodeIsBuilding = array();
|
||||
// Hierarchie-Kanten (Gerät→Gerät), werden durch echte Kabel ersetzt falls vorhanden
|
||||
$hierarchyEdges = array();
|
||||
// Zwischenspeicher: alle DB-Zeilen für Zwei-Pass-Verarbeitung
|
||||
$rows = array();
|
||||
|
||||
$resql = $db->query($sql);
|
||||
if ($resql) {
|
||||
// 1. Pass: Alle Zeilen laden und Gebäude-Typen merken
|
||||
while ($obj = $db->fetch_object($resql)) {
|
||||
// Typ bestimmt ob Raum oder Gerät (GLOBAL-Typ = Gebäude/Raum)
|
||||
$isBuilding = (!empty($obj->type_system_code) && $obj->type_system_code === 'GLOBAL');
|
||||
$nodeIsBuilding[(int)$obj->rowid] = $isBuilding;
|
||||
$nodeIds[$obj->rowid] = true;
|
||||
$rows[] = $obj;
|
||||
}
|
||||
$db->free($resql);
|
||||
|
||||
// 2. Pass: Nodes und Hierarchie-Edges aufbauen
|
||||
foreach ($rows as $obj) {
|
||||
$isBuilding = $nodeIsBuilding[(int)$obj->rowid];
|
||||
|
||||
$nodeId = 'n_'.$obj->rowid;
|
||||
$nodeIds[$obj->rowid] = true;
|
||||
|
||||
$nodeData = array(
|
||||
'id' => $nodeId,
|
||||
|
|
@ -115,38 +132,64 @@ if ($resql) {
|
|||
);
|
||||
|
||||
// Feldwerte mit Metadaten (Label, Display-Modus, Badge-Farbe)
|
||||
// Iteration über $fieldMeta (nach position sortiert), nicht über $rawValues (JSON-Reihenfolge)
|
||||
// Aufteilen: fields = auf dem Node (show_in_tree=1), hover_fields = im Tooltip (show_in_hover=1)
|
||||
if (!empty($obj->field_values)) {
|
||||
$rawValues = json_decode($obj->field_values, true);
|
||||
if (is_array($rawValues) && !empty($rawValues)) {
|
||||
$typeId = (int) $obj->fk_anlage_type;
|
||||
$meta = isset($fieldMeta[$typeId]) ? $fieldMeta[$typeId] : array();
|
||||
$fields = array();
|
||||
foreach ($rawValues as $code => $val) {
|
||||
$treeFields = array();
|
||||
$hoverFields = array();
|
||||
foreach ($meta as $code => $fm) {
|
||||
$val = isset($rawValues[$code]) ? $rawValues[$code] : null;
|
||||
if ($val === '' || $val === null) continue;
|
||||
$fm = isset($meta[$code]) ? $meta[$code] : array();
|
||||
$display = isset($fm['display']) ? $fm['display'] : 'badge';
|
||||
// Versteckte Felder überspringen
|
||||
if ($display === 'none') continue;
|
||||
$label = isset($fm['label']) ? $fm['label'] : $code;
|
||||
// Checkbox-Werte anpassen
|
||||
if (isset($fm['type']) && $fm['type'] === 'checkbox') {
|
||||
if ($fm['type'] === 'checkbox') {
|
||||
$val = $val ? '1' : '0';
|
||||
}
|
||||
$fields[] = array(
|
||||
'label' => $label,
|
||||
$fieldEntry = array(
|
||||
'label' => $fm['label'],
|
||||
'value' => $val,
|
||||
'display' => $display,
|
||||
'color' => isset($fm['color']) ? $fm['color'] : '',
|
||||
'type' => isset($fm['type']) ? $fm['type'] : 'text',
|
||||
'display' => $fm['display'],
|
||||
'color' => $fm['color'],
|
||||
'type' => $fm['type'],
|
||||
);
|
||||
// Auf dem Node: nur Felder mit show_in_tree=1
|
||||
if (!empty($fm['show_in_tree'])) {
|
||||
$treeFields[] = $fieldEntry;
|
||||
}
|
||||
// Im Tooltip: nur Felder mit show_in_hover=1
|
||||
if (!empty($fm['show_in_hover'])) {
|
||||
$hoverFields[] = $fieldEntry;
|
||||
}
|
||||
}
|
||||
$nodeData['fields'] = $fields;
|
||||
$nodeData['fields'] = $treeFields;
|
||||
$nodeData['hover_fields'] = $hoverFields;
|
||||
}
|
||||
}
|
||||
|
||||
// Compound-Parent aus fk_parent (Eltern-Kind-Verschachtelung)
|
||||
if ($obj->fk_parent > 0) {
|
||||
$nodeData['parent'] = 'n_'.$obj->fk_parent;
|
||||
// Compound-Parent: NUR wenn Eltern-Node ein Gebäude/Raum ist
|
||||
// Gerät→Gerät Hierarchie wird als Kante dargestellt (nicht verschachtelt)
|
||||
$parentId = (int) $obj->fk_parent;
|
||||
if ($parentId > 0 && isset($nodeIds[$parentId])) {
|
||||
$parentIsBuilding = !empty($nodeIsBuilding[$parentId]);
|
||||
if ($parentIsBuilding) {
|
||||
// Gebäude/Raum als Container → Compound-Parent
|
||||
$nodeData['parent'] = 'n_'.$parentId;
|
||||
} else {
|
||||
// Gerät→Gerät → Hierarchie-Kante vormerken (wird ggf. durch Kabel ersetzt)
|
||||
$hierKey = min($parentId, (int)$obj->rowid).'_'.max($parentId, (int)$obj->rowid);
|
||||
$hierarchyEdges[$hierKey] = array(
|
||||
'data' => array(
|
||||
'id' => 'hier_'.$parentId.'_'.$obj->rowid,
|
||||
'source' => 'n_'.$parentId,
|
||||
'target' => 'n_'.$obj->rowid,
|
||||
'is_hierarchy' => true,
|
||||
),
|
||||
'classes' => 'hierarchy-edge'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$elements['nodes'][] = array(
|
||||
|
|
@ -154,7 +197,6 @@ if ($resql) {
|
|||
'classes' => $isBuilding ? 'building-node' : 'device-node'
|
||||
);
|
||||
}
|
||||
$db->free($resql);
|
||||
}
|
||||
|
||||
// Verbindungen laden
|
||||
|
|
@ -164,6 +206,9 @@ $connections = $connObj->fetchBySociete($socId, 0);
|
|||
// Verwendete Kabeltypen für die Legende sammeln
|
||||
$usedCableTypes = array();
|
||||
|
||||
// Kabel-Paare merken (um Hierarchie-Kanten zu ersetzen)
|
||||
$cableConnectedPairs = array();
|
||||
|
||||
foreach ($connections as $conn) {
|
||||
// Nur Edges für tatsächlich geladene Nodes
|
||||
if (!isset($nodeIds[$conn->fk_source]) || !isset($nodeIds[$conn->fk_target])) {
|
||||
|
|
@ -207,6 +252,14 @@ foreach ($connections as $conn) {
|
|||
}
|
||||
}
|
||||
|
||||
// Echte Kabelverbindung (nicht durchgeschleift) → Hierarchie-Kante überflüssig
|
||||
if (!$isPassthrough) {
|
||||
$src = (int) $conn->fk_source;
|
||||
$tgt = (int) $conn->fk_target;
|
||||
$pairKey = min($src, $tgt).'_'.max($src, $tgt);
|
||||
$cableConnectedPairs[$pairKey] = true;
|
||||
}
|
||||
|
||||
$elements['edges'][] = array(
|
||||
'data' => array(
|
||||
'id' => 'conn_'.$conn->id,
|
||||
|
|
@ -224,6 +277,13 @@ foreach ($connections as $conn) {
|
|||
);
|
||||
}
|
||||
|
||||
// Hierarchie-Kanten hinzufügen, aber nur wenn kein echtes Kabel zwischen den Geräten existiert
|
||||
foreach ($hierarchyEdges as $hierKey => $hierEdge) {
|
||||
if (!isset($cableConnectedPairs[$hierKey])) {
|
||||
$elements['edges'][] = $hierEdge;
|
||||
}
|
||||
}
|
||||
|
||||
// Prüfen ob gespeicherte Positionen vorhanden sind
|
||||
$hasPositions = false;
|
||||
foreach ($elements['nodes'] as $node) {
|
||||
|
|
|
|||
0
ajax/graph_save_positions.php
Normal file → Executable file
0
ajax/graph_save_positions.php
Normal file → Executable file
|
|
@ -76,7 +76,7 @@ class modKundenKarte extends DolibarrModules
|
|||
$this->editor_squarred_logo = ''; // Must be image filename into the module/img directory followed with @modulename. Example: 'myimage.png@kundenkarte'
|
||||
|
||||
// Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z'
|
||||
$this->version = '5.0.0';
|
||||
$this->version = '5.1.0';
|
||||
// Url to the file with your last numberversion of this module
|
||||
//$this->url_last_version = 'http://www.example.com/versionmodule.txt';
|
||||
|
||||
|
|
|
|||
95
css/kundenkarte_cytoscape.css
Normal file → Executable file
95
css/kundenkarte_cytoscape.css
Normal file → Executable file
|
|
@ -134,6 +134,12 @@
|
|||
height: 0;
|
||||
}
|
||||
|
||||
.kundenkarte-graph-legend-line.hierarchy {
|
||||
background: none;
|
||||
border-top: 2px dotted #6a7a8a;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.kundenkarte-graph-legend-box {
|
||||
width: 16px;
|
||||
height: 12px;
|
||||
|
|
@ -177,46 +183,109 @@
|
|||
position: absolute;
|
||||
z-index: 100;
|
||||
background: var(--colorbacktabcard1, #1e2a3a);
|
||||
border: 1px solid var(--inputbordercolor, #3a6a8e);
|
||||
border-radius: 6px;
|
||||
padding: 10px 14px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 8px;
|
||||
padding: 0;
|
||||
font-size: 12px;
|
||||
color: var(--colortext, #ccc);
|
||||
max-width: 300px;
|
||||
max-width: 320px;
|
||||
min-width: 200px;
|
||||
pointer-events: none;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5), 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.kundenkarte-graph-tooltip .tooltip-header {
|
||||
padding: 10px 14px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.kundenkarte-graph-tooltip .tooltip-title {
|
||||
font-weight: bold;
|
||||
color: var(--colortextlink, #7ab0d4);
|
||||
margin-bottom: 4px;
|
||||
font-size: 13px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.kundenkarte-graph-tooltip .tooltip-title i {
|
||||
margin-right: 4px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.kundenkarte-graph-tooltip .tooltip-type {
|
||||
color: var(--colortext, #888);
|
||||
color: var(--colortext, #999);
|
||||
font-size: 11px;
|
||||
margin-bottom: 6px;
|
||||
margin-top: 3px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.kundenkarte-graph-tooltip .tooltip-system {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
padding: 1px 6px;
|
||||
border-radius: 3px;
|
||||
font-size: 10px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.kundenkarte-graph-tooltip .tooltip-fields {
|
||||
padding: 6px 14px;
|
||||
}
|
||||
|
||||
.kundenkarte-graph-tooltip .tooltip-field {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
padding: 2px 0;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
align-items: baseline;
|
||||
gap: 12px;
|
||||
padding: 3px 0;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
.kundenkarte-graph-tooltip .tooltip-field:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.kundenkarte-graph-tooltip .tooltip-field-label {
|
||||
color: var(--colortext, #888);
|
||||
opacity: 0.7;
|
||||
opacity: 0.6;
|
||||
font-size: 11px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.kundenkarte-graph-tooltip .tooltip-field-value {
|
||||
color: var(--colortext, #ddd);
|
||||
color: var(--colortext, #eee);
|
||||
text-align: right;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.kundenkarte-graph-tooltip .tooltip-field-badge {
|
||||
color: #fff;
|
||||
text-align: right;
|
||||
font-weight: 600;
|
||||
font-size: 11px;
|
||||
padding: 1px 8px;
|
||||
border-radius: 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.kundenkarte-graph-tooltip .tooltip-footer {
|
||||
padding: 6px 14px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.06);
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
font-size: 11px;
|
||||
color: var(--colortext, #888);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.kundenkarte-graph-tooltip .tooltip-file-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.kundenkarte-graph-tooltip .tooltip-file-badge i {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
/* Leer-Zustand */
|
||||
|
|
|
|||
0
js/cose-base.js
Normal file → Executable file
0
js/cose-base.js
Normal file → Executable file
0
js/cytoscape-cose-bilkent.js
Normal file → Executable file
0
js/cytoscape-cose-bilkent.js
Normal file → Executable file
0
js/cytoscape-dagre.js
Normal file → Executable file
0
js/cytoscape-dagre.js
Normal file → Executable file
0
js/cytoscape.min.js
vendored
Normal file → Executable file
0
js/cytoscape.min.js
vendored
Normal file → Executable file
0
js/dagre.min.js
vendored
Normal file → Executable file
0
js/dagre.min.js
vendored
Normal file → Executable file
101
js/kundenkarte_cytoscape.js
Normal file → Executable file
101
js/kundenkarte_cytoscape.js
Normal file → Executable file
|
|
@ -203,6 +203,10 @@
|
|||
html += '<span class="kundenkarte-graph-legend-item">';
|
||||
html += '<span class="kundenkarte-graph-legend-line passthrough"></span> Durchgeschleift</span>';
|
||||
|
||||
// Hierarchie (Eltern→Kind Beziehung zwischen Geräten)
|
||||
html += '<span class="kundenkarte-graph-legend-item">';
|
||||
html += '<span class="kundenkarte-graph-legend-line hierarchy"></span> Hierarchie</span>';
|
||||
|
||||
$legend.html(html);
|
||||
},
|
||||
|
||||
|
|
@ -281,7 +285,7 @@
|
|||
? namePart + ' (' + parens.join(', ') + ')'
|
||||
: namePart;
|
||||
} else {
|
||||
// Gerät: Name (+ Klammer-Felder) + Trennlinie + Badge-Felder
|
||||
// Gerät: Name (+ Klammer) + Typ + Trennlinie + Badge-Werte mit Feldbezeichnung
|
||||
var parenParts = [];
|
||||
var badgeLines = [];
|
||||
|
||||
|
|
@ -295,6 +299,7 @@
|
|||
if (f.display === 'parentheses') {
|
||||
parenParts.push(v);
|
||||
} else if (f.display === 'badge') {
|
||||
// Feldname: Wert
|
||||
badgeLines.push(f.label + ': ' + v);
|
||||
}
|
||||
}
|
||||
|
|
@ -307,12 +312,18 @@
|
|||
} else {
|
||||
lines.push(namePart);
|
||||
}
|
||||
// Badge-Felder als Karten-Zeilen
|
||||
if (badgeLines.length > 0) {
|
||||
lines.push('─────────────');
|
||||
for (var j = 0; j < badgeLines.length; j++) {
|
||||
lines.push(badgeLines[j]);
|
||||
}
|
||||
// Trennlinie + Typ + Badge-Werte
|
||||
var hasDetails = (n.data.type_label || badgeLines.length > 0);
|
||||
if (hasDetails) {
|
||||
lines.push('────────────────────');
|
||||
}
|
||||
// Typ-Bezeichnung immer unter dem Strich
|
||||
if (n.data.type_label) {
|
||||
lines.push('Typ: ' + n.data.type_label);
|
||||
}
|
||||
// Badge-Werte mit Feldbezeichnung
|
||||
for (var j = 0; j < badgeLines.length; j++) {
|
||||
lines.push(badgeLines[j]);
|
||||
}
|
||||
|
||||
// Datei-Indikatoren
|
||||
|
|
@ -320,8 +331,8 @@
|
|||
if (n.data.image_count > 0) fileInfo.push('\ud83d\uddbc ' + n.data.image_count);
|
||||
if (n.data.doc_count > 0) fileInfo.push('\ud83d\udcc4 ' + n.data.doc_count);
|
||||
if (fileInfo.length > 0) {
|
||||
if (badgeLines.length === 0) lines.push('─────────────');
|
||||
lines.push(fileInfo.join(' '));
|
||||
if (badgeLines.length === 0) lines.push('────────────────────');
|
||||
lines.push(fileInfo.join(' \u00b7 '));
|
||||
}
|
||||
|
||||
n.data.display_label = lines.join('\n');
|
||||
|
|
@ -453,31 +464,32 @@
|
|||
'min-height': '60px'
|
||||
}
|
||||
},
|
||||
// Geräte - Karten-Design mit Feldwerten
|
||||
// Geräte - Karten-Design mit Feldwerten (kompakt)
|
||||
{
|
||||
selector: '.device-node',
|
||||
style: {
|
||||
'shape': 'roundrectangle',
|
||||
'width': 'label',
|
||||
'height': 'label',
|
||||
'padding': '14px',
|
||||
'background-color': '#2d4a3a',
|
||||
'padding': '12px',
|
||||
'background-color': '#2d3d35',
|
||||
'background-opacity': 0.95,
|
||||
'border-width': 2,
|
||||
'border-color': function(node) { return node.data('type_color') || '#5a9a6a'; },
|
||||
'label': 'data(display_label)',
|
||||
'font-family': faFont,
|
||||
'font-weight': 'bold',
|
||||
'font-weight': 900,
|
||||
'text-valign': 'center',
|
||||
'text-halign': 'center',
|
||||
'text-justification': 'left',
|
||||
'font-size': '11px',
|
||||
'color': textColor,
|
||||
'text-wrap': 'wrap',
|
||||
'text-max-width': '220px',
|
||||
'line-height': 1.4
|
||||
'text-max-width': '240px',
|
||||
'line-height': 1.5
|
||||
}
|
||||
},
|
||||
// Kabel - Farbe aus medium_color, Fallback grün
|
||||
// Kabel - Farbe aus medium_color, Fallback grün, rechtwinklig
|
||||
{
|
||||
selector: '.cable-edge',
|
||||
style: {
|
||||
|
|
@ -487,7 +499,9 @@
|
|||
},
|
||||
'target-arrow-shape': 'none',
|
||||
'source-arrow-shape': 'none',
|
||||
'curve-style': 'bezier',
|
||||
'curve-style': 'taxi',
|
||||
'taxi-direction': 'downward',
|
||||
'taxi-turn': '40px',
|
||||
'label': 'data(label)',
|
||||
'font-size': '9px',
|
||||
'color': '#8a9aa8',
|
||||
|
|
@ -510,6 +524,21 @@
|
|||
'curve-style': 'bezier'
|
||||
}
|
||||
},
|
||||
// Hierarchie-Kanten (Gerät→Gerät Eltern-Kind, rechtwinklig)
|
||||
{
|
||||
selector: '.hierarchy-edge',
|
||||
style: {
|
||||
'width': 1.5,
|
||||
'line-color': '#6a7a8a',
|
||||
'line-style': 'dotted',
|
||||
'target-arrow-shape': 'none',
|
||||
'source-arrow-shape': 'none',
|
||||
'curve-style': 'taxi',
|
||||
'taxi-direction': 'downward',
|
||||
'taxi-turn': '30px',
|
||||
'opacity': 0.6
|
||||
}
|
||||
},
|
||||
// Hover
|
||||
{
|
||||
selector: 'node:active',
|
||||
|
|
@ -999,35 +1028,45 @@
|
|||
*/
|
||||
showNodeTooltip: function(node, position) {
|
||||
var data = node.data();
|
||||
|
||||
// Header: Typ-Farbe als Akzentlinie
|
||||
var accentColor = data.type_color || '#5a9a6a';
|
||||
var html = '<div class="tooltip-header" style="border-left:3px solid ' + accentColor + ';padding-left:10px;">';
|
||||
// Überschrift: Icon + Bezeichnung
|
||||
var html = '<div class="tooltip-title">';
|
||||
html += '<div class="tooltip-title">';
|
||||
if (data.type_picto) {
|
||||
html += '<i class="fa ' + this.escapeHtml(data.type_picto) + '"></i> ';
|
||||
}
|
||||
html += this.escapeHtml(data.label) + '</div>';
|
||||
html += '</div>';
|
||||
|
||||
if (data.fields && data.fields.length > 0) {
|
||||
for (var i = 0; i < data.fields.length && i < 10; i++) {
|
||||
var f = data.fields[i];
|
||||
// Hover-Felder anzeigen (nach Position sortiert, vom Backend gefiltert)
|
||||
var hoverFields = data.hover_fields || [];
|
||||
if (hoverFields.length > 0) {
|
||||
html += '<div class="tooltip-fields">';
|
||||
for (var i = 0; i < hoverFields.length; i++) {
|
||||
var f = hoverFields[i];
|
||||
if (!f.value || f.value === '') continue;
|
||||
var val = f.value;
|
||||
if (f.type === 'checkbox' && (val === '1' || val === 'true')) val = '\u2713';
|
||||
if (f.type === 'checkbox' && (val === '0' || val === 'false')) val = '\u2717';
|
||||
html += '<div class="tooltip-field">';
|
||||
html += '<span class="tooltip-field-label">' + this.escapeHtml(f.label) + '</span>';
|
||||
if (f.display === 'badge') {
|
||||
var bgColor = f.color || '#4a5568';
|
||||
html += '<span class="tooltip-field-value" style="background:' + bgColor + ';padding:1px 6px;border-radius:3px;color:#fff;">' + this.escapeHtml(String(val)) + '</span>';
|
||||
if (f.color) {
|
||||
html += '<span class="tooltip-field-badge" style="background:' + f.color + ';">' + this.escapeHtml(String(val)) + '</span>';
|
||||
} else {
|
||||
html += '<span class="tooltip-field-value">' + this.escapeHtml(String(val)) + '</span>';
|
||||
}
|
||||
html += '</div>';
|
||||
}
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
// Datei-Info am unteren Rand
|
||||
if (data.image_count > 0 || data.doc_count > 0) {
|
||||
html += '<div class="tooltip-type" style="margin-top:4px;">';
|
||||
if (data.image_count > 0) html += '<i class="fa fa-image"></i> ' + data.image_count + ' ';
|
||||
if (data.doc_count > 0) html += '<i class="fa fa-file"></i> ' + data.doc_count;
|
||||
html += '<div class="tooltip-footer">';
|
||||
if (data.image_count > 0) html += '<span class="tooltip-file-badge"><i class="fa fa-image"></i> ' + data.image_count + '</span>';
|
||||
if (data.doc_count > 0) html += '<span class="tooltip-file-badge"><i class="fa fa-file-text-o"></i> ' + data.doc_count + '</span>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
|
|
@ -1041,7 +1080,12 @@
|
|||
*/
|
||||
showEdgeTooltip: function(edge, position) {
|
||||
var data = edge.data();
|
||||
var html = '<div class="tooltip-title">Verbindung</div>';
|
||||
// Farbe der Verbindung als Akzent
|
||||
var edgeColor = data.medium_color || '#5a8a5a';
|
||||
var html = '<div class="tooltip-header" style="border-left:3px solid ' + edgeColor + ';padding-left:10px;">';
|
||||
html += '<div class="tooltip-title"><i class="fa fa-plug"></i> Verbindung</div>';
|
||||
html += '</div>';
|
||||
html += '<div class="tooltip-fields">';
|
||||
if (data.medium_type) {
|
||||
html += '<div class="tooltip-field"><span class="tooltip-field-label">Typ</span><span class="tooltip-field-value">' + this.escapeHtml(data.medium_type) + '</span></div>';
|
||||
}
|
||||
|
|
@ -1051,6 +1095,7 @@
|
|||
if (data.medium_length) {
|
||||
html += '<div class="tooltip-field"><span class="tooltip-field-label">Länge</span><span class="tooltip-field-value">' + this.escapeHtml(data.medium_length) + '</span></div>';
|
||||
}
|
||||
html += '</div>';
|
||||
this.tooltipEl.innerHTML = html;
|
||||
this.tooltipEl.style.display = 'block';
|
||||
this.positionTooltip(position);
|
||||
|
|
|
|||
0
js/layout-base.js
Normal file → Executable file
0
js/layout-base.js
Normal file → Executable file
160
lib/graph_view.lib.php
Normal file
160
lib/graph_view.lib.php
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 Alles Watt lauft
|
||||
*
|
||||
* Gemeinsame Graph-Ansicht Funktionen für anlagen.php und contact_anlagen.php
|
||||
* Vermeidet doppelten Code für Toolbar, Container, Kontextmenü, Legende
|
||||
*/
|
||||
|
||||
/**
|
||||
* Graph-spezifische JS/CSS-Dateien zu den Asset-Arrays hinzufügen
|
||||
*
|
||||
* @param array $jsFiles Referenz auf JS-Array
|
||||
* @param array $cssFiles Referenz auf CSS-Array
|
||||
* @param string $viewMode 'tree' oder 'graph'
|
||||
*/
|
||||
function kundenkarte_graph_add_assets(&$jsFiles, &$cssFiles, $viewMode)
|
||||
{
|
||||
if ($viewMode === 'graph') {
|
||||
$jsFiles[] = '/kundenkarte/js/dagre.min.js';
|
||||
$jsFiles[] = '/kundenkarte/js/cytoscape.min.js';
|
||||
$jsFiles[] = '/kundenkarte/js/cytoscape-dagre.js';
|
||||
$jsFiles[] = '/kundenkarte/js/kundenkarte_cytoscape.js?v='.time();
|
||||
$cssFiles[] = '/kundenkarte/css/kundenkarte_cytoscape.css?v='.time();
|
||||
} else {
|
||||
array_unshift($jsFiles, '/kundenkarte/js/pathfinding.min.js');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Graph-Toolbar rendern (2 Zeilen: Aktionen + Steuerung)
|
||||
* Wird UNTER der System-Tab-Borderlinie ausgegeben
|
||||
*
|
||||
* @param array $params Konfiguration:
|
||||
* 'socid' int Kunden-ID
|
||||
* 'contactid' int Kontakt-ID (0 für Kundenansicht)
|
||||
* 'systemid' int Aktuelles System
|
||||
* 'viewMode' string 'tree' oder 'graph'
|
||||
* 'permissiontoadd' bool Schreibberechtigung
|
||||
* 'pageUrl' string Aktuelle Seiten-URL ($_SERVER['PHP_SELF'])
|
||||
*/
|
||||
function kundenkarte_graph_print_toolbar($params)
|
||||
{
|
||||
global $langs;
|
||||
|
||||
$socId = (int) ($params['socid'] ?? 0);
|
||||
$contactId = (int) ($params['contactid'] ?? 0);
|
||||
$systemId = (int) ($params['systemid'] ?? 0);
|
||||
$viewMode = $params['viewMode'] ?? 'tree';
|
||||
$permissiontoadd = !empty($params['permissiontoadd']);
|
||||
$pageUrl = $params['pageUrl'] ?? $_SERVER['PHP_SELF'];
|
||||
|
||||
// View-Toggle URL: ID-Parameter je nach Kontext
|
||||
$idParam = ($contactId > 0) ? $contactId : $socId;
|
||||
$toggleView = ($viewMode === 'graph') ? 'tree' : 'graph';
|
||||
$toggleUrl = $pageUrl.'?id='.$idParam.'&system='.$systemId.'&view='.$toggleView;
|
||||
$toggleIcon = ($viewMode === 'graph') ? 'fa-list' : 'fa-sitemap';
|
||||
$toggleLabel = ($viewMode === 'graph') ? $langs->trans('TreeView') : $langs->trans('GraphView');
|
||||
|
||||
// Connection-URL: bei Kontakten wird socid + contactid übergeben
|
||||
$connUrlParams = 'socid='.$socId;
|
||||
if ($contactId > 0) {
|
||||
$connUrlParams .= '&contactid='.$contactId;
|
||||
}
|
||||
|
||||
print '<div class="kundenkarte-graph-toolbar">';
|
||||
|
||||
// Zeile 1: Ansicht-Wechsel + Aktionen
|
||||
print '<div class="kundenkarte-graph-toolbar-row">';
|
||||
print '<a class="button small" href="'.$toggleUrl.'" title="'.$toggleLabel.'"><i class="fa '.$toggleIcon.'"></i> '.$toggleLabel.'</a>';
|
||||
if ($permissiontoadd) {
|
||||
print '<a class="button small" href="'.$pageUrl.'?id='.$idParam.'&action=create&system='.$systemId.'"><i class="fa fa-plus"></i> '.$langs->trans('AddElement').'</a>';
|
||||
print '<a class="button small" href="'.dol_buildpath('/kundenkarte/anlage_connection.php', 1).'?'.$connUrlParams.'&system_id='.$systemId.'&action=create"><i class="fa fa-plug"></i> '.$langs->trans('AddConnection').'</a>';
|
||||
}
|
||||
print '</div>';
|
||||
|
||||
// Zeile 2: Graph-Steuerung (Anordnen rechts)
|
||||
print '<div class="kundenkarte-graph-toolbar-row">';
|
||||
print '<button type="button" class="button small" id="btn-graph-reset-layout" title="Layout zurücksetzen"><i class="fa fa-refresh"></i> Layout</button>';
|
||||
print '<button type="button" class="button small" id="btn-graph-wheel-zoom" title="Mausrad-Zoom"><i class="fa fa-arrows"></i> Scroll</button>';
|
||||
print '<button type="button" class="button small" id="btn-graph-zoom-in" title="Vergrößern"><i class="fa fa-search-plus"></i></button>';
|
||||
print '<button type="button" class="button small" id="btn-graph-zoom-out" title="Verkleinern"><i class="fa fa-search-minus"></i></button>';
|
||||
print '<button type="button" class="button small" id="btn-graph-fit" title="Einpassen"><i class="fa fa-crosshairs"></i> Fit</button>';
|
||||
print '<button type="button" class="button small" id="btn-graph-export-png" title="PNG exportieren"><i class="fa fa-download"></i> PNG</button>';
|
||||
if ($permissiontoadd) {
|
||||
print '<span class="kundenkarte-graph-toolbar-spacer"></span>';
|
||||
print '<button type="button" class="button small" id="btn-graph-edit-mode" title="Elemente anordnen"><i class="fa fa-hand-paper-o"></i> Anordnen</button>';
|
||||
print '<button type="button" class="button small btn-graph-save" id="btn-graph-save-positions" title="Positionen speichern" style="display:none;"><i class="fa fa-check"></i> Speichern</button>';
|
||||
print '<button type="button" class="button small btn-graph-cancel" id="btn-graph-cancel-edit" title="Abbrechen" style="display:none;"><i class="fa fa-times"></i> Abbrechen</button>';
|
||||
}
|
||||
print '</div>';
|
||||
|
||||
print '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Graph-Container, Kontextmenü und Legende rendern
|
||||
*
|
||||
* @param array $params Konfiguration:
|
||||
* 'socid' int Kunden-ID
|
||||
* 'contactid' int Kontakt-ID (0 für Kundenansicht)
|
||||
* 'systemid' int Aktuelles System
|
||||
* 'permissiontoadd' bool Schreibberechtigung
|
||||
* 'permissiontodelete' bool Löschberechtigung
|
||||
* 'pageUrl' string Aktuelle Seiten-URL
|
||||
*/
|
||||
function kundenkarte_graph_print_container($params)
|
||||
{
|
||||
global $langs;
|
||||
|
||||
$socId = (int) ($params['socid'] ?? 0);
|
||||
$contactId = (int) ($params['contactid'] ?? 0);
|
||||
$systemId = (int) ($params['systemid'] ?? 0);
|
||||
$permissiontoadd = !empty($params['permissiontoadd']);
|
||||
$permissiontodelete = !empty($params['permissiontodelete']);
|
||||
$pageUrl = $params['pageUrl'] ?? $_SERVER['PHP_SELF'];
|
||||
|
||||
$graphAjaxUrl = dol_buildpath('/kundenkarte/ajax/graph_data.php', 1);
|
||||
$graphSaveUrl = dol_buildpath('/kundenkarte/ajax/graph_save_positions.php', 1);
|
||||
$graphModuleUrl = dol_buildpath('/kundenkarte', 1);
|
||||
|
||||
print '<div class="kundenkarte-graph-wrapper">';
|
||||
|
||||
// Suchfeld als Overlay
|
||||
print '<div class="kundenkarte-graph-search-floating">';
|
||||
print '<input type="text" id="kundenkarte-graph-search" placeholder="'.$langs->trans('SearchPlaceholder').'" autocomplete="off">';
|
||||
print '</div>';
|
||||
|
||||
// Graph-Container mit Data-Attributen für JS-Initialisierung
|
||||
print '<div id="kundenkarte-graph-container"';
|
||||
print ' data-ajax-url="'.dol_escape_htmltag($graphAjaxUrl).'"';
|
||||
print ' data-save-url="'.dol_escape_htmltag($graphSaveUrl).'"';
|
||||
print ' data-module-url="'.dol_escape_htmltag($graphModuleUrl).'"';
|
||||
print ' data-socid="'.$socId.'"';
|
||||
if ($contactId > 0) {
|
||||
print ' data-contactid="'.$contactId.'"';
|
||||
}
|
||||
print ' data-systemid="'.$systemId.'"';
|
||||
print ' data-can-edit="'.($permissiontoadd ? '1' : '0').'"';
|
||||
print ' data-can-delete="'.($permissiontodelete ? '1' : '0').'"';
|
||||
print ' data-page-url="'.dol_escape_htmltag($pageUrl).'"';
|
||||
print '>';
|
||||
print '<div class="kundenkarte-graph-loading"><i class="fa fa-spinner fa-spin"></i> '.$langs->trans('GraphLoading').'</div>';
|
||||
print '</div>';
|
||||
|
||||
// Kontextmenü (Rechtsklick auf Node)
|
||||
print '<div id="kundenkarte-graph-contextmenu" class="kundenkarte-graph-contextmenu" style="display:none;">';
|
||||
print '<a class="ctx-item ctx-view" data-action="view"><i class="fa fa-eye"></i> '.$langs->trans('View').'</a>';
|
||||
if ($permissiontoadd) {
|
||||
print '<a class="ctx-item ctx-add-child" data-action="add-child"><i class="fa fa-plus"></i> '.$langs->trans('AddChild').'</a>';
|
||||
print '<a class="ctx-item ctx-edit" data-action="edit"><i class="fa fa-edit"></i> '.$langs->trans('Edit').'</a>';
|
||||
print '<a class="ctx-item ctx-copy" data-action="copy"><i class="fa fa-copy"></i> '.$langs->trans('Copy').'</a>';
|
||||
}
|
||||
if ($permissiontodelete) {
|
||||
print '<a class="ctx-item ctx-delete" data-action="delete"><i class="fa fa-trash"></i> '.$langs->trans('Delete').'</a>';
|
||||
}
|
||||
print '</div>';
|
||||
|
||||
// Legende - wird dynamisch vom JS befüllt (Kabeltypen mit Farben)
|
||||
print '<div id="kundenkarte-graph-legend" class="kundenkarte-graph-legend"></div>';
|
||||
print '</div>'; // End kundenkarte-graph-wrapper
|
||||
}
|
||||
|
|
@ -373,18 +373,11 @@ 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());
|
||||
|
||||
if ($viewMode === 'graph') {
|
||||
$jsFiles[] = '/kundenkarte/js/dagre.min.js';
|
||||
$jsFiles[] = '/kundenkarte/js/cytoscape.min.js';
|
||||
$jsFiles[] = '/kundenkarte/js/cytoscape-dagre.js';
|
||||
$jsFiles[] = '/kundenkarte/js/kundenkarte_cytoscape.js?v='.time();
|
||||
$cssFiles[] = '/kundenkarte/css/kundenkarte_cytoscape.css?v='.time();
|
||||
} else {
|
||||
array_unshift($jsFiles, '/kundenkarte/js/pathfinding.min.js');
|
||||
}
|
||||
kundenkarte_graph_add_assets($jsFiles, $cssFiles, $viewMode);
|
||||
|
||||
llxHeader('', $title, '', '', 0, 0, $jsFiles, $cssFiles);
|
||||
|
||||
|
|
@ -507,31 +500,14 @@ print '</div>'; // End kundenkarte-system-tabs-wrapper
|
|||
|
||||
// Graph-Toolbar: UNTER der System-Tab-Borderlinie
|
||||
if ($isTreeView && $viewMode === 'graph') {
|
||||
print '<div class="kundenkarte-graph-toolbar">';
|
||||
// Zeile 1: Ansicht-Wechsel + Aktionen
|
||||
print '<div class="kundenkarte-graph-toolbar-row">';
|
||||
print '<a class="button small" href="'.$toggleUrl.'" title="'.$toggleLabel.'"><i class="fa '.$toggleIcon.'"></i> '.$toggleLabel.'</a>';
|
||||
if ($user->hasRight('kundenkarte', 'write')) {
|
||||
print '<a class="button small" href="'.$_SERVER['PHP_SELF'].'?id='.$id.'&action=create&system='.$systemId.'"><i class="fa fa-plus"></i> '.$langs->trans('AddElement').'</a>';
|
||||
print '<a class="button small" href="'.dol_buildpath('/kundenkarte/anlage_connection.php', 1).'?socid='.$id.'&system_id='.$systemId.'&action=create"><i class="fa fa-plug"></i> '.$langs->trans('AddConnection').'</a>';
|
||||
}
|
||||
print '</div>';
|
||||
// Zeile 2: Graph-Steuerung (Anordnen rechts)
|
||||
print '<div class="kundenkarte-graph-toolbar-row">';
|
||||
print '<button type="button" class="button small" id="btn-graph-reset-layout" title="Layout zurücksetzen"><i class="fa fa-refresh"></i> Layout</button>';
|
||||
print '<button type="button" class="button small" id="btn-graph-wheel-zoom" title="Mausrad-Zoom"><i class="fa fa-arrows"></i> Scroll</button>';
|
||||
print '<button type="button" class="button small" id="btn-graph-zoom-in" title="Vergrößern"><i class="fa fa-search-plus"></i></button>';
|
||||
print '<button type="button" class="button small" id="btn-graph-zoom-out" title="Verkleinern"><i class="fa fa-search-minus"></i></button>';
|
||||
print '<button type="button" class="button small" id="btn-graph-fit" title="Einpassen"><i class="fa fa-crosshairs"></i> Fit</button>';
|
||||
print '<button type="button" class="button small" id="btn-graph-export-png" title="PNG exportieren"><i class="fa fa-download"></i> PNG</button>';
|
||||
if ($permissiontoadd) {
|
||||
print '<span class="kundenkarte-graph-toolbar-spacer"></span>';
|
||||
print '<button type="button" class="button small" id="btn-graph-edit-mode" title="Elemente anordnen"><i class="fa fa-hand-paper-o"></i> Anordnen</button>';
|
||||
print '<button type="button" class="button small btn-graph-save" id="btn-graph-save-positions" title="Positionen speichern" style="display:none;"><i class="fa fa-check"></i> Speichern</button>';
|
||||
print '<button type="button" class="button small btn-graph-cancel" id="btn-graph-cancel-edit" title="Abbrechen" style="display:none;"><i class="fa fa-times"></i> Abbrechen</button>';
|
||||
}
|
||||
print '</div>';
|
||||
print '</div>';
|
||||
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)
|
||||
|
|
@ -1062,43 +1038,14 @@ if (empty($customerSystems)) {
|
|||
|
||||
if ($viewMode === 'graph' && $isTreeView) {
|
||||
// Graph-Ansicht: Container rendern, Daten werden per AJAX geladen
|
||||
$graphAjaxUrl = dol_buildpath('/kundenkarte/ajax/graph_data.php', 1);
|
||||
$graphSaveUrl = dol_buildpath('/kundenkarte/ajax/graph_save_positions.php', 1);
|
||||
$graphModuleUrl = dol_buildpath('/kundenkarte', 1);
|
||||
|
||||
print '<div class="kundenkarte-graph-wrapper">';
|
||||
print '<div class="kundenkarte-graph-search-floating">';
|
||||
print '<input type="text" id="kundenkarte-graph-search" placeholder="'.$langs->trans('SearchPlaceholder').'" autocomplete="off">';
|
||||
print '</div>';
|
||||
print '<div id="kundenkarte-graph-container"';
|
||||
print ' data-ajax-url="'.dol_escape_htmltag($graphAjaxUrl).'"';
|
||||
print ' data-save-url="'.dol_escape_htmltag($graphSaveUrl).'"';
|
||||
print ' data-module-url="'.dol_escape_htmltag($graphModuleUrl).'"';
|
||||
print ' data-socid="'.$id.'"';
|
||||
print ' data-systemid="'.$systemId.'"';
|
||||
print ' data-can-edit="'.($permissiontoadd ? '1' : '0').'"';
|
||||
print ' data-can-delete="'.($permissiontodelete ? '1' : '0').'"';
|
||||
print ' data-page-url="'.dol_escape_htmltag($_SERVER['PHP_SELF']).'"';
|
||||
print '>';
|
||||
print '<div class="kundenkarte-graph-loading"><i class="fa fa-spinner fa-spin"></i> '.$langs->trans('GraphLoading').'</div>';
|
||||
print '</div>';
|
||||
|
||||
// Kontextmenü (Rechtsklick auf Node)
|
||||
print '<div id="kundenkarte-graph-contextmenu" class="kundenkarte-graph-contextmenu" style="display:none;">';
|
||||
print '<a class="ctx-item ctx-view" data-action="view"><i class="fa fa-eye"></i> '.$langs->trans('View').'</a>';
|
||||
if ($permissiontoadd) {
|
||||
print '<a class="ctx-item ctx-add-child" data-action="add-child"><i class="fa fa-plus"></i> '.$langs->trans('AddChild').'</a>';
|
||||
print '<a class="ctx-item ctx-edit" data-action="edit"><i class="fa fa-edit"></i> '.$langs->trans('Edit').'</a>';
|
||||
print '<a class="ctx-item ctx-copy" data-action="copy"><i class="fa fa-copy"></i> '.$langs->trans('Copy').'</a>';
|
||||
}
|
||||
if ($permissiontodelete) {
|
||||
print '<a class="ctx-item ctx-delete" data-action="delete"><i class="fa fa-trash"></i> '.$langs->trans('Delete').'</a>';
|
||||
}
|
||||
print '</div>';
|
||||
|
||||
// Legende - wird dynamisch vom JS befüllt (Kabeltypen mit Farben)
|
||||
print '<div id="kundenkarte-graph-legend" class="kundenkarte-graph-legend"></div>';
|
||||
print '</div>';
|
||||
kundenkarte_graph_print_container(array(
|
||||
'socid' => $id,
|
||||
'contactid' => 0,
|
||||
'systemid' => $systemId,
|
||||
'permissiontoadd' => $permissiontoadd,
|
||||
'permissiontodelete' => $permissiontodelete,
|
||||
'pageUrl' => $_SERVER['PHP_SELF'],
|
||||
));
|
||||
} else {
|
||||
// Baumansicht (klassisch)
|
||||
|
||||
|
|
|
|||
|
|
@ -365,21 +365,17 @@ if ($action == 'togglepin' && $permissiontoadd) {
|
|||
|
||||
$title = $langs->trans('TechnicalInstallations').' - '.$object->getFullName($langs);
|
||||
|
||||
// Ansichtsmodus (Admin-Setting)
|
||||
$viewMode = getDolGlobalString('KUNDENKARTE_DEFAULT_VIEW', 'tree');
|
||||
// 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());
|
||||
|
||||
if ($viewMode === 'graph') {
|
||||
$jsFiles[] = '/kundenkarte/js/dagre.min.js';
|
||||
$jsFiles[] = '/kundenkarte/js/cytoscape.min.js';
|
||||
$jsFiles[] = '/kundenkarte/js/cytoscape-dagre.js';
|
||||
$jsFiles[] = '/kundenkarte/js/kundenkarte_cytoscape.js?v='.time();
|
||||
$cssFiles[] = '/kundenkarte/css/kundenkarte_cytoscape.css?v='.time();
|
||||
} else {
|
||||
array_unshift($jsFiles, '/kundenkarte/js/pathfinding.min.js');
|
||||
}
|
||||
kundenkarte_graph_add_assets($jsFiles, $cssFiles, $viewMode);
|
||||
|
||||
llxHeader('', $title, '', '', 0, 0, $jsFiles, $cssFiles);
|
||||
|
||||
|
|
@ -465,29 +461,20 @@ if ($permissiontoadd) {
|
|||
}
|
||||
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) {
|
||||
if ($viewMode === 'graph') {
|
||||
// Graph Controls: Aktionen + Zoom
|
||||
print '<div class="kundenkarte-graph-toolbar">';
|
||||
if ($user->hasRight('kundenkarte', 'write')) {
|
||||
print '<div class="kundenkarte-graph-actions">';
|
||||
print '<a class="button small" href="'.$_SERVER['PHP_SELF'].'?id='.$id.'&action=create&system='.$systemId.'"><i class="fa fa-plus"></i> '.$langs->trans('AddElement').'</a>';
|
||||
print '<a class="button small" href="'.dol_buildpath('/kundenkarte/anlage_connection.php', 1).'?socid='.$object->socid.'&contactid='.$id.'&system_id='.$systemId.'&action=create"><i class="fa fa-plug"></i> '.$langs->trans('AddConnection').'</a>';
|
||||
print '</div>';
|
||||
}
|
||||
print '<div class="kundenkarte-graph-zoom-controls">';
|
||||
print '<button type="button" class="button small" id="btn-graph-reset-layout" title="Layout zurücksetzen"><i class="fa fa-undo"></i></button>';
|
||||
print '<button type="button" class="button small" id="btn-graph-wheel-zoom" title="Mausrad-Zoom ein"><i class="fa fa-mouse-pointer"></i></button>';
|
||||
print '<button type="button" class="button small" id="btn-graph-zoom-in" title="Zoom +"><i class="fa fa-search-plus"></i></button>';
|
||||
print '<button type="button" class="button small" id="btn-graph-zoom-out" title="Zoom -"><i class="fa fa-search-minus"></i></button>';
|
||||
print '<button type="button" class="button small" id="btn-graph-fit" title="Standard-Zoom"><i class="fa fa-crosshairs"></i></button>';
|
||||
print '</div>';
|
||||
print '</div>';
|
||||
} else {
|
||||
$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">';
|
||||
// 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 '<i class="fa fa-compress"></i> <span>Kompakt</span>';
|
||||
print '</button>';
|
||||
|
|
@ -509,6 +496,18 @@ if ($isTreeView) {
|
|||
|
||||
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;">';
|
||||
|
|
@ -1037,24 +1036,14 @@ if (empty($customerSystems)) {
|
|||
|
||||
if ($viewMode === 'graph' && $isTreeView) {
|
||||
// Graph-Ansicht: Container rendern, Daten werden per AJAX geladen
|
||||
$graphAjaxUrl = dol_buildpath('/kundenkarte/ajax/graph_data.php', 1);
|
||||
$graphSaveUrl = dol_buildpath('/kundenkarte/ajax/graph_save_positions.php', 1);
|
||||
$graphModuleUrl = dol_buildpath('/kundenkarte', 1);
|
||||
|
||||
print '<div class="kundenkarte-graph-wrapper">';
|
||||
print '<div id="kundenkarte-graph-container"';
|
||||
print ' data-ajax-url="'.dol_escape_htmltag($graphAjaxUrl).'"';
|
||||
print ' data-save-url="'.dol_escape_htmltag($graphSaveUrl).'"';
|
||||
print ' data-module-url="'.dol_escape_htmltag($graphModuleUrl).'"';
|
||||
print ' data-socid="'.$object->socid.'"';
|
||||
print ' data-contactid="'.$id.'"';
|
||||
print '>';
|
||||
print '<div class="kundenkarte-graph-loading"><i class="fa fa-spinner fa-spin"></i> '.$langs->trans('GraphLoading').'</div>';
|
||||
print '</div>';
|
||||
|
||||
// Legende - wird dynamisch vom JS befüllt (Kabeltypen mit Farben)
|
||||
print '<div id="kundenkarte-graph-legend" class="kundenkarte-graph-legend"></div>';
|
||||
print '</div>';
|
||||
kundenkarte_graph_print_container(array(
|
||||
'socid' => $object->socid,
|
||||
'contactid' => $id,
|
||||
'systemid' => $systemId,
|
||||
'permissiontoadd' => $permissiontoadd,
|
||||
'permissiontodelete' => $permissiontodelete,
|
||||
'pageUrl' => $_SERVER['PHP_SELF'],
|
||||
));
|
||||
} else {
|
||||
// Baumansicht (klassisch)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue