From 68f29ab9953cbe68b0b1dfca28c2d3b4b657a20a Mon Sep 17 00:00:00 2001 From: Daniel Spitzer Date: Tue, 2 Dec 2025 12:48:51 +0100 Subject: [PATCH] Poprack Features: * Komplettes kabelmanagement auf Rack He Modul Basis --- Layout/default/Pop/Detail.php | 375 +- Layout/default/Pop/_rack_body.php | 46 +- .../FiberPlanCable/FiberPlanCableModel.php | 72 +- application/FiberPlanFiber/FiberPlanFiber.php | 126 + .../FiberPlanFiberController.php | 186 + .../FiberPlanFiber/FiberPlanFiberModel.php | 314 ++ application/Pop/PopController.php | 1361 +++++- public/assets/css/print.min.css | 1 + public/assets/js/print.min.js | 1 + public/css/pages/Pop/Detail.css | 1049 +++++ public/js/pages/Pop/fiber.js | 4083 +++++++++++++++++ public/js/pages/pop/detail.js | 598 +-- 12 files changed, 7531 insertions(+), 681 deletions(-) create mode 100644 application/FiberPlanFiber/FiberPlanFiber.php create mode 100644 application/FiberPlanFiber/FiberPlanFiberController.php create mode 100644 application/FiberPlanFiber/FiberPlanFiberModel.php create mode 100644 public/assets/css/print.min.css create mode 100644 public/assets/js/print.min.js create mode 100644 public/css/pages/Pop/Detail.css create mode 100644 public/js/pages/Pop/fiber.js diff --git a/Layout/default/Pop/Detail.php b/Layout/default/Pop/Detail.php index b3b12f296..de8e24d67 100644 --- a/Layout/default/Pop/Detail.php +++ b/Layout/default/Pop/Detail.php @@ -1,274 +1,11 @@ - + + +
@@ -692,6 +429,10 @@ if (!empty(trim($pops->vlan_ipv6))) class="rack-name"> - Vorderseite + @@ -779,7 +520,8 @@ if (!empty(trim($pops->vlan_ipv6)))
- +
+ + +
+
+

Rack Detail

+
+ + + + + +
+
+
+
+
`; + + if (branchPath.next_branch) { + html += renderBranchPath(branchPath.next_branch, level + 1); + } + + return html; +} + +function getStateText(state) { + const states = { + 10: 'Geplant', + 20: 'In Bau', + 30: 'Produktiv' + }; + return states[state] || 'Unbekannt'; +} + +function getFiberStatusBadge(fiber) { + if (fiber.home_id) { + return 'Aktiv (Kunde)'; + } else if (fiber.branch_type === 'Abzweigkabel') { + return 'Abzweig'; + } else { + return 'Frei'; + } +} + +function getFiberShortInfo(fiber) { + if (fiber.home_id) { + return fiber.address || fiber.home_id; + } else if (fiber.branch_type === 'Abzweigkabel') { + return fiber.branch_cable_nr || 'Abzweig'; + } + return 'Frei'; +} + +$('#fiberPlanCableModal').on('hidden.bs.modal', function () { + modalHistory = []; +}); + +$(document).on('click', '#modal-edit-cable-btn', function (e) { + e.preventDefault(); + e.stopPropagation(); + + const cableId = $(this).attr('data-cable-id'); + const cableName = $(this).attr('data-cable-name'); + + if (!cableId) { + alert('Fehler: Keine Kabel-ID gefunden!'); + return; + } + + $('#fiberPlanCableModal').modal('hide'); + + currentCableId = cableId; + $('#cable-name').text(cableName || 'Unbekanntes Kabel'); + $('#excelEditorModal').modal('show'); + + loadCableData(cableId); +}); + +$('#fiberPlanCableModal').on('hidden.bs.modal', function () { + modalHistory = []; + $('#modal-edit-cable-btn').hide(); +}); + +function showCableRouteMap(cableId, cableName) { + // ÄNDERUNG: Modal-Body Layout angepasst für Karte (oben) und Schema (unten) + const mapModalHtml = ` + + `; + + $('#cableRouteMapModal').remove(); + + $('body').append(mapModalHtml); + + $('#cableRouteMapModal').modal('show'); + + $.ajax({ + url: linkGetCableDetails, + method: 'GET', + data: { cable_name: cableName }, + dataType: 'json', + success: function (response) { + if (response.status === 'OK' && response.result.cable) { + initializeCableRouteMap(response.result.cable); + // ÄNDERUNG: Schema rendern aufrufen + renderCableRouteSchema(response.result.cable); + } else { + $('#cable-route-map').html(` +
+ + Keine Daten für diese Route verfügbar. +
+ `); + } + }, + error: function () { + $('#cable-route-map').html(` +
+ + Fehler beim Laden der Kartendaten. +
+ `); + } + }); +} + +function initializeCableRouteMap(cable) { + setTimeout(function () { + const mapContainer = document.getElementById('cable-route-map'); + + if (!mapContainer) { + return; + } + + if (!cable.cable_route_full || cable.cable_route_full.length === 0) { + $('#cable-route-map').html(` +
+ + Keine GPS-Koordinaten für diese Kabelroute verfügbar. +
+ `); + return; + } + + const validStations = cable.cable_route_full.filter(station => + station.gps_lat && station.gps_long + ); + + if (validStations.length === 0) { + $('#cable-route-map').html(` +
+ + Keine gültigen GPS-Koordinaten gefunden. +
+ `); + return; + } + + let centerLat = 0; + let centerLng = 0; + validStations.forEach(station => { + centerLat += parseFloat(station.gps_lat); + centerLng += parseFloat(station.gps_long); + }); + centerLat /= validStations.length; + centerLng /= validStations.length; + + const map = L.map('cable-route-map').setView([centerLat, centerLng], 13); + + L.tileLayer('https://mapsneu.wien.gv.at/basemap/{id}/normal/google3857/{z}/{y}/{x}.{imgtype}', { + maxZoom: 22, + id: "geolandbasemap", + imgtype: "png" + }).addTo(map); + + const fibersByBundle = {}; + cable.fiber_list.forEach(fiber => { + const bundleKey = fiber.bundle_nr || 'ohne_buendel'; + if (!fibersByBundle[bundleKey]) { + fibersByBundle[bundleKey] = { + fibers: [], + bundle_color: fiber.bundle_color, + bundle_color_hex: fiber.bundle_color_hex, + bundle_nr: fiber.bundle_nr + }; + } + fibersByBundle[bundleKey].fibers.push(fiber); + }); + + const markerGroup = L.featureGroup(); + const routePoints = []; + + let detailedCoordinates = null; + + if (cable.coordinates) { + detailedCoordinates = cable.coordinates; + } + + if (detailedCoordinates && typeof detailedCoordinates === 'string') { + try { + detailedCoordinates = JSON.parse(detailedCoordinates); + } catch (e) { + detailedCoordinates = null; + } + } + + validStations.forEach((station, index) => { + const lat = parseFloat(station.gps_lat); + const lng = parseFloat(station.gps_long); + routePoints.push([lat, lng]); + + let iconColor = '#3388ff'; + let iconName = 'circle'; + let iconSize = 'm'; + + if (station.type === 'pop') { + iconColor = '#acf0ab'; + iconName = 'village'; + iconSize = 'l'; + } else if (station.type === 'dispatcher') { + iconColor = '#abbaf0'; + iconName = 'home'; + iconSize = 'm'; + } + + const marker = L.marker([lat, lng], { + icon: L.MakiMarkers.icon({ + icon: iconName, + color: iconColor, + size: iconSize + }) + }).bindPopup(` + ${index + 1}. ${station.name}
+ Typ: ${station.type === 'pop' ? 'POP' : 'Verteiler'}
+ ${lat.toFixed(5)}, ${lng.toFixed(5)} + `); + + markerGroup.addLayer(marker); + }); + + markerGroup.addTo(map); + let routePolyline; + if (detailedCoordinates && Array.isArray(detailedCoordinates) && detailedCoordinates.length > 0) { + const leafletCoords = detailedCoordinates.map(coord => [ + parseFloat(coord.gps_lat), + parseFloat(coord.gps_long) + ]); + + routePolyline = L.polyline(leafletCoords, { + color: '#FF6B6B', + weight: 4, + opacity: 0.7 + }).addTo(map); + } else { + routePolyline = L.polyline(routePoints, { + color: '#FF6B6B', + weight: 4, + opacity: 0.7 + }).addTo(map); + } + + let popupContent = ` +
+
${cable.description}
+
+
+ Fasern: ${cable.fibers} | Durchmesser: ${cable.diameter}mm +
+`; + + Object.keys(fibersByBundle).forEach(bundleKey => { + const bundle = fibersByBundle[bundleKey]; + const bundleColor = bundle.bundle_color_hex || '#cccccc'; + + popupContent += ` +
+
+ + Bündel ${bundle.bundle_nr || '-'} + ${bundle.bundle_color ? `${bundle.bundle_color}` : ''} +
+
+ `; + + bundle.fibers.forEach(fiber => { + const fiberColor = fiber.fiber_color_hex || '#cccccc'; + const status = fiber.home_id ? 'belegt' : (fiber.branch_type ? 'abzweig' : 'frei'); + const statusColor = fiber.home_id ? '#28a745' : (fiber.branch_type ? '#ffc107' : '#6c757d'); + + popupContent += ` +
+ + ${fiber.fiber_nr_cable} + ${fiber.fiber_nr_bundle ? `:${fiber.fiber_nr_bundle}` : ''} + +
+ `; + }); + + popupContent += ` +
+
+ `; + }); + + popupContent += `
`; + + routePolyline.bindPopup(popupContent, { + maxWidth: 400, + className: 'cable-info-popup' + }); + + routePolyline.bindTooltip(`${cable.description} (${cable.fibers} Fasern)`, { + permanent: false, + sticky: true, + direction: 'top' + }); + + if (cable.fiber_list && Array.isArray(cable.fiber_list)) { + cable.fiber_list.forEach(fiber => { + if (fiber.home_id && fiber.address) { + let dropoutStation = null; + + if (fiber.location) { + dropoutStation = validStations.find(station => + station.name === fiber.location + ); + } + + if (!dropoutStation && validStations.length > 0) { + dropoutStation = validStations[validStations.length - 1]; + } + + if (dropoutStation) { + const dropoutLat = parseFloat(dropoutStation.gps_lat); + const dropoutLng = parseFloat(dropoutStation.gps_long); + + const customerCircle = L.circleMarker([dropoutLat, dropoutLng], { + radius: 8, + fillColor: '#f0abab', + fillOpacity: 0.8, + color: '#000000', + weight: 2, + opacity: 1 + }).bindPopup(` +
+
Hausanschluss
+
+ Home-ID: ${fiber.home_id}
+ Adresse: ${fiber.address}
+ ${fiber.name ? `Name: ${fiber.name}
` : ''} +
+ Faser: #${fiber.fiber_nr_cable} + ${fiber.fiber_color ? ` (${fiber.fiber_color})` : ''}
+ ${fiber.bundle_nr ? `Bündel: ${fiber.bundle_nr}` : ''} + ${fiber.bundle_color ? ` (${fiber.bundle_color})` : ''} +
+ `).addTo(map); + + markerGroup.addLayer(customerCircle); + } + } + }); + } + + map.fitBounds(markerGroup.getBounds(), { padding: [50, 50] }); + + const legend = L.control({ position: 'bottomright' }); + legend.onAdd = function (map) { + const div = L.DomUtil.create('div', 'info legend'); + div.style.backgroundColor = 'white'; + div.style.padding = '10px'; + div.style.border = '2px solid #ccc'; + div.style.borderRadius = '5px'; + div.style.fontSize = '12px'; + + let html = 'Legende
'; + html += ' POP
'; + html += ' Verteiler
'; + html += '
'; + html += 'Status:
'; + html += ' Belegt
'; + html += ' Abzweig
'; + html += ' Frei
'; + + div.innerHTML = html; + return div; + }; + legend.addTo(map); + + }, 300); +} +function renderCableRouteSchema(cable) { + const schemaContainer = $('#cable-route-schema'); + + if (!cable || !cable.cable_route_full || cable.cable_route_full.length === 0) { + schemaContainer.html('
Keine Routen-Informationen verfügbar
'); + return; + } + + let html = '
'; + html += '
Schematischer Kabelverlauf
'; + html += '
'; + + cable.cable_route_full.forEach((station, index) => { + let style = { icon: 'fa-sitemap', cssClass: 'station-distributor', color: '#3388ff', typeLabel: 'Verteiler' }; + let badgeClass = 'badge-info'; + + if (station.type === 'pop') { + style = { icon: 'fa-building', cssClass: 'station-pop', color: '#4caf50', typeLabel: 'POP' }; + badgeClass = 'badge-success'; + } else if (station.object_type == 2) { // Schacht + style = { icon: 'fa-archive', cssClass: 'station-manhole', color: '#ffa726', typeLabel: 'Schacht' }; + badgeClass = 'badge-warning'; + } + + const popupContent = ` +
+
${station.name}
+ ${style.typeLabel} +
+
+ Position: ${index + 1}
+ ${station.gps_lat ? parseFloat(station.gps_lat).toFixed(5) + ', ' + parseFloat(station.gps_long).toFixed(5) : 'Kein GPS'} +
+
`; + + html += ` +
+
+ +
+
+ ${station.name} +
+
+ `; + if (index < cable.cable_route_full.length - 1) { + html += ` +
+
+
+ ${cable.description}
+
+
+
+ `; + } + }); + + html += '
'; + schemaContainer.html(html); + + // Popover Logik aktivieren (wiederverwendet) + $('.schema-station-clickable').off('click').on('click', function (e) { + e.stopPropagation(); + const $station = $(this); + const popupContent = $station.attr('data-popup-content').replace(/"/g, '"'); + + $station.popover('dispose'); + $station.popover({ + content: popupContent, + html: true, + placement: 'top', + trigger: 'manual', + container: 'body', + template: ` + + ` + }); + $('.schema-station-clickable').not($station).popover('hide'); + $station.popover('show'); + }); +} +function showFiberRouteMap(fiberId) { + const mapModalHtml = ` + + `; + + $('#fiberRouteMapModal').remove(); + + $('body').append(mapModalHtml); + + $('#fiberRouteMapModal').modal('show'); + + $.ajax({ + url: linkGetFiberPath, + method: 'GET', + data: { fiber_id: fiberId }, + dataType: 'json', + success: function (response) { + if (response.status === 'OK' && response.result.fiber) { + initializeFiberRouteMap(response.result.fiber); + } else { + $('#fiber-route-map').html(` +
+ + Keine GPS-Daten für diese Strecke verfügbar. +
+ `); + } + }, + error: function () { + $('#fiber-route-map').html(` +
+ + Fehler beim Laden der Kartendaten. +
+ `); + } + }); +} + +function findNearestBranchPoint(customerLat, customerLng, allSegments) { + let nearestBranchPoint = null; + let nearestDispatcher = null; + let minBranchDistance = Infinity; + let minDispatcherDistance = Infinity; + allSegments.forEach((segment, segIdx) => { + if (segment.stations && Array.isArray(segment.stations)) { + segment.stations.forEach((station, stationIdx) => { + if (!station.gps_lat || !station.gps_long) return; + const stationLat = parseFloat(station.gps_lat); + const stationLng = parseFloat(station.gps_long); + const distance = Math.sqrt( + Math.pow(customerLat - stationLat, 2) + + Math.pow(customerLng - stationLng, 2) + ); + if (station.object_type == 4 || station.object_type === '4') { + if (distance < minBranchDistance) { + minBranchDistance = distance; + nearestBranchPoint = { + lat: stationLat, + lng: stationLng, + name: station.name || 'Abzweigpunkt', + station: station, + type: 'branch_point' + }; + } + } + else if (station.type === 'dispatcher' && (station.object_type == 1 || station.object_type == 2 || station.object_type === '1' || station.object_type === '2')) { + if (distance < minDispatcherDistance) { + minDispatcherDistance = distance; + nearestDispatcher = { + lat: stationLat, + lng: stationLng, + name: station.name || 'Verteiler', + station: station, + type: 'dispatcher' + }; + } + } + }); + } + }); + if (nearestBranchPoint) { + return nearestBranchPoint; + } + else if (nearestDispatcher) { + return nearestDispatcher; + } + return null; +} + +function getDistanceToPolyline(pointLat, pointLng, polylineCoords) { + let minDistance = Infinity; + for (let i = 0; i < polylineCoords.length - 1; i++) { + const segmentStart = polylineCoords[i]; + const segmentEnd = polylineCoords[i + 1]; + const distance = pointToSegmentDistance( + pointLat, pointLng, + segmentStart[0], segmentStart[1], + segmentEnd[0], segmentEnd[1] + ); + + if (distance < minDistance) { + minDistance = distance; + } + } + + return minDistance; +} + +function pointToSegmentDistance(px, py, x1, y1, x2, y2) { + const dx = x2 - x1; + const dy = y2 - y1; + + if (dx === 0 && dy === 0) { + return Math.sqrt(Math.pow(px - x1, 2) + Math.pow(py - y1, 2)); + } + + const t = Math.max(0, Math.min(1, + ((px - x1) * dx + (py - y1) * dy) / (dx * dx + dy * dy) + )); + + const nearestX = x1 + t * dx; + const nearestY = y1 + t * dy; + + return Math.sqrt(Math.pow(px - nearestX, 2) + Math.pow(py - nearestY, 2)); +} + +function initializeFiberRouteMap(fiber, isLinework = false) { + setTimeout(function () { + const mapContainer = document.getElementById('fiber-route-map'); + const allSegments = []; + const allStations = []; + + if (fiber.cable_info && fiber.cable_info.cable_route_full) { + let mainFiberNr = fiber.fiber_nr_cable; + let mainFiberColor = fiber.fiber_color; + let mainFiberColorHex = fiber.fiber_color_hex; + let mainBundleNr = fiber.bundle_nr; + let mainBundleColor = fiber.bundle_color; + let mainBundleColorHex = fiber.bundle_color_hex; + let branchSource = null; + + if (fiber.branch_path && fiber.branch_path.source_fiber) { + branchSource = fiber.branch_path.source_fiber; + } else if (fiber.cable_info && fiber.cable_info.branch_path && fiber.cable_info.branch_path.source_fiber) { + branchSource = fiber.cable_info.branch_path.source_fiber; + } + if (isLinework && !branchSource && fiber.branch_path && fiber.branch_path.cable && fiber.cable_info.fibers) { + const firstBranchCableName = fiber.branch_path.cable.description || fiber.branch_path.cable.name; + const targetFiberNrInBranch = fiber.fiber_nr_cable; + + if (firstBranchCableName) { + const candidates = fiber.cable_info.fibers.filter(f => + f.branch_cable_nr === firstBranchCableName || + (f.branch_path && f.branch_path.cable && f.branch_path.cable.name === firstBranchCableName) + ); + + let foundSourceFiber = null; + + if (candidates.length > 0) { + foundSourceFiber = candidates.find(f => + f.branch_fiber_nr == targetFiberNrInBranch || + f.branch_fiber_nr_cable == targetFiberNrInBranch + ); + if (!foundSourceFiber) { + // console.log("⚠️ Kein exakter Faser-Match (z.B. auf Branch-Faser " + targetFiberNrInBranch + "), nehme ersten Kandidaten."); + foundSourceFiber = candidates[0]; + } + } + + if (foundSourceFiber) { + const correctFiberNr = foundSourceFiber.fiber_nr_cable || foundSourceFiber.fiber_nr; + + // console.log("🔧 Hauptkabel-Faser gefunden:", correctFiberNr, foundSourceFiber); + + branchSource = { + fiber_nr_cable: correctFiberNr, + fiber_color: foundSourceFiber.color_name || foundSourceFiber.fiber_color || foundSourceFiber.color, + fiber_color_hex: foundSourceFiber.color_hex || foundSourceFiber.fiber_color_hex || foundSourceFiber.color, + bundle_nr: foundSourceFiber.bundle_nr || foundSourceFiber.bundle, + bundle_color: foundSourceFiber.bundle_color || foundSourceFiber.bundle_color_name, + bundle_color_hex: foundSourceFiber.bundle_color_hex + }; + } + } + } + + if (branchSource) { + mainFiberNr = branchSource.fiber_nr_cable; + mainFiberColor = branchSource.fiber_color; + mainFiberColorHex = branchSource.fiber_color_hex; + mainBundleNr = branchSource.bundle_nr; + mainBundleColor = branchSource.bundle_color; + mainBundleColorHex = branchSource.bundle_color_hex; + // console.log("✅ Hauptkabel-Daten erfolgreich korrigiert auf Faser:", mainFiberNr); + } else { + // console.warn("⚠️ Konnte Quell-Faser nicht ermitteln (Fallback auf End-Faser)."); + } + + allSegments.push({ + name: fiber.cable_info.description, + stations: fiber.cable_info.cable_route_full, + color: mainFiberColorHex || '#3388ff', + level: 0, + type: 'main', + fiber_nr: mainFiberNr, + fiber_color: mainFiberColor, + fiber_color_hex: mainFiberColorHex, + bundle_nr: mainBundleNr, + bundle_color: mainBundleColor, + bundle_color_hex: mainBundleColorHex, + location: fiber.location || fiber.cable_info.location, + cable: fiber.cable_info + }); + } + + if (fiber.cable_info && fiber.cable_info.branch_points && Array.isArray(fiber.cable_info.branch_points) && fiber.cable_info.branch_points.length > 0) { + allSegments.push({ + name: 'Abzweigpunkte Netzwerk', + stations: fiber.cable_info.branch_points, + color: '#FF6B6B', + level: 0, + type: 'branch_points', + cable: null + }); + } else { + } + + function collectBranchRoutes(branchPath, level = 1) { + if (!branchPath || branchPath.error) { + return; + } + if (branchPath.cable && branchPath.cable.cable_route_full) { + const targetFiber = branchPath.target_fiber; + const fiberColor = targetFiber?.fiber_color_hex || getBranchColor(level); + + allSegments.push({ + name: branchPath.cable.description, + stations: branchPath.cable.cable_route_full, + color: fiberColor, + level: level, + type: 'branch', + location: branchPath.cable.location, + cable: branchPath.cable, + coordinates: branchPath.cable.coordinates, + cable_info: branchPath.cable.cable_info || branchPath.cable, + fiber_nr: targetFiber?.fiber_nr_cable, + fiber_color: targetFiber?.fiber_color, + fiber_color_hex: targetFiber?.fiber_color_hex, + bundle_nr: targetFiber?.bundle_nr, + bundle_color: targetFiber?.bundle_color, + bundle_color_hex: targetFiber?.bundle_color_hex, + target_fiber: targetFiber + }); + } + + if (branchPath.next_branch) { + collectBranchRoutes(branchPath.next_branch, level + 1); + } + } + + if (fiber.branch_path) { + collectBranchRoutes(fiber.branch_path); + } + if (allSegments.length > 1) { + const mainSegment = allSegments.find(seg => seg.type === 'main'); + const branchSegments = allSegments.filter(seg => seg.type === 'branch'); + + if (mainSegment && branchSegments.length > 0) { + branchSegments.forEach(branchSeg => { + if (fiber.cable_info && fiber.cable_info.fibers) { + const branchFiber = fiber.cable_info.fibers.find(f => + f.branch_type === 'Abzweigkabel' && + f.branch_cable_nr === branchSeg.name + ); + + if (branchFiber && branchFiber.branch_from_location) { + branchSeg.branch_from_location_main = branchFiber.branch_from_location; + if (!mainSegment.branch_dropouts) { + mainSegment.branch_dropouts = []; + } + mainSegment.branch_dropouts.push(branchFiber.branch_from_location); + } else { + } + } + }); + + if (mainSegment.branch_dropouts && mainSegment.branch_dropouts.length > 0) { + } + } + } + if (allSegments.length === 0) { + $('#fiber-route-map').html(` +
+ + Keine GPS-Koordinaten für diese Faser-Strecke verfügbar. +
+ `); + return; + } + + allSegments.forEach(segment => { + segment.stations.forEach(station => { + if (station.gps_lat && station.gps_long) { + allStations.push(station); + } + }); + }); + + if (allStations.length === 0) { + $('#fiber-route-map').html(` +
+ + Keine gültigen GPS-Koordinaten gefunden. +
+ `); + return; + } + + let centerLat = 0; + let centerLng = 0; + allStations.forEach(station => { + centerLat += parseFloat(station.gps_lat); + centerLat += parseFloat(station.gps_lat); + centerLng += parseFloat(station.gps_long); + }); + centerLat /= allStations.length; + centerLng /= allStations.length; + if (window.fiberRouteMapInstance) { + // console.log('🗑️ Entferne alte Map-Instanz'); + window.fiberRouteMapInstance.remove(); + window.fiberRouteMapInstance = null; + } + + const mapContainerold = document.getElementById('fiber-route-map'); + if (mapContainerold) { + mapContainerold.innerHTML = ''; + mapContainerold._leaflet_id = null; + if (mapContainerold._leaflet) { + delete mapContainerold._leaflet; + } + } + + // console.log('🗺️ Erstelle neue Map-Instanz'); + const map = L.map('fiber-route-map').setView([centerLat, centerLng], 13); + window.fiberRouteMapInstance = map; + L.tileLayer('https://mapsneu.wien.gv.at/basemap/{id}/normal/google3857/{z}/{y}/{x}.{imgtype}', { + maxZoom: 22, + id: "geolandbasemap", + imgtype: "png" + }).addTo(map); + + window.allBranchPoints = []; + if (fiber.cable_info && fiber.cable_info.branch_points && Array.isArray(fiber.cable_info.branch_points)) { + fiber.cable_info.branch_points.forEach(bp => { + window.allBranchPoints.push({ + lat: parseFloat(bp.gps_lat), + lng: parseFloat(bp.gps_long), + name: bp.name || 'Abzweigpunkt', + segment: null, + station: bp + }); + }); + // console.log(`${window.allBranchPoints.length} Abzweigpunkte aus Backend geladen`); + } + const allBounds = L.featureGroup(); + function getBranchConnectionsAtStation(stationName, currentSegment, allSegs) { + const connections = []; + if (currentSegment.type === 'main' && currentSegment.branch_dropouts) { + currentSegment.branch_dropouts.forEach(dropoutLocation => { + if (dropoutLocation === stationName) { + allSegs.forEach(seg => { + if (seg.type === 'branch' && seg.branch_from_location_main === stationName) { + connections.push({ + type: 'branch_from_main', + fromCable: currentSegment.name, + fromFiber: currentSegment.fiber_nr, + fromFiberColor: currentSegment.fiber_color, + fromFiberColorHex: currentSegment.color, + fromBundle: currentSegment.bundle_nr, + fromBundleColor: currentSegment.bundle_color, + fromBundleColorHex: currentSegment.bundle_color_hex, + toCable: seg.name, + toFiber: seg.fiber_nr, + toFiberColor: seg.fiber_color, + toFiberColorHex: seg.color, + toBundle: seg.bundle_nr, + toBundleColor: seg.bundle_color, + toBundleColorHex: seg.bundle_color_hex, + level: seg.level + }); + } + }); + } + }); + } + if (currentSegment.type === 'branch' || currentSegment.type === 'main') { + const currentLastStation = currentSegment.stations && currentSegment.stations.length > 0 + ? currentSegment.stations[currentSegment.stations.length - 1] : null; + allSegs.forEach(seg => { + if (seg.type === 'branch' && seg !== currentSegment) { + const firstStation = seg.stations && seg.stations.length > 0 ? seg.stations[0] : null; + if (seg.level > currentSegment.level && firstStation && firstStation.name === stationName) { + const alreadyAdded = connections.some(c => c.toCable === seg.name); + if (!alreadyAdded) { + connections.push({ + type: currentSegment.type === 'main' ? 'branch_from_main' : 'branch_from_branch', + fromCable: currentSegment.name, + fromFiber: currentSegment.fiber_nr, + fromFiberColor: currentSegment.fiber_color, + fromFiberColorHex: currentSegment.color, + fromBundle: currentSegment.bundle_nr, + fromBundleColor: currentSegment.bundle_color, + fromBundleColorHex: currentSegment.bundle_color_hex, + toCable: seg.name, + toFiber: seg.fiber_nr, + toFiberColor: seg.fiber_color, + toFiberColorHex: seg.color, + toBundle: seg.bundle_nr, + toBundleColor: seg.bundle_color, + toBundleColorHex: seg.bundle_color_hex, + level: seg.level + }); + } + } + + if (seg.level > currentSegment.level && + currentLastStation && currentLastStation.name === stationName && + firstStation && firstStation.name === stationName) { + const alreadyAdded = connections.some(c => c.toCable === seg.name); + if (!alreadyAdded) { + connections.push({ + type: 'branch_from_branch', + fromCable: currentSegment.name, + fromFiber: currentSegment.fiber_nr, + fromFiberColor: currentSegment.fiber_color, + fromFiberColorHex: currentSegment.color, + fromBundle: currentSegment.bundle_nr, + fromBundleColor: currentSegment.bundle_color, + fromBundleColorHex: currentSegment.bundle_color_hex, + toCable: seg.name, + toFiber: seg.fiber_nr, + toFiberColor: seg.fiber_color, + toFiberColorHex: seg.color, + toBundle: seg.bundle_nr, + toBundleColor: seg.bundle_color, + toBundleColorHex: seg.bundle_color_hex, + level: seg.level + }); + } + } + } + }); + } + + if (connections.length > 0) { + } + + return connections; + } + const drawnStations = new Map(); + + allSegments.forEach((segment, segmentIndex) => { + if (segment.type === 'branch_points') { + return; + } + const validStations = segment.stations.filter(s => s.gps_lat && s.gps_long); + if (validStations.length === 0) { + return; + } + + const routePoints = []; + let detailedCoordinates = null; + + if (segment.coordinates) { + detailedCoordinates = segment.coordinates; + } else if (segment.type === 'main' && fiber.cable_info && fiber.cable_info.coordinates) { + detailedCoordinates = fiber.cable_info.coordinates; + } else if (segment.cable && segment.cable.coordinates) { + detailedCoordinates = segment.cable.coordinates; + } else if (segment.cable_info && segment.cable_info.coordinates) { + detailedCoordinates = segment.cable_info.coordinates; + } + + + if (detailedCoordinates && typeof detailedCoordinates === 'string') { + try { + detailedCoordinates = JSON.parse(detailedCoordinates); + } catch (e) { + detailedCoordinates = null; + } + } + validStations.forEach((station, stationIndex) => { + const lat = parseFloat(station.gps_lat); + const lng = parseFloat(station.gps_long); + routePoints.push([lat, lng]); + + const branchConnections = getBranchConnectionsAtStation(station.name, segment, allSegments); + const isBranchJunction = branchConnections.length > 0; + let iconColor = '#3388ff'; + let iconName = 'circle'; + let iconSize = 's'; + + if (station.type === 'pop') { + iconColor = '#acf0ab'; + iconName = 'village'; + iconSize = 'l'; + } else if (station.type === 'dispatcher') { + if (isBranchJunction) { + iconColor = '#FF6B6B'; + iconName = 'home'; + iconSize = 'm'; + } else if (station.object_type == 2 || station.object_type === '2') { + iconColor = '#ffa726'; + iconName = 'home'; + iconSize = 'm'; + } else { + iconColor = '#abbaf0'; + iconName = 'home'; + iconSize = 'm'; + } + } + let branchConnectionsHtml = ''; + if (isBranchJunction) { + branchConnectionsHtml = '
'; + branchConnectionsHtml += '
Faser-Übergänge:
'; + + branchConnections.forEach((conn, idx) => { + const junctionType = conn.type === 'branch_from_main' ? 'Hauptkabel → Branch' : 'Branch → Sub-Branch'; + const fromFiberColorName = conn.fromFiberColor || (conn.fromFiberColorHex ? hexToColorName(conn.fromFiberColorHex) : ''); + const fromBundleColorName = conn.fromBundleColor || (conn.fromBundleColorHex ? hexToColorName(conn.fromBundleColorHex) : ''); + const toFiberColorName = conn.toFiberColor || (conn.toFiberColorHex ? hexToColorName(conn.toFiberColorHex) : ''); + const toBundleColorName = conn.toBundleColor || (conn.toBundleColorHex ? hexToColorName(conn.toBundleColorHex) : ''); + + branchConnectionsHtml += ` +
+ ${junctionType} (Level ${conn.level})
+
+ Von: ${conn.fromCable}
+ └─ Faser ${conn.fromFiber || '?'} + ${conn.fromFiberColorHex ? ` ${fromFiberColorName}` : ''}
+ ${conn.fromBundle ? `└─ Bündel ${conn.fromBundle}` : ''} + ${conn.fromBundleColorHex ? ` ${fromBundleColorName}` : ''} + ${conn.fromBundle ? '
' : ''} +
+
+ Nach: ${conn.toCable}
+ └─ Faser ${conn.toFiber || '?'} + ${conn.toFiberColorHex ? ` ${toFiberColorName}` : ''}
+ ${conn.toBundle ? `└─ Bündel ${conn.toBundle}` : ''} + ${conn.toBundleColorHex ? ` ${toBundleColorName}` : ''} + ${conn.toBundle ? '
' : ''} +
+
+ `; + }); + branchConnectionsHtml += '
'; + } + + const popupContent = ` +
+
${station.name}
+ + ${station.type === 'pop' ? 'POP' : (isBranchJunction ? 'Branch-Verbindung' : (station.object_type == 2 || station.object_type === '2' ? 'Schacht' : 'Verteiler'))} + + ${isBranchJunction ? '' + branchConnections.length + ' Übergänge' : ''} +
+
+ Kabel: ${segment.name}
+ ${segment.fiber_nr ? `Faser: ${segment.fiber_nr}` : ''} + ${segment.fiber_color ? ` (${segment.fiber_color})` : ''}
+ ${segment.bundle_nr ? `Bündel: ${segment.bundle_nr}` : ''} + ${segment.bundle_color ? ` (${segment.bundle_color})` : ''}
+ Level: ${segment.level}
+ ${lat.toFixed(5)}, ${lng.toFixed(5)} +
+ ${branchConnectionsHtml} +
+ `; + + const marker = L.marker([lat, lng], { + icon: L.MakiMarkers.icon({ + icon: iconName, + color: iconColor, + size: iconSize + }) + }).bindPopup(popupContent); + if (station.type === 'dispatcher') { + const tooltip = marker.bindTooltip(station.name, { + permanent: true, + direction: 'top', + className: 'dispatcher-label', + offset: [0, -25] + }); + + marker.stationTooltip = tooltip; + marker.stationName = station.name; + } + + const stationKey = `${station.name}_${lat.toFixed(6)}_${lng.toFixed(6)}`; + const existingStation = drawnStations.get(stationKey); + + if (existingStation) { + if (isBranchJunction && !existingStation.isBranchJunction) { + allBounds.removeLayer(existingStation.marker); + allBounds.addLayer(marker); + drawnStations.set(stationKey, { marker, isBranchJunction }); + } else { + } + } else { + allBounds.addLayer(marker); + drawnStations.set(stationKey, { marker, isBranchJunction }); + } + + if (station.type === 'dispatcher' && station.object_type == 4) { + if (!window.allBranchPoints) { + window.allBranchPoints = []; + } + window.allBranchPoints.push({ + lat: lat, + lng: lng, + name: station.name, + segment: segment, + station: station + }); + } + }); + + const lineWeight = segment.type === 'main' ? 6 : 5; + const lineOpacity = 0.9; + const borderWeight = lineWeight + 4; + + let coords = routePoints; + + if (detailedCoordinates && Array.isArray(detailedCoordinates) && detailedCoordinates.length > 0) { + coords = detailedCoordinates.map(coord => [ + parseFloat(coord.gps_lat), + parseFloat(coord.gps_long) + ]); + } else { + + } + if (coords.length > 100) { + const firstCoord = coords[0]; + const lastCoord = coords[coords.length - 1]; + + const distStartEnd = Math.sqrt( + Math.pow(firstCoord[0] - lastCoord[0], 2) + + Math.pow(firstCoord[1] - lastCoord[1], 2) + ); + + if (distStartEnd < 0.001) { + let maxDist = 0; + let turningPointIndex = 0; + + for (let i = 0; i < coords.length; i++) { + const d = Math.sqrt( + Math.pow(coords[i][0] - firstCoord[0], 2) + + Math.pow(coords[i][1] - firstCoord[1], 2) + ); + if (d > maxDist) { + maxDist = d; + turningPointIndex = i; + } + } + if (turningPointIndex > 10 && turningPointIndex < coords.length - 10) { + coords = coords.slice(0, turningPointIndex + 1); + } else { + + } + } else { + } + } + if (coords.length >= 2 && validStations.length >= 2) { + const firstCoord = coords[0]; + const lastCoord = coords[coords.length - 1]; + const popStation = validStations.find(s => s.type === 'pop') || validStations[0]; + + const popLat = parseFloat(popStation.gps_lat); + const popLng = parseFloat(popStation.gps_long); + + const distFirstToPop = Math.sqrt( + Math.pow(firstCoord[0] - popLat, 2) + + Math.pow(firstCoord[1] - popLng, 2) + ); + + const distLastToPop = Math.sqrt( + Math.pow(lastCoord[0] - popLat, 2) + + Math.pow(lastCoord[1] - popLng, 2) + ); + + if (distLastToPop < distFirstToPop) { + coords.reverse(); + } else { + } + } + if (validStations.length > 0 && coords.length > 0) { + const firstCoord = coords[0]; + const firstStation = validStations[0]; + + const distToPop = Math.sqrt( + Math.pow(firstCoord[0] - parseFloat(firstStation.gps_lat), 2) + + Math.pow(firstCoord[1] - parseFloat(firstStation.gps_long), 2) + ); + + + if (distToPop > 0.001) { + let closestStationIndex = -1; + let minDist = Infinity; + + for (let i = 0; i < validStations.length; i++) { + const station = validStations[i]; + const dist = Math.sqrt( + Math.pow(firstCoord[0] - parseFloat(station.gps_lat), 2) + + Math.pow(firstCoord[1] - parseFloat(station.gps_long), 2) + ); + if (dist < minDist) { + minDist = dist; + closestStationIndex = i; + } + } + + const missingCoords = []; + for (let i = 0; i < closestStationIndex; i++) { + const station = validStations[i]; + missingCoords.push([ + parseFloat(station.gps_lat), + parseFloat(station.gps_long) + ]); + } + + if (missingCoords.length > 0) { + coords = [...missingCoords, ...coords]; + } + } else { + } + } + + for (let i = 0; i < Math.min(3, coords.length); i++) { + const coord = coords[i]; + } + for (let i = 0; i < Math.min(3, validStations.length); i++) { + const station = validStations[i]; + } + + if (coords.length > 52) { + for (let i = 50; i <= Math.min(54, coords.length - 1); i++) { + const coord = coords[i]; + } + + } + + const dropoutLocationName = segment.location || fiber.location; + const dropoutStation = validStations.find(s => dropoutLocationName && s.name === dropoutLocationName); + if (dropoutStation) { + } + + for (let i = Math.max(0, coords.length - 3); i < coords.length; i++) { + const coord = coords[i]; + } + + for (let i = Math.max(0, validStations.length - 3); i < validStations.length; i++) { + const station = validStations[i]; + } + let tooltipText = `
+ ${segment.name}
+ ${segment.type === 'branch' ? `(Abzweig Ebene ${segment.level})
` : ''} + ${segment.fiber_nr ? `Faser: ${segment.fiber_nr}` : ''} + ${segment.fiber_color ? ` (${segment.fiber_color})` : ''}
+ ${segment.bundle_nr ? `Bündel: ${segment.bundle_nr}` : ''} + ${segment.bundle_color ? ` (${segment.bundle_color})` : ''} + ${segment.location ? `
Dropout: ${segment.location}` : ''} +
`; + + let shouldSplit = false; + let splitIndex = -1; + let splitPointName = ''; + const targetFiber = segment.target_fiber; + const segmentFiber = segment.cable_info || segment.cable || {}; + const hasCustomer = fiber.home_id || + targetFiber?.home_id || + segmentFiber.home_id || + fiber.address || + targetFiber?.address || + segmentFiber.address || + fiber.customer_gps || + targetFiber?.customer_gps || + segmentFiber.customer_gps; + + const hasBranchDropout = segment.type === 'main' && segment.branch_dropouts && segment.branch_dropouts.length > 0; + + if (validStations.length > 0 && (hasCustomer || hasBranchDropout)) { + const effectiveFiber = { + location: targetFiber?.location || targetFiber?.branch_from_location || segmentFiber.location || fiber.location, + branch_from_location: targetFiber?.branch_from_location || segmentFiber.branch_from_location, + branch_to_location: targetFiber?.branch_to_location || segmentFiber.branch_to_location, + home_id: targetFiber?.home_id || segmentFiber.home_id || fiber.home_id, + address: targetFiber?.address || segmentFiber.address || fiber.address, + customer_gps: targetFiber?.customer_gps || segmentFiber.customer_gps || fiber.customer_gps, + name: targetFiber?.name || segmentFiber.name || fiber.name, + customer_cable_type: targetFiber?.customer_cable_type || segmentFiber.customer_cable_type || fiber.customer_cable_type, + customer_connector_type: targetFiber?.customer_connector_type || segmentFiber.customer_connector_type || fiber.customer_connector_type, + customer_cable_spec: targetFiber?.customer_cable_spec || segmentFiber.customer_cable_spec || fiber.customer_cable_spec, + customer_fiber_range: targetFiber?.customer_fiber_range || segmentFiber.customer_fiber_range || fiber.customer_fiber_range + }; + + let dropoutLat = null; + let dropoutLng = null; + let dropoutName = ''; + + if (effectiveFiber.location) { + const dropoutLocationName = effectiveFiber.location; + let dropoutStation = validStations.find(s => s.name === dropoutLocationName); + if (!dropoutStation) { + for (let seg of allSegments) { + if (seg.type === 'branch_points') continue; + const foundStation = seg.stations?.find(s => s.name === dropoutLocationName); + if (foundStation && foundStation.gps_lat && foundStation.gps_long) { + dropoutStation = foundStation; + break; + } + } + } + if (dropoutStation) { + dropoutLat = parseFloat(dropoutStation.gps_lat); + dropoutLng = parseFloat(dropoutStation.gps_long); + dropoutName = dropoutStation.name; + // console.log("✅ DROPOUT GEFUNDEN:", dropoutStation.name); + // console.log(" GPS:", dropoutLat, dropoutLng); + } else { + // console.log("❌ DROPOUT NICHT GEFUNDEN!"); + } + } + + if (dropoutLat === null && segment.type === 'main' && segment.branch_dropouts && segment.branch_dropouts.length > 0) { + const firstBranchDropout = segment.branch_dropouts[0]; + const branchDropoutStation = validStations.find(s => s.name === firstBranchDropout); + + if (branchDropoutStation) { + dropoutLat = parseFloat(branchDropoutStation.gps_lat); + dropoutLng = parseFloat(branchDropoutStation.gps_long); + dropoutName = branchDropoutStation.name; + } + } + + if (dropoutLat === null && validStations.length > 0) { + const lastStation = validStations[validStations.length - 1]; + dropoutLat = parseFloat(lastStation.gps_lat); + dropoutLng = parseFloat(lastStation.gps_long); + dropoutName = lastStation.name; + } + + if (dropoutLat !== null && dropoutLng !== null) { + let dropoutIndex = -1; + let minDist = Infinity; + for (let i = 0; i < coords.length; i++) { + const dist = Math.sqrt( + Math.pow(coords[i][0] - dropoutLat, 2) + + Math.pow(coords[i][1] - dropoutLng, 2) + ); + if (dist < minDist) { + minDist = dist; + dropoutIndex = i; + } + } + splitIndex = dropoutIndex; + splitPointName = dropoutName; + if (splitIndex >= 0 && splitIndex < coords.length - 1) { + shouldSplit = true; + } + } + } + + if (shouldSplit && segment.type === 'branch' && splitIndex <= 1) { + // console.log("⊘ Branch-Segment: Split am Anfang (Index <= 1) - KEIN Split nötig"); + shouldSplit = false; + } + if (shouldSplit) { + const coords2 = coords.slice(splitIndex); + + L.polyline(coords2, { + color: '#666666', + weight: borderWeight, + opacity: 0.3, + dashArray: '5, 10' + }).addTo(map); + + L.polyline(coords2, { + color: '#999999', + weight: lineWeight, + opacity: 0.3, + dashArray: '5, 10' + }).addTo(map).bindTooltip(`${segment.name}
Inaktiv ab ${splitPointName}`, { + permanent: false, + sticky: true, + direction: 'top' + }); + const coords1 = coords.slice(0, splitIndex + 1); + + let branchDashArray = null; + if (segment.type === 'branch') { + if (segment.branch_level === 1) { + branchDashArray = '15, 10'; + } else if (segment.branch_level === 2) { + branchDashArray = '20, 15'; + } else { + branchDashArray = '25, 20'; + } + } + + L.polyline(coords1, { + color: '#333333', + weight: borderWeight, + opacity: 0.8, + dashArray: branchDashArray + }).addTo(map); + + const hasDetailedStart = coords.length > 100; + const dashPattern = hasDetailedStart ? null : '10, 5'; + + L.polyline(coords1, { + color: segment.color, + weight: lineWeight, + opacity: lineOpacity, + dashArray: dashPattern, + zIndex: 1000 + }).addTo(map).bindTooltip(tooltipText + '
Aktiv bis ' + splitPointName + '' + + (dashPattern ? '
Vereinfachte Strecke (nur Stationspunkte)' : ''), { + permanent: false, + sticky: true, + direction: 'top' + }); + if (validStations.length >= 2 && coords1.length > 2) { + const stationsBeforeSplit = []; + for (let station of validStations) { + stationsBeforeSplit.push(station); + if (station.name === splitPointName) { + break; + } + } + + for (let i = 0; i < stationsBeforeSplit.length - 1; i++) { + const station1 = stationsBeforeSplit[i]; + const station2 = stationsBeforeSplit[i + 1]; + + const lat1 = parseFloat(station1.gps_lat); + const lng1 = parseFloat(station1.gps_long); + const lat2 = parseFloat(station2.gps_lat); + const lng2 = parseFloat(station2.gps_long); + + let startIdx = 0; + let minDist1 = Infinity; + for (let j = 0; j < coords1.length; j++) { + const dist = Math.sqrt( + Math.pow(coords1[j][0] - lat1, 2) + + Math.pow(coords1[j][1] - lng1, 2) + ); + if (dist < minDist1) { + minDist1 = dist; + startIdx = j; + } + } + + let endIdx = coords1.length - 1; + let minDist2 = Infinity; + for (let j = startIdx; j < coords1.length; j++) { + const dist = Math.sqrt( + Math.pow(coords1[j][0] - lat2, 2) + + Math.pow(coords1[j][1] - lng2, 2) + ); + if (dist < minDist2) { + minDist2 = dist; + endIdx = j; + } + } + + const segmentCoords = coords1.slice(startIdx, endIdx + 1); + + if (segmentCoords.length > 0) { + const midIndex = Math.floor(segmentCoords.length / 2); + const midCoord = segmentCoords[midIndex]; + + const midLat = midCoord[0]; + const midLng = midCoord[1]; + + const cableLabelMarker = L.marker([midLat, midLng], { + icon: L.divIcon({ + className: 'cable-label-marker', + html: '', + iconSize: [0, 0] + }) + }).addTo(map); + + const labelText = `${segment.name}`; + + cableLabelMarker.bindTooltip(labelText, { + permanent: true, + direction: 'center', + className: 'cable-segment-label', + offset: [0, 0] + }); + + cableLabelMarker.cableLabel = true; + cableLabelMarker.cableLabelText = labelText; + + allBounds.addLayer(cableLabelMarker); + } + } + } + + if (validStations.length > 0) { + const popStation = validStations.find(s => s.type === 'pop') || validStations[0]; + const firstDetailedCoord = coords1[2]; + + if (firstDetailedCoord) { + const popLat = parseFloat(popStation.gps_lat); + const popLng = parseFloat(popStation.gps_long); + + const distToPop = Math.sqrt( + Math.pow(firstDetailedCoord[0] - popLat, 2) + + Math.pow(firstDetailedCoord[1] - popLng, 2) + ); + if (distToPop > 0.001) { + L.polyline([[popLat, popLng], firstDetailedCoord], { + color: '#FF8C00', + weight: lineWeight, + opacity: 0.6, + dashArray: '15, 10', + zIndex: 999 + }).addTo(map).bindTooltip(`${segment.name}
Vereinfachte Strecke (keine GPS-Daten)`, { + permanent: false, + sticky: true, + direction: 'top' + }); + } + } + } + } else { + let branchDashArray = null; + if (segment.type === 'branch') { + if (segment.level === 1) { + branchDashArray = '20, 8'; + } else if (segment.level === 2) { + branchDashArray = '10, 10'; + } else { + branchDashArray = '25, 20'; + } + } + + const borderPolyline = L.polyline(coords, { + color: '#333333', + weight: borderWeight, + opacity: 0.8, + dashArray: branchDashArray + }).addTo(map); + + const polyline = L.polyline(coords, { + color: segment.color, + weight: lineWeight, + opacity: lineOpacity, + dashArray: branchDashArray + }).addTo(map); + + polyline.bindTooltip(tooltipText, { + permanent: false, + sticky: true, + direction: 'top' + }); + if (validStations.length >= 2 && coords.length > 2) { + for (let i = 0; i < validStations.length - 1; i++) { + const station1 = validStations[i]; + const station2 = validStations[i + 1]; + + const lat1 = parseFloat(station1.gps_lat); + const lng1 = parseFloat(station1.gps_long); + const lat2 = parseFloat(station2.gps_lat); + const lng2 = parseFloat(station2.gps_long); + + let segmentCoords = []; + let startIdx = 0; + let minDist1 = Infinity; + for (let j = 0; j < coords.length; j++) { + const dist = Math.sqrt( + Math.pow(coords[j][0] - lat1, 2) + + Math.pow(coords[j][1] - lng1, 2) + ); + if (dist < minDist1) { + minDist1 = dist; + startIdx = j; + } + } + + let endIdx = coords.length - 1; + let minDist2 = Infinity; + for (let j = startIdx; j < coords.length; j++) { + const dist = Math.sqrt( + Math.pow(coords[j][0] - lat2, 2) + + Math.pow(coords[j][1] - lng2, 2) + ); + if (dist < minDist2) { + minDist2 = dist; + endIdx = j; + } + } + + segmentCoords = coords.slice(startIdx, endIdx + 1); + + if (segmentCoords.length > 0) { + const midIndex = Math.floor(segmentCoords.length / 2); + const midCoord = segmentCoords[midIndex]; + const midLat = midCoord[0]; + const midLng = midCoord[1]; + const cableLabelMarker = L.marker([midLat, midLng], { + icon: L.divIcon({ + className: 'cable-label-marker', + html: '', + iconSize: [0, 0] + }) + }).addTo(map); + + const labelText = `${segment.name}`; + + cableLabelMarker.bindTooltip(labelText, { + permanent: true, + direction: 'center', + className: 'cable-segment-label', + offset: [0, 0] + }); + + cableLabelMarker.cableLabel = true; + cableLabelMarker.cableLabelText = labelText; + + allBounds.addLayer(cableLabelMarker); + } + } + } + + } + + if (validStations.length > 0) { + const targetFiber = segment.target_fiber; + const segmentFiber = segment.cable_info || segment.cable || {}; + + const effectiveFiber = { + location: targetFiber?.location || targetFiber?.branch_from_location || segmentFiber.location || fiber.location, + home_id: targetFiber?.home_id || segmentFiber.home_id || fiber.home_id, + address: targetFiber?.address || segmentFiber.address || fiber.address, + customer_gps: targetFiber?.customer_gps || segmentFiber.customer_gps || fiber.customer_gps, + name: targetFiber?.name || segmentFiber.name || fiber.name, + customer_cable_type: targetFiber?.customer_cable_type || segmentFiber.customer_cable_type || fiber.customer_cable_type, + customer_connector_type: targetFiber?.customer_connector_type || segmentFiber.customer_connector_type || fiber.customer_connector_type, + customer_cable_spec: targetFiber?.customer_cable_spec || segmentFiber.customer_cable_spec || fiber.customer_cable_spec, + customer_fiber_range: targetFiber?.customer_fiber_range || segmentFiber.customer_fiber_range || fiber.customer_fiber_range + }; + const dropoutLocationName = effectiveFiber.location; + + let dropoutStation = validStations.find(s => dropoutLocationName && s.name === dropoutLocationName); + const isLastBranch = segment.type === 'branch' && ( + segment.level === Math.max(...allSegments.filter(s => s.type === 'branch').map(s => s.level)) + ); + + if (isLastBranch) { + // console.log(` ⚡ LETZTES Branch-Segment: Verwende LETZTE Station als Dropout für Hausanschluss`); + + if (validStations.length > 0) { + dropoutStation = validStations[validStations.length - 1]; + // console.log(` ✓ Letzte Station: "${dropoutStation.name}"`); + } + } else if (segment.type === 'branch') { + if (!dropoutStation && validStations.length > 0) { + dropoutStation = validStations[validStations.length - 1]; + } + } else { + if (!dropoutStation && splitIndex >= 0 && coords[splitIndex]) { + const splitCoord = coords[splitIndex]; + let closestDist = Infinity; + + for (let station of validStations) { + const stationLat = parseFloat(station.gps_lat); + const stationLng = parseFloat(station.gps_long); + const dist = Math.sqrt( + Math.pow(splitCoord[0] - stationLat, 2) + + Math.pow(splitCoord[1] - stationLng, 2) + ); + + if (dist < closestDist) { + closestDist = dist; + dropoutStation = station; + } + } + } + + if (!dropoutStation && validStations.length > 0) { + dropoutStation = validStations[validStations.length - 1]; + } + } + const dropoutLat = parseFloat(dropoutStation.gps_lat); + const dropoutLng = parseFloat(dropoutStation.gps_long); + + let customerGps = null; + let customerInfo = null; + + if (effectiveFiber.customer_gps && effectiveFiber.address) { + customerGps = effectiveFiber.customer_gps; + customerInfo = { + address: effectiveFiber.address, + home_id: effectiveFiber.home_id, + name: effectiveFiber.name, + customer_cable_type: effectiveFiber.customer_cable_type, + customer_connector_type: effectiveFiber.customer_connector_type, + customer_cable_spec: effectiveFiber.customer_cable_spec, + customer_fiber_range: effectiveFiber.customer_fiber_range + }; + } else if (effectiveFiber.address && effectiveFiber.home_id) { + customerInfo = { + address: effectiveFiber.address, + home_id: effectiveFiber.home_id, + name: effectiveFiber.name, + customer_cable_type: effectiveFiber.customer_cable_type, + customer_connector_type: effectiveFiber.customer_connector_type, + customer_cable_spec: effectiveFiber.customer_cable_spec, + customer_fiber_range: effectiveFiber.customer_fiber_range + }; + customerGps = { lat: dropoutLat, lng: dropoutLng, fallback: true }; + + } + if (customerInfo && customerGps) { + const customerLat = parseFloat(customerGps.lat); + const customerLng = parseFloat(customerGps.lng); + const isFallback = customerGps.fallback || false; + + let connectionPoint = { + lat: dropoutLat, + lng: dropoutLng, + name: dropoutStation.name, + type: 'dispatcher' + }; + + if (!isFallback) { + const nearestBranchPoint = findNearestBranchPoint(customerLat, customerLng, allSegments); + if (nearestBranchPoint) { + const distanceToPolyline = getDistanceToPolyline(nearestBranchPoint.lat, nearestBranchPoint.lng, coords); + if (distanceToPolyline <= 0.0001) { + connectionPoint = nearestBranchPoint; + } + } + } + + if (!isFallback && (customerLat !== connectionPoint.lat || customerLng !== connectionPoint.lng)) { + const cableMatch = findCableForCustomerConnection( + customerLat, + customerLng, + window.allCables || [] + ); + + if (cableMatch) { + // console.log('Echtes Kabel für Hausanschluss gefunden:', cableMatch.cable.description); + + let cableCoords = cableMatch.cable.coordinates; + + if (cableMatch.reverse) { + cableCoords = [...cableCoords].reverse(); + } + + const latLngs = cableCoords.map(c => [c.gps_lat, c.gps_long]); + + L.polyline(latLngs, { + color: '#333333', + weight: 7, + opacity: 0.7, + dashArray: '5, 10' + }).addTo(map); + + const customerCableLine = L.polyline(latLngs, { + color: '#FFA500', + weight: 4, + opacity: 0.9, + dashArray: '5, 10', + zIndex: 2000 + }).addTo(map).bindTooltip(`Hausanschluss von ${connectionPoint.name}
Kabel: ${cableMatch.cable.description}`, { + permanent: false, + sticky: true, + direction: 'top' + }); + + allBounds.addLayer(customerCableLine); + if (latLngs.length > 0) { + const midIndex = Math.floor(latLngs.length / 2); + const midPoint = latLngs[midIndex]; + + const customerCableLabelMarker = L.marker(midPoint, { + icon: L.divIcon({ + className: 'cable-label-marker', + html: '', + iconSize: [0, 0] + }) + }).addTo(map); + + const customerCableLabelText = cableMatch.cable.description || 'Hausanschluss'; + + customerCableLabelMarker.bindTooltip(customerCableLabelText, { + permanent: true, + direction: 'center', + className: 'customer-cable-label', + offset: [0, 0] + }); + + customerCableLabelMarker.cableLabel = true; + customerCableLabelMarker.cableLabelText = customerCableLabelText; + + allBounds.addLayer(customerCableLabelMarker); + } + if (window.allBranchPoints && window.allBranchPoints.length > 0) { + let drawnCount = 0; + window.allBranchPoints.forEach(branchPoint => { + const pointLat = branchPoint.lat; + const pointLng = branchPoint.lng; + + let isOnCustomerCable = false; + for (let coord of cableCoords) { + const dist = Math.sqrt( + Math.pow(coord.gps_lat - pointLat, 2) + + Math.pow(coord.gps_long - pointLng, 2) + ); + + if (dist < 0.00001) { + isOnCustomerCable = true; + break; + } + } + if (isOnCustomerCable) { + const branchRing = L.circleMarker([pointLat, pointLng], { + radius: 8, + fillColor: '#FFD700', + fillOpacity: 0.9, + color: '#FF6B6B', + weight: 3, + opacity: 0.8, + zIndex: 2001 + }).bindPopup(` +
+
Abzweigpunkt
+ ${branchPoint.name}
+ Auf Kundenstrecke +
+
+ Kundenkabel: ${cableMatch.cable.description}
+ Kunde: ${customerInfo.address || 'Hausanschluss'}
+ ${pointLat.toFixed(5)}, ${pointLng.toFixed(5)} +
+
+ `).addTo(map); + + allBounds.addLayer(branchRing); + drawnCount++; + } + }); + } + + } else { + console.warn('Kein Kabel für Hausanschluss gefunden, nutze direkte Linie:', customerInfo.address); + + L.polyline([[connectionPoint.lat, connectionPoint.lng], [customerLat, customerLng]], { + color: '#333333', weight: 7, opacity: 0.7, dashArray: '5, 10' + }).addTo(map); + + L.polyline([[connectionPoint.lat, connectionPoint.lng], [customerLat, customerLng]], { + color: '#FFA500', + weight: 4, + opacity: 0.9, + dashArray: '5, 10', + zIndex: 2000 + }).addTo(map).bindTooltip(`Hausanschluss von ${connectionPoint.name}`, { + permanent: false, sticky: true, direction: 'top' + }); + + const midLat = (connectionPoint.lat + customerLat) / 2; + const midLng = (connectionPoint.lng + customerLng) / 2; + + const fallbackLabelMarker = L.marker([midLat, midLng], { + icon: L.divIcon({ + className: 'cable-label-marker', + html: '', + iconSize: [0, 0] + }) + }).addTo(map); + + fallbackLabelMarker.bindTooltip('Hausanschluss', { + permanent: true, + direction: 'center', + className: 'customer-cable-label', + offset: [0, 0] + }); + + fallbackLabelMarker.cableLabel = true; + fallbackLabelMarker.cableLabelText = 'Hausanschluss'; + + allBounds.addLayer(fallbackLabelMarker); + + const connectionMarker = L.circleMarker([connectionPoint.lat, connectionPoint.lng], { + radius: 8, fillColor: '#FFD700', fillOpacity: 0.9, color: '#FF6B6B', weight: 3, opacity: 1 + }).bindPopup(` +
+
Verbindungspunkt
+ ${connectionPoint.name}
+ + ${connectionPoint.type === 'branch_point' ? 'Abzweigpunkt' : 'Dispatcher'} + +
+
+ Verbunden mit:
+ ${customerInfo.address || 'Hausanschluss'}
+ ${customerInfo.home_id ? `Home-ID: ${customerInfo.home_id}` : ''} +
+
+ `).addTo(map); + + allBounds.addLayer(connectionMarker); + } + } + + const connectionPointType = connectionPoint.type === 'branch_point' ? 'Abzweigpunkt' : 'Verteiler (Dropout)'; + const customerPopup = ` +
+
Kunden-Anschluss
+ ${isFallback ? '
Exakte GPS-Position nicht verfügbar
' : ''} +
+
+ Verbindungspunkt: ${connectionPoint.name}
+ (${connectionPointType})
+ Adresse: ${customerInfo.address}
+ ${customerInfo.home_id ? `Home-ID: ${customerInfo.home_id}
` : ''} + ${customerInfo.name ? `Name: ${customerInfo.name}
` : ''} +
+ Kabel: ${segment.name}
+ Faser: ${segment.fiber_nr} ${segment.fiber_color ? `(${segment.fiber_color})` : ''} +
+
+ `; + + const customerMarker = L.marker([customerLat, customerLng], { + icon: L.MakiMarkers.icon({ icon: 'home', color: isFallback ? '#ffd700' : '#f0abab', size: 'm' }) + }).bindPopup(customerPopup); + + allBounds.addLayer(customerMarker); + } else { + } + } + + + }); + + allBounds.addTo(map); + + if (allBounds.getLayers().length > 0) { + map.fitBounds(allBounds.getBounds(), { padding: [50, 50] }); + } + + const MIN_ZOOM_FOR_STATION_LABELS = 15; + const MIN_ZOOM_FOR_CABLE_LABELS = 15; + + function updateTooltipVisibility() { + const currentZoom = map.getZoom(); + const shouldShowStations = currentZoom >= MIN_ZOOM_FOR_STATION_LABELS; + const shouldShowCables = currentZoom >= MIN_ZOOM_FOR_CABLE_LABELS; + + map.eachLayer(function (layer) { + if (layer.stationTooltip && layer.stationName) { + if (shouldShowStations) { + if (!layer.getTooltip()) { + layer.bindTooltip(layer.stationName, { + permanent: true, + direction: 'top', + className: 'dispatcher-label', + offset: [0, -50] + }); + } + } else { + layer.unbindTooltip(); + } + } + if (layer.cableLabel && layer.cableLabelText) { + if (shouldShowCables) { + if (!layer.getTooltip()) { + const labelClass = layer.cableLabelText.includes('Hausanschluss') || + layer.options.icon?.options?.className?.includes('customer') + ? 'customer-cable-label' + : 'cable-segment-label'; + + layer.bindTooltip(layer.cableLabelText, { + permanent: true, + direction: 'center', + className: labelClass, + offset: [0, 0] + }); + } + } else { + layer.unbindTooltip(); + } + } + }); + } + updateTooltipVisibility(); + + map.on('zoomend', updateTooltipVisibility); + + const legend = L.control({ position: 'bottomright' }); + legend.onAdd = function (map) { + const div = L.DomUtil.create('div', 'info legend'); + div.style.backgroundColor = 'white'; + div.style.padding = '10px'; + div.style.border = '2px solid #ccc'; + div.style.borderRadius = '5px'; + div.style.fontSize = '12px'; + div.style.maxWidth = '250px'; + + let html = 'Legende
'; + + html += '
'; + html += ' POP
'; + html += ' Verteiler
'; + html += ' Schacht
'; + html += ' Kunden-Haus
'; + html += '
'; + + html += '
'; + + html += 'Kabel & Fasern:
'; + allSegments + .filter(seg => seg.type !== 'branch_points') + .forEach((segment, idx) => { + const lineStyle = segment.type === 'branch' ? 'dashed' : 'solid'; + const borderStyle = segment.type === 'branch' ? '3px dashed' : '3px solid'; + const fiberColorName = segment.fiber_color || (segment.color ? hexToColorName(segment.color) : ''); + const bundleColorName = segment.bundle_color || (segment.bundle_color_hex ? hexToColorName(segment.bundle_color_hex) : ''); + + html += `
+ + + ${segment.name} + ${segment.fiber_nr ? `
└─ Faser ${segment.fiber_nr}` : ''} + ${fiberColorName ? ` (${fiberColorName})` : ''} + ${segment.bundle_nr ? `
└─ Bündel ${segment.bundle_nr}` : ''} + ${segment.bundle_color_hex ? ` ` : ''} + ${bundleColorName ? ` (${bundleColorName})` : ''} + +
`; + }); + + div.innerHTML = html; + return div; + }; + legend.addTo(map); + if (isLinework) { + renderFiberPathSchemaLinework(fiber, allSegments); + } else { + renderFiberPathSchema(fiber, allSegments); + } + + }, 300); +} +function renderFiberPathSchema(fiber, allSegments) { + const schemaContainer = $('#fiber-route-schema'); + if (!fiber || allSegments.length === 0) { + schemaContainer.html('
Keine Daten verfügbar
'); + return; + } + + let html = '
'; + html += '
Schematische Darstellung des Faserweges
'; + html += '
'; + + function getBranchConnectionsAtStation(stationName, currentSegment, allSegs) { + const connections = []; + + if (currentSegment.type === 'main' && currentSegment.branch_dropouts) { + currentSegment.branch_dropouts.forEach(dropoutLocation => { + if (dropoutLocation === stationName) { + allSegs.forEach(seg => { + if (seg.type === 'branch' && seg.branch_from_location_main === stationName) { + connections.push({ + type: 'branch_from_main', + fromCable: currentSegment.name, + fromFiber: currentSegment.fiber_nr, + fromFiberColor: currentSegment.fiber_color, + fromFiberColorHex: currentSegment.color || currentSegment.fiber_color_hex, + fromBundle: currentSegment.bundle_nr, + fromBundleColor: currentSegment.bundle_color, + fromBundleColorHex: currentSegment.bundle_color_hex, + toCable: seg.name, + toFiber: seg.fiber_nr, + toFiberColor: seg.fiber_color, + toFiberColorHex: seg.color || seg.fiber_color_hex, + toBundle: seg.bundle_nr, + toBundleColor: seg.bundle_color, + toBundleColorHex: seg.bundle_color_hex, + level: seg.level + }); + } + }); + } + }); + } + + if (currentSegment.type === 'branch' || currentSegment.type === 'main') { + const currentLastStation = currentSegment.stations && currentSegment.stations.length > 0 + ? currentSegment.stations[currentSegment.stations.length - 1] : null; + + allSegs.forEach(seg => { + if (seg.type === 'branch' && seg !== currentSegment) { + const firstStation = seg.stations && seg.stations.length > 0 ? seg.stations[0] : null; + if (seg.level > currentSegment.level && firstStation && firstStation.name === stationName) { + const alreadyAdded = connections.some(c => c.toCable === seg.name); + if (!alreadyAdded) { + connections.push({ + type: currentSegment.type === 'main' ? 'branch_from_main' : 'branch_from_branch', + fromCable: currentSegment.name, + fromFiber: currentSegment.fiber_nr, + fromFiberColor: currentSegment.fiber_color, + fromFiberColorHex: currentSegment.color || currentSegment.fiber_color_hex, + fromBundle: currentSegment.bundle_nr, + fromBundleColor: currentSegment.bundle_color, + fromBundleColorHex: currentSegment.bundle_color_hex, + toCable: seg.name, + toFiber: seg.fiber_nr, + toFiberColor: seg.fiber_color, + toFiberColorHex: seg.color || seg.fiber_color_hex, + toBundle: seg.bundle_nr, + toBundleColor: seg.bundle_color, + toBundleColorHex: seg.bundle_color_hex, + level: seg.level + }); + } + } + + if (seg.level > currentSegment.level && + currentLastStation && currentLastStation.name === stationName && + firstStation && firstStation.name === stationName) { + const alreadyAdded = connections.some(c => c.toCable === seg.name); + if (!alreadyAdded) { + connections.push({ + type: 'branch_from_branch', + fromCable: currentSegment.name, + fromFiber: currentSegment.fiber_nr, + fromFiberColor: currentSegment.fiber_color, + fromFiberColorHex: currentSegment.color || currentSegment.fiber_color_hex, + fromBundle: currentSegment.bundle_nr, + fromBundleColor: currentSegment.bundle_color, + fromBundleColorHex: currentSegment.bundle_color_hex, + toCable: seg.name, + toFiber: seg.fiber_nr, + toFiberColor: seg.fiber_color, + toFiberColorHex: seg.color || seg.fiber_color_hex, + toBundle: seg.bundle_nr, + toBundleColor: seg.bundle_color, + toBundleColorHex: seg.bundle_color_hex, + level: seg.level + }); + } + } + } + }); + } + + return connections; + } + + function getStationInfo(station, isDropoutPoint, segment, allSegments) { + const objectType = parseInt(station.object_type); + const branchConnections = getBranchConnectionsAtStation(station.name, segment, allSegments); + const isBranchJunction = branchConnections.length > 0; + + let icon = 'fa-home'; + let colorClass = 'station-default'; + let color = '#3388ff'; + let label = station.name || 'Station'; + let badgeText = 'Station'; + let badgeClass = 'badge-secondary'; + + if (station.type === 'pop') { + icon = 'fa-building'; + colorClass = 'station-pop'; + color = '#acf0ab'; + label = station.name || 'POP'; + badgeText = 'POP'; + badgeClass = 'badge-success'; + } else if (station.type === 'dispatcher') { + if (isBranchJunction) { + icon = 'fa-code-branch'; + colorClass = 'station-branch'; + color = '#FF6B6B'; + label = station.name || 'Branch-Verbindung'; + badgeText = 'Branch-Verbindung'; + badgeClass = 'badge-danger'; + } else if (objectType === 2) { + icon = 'fa-archive'; + colorClass = 'station-manhole'; + color = '#ffa726'; + label = station.name || 'Schacht'; + badgeText = 'Schacht'; + badgeClass = 'badge-warning'; + } else { + icon = 'fa-sitemap'; + colorClass = 'station-distributor'; + color = '#abbaf0'; + label = station.name || 'Verteiler'; + badgeText = 'Verteiler'; + badgeClass = 'badge-info'; + } + } + + return { + icon, + colorClass, + label, + color, + badgeText, + badgeClass, + isBranchJunction, + branchConnections + }; + } + + function getDropoutName(segment, fiber) { + const targetFiber = segment.target_fiber; + const segmentFiber = segment.cable_info || segment.cable || {}; + + if (segment.type === 'main' && segment.branch_dropouts && segment.branch_dropouts.length > 0) { + return segment.branch_dropouts[0]; + } + + const effectiveFiber = { + location: targetFiber?.location || targetFiber?.branch_from_location || segmentFiber.location || fiber.location + }; + + if (effectiveFiber.location) { + return effectiveFiber.location; + } + + return null; + } + + function getStationsUntilDropout(segment, fiber) { + const validStations = segment.stations.filter(s => s.gps_lat && s.gps_long); + + if (validStations.length === 0) { + return []; + } + + const dropoutName = getDropoutName(segment, fiber); + + if (!dropoutName) { + return validStations; + } + + const dropoutIndex = validStations.findIndex(s => s.name === dropoutName); + + if (dropoutIndex === -1) { + return validStations; + } + + return validStations.slice(0, dropoutIndex + 1); + } + + let lastStationName = null; + + const segmentChain = allSegments + .filter(s => s.type !== 'branch_points') + .sort((a, b) => { + if (a.type === 'main' && b.type !== 'main') return -1; + if (a.type !== 'main' && b.type === 'main') return 1; + return (a.level || 0) - (b.level || 0); + }); + + let lastSegmentWithCustomer = null; + for (let i = segmentChain.length - 1; i >= 0; i--) { + const seg = segmentChain[i]; + const targetFiber = seg.target_fiber; + if (targetFiber?.home_id || targetFiber?.address || fiber.home_id || fiber.address) { + lastSegmentWithCustomer = seg; + break; + } + } + + let currentSegmentName = null; + + let previousSegment = null; + + segmentChain.forEach((segment, segmentIndex) => { + const stationsToShow = getStationsUntilDropout(segment, fiber); + const dropoutName = getDropoutName(segment, fiber); + + // console.log(`Segment ${segment.name} (${segment.type}, level ${segment.level}):`, { + // dropoutName, + // stationsToShow: stationsToShow.map(s => s.name) + // }); + + if (stationsToShow.length === 0) { + return; + } + + stationsToShow.forEach((station, stationIndex) => { + const isSegmentChange = previousSegment !== null && previousSegment.name !== segment.name; + const dropoutName = getDropoutName(segment, fiber); + const isDropoutPoint = dropoutName === station.name; + const isDuplicate = station.name === lastStationName && stationIndex === 0; + if (isDuplicate && isSegmentChange) { + // console.log(`⏭️ Branch beginnt an ${station.name} → Station überspringen, aber ${segment.name}-Linie zeichnen`); + previousSegment = segment; + } + else if (isDuplicate) { + // console.log(`⏭️ Skipping duplicate within segment: ${station.name}`); + return; + } + else { + // console.log(`📍 Drawing station ${station.name}: segment=${segment.name}, isDropout=${isDropoutPoint}`); + const stationInfo = getStationInfo(station, isDropoutPoint, segment, allSegments); + const showTransition = isDropoutPoint && isSegmentChange; + + let branchConnectionsHtml = ''; + if (stationInfo.isBranchJunction) { + branchConnectionsHtml = '
'; + branchConnectionsHtml += '
Faser-Übergänge:
'; + + stationInfo.branchConnections.forEach((conn, idx) => { + const junctionType = conn.type === 'branch_from_main' ? 'Hauptkabel → Branch' : 'Branch → Sub-Branch'; + const fromFiberColorName = conn.fromFiberColor || (conn.fromFiberColorHex ? hexToColorName(conn.fromFiberColorHex) : ''); + const fromBundleColorName = conn.fromBundleColor || (conn.fromBundleColorHex ? hexToColorName(conn.fromBundleColorHex) : ''); + const toFiberColorName = conn.toFiberColor || (conn.toFiberColorHex ? hexToColorName(conn.toFiberColorHex) : ''); + const toBundleColorName = conn.toBundleColor || (conn.toBundleColorHex ? hexToColorName(conn.toBundleColorHex) : ''); + + branchConnectionsHtml += ` +
+ ${junctionType} (Level ${conn.level})
+
+ Von: ${conn.fromCable}
+ └─ Faser ${conn.fromFiber || '?'} ${conn.fromFiberColorHex ? ` ${fromFiberColorName}` : ''}
+ ${conn.fromBundle ? `└─ Bündel ${conn.fromBundle}` : ''} + ${conn.fromBundleColorHex ? ` ${fromBundleColorName}` : ''} + ${conn.fromBundle ? '
' : ''} +
+
+ Nach: ${conn.toCable}
+ └─ Faser ${conn.toFiber || '?'} ${conn.toFiberColorHex ? ` ${toFiberColorName}` : ''}
+ ${conn.toBundle ? `└─ Bündel ${conn.toBundle}` : ''} + ${conn.toBundleColorHex ? ` ${toBundleColorName}` : ''} + ${conn.toBundle ? '
' : ''} +
+
+ `; + }); + branchConnectionsHtml += '
'; + } + + const popupContent = ` +
+
${station.name}
+ ${stationInfo.badgeText} + ${stationInfo.isBranchJunction ? '' + stationInfo.branchConnections.length + ' Übergänge' : ''} +
+
+ Kabel: ${segment.name}
+ ${segment.fiber_nr ? `F: ${segment.fiber_nr}` : ''} + ${segment.fiber_nr && segment.fiber_color_hex ? ` (${segment.fiber_color || hexToColorName(segment.fiber_color_hex)})` : ''}
+ ${segment.bundle_nr ? `B: ${segment.bundle_nr}` : ''} + ${segment.bundle_nr && segment.bundle_color_hex ? ` (${segment.bundle_color || hexToColorName(segment.bundle_color_hex)})` : ''}
+ Level: ${segment.level}
+ ${parseFloat(station.gps_lat).toFixed(5)}, ${parseFloat(station.gps_long).toFixed(5)} +
+ ${branchConnectionsHtml} +
+ `; + + html += ` +
+
+ +
+
+ ${stationInfo.label} + ${isDropoutPoint ? '
Dropout' : ''} + ${showTransition ? ` +
+ ⚡ Faser-Übergang:
+
+ Von: ${previousSegment.name}
+ └─ Faser ${previousSegment.fiber_nr || '?'} ${(previousSegment.color || previousSegment.fiber_color_hex) ? ` ${previousSegment.fiber_color || (previousSegment.fiber_color_hex ? hexToColorName(previousSegment.fiber_color_hex) : '') || (previousSegment.color ? hexToColorName(previousSegment.color) : '')}` : ''}
+ ${previousSegment.bundle_nr ? `└─ Bündel ${previousSegment.bundle_nr} ${previousSegment.bundle_color_hex ? ` ${previousSegment.bundle_color || (previousSegment.bundle_color_hex ? hexToColorName(previousSegment.bundle_color_hex) : '')}` : ''}
` : ''} +
+
+ Nach: ${segment.name}
+ └─ Faser ${segment.fiber_nr || '?'} ${(segment.color || segment.fiber_color_hex) ? ` ${segment.fiber_color || (segment.fiber_color_hex ? hexToColorName(segment.fiber_color_hex) : '') || (segment.color ? hexToColorName(segment.color) : '')}` : ''}
+ ${segment.bundle_nr ? `└─ Bündel ${segment.bundle_nr} ${segment.bundle_color_hex ? ` ${segment.bundle_color || (segment.bundle_color_hex ? hexToColorName(segment.bundle_color_hex) : '')}` : ''}
` : ''} +
+
+ ` : ''} +
+
+ `; + + lastStationName = station.name; + previousSegment = segment; + } + const isLastStationInSegment = stationIndex === stationsToShow.length - 1; + const isLastSegment = segmentIndex === segmentChain.length - 1; + + let shouldDrawConnection = false; + + if (!isLastStationInSegment) { + shouldDrawConnection = true; + } else if (isLastSegment) { + shouldDrawConnection = false; + } else if (isDuplicate && isSegmentChange) { + shouldDrawConnection = true; + } + + if (shouldDrawConnection) { + const lineStyle = segment.type === 'branch' ? 'schema-line-dashed-horizontal' : ''; + const segmentColor = segment.color || segment.fiber_color_hex || '#FF0000'; + const fiberColorName = segment.fiber_color || (segment.fiber_color_hex ? hexToColorName(segment.fiber_color_hex) : '') || (segment.color ? hexToColorName(segment.color) : ''); + const bundleColorName = segment.bundle_color || (segment.bundle_color_hex ? hexToColorName(segment.bundle_color_hex) : ''); + const fiberColorHex = segment.fiber_color_hex || segment.color || '#FF0000'; + const bundleColorHex = segment.bundle_color_hex || '#cccccc'; + + const labelText = ` + ${segment.name} + ${segment.fiber_nr ? `
F: ${segment.fiber_nr}` : ''} + ${fiberColorName ? ` ${fiberColorName}` : ''} + ${segment.fiber_nr ? `` : ''} + ${segment.bundle_nr ? `
B: ${segment.bundle_nr}` : ''} + ${bundleColorName ? ` ${bundleColorName}` : ''} + ${segment.bundle_nr ? `` : ''} + `.trim(); + + html += ` +
+
+
+ ${labelText} +
+
+
+ `; + } + }); + }); + + if (lastSegmentWithCustomer) { + const targetFiber = lastSegmentWithCustomer.target_fiber; + const customerAddress = targetFiber?.address || fiber.address; + const customerHomeId = targetFiber?.home_id || fiber.home_id; + const customerName = targetFiber?.name || fiber.name; + + if (customerHomeId || customerAddress) { + html += ` +
+
+
+ Hausanschluss +
+
+
+
+
+ +
+
+ ${customerHomeId ? `ID: ${customerHomeId}
` : ''} + ${customerAddress ? `${customerAddress}
` : ''} + ${customerName ? `${customerName}` : ''} +
+
+ `; + } + } + + html += '
'; + html += '
'; + + schemaContainer.html(html); + + $('.schema-station-clickable').off('click').on('click', function (e) { + e.stopPropagation(); + const $station = $(this); + const popupContent = $station.attr('data-popup-content').replace(/"/g, '"'); + const stationName = $station.attr('data-station-name'); + + $station.popover('dispose'); + $station.popover({ + content: popupContent, + html: true, + placement: 'top', + trigger: 'manual', + container: 'body', + template: ` + + ` + }); + + $('.schema-station-clickable').not($station).popover('hide'); + + $station.popover('show'); + }); + + $(document).off('click.schema-popup').on('click.schema-popup', function (e) { + if (!$(e.target).closest('.schema-station-clickable, .popover').length) { + $('.schema-station-clickable').popover('hide'); + } + }); +} + +function renderFiberPathSchemaLinework(fiber, allSegments) { + const schemaContainer = $('#fiber-route-schema'); + + if (!fiber || allSegments.length === 0) { + schemaContainer.html('
Keine Daten verfügbar
'); + return; + } + + const sortedSegments = allSegments + .filter(s => s.type !== 'branch_points') + .sort((a, b) => { + if (a.type === 'main') return -1; + if (b.type === 'main') return 1; + return (a.level || 0) - (b.level || 0); + }); + + let displayNodes = []; + + const normalize = (str) => str ? str.toString().toLowerCase().replace(/[^a-z0-9]/g, "") : ""; + + for (let i = 0; i < sortedSegments.length; i++) { + const segment = sortedSegments[i]; + const nextSegment = (i < sortedSegments.length - 1) ? sortedSegments[i + 1] : null; + let stations = segment.stations.filter(s => s.gps_lat && s.gps_long); + if (stations.length === 0) continue; + + if (segment.type !== 'main') { + let startNameRaw = null; + if (segment.cable_info && segment.cable_info.from_location) startNameRaw = segment.cable_info.from_location; + else if (segment.from_location) startNameRaw = segment.from_location; + else if (segment.target_fiber && segment.target_fiber.branch_from_location) startNameRaw = segment.target_fiber.branch_from_location; + + if (startNameRaw) { + const startIndex = stations.findIndex(s => normalize(s.name) === normalize(startNameRaw)); + if (startIndex !== -1) { + stations = stations.slice(startIndex); + } + } + } + let endNameRaw = null; + if (nextSegment) { + if (nextSegment.cable_info && nextSegment.cable_info.from_location) endNameRaw = nextSegment.cable_info.from_location; + else if (nextSegment.from_location) endNameRaw = nextSegment.from_location; + + if (!endNameRaw && nextSegment.stations.length > 0) { + endNameRaw = nextSegment.stations[0].name; + } + } else { + endNameRaw = segment.target_fiber?.location || fiber.location; + } + + if (endNameRaw) { + const endIndex = stations.findIndex(s => normalize(s.name) === normalize(endNameRaw)); + if (endIndex !== -1) { + if (endIndex === 0 && stations.length > 1) { + // console.log(`Schema: Ignoriere Cut bei Index 0 für ${segment.name} (Start=Ende)`); + } else { + stations = stations.slice(0, endIndex + 1); + } + } + } + + stations.forEach((station, idx) => { + const isLastInSegment = (idx === stations.length - 1); + const isTransitionPoint = isLastInSegment && (nextSegment !== null); + const segColor = segment.color || segment.fiber_color_hex || '#999'; + const lastAddedNode = displayNodes.length > 0 ? displayNodes[displayNodes.length - 1] : null; + const isDuplicate = lastAddedNode && normalize(lastAddedNode.name) === normalize(station.name); + + if (isDuplicate) { + if (isTransitionPoint) { + lastAddedNode.isBranchPoint = true; + lastAddedNode.transitionData = { + nextCable: nextSegment.name, + nextFiber: nextSegment.fiber_nr, + nextColor: nextSegment.color || nextSegment.fiber_color_hex, + nextFiberColor: nextSegment.fiber_color, + nextFiberColorHex: nextSegment.fiber_color_hex, + nextBundleNr: nextSegment.bundle_nr, + nextBundleColor: nextSegment.bundle_color, + nextBundleColorHex: nextSegment.bundle_color_hex + }; + } + } else { + displayNodes.push({ + name: station.name, + type: station.type, + object_type: station.object_type, + + lat: parseFloat(station.gps_lat), + lng: parseFloat(station.gps_long), + + cable: segment.name, + fiberNr: segment.fiber_nr, + color: segColor, + fiberColor: segment.fiber_color, + fiberColorHex: segment.fiber_color_hex, + bundleNr: segment.bundle_nr, + bundleColor: segment.bundle_color, + bundleColorHex: segment.bundle_color_hex, + level: segment.level, + + isBranchPoint: isTransitionPoint, + isDropout: (!nextSegment && isLastInSegment), + + transitionData: isTransitionPoint ? { + nextCable: nextSegment.name, + nextFiber: nextSegment.fiber_nr, + nextColor: nextSegment.color || nextSegment.fiber_color_hex, + nextFiberColor: nextSegment.fiber_color, + nextFiberColorHex: nextSegment.fiber_color_hex, + nextBundleNr: nextSegment.bundle_nr, + nextBundleColor: nextSegment.bundle_color, + nextBundleColorHex: nextSegment.bundle_color_hex + } : null + }); + } + }); + } + + let html = '
'; + html += '
Faserweg (POP → Kunde)
'; + html += '
'; + + displayNodes.forEach((node, index) => { + const style = getLineworkStationStyle(node); + + let badgeClass = 'badge-info'; + let badgeText = style.typeLabel; + + if (node.isBranchPoint) { + style.cssClass = 'station-branch'; + style.icon = 'fa-code-branch'; + style.color = '#9c27b0'; + badgeClass = 'badge-danger'; + badgeText = 'Branch-Verbindung'; + } else if (node.type === 'pop') { + badgeClass = 'badge-success'; + } else if (node.object_type == 2) { + badgeClass = 'badge-warning'; + } + + const fiberColorName = node.fiberColor || (node.fiberColorHex ? hexToColorName(node.fiberColorHex) : ''); + const bundleColorName = node.bundleColor || (node.bundleColorHex ? hexToColorName(node.bundleColorHex) : ''); + + let popupContent = ` +
+
${node.name}
+ ${badgeText} + ${node.isBranchPoint ? '1 Übergänge' : ''} + +
+ +
+ Kabel: ${node.cable}
+ ${node.fiberNr ? `F: ${node.fiberNr}` : ''} + ${node.fiberColorHex ? ` (${fiberColorName})` : ''}
+ + ${node.bundleNr ? `B: ${node.bundleNr}` : ''} + ${node.bundleColorHex ? ` (${bundleColorName})` : ''}
+ + Level: ${node.level || 0}
+ ${(node.lat && node.lng) ? `${node.lat.toFixed(5)}, ${node.lng.toFixed(5)}` : ''} +
`; + + if (node.transitionData) { + const nextFiberColorName = node.transitionData.nextFiberColor || (node.transitionData.nextFiberColorHex ? hexToColorName(node.transitionData.nextFiberColorHex) : ''); + const nextBundleColorName = node.transitionData.nextBundleColor || (node.transitionData.nextBundleColorHex ? hexToColorName(node.transitionData.nextBundleColorHex) : ''); + const junctionType = node.level === 0 ? 'Hauptkabel → Branch' : 'Branch → Sub-Branch'; + + popupContent += ` +
+
+
Faser-Übergänge:
+ +
+ ${junctionType} (Level ${node.level})
+ +
+ Von: ${node.cable}
+ └─ Faser ${node.fiberNr || '?'} + ${node.fiberColorHex ? ` ${fiberColorName}` : ''} +
+ └─ Bündel ${node.bundleNr || '?'} + ${node.bundleColorHex ? ` ${bundleColorName}` : ''} + +
+ +
+ Nach: ${node.transitionData.nextCable}
+ └─ Faser ${node.transitionData.nextFiber || '?'} + ${node.transitionData.nextFiberColorHex ? ` ${nextFiberColorName}` : ''} +
+ └─ Bündel ${node.transitionData.nextBundleNr || '?'} + ${node.transitionData.nextBundleColorHex ? ` ${nextBundleColorName}` : ''} + +
+
+
`; + } + + popupContent += `
`; + + html += ` +
+
+ +
+
+ ${node.name} + ${(node.isDropout || node.isBranchPoint) ? 'Dropout' : ''} +
+
+ `; + + if (index < displayNodes.length - 1) { + const nextNode = displayNodes[index + 1]; + const isBranchLine = node.isBranchPoint; + + let lineColor = '#999'; + let lineLabel = ''; + + const makeLabel = (cable, fNr, fCol, fColHex, bNr, bCol, bColHex) => { + const fName = fCol || (fColHex ? hexToColorName(fColHex) : ''); + const bName = bCol || (bColHex ? hexToColorName(bColHex) : ''); + return ` + ${cable}
+ + ${fNr ? `F: ${fNr}` : ''} ${fName ? ` ${fName}` : ''} + + ${bNr ? `
B: ${bNr} ${bName ? ` ${bName}` : ''}` : ''} + `; + }; + + if (isBranchLine && node.transitionData) { + lineColor = node.transitionData.nextColor; + lineLabel = makeLabel( + node.transitionData.nextCable, + node.transitionData.nextFiber, + node.transitionData.nextFiberColor, + node.transitionData.nextFiberColorHex, + node.transitionData.nextBundleNr, + node.transitionData.nextBundleColor, + node.transitionData.nextBundleColorHex + ); + } else { + lineColor = nextNode.color; + lineLabel = makeLabel( + nextNode.cable, + nextNode.fiberNr, + nextNode.fiberColor, + nextNode.fiberColorHex, + nextNode.bundleNr, + nextNode.bundleColor, + nextNode.bundleColorHex + ); + } + + if (!lineColor) lineColor = '#FF0000'; + + const lineClass = isBranchLine ? 'schema-line-dashed-horizontal' : 'schema-line-horizontal'; + const lineStyle = isBranchLine + ? `color: ${lineColor};` + : `background-color: ${lineColor}; height: 4px;`; + + html += ` +
+
+
${lineLabel}
+
+
+ `; + } + }); + + const targetFiber = fiber; + if (targetFiber.home_id || targetFiber.address) { + html += ` +
+
+
Hausanschluss
+
+
+
+
+ +
+
+ ${targetFiber.home_id || ''}
+ ${targetFiber.name || 'Kunde'} +
+
+ `; + } + + html += '
'; + schemaContainer.html(html); + + $('.schema-station-clickable').off('click').on('click', function (e) { + e.stopPropagation(); + const $station = $(this); + const popupContent = $station.attr('data-popup-content').replace(/"/g, '"'); + + $station.popover('dispose'); + $station.popover({ + content: popupContent, + html: true, + placement: 'top', + trigger: 'manual', + container: 'body', + template: ` + + ` + }); + $('.schema-station-clickable').not($station).popover('hide'); + $station.popover('show'); + }); + + $(document).off('click.schema-popup').on('click.schema-popup', function (e) { + if (!$(e.target).closest('.schema-station-clickable, .popover').length) { + $('.schema-station-clickable').popover('hide'); + } + }); +} + +function getLineworkStationStyle(node) { + if (node.type === 'pop') return { icon: 'fa-building', cssClass: 'station-pop', color: '#4caf50', typeLabel: 'POP' }; + if (node.object_type == 4) return { icon: 'fa-code-branch', cssClass: 'station-branch', color: '#9c27b0', typeLabel: 'Abzweig' }; + if (node.object_type == 2) return { icon: 'fa-archive', cssClass: 'station-manhole', color: '#ffa726', typeLabel: 'Schacht' }; + return { icon: 'fa-sitemap', cssClass: 'station-distributor', color: '#3388ff', typeLabel: 'Verteiler' }; +} +function getBranchColor(level) { + const colors = [ + '#FF6B6B', + '#FFA500', + '#FFD700', + '#9B59B6', + '#E91E63', + ]; + return colors[(level - 1) % colors.length]; +} + +function hexToColorName(hex) { + const colorMap = { + '#FF6B6B': 'Rot', + '#FFA500': 'Orange', + '#F79646': 'Orange', + '#FFD700': 'Gold', + '#9B59B6': 'Violett', + '#E91E63': 'Pink', + '#3388ff': 'Blau', + '#FF0000': 'Rot', + '#00FF00': 'Grün', + '#0000FF': 'Blau', + '#FFFF00': 'Gelb', + '#00FFFF': 'Cyan', + '#FF00FF': 'Magenta', + '#800000': 'Dunkelrot', + '#808000': 'Olive', + '#008000': 'Grün', + '#800080': 'Lila', + '#008080': 'Türkis', + '#00B0F0': 'Türkis', + '#000080': 'Marineblau', + '#FFC0CB': 'Rosa', + '#A52A2A': 'Braun', + '#948A54': 'Braun', + '#808080': 'Grau', + '#C0C0C0': 'Grau', + '#000000': 'Schwarz', + '#FFFFFF': 'Weiß', + '#7030A0': 'Violett', + + }; + + const normalizedHex = hex.toUpperCase().startsWith('#') ? hex.toUpperCase() : '#' + hex.toUpperCase(); + + if (colorMap[normalizedHex]) { + return colorMap[normalizedHex]; + } + + if (normalizedHex.match(/#FF[0-9A-F]{4}/)) return 'Rot'; + if (normalizedHex.match(/#[0-9A-F]{2}FF[0-9A-F]{2}/)) return 'Grün'; + if (normalizedHex.match(/#[0-9A-F]{4}FF/)) return 'Blau'; + if (normalizedHex.match(/#FFFF[0-9A-F]{2}/)) return 'Gelb'; + if (normalizedHex.match(/#FF[0-9A-F]{2}FF/)) return 'Magenta'; + if (normalizedHex.match(/#[0-9A-F]{2}FFFF/)) return 'Cyan'; + + return hex; +} + +$(document).on('hidden.bs.modal', '#cableRouteMapModal, #fiberRouteMapModal', function () { + if ($('.modal:visible').length > 0) { + $('body').addClass('modal-open'); + } +}); + +$(document).on('shown.bs.modal', '#cableRouteMapModal, #fiberRouteMapModal', function () { + $('#fiberPlanCableModal').css('overflow', 'hidden'); +}); + +$(document).on('hidden.bs.modal', '#cableRouteMapModal, #fiberRouteMapModal', function () { + $('#fiberPlanCableModal').css('overflow', 'auto'); +}); + +function findTargetFiberAtLevel(branchPath, targetLevel, currentLevel = 1) { + if (!branchPath || branchPath.error) { + return null; + } + + if (currentLevel === targetLevel && branchPath.target_fiber) { + return branchPath.target_fiber; + } + + if (branchPath.next_branch) { + return findTargetFiberAtLevel(branchPath.next_branch, targetLevel, currentLevel + 1); + } + + return null; +} +function findCableForCustomerConnection(customerLat, customerLng, cables) { + const tolerance = 0.00001; + + for (let cable of cables) { + if (!cable.coordinates || cable.coordinates.length === 0) continue; + + const firstPoint = cable.coordinates[0]; + const lastPoint = cable.coordinates[cable.coordinates.length - 1]; + + if (Math.abs(firstPoint.gps_lat - customerLat) < tolerance && + Math.abs(firstPoint.gps_long - customerLng) < tolerance) { + return { + cable: cable, + reverse: false + }; + } + + if (Math.abs(lastPoint.gps_lat - customerLat) < tolerance && + Math.abs(lastPoint.gps_long - customerLng) < tolerance) { + return { + cable: cable, + reverse: true + }; + } + } + + return null; +} +$('body').on('click', '#toggle-fiber-view', function () { + const btn = $(this); + const cableName = btn.data('cable-name'); + const showAll = btn.data('show-all'); + + if (showAll === false || showAll === 'false') { + btn.data('show-all', 'true'); + btn.html(' Gefilterte Ansicht'); + btn.attr('title', 'Nur Fasern vom Rack anzeigen'); + loadFiberPlanCableDetails(cableName, null, null); + } else { + const fiberStart = btn.data('fiber-start'); + const fiberEnd = btn.data('fiber-end'); + btn.data('show-all', 'false'); + btn.html(' Alle Fasern anzeigen'); + btn.attr('title', 'Alle Fasern anzeigen'); + loadFiberPlanCableDetails(cableName, fiberStart, fiberEnd); + } +}); +$(document).ready(function() { + $('body').on('click', '#fiber-details-table .view-fiber-path', function (e) { + e.preventDefault(); + e.stopPropagation(); + const fiberId = $(this).data('fiber-id'); + loadFiberPath(fiberId); + }); + + $('body').on('click', '#fiber-details-table .fiber-row-clickable', function (e) { + if ($(e.target).closest('button').length || $(e.target).closest('input').length) { + return; + } + const fiberId = $(this).data('fiber-id'); + loadFiberPath(fiberId); + }); + + $('body').on('click', '.load-branch-cable', function (e) { + e.preventDefault(); + const branchCableName = $(this).data('cable-name'); + loadFiberPlanCableDetails(branchCableName); + }); + + $('body').on('click', '.show-cable-route-map', function (e) { + const cableId = $(this).data('cable-id'); + const cableName = $(this).data('cable-name'); + showCableRouteMap(cableId, cableName); + }); +}); \ No newline at end of file diff --git a/public/js/pages/pop/detail.js b/public/js/pages/pop/detail.js index e1127f35a..aa6a9a926 100644 --- a/public/js/pages/pop/detail.js +++ b/public/js/pages/pop/detail.js @@ -3,70 +3,68 @@ $(document).ready(function () { Sortable.create(sortracklist, { handle: '.move-handle', onEnd: function () { - var popid = $('#sortracklist').data('popid'); var racksortids = []; $('#sortracklist').find('th').each(function (index, value) { - racksortids.push($(this).data('rackid')); }); $.post(linkSorTracklist + "&pop_id=" + popid, { racksortids: racksortids }, function (data) { if (data.success === true) { - } }, "json"); - } - }); } + $('#pop-rack-div').show(); + $('#rackModal').on('show.bs.modal', function (event) { - var thisclick = $(event.relatedTarget); - var rackhe = thisclick.closest('table').find('th').data('rackhe'); - var rackid = thisclick.closest('table').find('th').data('rackid'); - var rackname = thisclick.closest('table').find('th').data('rackname'); - var minhe = 1; - var modal = $(this); - var edit = 0; - modal.find('.alert').text(''); - modal.find('.alert').hide(); - if (rackid === undefined) { - $('#rack-name').val(''); - $('#rack-he').val(''); - var popid = thisclick.data('popid'); - $('#rack-add').data('popid', popid); - $('#rack-update').hide(); - $('#rack-remove').hide(); - $('#rack-add').show(); - } else { - edit = 1; - $('#rack-remove').hide(); - $('#rack-add').hide(); - $('#rack-update').show(); - $('#rack-he').val(rackhe); - $('#rack-name').val(rackname); - for (let i = 1; i <= rackhe; i++) { - if (!thisclick.closest('table').find('tbody').find('tr').eq(i - 1).find('td').eq(1).hasClass('he-free')) { - minhe = i; - } - } - if (minhe === 1) { + var thisclick = $(event.relatedTarget); + var rackhe = thisclick.closest('table').find('th').data('rackhe'); + var rackid = thisclick.closest('table').find('th').data('rackid'); + var rackname = thisclick.closest('table').find('th').data('rackname'); + var minhe = 1; + var modal = $(this); + var edit = 0; + modal.find('.alert').text(''); + modal.find('.alert').hide(); - $('#rack-remove').data('rackid', rackid); - $('#rack-remove').show(); + if (rackid === undefined) { + $('#rack-name').val(''); + $('#rack-he').val(''); + var popid = thisclick.data('popid'); + $('#rack-add').data('popid', popid); + $('#rack-update').hide(); + $('#rack-remove').hide(); + $('#rack-add').show(); + } else { + edit = 1; + $('#rack-remove').hide(); + $('#rack-add').hide(); + $('#rack-update').show(); + $('#rack-he').val(rackhe); + $('#rack-name').val(rackname); + + for (let i = 1; i <= rackhe; i++) { + if (!thisclick.closest('table').find('tbody').find('tr').eq(i - 1).find('td').eq(1).hasClass('he-free')) { + minhe = i; } - $('#rack-update').data('rackid', rackid); - $('#rack-update').data('rackminhe', minhe); } - } - ) - ; - $('#rackModuleModal').on('show.bs.modal', function (event) { + if (minhe === 1) { + $('#rack-remove').data('rackid', rackid); + $('#rack-remove').show(); + } + + $('#rack-update').data('rackid', rackid); + $('#rack-update').data('rackminhe', minhe); + } + }); + + $('#rackModuleModal').on('show.bs.modal', function (event) { trigger = $(event.relatedTarget); var destinationname = trigger.closest('table').find('th').text(); var rackhe = trigger.closest('table').find('th').data('rackhe'); @@ -74,8 +72,6 @@ $(document).ready(function () { modal.find('.modal-title').html('Modul (' + destinationname + ')'); modal.find('.alert').text(''); modal.find('.alert').hide(); - $('#module-slot-div').hide(); - $('#module-position-div').hide(); var options; var selected; var hemaxcount = 1; @@ -84,6 +80,7 @@ $(document).ready(function () { var side = trigger.closest('tbody').data('side'); $('#module-type option').prop('disabled', false); var parent = trigger.closest('tr'); + if (trigger.closest('tr').find('td').eq(1).html() === undefined) { edit = 1; parent = trigger.closest('tr').prev(); @@ -95,6 +92,7 @@ $(document).ready(function () { } } } + if (parent.find('td').eq(1).data('id') || parent.find('td').eq(2).data('id') || parent.find('td').eq(3).data('id') || parent.find('td').eq(4).data('id')) { var counttd = parent.find('td').length - 1; var newmodule = false; @@ -118,10 +116,8 @@ $(document).ready(function () { $('#module-slot').html(options); $('#module-position').html(options); $('#module-slot-div').show(); - } - else - { - + } else { + $('#module-slot-div').hide(); } $('#module-width').attr('disabled', 'disabled'); @@ -138,18 +134,20 @@ $(document).ready(function () { $('#module-update').show(); $('#module-add').hide(); - $('#module-type').val(parent.find('td').eq(1).data('type')).change(); + if ($('#module-type').val() == "1") { $('#module-device-id').hide(); $('#module-device-text').text(parent.find('td').eq(1).data('name')); $('#module-device-text').show(); $('#module-type option').prop('disabled', true); } + if (parent.find('td').eq(1).data('ports') != "") { $('#module-ports').val(parent.find('td').eq(1).data('ports')).change(); $('#module-plug').val(parent.find('td').eq(1).data('plug')); } + $('#module-name').val(parent.find('td').eq(1).data('name')); const status = parent.find('td').eq(1).data('status'); $('#module-type').val(parent.find('td').eq(1).data('type')).change(); @@ -157,7 +155,6 @@ $(document).ready(function () { $('#module-type option[value="1"]').prop('disabled', true); - $('#module-remove').data('moduleid', parent.find('td').eq(1).data('id')); $('#module-update').data('moduleid', parent.find('td').eq(1).data('id')); } else { @@ -184,29 +181,37 @@ $(document).ready(function () { $('#module-remove').hide(); $('#module-update').hide(); $('#module-add').show(); + for (let i = 1; i <= rackhe; i++) { if (i == trigger.data('he')) { selected = 'selected="selected"'; } else { selected = ''; } + if (trigger.closest('tbody').find('tr').eq(i - 1).find('td').eq(1).hasClass('he-free')) { options = options + ''; } + if (hemaxcountactive == 1 && i > trigger.data('he') && !trigger.closest('tbody').find('tr').eq(i - 1).find('td').eq(1).hasClass('he-free')) { hemaxcountactive = 0; } + if (hemaxcountactive == 1 && i > trigger.data('he') && trigger.closest('tbody').find('tr').eq(i - 1).find('td').eq(1).hasClass('he-free')) { hemaxcount++; } } + $('#he-start-div').html(``); options = ""; selected = ""; + for (let i = 1; i <= hemaxcount; i++) { options = options + ''; } + $('#he-count-div').html(``); + if (edit == 0) { $('#module-name-div').show(); $('#module-name').removeAttr('disabled'); @@ -225,9 +230,9 @@ $(document).ready(function () { $('#module-position-div').hide(); $('#module-device-text').hide(); $('#module-device-id').show(); - } } + $('#module-side').val(side); }); @@ -236,7 +241,6 @@ $(document).ready(function () { $('#module-name-div').hide(); $('#module-name').attr('disabled', 'disabled'); $('#module-device-div').show(); - $('#module-device-id').removeAttr('disabled'); $('#module-ports-div').hide(); $('#module-plug-div').hide(); @@ -266,6 +270,7 @@ $(document).ready(function () { var hemaxcountactive = 1 var options; var selected; + for (let i = 1; i <= rackhe; i++) { if (hemaxcountactive == 1 && i > $(this).val() && !trigger.closest('tbody').find('tr').eq(i - 1).find('td').eq(1).hasClass('he-free')) { hemaxcountactive = 0; @@ -275,9 +280,11 @@ $(document).ready(function () { hemaxcount++; } } + for (let i = 1; i <= hemaxcount; i++) { options = options + ''; } + $('#he-count-div').html(``); }); @@ -286,15 +293,14 @@ $(document).ready(function () { var rackid = trigger.closest('table').find('th').data('rackid'); var endhe = parseInt($.trim($('#module-he-start').val())) + parseInt($.trim($('#module-he-count').val())) - 1; - if (!$.trim($('#module-name').val()) && $.trim($('#module-type').val()) != "1" && $.trim($('#module-type').val()) != "0") { error = "Modul Name darf nicht leer sein"; } + if ($.trim($('#module-type').val()) == "1" && !$.trim($('#module-device-id').val())) { error = "Kein Device ausgewählt"; } - if (!error) { $.post(linkAddModule + "&poprack_id=" + rackid, { side: $.trim($('#module-side').val()), @@ -308,14 +314,11 @@ $(document).ready(function () { width: $.trim($('#module-width').val()), status: $.trim($('#module-status').val()), position: $.trim($('#module-position').val()) - }, function (data) { if (data.success === true) { $('#rackModuleModal').modal('toggle'); var currentSide = trigger.closest('tbody').data('side'); - $.get(linkGenerateRack + "&id=" + rackid + "&side=" + currentSide, function (data, status) { - trigger.closest('tbody').html(data); - }); + updateAllRackViews(rackid, currentSide); } }, "json"); } else { @@ -323,9 +326,11 @@ $(document).ready(function () { $(this).closest('.modal').find('.alert').show(); } }); + $("body").on("click", "#module-remove", function () { var moduleid = $(this).data('moduleid'); var rackid = trigger.closest('table').find('th').data('rackid'); + if (confirm("Modul entfernen?")) { let side = trigger.closest('tbody').data('side'); $.post(linkRemoveModule, { @@ -333,21 +338,22 @@ $(document).ready(function () { }, function (data) { if (data.success === true) { $('#rackModuleModal').modal('toggle'); - $.get(linkGenerateRack + "&id=" + rackid + "&side=" + side, function (data, status) { - trigger.closest('tbody').html(data); - }); + updateAllRackViews(rackid, side); } }, "json"); } }); + $("body").on("click", "#module-update", function () { var moduleid = $(this).data('moduleid'); var rackid = trigger.closest('table').find('th').data('rackid'); var error; let side = trigger.closest('tbody').data('side'); + if (!$.trim($('#module-name').val()) && $.trim($('#module-type').val()) != "1" && $.trim($('#module-type').val()) != "0") { error = "Modul Name darf nicht leer sein"; } + if (!error) { $.post(linkUpdateModule, { id: moduleid, @@ -359,9 +365,7 @@ $(document).ready(function () { }, function (data) { if (data.success === true) { $('#rackModuleModal').modal('toggle'); - $.get(linkGenerateRack + "&id=" + rackid + "&side=" + side, function (data, status) { - trigger.closest('tbody').html(data); - }); + updateAllRackViews(rackid, side); } }, "json"); } else { @@ -378,6 +382,7 @@ $(document).ready(function () { if ($('#rack-he').val() < rackmin) { error = "Minimale Höheneinheiten: " + rackmin; } + if ($('#rack-he').val() > 60) { error = "Maximale Höheneinheiten: 60"; } @@ -389,14 +394,15 @@ $(document).ready(function () { if (!$.trim($('#rack-he').val())) { error = "Höheneinheiten darf nicht leer sein"; } + if (!$.trim($('#rack-name').val())) { error = "Schrank Name darf nicht leer sein"; } + if (!error) { $.post(linkEditRack + "&poprack_id=" + rackid, { name: $.trim($('#rack-name').val()), he: $.trim($('#rack-he').val()) - }, function (data) { if (data.success === true) { $('#rackModal').modal('toggle'); @@ -408,6 +414,7 @@ $(document).ready(function () { $(this).closest('.modal').find('.alert').show(); } }); + $("body").on("click", "#rack-add", function () { var popid = $(this).data('popid'); var error; @@ -415,6 +422,7 @@ $(document).ready(function () { if ($('#rack-he').val() < 1) { error = "Minimale Höheneinheiten: " + 1; } + if ($('#rack-he').val() > 60) { error = error = "Maximale Höheneinheiten: 60"; } @@ -426,14 +434,15 @@ $(document).ready(function () { if (!$.trim($('#rack-he').val())) { error = "Höheneinheiten darf nicht leer sein"; } + if (!$.trim($('#rack-name').val())) { error = "Schrank Name darf nicht leer sein"; } + if (!error) { $.post(linkAddRack + "&pop_id=" + popid, { name: $.trim($('#rack-name').val()), he: $.trim($('#rack-he').val()) - }, function (data) { if (data.success === true) { $('#rackModal').modal('toggle'); @@ -457,33 +466,29 @@ $(document).ready(function () { location.reload(); } }, "json"); - }); + $("body").on("change", "#module-width", function () { if ($(this).val() == "12") { $('#module-position-div').hide(); } else if ($(this).val() == "6") { $('#module-position').html(` - -`); + `); $('#module-position-div').show(); } else if ($(this).val() == "4") { $('#module-position').html(` - - `); + `); $('#module-position-div').show(); } else if ($(this).val() == "3") { $('#module-position').html(` - -`); + `); $('#module-position-div').show(); } }); - $("body").on("change", "#module-ports", function () { var plugs = []; if ($(this).find(':selected').data('plugs') !== undefined) { @@ -504,6 +509,7 @@ $(document).ready(function () { $("body").on("change", "#module-slot", function () { $('#module-position').val($(this).val()); var parent = trigger.closest('tr'); + if (trigger.closest('tr').find('td').eq(1).html() === undefined) { parent = trigger.closest('tr').prev(); for (let i = 1; i <= rackhe; i++) { @@ -514,31 +520,35 @@ $(document).ready(function () { } } } + var newmodule = false; var tdnumber = parseInt($(this).val()) + if (parent.find('td').eq(tdnumber).data('id') === undefined) { newmodule = true; } + if (!newmodule) { $('#module-remove').show(); $('#module-update').show(); $('#module-add').hide(); - $('#module-type').val(parent.find('td').eq(tdnumber).data('type')).change(); + if ($('#module-type').val() == "1") { $('#module-device-id').hide(); $('#module-device-text').text(parent.find('td').eq(tdnumber).data('name')); $('#module-device-text').show(); } + if (parent.find('td').eq(tdnumber).data('ports') != "") { $('#module-ports').val(parent.find('td').eq(tdnumber).data('ports')).change(); $('#module-plug').val(parent.find('td').eq(tdnumber).data('plug')); } + $('#module-type').attr('disabled', 'disabled'); $('#module-name').val(parent.find('td').eq(tdnumber).data('name')); - $('#module-remove').data('moduleid', parent.find('td').eq(tdnumber).data('id')); $('#module-update').data('moduleid', parent.find('td').eq(tdnumber).data('id')); } else { @@ -562,6 +572,7 @@ $(document).ready(function () { $('#module-device-text').hide(); } }); + $("body").on("click", ".switch-rack-side", function () { var icon = $(this); var rackTh = icon.closest('th'); @@ -572,10 +583,13 @@ $(document).ready(function () { var currentSide = rackBody.data('side'); var newSide = (currentSide === 'front') ? 'back' : 'front'; + if (icon.hasClass('is-loading')) { return; } + icon.addClass('is-loading fa-spin'); + rackTable.fadeOut(150, function () { $.get(linkGenerateRack + "&id=" + rackId + "&side=" + newSide, function (data, status) { rackBody.html(data); @@ -586,6 +600,7 @@ $(document).ready(function () { } else { sideIndicator.text('- Rückseite'); } + rackTable.fadeIn(150); }).fail(function () { rackBody.html('Fehler beim Laden.'); @@ -595,335 +610,138 @@ $(document).ready(function () { }); }); }); - let currentCableTrigger; - - - $("body").on("click", ".cable-container", function (e) { + $("body").on("click", ".rack-fullscreen-btn", function (e) { e.stopPropagation(); - let parentTd = $(this).closest('td'); - currentCableTrigger = parentTd; - let moduleId = parentTd.data('id'); - let totalPorts = parentTd.data('ports'); - let moduleName = parentTd.data('name'); + var $originalTable = $(this).closest('table'); + var rackName = $(this).closest('th').find('.rack-name').text().trim(); + var $clonedTable = $originalTable.clone(); - $('#cableModal #cable-poprackmodule-id').val(moduleId); - $('#cableModal #port-start, #cableModal #port-end').attr('max', totalPorts); - $('#cableModal #cable-name, #cableModal #port-start, #cableModal #port-end').val(''); - $('#cableModal #cable-modal-alert').hide(); - $('#cableModal .modal-title').text('Kabel auf Panel ' + moduleName); + $clonedTable.find('.move-handle, .switch-rack-side, .fa-edit, .fa-expand').remove(); - $('#cableModal').modal('show'); - }); - - $("body").on("click", "#cable-add-button", function () { - if (!currentCableTrigger) return; - - let rackid = currentCableTrigger.closest('table').find('th').data('rackid'); - let modal = $('#cableModal'); - let alert = modal.find('#cable-modal-alert'); - let data = { - poprackmodule_id: modal.find('#cable-poprackmodule-id').val(), - cable_name: modal.find('#cable-name').val().trim(), - port_start: parseInt(modal.find('#port-start').val()), - port_end: parseInt(modal.find('#port-end').val()), - fiber_start: parseInt(modal.find('#fiber-start').val()) || null, - fiber_end: parseInt(modal.find('#fiber-end').val()) || null, - description: modal.find('#cable-description').val().trim() - }; - - if (!data.cable_name || !data.port_start || !data.port_end) { - alert.text('Bitte alle Felder ausfüllen.').show(); - return; - } - if (data.port_start > data.port_end) { - alert.text('Start-Port muss kleiner oder gleich dem End-Port sein.').show(); - return; - } - if (data.fiber_start && data.fiber_end) { - if (data.fiber_start > data.fiber_end) { - alert.text('Faser-Start muss kleiner oder gleich Faser-Ende sein.').show(); - return; - } - const portCount = data.port_end - data.port_start + 1; - const fiberCount = data.fiber_end - data.fiber_start + 1; - if (fiberCount > portCount) { - alert.text('Die Anzahl der Fasern (' + fiberCount + ') darf nicht größer als die Anzahl der Ports (' + portCount + ') sein.').show(); - return; - } - } - $.post(linkAddCable, data, function (response) { - if (response.success) { - modal.modal('hide'); - let currentSide = currentCableTrigger.closest('tbody').data('side'); - $.get(linkGenerateRack + "&id=" + rackid + "&side=" + currentSide, function (html) { - currentCableTrigger.closest('tbody').html(html); - }); - } else { - alert.text(response.error || 'Fehler beim Speichern.').show(); - } - }, 'json'); - }); - let currentCableElement; - let cableMenuPopper = null; - - function showCableMenuAtEvent(e) { - const $menu = $('#cable-context-menu'); - if (!$menu.length) return; - $menu.removeAttr('x-placement'); - if ($menu.parent()[0] !== document.body) $menu.detach().appendTo('body'); - $menu.css({display: 'block'}).addClass('show'); - const el = $menu[0]; - const w = $menu.outerWidth(), h = $menu.outerHeight(); - const vw = window.innerWidth, vh = window.innerHeight; - - let x = e.clientX, y = e.clientY; - if (x + w > vw) x = vw - w - 6; - if (y + h > vh) y = vh - h - 6; - - el.style.setProperty('position', 'fixed', 'important'); - el.style.setProperty('left', x + 'px', 'important'); - el.style.setProperty('top', y + 'px', 'important'); - el.style.setProperty('z-index', '2147483000', 'important'); - } - - $('body').on('contextmenu click', '.cable-item', function (e) { - if (e.type === 'contextmenu' || e.type === 'click') { - if (e.which !== 1) return; - e.preventDefault(); - e.stopPropagation(); - currentCableElement = $(this); - $('.dropdown-menu').not('#cable-context-menu').removeClass('show').hide(); - showCableMenuAtEvent(e); - requestAnimationFrame(() => { - const el = document.getElementById('cable-context-menu'); - console.log('OPEN fired, ctx styles =', el ? el.style.cssText : 'NO ELEMENT'); - }); - } - }); - - function hideCableMenu() { - const $m = $('#cable-context-menu'); - const el = $m[0]; - if (!$m.length) return; - - $m.removeClass('show').hide(); - - if (el) { - el.style.removeProperty('top'); - el.style.removeProperty('left'); - el.style.removeProperty('position'); - el.style.removeProperty('z-index'); - } - } - - $('#cable-context-menu') - .off('mousedown.ctx click.ctx contextmenu.ctx') - .on('mousedown.ctx click.ctx contextmenu.ctx', function (e) { - e.stopPropagation(); + $clonedTable.css({ + 'width': '800px', + 'margin': '0 auto', + 'background-color': '#fff', + 'box-shadow': 'none' }); - $(document) - .off('mousedown.ctx click.ctx contextmenu.ctx keydown.ctx') - .on('mousedown.ctx click.ctx contextmenu.ctx', function (e) { - if ($(e.target).closest('#cable-context-menu').length) return; - hideCableMenu(); - }) - .on('keydown.ctx', function (e) { - if (e.key === 'Escape' || e.keyCode === 27) hideCableMenu(); - }); - - $(window) - .off('scroll.ctx resize.ctx') - .on('scroll.ctx resize.ctx', hideCableMenu); - - $('#cable-context-menu .dropdown-item').on('click', function (e) { - e.preventDefault(); - e.stopPropagation(); - hideCableMenu(); - - const action = $(this).data('action'); - const rackid = currentCableElement.closest('table').find('th').data('rackid'); - const cableId = currentCableElement.data('cable-id'); - const cableName = currentCableElement.data('cable-name'); - - switch (action) { - case 'view': - alert("Detailansicht für Kabel '" + cableName + "' (ID: " + cableId + ")"); - break; - - case 'edit': { - const cableId = currentCableElement.data('cable-id'); - const cableName = currentCableElement.data('cable-name'); - const portStart = currentCableElement.data('port-start'); - const portEnd = currentCableElement.data('port-end'); - const fiberStart = currentCableElement.data('fiber-start'); - const fiberEnd = currentCableElement.data('fiber-end'); - const description = currentCableElement.data('description'); - $('#edit-cable-id').val(cableId); - $('#edit-cable-name').val(cableName); - $('#edit-port-start').val(portStart); - $('#edit-port-end').val(portEnd); - $('#edit-fiber-start').val(fiberStart); - $('#edit-fiber-end').val(fiberEnd); - $('#edit-cable-description').val(description); - $('#cable-edit-modal-alert').hide(); - $('#cableEditModal').modal('show'); - break; - } - - case 'delete': - if (confirm("Kabel '" + cableName + "' wirklich entfernen?")) { - $.post(linkRemoveCable, {id: cableId}, function (response) { - if (response.success) { - let currentSide = currentCableElement.closest('tbody').data('side'); - $.get(linkGenerateRack + "&id=" + rackid + "&side=" + currentSide, function (html) { - currentCableElement.closest('tbody').html(html); - }); - } else { - alert('Fehler beim Löschen des Kabels.'); - } - }, 'json'); - } - break; - } + $('#overlayTitle').text(rackName); + $('#overlayContent').html($clonedTable); + $('#customRackOverlay').fadeIn(); + $('body').css('overflow', 'hidden'); + $('#overlayContent [data-toggle="popover"]').popover(); }); - - $("body").on("click", "#cable-update-button", function () { - const modal = $('#cableEditModal'); - const cableId = modal.find('#edit-cable-id').val(); - const rackid = currentCableElement.closest('table').find('th').data('rackid'); - - const data = { - id: cableId, - cable_name: modal.find('#edit-cable-name').val().trim(), - port_start: parseInt(modal.find('#edit-port-start').val()), - port_end: parseInt(modal.find('#edit-port-end').val()), - fiber_start: parseInt(modal.find('#edit-fiber-start').val()) || null, - fiber_end: parseInt(modal.find('#edit-fiber-end').val()) || null, - description: modal.find('#edit-cable-description').val().trim() - }; - if (data.fiber_start && data.fiber_end) { - if (data.fiber_start > data.fiber_end) { - modal.find('#cable-edit-modal-alert').text('Faser-Start muss kleiner oder gleich Faser-Ende sein.').show(); - return; - } - const portCount = data.port_end - data.port_start + 1; - const fiberCount = data.fiber_end - data.fiber_start + 1; - if (fiberCount > portCount) { - modal.find('#cable-edit-modal-alert').text('Die Anzahl der Fasern (' + fiberCount + ') darf nicht größer als die Anzahl der Ports (' + portCount + ') sein.').show(); - return; - } - } - - $.post(linkUpdateCable, data, function (response) { - if (response.success) { - modal.modal('hide'); - let currentSide = currentCableElement.closest('tbody').data('side'); - $.get(linkGenerateRack + "&id=" + rackid + "&side=" + currentSide, function (html) { - currentCableElement.closest('tbody').html(html); - }); - } else { - modal.find('#cable-edit-modal-alert').text(response.error || 'Fehler beim Speichern.').show(); - } - }, 'json'); +}); +function updateAllRackViews(rackId, side) { + $.get(linkGenerateRack + "&id=" + rackId + "&side=" + side, function (data) { + var $targets = $('tbody[id="rack-body-' + rackId + '"]'); + $targets.html(data); + $targets.data('side', side); + $targets.find('[data-toggle="popover"]').popover(); }); - - function initCableSearch() { - // Zerstöre eine eventuell bereits existierende Select2-Instanz - if ($('#cable-search').data('select2')) { - $('#cable-search').select2('destroy'); - } - $('#cable-search').empty().append(new Option()); - - // 1. Kabel nach Namen gruppieren und Fasern summieren - let cableGroups = {}; - $('.cable-item').each(function () { - const $cableElement = $(this); - const cableName = $cableElement.data('cable-name'); - - // Überspringe Elemente ohne Kabelnamen - if (!cableName) return; - - // Berechne die Anzahl der Fasern für DIESES Segment - let fiberCount = 0; - const fiberStart = parseInt($cableElement.data('fiber-start')); - const fiberEnd = parseInt($cableElement.data('fiber-end')); - if (!isNaN(fiberStart) && !isNaN(fiberEnd)) { - fiberCount = (fiberEnd - fiberStart) + 1; +} +function printRackOverlay(orientation = 'landscape') { + printJS({ + printable: 'overlayContent', + type: 'html', + targetStyles: ['*'], + style: ` + @page { + size: ${orientation}; + margin: 5mm; + } + + body { + font-family: Arial, sans-serif; + margin: 0; + padding: 5px; + -webkit-print-color-adjust: exact !important; + print-color-adjust: exact !important; } - // Wenn die Gruppe für diesen Namen noch nicht existiert, erstelle sie - if (!cableGroups[cableName]) { - cableGroups[cableName] = { - totalFibers: 0 - }; + * { + color: #000 !important; + text-shadow: none !important; } - // Addiere die Fasern zur Gesamtsumme der Gruppe - cableGroups[cableName].totalFibers += fiberCount; - }); - - // 2. Erstelle die Datenquelle für Select2 aus den gruppierten Daten - let cableSearchData = []; - for (const name in cableGroups) { - let label = `Kabel: ${name}`; - if (cableGroups[name].totalFibers > 0) { - label += ` (Gesamt: ${cableGroups[name].totalFibers} Fasern)`; + table { + width: 100% !important; + border-collapse: collapse !important; + table-layout: fixed !important; + font-size: ${orientation === 'landscape' ? '10px' : '8px'} !important; + } + td, th { + padding: 1px !important; + border: 1px solid #666 !important; + overflow: hidden; } - // Die "id" ist jetzt der eindeutige Kabelname - cableSearchData.push({ - id: name, - text: label - }); - } + .rack-color-lwl { background-color: #81df44 !important; } + .rack-color-lwl-planned { background-color: #ff0000 !important; } + .rack-color-device { background-color: #b6c6f0 !important; } + .rack-color-infra { background-color: #f7b876 !important; } + .rack-color-panel { background-color: #96f3de !important; } + .rack-color-blocked { background-color: #ff0000 !important; } + .rack-color-rpanel { background-color: #58c9f0 !important; } + + .cable-container { + display: flex !important; + flex-direction: row !important; + width: 100% !important; + height: 100% !important; + min-height: 40px; + align-items: stretch !important; + background-color: #fffbe9 !important; + } - // 3. Select2 mit den gruppierten Daten initialisieren - $('#cable-search').select2({ - data: cableSearchData, - placeholder: "Kabelname suchen...", - allowClear: true, - }); - } + .cable-item { + display: flex !important; + flex-direction: column !important; + justify-content: flex-start !important; + align-items: flex-start !important; + box-sizing: border-box !important; + overflow: hidden !important; + padding: 2px !important; + text-align: left !important; + border-right: 1px solid #797979 !important; + background-color: #e1ffdf !important; + } - $('#cable-search').on('select2:select', function (e) { - const selectedData = e.params.data; + .hide-if-narrow { display: block !important; } + + .cable-item b { + font-size: ${orientation === 'landscape' ? '9px' : '7px'} !important; + white-space: normal !important; + line-height: 1.1 !important; + width: 100%; + text-align: center; + display: block; + margin-bottom: 2px; + } - // Die "id" aus der Auswahl ist jetzt der Kabelname - const selectedCableName = selectedData.id; - if (!selectedCableName) return; + .cable-description { + white-space: normal !important; + font-size: ${orientation === 'landscape' ? '8px' : '7px'} !important; + line-height: 1.0 !important; + font-style: italic !important; + width: 100%; + } + + .port-ranges-container { + display: flex !important; + flex-direction: column !important; + font-size: ${orientation === 'landscape' ? '8px' : '7px'} !important; + margin-top: 2px !important; + line-height: 1.0 !important; + width: 100%; + } - // 1. Finde ALLE .cable-item Elemente, die diesen Kabelnamen haben - const $targetElements = $('.cable-item[data-cable-name="' + selectedCableName + '"]'); - - if ($targetElements.length === 0) { - console.error("Keine Kabel-Elemente mit dem Namen '" + selectedCableName + "' gefunden."); - return; - } - - // Alle alten Markierungen entfernen - $('.cable-highlight, .cable-flash').removeClass('cable-highlight cable-flash'); - $('.module-highlight').removeClass('module-highlight'); - - // 2. Scrolle zum ERSTEN gefundenen Element - - - // 3. Hebe ALLE gefundenen Elemente und ihre zugehörigen Module hervor - $targetElements.each(function () { - const $el = $(this); - const $parentModule = $el.closest('td'); - - $el.addClass('cable-highlight cable-flash'); - $parentModule.addClass('module-highlight'); - }); + #overlayContent { width: 100%; display: block; } + + .rack-fullscreen-btn, .switch-rack-side, .move-handle, .fa-edit { + display: none !important; + } + ` }); - $('#cable-search').on('change', function (e) { - if ($(this).val() === null || $(this).val() === '') { - $('.cable-highlight, .cable-flash').removeClass('cable-highlight cable-flash'); - $('.module-highlight').removeClass('module-highlight'); - } - }); - - initCableSearch(); -}); \ No newline at end of file +} \ No newline at end of file