let modalHistory = []; let currentCableElement; let cableMenuPopper = null; let currentCableTrigger; $(document).ready(function () { $("body").on("click", ".cable-container", 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'); $('#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); $('#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'); updateAllRackViews(rackid, currentSide); } else { alert.text(response.error || 'Fehler beim Speichern.').show(); } }, 'json'); }); 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'); }); } }); 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(); }); $(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': const fiberStart = currentCableElement.data('fiber-start'); const fiberEnd = currentCableElement.data('fiber-end'); loadFiberPlanCableDetails(cableName, fiberStart, fiberEnd); 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'); updateAllRackViews(rackid, currentSide); } else { alert('Fehler beim Löschen des Kabels.'); } }, 'json'); } break; } }); $("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'); updateAllRackViews(rackid, currentSide); } else { modal.find('#cable-edit-modal-alert').text(response.error || 'Fehler beim Speichern.').show(); } }, 'json'); }); function initCableSearch() { if ($('#cable-search').data('select2')) { $('#cable-search').select2('destroy'); } $('#cable-search').empty().append(new Option()); let cableGroups = {}; $('.cable-item').each(function () { const $cableElement = $(this); const cableName = $cableElement.data('cable-name'); if (!cableName) return; 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; } if (!cableGroups[cableName]) { cableGroups[cableName] = { totalFibers: 0 }; } cableGroups[cableName].totalFibers += fiberCount; }); let cableSearchData = []; for (const name in cableGroups) { let label = `Kabel: ${name}`; if (cableGroups[name].totalFibers > 0) { label += ` (Gesamt: ${cableGroups[name].totalFibers} Fasern)`; } cableSearchData.push({ id: name, text: label }); } $('#cable-search').select2({ data: cableSearchData, placeholder: "Kabelname suchen...", allowClear: true, }); } $('#cable-search').on('select2:select', function (e) { const selectedData = e.params.data; const selectedCableName = selectedData.id; if (!selectedCableName) return; const $targetElements = $('.cable-item[data-cable-name="' + selectedCableName + '"]'); if ($targetElements.length === 0) { return; } $('.cable-highlight, .cable-flash').removeClass('cable-highlight cable-flash'); $('.module-highlight').removeClass('module-highlight'); $targetElements.each(function () { const $el = $(this); const $parentModule = $el.closest('td'); $el.addClass('cable-highlight cable-flash'); $parentModule.addClass('module-highlight'); }); }); $('#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(); }); function loadFiberPlanCableDetails(cableName, fiberStart = null, fiberEnd = null, homeId = null) { if (cableName) { cableName = cableName.split(' ')[0]; } const modal = $('#fiberPlanCableModal'); const modalBody = $('#fiberPlanCableModalBody'); modalHistory.push({ type: 'cable', data: cableName, homeId: homeId }); modalBody.html(`

Lade Kabel-Informationen...

`); modal.modal('show'); const requestData = { cable_name: cableName, network_ids: popNetworkIds // NEU: Network IDs mitsenden }; if (fiberStart !== null && fiberEnd !== null && fiberStart !== '' && fiberEnd !== '') { requestData.fiber_start = fiberStart; requestData.fiber_end = fiberEnd; } $.ajax({ url: linkGetCableDetails, method: 'GET', data: requestData, dataType: 'json', success: function (response) { if (response.result && response.result.cable && response.result.cable.fiber_list) { } if (response.status === 'OK' && response.result.cable) { const cable = response.result.cable; const editBtn = $('#modal-edit-cable-btn'); editBtn.attr('data-cable-id', cable.id); editBtn.attr('data-cable-name', cable.description); editBtn.show(); renderFiberPlanCableDetails(response.result.cable, fiberStart, fiberEnd, homeId); } else { $('#modal-edit-cable-btn').hide(); modalBody.html(`
Kabel "${cableName}" wurde nicht in der Faserplan-Datenbank gefunden.
`); } }, error: function (xhr, status, error) { modalBody.html(`
Keine Faserdetails für dieses Kabel vorhanden.
`); } }); } function renderFiberPlanCableDetails(cable, fiberStart = null, fiberEnd = null, homeId = null) { const modalBody = $('#fiberPlanCableModalBody'); let displayFibers = cable.fiber_list; if (homeId) { displayFibers = displayFibers.filter(f => f.home_id === homeId); $('#fiberPlanCableModal .modal-title').html(' Fasern für Hausanschluss: ' + homeId + ''); } else { $('#fiberPlanCableModal .modal-title').html(' Kabel-Details' + ''); } const usedFibers = cable.fiber_list.filter(f => f.home_id || f.branch_type).length; const freeFibers = cable.fiber_list.filter(f => !f.home_id && !f.branch_type).length; let lastCustomer = null; for (let i = cable.fiber_list.length - 1; i >= 0; i--) { if (cable.fiber_list[i].home_id) { lastCustomer = cable.fiber_list[i]; break; } } let lastLocation = null; for (let i = cable.fiber_list.length - 1; i >= 0; i--) { const fiber = cable.fiber_list[i]; if (fiber.branch_to_location) { lastLocation = fiber.branch_to_location; break; } else if (fiber.address) { lastLocation = fiber.address; break; } } let routeDisplay = '-'; let mapButton = ''; if (cable.cable_route_array && cable.cable_route_array.length > 0) { routeDisplay = cable.cable_route_array.join(' '); if (cable.cable_route_full && cable.cable_route_full.length > 0) { mapButton = ``; } } let toggleButton = ''; if (fiberStart && fiberEnd) { toggleButton = ``; } let html = `
Kabel-Informationen
Bezeichnung: ${cable.description || '-'}
Kabelroute: ${routeDisplay} ${mapButton} ${toggleButton}
Fasern gesamt: ${cable.fibers || '-'}
Durchmesser: ${cable.diameter ? cable.diameter + ' mm' : '-'}
Status: ${getStateText(cable.state)}
Fasern belegt: ${usedFibers} / ${cable.fibers}
Fasern frei: ${freeFibers}
`; html += `
Fasern (${displayFibers.length})
`; if (displayFibers.length > 0) { displayFibers.forEach(fiber => { const colorStyle = fiber.fiber_color_hex ? `style="background-color: ${fiber.fiber_color_hex}"` : ''; const statusBadge = getFiberStatusBadge(fiber); const info = getFiberShortInfo(fiber); const bundleColorStyle = fiber.bundle_color_hex ? `style="background-color: ${fiber.bundle_color_hex}"` : ''; const bundleColorDisplay = fiber.bundle_color_hex ? `${(fiber.bundle_color ? '' : '') || (fiber.bundle_nr ? '' : '-')}` : '-'; // const hasPath = fiber.branch_type === 'Abzweigkabel' || fiber.home_id; const hasPath = fiber.home_id && fiber.home_id !== '' || fiber.final_home_id; const actionButton = hasPath ? `` : '-'; html += ` `; }); } else { html += ` `; } html += `
Stecker Faser Bündel Status Home ID Location Ort Info Aktion
${fiber.connector_nr || '-'} ${fiber.fiber_nr_cable} ${bundleColorDisplay}${fiber.bundle_nr || '-'} ${statusBadge} ${fiber.final_home_id || fiber.home_id || '-'} ${fiber.branch_from_location || fiber.location || '-'} ${fiber.final_location || fiber.branch_to_location || fiber.address || '-'} ${info} ${actionButton}
Keine Fasern vorhanden
`; modalBody.html(html); if (displayFibers.length > 0) { $('#fiber-details-table thead tr').clone(true).addClass('filters').appendTo('#fiber-details-table thead'); $('#fiber-details-table').DataTable({ orderCellsTop: true, fixedHeader: true, paging: false, info: false, lengthChange: false, dom: '<"row"<"col-sm-12"f>>t', order: [[1, 'asc']], language: { search: "Suche:", zeroRecords: "Keine Fasern gefunden", infoEmpty: "Keine Einträge verfügbar", searchPlaceholder: "Suchen..." }, initComplete: function () { var api = this.api(); api.columns().eq(0).each(function (colIdx) { var cell = $('.filters th').eq( $(api.column(colIdx).header()).index() ); var title = $(cell).text(); if (title !== 'Aktion' && title !== '') { $(cell).html(''); } else { $(cell).html(''); } $('input', cell).off('keyup input').on('keyup input', function (e) { e.stopPropagation(); $(this).attr('title', $(this).val()); var regexr = '({search})'; api.column(colIdx) .search( this.value != '' ? regexr.replace('{search}', '(((' + this.value + ')))') : '', this.value != '', this.value == '' ) .draw(); }); }); } }); } } function showCustomerFibers(homeId, cableName) { if (!homeId) { alert('Keine Home-ID vorhanden.'); return; } showAllCustomerFiberRoutes(homeId); } function showAllCustomerFiberRoutes(homeId) { const mapModalHtml = ` `; $('#fiberRouteMapModal').remove(); const fsElement = document.fullscreenElement || document.webkitFullscreenElement || document.msFullscreenElement; if (fsElement) { $(fsElement).append(mapModalHtml); } else { $('body').append(mapModalHtml); } $('#fiberRouteMapModal').modal('show'); $('#fiber-route-map').html(`

Lade Strecken...

`); $.ajax({ url: linkGetAllFiberPathsForHome, method: 'GET', data: {home_id: homeId}, dataType: 'json', success: function (response) { if (response.status === 'OK' && response.result.paths) { initializeMultiFiberMap(response.result.paths); renderMultiCableRouteSchema(response.result.paths); } else { $('#fiber-route-map').html(`
Keine Strecken gefunden.
`); } }, error: function () { $('#fiber-route-map').html(`
Fehler beim Laden der Daten.
`); } }); } function initializeMultiFiberMap(paths) { setTimeout(function () { // 1. Alte Karte sauber entfernen if (window.fiberRouteMapInstance) { window.fiberRouteMapInstance.remove(); window.fiberRouteMapInstance = null; } const mapContainer = document.getElementById('fiber-route-map'); if (!mapContainer) return; mapContainer.innerHTML = ''; // Spinner entfernen const allBounds = L.featureGroup(); let hasData = false; // 2. Neue Karte initialisieren const map = L.map('fiber-route-map'); 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); // 3. Daten durchlaufen und zeichnen paths.forEach((path, pathIndex) => { const fiber = path.fiber; // WICHTIG: Hier holen wir die Kabeldaten. // Entweder aus 'all_cables' (neues PHP) oder 'chain' (altes Fallback) let segmentsToDraw = []; if (fiber && fiber.all_cables) { segmentsToDraw = fiber.all_cables; } else if (path.chain) { segmentsToDraw = path.chain; } // Zufallsfarbe oder basierend auf Index, falls keine Farbe da ist const defaultColor = ['#3388ff', '#ff3333', '#33ff33', '#ffaa00'][pathIndex % 4]; segmentsToDraw.forEach(segment => { let routePoints = []; // Koordinaten parsen if (segment.coordinates) { let coords = segment.coordinates; if (typeof coords === 'string') { try { coords = JSON.parse(coords); } catch (e) {} } if (Array.isArray(coords) && coords.length > 0) { routePoints = coords.map(c => [parseFloat(c.gps_lat), parseFloat(c.gps_long)]); } } if (routePoints.length > 0) { hasData = true; // Linie zeichnen const polyline = L.polyline(routePoints, { color: fiber.fiber_color_hex || defaultColor, weight: 4, opacity: 0.7 }).bindTooltip(` ${segment.description || segment.cable_name || 'Kabel'}
Faser: ${fiber.fiber_nr_cable} (${fiber.fiber_color}) `, { sticky: true }); allBounds.addLayer(polyline); } }); // Stationen (Verteiler/POP) zeichnen, falls vorhanden (im Hauptkabel-Info) if (fiber.cable_info && fiber.cable_info.cable_route_full) { fiber.cable_info.cable_route_full.forEach(station => { if (station.gps_lat && station.gps_long) { const lat = parseFloat(station.gps_lat); const lng = parseFloat(station.gps_long); // Icon Logik let iconColor = '#3388ff'; let iconName = 'circle'; if (station.type === 'pop') { iconColor = '#acf0ab'; iconName = 'village'; } else if (station.object_type == 4) { iconColor = '#FF6B6B'; iconName = 'cross'; } // Abzweig else if (station.object_type == 2) { iconColor = '#ffa726'; iconName = 'square'; } // Schacht const marker = L.marker([lat, lng], { icon: L.MakiMarkers.icon({ icon: iconName, color: iconColor, size: 's' }) }).bindPopup(`${station.name}`); allBounds.addLayer(marker); } }); } }); if (hasData) { allBounds.addTo(map); map.fitBounds(allBounds.getBounds(), { padding: [50, 50] }); } else { // Fallback: Wenn keine Geodaten da sind, leere Karte auf Wien zentrieren map.setView([48.2082, 16.3738], 12); // Optional: Nachricht anzeigen, aber Karte nicht killen // L.popup().setLatLng([48.2082, 16.3738]).setContent("Keine GPS-Daten für diese Fasern.").openOn(map); } }, 300); // Timeout lassen, damit das Modal sicher offen ist } function renderMultiCableRouteSchema(paths) { const container = $('#fiber-route-schema'); container.empty(); if (!paths || paths.length === 0) { container.html('
Keine Daten verfügbar
'); return; } paths.forEach((path, pathIndex) => { const fiber = path.fiber; const chain = path.chain; // Chain is [POP-Side ... Home-Side] let html = `
Faser #${fiber.fiber_nr_cable} (Bündel ${fiber.bundle_nr}) - HomeID: ${fiber.home_id || '?'}
`; // 1. Start Node: POP // Attempt to find POP name from the first segment's route let popName = 'POP'; if (chain.length > 0 && chain[0].cable_route_full) { const popStation = chain[0].cable_route_full.find(s => s.type === 'pop'); if (popStation) popName = popStation.name; } html += `
${popName}
`; chain.forEach((segment, segIndex) => { const isLast = segIndex === chain.length - 1; const segmentColor = segment.fiber_color_hex || '#666'; const fiberInfo = `F${segment.fiber_nr} / B${segment.bundle_nr}`; const lineClass = (segment.type === 'branch') ? 'schema-line-dashed-horizontal' : 'schema-line-horizontal'; // Cable Line html += `
${segment.cable_name}
${fiberInfo}
`; // Next Node let nodeClass = 'station-distributor'; let icon = 'fa-sitemap'; let iconColor = '#3388ff'; let label = 'Verteiler'; if (isLast) { // Destination of the last cable is the Home nodeClass = 'station-customer'; icon = 'fa-home'; iconColor = '#c62828'; label = 'Haus'; if (fiber.address) { label += `
${fiber.address}`; } } else { // Intermediate Node (Distributor/Branch) // We check the next segment to see where this one ends, // or check this segment's destination info if available. // Often the cable route of the current segment ends at the distributor where the next segment starts. // We can try to get the name of the last station of this cable. if (segment.cable_route_full && segment.cable_route_full.length > 0) { const lastStation = segment.cable_route_full[segment.cable_route_full.length - 1]; if (lastStation) { label = lastStation.name; if (lastStation.object_type == 4) { nodeClass = 'station-branch'; icon = 'fa-code-branch'; iconColor = '#b71c1c'; } else if (lastStation.object_type == 2) { nodeClass = 'station-manhole'; icon = 'fa-square'; iconColor = '#e65100'; } } } if (segment.type === 'branch' && nodeClass === 'station-distributor') { // Fallback for branch cable end if not clear nodeClass = 'station-branch'; icon = 'fa-code-branch'; iconColor = '#b71c1c'; label = 'Abzweig'; } } html += `
${label}
`; }); html += `
`; container.append(html); }); } function loadFiberPath(fiberId) { const modal = $('#fiberPlanCableModal'); const modalBody = $('#fiberPlanCableModalBody'); modalHistory.push({ type: 'fiber', data: fiberId }); modalBody.html(`

Lade Faser-Strecke...

`); $.ajax({ url: linkGetFiberPath, method: 'GET', data: {fiber_id: fiberId}, dataType: 'json', success: function (response) { if (response.status === 'OK' && response.result.fiber) { renderFiberPath(response.result.fiber); } else { modalBody.html(`
Faser-Details konnten nicht geladen werden.
`); } }, error: function (xhr, status, error) { modalBody.html(`
Fehler beim Laden der Faser-Details.
`); } }); } function goBackInModal() { modalHistory.pop(); if (modalHistory.length > 0) { const previousState = modalHistory[modalHistory.length - 1]; modalHistory.pop(); if (previousState.type === 'cable') { loadFiberPlanCableDetails(previousState.data); } else if (previousState.type === 'fiber') { loadFiberPath(previousState.data); } } else { $('#fiberPlanCableModal').modal('hide'); } } function renderFiberPath(fiber) { const modalBody = $('#fiberPlanCableModalBody'); const colorStyle = fiber.fiber_color_hex ? `style="background-color: ${fiber.fiber_color_hex}"` : ''; let html = `
Faser-Strecke
Start-Faser
${fiber.cable_info && fiber.cable_info.cable_route_array && fiber.cable_info.cable_route_array.length > 0 ? ` ` : ''}
Kabel: ${fiber.cable_info ? fiber.cable_info.description : '-'}
Kabelroute: ${fiber.cable_info.cable_route_array.join(' ')}
Faser-Nr: ${fiber.fiber_nr_cable}
Farbe:
Stecker: ${fiber.connector_nr || '-'}
Bündel: ${fiber.bundle_color ? ` ${fiber.bundle_nr || '-'} (${fiber.bundle_color}) ` : (fiber.bundle_nr || '-')}
Dropout: ${fiber.branch_from_location || fiber.location || '-'}
Status
Status: ${getFiberStatusBadge(fiber)}
Adresse: ${fiber.address || '-'}
Home-ID: ${fiber.home_id || '-'}
Name: ${fiber.name || '-'}
`; const hasCustomerData = fiber.customer_cable_type || fiber.customer_cable_fiber_nr || fiber.customer_connector_type || fiber.customer_cable_spec || fiber.customer_fiber_range; if (hasCustomerData) { } if (fiber.branch_type === 'Abzweigkabel' && fiber.branch_path) { html += renderBranchPath(fiber.branch_path, 1); } else if (hasCustomerData && fiber.address) { html += `
Kunden-Anschluss
${fiber.home_id ? ` ` : ''} ${fiber.address ? ` ` : ''} ${fiber.name ? ` ` : ''}
Endpunkt erreicht
Home-ID: ${fiber.home_id}
Adresse: ${fiber.address}
Name: ${fiber.name}
${fiber.customer_connector_type || fiber.customer_cable_spec || fiber.customer_fiber_range ? `
Anschluss-Details
${fiber.customer_connector_type ? `Steckertyp: ${fiber.customer_connector_type}
` : ''} ${fiber.customer_cable_spec ? `Kabelspezifikation: ${fiber.customer_cable_spec}
` : ''} ${fiber.customer_fiber_range ? `Faser-Bereich: ${fiber.customer_fiber_range}` : ''}
` : ''}
`; } modalBody.html(html); $('.show-fiber-route-map').on('click', function () { const fiberId = $(this).data('fiber-id'); showFiberRouteMap(fiberId); }); } function renderBranchPath(branchPath, level) { if (!branchPath) return ''; let html = `
Strecken-Verfolgung ${level > 1 ? '- Ebene ' + level : ''}
`; if (branchPath.error) { html += `
${branchPath.error}
`; html += `
`; return html; } if (branchPath.cable) { let cableRouteDisplay = ''; if (branchPath.cable.cable_route_array && branchPath.cable.cable_route_array.length > 0) { cableRouteDisplay = ` Kabelroute: ${branchPath.cable.cable_route_array.join(' ')} `; } let locationDisplay = ''; if (branchPath.cable.location) { locationDisplay = ` Dropout: ${branchPath.cable.location} `; } else if (branchPath.cable.from_location || branchPath.cable.to_location) { locationDisplay = ` Von → Nach: ${branchPath.cable.from_location || '-'}${branchPath.cable.to_location || '-'} `; } html += `
${cableRouteDisplay} ${locationDisplay}
${branchPath.cable.description}
Fasern: ${branchPath.cable.fibers || '-'}
`; } if (branchPath.target_fiber) { const targetFiber = branchPath.target_fiber; const colorStyle = targetFiber.fiber_color_hex ? `style="background-color: ${targetFiber.fiber_color_hex}"` : ''; const bundleColorStyle = targetFiber.bundle_color_hex ? `style="background-color: ${targetFiber.bundle_color_hex}"` : ''; html += `
${targetFiber.address ? ` ` : ''} ${targetFiber.home_id ? ` ` : ''}
Faser #${targetFiber.fiber_nr_cable} ${targetFiber.connector_nr ? 'Stecker ' + targetFiber.connector_nr + '' : ''}
Faserfarbe:
Bündel: ${targetFiber.bundle_color_hex ? `` : ''} ${targetFiber.bundle_nr || '-'}
Adresse: ${targetFiber.address}
Home-ID: ${targetFiber.home_id}
`; const hasCustomerData = targetFiber.customer_cable_type || targetFiber.customer_cable_fiber_nr || targetFiber.customer_connector_type || targetFiber.customer_cable_spec || targetFiber.customer_fiber_range; if (hasCustomerData) { html += `
Kunden-Anschluss
${targetFiber.customer_connector_type ? ` ` : ''} ${targetFiber.customer_cable_spec ? ` ` : ''} ${targetFiber.customer_fiber_range ? ` ` : ''}
Steckertyp: ${targetFiber.customer_connector_type}
Kabelspezifikation: ${targetFiber.customer_cable_spec}
Faser-Bereich: ${targetFiber.customer_fiber_range}
${targetFiber.customer_cable_type ? ` ` : ''} ${targetFiber.customer_cable_fiber_nr ? ` ` : ''}
Kabeltyp: ${targetFiber.customer_cable_type}
Faser-Nr: ${targetFiber.customer_cable_fiber_nr}
`; } } if (branchPath.endpoint && branchPath.endpoint.type === 'customer') { html += `
Endpunkt erreicht
${branchPath.endpoint.home_id ? 'Home-ID: ' + branchPath.endpoint.home_id : ''} ${branchPath.endpoint.address ? ' | Adresse: ' + branchPath.endpoint.address : ''} ${branchPath.endpoint.name ? ' | Name: ' + branchPath.endpoint.name : ''}
`; } html += ``; 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]; } } console.log(foundSourceFiber); if (foundSourceFiber) { console.log("✅ Gefundene Source-Faser (Rohdaten):", foundSourceFiber); console.log("Verfügbare Keys:", Object.keys(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 }; console.log(branchSource); } } } 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, targetContainer = null) { const schemaContainer = targetContainer ? $(targetContainer) : $('#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 isDashed = nextNode.level > 0; const lineClass = isDashed ? 'schema-line-dashed-horizontal' : 'schema-line-horizontal'; const lineStyle = isDashed ? `color: ${lineColor};` : `background-color: ${lineColor}; height: 4px;`; // ÄNDERUNG ENDE 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', '#0070C0': '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); }); }); function openFiberSelectionModal(homeId) { $('#fiberRouteMapModal').modal('show'); const modalHeader = $('#fiberRouteMapModal .modal-header'); // Container für Dropdown und Button erstellen if ($('#fiber-select-container').length === 0) { const controlsHtml = `
`; modalHeader.find('.modal-title').after(controlsHtml); } // Spinner anzeigen $('#fiber-route-map').html('
'); $('#fiber-route-schema').empty(); $.ajax({ url: linkGetAllFiberPathsForHome, method: 'GET', data: { home_id: homeId }, dataType: 'json', success: function (response) { if (response.status === 'OK' && response.result.paths && response.result.paths.length > 0) { const paths = response.result.paths; const count = paths.length; modalHeader.find('.modal-title').html(` ${homeId} ${count} Fasern `); populateFiberDropdown(paths, homeId); // --- KLICK-EVENT FÜR DEN "ALLE" BUTTON --- $('#btn-show-all-details').off('click').on('click', function() { renderAllDetailedSchemas(paths); }); } else { $('#fiber-route-map').html('
Keine Fasern gefunden.
'); } }, error: function () { $('#fiber-route-map').html('
Fehler beim Laden.
'); } }); } function populateFiberDropdown(paths, homeId) { const selector = $('#fiber-path-selector'); // Falls Select2 schon existiert, zerstören um sauber neu zu bauen if (selector.data('select2')) { selector.select2('destroy'); } selector.empty(); paths.forEach((path, index) => { const f = path.fiber; // Farben und Texte vorbereiten const fHex = f.fiber_color_hex || '#cccccc'; const fName = f.fiber_color || ''; const bHex = f.bundle_color_hex || '#cccccc'; const bNr = f.bundle_nr || ''; // Wir speichern die Infos in Data-Attributen für den Formatter const option = $('