feat(schematic): Wire-Dragging, Farbpropagierung, Busbar-Typen
- Wire-Segment-Dragging: Shift+Klick/Mittlere Maustaste zum Verschieben - Horizontale Segmente nur vertikal, vertikale nur horizontal - Grid-Snapping, Live-Vorschau, Start/End-Segmente fixiert - PWA: Automatische Farbpropagierung bei Einspeisung (L1/L2/L3/N/PE) - N-Phase als Input hinzugefuegt - propagateInputColor() aktualisiert Abgaenge - Busbar-Typen aus Datenbank statt hardcodiert - ChangeLog aktualisiert Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
95e1860940
commit
848232c5a6
4 changed files with 410 additions and 4 deletions
24
ChangeLog.md
24
ChangeLog.md
|
|
@ -14,6 +14,30 @@
|
||||||
- Leitungen "verschwinden" hinter Bloecken und kommen auf der anderen Seite wieder raus
|
- Leitungen "verschwinden" hinter Bloecken und kommen auf der anderen Seite wieder raus
|
||||||
- Professionelleres Erscheinungsbild wie in echten Schaltplan-Editoren
|
- Professionelleres Erscheinungsbild wie in echten Schaltplan-Editoren
|
||||||
|
|
||||||
|
- **Wire-Segment-Dragging**: Leitungen koennen verschoben werden ohne Verbindungen zu verlieren
|
||||||
|
- Shift+Klick oder Mittlere Maustaste auf Leitungssegment zum Ziehen
|
||||||
|
- Horizontale Segmente nur vertikal verschiebbar, vertikale nur horizontal
|
||||||
|
- Start- und End-Segmente (an Terminals) bleiben fix
|
||||||
|
- Automatisches Grid-Snapping (25px)
|
||||||
|
- Live-Vorschau waehrend dem Ziehen
|
||||||
|
- Neue Funktionen: `parsePathToPoints()`, `pointsToPath()`, `findClickedSegment()`
|
||||||
|
- `startWireDrag()`, `handleWireDragMove()`, `finishWireDrag()`, `cancelWireDrag()`
|
||||||
|
|
||||||
|
- **Busbar-Typen aus Datenbank**: Phasenschienen-Typen dynamisch aus DB laden
|
||||||
|
- Edit-Dialog nutzt jetzt `busbarTypes` Array statt hardcodierter Optionen
|
||||||
|
- `fk_busbar_type` wird beim Update korrekt gespeichert
|
||||||
|
- Admin-Seite fuer Busbar-Typen mit phases_config JSON-Feld
|
||||||
|
|
||||||
|
- **PWA: Farbpropagierung bei Einspeisung**: Automatische Farbuebernahme
|
||||||
|
- Bei Auswahl einer Input-Phase (L1/L2/L3/N/PE) wird Farbe automatisch gesetzt
|
||||||
|
- Funktion `propagateInputColor()` aktualisiert Farben auf Abgaengen
|
||||||
|
- Phase-Matching: L1 matched L1, LN, L1N; N matched N; etc.
|
||||||
|
- Funktioniert online und offline (mit Queue)
|
||||||
|
|
||||||
|
- **PWA: N-Phase als Einspeisung**: Neutralleiter jetzt als Input-Phase waehlbar
|
||||||
|
- INPUT_PHASES erweitert um 'N': ['L1', 'L2', 'L3', 'N', 'PE']
|
||||||
|
- 3P und 3P+N entfernt (nur Einzel-Phasen)
|
||||||
|
|
||||||
### Verbesserungen
|
### Verbesserungen
|
||||||
|
|
||||||
- **Zeichenmodus-Verhalten**: Konsistentes Verhalten im manuellen Zeichenmodus
|
- **Zeichenmodus-Verhalten**: Konsistentes Verhalten im manuellen Zeichenmodus
|
||||||
|
|
|
||||||
|
|
@ -5222,6 +5222,14 @@
|
||||||
wireExtendExistingPoints: [], // Existing path points of connection being extended
|
wireExtendExistingPoints: [], // Existing path points of connection being extended
|
||||||
WIRE_GRID_SIZE: 25, // Snap grid size in pixels (larger = fewer points)
|
WIRE_GRID_SIZE: 25, // Snap grid size in pixels (larger = fewer points)
|
||||||
|
|
||||||
|
// Wire segment dragging
|
||||||
|
wireDragMode: false, // Currently dragging a wire segment
|
||||||
|
wireDragConnId: null, // Connection ID being dragged
|
||||||
|
wireDragSegmentIndex: null, // Index of segment being dragged (0-based)
|
||||||
|
wireDragIsHorizontal: null, // true = horizontal segment (drag Y), false = vertical (drag X)
|
||||||
|
wireDragStartPos: null, // Starting mouse position
|
||||||
|
wireDragOriginalPoints: [], // Original path points before drag
|
||||||
|
|
||||||
// Display settings (persisted in localStorage)
|
// Display settings (persisted in localStorage)
|
||||||
displaySettings: {
|
displaySettings: {
|
||||||
phaseColors: true, // Color wires by phase (L1=brown, L2=black, L3=gray, N=blue, PE=green)
|
phaseColors: true, // Color wires by phase (L1=brown, L2=black, L3=gray, N=blue, PE=green)
|
||||||
|
|
@ -8031,6 +8039,44 @@
|
||||||
this.addEventListener('mouseleave', function() {
|
this.addEventListener('mouseleave', function() {
|
||||||
$visiblePath.attr('stroke-width', wireWidth);
|
$visiblePath.attr('stroke-width', wireWidth);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Wire segment dragging - mousedown to start drag
|
||||||
|
this.addEventListener('mousedown', function(e) {
|
||||||
|
// Only with middle mouse button or Shift+left click
|
||||||
|
if (e.button === 1 || (e.button === 0 && e.shiftKey)) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
// Don't start drag if in wire draw mode
|
||||||
|
if (self.wireDrawMode) return;
|
||||||
|
|
||||||
|
var $svg = $(e.target).closest('svg');
|
||||||
|
var svgRect = $svg[0].getBoundingClientRect();
|
||||||
|
var clickX = e.clientX - svgRect.left;
|
||||||
|
var clickY = e.clientY - svgRect.top;
|
||||||
|
|
||||||
|
if (self.startWireDrag(connId, clickX, clickY)) {
|
||||||
|
// Successfully started drag - bind move/up handlers
|
||||||
|
var moveHandler = function(moveE) {
|
||||||
|
var mx = moveE.clientX - svgRect.left;
|
||||||
|
var my = moveE.clientY - svgRect.top;
|
||||||
|
self.handleWireDragMove(mx, my);
|
||||||
|
};
|
||||||
|
|
||||||
|
var upHandler = function(upE) {
|
||||||
|
var ux = upE.clientX - svgRect.left;
|
||||||
|
var uy = upE.clientY - svgRect.top;
|
||||||
|
self.finishWireDrag(ux, uy);
|
||||||
|
document.removeEventListener('mousemove', moveHandler);
|
||||||
|
document.removeEventListener('mouseup', upHandler);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', moveHandler);
|
||||||
|
document.addEventListener('mouseup', upHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.style.cursor = 'pointer';
|
this.style.cursor = 'pointer';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -8193,6 +8239,37 @@
|
||||||
this.addEventListener('mouseleave', function() {
|
this.addEventListener('mouseleave', function() {
|
||||||
$visiblePath.attr('stroke-width', wireWidth);
|
$visiblePath.attr('stroke-width', wireWidth);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Wire segment dragging
|
||||||
|
this.addEventListener('mousedown', function(e) {
|
||||||
|
if (e.button === 1 || (e.button === 0 && e.shiftKey)) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
if (self.wireDrawMode) return;
|
||||||
|
|
||||||
|
var $svg = $(e.target).closest('svg');
|
||||||
|
var svgRect = $svg[0].getBoundingClientRect();
|
||||||
|
var clickX = e.clientX - svgRect.left;
|
||||||
|
var clickY = e.clientY - svgRect.top;
|
||||||
|
|
||||||
|
if (self.startWireDrag(connId, clickX, clickY)) {
|
||||||
|
var moveHandler = function(moveE) {
|
||||||
|
var mx = moveE.clientX - svgRect.left;
|
||||||
|
var my = moveE.clientY - svgRect.top;
|
||||||
|
self.handleWireDragMove(mx, my);
|
||||||
|
};
|
||||||
|
var upHandler = function(upE) {
|
||||||
|
var ux = upE.clientX - svgRect.left;
|
||||||
|
var uy = upE.clientY - svgRect.top;
|
||||||
|
self.finishWireDrag(ux, uy);
|
||||||
|
document.removeEventListener('mousemove', moveHandler);
|
||||||
|
document.removeEventListener('mouseup', upHandler);
|
||||||
|
};
|
||||||
|
document.addEventListener('mousemove', moveHandler);
|
||||||
|
document.addEventListener('mouseup', upHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
this.style.cursor = 'pointer';
|
this.style.cursor = 'pointer';
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
@ -10802,6 +10879,226 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// Wire Segment Dragging
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse SVG path data to array of points
|
||||||
|
*/
|
||||||
|
parsePathToPoints: function(pathData) {
|
||||||
|
if (!pathData) return [];
|
||||||
|
var points = [];
|
||||||
|
var regex = /([ML])\s*([\d.]+)\s+([\d.]+)/g;
|
||||||
|
var match;
|
||||||
|
while ((match = regex.exec(pathData)) !== null) {
|
||||||
|
points.push({
|
||||||
|
x: parseFloat(match[2]),
|
||||||
|
y: parseFloat(match[3])
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return points;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert points array back to SVG path string
|
||||||
|
*/
|
||||||
|
pointsToPath: function(points) {
|
||||||
|
if (!points || points.length === 0) return '';
|
||||||
|
var path = '';
|
||||||
|
for (var i = 0; i < points.length; i++) {
|
||||||
|
path += (i === 0 ? 'M ' : 'L ') + Math.round(points[i].x) + ' ' + Math.round(points[i].y) + ' ';
|
||||||
|
}
|
||||||
|
return path.trim();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find which segment of a path was clicked
|
||||||
|
* Returns: { index: segmentIndex, isHorizontal: bool } or null
|
||||||
|
*/
|
||||||
|
findClickedSegment: function(points, clickX, clickY, threshold) {
|
||||||
|
threshold = threshold || 15;
|
||||||
|
|
||||||
|
for (var i = 0; i < points.length - 1; i++) {
|
||||||
|
var p1 = points[i];
|
||||||
|
var p2 = points[i + 1];
|
||||||
|
|
||||||
|
// Check if click is near this segment
|
||||||
|
var isHorizontal = Math.abs(p1.y - p2.y) < 2; // Horizontal if Y values are same
|
||||||
|
var isVertical = Math.abs(p1.x - p2.x) < 2; // Vertical if X values are same
|
||||||
|
|
||||||
|
if (isHorizontal) {
|
||||||
|
// Horizontal segment - check if click is within X range and near Y
|
||||||
|
var minX = Math.min(p1.x, p2.x);
|
||||||
|
var maxX = Math.max(p1.x, p2.x);
|
||||||
|
if (clickX >= minX - threshold && clickX <= maxX + threshold &&
|
||||||
|
Math.abs(clickY - p1.y) <= threshold) {
|
||||||
|
return { index: i, isHorizontal: true };
|
||||||
|
}
|
||||||
|
} else if (isVertical) {
|
||||||
|
// Vertical segment - check if click is within Y range and near X
|
||||||
|
var minY = Math.min(p1.y, p2.y);
|
||||||
|
var maxY = Math.max(p1.y, p2.y);
|
||||||
|
if (clickY >= minY - threshold && clickY <= maxY + threshold &&
|
||||||
|
Math.abs(clickX - p1.x) <= threshold) {
|
||||||
|
return { index: i, isHorizontal: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start dragging a wire segment
|
||||||
|
*/
|
||||||
|
startWireDrag: function(connId, clickX, clickY) {
|
||||||
|
var conn = this.connections.find(function(c) { return c.id == connId; });
|
||||||
|
if (!conn || !conn.path_data) return false;
|
||||||
|
|
||||||
|
var points = this.parsePathToPoints(conn.path_data);
|
||||||
|
if (points.length < 3) return false; // Need at least 3 points to have a draggable segment
|
||||||
|
|
||||||
|
var segment = this.findClickedSegment(points, clickX, clickY);
|
||||||
|
if (!segment) return false;
|
||||||
|
|
||||||
|
// Don't allow dragging the first or last segment (connected to terminals)
|
||||||
|
if (segment.index === 0 || segment.index === points.length - 2) {
|
||||||
|
this.showMessage('Start- und End-Segmente können nicht verschoben werden', 'warning');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.wireDragMode = true;
|
||||||
|
this.wireDragConnId = connId;
|
||||||
|
this.wireDragSegmentIndex = segment.index;
|
||||||
|
this.wireDragIsHorizontal = segment.isHorizontal;
|
||||||
|
this.wireDragStartPos = { x: clickX, y: clickY };
|
||||||
|
this.wireDragOriginalPoints = JSON.parse(JSON.stringify(points));
|
||||||
|
|
||||||
|
// Visual feedback
|
||||||
|
$(this.svgElement).css('cursor', segment.isHorizontal ? 'ns-resize' : 'ew-resize');
|
||||||
|
this.log('Wire drag started - segment:', segment.index, 'horizontal:', segment.isHorizontal);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle mouse move during wire drag
|
||||||
|
*/
|
||||||
|
handleWireDragMove: function(mouseX, mouseY) {
|
||||||
|
if (!this.wireDragMode) return;
|
||||||
|
|
||||||
|
var points = JSON.parse(JSON.stringify(this.wireDragOriginalPoints));
|
||||||
|
var segIdx = this.wireDragSegmentIndex;
|
||||||
|
|
||||||
|
if (this.wireDragIsHorizontal) {
|
||||||
|
// Horizontal segment - move Y (vertical drag)
|
||||||
|
var deltaY = mouseY - this.wireDragStartPos.y;
|
||||||
|
var newY = points[segIdx].y + deltaY;
|
||||||
|
|
||||||
|
// Snap to grid
|
||||||
|
newY = Math.round(newY / this.WIRE_GRID_SIZE) * this.WIRE_GRID_SIZE;
|
||||||
|
|
||||||
|
// Update both points of this segment
|
||||||
|
points[segIdx].y = newY;
|
||||||
|
points[segIdx + 1].y = newY;
|
||||||
|
} else {
|
||||||
|
// Vertical segment - move X (horizontal drag)
|
||||||
|
var deltaX = mouseX - this.wireDragStartPos.x;
|
||||||
|
var newX = points[segIdx].x + deltaX;
|
||||||
|
|
||||||
|
// Snap to grid
|
||||||
|
newX = Math.round(newX / this.WIRE_GRID_SIZE) * this.WIRE_GRID_SIZE;
|
||||||
|
|
||||||
|
// Update both points of this segment
|
||||||
|
points[segIdx].x = newX;
|
||||||
|
points[segIdx + 1].x = newX;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the visual path in SVG
|
||||||
|
var newPath = this.pointsToPath(points);
|
||||||
|
var $conn = $(this.svgElement).find('.schematic-connection[data-connection-id="' + this.wireDragConnId + '"]');
|
||||||
|
var $hitarea = $(this.svgElement).find('.schematic-connection-hitarea[data-connection-id="' + this.wireDragConnId + '"]');
|
||||||
|
var $shadow = $conn.closest('.schematic-connection-group').find('.schematic-connection-shadow');
|
||||||
|
|
||||||
|
$conn.attr('d', newPath);
|
||||||
|
$hitarea.attr('d', newPath);
|
||||||
|
$shadow.attr('d', newPath);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finish wire drag and save
|
||||||
|
*/
|
||||||
|
finishWireDrag: function(mouseX, mouseY) {
|
||||||
|
if (!this.wireDragMode) return;
|
||||||
|
|
||||||
|
var points = JSON.parse(JSON.stringify(this.wireDragOriginalPoints));
|
||||||
|
var segIdx = this.wireDragSegmentIndex;
|
||||||
|
|
||||||
|
if (this.wireDragIsHorizontal) {
|
||||||
|
var deltaY = mouseY - this.wireDragStartPos.y;
|
||||||
|
var newY = points[segIdx].y + deltaY;
|
||||||
|
newY = Math.round(newY / this.WIRE_GRID_SIZE) * this.WIRE_GRID_SIZE;
|
||||||
|
points[segIdx].y = newY;
|
||||||
|
points[segIdx + 1].y = newY;
|
||||||
|
} else {
|
||||||
|
var deltaX = mouseX - this.wireDragStartPos.x;
|
||||||
|
var newX = points[segIdx].x + deltaX;
|
||||||
|
newX = Math.round(newX / this.WIRE_GRID_SIZE) * this.WIRE_GRID_SIZE;
|
||||||
|
points[segIdx].x = newX;
|
||||||
|
points[segIdx + 1].x = newX;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up collinear points
|
||||||
|
points = this.cleanupPathPoints(points);
|
||||||
|
|
||||||
|
var newPath = this.pointsToPath(points);
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
// Save to server
|
||||||
|
$.ajax({
|
||||||
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment_connection.php',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
action: 'update',
|
||||||
|
connection_id: this.wireDragConnId,
|
||||||
|
path_data: newPath,
|
||||||
|
token: $('input[name="token"]').val()
|
||||||
|
},
|
||||||
|
success: function(response) {
|
||||||
|
if (response.success) {
|
||||||
|
// Update local data
|
||||||
|
var conn = self.connections.find(function(c) { return c.id == self.wireDragConnId; });
|
||||||
|
if (conn) {
|
||||||
|
conn.path_data = newPath;
|
||||||
|
}
|
||||||
|
self.showMessage('Leitung verschoben', 'success');
|
||||||
|
} else {
|
||||||
|
self.showMessage('Fehler: ' + (response.error || 'Unbekannt'), 'error');
|
||||||
|
self.renderConnections(); // Revert visual
|
||||||
|
}
|
||||||
|
self.cancelWireDrag();
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
self.showMessage('Netzwerkfehler', 'error');
|
||||||
|
self.renderConnections(); // Revert visual
|
||||||
|
self.cancelWireDrag();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel wire drag
|
||||||
|
*/
|
||||||
|
cancelWireDrag: function() {
|
||||||
|
this.wireDragMode = false;
|
||||||
|
this.wireDragConnId = null;
|
||||||
|
this.wireDragSegmentIndex = null;
|
||||||
|
this.wireDragIsHorizontal = null;
|
||||||
|
this.wireDragStartPos = null;
|
||||||
|
this.wireDragOriginalPoints = [];
|
||||||
|
$(this.svgElement).css('cursor', '');
|
||||||
|
},
|
||||||
|
|
||||||
// Update an existing connection with extended path
|
// Update an existing connection with extended path
|
||||||
updateExtendedConnection: function(connId, newTargetEqId, newTargetTermId, newPathData) {
|
updateExtendedConnection: function(connId, newTargetEqId, newTargetTermId, newPathData) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
|
||||||
89
js/pwa.js
89
js/pwa.js
|
|
@ -226,6 +226,17 @@
|
||||||
// Medium-Type Change -> Spezifikationen laden
|
// Medium-Type Change -> Spezifikationen laden
|
||||||
$('#conn-medium-type').on('change', handleMediumTypeChange);
|
$('#conn-medium-type').on('change', handleMediumTypeChange);
|
||||||
|
|
||||||
|
// Connection-Type Change -> Auto-Farbe bei Input-Phasen
|
||||||
|
$('#conn-type').on('change', function() {
|
||||||
|
if (App.connectionDirection === 'input') {
|
||||||
|
const phase = $(this).val();
|
||||||
|
if (phase) {
|
||||||
|
const color = getPhaseColor(phase);
|
||||||
|
$('#conn-color').val(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Bestätigungsdialog
|
// Bestätigungsdialog
|
||||||
$('#btn-confirm-ok').on('click', function() {
|
$('#btn-confirm-ok').on('click', function() {
|
||||||
closeModal('confirm');
|
closeModal('confirm');
|
||||||
|
|
@ -2790,7 +2801,13 @@
|
||||||
const response = await apiCall('ajax/pwa_api.php', data);
|
const response = await apiCall('ajax/pwa_api.php', data);
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
const list = App.connectionDirection === 'input' ? App.inputs : App.outputs;
|
const list = App.connectionDirection === 'input' ? App.inputs : App.outputs;
|
||||||
updateLocal(list.find(c => c.id == App.editConnectionId));
|
const conn = list.find(c => c.id == App.editConnectionId);
|
||||||
|
updateLocal(conn);
|
||||||
|
// Farbpropagierung bei Input
|
||||||
|
if (App.connectionDirection === 'input' && connectionType) {
|
||||||
|
const eqId = conn ? conn.fk_target : null;
|
||||||
|
await propagateInputColor(eqId, connectionType, color);
|
||||||
|
}
|
||||||
renderEditor();
|
renderEditor();
|
||||||
showToast('Verbindung aktualisiert', 'success');
|
showToast('Verbindung aktualisiert', 'success');
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -2803,7 +2820,13 @@
|
||||||
} else {
|
} else {
|
||||||
queueOfflineAction(data);
|
queueOfflineAction(data);
|
||||||
const list = App.connectionDirection === 'input' ? App.inputs : App.outputs;
|
const list = App.connectionDirection === 'input' ? App.inputs : App.outputs;
|
||||||
updateLocal(list.find(c => c.id == App.editConnectionId));
|
const conn = list.find(c => c.id == App.editConnectionId);
|
||||||
|
updateLocal(conn);
|
||||||
|
// Farbpropagierung bei Input (offline)
|
||||||
|
if (App.connectionDirection === 'input' && connectionType) {
|
||||||
|
const eqId = conn ? conn.fk_target : null;
|
||||||
|
propagateInputColor(eqId, connectionType, color);
|
||||||
|
}
|
||||||
renderEditor();
|
renderEditor();
|
||||||
showToast('Wird synchronisiert...', 'warning');
|
showToast('Wird synchronisiert...', 'warning');
|
||||||
}
|
}
|
||||||
|
|
@ -2846,6 +2869,10 @@
|
||||||
if (App.connectionDirection === 'input') {
|
if (App.connectionDirection === 'input') {
|
||||||
newConn.fk_target = App.connectionEquipmentId;
|
newConn.fk_target = App.connectionEquipmentId;
|
||||||
App.inputs.push(newConn);
|
App.inputs.push(newConn);
|
||||||
|
// Farbpropagierung bei neuem Input
|
||||||
|
if (connectionType) {
|
||||||
|
await propagateInputColor(App.connectionEquipmentId, connectionType, color);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
newConn.fk_source = App.connectionEquipmentId;
|
newConn.fk_source = App.connectionEquipmentId;
|
||||||
App.outputs.push(newConn);
|
App.outputs.push(newConn);
|
||||||
|
|
@ -2865,6 +2892,10 @@
|
||||||
if (App.connectionDirection === 'input') {
|
if (App.connectionDirection === 'input') {
|
||||||
newConn.fk_target = App.connectionEquipmentId;
|
newConn.fk_target = App.connectionEquipmentId;
|
||||||
App.inputs.push(newConn);
|
App.inputs.push(newConn);
|
||||||
|
// Farbpropagierung bei neuem Input (offline)
|
||||||
|
if (connectionType) {
|
||||||
|
propagateInputColor(App.connectionEquipmentId, connectionType, color);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
newConn.fk_source = App.connectionEquipmentId;
|
newConn.fk_source = App.connectionEquipmentId;
|
||||||
App.outputs.push(newConn);
|
App.outputs.push(newConn);
|
||||||
|
|
@ -2877,6 +2908,60 @@
|
||||||
App.editConnectionId = null;
|
App.editConnectionId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Farbpropagierung: Wenn Input-Phase gesetzt, Farbe auf Outputs übertragen
|
||||||
|
* @param {number} equipmentId - Equipment-ID
|
||||||
|
* @param {string} phase - Phase (L1, L2, L3, N, PE)
|
||||||
|
* @param {string} color - Farbe der Einspeisung
|
||||||
|
*/
|
||||||
|
async function propagateInputColor(equipmentId, phase, color) {
|
||||||
|
if (!equipmentId || !phase || !color) return;
|
||||||
|
|
||||||
|
// Finde alle Outputs dieses Equipment
|
||||||
|
const outputs = App.outputs.filter(o => o.fk_source == equipmentId);
|
||||||
|
if (outputs.length === 0) return;
|
||||||
|
|
||||||
|
// Update Outputs mit passender Phase
|
||||||
|
for (const output of outputs) {
|
||||||
|
// Phase-Matching: L1 -> L1, LN, L1N; L2 -> L2; etc.
|
||||||
|
const outputType = output.connection_type || '';
|
||||||
|
let matches = false;
|
||||||
|
|
||||||
|
if (phase === 'L1' && (outputType.includes('L1') || outputType === 'LN')) matches = true;
|
||||||
|
else if (phase === 'L2' && outputType.includes('L2')) matches = true;
|
||||||
|
else if (phase === 'L3' && outputType.includes('L3')) matches = true;
|
||||||
|
else if (phase === 'N' && outputType.includes('N')) matches = true;
|
||||||
|
else if (phase === 'PE' && outputType.includes('PE')) matches = true;
|
||||||
|
|
||||||
|
if (matches && output.color !== color) {
|
||||||
|
output.color = color;
|
||||||
|
|
||||||
|
// Backend aktualisieren
|
||||||
|
if (App.isOnline) {
|
||||||
|
try {
|
||||||
|
await apiCall('ajax/pwa_api.php', {
|
||||||
|
action: 'update_connection',
|
||||||
|
connection_id: output.id,
|
||||||
|
color: color
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
queueOfflineAction({
|
||||||
|
action: 'update_connection',
|
||||||
|
connection_id: output.id,
|
||||||
|
color: color
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
queueOfflineAction({
|
||||||
|
action: 'update_connection',
|
||||||
|
connection_id: output.id,
|
||||||
|
color: color
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connection löschen (mit Bestätigung)
|
* Connection löschen (mit Bestätigung)
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
4
sw.js
4
sw.js
|
|
@ -3,8 +3,8 @@
|
||||||
* Offline-First für Schaltschrank-Dokumentation
|
* Offline-First für Schaltschrank-Dokumentation
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const CACHE_NAME = 'kundenkarte-pwa-v11.6';
|
const CACHE_NAME = 'kundenkarte-pwa-v11.7';
|
||||||
const OFFLINE_CACHE = 'kundenkarte-offline-v11.6';
|
const OFFLINE_CACHE = 'kundenkarte-offline-v11.7';
|
||||||
|
|
||||||
// Statische Assets die immer gecached werden (ohne Query-String)
|
// Statische Assets die immer gecached werden (ohne Query-String)
|
||||||
const STATIC_ASSETS = [
|
const STATIC_ASSETS = [
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue