Graph: Bearbeitungsmodus, Toolbar-Layout, Contact-Filter
- Nodes standardmäßig gesperrt (autoungrabify), nur per "Anordnen"-Button verschiebbar, explizites Speichern/Abbrechen - Graph-Toolbar unter die System-Tab-Borderlinie verschoben - Anordnen/Speichern/Abbrechen rechts in Zeile 2 (Spacer) - Contact-Filter in graph_data.php: Kunden-Ebene zeigt nur Elemente ohne Kontaktzuweisung (konsistent mit Baumansicht) - CSS: display ohne !important damit style="display:none" greift - Changelog und Sprachdateien aktualisiert Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
840c0132c3
commit
e6b28fe85e
8 changed files with 520 additions and 75 deletions
13
ChangeLog.md
13
ChangeLog.md
|
|
@ -7,15 +7,18 @@
|
||||||
- Raeume als Compound-Container, Geraete als Nodes darin
|
- Raeume als Compound-Container, Geraete als Nodes darin
|
||||||
- Kabelverbindungen als sichtbare Edges (auch raumuebergreifend)
|
- Kabelverbindungen als sichtbare Edges (auch raumuebergreifend)
|
||||||
- Durchgeschleifte Leitungen als gestrichelte Linien
|
- Durchgeschleifte Leitungen als gestrichelte Linien
|
||||||
- Zwei Layout-Modi per Knopfdruck:
|
- Dagre-Layout: Hierarchischer Stromfluss top-down
|
||||||
- **Raeumlich** (cose-bilkent): Geraete gruppiert nach Raeumen
|
|
||||||
- **Technisch** (dagre): Hierarchischer Stromfluss top-down
|
|
||||||
- Zoom/Pan/Fit-Controls, Mausrad-Zoom Toggle
|
- Zoom/Pan/Fit-Controls, Mausrad-Zoom Toggle
|
||||||
- Kabeltyp-Legende mit Farben
|
- Kabeltyp-Legende mit Farben
|
||||||
- Node-Positionen speicherbar (Drag&Drop → AJAX-Speicherung)
|
- **Bearbeitungsmodus**: Nodes nur per "Anordnen"-Button verschiebbar,
|
||||||
|
Positionen per "Speichern"-Button fest, "Abbrechen" setzt zurueck
|
||||||
- Viewport-Persistenz (Zoom/Pan bleibt beim Seitenwechsel)
|
- Viewport-Persistenz (Zoom/Pan bleibt beim Seitenwechsel)
|
||||||
- Klick auf Node/Edge oeffnet Detail-/Bearbeitungsseite
|
- Klick auf Node/Edge oeffnet Detail-/Bearbeitungsseite
|
||||||
|
- Suche als Overlay im Graph-Container (Nodes hervorheben/abdunkeln)
|
||||||
|
- Kontextmenue (Rechtsklick): Ansehen, Bearbeiten, Kopieren, Loeschen
|
||||||
|
- PNG-Export des Graphen
|
||||||
- Admin-Setting: Ansichtsmodus (Baum/Graph) in Setup waehlbar
|
- Admin-Setting: Ansichtsmodus (Baum/Graph) in Setup waehlbar
|
||||||
|
- Toolbar zweizeilig: Aktionen oben, Graph-Steuerung unten
|
||||||
|
|
||||||
- **Verbindungsformular verbessert**
|
- **Verbindungsformular verbessert**
|
||||||
- Select-Dropdowns zeigen nur Geraete (keine Gebaeude/Raeume)
|
- Select-Dropdowns zeigen nur Geraete (keine Gebaeude/Raeume)
|
||||||
|
|
@ -31,6 +34,8 @@
|
||||||
- `js/cytoscape.min.js`, `js/dagre.min.js`, `js/cytoscape-dagre.js`, `js/cytoscape-cose-bilkent.js` - Bibliotheken
|
- `js/cytoscape.min.js`, `js/dagre.min.js`, `js/cytoscape-dagre.js`, `js/cytoscape-cose-bilkent.js` - Bibliotheken
|
||||||
|
|
||||||
### Bugfixes
|
### Bugfixes
|
||||||
|
- **Contact-Filter im Graph**: Graph zeigte faelschlicherweise Kontakt-Elemente auf Kunden-Ebene
|
||||||
|
- Fix: `fk_contact` Filter in `graph_data.php` analog zum Baum
|
||||||
- **Verbindung hinzufuegen**: Formular zeigte "Feld erforderlich"-Fehler beim Oeffnen
|
- **Verbindung hinzufuegen**: Formular zeigte "Feld erforderlich"-Fehler beim Oeffnen
|
||||||
- Ursache: `action=create` in URL triggerte Handler vor Formular-Anzeige
|
- Ursache: `action=create` in URL triggerte Handler vor Formular-Anzeige
|
||||||
- Fix: Korrekte Dolibarr-Konvention (create=Formular, add=Verarbeitung)
|
- Fix: Korrekte Dolibarr-Konvention (create=Formular, add=Verarbeitung)
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ if ($resFields) {
|
||||||
// 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,";
|
||||||
$sql .= " t.label as type_label, t.picto as type_picto,";
|
$sql .= " t.label as type_label, t.picto as type_picto, t.color as type_color,";
|
||||||
$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,";
|
||||||
|
|
@ -78,6 +78,9 @@ $sql .= " WHERE a.fk_soc = ".(int)$socId;
|
||||||
$sql .= " AND s.code != 'GLOBAL'";
|
$sql .= " AND s.code != 'GLOBAL'";
|
||||||
if ($contactId > 0) {
|
if ($contactId > 0) {
|
||||||
$sql .= " AND a.fk_contact = ".(int)$contactId;
|
$sql .= " AND a.fk_contact = ".(int)$contactId;
|
||||||
|
} else {
|
||||||
|
// Auf Kunden-Ebene nur Elemente ohne Kontaktzuweisung (wie im Baum)
|
||||||
|
$sql .= " AND (a.fk_contact IS NULL OR a.fk_contact = 0)";
|
||||||
}
|
}
|
||||||
$sql .= " AND a.status = 1";
|
$sql .= " AND a.status = 1";
|
||||||
$sql .= " ORDER BY a.fk_parent, a.rang, a.rowid";
|
$sql .= " ORDER BY a.fk_parent, a.rang, a.rowid";
|
||||||
|
|
@ -99,6 +102,7 @@ if ($resql) {
|
||||||
'label' => $obj->label,
|
'label' => $obj->label,
|
||||||
'type_label' => $obj->type_label ?: '',
|
'type_label' => $obj->type_label ?: '',
|
||||||
'type_picto' => $obj->type_picto ?: '',
|
'type_picto' => $obj->type_picto ?: '',
|
||||||
|
'type_color' => $obj->type_color ?: '',
|
||||||
'system_code' => $obj->system_code ?: '',
|
'system_code' => $obj->system_code ?: '',
|
||||||
'system_label' => $obj->system_label ?: '',
|
'system_label' => $obj->system_label ?: '',
|
||||||
'fk_parent' => (int) $obj->fk_parent,
|
'fk_parent' => (int) $obj->fk_parent,
|
||||||
|
|
@ -133,13 +137,14 @@ if ($resql) {
|
||||||
'value' => $val,
|
'value' => $val,
|
||||||
'display' => $display,
|
'display' => $display,
|
||||||
'color' => isset($fm['color']) ? $fm['color'] : '',
|
'color' => isset($fm['color']) ? $fm['color'] : '',
|
||||||
|
'type' => isset($fm['type']) ? $fm['type'] : 'text',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
$nodeData['fields'] = $fields;
|
$nodeData['fields'] = $fields;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compound-Parent aus fk_parent (wie im Baum)
|
// Compound-Parent aus fk_parent (Eltern-Kind-Verschachtelung)
|
||||||
if ($obj->fk_parent > 0) {
|
if ($obj->fk_parent > 0) {
|
||||||
$nodeData['parent'] = 'n_'.$obj->fk_parent;
|
$nodeData['parent'] = 'n_'.$obj->fk_parent;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -618,6 +618,21 @@ body.kundenkarte-drag-active * {
|
||||||
vertical-align: middle !important;
|
vertical-align: middle !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* System hinzufügen Button - kleiner, knapp über der Linie */
|
||||||
|
.kundenkarte-add-system-btn {
|
||||||
|
font-size: 11px !important;
|
||||||
|
padding: 4px 10px !important;
|
||||||
|
margin-left: auto !important;
|
||||||
|
align-self: flex-end !important;
|
||||||
|
margin-bottom: -1px !important;
|
||||||
|
height: auto !important;
|
||||||
|
line-height: 1.3 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kundenkarte-add-system-btn i {
|
||||||
|
font-size: 9px !important;
|
||||||
|
}
|
||||||
|
|
||||||
.kundenkarte-system-tab {
|
.kundenkarte-system-tab {
|
||||||
padding: 8px 16px !important;
|
padding: 8px 16px !important;
|
||||||
border: 1px solid #555 !important;
|
border: 1px solid #555 !important;
|
||||||
|
|
@ -643,7 +658,7 @@ body.kundenkarte-drag-active * {
|
||||||
}
|
}
|
||||||
|
|
||||||
.kundenkarte-system-tab-icon {
|
.kundenkarte-system-tab-icon {
|
||||||
font-size: 16px !important;
|
font-size: 14px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ========================================
|
/* ========================================
|
||||||
|
|
|
||||||
|
|
@ -33,27 +33,37 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Toolbar: Aktionen links, Zoom rechts */
|
/* Toolbar: Zweizeilig */
|
||||||
.kundenkarte-graph-toolbar {
|
.kundenkarte-graph-toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
flex-direction: column;
|
||||||
align-items: center;
|
|
||||||
padding: 8px 0;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.kundenkarte-graph-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
|
padding: 8px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.kundenkarte-graph-actions .button {
|
.kundenkarte-graph-toolbar-row {
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.kundenkarte-graph-zoom-controls {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 4px;
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Alle Buttons in der Graph-Toolbar einheitlich */
|
||||||
|
.kundenkarte-graph-toolbar .button,
|
||||||
|
.kundenkarte-graph-toolbar button.button {
|
||||||
|
white-space: nowrap;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center !important;
|
||||||
|
gap: 4px !important;
|
||||||
|
padding: 6px 12px !important;
|
||||||
|
font-size: 12px !important;
|
||||||
|
height: 30px !important;
|
||||||
|
box-sizing: border-box !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Spacer: schiebt Anordnen/Speichern/Abbrechen nach rechts */
|
||||||
|
.kundenkarte-graph-toolbar-spacer {
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#btn-graph-wheel-zoom.active {
|
#btn-graph-wheel-zoom.active {
|
||||||
|
|
@ -62,6 +72,33 @@
|
||||||
border-color: var(--butactionbg, #4390dc) !important;
|
border-color: var(--butactionbg, #4390dc) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Bearbeitungsmodus: Container-Rahmen */
|
||||||
|
#kundenkarte-graph-container.graph-edit-mode {
|
||||||
|
border-color: #5a9a6a;
|
||||||
|
box-shadow: 0 0 0 2px rgba(90, 154, 106, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Speichern-Button grün, Abbrechen-Button rot */
|
||||||
|
.btn-graph-save {
|
||||||
|
background: #2e7d32 !important;
|
||||||
|
border-color: #2e7d32 !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-graph-save:hover {
|
||||||
|
background: #388e3c !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-graph-cancel {
|
||||||
|
background: #c62828 !important;
|
||||||
|
border-color: #c62828 !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-graph-cancel:hover {
|
||||||
|
background: #d32f2f !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* Legende */
|
/* Legende */
|
||||||
.kundenkarte-graph-legend {
|
.kundenkarte-graph-legend {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -199,31 +236,98 @@
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Kontextmenü (Rechtsklick auf Node) */
|
||||||
|
.kundenkarte-graph-contextmenu {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 200;
|
||||||
|
background: var(--colorbacktabcard1, #1e2a3a);
|
||||||
|
border: 1px solid var(--inputbordercolor, #3a6a8e);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 4px 0;
|
||||||
|
min-width: 160px;
|
||||||
|
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.kundenkarte-graph-contextmenu .ctx-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 14px;
|
||||||
|
color: var(--colortext, #ddd);
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kundenkarte-graph-contextmenu .ctx-item:hover {
|
||||||
|
background: var(--butactionbg, #4390dc);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kundenkarte-graph-contextmenu .ctx-item i {
|
||||||
|
width: 16px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kundenkarte-graph-contextmenu .ctx-delete {
|
||||||
|
color: #e57373;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kundenkarte-graph-contextmenu .ctx-delete:hover {
|
||||||
|
background: #c62828;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Suchfeld als Overlay im Graph-Container */
|
||||||
|
.kundenkarte-graph-search-floating {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 8px;
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kundenkarte-graph-search-floating input {
|
||||||
|
padding: 6px 10px 6px 28px;
|
||||||
|
border: 1px solid var(--inputbordercolor, #3a3a3a);
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--colorbackbody, #1d1e20);
|
||||||
|
color: var(--colortext, #ddd);
|
||||||
|
font-size: 12px;
|
||||||
|
width: 180px;
|
||||||
|
opacity: 0.7;
|
||||||
|
transition: opacity 0.2s, width 0.2s;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2'%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'/%3E%3C/svg%3E");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: 8px center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kundenkarte-graph-search-floating input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--butactionbg, #4390dc);
|
||||||
|
opacity: 1;
|
||||||
|
width: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Mobile Anpassungen */
|
/* Mobile Anpassungen */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.kundenkarte-graph-toolbar {
|
.kundenkarte-graph-toolbar-row {
|
||||||
flex-wrap: wrap;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.kundenkarte-graph-actions {
|
.kundenkarte-graph-toolbar .button,
|
||||||
flex: 1 1 100%;
|
.kundenkarte-graph-toolbar button.button {
|
||||||
order: 2;
|
padding: 8px 8px !important;
|
||||||
|
font-size: 11px !important;
|
||||||
|
height: 28px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.kundenkarte-graph-actions .button {
|
.kundenkarte-graph-search-floating input {
|
||||||
flex: 1;
|
width: 140px;
|
||||||
text-align: center;
|
|
||||||
padding: 10px 8px !important;
|
|
||||||
font-size: 12px !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.kundenkarte-graph-zoom-controls {
|
.kundenkarte-graph-search-floating input:focus {
|
||||||
order: 1;
|
width: 160px;
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.kundenkarte-graph-zoom-controls .button {
|
|
||||||
padding: 10px 8px !important;
|
|
||||||
font-size: 12px !important;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,12 +61,20 @@
|
||||||
ajaxUrl: '',
|
ajaxUrl: '',
|
||||||
saveUrl: '',
|
saveUrl: '',
|
||||||
moduleUrl: '',
|
moduleUrl: '',
|
||||||
|
pageUrl: '',
|
||||||
|
systemId: 0,
|
||||||
socId: 0,
|
socId: 0,
|
||||||
contactId: 0,
|
contactId: 0,
|
||||||
isContact: false,
|
isContact: false,
|
||||||
|
canEdit: false,
|
||||||
|
canDelete: false,
|
||||||
tooltipEl: null,
|
tooltipEl: null,
|
||||||
|
contextMenuEl: null,
|
||||||
|
contextMenuNodeId: null,
|
||||||
wheelZoomEnabled: false,
|
wheelZoomEnabled: false,
|
||||||
hasPositions: false,
|
hasPositions: false,
|
||||||
|
editMode: false,
|
||||||
|
_savedPositions: {},
|
||||||
_saveTimer: null,
|
_saveTimer: null,
|
||||||
_dirtyNodes: {},
|
_dirtyNodes: {},
|
||||||
_viewportTimer: null,
|
_viewportTimer: null,
|
||||||
|
|
@ -80,7 +88,9 @@
|
||||||
this.isContact = config.isContact || false;
|
this.isContact = config.isContact || false;
|
||||||
|
|
||||||
this.createTooltipElement();
|
this.createTooltipElement();
|
||||||
|
this.initContextMenu();
|
||||||
this.bindZoomButtons();
|
this.bindZoomButtons();
|
||||||
|
this.bindSearch();
|
||||||
this.loadGraphData();
|
this.loadGraphData();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -280,7 +290,7 @@
|
||||||
var f = n.data.fields[i];
|
var f = n.data.fields[i];
|
||||||
var v = f.value;
|
var v = f.value;
|
||||||
if (!v || v === '') continue;
|
if (!v || v === '') continue;
|
||||||
if (v === '1' && f.display === 'badge') v = '\u2713';
|
if (f.type === 'checkbox' && (v === '1' || v === 'true')) v = '\u2713';
|
||||||
|
|
||||||
if (f.display === 'parentheses') {
|
if (f.display === 'parentheses') {
|
||||||
parenParts.push(v);
|
parenParts.push(v);
|
||||||
|
|
@ -305,6 +315,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Datei-Indikatoren
|
||||||
|
var fileInfo = [];
|
||||||
|
if (n.data.image_count > 0) fileInfo.push('\ud83d\uddbc ' + n.data.image_count);
|
||||||
|
if (n.data.doc_count > 0) fileInfo.push('\ud83d\udcc4 ' + n.data.doc_count);
|
||||||
|
if (fileInfo.length > 0) {
|
||||||
|
if (badgeLines.length === 0) lines.push('─────────────');
|
||||||
|
lines.push(fileInfo.join(' '));
|
||||||
|
}
|
||||||
|
|
||||||
n.data.display_label = lines.join('\n');
|
n.data.display_label = lines.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -350,6 +369,7 @@
|
||||||
userZoomingEnabled: false,
|
userZoomingEnabled: false,
|
||||||
userPanningEnabled: true,
|
userPanningEnabled: true,
|
||||||
boxSelectionEnabled: false,
|
boxSelectionEnabled: false,
|
||||||
|
autoungrabify: true,
|
||||||
layout: { name: 'preset' }
|
layout: { name: 'preset' }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -385,11 +405,25 @@
|
||||||
this.bindGraphEvents();
|
this.bindGraphEvents();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dolibarr CSS-Variablen auslesen (Fallback für nicht-gesetzte Werte)
|
||||||
|
*/
|
||||||
|
getCssVar: function(name, fallback) {
|
||||||
|
try {
|
||||||
|
var val = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
||||||
|
return val || fallback;
|
||||||
|
} catch(e) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cytoscape Stylesheet
|
* Cytoscape Stylesheet
|
||||||
*/
|
*/
|
||||||
getStylesheet: function() {
|
getStylesheet: function() {
|
||||||
var faFont = '"Font Awesome 5 Free", "FontAwesome", sans-serif';
|
var faFont = '"Font Awesome 5 Free", "FontAwesome", sans-serif';
|
||||||
|
// Dolibarr-Schriftfarbe verwenden
|
||||||
|
var textColor = this.getCssVar('--colortext', '#dcdcdc');
|
||||||
|
|
||||||
return [
|
return [
|
||||||
// Räume/Gebäude (Compound-Container)
|
// Räume/Gebäude (Compound-Container)
|
||||||
|
|
@ -400,7 +434,7 @@
|
||||||
'background-color': '#2a2b2d',
|
'background-color': '#2a2b2d',
|
||||||
'background-opacity': 1,
|
'background-opacity': 1,
|
||||||
'border-width': 2,
|
'border-width': 2,
|
||||||
'border-color': '#4390dc',
|
'border-color': function(node) { return node.data('type_color') || '#4390dc'; },
|
||||||
'border-style': 'dashed',
|
'border-style': 'dashed',
|
||||||
'padding': '25px',
|
'padding': '25px',
|
||||||
'label': 'data(display_label)',
|
'label': 'data(display_label)',
|
||||||
|
|
@ -409,7 +443,7 @@
|
||||||
'text-valign': 'top',
|
'text-valign': 'top',
|
||||||
'text-halign': 'center',
|
'text-halign': 'center',
|
||||||
'font-size': '13px',
|
'font-size': '13px',
|
||||||
'color': '#ffffff',
|
'color': textColor,
|
||||||
'text-margin-y': -8,
|
'text-margin-y': -8,
|
||||||
'text-background-color': '#2a2b2d',
|
'text-background-color': '#2a2b2d',
|
||||||
'text-background-opacity': 0.95,
|
'text-background-opacity': 0.95,
|
||||||
|
|
@ -429,7 +463,7 @@
|
||||||
'padding': '14px',
|
'padding': '14px',
|
||||||
'background-color': '#2d4a3a',
|
'background-color': '#2d4a3a',
|
||||||
'border-width': 2,
|
'border-width': 2,
|
||||||
'border-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': 'bold',
|
||||||
|
|
@ -437,7 +471,7 @@
|
||||||
'text-halign': 'center',
|
'text-halign': 'center',
|
||||||
'text-justification': 'left',
|
'text-justification': 'left',
|
||||||
'font-size': '11px',
|
'font-size': '11px',
|
||||||
'color': '#ffffff',
|
'color': textColor,
|
||||||
'text-wrap': 'wrap',
|
'text-wrap': 'wrap',
|
||||||
'text-max-width': '220px',
|
'text-max-width': '220px',
|
||||||
'line-height': 1.4
|
'line-height': 1.4
|
||||||
|
|
@ -491,10 +525,122 @@
|
||||||
'border-color': '#d4944a',
|
'border-color': '#d4944a',
|
||||||
'border-width': 3
|
'border-width': 3
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
// Suche: abgedunkelte Nodes
|
||||||
|
{
|
||||||
|
selector: '.search-dimmed',
|
||||||
|
style: {
|
||||||
|
'opacity': 0.2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Suche: hervorgehobene Treffer
|
||||||
|
{
|
||||||
|
selector: '.search-highlight',
|
||||||
|
style: {
|
||||||
|
'border-color': '#e8a838',
|
||||||
|
'border-width': 3,
|
||||||
|
'opacity': 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kontextmenü initialisieren
|
||||||
|
*/
|
||||||
|
initContextMenu: function() {
|
||||||
|
var self = this;
|
||||||
|
this.contextMenuEl = document.getElementById('kundenkarte-graph-contextmenu');
|
||||||
|
if (!this.contextMenuEl) return;
|
||||||
|
|
||||||
|
// Klick auf Menü-Eintrag
|
||||||
|
$(this.contextMenuEl).on('click', '.ctx-item', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var action = $(this).data('action');
|
||||||
|
var anlageId = self.contextMenuNodeId;
|
||||||
|
if (!anlageId) return;
|
||||||
|
|
||||||
|
var baseUrl = self.pageUrl + '?id=' + self.socId + '&system=' + self.systemId;
|
||||||
|
switch (action) {
|
||||||
|
case 'view':
|
||||||
|
window.location.href = baseUrl + '&action=view&anlage_id=' + anlageId;
|
||||||
|
break;
|
||||||
|
case 'edit':
|
||||||
|
window.location.href = baseUrl + '&action=edit&anlage_id=' + anlageId;
|
||||||
|
break;
|
||||||
|
case 'copy':
|
||||||
|
window.location.href = baseUrl + '&action=copy&anlage_id=' + anlageId;
|
||||||
|
break;
|
||||||
|
case 'delete':
|
||||||
|
window.location.href = baseUrl + '&action=delete&anlage_id=' + anlageId;
|
||||||
|
break;
|
||||||
|
case 'add-child':
|
||||||
|
window.location.href = baseUrl + '&action=create&parent_id=' + anlageId;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
self.hideContextMenu();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Klick außerhalb schließt Menü
|
||||||
|
$(document).on('click', function() {
|
||||||
|
self.hideContextMenu();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
showContextMenu: function(node, renderedPos) {
|
||||||
|
if (!this.contextMenuEl) return;
|
||||||
|
this.contextMenuNodeId = node.data('id').replace('n_', '');
|
||||||
|
|
||||||
|
var container = document.getElementById(this.containerId);
|
||||||
|
var rect = container.getBoundingClientRect();
|
||||||
|
var x = rect.left + renderedPos.x;
|
||||||
|
var y = rect.top + renderedPos.y + window.scrollY;
|
||||||
|
|
||||||
|
// Nicht über Bildschirmrand hinaus
|
||||||
|
this.contextMenuEl.style.display = 'block';
|
||||||
|
var menuW = this.contextMenuEl.offsetWidth;
|
||||||
|
var menuH = this.contextMenuEl.offsetHeight;
|
||||||
|
if (x + menuW > window.innerWidth) x = window.innerWidth - menuW - 10;
|
||||||
|
if (y + menuH > window.scrollY + window.innerHeight) y = y - menuH;
|
||||||
|
|
||||||
|
this.contextMenuEl.style.left = x + 'px';
|
||||||
|
this.contextMenuEl.style.top = y + 'px';
|
||||||
|
},
|
||||||
|
|
||||||
|
hideContextMenu: function() {
|
||||||
|
if (this.contextMenuEl) {
|
||||||
|
this.contextMenuEl.style.display = 'none';
|
||||||
|
this.contextMenuNodeId = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suchfunktion binden
|
||||||
|
*/
|
||||||
|
bindSearch: function() {
|
||||||
|
var self = this;
|
||||||
|
$(document).on('input', '#kundenkarte-graph-search', function() {
|
||||||
|
var query = $(this).val().toLowerCase().trim();
|
||||||
|
if (!self.cy) return;
|
||||||
|
|
||||||
|
if (query === '') {
|
||||||
|
// Alle Nodes wieder normal anzeigen
|
||||||
|
self.cy.nodes().removeClass('search-dimmed search-highlight');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.cy.nodes().forEach(function(node) {
|
||||||
|
var label = (node.data('label') || '').toLowerCase();
|
||||||
|
var typeLabel = (node.data('type_label') || '').toLowerCase();
|
||||||
|
if (label.indexOf(query) !== -1 || typeLabel.indexOf(query) !== -1) {
|
||||||
|
node.removeClass('search-dimmed').addClass('search-highlight');
|
||||||
|
} else {
|
||||||
|
node.removeClass('search-highlight').addClass('search-dimmed');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Graph-Events binden
|
* Graph-Events binden
|
||||||
*/
|
*/
|
||||||
|
|
@ -539,6 +685,20 @@
|
||||||
self.hideTooltip();
|
self.hideTooltip();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Rechtsklick → Kontextmenü
|
||||||
|
this.cy.on('cxttap', 'node', function(evt) {
|
||||||
|
evt.originalEvent.preventDefault();
|
||||||
|
self.hideTooltip();
|
||||||
|
self.showContextMenu(evt.target, evt.renderedPosition);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Klick auf Canvas schließt Kontextmenü
|
||||||
|
this.cy.on('tap', function(evt) {
|
||||||
|
if (evt.target === self.cy) {
|
||||||
|
self.hideContextMenu();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Drag: Begrenzung auf sichtbaren Bereich
|
// Drag: Begrenzung auf sichtbaren Bereich
|
||||||
this.cy.on('drag', 'node', function(evt) {
|
this.cy.on('drag', 'node', function(evt) {
|
||||||
var node = evt.target;
|
var node = evt.target;
|
||||||
|
|
@ -553,8 +713,9 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Drag: Snap-to-Grid + Position merken (inkl. Kinder bei Compound-Nodes)
|
// Drag: Snap-to-Grid + Position merken (nur im Bearbeitungsmodus)
|
||||||
this.cy.on('dragfree', 'node', function(evt) {
|
this.cy.on('dragfree', 'node', function(evt) {
|
||||||
|
if (!self.editMode) return;
|
||||||
var node = evt.target;
|
var node = evt.target;
|
||||||
var pos = node.position();
|
var pos = node.position();
|
||||||
var grid = self.gridSize;
|
var grid = self.gridSize;
|
||||||
|
|
@ -584,11 +745,6 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debounced speichern (500ms nach letztem Drag)
|
|
||||||
if (self._saveTimer) clearTimeout(self._saveTimer);
|
|
||||||
self._saveTimer = setTimeout(function() {
|
|
||||||
self.savePositions();
|
|
||||||
}, 500);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Viewport-Änderungen speichern (Pan + Zoom)
|
// Viewport-Änderungen speichern (Pan + Zoom)
|
||||||
|
|
@ -656,6 +812,106 @@
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
self.resetLayout();
|
self.resetLayout();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Bearbeitungsmodus: Anordnen ein/aus
|
||||||
|
$(document).on('click', '#btn-graph-edit-mode', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
self.enterEditMode();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Bearbeitungsmodus: Speichern
|
||||||
|
$(document).on('click', '#btn-graph-save-positions', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
self.savePositions();
|
||||||
|
self.exitEditMode();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Bearbeitungsmodus: Abbrechen
|
||||||
|
$(document).on('click', '#btn-graph-cancel-edit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
self.cancelEditMode();
|
||||||
|
});
|
||||||
|
|
||||||
|
// PNG-Export
|
||||||
|
$(document).on('click', '#btn-graph-export-png', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!self.cy) return;
|
||||||
|
var png64 = self.cy.png({
|
||||||
|
output: 'base64uri',
|
||||||
|
bg: getComputedStyle(document.documentElement).getPropertyValue('--colorbackbody').trim() || '#1d1e20',
|
||||||
|
full: true,
|
||||||
|
scale: 2
|
||||||
|
});
|
||||||
|
var link = document.createElement('a');
|
||||||
|
link.download = 'graph_export.png';
|
||||||
|
link.href = png64;
|
||||||
|
link.click();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bearbeitungsmodus aktivieren - Nodes werden verschiebbar
|
||||||
|
*/
|
||||||
|
enterEditMode: function() {
|
||||||
|
if (!this.cy || this.editMode) return;
|
||||||
|
this.editMode = true;
|
||||||
|
this._dirtyNodes = {};
|
||||||
|
|
||||||
|
// Aktuelle Positionen sichern (für Abbrechen)
|
||||||
|
this._savedPositions = {};
|
||||||
|
var self = this;
|
||||||
|
this.cy.nodes().forEach(function(node) {
|
||||||
|
var pos = node.position();
|
||||||
|
self._savedPositions[node.data('id')] = { x: pos.x, y: pos.y };
|
||||||
|
});
|
||||||
|
|
||||||
|
// Nodes verschiebbar machen
|
||||||
|
this.cy.autoungrabify(false);
|
||||||
|
|
||||||
|
// Buttons umschalten
|
||||||
|
$('#btn-graph-edit-mode').hide();
|
||||||
|
$('#btn-graph-save-positions, #btn-graph-cancel-edit').show();
|
||||||
|
|
||||||
|
// Visuelles Feedback: Container-Rahmen
|
||||||
|
$('#' + this.containerId).addClass('graph-edit-mode');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bearbeitungsmodus beenden (nach Speichern)
|
||||||
|
*/
|
||||||
|
exitEditMode: function() {
|
||||||
|
if (!this.cy) return;
|
||||||
|
this.editMode = false;
|
||||||
|
this._savedPositions = {};
|
||||||
|
|
||||||
|
// Nodes wieder sperren
|
||||||
|
this.cy.autoungrabify(true);
|
||||||
|
|
||||||
|
// Buttons zurückschalten
|
||||||
|
$('#btn-graph-edit-mode').show();
|
||||||
|
$('#btn-graph-save-positions, #btn-graph-cancel-edit').hide();
|
||||||
|
|
||||||
|
// Visuelles Feedback entfernen
|
||||||
|
$('#' + this.containerId).removeClass('graph-edit-mode');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bearbeitungsmodus abbrechen - Positionen zurücksetzen
|
||||||
|
*/
|
||||||
|
cancelEditMode: function() {
|
||||||
|
if (!this.cy) return;
|
||||||
|
|
||||||
|
// Positionen zurücksetzen
|
||||||
|
var self = this;
|
||||||
|
this.cy.nodes().forEach(function(node) {
|
||||||
|
var saved = self._savedPositions[node.data('id')];
|
||||||
|
if (saved) {
|
||||||
|
node.position({ x: saved.x, y: saved.y });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this._dirtyNodes = {};
|
||||||
|
this.exitEditMode();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -755,7 +1011,7 @@
|
||||||
var f = data.fields[i];
|
var f = data.fields[i];
|
||||||
if (!f.value || f.value === '') continue;
|
if (!f.value || f.value === '') continue;
|
||||||
var val = f.value;
|
var val = f.value;
|
||||||
if (val === '1') val = '\u2713';
|
if (f.type === 'checkbox' && (val === '1' || val === 'true')) val = '\u2713';
|
||||||
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.display === 'badge') {
|
||||||
|
|
@ -839,6 +1095,10 @@
|
||||||
KundenKarte.Graph.ajaxUrl = container.getAttribute('data-ajax-url') || '';
|
KundenKarte.Graph.ajaxUrl = container.getAttribute('data-ajax-url') || '';
|
||||||
KundenKarte.Graph.saveUrl = container.getAttribute('data-save-url') || '';
|
KundenKarte.Graph.saveUrl = container.getAttribute('data-save-url') || '';
|
||||||
KundenKarte.Graph.moduleUrl = container.getAttribute('data-module-url') || '';
|
KundenKarte.Graph.moduleUrl = container.getAttribute('data-module-url') || '';
|
||||||
|
KundenKarte.Graph.pageUrl = container.getAttribute('data-page-url') || '';
|
||||||
|
KundenKarte.Graph.systemId = parseInt(container.getAttribute('data-systemid')) || 0;
|
||||||
|
KundenKarte.Graph.canEdit = container.getAttribute('data-can-edit') === '1';
|
||||||
|
KundenKarte.Graph.canDelete = container.getAttribute('data-can-delete') === '1';
|
||||||
KundenKarte.Graph.init({
|
KundenKarte.Graph.init({
|
||||||
socId: parseInt(container.getAttribute('data-socid')) || 0,
|
socId: parseInt(container.getAttribute('data-socid')) || 0,
|
||||||
contactId: parseInt(container.getAttribute('data-contactid')) || 0,
|
contactId: parseInt(container.getAttribute('data-contactid')) || 0,
|
||||||
|
|
|
||||||
|
|
@ -539,3 +539,9 @@ NoConnections = Keine Verbindungen vorhanden
|
||||||
ConnectionCreated = Verbindung erstellt
|
ConnectionCreated = Verbindung erstellt
|
||||||
ConnectionUpdated = Verbindung aktualisiert
|
ConnectionUpdated = Verbindung aktualisiert
|
||||||
ConnectionDeleted = Verbindung geloescht
|
ConnectionDeleted = Verbindung geloescht
|
||||||
|
|
||||||
|
# Graph-Ansicht
|
||||||
|
TreeView = Baumansicht
|
||||||
|
GraphView = Graph-Ansicht
|
||||||
|
GraphLoading = Graph wird geladen...
|
||||||
|
SearchPlaceholder = Suchen...
|
||||||
|
|
|
||||||
|
|
@ -287,3 +287,9 @@ BuildingLevelWing = Wing
|
||||||
BuildingLevelCorridor = Corridor / Hallway
|
BuildingLevelCorridor = Corridor / Hallway
|
||||||
BuildingLevelRoom = Room
|
BuildingLevelRoom = Room
|
||||||
BuildingLevelArea = Area / Zone
|
BuildingLevelArea = Area / Zone
|
||||||
|
|
||||||
|
# Graph View
|
||||||
|
TreeView = Tree View
|
||||||
|
GraphView = Graph View
|
||||||
|
GraphLoading = Loading graph...
|
||||||
|
SearchPlaceholder = Search...
|
||||||
|
|
|
||||||
|
|
@ -366,8 +366,12 @@ if ($action == 'togglepin' && $permissiontoadd) {
|
||||||
|
|
||||||
$title = $langs->trans('TechnicalInstallations').' - '.$object->name;
|
$title = $langs->trans('TechnicalInstallations').' - '.$object->name;
|
||||||
|
|
||||||
// Ansichtsmodus (Admin-Setting)
|
// Ansichtsmodus: URL-Parameter hat Vorrang, sonst Admin-Setting
|
||||||
$viewMode = getDolGlobalString('KUNDENKARTE_DEFAULT_VIEW', 'tree');
|
$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());
|
||||||
|
|
@ -459,36 +463,27 @@ if ($permissiontoadd) {
|
||||||
// Get systems not yet enabled for this customer
|
// Get systems not yet enabled for this customer
|
||||||
$availableSystems = array_diff_key($allSystems, $customerSystems);
|
$availableSystems = array_diff_key($allSystems, $customerSystems);
|
||||||
if (!empty($availableSystems)) {
|
if (!empty($availableSystems)) {
|
||||||
print '<button type="button" class="button small" style="margin-left:auto;" onclick="document.getElementById(\'add-system-form\').style.display=\'block\';">';
|
print '<button type="button" class="button small kundenkarte-add-system-btn" onclick="document.getElementById(\'add-system-form\').style.display=\'block\';">';
|
||||||
print '<i class="fa fa-plus"></i> '.$langs->trans('AddSystem');
|
print '<i class="fa fa-plus"></i> '.$langs->trans('AddSystem');
|
||||||
print '</button>';
|
print '</button>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
print '</div>';
|
print '</div>';
|
||||||
|
|
||||||
// Expand/Collapse buttons (only in tree view, not in create/edit/view/copy)
|
// Steuerungs-Buttons (nur wenn kein Formular aktiv)
|
||||||
$isTreeView = !in_array($action, array('create', 'edit', 'view', 'copy'));
|
$isTreeView = !in_array($action, array('create', 'edit', 'view', 'copy'));
|
||||||
if ($isTreeView) {
|
if ($isTreeView) {
|
||||||
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='.$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>';
|
||||||
|
|
@ -510,6 +505,35 @@ 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') {
|
||||||
|
print '<div class="kundenkarte-graph-toolbar">';
|
||||||
|
// Zeile 1: Ansicht-Wechsel + Aktionen
|
||||||
|
print '<div class="kundenkarte-graph-toolbar-row">';
|
||||||
|
print '<a class="button small" href="'.$toggleUrl.'" title="'.$toggleLabel.'"><i class="fa '.$toggleIcon.'"></i> '.$toggleLabel.'</a>';
|
||||||
|
if ($user->hasRight('kundenkarte', 'write')) {
|
||||||
|
print '<a class="button small" href="'.$_SERVER['PHP_SELF'].'?id='.$id.'&action=create&system='.$systemId.'"><i class="fa fa-plus"></i> '.$langs->trans('AddElement').'</a>';
|
||||||
|
print '<a class="button small" href="'.dol_buildpath('/kundenkarte/anlage_connection.php', 1).'?socid='.$id.'&system_id='.$systemId.'&action=create"><i class="fa fa-plug"></i> '.$langs->trans('AddConnection').'</a>';
|
||||||
|
}
|
||||||
|
print '</div>';
|
||||||
|
// Zeile 2: Graph-Steuerung (Anordnen rechts)
|
||||||
|
print '<div class="kundenkarte-graph-toolbar-row">';
|
||||||
|
print '<button type="button" class="button small" id="btn-graph-reset-layout" title="Layout zurücksetzen"><i class="fa fa-refresh"></i> Layout</button>';
|
||||||
|
print '<button type="button" class="button small" id="btn-graph-wheel-zoom" title="Mausrad-Zoom"><i class="fa fa-arrows"></i> Scroll</button>';
|
||||||
|
print '<button type="button" class="button small" id="btn-graph-zoom-in" title="Vergrößern"><i class="fa fa-search-plus"></i></button>';
|
||||||
|
print '<button type="button" class="button small" id="btn-graph-zoom-out" title="Verkleinern"><i class="fa fa-search-minus"></i></button>';
|
||||||
|
print '<button type="button" class="button small" id="btn-graph-fit" title="Einpassen"><i class="fa fa-crosshairs"></i> Fit</button>';
|
||||||
|
print '<button type="button" class="button small" id="btn-graph-export-png" title="PNG exportieren"><i class="fa fa-download"></i> PNG</button>';
|
||||||
|
if ($permissiontoadd) {
|
||||||
|
print '<span class="kundenkarte-graph-toolbar-spacer"></span>';
|
||||||
|
print '<button type="button" class="button small" id="btn-graph-edit-mode" title="Elemente anordnen"><i class="fa fa-hand-paper-o"></i> Anordnen</button>';
|
||||||
|
print '<button type="button" class="button small btn-graph-save" id="btn-graph-save-positions" title="Positionen speichern" style="display:none;"><i class="fa fa-check"></i> Speichern</button>';
|
||||||
|
print '<button type="button" class="button small btn-graph-cancel" id="btn-graph-cancel-edit" title="Abbrechen" style="display:none;"><i class="fa fa-times"></i> Abbrechen</button>';
|
||||||
|
}
|
||||||
|
print '</div>';
|
||||||
|
print '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
// 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;">';
|
||||||
|
|
@ -1043,15 +1067,35 @@ if (empty($customerSystems)) {
|
||||||
$graphModuleUrl = dol_buildpath('/kundenkarte', 1);
|
$graphModuleUrl = dol_buildpath('/kundenkarte', 1);
|
||||||
|
|
||||||
print '<div class="kundenkarte-graph-wrapper">';
|
print '<div class="kundenkarte-graph-wrapper">';
|
||||||
|
print '<div class="kundenkarte-graph-search-floating">';
|
||||||
|
print '<input type="text" id="kundenkarte-graph-search" placeholder="'.$langs->trans('SearchPlaceholder').'" autocomplete="off">';
|
||||||
|
print '</div>';
|
||||||
print '<div id="kundenkarte-graph-container"';
|
print '<div id="kundenkarte-graph-container"';
|
||||||
print ' data-ajax-url="'.dol_escape_htmltag($graphAjaxUrl).'"';
|
print ' data-ajax-url="'.dol_escape_htmltag($graphAjaxUrl).'"';
|
||||||
print ' data-save-url="'.dol_escape_htmltag($graphSaveUrl).'"';
|
print ' data-save-url="'.dol_escape_htmltag($graphSaveUrl).'"';
|
||||||
print ' data-module-url="'.dol_escape_htmltag($graphModuleUrl).'"';
|
print ' data-module-url="'.dol_escape_htmltag($graphModuleUrl).'"';
|
||||||
print ' data-socid="'.$id.'"';
|
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 '>';
|
||||||
print '<div class="kundenkarte-graph-loading"><i class="fa fa-spinner fa-spin"></i> '.$langs->trans('GraphLoading').'</div>';
|
print '<div class="kundenkarte-graph-loading"><i class="fa fa-spinner fa-spin"></i> '.$langs->trans('GraphLoading').'</div>';
|
||||||
print '</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)
|
// Legende - wird dynamisch vom JS befüllt (Kabeltypen mit Farben)
|
||||||
print '<div id="kundenkarte-graph-legend" class="kundenkarte-graph-legend"></div>';
|
print '<div id="kundenkarte-graph-legend" class="kundenkarte-graph-legend"></div>';
|
||||||
print '</div>';
|
print '</div>';
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue