4083 lines
187 KiB
JavaScript
4083 lines
187 KiB
JavaScript
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) {
|
|
if (cableName) {
|
|
cableName = cableName.split(' ')[0];
|
|
}
|
|
const modal = $('#fiberPlanCableModal');
|
|
const modalBody = $('#fiberPlanCableModalBody');
|
|
|
|
modalHistory.push({
|
|
type: 'cable',
|
|
data: cableName
|
|
});
|
|
|
|
modalBody.html(`
|
|
<div class="text-center py-5">
|
|
<div class="spinner-border text-primary" role="status"></div>
|
|
<p class="mt-3">Lade Kabel-Informationen...</p>
|
|
</div>
|
|
`);
|
|
|
|
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);
|
|
} else {
|
|
$('#modal-edit-cable-btn').hide();
|
|
modalBody.html(`
|
|
<div class="alert alert-warning">
|
|
<i class="fa fa-exclamation-triangle"></i>
|
|
Kabel "${cableName}" wurde nicht in der Faserplan-Datenbank gefunden.
|
|
</div>
|
|
`);
|
|
}
|
|
},
|
|
error: function (xhr, status, error) {
|
|
modalBody.html(`
|
|
<div class="alert alert-danger">
|
|
<i class="fa fa-exclamation-circle"></i>
|
|
Keine Faserdetails für dieses Kabel vorhanden.
|
|
</div>
|
|
`);
|
|
}
|
|
});
|
|
}
|
|
|
|
function renderFiberPlanCableDetails(cable, fiberStart = null, fiberEnd = null) {
|
|
const modalBody = $('#fiberPlanCableModalBody');
|
|
|
|
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(' <i class="fa fa-arrow-right text-primary"></i> ');
|
|
|
|
if (cable.cable_route_full && cable.cable_route_full.length > 0) {
|
|
mapButton = `<button class="btn btn-sm btn-info ms-2 show-cable-route-map"
|
|
data-cable-id="${cable.id}"
|
|
data-cable-name="${cable.description}"
|
|
title="Route auf Karte anzeigen">
|
|
<i class="fa fa-map-marked-alt"></i> Karte
|
|
</button>`;
|
|
}
|
|
}
|
|
let toggleButton = '';
|
|
if (fiberStart && fiberEnd) {
|
|
toggleButton = `<button class="btn btn-sm btn-secondary ms-2" id="toggle-fiber-view"
|
|
data-cable-name="${cable.description}"
|
|
data-fiber-start="${fiberStart}"
|
|
data-fiber-end="${fiberEnd}"
|
|
data-show-all="false"
|
|
title="Alle Fasern anzeigen">
|
|
<i class="fa fa-eye"></i> Alle Fasern anzeigen
|
|
</button>`;
|
|
}
|
|
|
|
let html = `
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<div class="card cable-info-card mb-3">
|
|
<div class="card-body">
|
|
<h5 class="card-title">
|
|
<i class="fa fa-info-circle"></i> Kabel-Informationen
|
|
</h5>
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<table class="table table-sm table-borderless">
|
|
<tr>
|
|
<th width="40%">Bezeichnung:</th>
|
|
<td><strong>${cable.description || '-'}</strong></td>
|
|
</tr>
|
|
<tr>
|
|
<th>Kabelroute:</th>
|
|
<td>
|
|
${routeDisplay}
|
|
${mapButton}
|
|
${toggleButton}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Fasern gesamt:</th>
|
|
<td>${cable.fibers || '-'}</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Durchmesser:</th>
|
|
<td>${cable.diameter ? cable.diameter + ' mm' : '-'}</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<table class="table table-sm table-borderless">
|
|
<tr>
|
|
<th width="40%">Status:</th>
|
|
<td>${getStateText(cable.state)}</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Fasern belegt:</th>
|
|
<td><span class="badge bg-info">${usedFibers}</span> / ${cable.fibers}</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Fasern frei:</th>
|
|
<td><span class="badge bg-success">${freeFibers}</span></td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
html += `
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<h6 class="mb-3">
|
|
<i class="fa fa-list"></i> Fasern (${cable.fiber_list.length})
|
|
</h6>
|
|
<div class="table-responsive">
|
|
<table id="fiber-details-table" class="table table-sm table-hover table-striped fiber-list-table" style="width: 100%">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th class="text-center">Stecker</th>
|
|
<th class="text-center">Faser</th>
|
|
<th class="text-center">Bündel</th>
|
|
<th class="text-center">Status</th>
|
|
<th class="text-center">Home ID</th>
|
|
<th class="text-center">Location</th>
|
|
<th class="text-center">Ort</th>
|
|
<th class="text-center">Info</th>
|
|
<th class="text-center">Aktion</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
`;
|
|
|
|
if (cable.fiber_list.length > 0) {
|
|
cable.fiber_list.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 ?
|
|
`<span class="fiber-color-badge-modal" ${bundleColorStyle} title="${fiber.bundle_color || 'Bündel ' + (fiber.bundle_nr || '')}"></span>
|
|
<span class="ms-2 small">${(fiber.bundle_color ? '' : '') || (fiber.bundle_nr ? '' : '-')}</span>` :
|
|
'<span class="text-muted">-</span>';
|
|
// const hasPath = fiber.branch_type === 'Abzweigkabel' || fiber.home_id;
|
|
const hasPath = fiber.home_id && fiber.home_id !== '' || fiber.final_home_id ;
|
|
const actionButton = hasPath ?
|
|
`<button class="btn btn-sm btn-outline-info view-fiber-path"
|
|
data-fiber-id="${fiber.id}"
|
|
title="Strecke anzeigen">
|
|
<i class="fa fa-route"></i>
|
|
</button>` :
|
|
'<span class="text-muted">-</span>';
|
|
|
|
html += `
|
|
<tr class="${hasPath ? 'fiber-row-clickable' : ''}" data-fiber-id="${fiber.id}">
|
|
<td>${fiber.connector_nr || '-'}</td>
|
|
<td><span class="fiber-color-badge-modal" ${colorStyle} title="${fiber.fiber_color || 'N/A'}"></span><span class="ml-1"><strong>${fiber.fiber_nr_cable}</strong></span></td>
|
|
<td>${bundleColorDisplay}<span class="ml-1">${fiber.bundle_nr || '-'}</span></td>
|
|
<td>${statusBadge}</td>
|
|
<td class="text-nowrap small">${fiber.final_home_id || fiber.home_id || '-'}</td>
|
|
<td class="small">${fiber.branch_from_location || fiber.location || '-'}</td>
|
|
<td class="small text-nowrap">${fiber.final_location || fiber.branch_to_location || fiber.address || '-'}</td>
|
|
<td class="small text-muted">${info}</td>
|
|
<td class="text-center">${actionButton}</td>
|
|
</tr>
|
|
`;
|
|
});
|
|
} else {
|
|
html += `
|
|
<tr>
|
|
<td colspan="8" class="text-center text-muted">
|
|
<i>Keine Fasern vorhanden</i>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
}
|
|
|
|
html += `
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
modalBody.html(html);
|
|
if (cable.fiber_list.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('<input type="text" class="form-control form-control-sm" placeholder="' + title + '" />');
|
|
} 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 loadFiberPath(fiberId) {
|
|
const modal = $('#fiberPlanCableModal');
|
|
const modalBody = $('#fiberPlanCableModalBody');
|
|
modalHistory.push({
|
|
type: 'fiber',
|
|
data: fiberId
|
|
});
|
|
|
|
modalBody.html(`
|
|
<div class="text-center py-5">
|
|
<div class="spinner-border text-primary" role="status"></div>
|
|
<p class="mt-3">Lade Faser-Strecke...</p>
|
|
</div>
|
|
`);
|
|
|
|
$.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(`
|
|
<div class="alert alert-warning">
|
|
<i class="fa fa-exclamation-triangle"></i>
|
|
Faser-Details konnten nicht geladen werden.
|
|
</div>
|
|
`);
|
|
}
|
|
},
|
|
error: function (xhr, status, error) {
|
|
modalBody.html(`
|
|
<div class="alert alert-danger">
|
|
<i class="fa fa-exclamation-circle"></i>
|
|
Fehler beim Laden der Faser-Details.
|
|
</div>
|
|
`);
|
|
}
|
|
});
|
|
}
|
|
|
|
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 = `
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<div class="card cable-info-card mb-3">
|
|
<div class="card-body">
|
|
<h5 class="card-title">
|
|
<i class="fa fa-route"></i> Faser-Strecke
|
|
</h5>
|
|
<div class="float-end">
|
|
<button class="btn btn-sm btn-info me-2 show-fiber-route-map"
|
|
data-fiber-id="${fiber.id}"
|
|
title="Gesamte Strecke auf Karte anzeigen">
|
|
<i class="fa fa-map-marked-alt"></i> Karte
|
|
</button>
|
|
<button class="btn btn-sm btn-secondary" onclick="goBackInModal()">
|
|
<i class="fa fa-arrow-left"></i> Zurück
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<h6 class="border-bottom pb-2 mb-3">Start-Faser</h6>
|
|
<table class="table table-sm">
|
|
<tr>
|
|
<th width="40%">Kabel:</th>
|
|
<td><strong>${fiber.cable_info ? fiber.cable_info.description : '-'}</strong></td>
|
|
</tr>
|
|
${fiber.cable_info && fiber.cable_info.cable_route_array && fiber.cable_info.cable_route_array.length > 0 ? `
|
|
<tr>
|
|
<th>Kabelroute:</th>
|
|
<td>${fiber.cable_info.cable_route_array.join(' <i class="fa fa-arrow-right text-primary"></i> ')}</td>
|
|
</tr>
|
|
` : ''}
|
|
<tr>
|
|
<th>Faser-Nr:</th>
|
|
<td><strong>${fiber.fiber_nr_cable}</strong></td>
|
|
</tr>
|
|
<tr>
|
|
<th>Farbe:</th>
|
|
<td>
|
|
<span class="fiber-color-badge-modal" ${colorStyle}></span>
|
|
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Stecker:</th>
|
|
<td>${fiber.connector_nr || '-'}</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Bündel:</th>
|
|
<td>
|
|
${fiber.bundle_color ? `
|
|
<span class="fiber-color-badge-modal" style="background-color: ${fiber.bundle_color_hex || '#cccccc'}" title="${fiber.bundle_color}"></span>
|
|
<span class="ms-2">${fiber.bundle_nr || '-'} (${fiber.bundle_color})</span>
|
|
` : (fiber.bundle_nr || '-')}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Dropout:</th>
|
|
<td class="font-weight-bolder">${fiber.branch_from_location || fiber.location || '-'}</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<h6 class="border-bottom pb-2 mb-3">Status</h6>
|
|
<table class="table table-sm">
|
|
<tr>
|
|
<th width="40%">Status:</th>
|
|
<td>${getFiberStatusBadge(fiber)}</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Adresse:</th>
|
|
<td>${fiber.address || '-'}</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Home-ID:</th>
|
|
<td>${fiber.home_id || '-'}</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Name:</th>
|
|
<td>${fiber.name || '-'}</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
`;
|
|
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 += `
|
|
<div class="row mt-3">
|
|
<div class="col-12">
|
|
<div class="card border-success">
|
|
<div class="card-header bg-opacity-10">
|
|
<h6 class="mb-0">
|
|
<i class="fa fa-plug"></i> Kunden-Anschluss
|
|
</h6>
|
|
</div>
|
|
<div class="card-body p-3">
|
|
<div class="table-responsive mb-2">
|
|
<table class="table table-sm table-bordered mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th colspan="2">
|
|
<i class="fa fa-check-circle text-success"></i>
|
|
<strong>Endpunkt erreicht</strong>
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="small">
|
|
${fiber.home_id ? `
|
|
<tr>
|
|
<td class="text-muted" width="30%">Home-ID:</td>
|
|
<td><strong>${fiber.home_id}</strong></td>
|
|
</tr>
|
|
` : ''}
|
|
${fiber.address ? `
|
|
<tr>
|
|
<td class="text-muted">Adresse:</td>
|
|
<td>${fiber.address}</td>
|
|
</tr>
|
|
` : ''}
|
|
${fiber.name ? `
|
|
<tr>
|
|
<td class="text-muted">Name:</td>
|
|
<td>${fiber.name}</td>
|
|
</tr>
|
|
` : ''}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
${fiber.customer_connector_type || fiber.customer_cable_spec || fiber.customer_fiber_range ? `
|
|
<div class="alert alert-success mb-0 py-2">
|
|
<h6 class="alert-heading mb-2" style="font-size: 0.9rem;">
|
|
<i class="fa fa-info-circle"></i> Anschluss-Details
|
|
</h6>
|
|
<div style="font-size: 0.85rem;">
|
|
${fiber.customer_connector_type ? `<strong>Steckertyp:</strong> ${fiber.customer_connector_type}<br>` : ''}
|
|
${fiber.customer_cable_spec ? `<strong>Kabelspezifikation:</strong> ${fiber.customer_cable_spec}<br>` : ''}
|
|
${fiber.customer_fiber_range ? `<strong>Faser-Bereich:</strong> ${fiber.customer_fiber_range}` : ''}
|
|
</div>
|
|
</div>
|
|
` : ''}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
|
|
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 = `
|
|
<div class="row mt-3">
|
|
<div class="col-12">
|
|
<div class="card border-primary">
|
|
<div class="card-header bg-opacity-10">
|
|
<h6 class="mb-0">
|
|
<i class="fa fa-route text-primary"></i>
|
|
Strecken-Verfolgung ${level > 1 ? '- Ebene ' + level : ''}
|
|
</h6>
|
|
</div>
|
|
<div class="card-body p-3">
|
|
`;
|
|
|
|
if (branchPath.error) {
|
|
html += `
|
|
<div class="alert alert-warning mb-0 py-2">
|
|
<i class="fa fa-exclamation-triangle"></i> ${branchPath.error}
|
|
</div>
|
|
`;
|
|
html += `</div></div></div></div>`;
|
|
return html;
|
|
}
|
|
|
|
if (branchPath.cable) {
|
|
let cableRouteDisplay = '';
|
|
if (branchPath.cable.cable_route_array && branchPath.cable.cable_route_array.length > 0) {
|
|
cableRouteDisplay = `
|
|
<tr>
|
|
<td class="text-muted" width="30%">Kabelroute:</td>
|
|
<td>${branchPath.cable.cable_route_array.join(' <i class="fa fa-arrow-right text-primary"></i> ')}</td>
|
|
</tr>
|
|
`;
|
|
}
|
|
let locationDisplay = '';
|
|
if (branchPath.cable.location) {
|
|
locationDisplay = `
|
|
<tr>
|
|
<td class="text-muted" width="30%">Dropout:</td>
|
|
<td><strong>${branchPath.cable.location}</strong></td>
|
|
</tr>
|
|
`;
|
|
} else if (branchPath.cable.from_location || branchPath.cable.to_location) {
|
|
locationDisplay = `
|
|
<tr>
|
|
<td class="text-muted" width="30%">Von → Nach:</td>
|
|
<td><strong>${branchPath.cable.from_location || '-'}</strong> → <strong>${branchPath.cable.to_location || '-'}</strong></td>
|
|
</tr>
|
|
`;
|
|
}
|
|
html += `
|
|
<div class="table-responsive mb-2">
|
|
<table class="table table-sm table-bordered mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th colspan="2" class=" bg-opacity-10">
|
|
<i class="fa fa-arrow-right text-primary"></i>
|
|
<strong>${branchPath.cable.description}</strong>
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="small">
|
|
${cableRouteDisplay}
|
|
${locationDisplay}
|
|
<tr>
|
|
<td class="text-muted">Fasern:</td>
|
|
<td>${branchPath.cable.fibers || '-'}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
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 += `
|
|
<div class="table-responsive mb-2">
|
|
<table class="table table-sm table-bordered mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th colspan="2">
|
|
<span class="fiber-color-badge-modal" ${colorStyle}></span>
|
|
Faser #${targetFiber.fiber_nr_cable}
|
|
${targetFiber.connector_nr ? '<span class="badge bg-secondary ms-2">Stecker ' + targetFiber.connector_nr + '</span>' : ''}
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="small">
|
|
<tr>
|
|
<td class="text-muted" width="30%">Faserfarbe:</td>
|
|
<td>
|
|
<span class="fiber-color-badge-modal" ${colorStyle}></span>
|
|
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="text-muted">Bündel:</td>
|
|
<td>
|
|
${targetFiber.bundle_color_hex ? `<span class="fiber-color-badge-modal" ${bundleColorStyle}></span>` : ''}
|
|
${targetFiber.bundle_nr || '-'}
|
|
|
|
</td>
|
|
</tr>
|
|
${targetFiber.address ? `
|
|
<tr>
|
|
<td class="text-muted">Adresse:</td>
|
|
<td>${targetFiber.address}</td>
|
|
</tr>
|
|
` : ''}
|
|
${targetFiber.home_id ? `
|
|
<tr>
|
|
<td class="text-muted">Home-ID:</td>
|
|
<td><span class="badge bg-success">${targetFiber.home_id}</span></td>
|
|
</tr>
|
|
` : ''}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
`;
|
|
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 += `
|
|
<div class="row mt-3">
|
|
<div class="col-md-12">
|
|
<div class="alert alert-success">
|
|
<h6 class="alert-heading">
|
|
<i class="fa fa-plug"></i> Kunden-Anschluss
|
|
</h6>
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<table class="table table-sm table-borderless mb-0">
|
|
${targetFiber.customer_connector_type ? `
|
|
<tr>
|
|
<th width="40%">Steckertyp:</th>
|
|
<td>${targetFiber.customer_connector_type}</td>
|
|
</tr>
|
|
` : ''}
|
|
${targetFiber.customer_cable_spec ? `
|
|
<tr>
|
|
<th>Kabelspezifikation:</th>
|
|
<td>${targetFiber.customer_cable_spec}</td>
|
|
</tr>
|
|
` : ''}
|
|
${targetFiber.customer_fiber_range ? `
|
|
<tr>
|
|
<th>Faser-Bereich:</th>
|
|
<td>${targetFiber.customer_fiber_range}</td>
|
|
</tr>
|
|
` : ''}
|
|
</table>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<table class="table table-sm table-borderless mb-0">
|
|
${targetFiber.customer_cable_type ? `
|
|
<tr>
|
|
<th width="40%">Kabeltyp:</th>
|
|
<td>${targetFiber.customer_cable_type}</td>
|
|
</tr>
|
|
` : ''}
|
|
${targetFiber.customer_cable_fiber_nr ? `
|
|
<tr>
|
|
<th>Faser-Nr:</th>
|
|
<td>${targetFiber.customer_cable_fiber_nr}</td>
|
|
</tr>
|
|
` : ''}
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
if (branchPath.endpoint && branchPath.endpoint.type === 'customer') {
|
|
html += `
|
|
<div class="alert alert-success mb-0 py-2">
|
|
<div class="d-flex align-items-center">
|
|
<i class="fa fa-check-circle fa-2x me-3"></i>
|
|
<div>
|
|
<strong>Endpunkt erreicht</strong><br>
|
|
<small>
|
|
${branchPath.endpoint.home_id ? '<strong>Home-ID:</strong> ' + branchPath.endpoint.home_id : ''}
|
|
${branchPath.endpoint.address ? ' | <strong>Adresse:</strong> ' + branchPath.endpoint.address : ''}
|
|
${branchPath.endpoint.name ? ' | <strong>Name:</strong> ' + branchPath.endpoint.name : ''}
|
|
</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
html += `</div></div></div></div>`;
|
|
|
|
if (branchPath.next_branch) {
|
|
html += renderBranchPath(branchPath.next_branch, level + 1);
|
|
}
|
|
|
|
return html;
|
|
}
|
|
|
|
function getStateText(state) {
|
|
const states = {
|
|
10: '<span class="badge bg-warning text-dark">Geplant</span>',
|
|
20: '<span class="badge bg-info">In Bau</span>',
|
|
30: '<span class="badge bg-success">Produktiv</span>'
|
|
};
|
|
return states[state] || '<span class="badge bg-secondary">Unbekannt</span>';
|
|
}
|
|
|
|
function getFiberStatusBadge(fiber) {
|
|
if (fiber.home_id) {
|
|
return '<span class="badge bg-success">Aktiv (Kunde)</span>';
|
|
} else if (fiber.branch_type === 'Abzweigkabel') {
|
|
return '<span class="badge bg-warning text-dark">Abzweig</span>';
|
|
} else {
|
|
return '<span class="badge bg-secondary">Frei</span>';
|
|
}
|
|
}
|
|
|
|
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 = `
|
|
<div class="modal fade" id="cableRouteMapModal" tabindex="-1" role="dialog">
|
|
<div class="modal-dialog modal-fullscreen-map" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">
|
|
<i class="fa fa-map-marked-alt"></i>
|
|
Kabelroute: ${cableName}
|
|
</h5>
|
|
<button type="button" class="close" data-dismiss="modal">
|
|
<span>×</span>
|
|
</button>
|
|
</div>
|
|
<div class="modal-body p-0" style="display: flex; flex-direction: column; height: calc(100vh - 120px);">
|
|
<div id="cable-route-map" style="flex: 0 0 70%; width: 100%; min-height: 400px;"></div>
|
|
<div id="cable-route-schema" style="flex: 0 0 auto; max-height: 30%; width: 100%; overflow-y: auto; border-top: 3px solid #007bff; background: #ffffff; padding: 20px;" class="pt-0"></div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
|
Schließen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
$('#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(`
|
|
<div class="alert alert-warning m-3">
|
|
<i class="fa fa-exclamation-triangle"></i>
|
|
Keine Daten für diese Route verfügbar.
|
|
</div>
|
|
`);
|
|
}
|
|
},
|
|
error: function () {
|
|
$('#cable-route-map').html(`
|
|
<div class="alert alert-danger m-3">
|
|
<i class="fa fa-exclamation-circle"></i>
|
|
Fehler beim Laden der Kartendaten.
|
|
</div>
|
|
`);
|
|
}
|
|
});
|
|
}
|
|
|
|
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(`
|
|
<div class="alert alert-warning m-3">
|
|
<i class="fa fa-exclamation-triangle"></i>
|
|
Keine GPS-Koordinaten für diese Kabelroute verfügbar.
|
|
</div>
|
|
`);
|
|
return;
|
|
}
|
|
|
|
const validStations = cable.cable_route_full.filter(station =>
|
|
station.gps_lat && station.gps_long
|
|
);
|
|
|
|
if (validStations.length === 0) {
|
|
$('#cable-route-map').html(`
|
|
<div class="alert alert-warning m-3">
|
|
<i class="fa fa-exclamation-triangle"></i>
|
|
Keine gültigen GPS-Koordinaten gefunden.
|
|
</div>
|
|
`);
|
|
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(`
|
|
<strong>${index + 1}. ${station.name}</strong><br>
|
|
<small>Typ: ${station.type === 'pop' ? 'POP' : 'Verteiler'}</small><br>
|
|
<small>${lat.toFixed(5)}, ${lng.toFixed(5)}</small>
|
|
`);
|
|
|
|
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 = `
|
|
<div style="min-width: 300px; max-height: 400px; overflow-y: auto;">
|
|
<h6><strong>${cable.description}</strong></h6>
|
|
<hr class="my-2">
|
|
<div class="mb-2">
|
|
<small class="text-muted">Fasern: ${cable.fibers} | Durchmesser: ${cable.diameter}mm</small>
|
|
</div>
|
|
`;
|
|
|
|
Object.keys(fibersByBundle).forEach(bundleKey => {
|
|
const bundle = fibersByBundle[bundleKey];
|
|
const bundleColor = bundle.bundle_color_hex || '#cccccc';
|
|
|
|
popupContent += `
|
|
<div class="border rounded p-2 mb-2" style="background-color: rgba(0,0,0,0.03);">
|
|
<div class="d-flex align-items-center mb-1">
|
|
<span style="display: inline-block; width: 20px; height: 20px; border-radius: 50%; background-color: ${bundleColor}; border: 2px solid #333; margin-right: 8px;"></span>
|
|
<strong>Bündel ${bundle.bundle_nr || '-'}</strong>
|
|
${bundle.bundle_color ? `<span class="badge badge-secondary ms-2">${bundle.bundle_color}</span>` : ''}
|
|
</div>
|
|
<div style="display: flex; flex-wrap: wrap; gap: 4px;">
|
|
`;
|
|
|
|
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 += `
|
|
<div style="display: inline-flex; align-items: center; padding: 2px 6px; background: white; border: 1px solid #ddd; border-radius: 3px; font-size: 11px;">
|
|
<span style="display: inline-block; width: 12px; height: 12px; border-radius: 50%; background-color: ${fiberColor}; border: 1px solid #666; margin-right: 4px;"></span>
|
|
<span style="font-weight: bold;">${fiber.fiber_nr_cable}</span>
|
|
${fiber.fiber_nr_bundle ? `<span style="color: #666; margin-left: 2px;">:${fiber.fiber_nr_bundle}</span>` : ''}
|
|
<span style="width: 6px; height: 6px; border-radius: 50%; background-color: ${statusColor}; margin-left: 4px;" title="${status}"></span>
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
popupContent += `
|
|
</div>
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
popupContent += `</div>`;
|
|
|
|
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(`
|
|
<div style="min-width: 200px;">
|
|
<h6><i class="fa fa-home"></i> Hausanschluss</h6>
|
|
<hr style="margin: 5px 0;">
|
|
<strong>Home-ID:</strong> ${fiber.home_id}<br>
|
|
<strong>Adresse:</strong> ${fiber.address}<br>
|
|
${fiber.name ? `<strong>Name:</strong> ${fiber.name}<br>` : ''}
|
|
<hr style="margin: 5px 0;">
|
|
<strong>Faser:</strong> #${fiber.fiber_nr_cable}
|
|
${fiber.fiber_color ? ` (${fiber.fiber_color})` : ''}<br>
|
|
${fiber.bundle_nr ? `<strong>Bündel:</strong> ${fiber.bundle_nr}` : ''}
|
|
${fiber.bundle_color ? ` (${fiber.bundle_color})` : ''}
|
|
</div>
|
|
`).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 = '<strong>Legende</strong><br>';
|
|
html += '<i class="fa fa-circle" style="color: #acf0ab; margin-right: 5px;"></i> POP<br>';
|
|
html += '<i class="fa fa-circle" style="color: #abbaf0; margin-right: 5px;"></i> Verteiler<br>';
|
|
html += '<hr style="margin: 5px 0;">';
|
|
html += '<strong>Status:</strong><br>';
|
|
html += '<span style="display: inline-block; width: 10px; height: 10px; border-radius: 50%; background-color: #28a745; margin-right: 5px;"></span> Belegt<br>';
|
|
html += '<span style="display: inline-block; width: 10px; height: 10px; border-radius: 50%; background-color: #ffc107; margin-right: 5px;"></span> Abzweig<br>';
|
|
html += '<span style="display: inline-block; width: 10px; height: 10px; border-radius: 50%; background-color: #6c757d; margin-right: 5px;"></span> Frei<br>';
|
|
|
|
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('<div class="text-center text-muted">Keine Routen-Informationen verfügbar</div>');
|
|
return;
|
|
}
|
|
|
|
let html = '<div class="fiber-path-schema-horizontal">';
|
|
html += '<h6 class="p-0"><i class="fa fa-project-diagram"></i> Schematischer Kabelverlauf</h6>';
|
|
html += '<div class="schema-flow-horizontal">';
|
|
|
|
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 = `
|
|
<div style="min-width: 200px;">
|
|
<h6><strong>${station.name}</strong></h6>
|
|
<span class="badge ${badgeClass}">${style.typeLabel}</span>
|
|
<hr style="margin: 8px 0;">
|
|
<div style="font-size: 11px;">
|
|
<strong>Position:</strong> ${index + 1}<br>
|
|
<small class="text-muted">${station.gps_lat ? parseFloat(station.gps_lat).toFixed(5) + ', ' + parseFloat(station.gps_long).toFixed(5) : 'Kein GPS'}</small>
|
|
</div>
|
|
</div>`;
|
|
|
|
html += `
|
|
<div class="schema-node-horizontal ${style.cssClass} schema-station-clickable"
|
|
data-popup-content="${popupContent.replace(/"/g, '"')}"
|
|
data-station-name="${station.name}">
|
|
<div class="schema-node-icon-horizontal" style="color: ${style.color};">
|
|
<i class="fa ${style.icon}"></i>
|
|
</div>
|
|
<div class="schema-node-label-horizontal">
|
|
<strong>${station.name}</strong>
|
|
</div>
|
|
</div>
|
|
`;
|
|
if (index < cable.cable_route_full.length - 1) {
|
|
html += `
|
|
<div class="schema-connection-horizontal">
|
|
<div class="schema-line-horizontal" style="background-color: #666; height: 4px;">
|
|
<div class="schema-cable-label">
|
|
<strong>${cable.description}</strong><br>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
});
|
|
|
|
html += '</div></div>';
|
|
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: `
|
|
<div class="popover station-schema-popover" role="tooltip">
|
|
<div class="arrow"></div>
|
|
<h3 class="popover-header"></h3>
|
|
<div class="popover-body"></div>
|
|
</div>
|
|
`
|
|
});
|
|
$('.schema-station-clickable').not($station).popover('hide');
|
|
$station.popover('show');
|
|
});
|
|
}
|
|
function showFiberRouteMap(fiberId) {
|
|
const mapModalHtml = `
|
|
<div class="modal fade" id="fiberRouteMapModal" tabindex="-1" role="dialog">
|
|
<div class="modal-dialog modal-fullscreen-map" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">
|
|
<i class="fa fa-map-marked-alt"></i>
|
|
Komplette Faser-Strecke
|
|
</h5>
|
|
<button type="button" class="close" data-dismiss="modal">
|
|
<span>×</span>
|
|
</button>
|
|
</div>
|
|
<div class="modal-body p-0" style="display: flex; flex-direction: column; height: calc(100vh - 120px);">
|
|
<!-- ÄNDERUNG: Map nimmt 70% der Höhe -->
|
|
<div id="fiber-route-map" style="flex: 0 0 70%; width: 100%; min-height: 400px;"></div>
|
|
<!-- ÄNDERUNG: Schema unter der Map mit automatischer Höhe, scrollbar -->
|
|
<div id="fiber-route-schema" style="flex: 0 0 auto; max-height: 30%; width: 100%; overflow-y: auto; border-top: 3px solid #007bff; background: #ffffff; padding: 20px;" class="pt-0"></div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
|
Schließen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
$('#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(`
|
|
<div class="alert alert-warning m-3">
|
|
<i class="fa fa-exclamation-triangle"></i>
|
|
Keine GPS-Daten für diese Strecke verfügbar.
|
|
</div>
|
|
`);
|
|
}
|
|
},
|
|
error: function () {
|
|
$('#fiber-route-map').html(`
|
|
<div class="alert alert-danger m-3">
|
|
<i class="fa fa-exclamation-circle"></i>
|
|
Fehler beim Laden der Kartendaten.
|
|
</div>
|
|
`);
|
|
}
|
|
});
|
|
}
|
|
|
|
function findNearestBranchPoint(customerLat, customerLng, allSegments) {
|
|
let nearestBranchPoint = null;
|
|
let nearestDispatcher = null;
|
|
let minBranchDistance = Infinity;
|
|
let minDispatcherDistance = Infinity;
|
|
allSegments.forEach((segment, segIdx) => {
|
|
if (segment.stations && Array.isArray(segment.stations)) {
|
|
segment.stations.forEach((station, stationIdx) => {
|
|
if (!station.gps_lat || !station.gps_long) return;
|
|
const stationLat = parseFloat(station.gps_lat);
|
|
const stationLng = parseFloat(station.gps_long);
|
|
const distance = Math.sqrt(
|
|
Math.pow(customerLat - stationLat, 2) +
|
|
Math.pow(customerLng - stationLng, 2)
|
|
);
|
|
if (station.object_type == 4 || station.object_type === '4') {
|
|
if (distance < minBranchDistance) {
|
|
minBranchDistance = distance;
|
|
nearestBranchPoint = {
|
|
lat: stationLat,
|
|
lng: stationLng,
|
|
name: station.name || 'Abzweigpunkt',
|
|
station: station,
|
|
type: 'branch_point'
|
|
};
|
|
}
|
|
}
|
|
else if (station.type === 'dispatcher' && (station.object_type == 1 || station.object_type == 2 || station.object_type === '1' || station.object_type === '2')) {
|
|
if (distance < minDispatcherDistance) {
|
|
minDispatcherDistance = distance;
|
|
nearestDispatcher = {
|
|
lat: stationLat,
|
|
lng: stationLng,
|
|
name: station.name || 'Verteiler',
|
|
station: station,
|
|
type: 'dispatcher'
|
|
};
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
if (nearestBranchPoint) {
|
|
return nearestBranchPoint;
|
|
}
|
|
else if (nearestDispatcher) {
|
|
return nearestDispatcher;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function getDistanceToPolyline(pointLat, pointLng, polylineCoords) {
|
|
let minDistance = Infinity;
|
|
for (let i = 0; i < polylineCoords.length - 1; i++) {
|
|
const segmentStart = polylineCoords[i];
|
|
const segmentEnd = polylineCoords[i + 1];
|
|
const distance = pointToSegmentDistance(
|
|
pointLat, pointLng,
|
|
segmentStart[0], segmentStart[1],
|
|
segmentEnd[0], segmentEnd[1]
|
|
);
|
|
|
|
if (distance < minDistance) {
|
|
minDistance = distance;
|
|
}
|
|
}
|
|
|
|
return minDistance;
|
|
}
|
|
|
|
function pointToSegmentDistance(px, py, x1, y1, x2, y2) {
|
|
const dx = x2 - x1;
|
|
const dy = y2 - y1;
|
|
|
|
if (dx === 0 && dy === 0) {
|
|
return Math.sqrt(Math.pow(px - x1, 2) + Math.pow(py - y1, 2));
|
|
}
|
|
|
|
const t = Math.max(0, Math.min(1,
|
|
((px - x1) * dx + (py - y1) * dy) / (dx * dx + dy * dy)
|
|
));
|
|
|
|
const nearestX = x1 + t * dx;
|
|
const nearestY = y1 + t * dy;
|
|
|
|
return Math.sqrt(Math.pow(px - nearestX, 2) + Math.pow(py - nearestY, 2));
|
|
}
|
|
|
|
function initializeFiberRouteMap(fiber, isLinework = false) {
|
|
setTimeout(function () {
|
|
const mapContainer = document.getElementById('fiber-route-map');
|
|
const allSegments = [];
|
|
const allStations = [];
|
|
|
|
if (fiber.cable_info && fiber.cable_info.cable_route_full) {
|
|
let mainFiberNr = fiber.fiber_nr_cable;
|
|
let mainFiberColor = fiber.fiber_color;
|
|
let mainFiberColorHex = fiber.fiber_color_hex;
|
|
let mainBundleNr = fiber.bundle_nr;
|
|
let mainBundleColor = fiber.bundle_color;
|
|
let mainBundleColorHex = fiber.bundle_color_hex;
|
|
let branchSource = null;
|
|
|
|
if (fiber.branch_path && fiber.branch_path.source_fiber) {
|
|
branchSource = fiber.branch_path.source_fiber;
|
|
} else if (fiber.cable_info && fiber.cable_info.branch_path && fiber.cable_info.branch_path.source_fiber) {
|
|
branchSource = fiber.cable_info.branch_path.source_fiber;
|
|
}
|
|
if (isLinework && !branchSource && fiber.branch_path && fiber.branch_path.cable && fiber.cable_info.fibers) {
|
|
const firstBranchCableName = fiber.branch_path.cable.description || fiber.branch_path.cable.name;
|
|
const targetFiberNrInBranch = fiber.fiber_nr_cable;
|
|
|
|
if (firstBranchCableName) {
|
|
const candidates = fiber.cable_info.fibers.filter(f =>
|
|
f.branch_cable_nr === firstBranchCableName ||
|
|
(f.branch_path && f.branch_path.cable && f.branch_path.cable.name === firstBranchCableName)
|
|
);
|
|
|
|
let foundSourceFiber = null;
|
|
|
|
if (candidates.length > 0) {
|
|
foundSourceFiber = candidates.find(f =>
|
|
f.branch_fiber_nr == targetFiberNrInBranch ||
|
|
f.branch_fiber_nr_cable == targetFiberNrInBranch
|
|
);
|
|
if (!foundSourceFiber) {
|
|
// console.log("⚠️ Kein exakter Faser-Match (z.B. auf Branch-Faser " + targetFiberNrInBranch + "), nehme ersten Kandidaten.");
|
|
foundSourceFiber = candidates[0];
|
|
}
|
|
}
|
|
|
|
if (foundSourceFiber) {
|
|
const correctFiberNr = foundSourceFiber.fiber_nr_cable || foundSourceFiber.fiber_nr;
|
|
|
|
// console.log("🔧 Hauptkabel-Faser gefunden:", correctFiberNr, foundSourceFiber);
|
|
|
|
branchSource = {
|
|
fiber_nr_cable: correctFiberNr,
|
|
fiber_color: foundSourceFiber.color_name || foundSourceFiber.fiber_color || foundSourceFiber.color,
|
|
fiber_color_hex: foundSourceFiber.color_hex || foundSourceFiber.fiber_color_hex || foundSourceFiber.color,
|
|
bundle_nr: foundSourceFiber.bundle_nr || foundSourceFiber.bundle,
|
|
bundle_color: foundSourceFiber.bundle_color || foundSourceFiber.bundle_color_name,
|
|
bundle_color_hex: foundSourceFiber.bundle_color_hex
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
if (branchSource) {
|
|
mainFiberNr = branchSource.fiber_nr_cable;
|
|
mainFiberColor = branchSource.fiber_color;
|
|
mainFiberColorHex = branchSource.fiber_color_hex;
|
|
mainBundleNr = branchSource.bundle_nr;
|
|
mainBundleColor = branchSource.bundle_color;
|
|
mainBundleColorHex = branchSource.bundle_color_hex;
|
|
// console.log("✅ Hauptkabel-Daten erfolgreich korrigiert auf Faser:", mainFiberNr);
|
|
} else {
|
|
// console.warn("⚠️ Konnte Quell-Faser nicht ermitteln (Fallback auf End-Faser).");
|
|
}
|
|
|
|
allSegments.push({
|
|
name: fiber.cable_info.description,
|
|
stations: fiber.cable_info.cable_route_full,
|
|
color: mainFiberColorHex || '#3388ff',
|
|
level: 0,
|
|
type: 'main',
|
|
fiber_nr: mainFiberNr,
|
|
fiber_color: mainFiberColor,
|
|
fiber_color_hex: mainFiberColorHex,
|
|
bundle_nr: mainBundleNr,
|
|
bundle_color: mainBundleColor,
|
|
bundle_color_hex: mainBundleColorHex,
|
|
location: fiber.location || fiber.cable_info.location,
|
|
cable: fiber.cable_info
|
|
});
|
|
}
|
|
|
|
if (fiber.cable_info && fiber.cable_info.branch_points && Array.isArray(fiber.cable_info.branch_points) && fiber.cable_info.branch_points.length > 0) {
|
|
allSegments.push({
|
|
name: 'Abzweigpunkte Netzwerk',
|
|
stations: fiber.cable_info.branch_points,
|
|
color: '#FF6B6B',
|
|
level: 0,
|
|
type: 'branch_points',
|
|
cable: null
|
|
});
|
|
} else {
|
|
}
|
|
|
|
function collectBranchRoutes(branchPath, level = 1) {
|
|
if (!branchPath || branchPath.error) {
|
|
return;
|
|
}
|
|
if (branchPath.cable && branchPath.cable.cable_route_full) {
|
|
const targetFiber = branchPath.target_fiber;
|
|
const fiberColor = targetFiber?.fiber_color_hex || getBranchColor(level);
|
|
|
|
allSegments.push({
|
|
name: branchPath.cable.description,
|
|
stations: branchPath.cable.cable_route_full,
|
|
color: fiberColor,
|
|
level: level,
|
|
type: 'branch',
|
|
location: branchPath.cable.location,
|
|
cable: branchPath.cable,
|
|
coordinates: branchPath.cable.coordinates,
|
|
cable_info: branchPath.cable.cable_info || branchPath.cable,
|
|
fiber_nr: targetFiber?.fiber_nr_cable,
|
|
fiber_color: targetFiber?.fiber_color,
|
|
fiber_color_hex: targetFiber?.fiber_color_hex,
|
|
bundle_nr: targetFiber?.bundle_nr,
|
|
bundle_color: targetFiber?.bundle_color,
|
|
bundle_color_hex: targetFiber?.bundle_color_hex,
|
|
target_fiber: targetFiber
|
|
});
|
|
}
|
|
|
|
if (branchPath.next_branch) {
|
|
collectBranchRoutes(branchPath.next_branch, level + 1);
|
|
}
|
|
}
|
|
|
|
if (fiber.branch_path) {
|
|
collectBranchRoutes(fiber.branch_path);
|
|
}
|
|
if (allSegments.length > 1) {
|
|
const mainSegment = allSegments.find(seg => seg.type === 'main');
|
|
const branchSegments = allSegments.filter(seg => seg.type === 'branch');
|
|
|
|
if (mainSegment && branchSegments.length > 0) {
|
|
branchSegments.forEach(branchSeg => {
|
|
if (fiber.cable_info && fiber.cable_info.fibers) {
|
|
const branchFiber = fiber.cable_info.fibers.find(f =>
|
|
f.branch_type === 'Abzweigkabel' &&
|
|
f.branch_cable_nr === branchSeg.name
|
|
);
|
|
|
|
if (branchFiber && branchFiber.branch_from_location) {
|
|
branchSeg.branch_from_location_main = branchFiber.branch_from_location;
|
|
if (!mainSegment.branch_dropouts) {
|
|
mainSegment.branch_dropouts = [];
|
|
}
|
|
mainSegment.branch_dropouts.push(branchFiber.branch_from_location);
|
|
} else {
|
|
}
|
|
}
|
|
});
|
|
|
|
if (mainSegment.branch_dropouts && mainSegment.branch_dropouts.length > 0) {
|
|
}
|
|
}
|
|
}
|
|
if (allSegments.length === 0) {
|
|
$('#fiber-route-map').html(`
|
|
<div class="alert alert-warning m-3">
|
|
<i class="fa fa-exclamation-triangle"></i>
|
|
Keine GPS-Koordinaten für diese Faser-Strecke verfügbar.
|
|
</div>
|
|
`);
|
|
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(`
|
|
<div class="alert alert-warning m-3">
|
|
<i class="fa fa-exclamation-triangle"></i>
|
|
Keine gültigen GPS-Koordinaten gefunden.
|
|
</div>
|
|
`);
|
|
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 = '<hr style="margin: 8px 0;"><div style=" border: 1px solid #d1dcff; padding: 8px; border-radius: 4px; margin-top: 8px;">';
|
|
branchConnectionsHtml += '<h6 style="margin: 0 0 8px 0; color: #000000;"><i class="fa fa-code-branch" style="color:#fff"></i> Faser-Übergänge:</h6>';
|
|
|
|
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 += `
|
|
<div style="background: white; padding: 6px; margin-bottom: 6px; border-left: 3px solid #FF6B6B; font-size: 10px;">
|
|
<strong style="color: #000000;">${junctionType} (Level ${conn.level})</strong><br>
|
|
<div style="margin-top: 4px;">
|
|
<strong>Von:</strong> ${conn.fromCable}<br>
|
|
<span style="padding-left: 10px;">└─ Faser ${conn.fromFiber || '?'}</span>
|
|
${conn.fromFiberColorHex ? ` <span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: ${conn.fromFiberColorHex}; border: 1px solid #666;"></span> ${fromFiberColorName}` : ''}<br>
|
|
${conn.fromBundle ? `<span style="padding-left: 10px;">└─ Bündel ${conn.fromBundle}</span>` : ''}
|
|
${conn.fromBundleColorHex ? ` <span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: ${conn.fromBundleColorHex}; border: 1px solid #666;"></span> ${fromBundleColorName}` : ''}
|
|
${conn.fromBundle ? '<br>' : ''}
|
|
</div>
|
|
<div style="margin-top: 4px; padding-top: 4px; border-top: 1px dashed #ddd;">
|
|
<strong>Nach:</strong> ${conn.toCable}<br>
|
|
<span style="padding-left: 10px;">└─ Faser ${conn.toFiber || '?'}</span>
|
|
${conn.toFiberColorHex ? ` <span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: ${conn.toFiberColorHex}; border: 1px solid #666;"></span> ${toFiberColorName}` : ''}<br>
|
|
${conn.toBundle ? `<span style="padding-left: 10px;">└─ Bündel ${conn.toBundle}</span>` : ''}
|
|
${conn.toBundleColorHex ? ` <span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: ${conn.toBundleColorHex}; border: 1px solid #666;"></span> ${toBundleColorName}` : ''}
|
|
${conn.toBundle ? '<br>' : ''}
|
|
</div>
|
|
</div>
|
|
`;
|
|
});
|
|
branchConnectionsHtml += '</div>';
|
|
}
|
|
|
|
const popupContent = `
|
|
<div style="min-width: 250px;">
|
|
<h6><strong>${station.name}</strong></h6>
|
|
<span class="badge badge-${station.type === 'pop' ? 'success' : (isBranchJunction ? 'danger' : 'info')}">
|
|
${station.type === 'pop' ? 'POP' : (isBranchJunction ? 'Branch-Verbindung' : (station.object_type == 2 || station.object_type === '2' ? 'Schacht' : 'Verteiler'))}
|
|
</span>
|
|
${isBranchJunction ? '<span class="badge badge-warning ml-1">' + branchConnections.length + ' Übergänge</span>' : ''}
|
|
<hr style="margin: 8px 0;">
|
|
<div style="font-size: 11px;">
|
|
<strong>Kabel:</strong> ${segment.name}<br>
|
|
${segment.fiber_nr ? `<strong>Faser:</strong> ${segment.fiber_nr}` : ''}
|
|
${segment.fiber_color ? ` (${segment.fiber_color})` : ''}<br>
|
|
${segment.bundle_nr ? `<strong>Bündel:</strong> ${segment.bundle_nr}` : ''}
|
|
${segment.bundle_color ? ` (${segment.bundle_color})` : ''}<br>
|
|
<strong>Level:</strong> ${segment.level}<br>
|
|
<small class="text-muted">${lat.toFixed(5)}, ${lng.toFixed(5)}</small>
|
|
</div>
|
|
${branchConnectionsHtml}
|
|
</div>
|
|
`;
|
|
|
|
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 = `<div style="text-align: left;">
|
|
<strong>${segment.name}</strong><br>
|
|
${segment.type === 'branch' ? `<small>(Abzweig Ebene ${segment.level})</small><br>` : ''}
|
|
${segment.fiber_nr ? `<span style="display: inline-block; width: 12px; height: 12px; border-radius: 50%; background-color: ${segment.color}; border: 1px solid #fff; margin-right: 4px; vertical-align: middle;"></span>Faser: ${segment.fiber_nr}` : ''}
|
|
${segment.fiber_color ? ` (${segment.fiber_color})` : ''}<br>
|
|
${segment.bundle_nr ? `<span style="display: inline-block; width: 12px; height: 12px; border-radius: 50%; background-color: ${segment.bundle_color_hex || '#ccc'}; border: 1px solid #fff; margin-right: 4px; vertical-align: middle;"></span>Bündel: ${segment.bundle_nr}` : ''}
|
|
${segment.bundle_color ? ` (${segment.bundle_color})` : ''}
|
|
${segment.location ? `<br><strong>Dropout:</strong> ${segment.location}` : ''}
|
|
</div>`;
|
|
|
|
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}<br><span class="badge badge-secondary">Inaktiv ab ${splitPointName}</span>`, {
|
|
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 + '<br><span class="badge badge-success">Aktiv bis ' + splitPointName + '</span>' +
|
|
(dashPattern ? '<br><small>Vereinfachte Strecke (nur Stationspunkte)</small>' : ''), {
|
|
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}<br><span class="badge badge-warning">Vereinfachte Strecke (keine GPS-Daten)</span>`, {
|
|
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}<br>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(`
|
|
<div style="min-width: 200px;">
|
|
<h6><i class="fa fa-code-branch"></i> <strong>Abzweigpunkt</strong></h6>
|
|
<strong>${branchPoint.name}</strong><br>
|
|
<span class="badge badge-warning">Auf Kundenstrecke</span>
|
|
<hr style="margin: 8px 0;">
|
|
<div style="font-size: 11px;">
|
|
<strong>Kundenkabel:</strong> ${cableMatch.cable.description}<br>
|
|
<strong>Kunde:</strong> ${customerInfo.address || 'Hausanschluss'}<br>
|
|
<small class="text-muted">${pointLat.toFixed(5)}, ${pointLng.toFixed(5)}</small>
|
|
</div>
|
|
</div>
|
|
`).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(`
|
|
<div style="min-width: 200px;">
|
|
<h6><i class="fa fa-plug"></i> <strong>Verbindungspunkt</strong></h6>
|
|
<strong>${connectionPoint.name}</strong><br>
|
|
<span class="badge badge-${connectionPoint.type === 'branch_point' ? 'warning' : 'info'}">
|
|
${connectionPoint.type === 'branch_point' ? 'Abzweigpunkt' : 'Dispatcher'}
|
|
</span>
|
|
<hr style="margin: 8px 0;">
|
|
<div style="font-size: 11px;">
|
|
<strong>Verbunden mit:</strong><br>
|
|
${customerInfo.address || 'Hausanschluss'}<br>
|
|
${customerInfo.home_id ? `<strong>Home-ID:</strong> ${customerInfo.home_id}` : ''}
|
|
</div>
|
|
</div>
|
|
`).addTo(map);
|
|
|
|
allBounds.addLayer(connectionMarker);
|
|
}
|
|
}
|
|
|
|
const connectionPointType = connectionPoint.type === 'branch_point' ? 'Abzweigpunkt' : 'Verteiler (Dropout)';
|
|
const customerPopup = `
|
|
<div style="min-width: 250px;">
|
|
<h6><i class="fa fa-home"></i> <strong>Kunden-Anschluss</strong></h6>
|
|
${isFallback ? '<div class="alert alert-warning py-1 px-2 mb-2" style="font-size: 10px;"><i class="fa fa-exclamation-triangle"></i> Exakte GPS-Position nicht verfügbar</div>' : ''}
|
|
<hr style="margin: 8px 0;">
|
|
<div style="font-size: 11px;">
|
|
<strong>Verbindungspunkt:</strong> ${connectionPoint.name}<br>
|
|
<small class="text-muted">(${connectionPointType})</small><br>
|
|
<strong>Adresse:</strong> ${customerInfo.address}<br>
|
|
${customerInfo.home_id ? `<strong>Home-ID:</strong> ${customerInfo.home_id}<br>` : ''}
|
|
${customerInfo.name ? `<strong>Name:</strong> ${customerInfo.name}<br>` : ''}
|
|
<hr style="margin: 8px 0;">
|
|
<strong>Kabel:</strong> ${segment.name}<br>
|
|
Faser: ${segment.fiber_nr} ${segment.fiber_color ? `(${segment.fiber_color})` : ''}
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
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 = '<strong>Legende</strong><br>';
|
|
|
|
html += '<div style="margin: 8px 0;">';
|
|
html += '<i class="fa fa-circle" style="color: #acf0ab; margin-right: 5px;"></i> POP<br>';
|
|
html += '<i class="fa fa-circle" style="color: #abbaf0; margin-right: 5px;"></i> Verteiler<br>';
|
|
html += '<i class="fa fa-circle" style="color: #ffa726; margin-right: 5px;"></i> Schacht<br>';
|
|
html += '<i class="fa fa-circle" style="color: #f0abab; margin-right: 5px;"></i> Kunden-Haus<br>';
|
|
html += '</div>';
|
|
|
|
html += '<hr style="margin: 8px 0;">';
|
|
|
|
html += '<strong>Kabel & Fasern:</strong><br>';
|
|
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 += `<div style="margin: 4px 0; display: flex; align-items: center;">
|
|
<i style="background: ${segment.color}; width: 20px; height: 3px; display: inline-block; margin-right: 8px; border-top: ${borderStyle} ${segment.color};"></i>
|
|
<span style="font-size: 10px; line-height: 1.4;">
|
|
<strong>${segment.name}</strong>
|
|
${segment.fiber_nr ? `<br><span style="margin-left: 25px;">└─ Faser ${segment.fiber_nr}` : ''}
|
|
${fiberColorName ? ` <span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: ${segment.color}; border: 1px solid #666;"></span> (${fiberColorName})` : ''}
|
|
${segment.bundle_nr ? `<br><span style="margin-left: 25px;">└─ Bündel ${segment.bundle_nr}` : ''}
|
|
${segment.bundle_color_hex ? ` <span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: ${segment.bundle_color_hex}; border: 1px solid #666;"></span>` : ''}
|
|
${bundleColorName ? ` (${bundleColorName})` : ''}
|
|
</span>
|
|
</div>`;
|
|
});
|
|
|
|
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('<div class="text-center text-muted">Keine Daten verfügbar</div>');
|
|
return;
|
|
}
|
|
|
|
let html = '<div class="fiber-path-schema-horizontal">';
|
|
html += '<h6 class="p-0"><i class="fa fa-project-diagram"></i> Schematische Darstellung des Faserweges</h6>';
|
|
html += '<div class="schema-flow-horizontal">';
|
|
|
|
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 = '<hr style="margin: 8px 0;"><div style="border: 1px solid #d1dcff; padding: 8px; border-radius: 4px; margin-top: 8px;">';
|
|
branchConnectionsHtml += '<h6 style="margin: 0 0 8px 0; color: #000000;"><i class="fa fa-code-branch"></i> Faser-Übergänge:</h6>';
|
|
|
|
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 += `
|
|
<div style="background: white; padding: 6px; margin-bottom: 6px; border-left: 3px solid #FF6B6B; font-size: 10px;">
|
|
<strong style="color: #000000;">${junctionType} (Level ${conn.level})</strong><br>
|
|
<div style="margin-top: 4px;">
|
|
<strong>Von:</strong> ${conn.fromCable}<br>
|
|
<span style="padding-left: 10px;">└─ Faser ${conn.fromFiber || '?'} ${conn.fromFiberColorHex ? `<span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: ${conn.fromFiberColorHex}; border: 1px solid #666; margin-left: 4px;"></span> ${fromFiberColorName}` : ''}</span><br>
|
|
${conn.fromBundle ? `<span style="padding-left: 10px;">└─ Bündel ${conn.fromBundle}</span>` : ''}
|
|
${conn.fromBundleColorHex ? ` <span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: ${conn.fromBundleColorHex}; border: 1px solid #666;"></span> ${fromBundleColorName}` : ''}
|
|
${conn.fromBundle ? '<br>' : ''}
|
|
</div>
|
|
<div style="margin-top: 4px; padding-top: 4px; border-top: 1px dashed #ddd;">
|
|
<strong>Nach:</strong> ${conn.toCable}<br>
|
|
<span style="padding-left: 10px;">└─ Faser ${conn.toFiber || '?'} ${conn.toFiberColorHex ? `<span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: ${conn.toFiberColorHex}; border: 1px solid #666; margin-left: 4px;"></span> ${toFiberColorName}` : ''}</span><br>
|
|
${conn.toBundle ? `<span style="padding-left: 10px;">└─ Bündel ${conn.toBundle}</span>` : ''}
|
|
${conn.toBundleColorHex ? ` <span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: ${conn.toBundleColorHex}; border: 1px solid #666;"></span> ${toBundleColorName}` : ''}
|
|
${conn.toBundle ? '<br>' : ''}
|
|
</div>
|
|
</div>
|
|
`;
|
|
});
|
|
branchConnectionsHtml += '</div>';
|
|
}
|
|
|
|
const popupContent = `
|
|
<div style="min-width: 250px;">
|
|
<h6><strong>${station.name}</strong></h6>
|
|
<span class="badge ${stationInfo.badgeClass}">${stationInfo.badgeText}</span>
|
|
${stationInfo.isBranchJunction ? '<span class="badge badge-warning ml-1">' + stationInfo.branchConnections.length + ' Übergänge</span>' : ''}
|
|
<hr style="margin: 8px 0;">
|
|
<div style="font-size: 11px;">
|
|
<strong>Kabel:</strong> ${segment.name}<br>
|
|
${segment.fiber_nr ? `<strong>F:</strong> ${segment.fiber_nr}` : ''}
|
|
${segment.fiber_nr && segment.fiber_color_hex ? ` <span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: ${segment.fiber_color_hex}; border: 1px solid #666;"></span> (${segment.fiber_color || hexToColorName(segment.fiber_color_hex)})` : ''}<br>
|
|
${segment.bundle_nr ? `<strong>B:</strong> ${segment.bundle_nr}` : ''}
|
|
${segment.bundle_nr && segment.bundle_color_hex ? ` <span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: ${segment.bundle_color_hex}; border: 1px solid #666;"></span> (${segment.bundle_color || hexToColorName(segment.bundle_color_hex)})` : ''}<br>
|
|
<strong>Level:</strong> ${segment.level}<br>
|
|
<small class="text-muted">${parseFloat(station.gps_lat).toFixed(5)}, ${parseFloat(station.gps_long).toFixed(5)}</small>
|
|
</div>
|
|
${branchConnectionsHtml}
|
|
</div>
|
|
`;
|
|
|
|
html += `
|
|
<div class="schema-node-horizontal ${stationInfo.colorClass} schema-station-clickable"
|
|
data-popup-content="${popupContent.replace(/"/g, '"')}"
|
|
data-station-name="${station.name}"
|
|
style="cursor: pointer;">
|
|
<div class="schema-node-icon-horizontal" style="color: ${stationInfo.color};">
|
|
<i class="fa ${stationInfo.icon}" style="color:#fff"></i>
|
|
</div>
|
|
<div class="schema-node-label-horizontal">
|
|
<strong>${stationInfo.label}</strong>
|
|
${isDropoutPoint ? '<br><span class="badge badge-warning" style="font-size: 8px;">Dropout</span>' : ''}
|
|
${showTransition ? `
|
|
<br><div style="margin-top: 6px; padding: 4px 8px; background: #f8f9fa; border-left: 3px solid #dc3545; font-size: 10px; text-align: left;">
|
|
<strong style="font-size: 9px; color: #666;">⚡ Faser-Übergang:</strong><br>
|
|
<div style="margin-top: 3px; padding-left: 8px;">
|
|
<strong>Von:</strong> ${previousSegment.name}<br>
|
|
<span style="padding-left: 10px;">└─ Faser ${previousSegment.fiber_nr || '?'} ${(previousSegment.color || previousSegment.fiber_color_hex) ? `<span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: ${previousSegment.color || previousSegment.fiber_color_hex}; border: 1px solid #666; margin-left: 4px;"></span> ${previousSegment.fiber_color || (previousSegment.fiber_color_hex ? hexToColorName(previousSegment.fiber_color_hex) : '') || (previousSegment.color ? hexToColorName(previousSegment.color) : '')}` : ''}</span><br>
|
|
${previousSegment.bundle_nr ? `<span style="padding-left: 10px;">└─ Bündel ${previousSegment.bundle_nr} ${previousSegment.bundle_color_hex ? `<span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: ${previousSegment.bundle_color_hex}; border: 1px solid #666; margin-left: 4px;"></span> ${previousSegment.bundle_color || (previousSegment.bundle_color_hex ? hexToColorName(previousSegment.bundle_color_hex) : '')}` : ''}</span><br>` : ''}
|
|
</div>
|
|
<div style="margin-top: 4px; padding-top: 4px; border-top: 1px dashed #ddd; padding-left: 8px;">
|
|
<strong>Nach:</strong> ${segment.name}<br>
|
|
<span style="padding-left: 10px;">└─ Faser ${segment.fiber_nr || '?'} ${(segment.color || segment.fiber_color_hex) ? `<span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: ${segment.color || segment.fiber_color_hex}; border: 1px solid #666; margin-left: 4px;"></span> ${segment.fiber_color || (segment.fiber_color_hex ? hexToColorName(segment.fiber_color_hex) : '') || (segment.color ? hexToColorName(segment.color) : '')}` : ''}</span><br>
|
|
${segment.bundle_nr ? `<span style="padding-left: 10px;">└─ Bündel ${segment.bundle_nr} ${segment.bundle_color_hex ? `<span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: ${segment.bundle_color_hex}; border: 1px solid #666; margin-left: 4px;"></span> ${segment.bundle_color || (segment.bundle_color_hex ? hexToColorName(segment.bundle_color_hex) : '')}` : ''}</span><br>` : ''}
|
|
</div>
|
|
</div>
|
|
` : ''}
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
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 = `
|
|
<strong>${segment.name}</strong>
|
|
${segment.fiber_nr ? `<br><span style="font-size: 12px;"><strong>F:</strong> ${segment.fiber_nr}` : ''}
|
|
${fiberColorName ? ` <span class="fiber-color-dot" style="background: ${fiberColorHex}; display: inline-block; width: 8px; height: 8px; border-radius: 50%; border: 1px solid #333; margin-left: 2px; vertical-align: middle;"></span> ${fiberColorName}` : ''}
|
|
${segment.fiber_nr ? `</span>` : ''}
|
|
${segment.bundle_nr ? `<br><span style="font-size: 12px;"><strong>B:</strong> ${segment.bundle_nr}` : ''}
|
|
${bundleColorName ? ` <span class="fiber-color-dot" style="background: ${bundleColorHex}; display: inline-block; width: 8px; height: 8px; border-radius: 50%; border: 1px solid #333; margin-left: 2px; vertical-align: middle;"></span> ${bundleColorName}` : ''}
|
|
${segment.bundle_nr ? `</span>` : ''}
|
|
`.trim();
|
|
|
|
html += `
|
|
<div class="schema-connection-horizontal">
|
|
<div class="schema-line-horizontal ${lineStyle}" style="${segment.type === 'branch' ? `background: repeating-linear-gradient(to right, ${segmentColor}, ${segmentColor} 8px, transparent 8px, transparent 16px); height: 4px;` : `background-color: ${segmentColor};`}">
|
|
<div class="schema-cable-label">
|
|
${labelText}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
});
|
|
});
|
|
|
|
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 += `
|
|
<div class="schema-connection-horizontal">
|
|
<div class="schema-line-horizontal" style="background: repeating-linear-gradient(to right, #FFA500, #FFA500 5px, transparent 5px, transparent 10px); height: 4px;">
|
|
<div class="schema-cable-label">
|
|
<strong>Hausanschluss</strong>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="schema-node-horizontal station-customer">
|
|
<div class="schema-node-icon-horizontal" style="color: #f0abab;">
|
|
<i class="fa fa-home"></i>
|
|
</div>
|
|
<div class="schema-node-label-horizontal">
|
|
${customerHomeId ? `<strong>ID: ${customerHomeId}</strong><br>` : ''}
|
|
${customerAddress ? `<span class="small">${customerAddress}</span><br>` : ''}
|
|
${customerName ? `<span class="small">${customerName}</span>` : ''}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
html += '</div>';
|
|
html += '</div>';
|
|
|
|
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: `
|
|
<div class="popover station-schema-popover" role="tooltip">
|
|
<div class="arrow"></div>
|
|
<h3 class="popover-header"></h3>
|
|
<div class="popover-body"></div>
|
|
</div>
|
|
`
|
|
});
|
|
|
|
$('.schema-station-clickable').not($station).popover('hide');
|
|
|
|
$station.popover('show');
|
|
});
|
|
|
|
$(document).off('click.schema-popup').on('click.schema-popup', function (e) {
|
|
if (!$(e.target).closest('.schema-station-clickable, .popover').length) {
|
|
$('.schema-station-clickable').popover('hide');
|
|
}
|
|
});
|
|
}
|
|
|
|
function renderFiberPathSchemaLinework(fiber, allSegments) {
|
|
const schemaContainer = $('#fiber-route-schema');
|
|
|
|
if (!fiber || allSegments.length === 0) {
|
|
schemaContainer.html('<div class="text-center text-muted">Keine Daten verfügbar</div>');
|
|
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 = '<div class="fiber-path-schema-horizontal">';
|
|
html += '<h6 class="p-0"><i class="fa fa-project-diagram"></i> Faserweg (POP → Kunde)</h6>';
|
|
html += '<div class="schema-flow-horizontal" id="linework-schema-flow">';
|
|
|
|
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 = `
|
|
<div style="min-width: 250px;">
|
|
<h6><strong>${node.name}</strong></h6>
|
|
<span class="badge ${badgeClass}">${badgeText}</span>
|
|
${node.isBranchPoint ? '<span class="badge badge-warning ml-1">1 Übergänge</span>' : ''}
|
|
|
|
<hr style="margin: 8px 0;">
|
|
|
|
<div style="font-size: 11px;">
|
|
<strong>Kabel:</strong> ${node.cable}<br>
|
|
${node.fiberNr ? `<strong>F:</strong> ${node.fiberNr}` : ''}
|
|
${node.fiberColorHex ? ` <span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: ${node.fiberColorHex}; border: 1px solid #666;"></span> (${fiberColorName})` : ''}<br>
|
|
|
|
${node.bundleNr ? `<strong>B:</strong> ${node.bundleNr}` : ''}
|
|
${node.bundleColorHex ? ` <span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: ${node.bundleColorHex}; border: 1px solid #666;"></span> (${bundleColorName})` : ''}<br>
|
|
|
|
<strong>Level:</strong> ${node.level || 0}<br>
|
|
<small class="text-muted">${(node.lat && node.lng) ? `${node.lat.toFixed(5)}, ${node.lng.toFixed(5)}` : ''}</small>
|
|
</div>`;
|
|
|
|
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 += `
|
|
<hr style="margin: 8px 0;">
|
|
<div style="border: 1px solid #d1dcff; padding: 8px; border-radius: 4px; margin-top: 8px;">
|
|
<h6 style="margin: 0 0 8px 0; color: #000000; font-size: 12px;"><i class="fa fa-code-branch"></i> Faser-Übergänge:</h6>
|
|
|
|
<div style="background: white; padding: 6px; margin-bottom: 6px; border-left: 3px solid #FF6B6B; font-size: 10px;">
|
|
<strong style="color: #000000;">${junctionType} (Level ${node.level})</strong><br>
|
|
|
|
<div style="margin-top: 4px;">
|
|
<strong>Von:</strong> ${node.cable}<br>
|
|
<span style="padding-left: 10px;">└─ Faser ${node.fiberNr || '?'}
|
|
${node.fiberColorHex ? `<span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: ${node.fiberColorHex}; border: 1px solid #666;"></span> ${fiberColorName}` : ''}
|
|
</span><br>
|
|
<span style="padding-left: 10px;">└─ Bündel ${node.bundleNr || '?'}
|
|
${node.bundleColorHex ? `<span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: ${node.bundleColorHex}; border: 1px solid #666;"></span> ${bundleColorName}` : ''}
|
|
</span>
|
|
</div>
|
|
|
|
<div style="margin-top: 4px; padding-top: 4px; border-top: 1px dashed #ddd;">
|
|
<strong>Nach:</strong> ${node.transitionData.nextCable}<br>
|
|
<span style="padding-left: 10px;">└─ Faser ${node.transitionData.nextFiber || '?'}
|
|
${node.transitionData.nextFiberColorHex ? `<span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: ${node.transitionData.nextFiberColorHex}; border: 1px solid #666;"></span> ${nextFiberColorName}` : ''}
|
|
</span><br>
|
|
<span style="padding-left: 10px;">└─ Bündel ${node.transitionData.nextBundleNr || '?'}
|
|
${node.transitionData.nextBundleColorHex ? `<span style="display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: ${node.transitionData.nextBundleColorHex}; border: 1px solid #666;"></span> ${nextBundleColorName}` : ''}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>`;
|
|
}
|
|
|
|
popupContent += `</div>`;
|
|
|
|
html += `
|
|
<div class="schema-node-horizontal ${style.cssClass} schema-station-clickable"
|
|
data-popup-content="${popupContent.replace(/"/g, '"')}"
|
|
data-station-name="${node.name}">
|
|
<div class="schema-node-icon-horizontal" style="color: ${style.color};">
|
|
<i class="fa ${style.icon}"></i>
|
|
</div>
|
|
<div class="schema-node-label-horizontal">
|
|
<strong>${node.name}</strong>
|
|
${(node.isDropout || node.isBranchPoint) ? '<span class="badge badge-warning mt-1" style="background-color: #ffc107; color: #000;">Dropout</span>' : ''}
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
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 `
|
|
<strong>${cable}</strong><br>
|
|
<span style="font-size: 9px;">
|
|
${fNr ? `F: ${fNr}` : ''} ${fName ? `<span style="display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: ${fColHex || '#000'}; border: 1px solid #333;"></span> ${fName}` : ''}
|
|
</span>
|
|
${bNr ? `<br><span style="font-size: 9px;">B: ${bNr} ${bName ? `<span style="display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: ${bColHex || '#ccc'}; border: 1px solid #333;"></span> ${bName}` : ''}</span>` : ''}
|
|
`;
|
|
};
|
|
|
|
if (isBranchLine && node.transitionData) {
|
|
lineColor = node.transitionData.nextColor;
|
|
lineLabel = makeLabel(
|
|
node.transitionData.nextCable,
|
|
node.transitionData.nextFiber,
|
|
node.transitionData.nextFiberColor,
|
|
node.transitionData.nextFiberColorHex,
|
|
node.transitionData.nextBundleNr,
|
|
node.transitionData.nextBundleColor,
|
|
node.transitionData.nextBundleColorHex
|
|
);
|
|
} else {
|
|
lineColor = nextNode.color;
|
|
lineLabel = makeLabel(
|
|
nextNode.cable,
|
|
nextNode.fiberNr,
|
|
nextNode.fiberColor,
|
|
nextNode.fiberColorHex,
|
|
nextNode.bundleNr,
|
|
nextNode.bundleColor,
|
|
nextNode.bundleColorHex
|
|
);
|
|
}
|
|
|
|
if (!lineColor) lineColor = '#FF0000';
|
|
|
|
const lineClass = isBranchLine ? 'schema-line-dashed-horizontal' : 'schema-line-horizontal';
|
|
const lineStyle = isBranchLine
|
|
? `color: ${lineColor};`
|
|
: `background-color: ${lineColor}; height: 4px;`;
|
|
|
|
html += `
|
|
<div class="schema-connection-horizontal">
|
|
<div class="${lineClass}" style="${lineStyle}">
|
|
<div class="schema-cable-label">${lineLabel}</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
});
|
|
|
|
const targetFiber = fiber;
|
|
if (targetFiber.home_id || targetFiber.address) {
|
|
html += `
|
|
<div class="schema-connection-horizontal">
|
|
<div class="schema-line-horizontal" style="background: repeating-linear-gradient(to right, #FFA500, #FFA500 5px, transparent 5px, transparent 10px); height: 4px;">
|
|
<div class="schema-cable-label"><strong>Hausanschluss</strong></div>
|
|
</div>
|
|
</div>
|
|
<div class="schema-node-horizontal station-customer">
|
|
<div class="schema-node-icon-horizontal" style="color: #f0abab; border-color: #f0abab;">
|
|
<i class="fa fa-home"></i>
|
|
</div>
|
|
<div class="schema-node-label-horizontal">
|
|
<strong>${targetFiber.home_id || ''}</strong><br>
|
|
<span class="small text-muted">${targetFiber.name || 'Kunde'}</span>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
html += '</div></div>';
|
|
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: `
|
|
<div class="popover station-schema-popover" role="tooltip">
|
|
<div class="arrow"></div>
|
|
<h3 class="popover-header"></h3>
|
|
<div class="popover-body"></div>
|
|
</div>
|
|
`
|
|
});
|
|
$('.schema-station-clickable').not($station).popover('hide');
|
|
$station.popover('show');
|
|
});
|
|
|
|
$(document).off('click.schema-popup').on('click.schema-popup', function (e) {
|
|
if (!$(e.target).closest('.schema-station-clickable, .popover').length) {
|
|
$('.schema-station-clickable').popover('hide');
|
|
}
|
|
});
|
|
}
|
|
|
|
function getLineworkStationStyle(node) {
|
|
if (node.type === 'pop') return { icon: 'fa-building', cssClass: 'station-pop', color: '#4caf50', typeLabel: 'POP' };
|
|
if (node.object_type == 4) return { icon: 'fa-code-branch', cssClass: 'station-branch', color: '#9c27b0', typeLabel: 'Abzweig' };
|
|
if (node.object_type == 2) return { icon: 'fa-archive', cssClass: 'station-manhole', color: '#ffa726', typeLabel: 'Schacht' };
|
|
return { icon: 'fa-sitemap', cssClass: 'station-distributor', color: '#3388ff', typeLabel: 'Verteiler' };
|
|
}
|
|
function getBranchColor(level) {
|
|
const colors = [
|
|
'#FF6B6B',
|
|
'#FFA500',
|
|
'#FFD700',
|
|
'#9B59B6',
|
|
'#E91E63',
|
|
];
|
|
return colors[(level - 1) % colors.length];
|
|
}
|
|
|
|
function hexToColorName(hex) {
|
|
const colorMap = {
|
|
'#FF6B6B': 'Rot',
|
|
'#FFA500': 'Orange',
|
|
'#F79646': 'Orange',
|
|
'#FFD700': 'Gold',
|
|
'#9B59B6': 'Violett',
|
|
'#E91E63': 'Pink',
|
|
'#3388ff': 'Blau',
|
|
'#FF0000': 'Rot',
|
|
'#00FF00': 'Grün',
|
|
'#0000FF': 'Blau',
|
|
'#FFFF00': 'Gelb',
|
|
'#00FFFF': 'Cyan',
|
|
'#FF00FF': 'Magenta',
|
|
'#800000': 'Dunkelrot',
|
|
'#808000': 'Olive',
|
|
'#008000': 'Grün',
|
|
'#800080': 'Lila',
|
|
'#008080': 'Türkis',
|
|
'#00B0F0': 'Türkis',
|
|
'#000080': 'Marineblau',
|
|
'#FFC0CB': 'Rosa',
|
|
'#A52A2A': 'Braun',
|
|
'#948A54': 'Braun',
|
|
'#808080': 'Grau',
|
|
'#C0C0C0': 'Grau',
|
|
'#000000': 'Schwarz',
|
|
'#FFFFFF': 'Weiß',
|
|
'#7030A0': 'Violett',
|
|
|
|
};
|
|
|
|
const normalizedHex = hex.toUpperCase().startsWith('#') ? hex.toUpperCase() : '#' + hex.toUpperCase();
|
|
|
|
if (colorMap[normalizedHex]) {
|
|
return colorMap[normalizedHex];
|
|
}
|
|
|
|
if (normalizedHex.match(/#FF[0-9A-F]{4}/)) return 'Rot';
|
|
if (normalizedHex.match(/#[0-9A-F]{2}FF[0-9A-F]{2}/)) return 'Grün';
|
|
if (normalizedHex.match(/#[0-9A-F]{4}FF/)) return 'Blau';
|
|
if (normalizedHex.match(/#FFFF[0-9A-F]{2}/)) return 'Gelb';
|
|
if (normalizedHex.match(/#FF[0-9A-F]{2}FF/)) return 'Magenta';
|
|
if (normalizedHex.match(/#[0-9A-F]{2}FFFF/)) return 'Cyan';
|
|
|
|
return hex;
|
|
}
|
|
|
|
$(document).on('hidden.bs.modal', '#cableRouteMapModal, #fiberRouteMapModal', function () {
|
|
if ($('.modal:visible').length > 0) {
|
|
$('body').addClass('modal-open');
|
|
}
|
|
});
|
|
|
|
$(document).on('shown.bs.modal', '#cableRouteMapModal, #fiberRouteMapModal', function () {
|
|
$('#fiberPlanCableModal').css('overflow', 'hidden');
|
|
});
|
|
|
|
$(document).on('hidden.bs.modal', '#cableRouteMapModal, #fiberRouteMapModal', function () {
|
|
$('#fiberPlanCableModal').css('overflow', 'auto');
|
|
});
|
|
|
|
function findTargetFiberAtLevel(branchPath, targetLevel, currentLevel = 1) {
|
|
if (!branchPath || branchPath.error) {
|
|
return null;
|
|
}
|
|
|
|
if (currentLevel === targetLevel && branchPath.target_fiber) {
|
|
return branchPath.target_fiber;
|
|
}
|
|
|
|
if (branchPath.next_branch) {
|
|
return findTargetFiberAtLevel(branchPath.next_branch, targetLevel, currentLevel + 1);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
function findCableForCustomerConnection(customerLat, customerLng, cables) {
|
|
const tolerance = 0.00001;
|
|
|
|
for (let cable of cables) {
|
|
if (!cable.coordinates || cable.coordinates.length === 0) continue;
|
|
|
|
const firstPoint = cable.coordinates[0];
|
|
const lastPoint = cable.coordinates[cable.coordinates.length - 1];
|
|
|
|
if (Math.abs(firstPoint.gps_lat - customerLat) < tolerance &&
|
|
Math.abs(firstPoint.gps_long - customerLng) < tolerance) {
|
|
return {
|
|
cable: cable,
|
|
reverse: false
|
|
};
|
|
}
|
|
|
|
if (Math.abs(lastPoint.gps_lat - customerLat) < tolerance &&
|
|
Math.abs(lastPoint.gps_long - customerLng) < tolerance) {
|
|
return {
|
|
cable: cable,
|
|
reverse: true
|
|
};
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
$('body').on('click', '#toggle-fiber-view', function () {
|
|
const btn = $(this);
|
|
const cableName = btn.data('cable-name');
|
|
const showAll = btn.data('show-all');
|
|
|
|
if (showAll === false || showAll === 'false') {
|
|
btn.data('show-all', 'true');
|
|
btn.html('<i class="fa fa-filter"></i> 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('<i class="fa fa-eye"></i> 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);
|
|
});
|
|
}); |