feat(graph): View-Modes pro System, Compound-Node Fixes, Resize-Handles, Admin-Gear
- Ansichtsmodus (tree/graph/both) pro System konfigurierbar - Admin-Zahnrad-Icon auf Kunden- und Kontakt-Anlagen-Tab - Compound-Nodes: Alle Nachkommen eines Gebäudes werden umschlossen - Leitungen/Verbindungen aus der Baumansicht entfernt (nur noch im Graph) - Resize-Handles für Gebäude-Nodes im Bearbeitungsmodus - graph_width/graph_height Spalten für persistente Gebäudegrößen - view_modes Spalte in System-Tabelle - DB-Migrationen in modKundenKarte Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e269584396
commit
ee4c6688d9
12 changed files with 442 additions and 54 deletions
|
|
@ -36,6 +36,8 @@ if ($action == 'add') {
|
||||||
$picto = GETPOST('picto', 'alphanohtml');
|
$picto = GETPOST('picto', 'alphanohtml');
|
||||||
$color = GETPOST('color', 'alphanohtml');
|
$color = GETPOST('color', 'alphanohtml');
|
||||||
$position = GETPOSTINT('position');
|
$position = GETPOSTINT('position');
|
||||||
|
$viewModes = GETPOST('view_modes', 'aZ09');
|
||||||
|
if (!in_array($viewModes, array('tree', 'graph', 'both'))) $viewModes = 'both';
|
||||||
|
|
||||||
// Tree display config
|
// Tree display config
|
||||||
$treeConfig = array(
|
$treeConfig = array(
|
||||||
|
|
@ -54,12 +56,13 @@ if ($action == 'add') {
|
||||||
setEventMessages($langs->trans('ErrorFieldRequired'), null, 'errors');
|
setEventMessages($langs->trans('ErrorFieldRequired'), null, 'errors');
|
||||||
} else {
|
} else {
|
||||||
$sql = "INSERT INTO ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system";
|
$sql = "INSERT INTO ".MAIN_DB_PREFIX."c_kundenkarte_anlage_system";
|
||||||
$sql .= " (code, label, picto, color, position, active, entity, tree_display_config)";
|
$sql .= " (code, label, picto, color, position, active, entity, tree_display_config, view_modes)";
|
||||||
$sql .= " VALUES ('".$db->escape(strtoupper($code))."', '".$db->escape($label)."',";
|
$sql .= " VALUES ('".$db->escape(strtoupper($code))."', '".$db->escape($label)."',";
|
||||||
$sql .= " ".($picto ? "'".$db->escape($picto)."'" : "NULL").",";
|
$sql .= " ".($picto ? "'".$db->escape($picto)."'" : "NULL").",";
|
||||||
$sql .= " ".($color ? "'".$db->escape($color)."'" : "NULL").",";
|
$sql .= " ".($color ? "'".$db->escape($color)."'" : "NULL").",";
|
||||||
$sql .= " ".((int) $position).", 1, 0,";
|
$sql .= " ".((int) $position).", 1, 0,";
|
||||||
$sql .= " '".$db->escape($treeConfigJson)."')";
|
$sql .= " '".$db->escape($treeConfigJson)."',";
|
||||||
|
$sql .= " '".$db->escape($viewModes)."')";
|
||||||
|
|
||||||
$result = $db->query($sql);
|
$result = $db->query($sql);
|
||||||
if ($result) {
|
if ($result) {
|
||||||
|
|
@ -77,6 +80,8 @@ if ($action == 'update') {
|
||||||
$picto = GETPOST('picto', 'alphanohtml');
|
$picto = GETPOST('picto', 'alphanohtml');
|
||||||
$color = GETPOST('color', 'alphanohtml');
|
$color = GETPOST('color', 'alphanohtml');
|
||||||
$position = GETPOSTINT('position');
|
$position = GETPOSTINT('position');
|
||||||
|
$viewModes = GETPOST('view_modes', 'aZ09');
|
||||||
|
if (!in_array($viewModes, array('tree', 'graph', 'both'))) $viewModes = 'both';
|
||||||
|
|
||||||
// Tree display config
|
// Tree display config
|
||||||
$treeConfig = array(
|
$treeConfig = array(
|
||||||
|
|
@ -98,6 +103,7 @@ if ($action == 'update') {
|
||||||
$sql .= ", color = ".($color ? "'".$db->escape($color)."'" : "NULL");
|
$sql .= ", color = ".($color ? "'".$db->escape($color)."'" : "NULL");
|
||||||
$sql .= ", position = ".((int) $position);
|
$sql .= ", position = ".((int) $position);
|
||||||
$sql .= ", tree_display_config = '".$db->escape($treeConfigJson)."'";
|
$sql .= ", tree_display_config = '".$db->escape($treeConfigJson)."'";
|
||||||
|
$sql .= ", view_modes = '".$db->escape($viewModes)."'";
|
||||||
$sql .= " WHERE rowid = ".((int) $systemId);
|
$sql .= " WHERE rowid = ".((int) $systemId);
|
||||||
|
|
||||||
$result = $db->query($sql);
|
$result = $db->query($sql);
|
||||||
|
|
@ -212,6 +218,21 @@ if ($action == 'create' || $action == 'edit') {
|
||||||
print '<tr><td>'.$langs->trans('Position').'</td>';
|
print '<tr><td>'.$langs->trans('Position').'</td>';
|
||||||
print '<td><input type="number" name="position" class="flat" value="'.($system ? $system->position : 0).'" min="0"></td></tr>';
|
print '<td><input type="number" name="position" class="flat" value="'.($system ? $system->position : 0).'" min="0"></td></tr>';
|
||||||
|
|
||||||
|
// Verfügbare Ansichten
|
||||||
|
$currentViewModes = ($system && !empty($system->view_modes)) ? $system->view_modes : 'both';
|
||||||
|
print '<tr><td>'.$langs->trans('ViewModes').'</td>';
|
||||||
|
print '<td><select name="view_modes" class="flat">';
|
||||||
|
$viewModesOptions = array(
|
||||||
|
'both' => $langs->trans('ViewModesBoth'),
|
||||||
|
'tree' => $langs->trans('ViewModesTreeOnly'),
|
||||||
|
'graph' => $langs->trans('ViewModesGraphOnly')
|
||||||
|
);
|
||||||
|
foreach ($viewModesOptions as $val => $lbl) {
|
||||||
|
$selected = ($currentViewModes == $val) ? ' selected' : '';
|
||||||
|
print '<option value="'.$val.'"'.$selected.'>'.$lbl.'</option>';
|
||||||
|
}
|
||||||
|
print '</select></td></tr>';
|
||||||
|
|
||||||
print '</table>';
|
print '</table>';
|
||||||
|
|
||||||
// Tree display configuration
|
// Tree display configuration
|
||||||
|
|
@ -304,6 +325,7 @@ if ($action == 'create' || $action == 'edit') {
|
||||||
print '<th>'.$langs->trans('SystemCode').'</th>';
|
print '<th>'.$langs->trans('SystemCode').'</th>';
|
||||||
print '<th>'.$langs->trans('SystemLabel').'</th>';
|
print '<th>'.$langs->trans('SystemLabel').'</th>';
|
||||||
print '<th>'.$langs->trans('SystemPicto').'</th>';
|
print '<th>'.$langs->trans('SystemPicto').'</th>';
|
||||||
|
print '<th class="center">'.$langs->trans('ViewModes').'</th>';
|
||||||
print '<th class="center">'.$langs->trans('Position').'</th>';
|
print '<th class="center">'.$langs->trans('Position').'</th>';
|
||||||
print '<th class="center">'.$langs->trans('Status').'</th>';
|
print '<th class="center">'.$langs->trans('Status').'</th>';
|
||||||
print '<th class="center">'.$langs->trans('Actions').'</th>';
|
print '<th class="center">'.$langs->trans('Actions').'</th>';
|
||||||
|
|
@ -321,6 +343,10 @@ if ($action == 'create' || $action == 'edit') {
|
||||||
print dol_escape_htmltag($obj->picto);
|
print dol_escape_htmltag($obj->picto);
|
||||||
}
|
}
|
||||||
print '</td>';
|
print '</td>';
|
||||||
|
// Ansichtsmodus
|
||||||
|
$vmLabel = array('both' => 'Baum & Graph', 'tree' => 'Nur Baum', 'graph' => 'Nur Graph');
|
||||||
|
$vm = !empty($obj->view_modes) ? $obj->view_modes : 'both';
|
||||||
|
print '<td class="center">'.($vmLabel[$vm] ?? $vm).'</td>';
|
||||||
print '<td class="center">'.$obj->position.'</td>';
|
print '<td class="center">'.$obj->position.'</td>';
|
||||||
|
|
||||||
print '<td class="center">';
|
print '<td class="center">';
|
||||||
|
|
|
||||||
|
|
@ -67,8 +67,9 @@ if ($resFields) {
|
||||||
// Gebäude/Räume werden über den Typ erkannt (type_system_code = GLOBAL)
|
// Gebäude/Räume werden über den Typ erkannt (type_system_code = GLOBAL)
|
||||||
// Hierarchie kommt aus fk_parent (wie im Baum)
|
// Hierarchie kommt aus fk_parent (wie im Baum)
|
||||||
$sql = "SELECT a.rowid, a.label, a.fk_parent, a.fk_system, a.fk_anlage_type,";
|
$sql = "SELECT a.rowid, a.label, a.fk_parent, a.fk_system, a.fk_anlage_type,";
|
||||||
$sql .= " a.field_values, a.fk_contact, a.graph_x, a.graph_y,";
|
$sql .= " a.field_values, a.fk_contact, a.graph_x, a.graph_y, a.graph_width, a.graph_height,";
|
||||||
$sql .= " t.label as type_label, t.picto as type_picto, t.color as type_color,";
|
$sql .= " t.label as type_label, t.picto as type_picto, t.color as type_color,";
|
||||||
|
$sql .= " t.can_have_children as type_can_have_children,";
|
||||||
$sql .= " s.code as system_code, s.label as system_label,";
|
$sql .= " s.code as system_code, s.label as system_label,";
|
||||||
$sql .= " ts.code as type_system_code,";
|
$sql .= " ts.code as type_system_code,";
|
||||||
$sql .= " (SELECT COUNT(*) FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_files f WHERE f.fk_anlage = a.rowid AND f.file_type IN ('image/jpeg','image/png','image/gif','image/webp')) as image_count,";
|
$sql .= " (SELECT COUNT(*) FROM ".MAIN_DB_PREFIX."kundenkarte_anlage_files f WHERE f.fk_anlage = a.rowid AND f.file_type IN ('image/jpeg','image/png','image/gif','image/webp')) as image_count,";
|
||||||
|
|
@ -90,8 +91,9 @@ $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();
|
$nodeIsBuilding = array();
|
||||||
|
// Hierarchie: rowid → fk_parent (zum Traversieren nach oben)
|
||||||
|
$nodeParentMap = array();
|
||||||
// Hierarchie-Kanten (Gerät→Gerät), werden durch echte Kabel ersetzt falls vorhanden
|
// Hierarchie-Kanten (Gerät→Gerät), werden durch echte Kabel ersetzt falls vorhanden
|
||||||
$hierarchyEdges = array();
|
$hierarchyEdges = array();
|
||||||
// Zwischenspeicher: alle DB-Zeilen für Zwei-Pass-Verarbeitung
|
// Zwischenspeicher: alle DB-Zeilen für Zwei-Pass-Verarbeitung
|
||||||
|
|
@ -99,10 +101,11 @@ $rows = array();
|
||||||
|
|
||||||
$resql = $db->query($sql);
|
$resql = $db->query($sql);
|
||||||
if ($resql) {
|
if ($resql) {
|
||||||
// 1. Pass: Alle Zeilen laden und Gebäude-Typen merken
|
// 1. Pass: Alle Zeilen laden, Gebäude-Typen und Hierarchie merken
|
||||||
while ($obj = $db->fetch_object($resql)) {
|
while ($obj = $db->fetch_object($resql)) {
|
||||||
$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;
|
$nodeIsBuilding[(int)$obj->rowid] = $isBuilding;
|
||||||
|
$nodeParentMap[(int)$obj->rowid] = (int)$obj->fk_parent;
|
||||||
$nodeIds[$obj->rowid] = true;
|
$nodeIds[$obj->rowid] = true;
|
||||||
$rows[] = $obj;
|
$rows[] = $obj;
|
||||||
}
|
}
|
||||||
|
|
@ -129,6 +132,8 @@ if ($resql) {
|
||||||
'doc_count' => (int) $obj->doc_count,
|
'doc_count' => (int) $obj->doc_count,
|
||||||
'graph_x' => $obj->graph_x !== null ? (float) $obj->graph_x : null,
|
'graph_x' => $obj->graph_x !== null ? (float) $obj->graph_x : null,
|
||||||
'graph_y' => $obj->graph_y !== null ? (float) $obj->graph_y : null,
|
'graph_y' => $obj->graph_y !== null ? (float) $obj->graph_y : null,
|
||||||
|
'graph_width' => $obj->graph_width !== null ? (float) $obj->graph_width : null,
|
||||||
|
'graph_height' => $obj->graph_height !== null ? (float) $obj->graph_height : null,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Feldwerte mit Metadaten (Label, Display-Modus, Badge-Farbe)
|
// Feldwerte mit Metadaten (Label, Display-Modus, Badge-Farbe)
|
||||||
|
|
@ -169,16 +174,13 @@ if ($resql) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compound-Parent: NUR wenn Eltern-Node ein Gebäude/Raum ist
|
// Compound-Parent: Nächstes Gebäude in der Hierarchie nach oben finden
|
||||||
|
// Alle Nodes innerhalb eines Gebäudes werden von diesem umschlossen
|
||||||
// Gerät→Gerät Hierarchie wird als Kante dargestellt (nicht verschachtelt)
|
// Gerät→Gerät Hierarchie wird als Kante dargestellt (nicht verschachtelt)
|
||||||
$parentId = (int) $obj->fk_parent;
|
$parentId = (int) $obj->fk_parent;
|
||||||
if ($parentId > 0 && isset($nodeIds[$parentId])) {
|
if ($parentId > 0 && isset($nodeIds[$parentId])) {
|
||||||
$parentIsBuilding = !empty($nodeIsBuilding[$parentId]);
|
// Hierarchie-Kante zum direkten Parent (wird ggf. durch Kabel ersetzt)
|
||||||
if ($parentIsBuilding) {
|
if (!$isBuilding) {
|
||||||
// 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);
|
$hierKey = min($parentId, (int)$obj->rowid).'_'.max($parentId, (int)$obj->rowid);
|
||||||
$hierarchyEdges[$hierKey] = array(
|
$hierarchyEdges[$hierKey] = array(
|
||||||
'data' => array(
|
'data' => array(
|
||||||
|
|
@ -190,6 +192,17 @@ if ($resql) {
|
||||||
'classes' => 'hierarchy-edge'
|
'classes' => 'hierarchy-edge'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Nach oben traversieren bis ein Gebäude gefunden wird → Compound-Parent
|
||||||
|
$ancestorId = $parentId;
|
||||||
|
$maxDepth = 20; // Endlosschleifen vermeiden
|
||||||
|
while ($ancestorId > 0 && $maxDepth-- > 0) {
|
||||||
|
if (!empty($nodeIsBuilding[$ancestorId])) {
|
||||||
|
$nodeData['parent'] = 'n_'.$ancestorId;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$ancestorId = isset($nodeParentMap[$ancestorId]) ? $nodeParentMap[$ancestorId] : 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$elements['nodes'][] = array(
|
$elements['nodes'][] = array(
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,12 @@ if ($action === 'save') {
|
||||||
|
|
||||||
$sql = "UPDATE ".MAIN_DB_PREFIX."kundenkarte_anlage";
|
$sql = "UPDATE ".MAIN_DB_PREFIX."kundenkarte_anlage";
|
||||||
$sql .= " SET graph_x = ".$x.", graph_y = ".$y;
|
$sql .= " SET graph_x = ".$x.", graph_y = ".$y;
|
||||||
|
// Gebäude-Größe optional mitspeichern
|
||||||
|
if (isset($pos['w']) && isset($pos['h'])) {
|
||||||
|
$w = (float) $pos['w'];
|
||||||
|
$h = (float) $pos['h'];
|
||||||
|
$sql .= ", graph_width = ".($w > 0 ? $w : "NULL").", graph_height = ".($h > 0 ? $h : "NULL");
|
||||||
|
}
|
||||||
$sql .= " WHERE rowid = ".$anlageId;
|
$sql .= " WHERE rowid = ".$anlageId;
|
||||||
if ($db->query($sql)) {
|
if ($db->query($sql)) {
|
||||||
$saved++;
|
$saved++;
|
||||||
|
|
@ -73,7 +79,7 @@ if ($action === 'save') {
|
||||||
}
|
}
|
||||||
|
|
||||||
$sql = "UPDATE ".MAIN_DB_PREFIX."kundenkarte_anlage";
|
$sql = "UPDATE ".MAIN_DB_PREFIX."kundenkarte_anlage";
|
||||||
$sql .= " SET graph_x = NULL, graph_y = NULL";
|
$sql .= " SET graph_x = NULL, graph_y = NULL, graph_width = NULL, graph_height = NULL";
|
||||||
$sql .= " WHERE fk_soc = ".(int)$socId;
|
$sql .= " WHERE fk_soc = ".(int)$socId;
|
||||||
if ($contactId > 0) {
|
if ($contactId > 0) {
|
||||||
$sql .= " AND fk_contact = ".(int)$contactId;
|
$sql .= " AND fk_contact = ".(int)$contactId;
|
||||||
|
|
|
||||||
|
|
@ -622,6 +622,12 @@ class modKundenKarte extends DolibarrModules
|
||||||
// v5.2.0: Halbe TE-Breiten (4.5 TE für Neozed etc.)
|
// v5.2.0: Halbe TE-Breiten (4.5 TE für Neozed etc.)
|
||||||
$this->migrate_v520_decimal_te();
|
$this->migrate_v520_decimal_te();
|
||||||
|
|
||||||
|
// v5.3.0: Ansichtsmodus pro System (tree/graph/both)
|
||||||
|
$this->migrate_v530_system_view_modes();
|
||||||
|
|
||||||
|
// v5.3.0: Gebäude-Größe im Graph speichern
|
||||||
|
$this->migrate_v530_graph_dimensions();
|
||||||
|
|
||||||
// v6.8.0: Gebündelte Terminals für Multi-Phasen-Abgänge
|
// v6.8.0: Gebündelte Terminals für Multi-Phasen-Abgänge
|
||||||
$this->migrate_v680_bundled_terminals();
|
$this->migrate_v680_bundled_terminals();
|
||||||
|
|
||||||
|
|
@ -835,6 +841,37 @@ class modKundenKarte extends DolibarrModules
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migration v5.3.0: Spalte view_modes für Ansichtsmodus pro System
|
||||||
|
*/
|
||||||
|
private function migrate_v530_system_view_modes()
|
||||||
|
{
|
||||||
|
$table = MAIN_DB_PREFIX."c_kundenkarte_anlage_system";
|
||||||
|
$resql = $this->db->query("SHOW TABLES LIKE '".$this->db->escape($table)."'");
|
||||||
|
if ($resql && $this->db->num_rows($resql) > 0) {
|
||||||
|
$resql = $this->db->query("SHOW COLUMNS FROM ".$table." WHERE Field = 'view_modes'");
|
||||||
|
if ($resql && $this->db->num_rows($resql) == 0) {
|
||||||
|
$this->db->query("ALTER TABLE ".$table." ADD COLUMN view_modes VARCHAR(20) NOT NULL DEFAULT 'both' AFTER tree_display_config");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migration v5.3.0: Gebäude-Größe im Graph speichern (graph_width, graph_height)
|
||||||
|
*/
|
||||||
|
private function migrate_v530_graph_dimensions()
|
||||||
|
{
|
||||||
|
$table = MAIN_DB_PREFIX."kundenkarte_anlage";
|
||||||
|
$resql = $this->db->query("SHOW TABLES LIKE '".$this->db->escape($table)."'");
|
||||||
|
if ($resql && $this->db->num_rows($resql) > 0) {
|
||||||
|
$resql = $this->db->query("SHOW COLUMNS FROM ".$table." WHERE Field = 'graph_width'");
|
||||||
|
if ($resql && $this->db->num_rows($resql) == 0) {
|
||||||
|
$this->db->query("ALTER TABLE ".$table." ADD COLUMN graph_width DECIMAL(10,2) DEFAULT NULL AFTER graph_y");
|
||||||
|
$this->db->query("ALTER TABLE ".$table." ADD COLUMN graph_height DECIMAL(10,2) DEFAULT NULL AFTER graph_width");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Migration v6.8.0: Gebündelte Terminals für Multi-Phasen-Abgänge
|
* Migration v6.8.0: Gebündelte Terminals für Multi-Phasen-Abgänge
|
||||||
* Ermöglicht einen Abgang der alle Terminals eines breiten Equipment belegt
|
* Ermöglicht einen Abgang der alle Terminals eines breiten Equipment belegt
|
||||||
|
|
|
||||||
|
|
@ -400,3 +400,30 @@
|
||||||
width: 160px;
|
width: 160px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ==========================================
|
||||||
|
* Resize-Handles für Gebäude-Compound-Nodes
|
||||||
|
* ========================================== */
|
||||||
|
.kundenkarte-resize-handle {
|
||||||
|
position: absolute;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
background: rgba(67, 144, 220, 0.6);
|
||||||
|
border: 1px solid #4390dc;
|
||||||
|
border-radius: 2px;
|
||||||
|
z-index: 100;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.kundenkarte-resize-handle:hover {
|
||||||
|
background: rgba(67, 144, 220, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cursor je nach Handle-Position */
|
||||||
|
.kundenkarte-resize-handle.resize-nw { cursor: nw-resize; }
|
||||||
|
.kundenkarte-resize-handle.resize-n { cursor: n-resize; }
|
||||||
|
.kundenkarte-resize-handle.resize-ne { cursor: ne-resize; }
|
||||||
|
.kundenkarte-resize-handle.resize-e { cursor: e-resize; }
|
||||||
|
.kundenkarte-resize-handle.resize-se { cursor: se-resize; }
|
||||||
|
.kundenkarte-resize-handle.resize-s { cursor: s-resize; }
|
||||||
|
.kundenkarte-resize-handle.resize-sw { cursor: sw-resize; }
|
||||||
|
.kundenkarte-resize-handle.resize-w { cursor: w-resize; }
|
||||||
|
|
|
||||||
|
|
@ -460,8 +460,8 @@
|
||||||
'text-background-opacity': 0.95,
|
'text-background-opacity': 0.95,
|
||||||
'text-background-padding': '4px',
|
'text-background-padding': '4px',
|
||||||
'text-background-shape': 'roundrectangle',
|
'text-background-shape': 'roundrectangle',
|
||||||
'min-width': '120px',
|
'min-width': function(node) { return node.data('graph_width') || 120; },
|
||||||
'min-height': '60px'
|
'min-height': function(node) { return node.data('graph_height') || 60; }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Geräte - Karten-Design mit Feldwerten (kompakt)
|
// Geräte - Karten-Design mit Feldwerten (kompakt)
|
||||||
|
|
@ -754,9 +754,14 @@
|
||||||
var snappedY = Math.round(pos.y / grid) * grid;
|
var snappedY = Math.round(pos.y / grid) * grid;
|
||||||
node.position({ x: snappedX, y: snappedY });
|
node.position({ x: snappedX, y: snappedY });
|
||||||
|
|
||||||
// Position zum Speichern vormerken
|
// Position zum Speichern vormerken (bei Buildings auch Größe)
|
||||||
var anlageId = node.data('id').replace('n_', '');
|
var anlageId = node.data('id').replace('n_', '');
|
||||||
self._dirtyNodes[anlageId] = { id: parseInt(anlageId), x: snappedX, y: snappedY };
|
var dirtyEntry = { id: parseInt(anlageId), x: snappedX, y: snappedY };
|
||||||
|
if (node.hasClass('building-node') && node.data('graph_width')) {
|
||||||
|
dirtyEntry.w = node.data('graph_width');
|
||||||
|
dirtyEntry.h = node.data('graph_height');
|
||||||
|
}
|
||||||
|
self._dirtyNodes[anlageId] = dirtyEntry;
|
||||||
|
|
||||||
// Bei Compound-Nodes auch alle Kinder speichern
|
// Bei Compound-Nodes auch alle Kinder speichern
|
||||||
var children = node.children();
|
var children = node.children();
|
||||||
|
|
@ -886,13 +891,20 @@
|
||||||
this.editMode = true;
|
this.editMode = true;
|
||||||
this._dirtyNodes = {};
|
this._dirtyNodes = {};
|
||||||
|
|
||||||
// Aktuelle Positionen sichern (für Abbrechen)
|
// Aktuelle Positionen und Größen sichern (für Abbrechen)
|
||||||
this._savedPositions = {};
|
this._savedPositions = {};
|
||||||
|
this._savedSizes = {};
|
||||||
var self = this;
|
var self = this;
|
||||||
this.cy.nodes().forEach(function(node) {
|
this.cy.nodes().forEach(function(node) {
|
||||||
var pos = node.position();
|
var pos = node.position();
|
||||||
self._savedPositions[node.data('id')] = { x: pos.x, y: pos.y };
|
self._savedPositions[node.data('id')] = { x: pos.x, y: pos.y };
|
||||||
});
|
});
|
||||||
|
this.cy.nodes('.building-node').forEach(function(node) {
|
||||||
|
self._savedSizes[node.data('id')] = {
|
||||||
|
w: node.data('graph_width'),
|
||||||
|
h: node.data('graph_height')
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
// Nodes verschiebbar machen
|
// Nodes verschiebbar machen
|
||||||
this.cy.autoungrabify(false);
|
this.cy.autoungrabify(false);
|
||||||
|
|
@ -903,6 +915,9 @@
|
||||||
|
|
||||||
// Visuelles Feedback: Container-Rahmen
|
// Visuelles Feedback: Container-Rahmen
|
||||||
$('#' + this.containerId).addClass('graph-edit-mode');
|
$('#' + this.containerId).addClass('graph-edit-mode');
|
||||||
|
|
||||||
|
// Resize-Handles für Gebäude
|
||||||
|
this.showResizeHandles();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -912,6 +927,7 @@
|
||||||
if (!this.cy) return;
|
if (!this.cy) return;
|
||||||
this.editMode = false;
|
this.editMode = false;
|
||||||
this._savedPositions = {};
|
this._savedPositions = {};
|
||||||
|
this._savedSizes = {};
|
||||||
|
|
||||||
// Nodes wieder sperren
|
// Nodes wieder sperren
|
||||||
this.cy.autoungrabify(true);
|
this.cy.autoungrabify(true);
|
||||||
|
|
@ -922,10 +938,13 @@
|
||||||
|
|
||||||
// Visuelles Feedback entfernen
|
// Visuelles Feedback entfernen
|
||||||
$('#' + this.containerId).removeClass('graph-edit-mode');
|
$('#' + this.containerId).removeClass('graph-edit-mode');
|
||||||
|
|
||||||
|
// Resize-Handles entfernen
|
||||||
|
this.hideResizeHandles();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bearbeitungsmodus abbrechen - Positionen zurücksetzen
|
* Bearbeitungsmodus abbrechen - Positionen und Größen zurücksetzen
|
||||||
*/
|
*/
|
||||||
cancelEditMode: function() {
|
cancelEditMode: function() {
|
||||||
if (!this.cy) return;
|
if (!this.cy) return;
|
||||||
|
|
@ -939,10 +958,228 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Gebäude-Größen zurücksetzen
|
||||||
|
this.cy.nodes('.building-node').forEach(function(node) {
|
||||||
|
var saved = self._savedSizes[node.data('id')];
|
||||||
|
if (saved) {
|
||||||
|
node.data('graph_width', saved.w);
|
||||||
|
node.data('graph_height', saved.h);
|
||||||
|
node.style({
|
||||||
|
'min-width': saved.w || 120,
|
||||||
|
'min-height': saved.h || 60
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this._dirtyNodes = {};
|
this._dirtyNodes = {};
|
||||||
this.exitEditMode();
|
this.exitEditMode();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// Resize-Handles für Gebäude-Compound-Nodes
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
|
_resizeHandles: [],
|
||||||
|
_resizeListeners: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resize-Handles für alle Building-Nodes anzeigen
|
||||||
|
*/
|
||||||
|
showResizeHandles: function() {
|
||||||
|
var self = this;
|
||||||
|
var $container = $('#' + this.containerId);
|
||||||
|
|
||||||
|
// Container relativ positionieren falls nötig
|
||||||
|
if ($container.css('position') === 'static') {
|
||||||
|
$container.css('position', 'relative');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles für jedes Gebäude erstellen
|
||||||
|
this.cy.nodes('.building-node').forEach(function(node) {
|
||||||
|
self._createHandlesForNode(node, $container);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Viewport-Änderungen: Handles repositionieren
|
||||||
|
this._resizeListeners = {
|
||||||
|
viewport: function() { self.updateResizeHandles(); },
|
||||||
|
position: function() { self.updateResizeHandles(); },
|
||||||
|
// Nach Drag repositionieren
|
||||||
|
dragfree: function() { self.updateResizeHandles(); }
|
||||||
|
};
|
||||||
|
this.cy.on('viewport', this._resizeListeners.viewport);
|
||||||
|
this.cy.on('position', 'node', this._resizeListeners.position);
|
||||||
|
this.cy.on('dragfree', 'node', this._resizeListeners.dragfree);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles für einen einzelnen Building-Node erstellen
|
||||||
|
*/
|
||||||
|
_createHandlesForNode: function(node, $container) {
|
||||||
|
var self = this;
|
||||||
|
var nodeId = node.data('id');
|
||||||
|
// 4 Ecken + 4 Kanten
|
||||||
|
var positions = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'];
|
||||||
|
|
||||||
|
positions.forEach(function(pos) {
|
||||||
|
var $handle = $('<div class="kundenkarte-resize-handle resize-' + pos + '" data-node="' + nodeId + '" data-pos="' + pos + '"></div>');
|
||||||
|
$container.append($handle);
|
||||||
|
self._resizeHandles.push($handle[0]);
|
||||||
|
|
||||||
|
$handle.on('mousedown', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
self._startResize(node, pos, e);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this._positionHandles(node);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles eines Nodes an die aktuelle Bounding-Box positionieren
|
||||||
|
*/
|
||||||
|
_positionHandles: function(node) {
|
||||||
|
var nodeId = node.data('id');
|
||||||
|
var bb = node.renderedBoundingBox({ includeLabels: false });
|
||||||
|
var $container = $('#' + this.containerId);
|
||||||
|
var containerOffset = $container.offset();
|
||||||
|
var canvasOffset = $container.find('canvas').first().offset() || containerOffset;
|
||||||
|
// Offset relativ zum Container
|
||||||
|
var ox = canvasOffset.left - containerOffset.left;
|
||||||
|
var oy = canvasOffset.top - containerOffset.top;
|
||||||
|
|
||||||
|
var handleSize = 10;
|
||||||
|
var half = handleSize / 2;
|
||||||
|
|
||||||
|
var coords = {
|
||||||
|
'nw': { left: bb.x1 + ox - half, top: bb.y1 + oy - half },
|
||||||
|
'n': { left: bb.x1 + ox + (bb.w / 2) - half, top: bb.y1 + oy - half },
|
||||||
|
'ne': { left: bb.x2 + ox - half, top: bb.y1 + oy - half },
|
||||||
|
'e': { left: bb.x2 + ox - half, top: bb.y1 + oy + (bb.h / 2) - half },
|
||||||
|
'se': { left: bb.x2 + ox - half, top: bb.y2 + oy - half },
|
||||||
|
's': { left: bb.x1 + ox + (bb.w / 2) - half, top: bb.y2 + oy - half },
|
||||||
|
'sw': { left: bb.x1 + ox - half, top: bb.y2 + oy - half },
|
||||||
|
'w': { left: bb.x1 + ox - half, top: bb.y1 + oy + (bb.h / 2) - half }
|
||||||
|
};
|
||||||
|
|
||||||
|
$('.kundenkarte-resize-handle[data-node="' + nodeId + '"]').each(function() {
|
||||||
|
var pos = $(this).data('pos');
|
||||||
|
if (coords[pos]) {
|
||||||
|
$(this).css({ left: coords[pos].left + 'px', top: coords[pos].top + 'px' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alle Handles repositionieren (nach Zoom/Pan/Drag)
|
||||||
|
*/
|
||||||
|
updateResizeHandles: function() {
|
||||||
|
var self = this;
|
||||||
|
if (this._updateHandleTimer) clearTimeout(this._updateHandleTimer);
|
||||||
|
this._updateHandleTimer = setTimeout(function() {
|
||||||
|
self.cy.nodes('.building-node').forEach(function(node) {
|
||||||
|
self._positionHandles(node);
|
||||||
|
});
|
||||||
|
}, 16); // ~60fps
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resize starten bei mousedown auf Handle
|
||||||
|
*/
|
||||||
|
_startResize: function(node, handlePos, startEvent) {
|
||||||
|
var self = this;
|
||||||
|
var startX = startEvent.clientX;
|
||||||
|
var startY = startEvent.clientY;
|
||||||
|
var zoom = this.cy.zoom();
|
||||||
|
|
||||||
|
// Aktuelle Größe (model-Koordinaten, nicht rendered)
|
||||||
|
var bb = node.boundingBox({ includeLabels: false });
|
||||||
|
var startW = bb.w;
|
||||||
|
var startH = bb.h;
|
||||||
|
|
||||||
|
// Minimale Größe
|
||||||
|
var minW = 80;
|
||||||
|
var minH = 40;
|
||||||
|
|
||||||
|
// Node während Resize nicht verschiebbar
|
||||||
|
node.ungrabify();
|
||||||
|
|
||||||
|
function onMouseMove(e) {
|
||||||
|
var dx = (e.clientX - startX) / zoom;
|
||||||
|
var dy = (e.clientY - startY) / zoom;
|
||||||
|
|
||||||
|
var newW = startW;
|
||||||
|
var newH = startH;
|
||||||
|
|
||||||
|
// Breite anpassen je nach Handle-Position
|
||||||
|
if (handlePos.indexOf('e') >= 0) newW = startW + dx;
|
||||||
|
if (handlePos.indexOf('w') >= 0) newW = startW - dx;
|
||||||
|
// Höhe anpassen
|
||||||
|
if (handlePos.indexOf('s') >= 0) newH = startH + dy;
|
||||||
|
if (handlePos.indexOf('n') >= 0) newH = startH - dy;
|
||||||
|
|
||||||
|
// Minimum
|
||||||
|
newW = Math.max(minW, newW);
|
||||||
|
newH = Math.max(minH, newH);
|
||||||
|
|
||||||
|
// Auf Grid rasten
|
||||||
|
newW = Math.round(newW / self.gridSize) * self.gridSize;
|
||||||
|
newH = Math.round(newH / self.gridSize) * self.gridSize;
|
||||||
|
|
||||||
|
// Cytoscape Style aktualisieren
|
||||||
|
node.style({ 'min-width': newW, 'min-height': newH });
|
||||||
|
node.data('graph_width', newW);
|
||||||
|
node.data('graph_height', newH);
|
||||||
|
|
||||||
|
// Handles repositionieren
|
||||||
|
self._positionHandles(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMouseUp() {
|
||||||
|
$(document).off('mousemove', onMouseMove);
|
||||||
|
$(document).off('mouseup', onMouseUp);
|
||||||
|
|
||||||
|
// Node wieder verschiebbar
|
||||||
|
node.grabify();
|
||||||
|
|
||||||
|
// Endgültige Größe merken
|
||||||
|
var anlageId = node.data('id').replace('n_', '');
|
||||||
|
var pos = node.position();
|
||||||
|
self._dirtyNodes[anlageId] = {
|
||||||
|
id: parseInt(anlageId),
|
||||||
|
x: pos.x,
|
||||||
|
y: pos.y,
|
||||||
|
w: node.data('graph_width') || 0,
|
||||||
|
h: node.data('graph_height') || 0
|
||||||
|
};
|
||||||
|
|
||||||
|
self.updateResizeHandles();
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).on('mousemove', onMouseMove);
|
||||||
|
$(document).on('mouseup', onMouseUp);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alle Resize-Handles entfernen
|
||||||
|
*/
|
||||||
|
hideResizeHandles: function() {
|
||||||
|
$('.kundenkarte-resize-handle').remove();
|
||||||
|
this._resizeHandles = [];
|
||||||
|
if (this._updateHandleTimer) {
|
||||||
|
clearTimeout(this._updateHandleTimer);
|
||||||
|
this._updateHandleTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event-Listener entfernen
|
||||||
|
if (this._resizeListeners && this.cy) {
|
||||||
|
this.cy.off('viewport', this._resizeListeners.viewport);
|
||||||
|
this.cy.off('position', 'node', this._resizeListeners.position);
|
||||||
|
this.cy.off('dragfree', 'node', this._resizeListeners.dragfree);
|
||||||
|
this._resizeListeners = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Geänderte Positionen an Server senden
|
* Geänderte Positionen an Server senden
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -549,3 +549,9 @@ TreeView = Baumansicht
|
||||||
GraphView = Graph-Ansicht
|
GraphView = Graph-Ansicht
|
||||||
GraphLoading = Graph wird geladen...
|
GraphLoading = Graph wird geladen...
|
||||||
SearchPlaceholder = Suchen...
|
SearchPlaceholder = Suchen...
|
||||||
|
|
||||||
|
# Ansichtsmodus pro System
|
||||||
|
ViewModes = Verfuegbare Ansichten
|
||||||
|
ViewModesBoth = Baum & Graph
|
||||||
|
ViewModesTreeOnly = Nur Baum
|
||||||
|
ViewModesGraphOnly = Nur Graph
|
||||||
|
|
|
||||||
|
|
@ -297,3 +297,9 @@ TreeView = Tree View
|
||||||
GraphView = Graph View
|
GraphView = Graph View
|
||||||
GraphLoading = Loading graph...
|
GraphLoading = Loading graph...
|
||||||
SearchPlaceholder = Search...
|
SearchPlaceholder = Search...
|
||||||
|
|
||||||
|
# View modes per system
|
||||||
|
ViewModes = Available Views
|
||||||
|
ViewModesBoth = Tree & Graph
|
||||||
|
ViewModesTreeOnly = Tree Only
|
||||||
|
ViewModesGraphOnly = Graph Only
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ function kundenkarte_graph_print_toolbar($params)
|
||||||
$viewMode = $params['viewMode'] ?? 'tree';
|
$viewMode = $params['viewMode'] ?? 'tree';
|
||||||
$permissiontoadd = !empty($params['permissiontoadd']);
|
$permissiontoadd = !empty($params['permissiontoadd']);
|
||||||
$pageUrl = $params['pageUrl'] ?? $_SERVER['PHP_SELF'];
|
$pageUrl = $params['pageUrl'] ?? $_SERVER['PHP_SELF'];
|
||||||
|
$allowedViews = $params['allowedViews'] ?? 'both';
|
||||||
|
|
||||||
// View-Toggle URL: ID-Parameter je nach Kontext
|
// View-Toggle URL: ID-Parameter je nach Kontext
|
||||||
$idParam = ($contactId > 0) ? $contactId : $socId;
|
$idParam = ($contactId > 0) ? $contactId : $socId;
|
||||||
|
|
@ -65,7 +66,9 @@ function kundenkarte_graph_print_toolbar($params)
|
||||||
|
|
||||||
// Zeile 1: Ansicht-Wechsel + Aktionen
|
// Zeile 1: Ansicht-Wechsel + Aktionen
|
||||||
print '<div class="kundenkarte-graph-toolbar-row">';
|
print '<div class="kundenkarte-graph-toolbar-row">';
|
||||||
print '<a class="button small" href="'.$toggleUrl.'" title="'.$toggleLabel.'"><i class="fa '.$toggleIcon.'"></i> '.$toggleLabel.'</a>';
|
if ($allowedViews === 'both') {
|
||||||
|
print '<a class="button small" href="'.$toggleUrl.'" title="'.$toggleLabel.'"><i class="fa '.$toggleIcon.'"></i> '.$toggleLabel.'</a>';
|
||||||
|
}
|
||||||
if ($permissiontoadd) {
|
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="'.$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 '<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>';
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,9 @@ CREATE TABLE llx_c_kundenkarte_anlage_system
|
||||||
-- Tree display configuration (JSON)
|
-- Tree display configuration (JSON)
|
||||||
tree_display_config text COMMENT 'JSON config for tree display options',
|
tree_display_config text COMMENT 'JSON config for tree display options',
|
||||||
|
|
||||||
|
-- Verfügbare Ansichten: tree, graph, both
|
||||||
|
view_modes varchar(20) NOT NULL DEFAULT 'both',
|
||||||
|
|
||||||
position integer DEFAULT 0,
|
position integer DEFAULT 0,
|
||||||
active tinyint DEFAULT 1 NOT NULL
|
active tinyint DEFAULT 1 NOT NULL
|
||||||
) ENGINE=innodb;
|
) ENGINE=innodb;
|
||||||
|
|
|
||||||
|
|
@ -366,12 +366,23 @@ if ($action == 'togglepin' && $permissiontoadd) {
|
||||||
|
|
||||||
$title = $langs->trans('TechnicalInstallations').' - '.$object->name;
|
$title = $langs->trans('TechnicalInstallations').' - '.$object->name;
|
||||||
|
|
||||||
// Ansichtsmodus: URL-Parameter hat Vorrang, sonst Admin-Setting
|
// Ansichtsmodus: pro System konfigurierbar (tree/graph/both)
|
||||||
|
$allowedViews = 'both';
|
||||||
|
if ($systemId > 0 && isset($customerSystems[$systemId]) && !empty($customerSystems[$systemId]->view_modes)) {
|
||||||
|
$allowedViews = $customerSystems[$systemId]->view_modes;
|
||||||
|
}
|
||||||
|
|
||||||
$defaultView = getDolGlobalString('KUNDENKARTE_DEFAULT_VIEW', 'tree');
|
$defaultView = getDolGlobalString('KUNDENKARTE_DEFAULT_VIEW', 'tree');
|
||||||
$viewMode = GETPOST('view', 'aZ09');
|
$viewMode = GETPOST('view', 'aZ09');
|
||||||
if (!in_array($viewMode, array('tree', 'graph'))) {
|
if (!in_array($viewMode, array('tree', 'graph'))) {
|
||||||
$viewMode = $defaultView;
|
$viewMode = $defaultView;
|
||||||
}
|
}
|
||||||
|
// Erzwinge erlaubten Modus wenn nur einer verfügbar
|
||||||
|
if ($allowedViews === 'tree') {
|
||||||
|
$viewMode = 'tree';
|
||||||
|
} elseif ($allowedViews === 'graph') {
|
||||||
|
$viewMode = 'graph';
|
||||||
|
}
|
||||||
|
|
||||||
dol_include_once('/kundenkarte/lib/graph_view.lib.php');
|
dol_include_once('/kundenkarte/lib/graph_view.lib.php');
|
||||||
|
|
||||||
|
|
@ -390,6 +401,14 @@ print dol_get_fiche_head($head, 'anlagen', $langs->trans("ThirdParty"), -1, 'com
|
||||||
$linkback = '<a href="'.DOL_URL_ROOT.'/societe/list.php?restore_lastsearch_values=1">'.$langs->trans("BackToList").'</a>';
|
$linkback = '<a href="'.DOL_URL_ROOT.'/societe/list.php?restore_lastsearch_values=1">'.$langs->trans("BackToList").'</a>';
|
||||||
dol_banner_tab($object, 'socid', $linkback, ($user->socid ? 0 : 1), 'rowid', 'nom');
|
dol_banner_tab($object, 'socid', $linkback, ($user->socid ? 0 : 1), 'rowid', 'nom');
|
||||||
|
|
||||||
|
// Admin-Schnellzugriff (nur für Modul-Admins)
|
||||||
|
if ($user->admin || $user->hasRight('kundenkarte', 'admin')) {
|
||||||
|
$adminUrl = dol_buildpath('/kundenkarte/admin/setup.php', 1);
|
||||||
|
print '<div class="kundenkarte-admin-shortcut" style="text-align:right;margin:-8px 0 4px 0;">';
|
||||||
|
print '<a href="'.$adminUrl.'" title="'.$langs->trans('ModuleSetup').'" style="color:var(--colortextlink,#666);font-size:20px;opacity:0.7;"><i class="fa fa-cog"></i></a>';
|
||||||
|
print '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
print '<div class="fichecenter">';
|
print '<div class="fichecenter">';
|
||||||
|
|
||||||
// Confirmation dialogs
|
// Confirmation dialogs
|
||||||
|
|
@ -474,9 +493,11 @@ if ($isTreeView) {
|
||||||
if ($viewMode !== 'graph') {
|
if ($viewMode !== 'graph') {
|
||||||
// Baumansicht: Controls auf gleicher Zeile wie System-Tabs (im Wrapper)
|
// Baumansicht: Controls auf gleicher Zeile wie System-Tabs (im Wrapper)
|
||||||
print '<div class="kundenkarte-tree-controls">';
|
print '<div class="kundenkarte-tree-controls">';
|
||||||
print '<a class="button small" href="'.$toggleUrl.'" title="'.$toggleLabel.'">';
|
if ($allowedViews === 'both') {
|
||||||
print '<i class="fa '.$toggleIcon.'"></i> '.$toggleLabel;
|
print '<a class="button small" href="'.$toggleUrl.'" title="'.$toggleLabel.'">';
|
||||||
print '</a>';
|
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>';
|
||||||
|
|
@ -505,6 +526,7 @@ if ($isTreeView && $viewMode === 'graph') {
|
||||||
'contactid' => 0,
|
'contactid' => 0,
|
||||||
'systemid' => $systemId,
|
'systemid' => $systemId,
|
||||||
'viewMode' => $viewMode,
|
'viewMode' => $viewMode,
|
||||||
|
'allowedViews' => $allowedViews,
|
||||||
'permissiontoadd' => $permissiontoadd,
|
'permissiontoadd' => $permissiontoadd,
|
||||||
'pageUrl' => $_SERVER['PHP_SELF'],
|
'pageUrl' => $_SERVER['PHP_SELF'],
|
||||||
));
|
));
|
||||||
|
|
@ -1066,18 +1088,8 @@ if (empty($customerSystems)) {
|
||||||
$db->free($resql);
|
$db->free($resql);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pre-load all connections for this customer/system
|
// Verbindungen werden nur im Graph angezeigt, nicht im Baum
|
||||||
dol_include_once('/kundenkarte/class/anlageconnection.class.php');
|
|
||||||
$connObj = new AnlageConnection($db);
|
|
||||||
$allConnections = $connObj->fetchBySociete($id, $systemId);
|
|
||||||
// Index by target_id for quick lookup (connection shows ABOVE the target element)
|
|
||||||
$connectionsByTarget = array();
|
$connectionsByTarget = array();
|
||||||
foreach ($allConnections as $conn) {
|
|
||||||
if (!isset($connectionsByTarget[$conn->fk_target])) {
|
|
||||||
$connectionsByTarget[$conn->fk_target] = array();
|
|
||||||
}
|
|
||||||
$connectionsByTarget[$conn->fk_target][] = $conn;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($tree)) {
|
if (!empty($tree)) {
|
||||||
print '<div class="kundenkarte-tree" data-system="'.$systemId.'" data-socid="'.$id.'">';
|
print '<div class="kundenkarte-tree" data-system="'.$systemId.'" data-socid="'.$id.'">';
|
||||||
|
|
@ -1289,7 +1301,7 @@ function printTree($nodes, $socid, $systemId, $canEdit, $canDelete, $langs, $lev
|
||||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$socid.'&system='.$systemId.'&action=view&anlage_id='.$node->id.'" title="'.$langs->trans('View').'"><i class="fa fa-eye"></i></a>';
|
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$socid.'&system='.$systemId.'&action=view&anlage_id='.$node->id.'" title="'.$langs->trans('View').'"><i class="fa fa-eye"></i></a>';
|
||||||
if ($canEdit) {
|
if ($canEdit) {
|
||||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$socid.'&system='.$systemId.'&action=create&parent_id='.$node->id.'" title="'.$langs->trans('AddChild').'"><i class="fa fa-plus"></i></a>';
|
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$socid.'&system='.$systemId.'&action=create&parent_id='.$node->id.'" title="'.$langs->trans('AddChild').'"><i class="fa fa-plus"></i></a>';
|
||||||
print '<a href="#" class="anlage-connection-add" data-anlage-id="'.$node->id.'" data-soc-id="'.$socid.'" data-system-id="'.$systemId.'" title="'.$langs->trans('AddCableConnection').'"><i class="fa fa-plug"></i></a>';
|
|
||||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$socid.'&system='.$systemId.'&action=edit&anlage_id='.$node->id.'" title="'.$langs->trans('Edit').'"><i class="fa fa-edit"></i></a>';
|
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$socid.'&system='.$systemId.'&action=edit&anlage_id='.$node->id.'" title="'.$langs->trans('Edit').'"><i class="fa fa-edit"></i></a>';
|
||||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$socid.'&system='.$systemId.'&action=copy&anlage_id='.$node->id.'" title="'.$langs->trans('Copy').'"><i class="fa fa-copy"></i></a>';
|
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$socid.'&system='.$systemId.'&action=copy&anlage_id='.$node->id.'" title="'.$langs->trans('Copy').'"><i class="fa fa-copy"></i></a>';
|
||||||
}
|
}
|
||||||
|
|
@ -1553,7 +1565,7 @@ function printTreeWithCableLines($nodes, $socid, $systemId, $canEdit, $canDelete
|
||||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$socid.'&system='.$systemId.'&action=view&anlage_id='.$node->id.'" title="'.$langs->trans('View').'"><i class="fa fa-eye"></i></a>';
|
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$socid.'&system='.$systemId.'&action=view&anlage_id='.$node->id.'" title="'.$langs->trans('View').'"><i class="fa fa-eye"></i></a>';
|
||||||
if ($canEdit) {
|
if ($canEdit) {
|
||||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$socid.'&system='.$systemId.'&action=create&parent_id='.$node->id.'" title="'.$langs->trans('AddChild').'"><i class="fa fa-plus"></i></a>';
|
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$socid.'&system='.$systemId.'&action=create&parent_id='.$node->id.'" title="'.$langs->trans('AddChild').'"><i class="fa fa-plus"></i></a>';
|
||||||
print '<a href="#" class="anlage-connection-add" data-anlage-id="'.$node->id.'" data-soc-id="'.$socid.'" data-system-id="'.$systemId.'" title="'.$langs->trans('AddCableConnection').'"><i class="fa fa-plug"></i></a>';
|
|
||||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$socid.'&system='.$systemId.'&action=edit&anlage_id='.$node->id.'" title="'.$langs->trans('Edit').'"><i class="fa fa-edit"></i></a>';
|
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$socid.'&system='.$systemId.'&action=edit&anlage_id='.$node->id.'" title="'.$langs->trans('Edit').'"><i class="fa fa-edit"></i></a>';
|
||||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$socid.'&system='.$systemId.'&action=copy&anlage_id='.$node->id.'" title="'.$langs->trans('Copy').'"><i class="fa fa-copy"></i></a>';
|
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$socid.'&system='.$systemId.'&action=copy&anlage_id='.$node->id.'" title="'.$langs->trans('Copy').'"><i class="fa fa-copy"></i></a>';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -365,13 +365,24 @@ if ($action == 'togglepin' && $permissiontoadd) {
|
||||||
|
|
||||||
$title = $langs->trans('TechnicalInstallations').' - '.$object->getFullName($langs);
|
$title = $langs->trans('TechnicalInstallations').' - '.$object->getFullName($langs);
|
||||||
|
|
||||||
// Ansichtsmodus: URL-Parameter hat Vorrang, sonst Admin-Setting
|
// Ansichtsmodus: pro System konfigurierbar (tree/graph/both)
|
||||||
dol_include_once('/kundenkarte/lib/graph_view.lib.php');
|
dol_include_once('/kundenkarte/lib/graph_view.lib.php');
|
||||||
|
$allowedViews = 'both';
|
||||||
|
if ($systemId > 0 && isset($customerSystems[$systemId]) && !empty($customerSystems[$systemId]->view_modes)) {
|
||||||
|
$allowedViews = $customerSystems[$systemId]->view_modes;
|
||||||
|
}
|
||||||
|
|
||||||
$defaultView = getDolGlobalString('KUNDENKARTE_DEFAULT_VIEW', 'tree');
|
$defaultView = getDolGlobalString('KUNDENKARTE_DEFAULT_VIEW', 'tree');
|
||||||
$viewMode = GETPOST('view', 'aZ09');
|
$viewMode = GETPOST('view', 'aZ09');
|
||||||
if (!in_array($viewMode, array('tree', 'graph'))) {
|
if (!in_array($viewMode, array('tree', 'graph'))) {
|
||||||
$viewMode = $defaultView;
|
$viewMode = $defaultView;
|
||||||
}
|
}
|
||||||
|
// Erzwinge erlaubten Modus wenn nur einer verfügbar
|
||||||
|
if ($allowedViews === 'tree') {
|
||||||
|
$viewMode = 'tree';
|
||||||
|
} elseif ($allowedViews === 'graph') {
|
||||||
|
$viewMode = 'graph';
|
||||||
|
}
|
||||||
|
|
||||||
$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());
|
||||||
|
|
@ -388,6 +399,14 @@ print dol_get_fiche_head($head, 'anlagen', $langs->trans("ContactAddress"), -1,
|
||||||
$linkback = '<a href="'.DOL_URL_ROOT.'/contact/list.php?restore_lastsearch_values=1">'.$langs->trans("BackToList").'</a>';
|
$linkback = '<a href="'.DOL_URL_ROOT.'/contact/list.php?restore_lastsearch_values=1">'.$langs->trans("BackToList").'</a>';
|
||||||
dol_banner_tab($object, 'id', $linkback, 1, 'rowid', 'nom');
|
dol_banner_tab($object, 'id', $linkback, 1, 'rowid', 'nom');
|
||||||
|
|
||||||
|
// Admin-Schnellzugriff (nur für Modul-Admins)
|
||||||
|
if ($user->admin || $user->hasRight('kundenkarte', 'admin')) {
|
||||||
|
$adminUrl = dol_buildpath('/kundenkarte/admin/setup.php', 1);
|
||||||
|
print '<div class="kundenkarte-admin-shortcut" style="text-align:right;margin:-8px 0 4px 0;">';
|
||||||
|
print '<a href="'.$adminUrl.'" title="'.$langs->trans('ModuleSetup').'" style="color:var(--colortextlink,#666);font-size:20px;opacity:0.7;"><i class="fa fa-cog"></i></a>';
|
||||||
|
print '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
print '<div class="fichecenter">';
|
print '<div class="fichecenter">';
|
||||||
|
|
||||||
// Confirmation dialogs
|
// Confirmation dialogs
|
||||||
|
|
@ -472,9 +491,11 @@ if ($isTreeView) {
|
||||||
if ($viewMode !== 'graph') {
|
if ($viewMode !== 'graph') {
|
||||||
// Baumansicht: Controls auf gleicher Zeile wie System-Tabs (im Wrapper)
|
// Baumansicht: Controls auf gleicher Zeile wie System-Tabs (im Wrapper)
|
||||||
print '<div class="kundenkarte-tree-controls">';
|
print '<div class="kundenkarte-tree-controls">';
|
||||||
print '<a class="button small" href="'.$toggleUrl.'" title="'.$toggleLabel.'">';
|
if ($allowedViews === 'both') {
|
||||||
print '<i class="fa '.$toggleIcon.'"></i> '.$toggleLabel;
|
print '<a class="button small" href="'.$toggleUrl.'" title="'.$toggleLabel.'">';
|
||||||
print '</a>';
|
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>';
|
||||||
|
|
@ -503,6 +524,7 @@ if ($isTreeView && $viewMode === 'graph') {
|
||||||
'contactid' => $id,
|
'contactid' => $id,
|
||||||
'systemid' => $systemId,
|
'systemid' => $systemId,
|
||||||
'viewMode' => $viewMode,
|
'viewMode' => $viewMode,
|
||||||
|
'allowedViews' => $allowedViews,
|
||||||
'permissiontoadd' => $permissiontoadd,
|
'permissiontoadd' => $permissiontoadd,
|
||||||
'pageUrl' => $_SERVER['PHP_SELF'],
|
'pageUrl' => $_SERVER['PHP_SELF'],
|
||||||
));
|
));
|
||||||
|
|
@ -1064,18 +1086,8 @@ if (empty($customerSystems)) {
|
||||||
$db->free($resql);
|
$db->free($resql);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pre-load all connections for this contact/system
|
// Verbindungen werden nur im Graph angezeigt, nicht im Baum
|
||||||
dol_include_once('/kundenkarte/class/anlageconnection.class.php');
|
|
||||||
$connObj = new AnlageConnection($db);
|
|
||||||
$allConnections = $connObj->fetchBySociete($object->socid, $systemId);
|
|
||||||
// Index by target_id for quick lookup (connection shows ABOVE the target element)
|
|
||||||
$connectionsByTarget = array();
|
$connectionsByTarget = array();
|
||||||
foreach ($allConnections as $conn) {
|
|
||||||
if (!isset($connectionsByTarget[$conn->fk_target])) {
|
|
||||||
$connectionsByTarget[$conn->fk_target] = array();
|
|
||||||
}
|
|
||||||
$connectionsByTarget[$conn->fk_target][] = $conn;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($tree)) {
|
if (!empty($tree)) {
|
||||||
print '<div class="kundenkarte-tree" data-system="'.$systemId.'" data-socid="'.$object->socid.'">';
|
print '<div class="kundenkarte-tree" data-system="'.$systemId.'" data-socid="'.$object->socid.'">';
|
||||||
|
|
@ -1287,7 +1299,7 @@ function printTree($nodes, $contactid, $systemId, $canEdit, $canDelete, $langs,
|
||||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=view&anlage_id='.$node->id.'" title="'.$langs->trans('View').'"><i class="fa fa-eye"></i></a>';
|
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=view&anlage_id='.$node->id.'" title="'.$langs->trans('View').'"><i class="fa fa-eye"></i></a>';
|
||||||
if ($canEdit) {
|
if ($canEdit) {
|
||||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=create&parent_id='.$node->id.'" title="'.$langs->trans('AddChild').'"><i class="fa fa-plus"></i></a>';
|
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=create&parent_id='.$node->id.'" title="'.$langs->trans('AddChild').'"><i class="fa fa-plus"></i></a>';
|
||||||
print '<a href="#" class="anlage-connection-add" data-anlage-id="'.$node->id.'" data-soc-id="'.$node->fk_soc.'" data-system-id="'.$systemId.'" title="'.$langs->trans('AddCableConnection').'"><i class="fa fa-plug"></i></a>';
|
|
||||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=edit&anlage_id='.$node->id.'" title="'.$langs->trans('Edit').'"><i class="fa fa-edit"></i></a>';
|
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=edit&anlage_id='.$node->id.'" title="'.$langs->trans('Edit').'"><i class="fa fa-edit"></i></a>';
|
||||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=copy&anlage_id='.$node->id.'" title="'.$langs->trans('Copy').'"><i class="fa fa-copy"></i></a>';
|
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=copy&anlage_id='.$node->id.'" title="'.$langs->trans('Copy').'"><i class="fa fa-copy"></i></a>';
|
||||||
}
|
}
|
||||||
|
|
@ -1583,7 +1595,7 @@ function printTreeWithCableLines($nodes, $contactid, $systemId, $canEdit, $canDe
|
||||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=view&anlage_id='.$node->id.'" title="'.$langs->trans('View').'"><i class="fa fa-eye"></i></a>';
|
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=view&anlage_id='.$node->id.'" title="'.$langs->trans('View').'"><i class="fa fa-eye"></i></a>';
|
||||||
if ($canEdit) {
|
if ($canEdit) {
|
||||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=create&parent_id='.$node->id.'" title="'.$langs->trans('AddChild').'"><i class="fa fa-plus"></i></a>';
|
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=create&parent_id='.$node->id.'" title="'.$langs->trans('AddChild').'"><i class="fa fa-plus"></i></a>';
|
||||||
print '<a href="#" class="anlage-connection-add" data-anlage-id="'.$node->id.'" data-soc-id="'.$node->fk_soc.'" data-system-id="'.$systemId.'" title="'.$langs->trans('AddCableConnection').'"><i class="fa fa-plug"></i></a>';
|
|
||||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=edit&anlage_id='.$node->id.'" title="'.$langs->trans('Edit').'"><i class="fa fa-edit"></i></a>';
|
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=edit&anlage_id='.$node->id.'" title="'.$langs->trans('Edit').'"><i class="fa fa-edit"></i></a>';
|
||||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=copy&anlage_id='.$node->id.'" title="'.$langs->trans('Copy').'"><i class="fa fa-copy"></i></a>';
|
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$contactid.'&system='.$systemId.'&action=copy&anlage_id='.$node->id.'" title="'.$langs->trans('Copy').'"><i class="fa fa-copy"></i></a>';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue