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)
|
# 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)
|
## 5.0.0 (2026-02)
|
||||||
|
|
||||||
### Neue Features
|
### 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)
|
// Feld-Metadaten laden (field_code → Label, Display-Modus, Badge-Farbe pro Typ)
|
||||||
$fieldMeta = array(); // [fk_anlage_type][field_code] = {label, display_mode, badge_color}
|
// Sortiert nach position → Reihenfolge wird in der Graph-Ansicht beibehalten
|
||||||
$sqlFields = "SELECT fk_anlage_type, field_code, field_label, field_type, tree_display_mode, badge_color";
|
$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";
|
$sqlFields .= " FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_type_field WHERE active = 1 ORDER BY position";
|
||||||
$resFields = $db->query($sqlFields);
|
$resFields = $db->query($sqlFields);
|
||||||
if ($resFields) {
|
if ($resFields) {
|
||||||
|
|
@ -55,6 +56,8 @@ if ($resFields) {
|
||||||
'display' => $fObj->tree_display_mode ?: 'badge',
|
'display' => $fObj->tree_display_mode ?: 'badge',
|
||||||
'color' => $fObj->badge_color ?: '',
|
'color' => $fObj->badge_color ?: '',
|
||||||
'type' => $fObj->field_type,
|
'type' => $fObj->field_type,
|
||||||
|
'show_in_tree' => (int) $fObj->show_in_tree,
|
||||||
|
'show_in_hover' => (int) $fObj->show_in_hover,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
$db->free($resFields);
|
$db->free($resFields);
|
||||||
|
|
@ -87,15 +90,29 @@ $sql .= " ORDER BY a.fk_parent, a.rang, a.rowid";
|
||||||
|
|
||||||
$elements = array('nodes' => array(), 'edges' => array());
|
$elements = array('nodes' => array(), 'edges' => array());
|
||||||
$nodeIds = 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);
|
$resql = $db->query($sql);
|
||||||
if ($resql) {
|
if ($resql) {
|
||||||
|
// 1. Pass: Alle Zeilen laden und Gebäude-Typen merken
|
||||||
while ($obj = $db->fetch_object($resql)) {
|
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');
|
$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;
|
$nodeId = 'n_'.$obj->rowid;
|
||||||
$nodeIds[$obj->rowid] = true;
|
|
||||||
|
|
||||||
$nodeData = array(
|
$nodeData = array(
|
||||||
'id' => $nodeId,
|
'id' => $nodeId,
|
||||||
|
|
@ -115,38 +132,64 @@ if ($resql) {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Feldwerte mit Metadaten (Label, Display-Modus, Badge-Farbe)
|
// 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)) {
|
if (!empty($obj->field_values)) {
|
||||||
$rawValues = json_decode($obj->field_values, true);
|
$rawValues = json_decode($obj->field_values, true);
|
||||||
if (is_array($rawValues) && !empty($rawValues)) {
|
if (is_array($rawValues) && !empty($rawValues)) {
|
||||||
$typeId = (int) $obj->fk_anlage_type;
|
$typeId = (int) $obj->fk_anlage_type;
|
||||||
$meta = isset($fieldMeta[$typeId]) ? $fieldMeta[$typeId] : array();
|
$meta = isset($fieldMeta[$typeId]) ? $fieldMeta[$typeId] : array();
|
||||||
$fields = array();
|
$treeFields = array();
|
||||||
foreach ($rawValues as $code => $val) {
|
$hoverFields = array();
|
||||||
|
foreach ($meta as $code => $fm) {
|
||||||
|
$val = isset($rawValues[$code]) ? $rawValues[$code] : null;
|
||||||
if ($val === '' || $val === null) continue;
|
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
|
// Checkbox-Werte anpassen
|
||||||
if (isset($fm['type']) && $fm['type'] === 'checkbox') {
|
if ($fm['type'] === 'checkbox') {
|
||||||
$val = $val ? '1' : '0';
|
$val = $val ? '1' : '0';
|
||||||
}
|
}
|
||||||
$fields[] = array(
|
$fieldEntry = array(
|
||||||
'label' => $label,
|
'label' => $fm['label'],
|
||||||
'value' => $val,
|
'value' => $val,
|
||||||
'display' => $display,
|
'display' => $fm['display'],
|
||||||
'color' => isset($fm['color']) ? $fm['color'] : '',
|
'color' => $fm['color'],
|
||||||
'type' => isset($fm['type']) ? $fm['type'] : 'text',
|
'type' => $fm['type'],
|
||||||
);
|
);
|
||||||
|
// Auf dem Node: nur Felder mit show_in_tree=1
|
||||||
|
if (!empty($fm['show_in_tree'])) {
|
||||||
|
$treeFields[] = $fieldEntry;
|
||||||
}
|
}
|
||||||
$nodeData['fields'] = $fields;
|
// Im Tooltip: nur Felder mit show_in_hover=1
|
||||||
|
if (!empty($fm['show_in_hover'])) {
|
||||||
|
$hoverFields[] = $fieldEntry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$nodeData['fields'] = $treeFields;
|
||||||
|
$nodeData['hover_fields'] = $hoverFields;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compound-Parent aus fk_parent (Eltern-Kind-Verschachtelung)
|
// Compound-Parent: NUR wenn Eltern-Node ein Gebäude/Raum ist
|
||||||
if ($obj->fk_parent > 0) {
|
// Gerät→Gerät Hierarchie wird als Kante dargestellt (nicht verschachtelt)
|
||||||
$nodeData['parent'] = 'n_'.$obj->fk_parent;
|
$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(
|
$elements['nodes'][] = array(
|
||||||
|
|
@ -154,7 +197,6 @@ if ($resql) {
|
||||||
'classes' => $isBuilding ? 'building-node' : 'device-node'
|
'classes' => $isBuilding ? 'building-node' : 'device-node'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
$db->free($resql);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verbindungen laden
|
// Verbindungen laden
|
||||||
|
|
@ -164,6 +206,9 @@ $connections = $connObj->fetchBySociete($socId, 0);
|
||||||
// Verwendete Kabeltypen für die Legende sammeln
|
// Verwendete Kabeltypen für die Legende sammeln
|
||||||
$usedCableTypes = array();
|
$usedCableTypes = array();
|
||||||
|
|
||||||
|
// Kabel-Paare merken (um Hierarchie-Kanten zu ersetzen)
|
||||||
|
$cableConnectedPairs = array();
|
||||||
|
|
||||||
foreach ($connections as $conn) {
|
foreach ($connections as $conn) {
|
||||||
// Nur Edges für tatsächlich geladene Nodes
|
// Nur Edges für tatsächlich geladene Nodes
|
||||||
if (!isset($nodeIds[$conn->fk_source]) || !isset($nodeIds[$conn->fk_target])) {
|
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(
|
$elements['edges'][] = array(
|
||||||
'data' => array(
|
'data' => array(
|
||||||
'id' => 'conn_'.$conn->id,
|
'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
|
// Prüfen ob gespeicherte Positionen vorhanden sind
|
||||||
$hasPositions = false;
|
$hasPositions = false;
|
||||||
foreach ($elements['nodes'] as $node) {
|
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'
|
$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'
|
// 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
|
// Url to the file with your last numberversion of this module
|
||||||
//$this->url_last_version = 'http://www.example.com/versionmodule.txt';
|
//$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;
|
height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.kundenkarte-graph-legend-line.hierarchy {
|
||||||
|
background: none;
|
||||||
|
border-top: 2px dotted #6a7a8a;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.kundenkarte-graph-legend-box {
|
.kundenkarte-graph-legend-box {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
|
|
@ -177,46 +183,109 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
background: var(--colorbacktabcard1, #1e2a3a);
|
background: var(--colorbacktabcard1, #1e2a3a);
|
||||||
border: 1px solid var(--inputbordercolor, #3a6a8e);
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
border-radius: 6px;
|
border-radius: 8px;
|
||||||
padding: 10px 14px;
|
padding: 0;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--colortext, #ccc);
|
color: var(--colortext, #ccc);
|
||||||
max-width: 300px;
|
max-width: 320px;
|
||||||
|
min-width: 200px;
|
||||||
pointer-events: none;
|
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 {
|
.kundenkarte-graph-tooltip .tooltip-title {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: var(--colortextlink, #7ab0d4);
|
color: var(--colortextlink, #7ab0d4);
|
||||||
margin-bottom: 4px;
|
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kundenkarte-graph-tooltip .tooltip-title i {
|
||||||
|
margin-right: 4px;
|
||||||
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.kundenkarte-graph-tooltip .tooltip-type {
|
.kundenkarte-graph-tooltip .tooltip-type {
|
||||||
color: var(--colortext, #888);
|
color: var(--colortext, #999);
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
margin-bottom: 6px;
|
margin-top: 3px;
|
||||||
opacity: 0.7;
|
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 {
|
.kundenkarte-graph-tooltip .tooltip-field {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 10px;
|
align-items: baseline;
|
||||||
padding: 2px 0;
|
gap: 12px;
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
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 {
|
.kundenkarte-graph-tooltip .tooltip-field-label {
|
||||||
color: var(--colortext, #888);
|
color: var(--colortext, #888);
|
||||||
opacity: 0.7;
|
opacity: 0.6;
|
||||||
|
font-size: 11px;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.kundenkarte-graph-tooltip .tooltip-field-value {
|
.kundenkarte-graph-tooltip .tooltip-field-value {
|
||||||
color: var(--colortext, #ddd);
|
color: var(--colortext, #eee);
|
||||||
text-align: right;
|
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 */
|
/* 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
97
js/kundenkarte_cytoscape.js
Normal file → Executable file
97
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-item">';
|
||||||
html += '<span class="kundenkarte-graph-legend-line passthrough"></span> Durchgeschleift</span>';
|
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);
|
$legend.html(html);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -281,7 +285,7 @@
|
||||||
? namePart + ' (' + parens.join(', ') + ')'
|
? namePart + ' (' + parens.join(', ') + ')'
|
||||||
: namePart;
|
: namePart;
|
||||||
} else {
|
} else {
|
||||||
// Gerät: Name (+ Klammer-Felder) + Trennlinie + Badge-Felder
|
// Gerät: Name (+ Klammer) + Typ + Trennlinie + Badge-Werte mit Feldbezeichnung
|
||||||
var parenParts = [];
|
var parenParts = [];
|
||||||
var badgeLines = [];
|
var badgeLines = [];
|
||||||
|
|
||||||
|
|
@ -295,6 +299,7 @@
|
||||||
if (f.display === 'parentheses') {
|
if (f.display === 'parentheses') {
|
||||||
parenParts.push(v);
|
parenParts.push(v);
|
||||||
} else if (f.display === 'badge') {
|
} else if (f.display === 'badge') {
|
||||||
|
// Feldname: Wert
|
||||||
badgeLines.push(f.label + ': ' + v);
|
badgeLines.push(f.label + ': ' + v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -307,21 +312,27 @@
|
||||||
} else {
|
} else {
|
||||||
lines.push(namePart);
|
lines.push(namePart);
|
||||||
}
|
}
|
||||||
// Badge-Felder als Karten-Zeilen
|
// Trennlinie + Typ + Badge-Werte
|
||||||
if (badgeLines.length > 0) {
|
var hasDetails = (n.data.type_label || badgeLines.length > 0);
|
||||||
lines.push('─────────────');
|
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++) {
|
for (var j = 0; j < badgeLines.length; j++) {
|
||||||
lines.push(badgeLines[j]);
|
lines.push(badgeLines[j]);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Datei-Indikatoren
|
// Datei-Indikatoren
|
||||||
var fileInfo = [];
|
var fileInfo = [];
|
||||||
if (n.data.image_count > 0) fileInfo.push('\ud83d\uddbc ' + n.data.image_count);
|
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 (n.data.doc_count > 0) fileInfo.push('\ud83d\udcc4 ' + n.data.doc_count);
|
||||||
if (fileInfo.length > 0) {
|
if (fileInfo.length > 0) {
|
||||||
if (badgeLines.length === 0) lines.push('─────────────');
|
if (badgeLines.length === 0) lines.push('────────────────────');
|
||||||
lines.push(fileInfo.join(' '));
|
lines.push(fileInfo.join(' \u00b7 '));
|
||||||
}
|
}
|
||||||
|
|
||||||
n.data.display_label = lines.join('\n');
|
n.data.display_label = lines.join('\n');
|
||||||
|
|
@ -453,31 +464,32 @@
|
||||||
'min-height': '60px'
|
'min-height': '60px'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Geräte - Karten-Design mit Feldwerten
|
// Geräte - Karten-Design mit Feldwerten (kompakt)
|
||||||
{
|
{
|
||||||
selector: '.device-node',
|
selector: '.device-node',
|
||||||
style: {
|
style: {
|
||||||
'shape': 'roundrectangle',
|
'shape': 'roundrectangle',
|
||||||
'width': 'label',
|
'width': 'label',
|
||||||
'height': 'label',
|
'height': 'label',
|
||||||
'padding': '14px',
|
'padding': '12px',
|
||||||
'background-color': '#2d4a3a',
|
'background-color': '#2d3d35',
|
||||||
|
'background-opacity': 0.95,
|
||||||
'border-width': 2,
|
'border-width': 2,
|
||||||
'border-color': function(node) { return node.data('type_color') || '#5a9a6a'; },
|
'border-color': function(node) { return node.data('type_color') || '#5a9a6a'; },
|
||||||
'label': 'data(display_label)',
|
'label': 'data(display_label)',
|
||||||
'font-family': faFont,
|
'font-family': faFont,
|
||||||
'font-weight': 'bold',
|
'font-weight': 900,
|
||||||
'text-valign': 'center',
|
'text-valign': 'center',
|
||||||
'text-halign': 'center',
|
'text-halign': 'center',
|
||||||
'text-justification': 'left',
|
'text-justification': 'left',
|
||||||
'font-size': '11px',
|
'font-size': '11px',
|
||||||
'color': textColor,
|
'color': textColor,
|
||||||
'text-wrap': 'wrap',
|
'text-wrap': 'wrap',
|
||||||
'text-max-width': '220px',
|
'text-max-width': '240px',
|
||||||
'line-height': 1.4
|
'line-height': 1.5
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Kabel - Farbe aus medium_color, Fallback grün
|
// Kabel - Farbe aus medium_color, Fallback grün, rechtwinklig
|
||||||
{
|
{
|
||||||
selector: '.cable-edge',
|
selector: '.cable-edge',
|
||||||
style: {
|
style: {
|
||||||
|
|
@ -487,7 +499,9 @@
|
||||||
},
|
},
|
||||||
'target-arrow-shape': 'none',
|
'target-arrow-shape': 'none',
|
||||||
'source-arrow-shape': 'none',
|
'source-arrow-shape': 'none',
|
||||||
'curve-style': 'bezier',
|
'curve-style': 'taxi',
|
||||||
|
'taxi-direction': 'downward',
|
||||||
|
'taxi-turn': '40px',
|
||||||
'label': 'data(label)',
|
'label': 'data(label)',
|
||||||
'font-size': '9px',
|
'font-size': '9px',
|
||||||
'color': '#8a9aa8',
|
'color': '#8a9aa8',
|
||||||
|
|
@ -510,6 +524,21 @@
|
||||||
'curve-style': 'bezier'
|
'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
|
// Hover
|
||||||
{
|
{
|
||||||
selector: 'node:active',
|
selector: 'node:active',
|
||||||
|
|
@ -999,35 +1028,45 @@
|
||||||
*/
|
*/
|
||||||
showNodeTooltip: function(node, position) {
|
showNodeTooltip: function(node, position) {
|
||||||
var data = node.data();
|
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
|
// Überschrift: Icon + Bezeichnung
|
||||||
var html = '<div class="tooltip-title">';
|
html += '<div class="tooltip-title">';
|
||||||
if (data.type_picto) {
|
if (data.type_picto) {
|
||||||
html += '<i class="fa ' + this.escapeHtml(data.type_picto) + '"></i> ';
|
html += '<i class="fa ' + this.escapeHtml(data.type_picto) + '"></i> ';
|
||||||
}
|
}
|
||||||
html += this.escapeHtml(data.label) + '</div>';
|
html += this.escapeHtml(data.label) + '</div>';
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
if (data.fields && data.fields.length > 0) {
|
// Hover-Felder anzeigen (nach Position sortiert, vom Backend gefiltert)
|
||||||
for (var i = 0; i < data.fields.length && i < 10; i++) {
|
var hoverFields = data.hover_fields || [];
|
||||||
var f = data.fields[i];
|
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;
|
if (!f.value || f.value === '') continue;
|
||||||
var val = f.value;
|
var val = f.value;
|
||||||
if (f.type === 'checkbox' && (val === '1' || val === 'true')) val = '\u2713';
|
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 += '<div class="tooltip-field">';
|
||||||
html += '<span class="tooltip-field-label">' + this.escapeHtml(f.label) + '</span>';
|
html += '<span class="tooltip-field-label">' + this.escapeHtml(f.label) + '</span>';
|
||||||
if (f.display === 'badge') {
|
if (f.color) {
|
||||||
var bgColor = f.color || '#4a5568';
|
html += '<span class="tooltip-field-badge" style="background:' + f.color + ';">' + this.escapeHtml(String(val)) + '</span>';
|
||||||
html += '<span class="tooltip-field-value" style="background:' + bgColor + ';padding:1px 6px;border-radius:3px;color:#fff;">' + this.escapeHtml(String(val)) + '</span>';
|
|
||||||
} else {
|
} else {
|
||||||
html += '<span class="tooltip-field-value">' + this.escapeHtml(String(val)) + '</span>';
|
html += '<span class="tooltip-field-value">' + this.escapeHtml(String(val)) + '</span>';
|
||||||
}
|
}
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
}
|
}
|
||||||
|
html += '</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Datei-Info am unteren Rand
|
||||||
if (data.image_count > 0 || data.doc_count > 0) {
|
if (data.image_count > 0 || data.doc_count > 0) {
|
||||||
html += '<div class="tooltip-type" style="margin-top:4px;">';
|
html += '<div class="tooltip-footer">';
|
||||||
if (data.image_count > 0) html += '<i class="fa fa-image"></i> ' + data.image_count + ' ';
|
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 += '<i class="fa fa-file"></i> ' + data.doc_count;
|
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>';
|
html += '</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1041,7 +1080,12 @@
|
||||||
*/
|
*/
|
||||||
showEdgeTooltip: function(edge, position) {
|
showEdgeTooltip: function(edge, position) {
|
||||||
var data = edge.data();
|
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) {
|
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>';
|
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) {
|
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 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.innerHTML = html;
|
||||||
this.tooltipEl.style.display = 'block';
|
this.tooltipEl.style.display = 'block';
|
||||||
this.positionTooltip(position);
|
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;
|
$viewMode = $defaultView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dol_include_once('/kundenkarte/lib/graph_view.lib.php');
|
||||||
|
|
||||||
$jsFiles = array('/kundenkarte/js/kundenkarte.js?v='.time());
|
$jsFiles = array('/kundenkarte/js/kundenkarte.js?v='.time());
|
||||||
$cssFiles = array('/kundenkarte/css/kundenkarte.css?v='.time());
|
$cssFiles = array('/kundenkarte/css/kundenkarte.css?v='.time());
|
||||||
|
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');
|
|
||||||
}
|
|
||||||
|
|
||||||
llxHeader('', $title, '', '', 0, 0, $jsFiles, $cssFiles);
|
llxHeader('', $title, '', '', 0, 0, $jsFiles, $cssFiles);
|
||||||
|
|
||||||
|
|
@ -507,31 +500,14 @@ print '</div>'; // End kundenkarte-system-tabs-wrapper
|
||||||
|
|
||||||
// Graph-Toolbar: UNTER der System-Tab-Borderlinie
|
// Graph-Toolbar: UNTER der System-Tab-Borderlinie
|
||||||
if ($isTreeView && $viewMode === 'graph') {
|
if ($isTreeView && $viewMode === 'graph') {
|
||||||
print '<div class="kundenkarte-graph-toolbar">';
|
kundenkarte_graph_print_toolbar(array(
|
||||||
// Zeile 1: Ansicht-Wechsel + Aktionen
|
'socid' => $id,
|
||||||
print '<div class="kundenkarte-graph-toolbar-row">';
|
'contactid' => 0,
|
||||||
print '<a class="button small" href="'.$toggleUrl.'" title="'.$toggleLabel.'"><i class="fa '.$toggleIcon.'"></i> '.$toggleLabel.'</a>';
|
'systemid' => $systemId,
|
||||||
if ($user->hasRight('kundenkarte', 'write')) {
|
'viewMode' => $viewMode,
|
||||||
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>';
|
'permissiontoadd' => $permissiontoadd,
|
||||||
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>';
|
'pageUrl' => $_SERVER['PHP_SELF'],
|
||||||
}
|
));
|
||||||
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>';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add system form (hidden by default)
|
// Add system form (hidden by default)
|
||||||
|
|
@ -1062,43 +1038,14 @@ if (empty($customerSystems)) {
|
||||||
|
|
||||||
if ($viewMode === 'graph' && $isTreeView) {
|
if ($viewMode === 'graph' && $isTreeView) {
|
||||||
// Graph-Ansicht: Container rendern, Daten werden per AJAX geladen
|
// Graph-Ansicht: Container rendern, Daten werden per AJAX geladen
|
||||||
$graphAjaxUrl = dol_buildpath('/kundenkarte/ajax/graph_data.php', 1);
|
kundenkarte_graph_print_container(array(
|
||||||
$graphSaveUrl = dol_buildpath('/kundenkarte/ajax/graph_save_positions.php', 1);
|
'socid' => $id,
|
||||||
$graphModuleUrl = dol_buildpath('/kundenkarte', 1);
|
'contactid' => 0,
|
||||||
|
'systemid' => $systemId,
|
||||||
print '<div class="kundenkarte-graph-wrapper">';
|
'permissiontoadd' => $permissiontoadd,
|
||||||
print '<div class="kundenkarte-graph-search-floating">';
|
'permissiontodelete' => $permissiontodelete,
|
||||||
print '<input type="text" id="kundenkarte-graph-search" placeholder="'.$langs->trans('SearchPlaceholder').'" autocomplete="off">';
|
'pageUrl' => $_SERVER['PHP_SELF'],
|
||||||
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>';
|
|
||||||
} else {
|
} else {
|
||||||
// Baumansicht (klassisch)
|
// Baumansicht (klassisch)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -365,21 +365,17 @@ if ($action == 'togglepin' && $permissiontoadd) {
|
||||||
|
|
||||||
$title = $langs->trans('TechnicalInstallations').' - '.$object->getFullName($langs);
|
$title = $langs->trans('TechnicalInstallations').' - '.$object->getFullName($langs);
|
||||||
|
|
||||||
// Ansichtsmodus (Admin-Setting)
|
// Ansichtsmodus: URL-Parameter hat Vorrang, sonst Admin-Setting
|
||||||
$viewMode = getDolGlobalString('KUNDENKARTE_DEFAULT_VIEW', 'tree');
|
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());
|
$jsFiles = array('/kundenkarte/js/kundenkarte.js?v='.time());
|
||||||
$cssFiles = array('/kundenkarte/css/kundenkarte.css?v='.time());
|
$cssFiles = array('/kundenkarte/css/kundenkarte.css?v='.time());
|
||||||
|
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');
|
|
||||||
}
|
|
||||||
|
|
||||||
llxHeader('', $title, '', '', 0, 0, $jsFiles, $cssFiles);
|
llxHeader('', $title, '', '', 0, 0, $jsFiles, $cssFiles);
|
||||||
|
|
||||||
|
|
@ -465,29 +461,20 @@ if ($permissiontoadd) {
|
||||||
}
|
}
|
||||||
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) {
|
||||||
if ($viewMode === 'graph') {
|
$toggleView = ($viewMode === 'graph') ? 'tree' : 'graph';
|
||||||
// Graph Controls: Aktionen + Zoom
|
$toggleUrl = $_SERVER['PHP_SELF'].'?id='.$id.'&system='.$systemId.'&view='.$toggleView;
|
||||||
print '<div class="kundenkarte-graph-toolbar">';
|
$toggleIcon = ($viewMode === 'graph') ? 'fa-list' : 'fa-sitemap';
|
||||||
if ($user->hasRight('kundenkarte', 'write')) {
|
$toggleLabel = ($viewMode === 'graph') ? $langs->trans('TreeView') : $langs->trans('GraphView');
|
||||||
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>';
|
if ($viewMode !== 'graph') {
|
||||||
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>';
|
// Baumansicht: Controls auf gleicher Zeile wie System-Tabs (im Wrapper)
|
||||||
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 {
|
|
||||||
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>';
|
||||||
|
|
@ -509,6 +496,18 @@ if ($isTreeView) {
|
||||||
|
|
||||||
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;">';
|
||||||
|
|
@ -1037,24 +1036,14 @@ if (empty($customerSystems)) {
|
||||||
|
|
||||||
if ($viewMode === 'graph' && $isTreeView) {
|
if ($viewMode === 'graph' && $isTreeView) {
|
||||||
// Graph-Ansicht: Container rendern, Daten werden per AJAX geladen
|
// Graph-Ansicht: Container rendern, Daten werden per AJAX geladen
|
||||||
$graphAjaxUrl = dol_buildpath('/kundenkarte/ajax/graph_data.php', 1);
|
kundenkarte_graph_print_container(array(
|
||||||
$graphSaveUrl = dol_buildpath('/kundenkarte/ajax/graph_save_positions.php', 1);
|
'socid' => $object->socid,
|
||||||
$graphModuleUrl = dol_buildpath('/kundenkarte', 1);
|
'contactid' => $id,
|
||||||
|
'systemid' => $systemId,
|
||||||
print '<div class="kundenkarte-graph-wrapper">';
|
'permissiontoadd' => $permissiontoadd,
|
||||||
print '<div id="kundenkarte-graph-container"';
|
'permissiontodelete' => $permissiontodelete,
|
||||||
print ' data-ajax-url="'.dol_escape_htmltag($graphAjaxUrl).'"';
|
'pageUrl' => $_SERVER['PHP_SELF'],
|
||||||
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>';
|
|
||||||
} else {
|
} else {
|
||||||
// Baumansicht (klassisch)
|
// Baumansicht (klassisch)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue