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 +
' Excel-Editor ');
} else {
$('#fiberPlanCableModal .modal-title').html(' Kabel-Details' +
' Excel-Editor ');
}
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 = `
Karte
`;
}
}
let toggleButton = '';
if (fiberStart && fiberEnd) {
toggleButton = `
Alle Fasern anzeigen
`;
}
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})
Stecker
Faser
Bündel
Status
Home ID
Location
Ort
Info
Aktion
`;
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 += `
${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}
`;
});
} else {
html += `
Keine Fasern vorhanden
`;
}
html += `
`;
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(`
`);
$.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 += `
`;
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 += `
`;
});
html += `
`;
container.append(html);
});
}
function loadFiberPath(fiberId) {
const modal = $('#fiberPlanCableModal');
const modalBody = $('#fiberPlanCableModalBody');
modalHistory.push({
type: 'fiber',
data: fiberId
});
modalBody.html(`
`);
$.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
Karte
Zurück
Start-Faser
Kabel:
${fiber.cable_info ? fiber.cable_info.description : '-'}
${fiber.cable_info && fiber.cable_info.cable_route_array && fiber.cable_info.cable_route_array.length > 0 ? `
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 += `
Endpunkt erreicht
${fiber.home_id ? `
Home-ID:
${fiber.home_id}
` : ''}
${fiber.address ? `
Adresse:
${fiber.address}
` : ''}
${fiber.name ? `
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 = `
`;
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 += `
${branchPath.cable.description}
${cableRouteDisplay}
${locationDisplay}
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 += `
Faser #${targetFiber.fiber_nr_cable}
${targetFiber.connector_nr ? 'Stecker ' + targetFiber.connector_nr + ' ' : ''}
Faserfarbe:
Bündel:
${targetFiber.bundle_color_hex ? ` ` : ''}
${targetFiber.bundle_nr || '-'}
${targetFiber.address ? `
Adresse:
${targetFiber.address}
` : ''}
${targetFiber.home_id ? `
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 ? `
Steckertyp:
${targetFiber.customer_connector_type}
` : ''}
${targetFiber.customer_cable_spec ? `
Kabelspezifikation:
${targetFiber.customer_cable_spec}
` : ''}
${targetFiber.customer_fiber_range ? `
Faser-Bereich:
${targetFiber.customer_fiber_range}
` : ''}
${targetFiber.customer_cable_type ? `
Kabeltyp:
${targetFiber.customer_cable_type}
` : ''}
${targetFiber.customer_cable_fiber_nr ? `
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 += `
`;
if (index < cable.cable_route_full.length - 1) {
html += `
`;
}
});
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 += `
`;
}
});
});
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 += `
${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 += `
`;
}
});
const targetFiber = fiber;
if (targetFiber.home_id || targetFiber.address) {
html += `
${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 = `
Lade Fasern...
Alle Schemata
`;
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 = $('', {
value: f.id,
text: `Faser ${f.fiber_nr_cable}`, // Fallback Text
'data-fiber-nr': f.fiber_nr_cable,
'data-fiber-hex': fHex,
'data-fiber-name': fName,
'data-bundle-nr': bNr,
'data-bundle-hex': bHex
});
selector.append(option);
});
// --- NEU: Select2 initialisieren ---
selector.select2({
dropdownParent: $('#fiberRouteMapModal'), // Wichtig damit es im Modal funktioniert
minimumResultsForSearch: 10, // Suche erst ab 10 Einträgen anzeigen
templateResult: formatFiberOption, // Custom HTML für die Liste
templateSelection: formatFiberOption, // Custom HTML für die Auswahl
width: '100%',
escapeMarkup: function(m) { return m; } // HTML erlauben
});
// Event Listener für Wechsel
selector.off('change').on('change', function() {
const selectedFiberId = $(this).val();
loadSingleFiberDetailToMap(selectedFiberId, homeId);
});
// Erste Faser automatisch wählen
if (paths.length > 0) {
selector.val(paths[0].fiber.id).trigger('change');
}
}
/**
* Hilfsfunktion: Baut das HTML für die Select2 Optionen mit Farbpunkten
*/
/**
* Hilfsfunktion: Baut das HTML für die Select2 Optionen
* Zeigt Faser-Nummer, Farb-Punkt und Farb-NAMEN an.
*/
function formatFiberOption(state) {
if (!state.id) {
return state.text;
}
const element = $(state.element);
const fiberNr = element.data('fiber-nr');
const fiberHex = element.data('fiber-hex') || '#cccccc';
let fiberName = element.data('fiber-name'); // Name aus DB (kann leer sein)
const bundleNr = element.data('bundle-nr');
const bundleHex = element.data('bundle-hex') || '#cccccc';
// 1. FALLBACK: Wenn kein Faser-Name da ist, aus Hex-Code berechnen
if (!fiberName && fiberHex) {
// hexToColorName ist in fiber.js vorhanden
if (typeof hexToColorName === 'function') {
fiberName = hexToColorName(fiberHex);
}
}
// Styling für die Punkte
const dotStyle = `display: inline-block; width: 10px; height: 10px; border-radius: 50%; border: 1px solid #666; vertical-align: middle; margin-right: 4px;`;
let html = ``;
// Linke Seite: Faser
html += `
`;
html += ` `;
html += `Faser ${fiberNr} `;
// Name anzeigen (jetzt sicher befüllt durch Fallback)
if (fiberName) {
html += ` (${fiberName}) `;
}
html += `
`;
// Rechte Seite: Bündel
if (bundleNr) {
// Optional: Auch Bündel-Farbe berechnen
let bundleName = '';
if (typeof hexToColorName === 'function' && bundleHex) {
bundleName = hexToColorName(bundleHex);
}
html += `
`;
html += ` `;
html += `Bündel ${bundleNr}`;
if (bundleName) {
html += ` (${bundleName}) `;
}
html += `
`;
}
html += `
`;
return $(html); // Wichtig: Als jQuery-Objekt zurückgeben für Select2
}
function loadSingleFiberDetailToMap(fiberId, homeId) {
if (!fiberId) return;
$('#fiber-route-map').html(`
`);
// Wir senden home_id mit, damit das Backend weiß: "Das ist ein Hausanschluss -> Rückwärts suchen"
const requestData = { fiber_id: fiberId };
if (homeId) {
requestData.home_id = homeId;
}
$.ajax({
url: linkGetFiberPath,
method: 'GET',
data: requestData,
dataType: 'json',
success: function (response) {
if (response.status === 'OK' && response.result.fiber) {
if (response.result.fiber.all_cables) {
window.allCables = response.result.fiber.all_cables;
}
// true = Linework Modus
initializeFiberRouteMap(response.result.fiber, true);
} else {
$('#fiber-route-map').html('Details konnten nicht geladen werden.
');
}
},
error: function () {
$('#fiber-route-map').html('API Fehler.
');
}
});
}
/**
* Berechnet die Segmente für eine Faser (ohne Karte zu zeichnen).
* Extrahiert aus initializeFiberRouteMap.
*/
function calculateSegmentsFromFiber(fiber) {
const allSegments = [];
// 1. Hauptkabel
if (fiber.cable_info && fiber.cable_info.cable_route_full) {
let mainFiberNr = fiber.fiber_nr_cable;
let mainFiberColorHex = fiber.fiber_color_hex;
// ... (hier können weitere Fallbacks für Farben rein, falls nötig)
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_hex: mainFiberColorHex,
bundle_nr: fiber.bundle_nr,
bundle_color_hex: fiber.bundle_color_hex,
location: fiber.location || fiber.cable_info.location,
cable: fiber.cable_info
});
}
// 2. Branch Points (optional, für Schema nicht zwingend, aber gut für Vollständigkeit)
if (fiber.cable_info && fiber.cable_info.branch_points) {
allSegments.push({
name: 'Abzweigpunkte Netzwerk',
stations: fiber.cable_info.branch_points,
type: 'branch_points',
cable: null
});
}
// 3. Rekursive Branch-Strecken sammeln
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,
fiber_nr: targetFiber?.fiber_nr_cable,
fiber_color_hex: targetFiber?.fiber_color_hex,
bundle_nr: targetFiber?.bundle_nr,
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);
}
// Branch Dropouts auf Main mappen (für korrekte Darstellung der Übergänge)
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 && fiber.cable_info && fiber.cable_info.fibers) {
branchSegments.forEach(branchSeg => {
const branchFiber = fiber.cable_info.fibers.find(f =>
f.branch_type === 'Abzweigkabel' &&
f.branch_cable_nr === branchSeg.name
);
if (branchFiber && branchFiber.branch_from_location) {
if (!mainSegment.branch_dropouts) mainSegment.branch_dropouts = [];
mainSegment.branch_dropouts.push(branchFiber.branch_from_location);
}
});
}
}
return allSegments;
}
function renderAllDetailedSchemas(paths) {
const schemaContainer = $('#fiber-route-schema');
schemaContainer.empty();
// 1. Karte aktualisieren (Multi-Map)
// if (typeof initializeMultiFiberMap === 'function') {
// $('#fiber-route-map').html('');
// initializeMultiFiberMap(paths);
// }
// 2. Schemata rendern (Gestapelt)
paths.forEach((path, index) => {
const fiber = path.fiber;
// Berechne Details (ohne Karte)
const segments = calculateSegmentsFromFiber(fiber);
// --- FARBNAMEN BERECHNEN (NEU) ---
let fName = fiber.fiber_color;
// Wenn kein Name da ist oder der Name mit '#' beginnt (also ein Hex-Code ist)
if ((!fName || fName.startsWith('#')) && fiber.fiber_color_hex) {
fName = hexToColorName(fiber.fiber_color_hex);
}
let bName = fiber.bundle_color;
// Gleiches für Bündel
if ((!bName || bName.startsWith('#')) && fiber.bundle_color_hex) {
bName = hexToColorName(fiber.bundle_color_hex);
}
// ----------------------------------
// Header für dieses Schema bauen
const headerHtml = `
Pfad ${index + 1}
Faser ${fiber.fiber_nr_cable}
${fiber.fiber_color_hex ? ` ` : ''}
${fName ? `(${fName}) ` : ''}
Bündel: ${fiber.bundle_nr || '-'}
${fiber.bundle_color_hex ? ` ` : ''}
${bName ? `(${bName})` : ''}
`;
schemaContainer.append(headerHtml);
// Rendere das Detail-Schema in den spezifischen Container
renderFiberPathSchemaLinework(fiber, segments, $(`#schema-container-${fiber.id}`));
});
}
$('#fiberRouteMapModal').on('hidden.bs.modal', function () {
$('#fiber-select-container').remove();
});