5385 lines
236 KiB
JavaScript
Executable file
5385 lines
236 KiB
JavaScript
Executable file
/**
|
|
* KundenKarte Module JavaScript
|
|
* Copyright (C) 2026 Alles Watt lauft
|
|
*/
|
|
|
|
(function() {
|
|
'use strict';
|
|
|
|
// Namespace
|
|
window.KundenKarte = window.KundenKarte || {};
|
|
|
|
// Get base URL for AJAX calls
|
|
var baseUrl = (typeof DOL_URL_ROOT !== 'undefined') ? DOL_URL_ROOT : '';
|
|
if (!baseUrl) {
|
|
// Try to detect from script src
|
|
var scripts = document.getElementsByTagName('script');
|
|
for (var i = 0; i < scripts.length; i++) {
|
|
var src = scripts[i].src;
|
|
if (src && src.indexOf('/kundenkarte/js/kundenkarte.js') > -1) {
|
|
baseUrl = src.replace('/custom/kundenkarte/js/kundenkarte.js', '').replace(/\?.*$/, '');
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Tree Component
|
|
*/
|
|
KundenKarte.Tree = {
|
|
tooltipTimeout: null,
|
|
hideTimeout: null,
|
|
currentTooltip: null,
|
|
currentItem: null,
|
|
|
|
init: function() {
|
|
this.bindEvents();
|
|
},
|
|
|
|
bindEvents: function() {
|
|
var self = this;
|
|
|
|
// Toggle tree nodes - MUST use stopImmediatePropagation for delegated handlers on same element
|
|
$(document).on('click', '.kundenkarte-tree-toggle', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
e.stopImmediatePropagation();
|
|
|
|
var $toggle = $(this);
|
|
var $node = $toggle.closest('.kundenkarte-tree-node');
|
|
var $children = $node.children('.kundenkarte-tree-children');
|
|
|
|
$toggle.toggleClass('collapsed');
|
|
$children.toggleClass('collapsed');
|
|
});
|
|
|
|
// Expand all nodes
|
|
$(document).on('click', '#btn-expand-all', function(e) {
|
|
e.preventDefault();
|
|
self.expandAll();
|
|
});
|
|
|
|
// Collapse all nodes
|
|
$(document).on('click', '#btn-collapse-all', function(e) {
|
|
e.preventDefault();
|
|
self.collapseAll();
|
|
});
|
|
|
|
// Hover tooltip on ICON only - show after delay
|
|
$(document).on('mouseenter', '.kundenkarte-tooltip-trigger', function(e) {
|
|
var $trigger = $(this);
|
|
var anlageId = $trigger.data('anlage-id');
|
|
|
|
if (!anlageId) return;
|
|
|
|
// Cancel any pending hide
|
|
clearTimeout(self.hideTimeout);
|
|
self.hideTimeout = null;
|
|
|
|
self.currentItem = $trigger;
|
|
|
|
self.tooltipTimeout = setTimeout(function() {
|
|
self.showTooltip($trigger, anlageId);
|
|
}, 300);
|
|
});
|
|
|
|
// Hide tooltip when leaving icon
|
|
$(document).on('mouseleave', '.kundenkarte-tooltip-trigger', function() {
|
|
clearTimeout(self.tooltipTimeout);
|
|
self.tooltipTimeout = null;
|
|
self.currentItem = null;
|
|
|
|
// Hide after short delay (allows moving to tooltip)
|
|
self.hideTimeout = setTimeout(function() {
|
|
self.hideTooltip();
|
|
}, 100);
|
|
});
|
|
|
|
// Images tooltip on hover
|
|
$(document).on('mouseenter', '.kundenkarte-images-trigger', function(e) {
|
|
var $trigger = $(this);
|
|
var anlageId = $trigger.data('anlage-id');
|
|
|
|
if (!anlageId) return;
|
|
|
|
clearTimeout(self.hideTimeout);
|
|
self.hideTimeout = null;
|
|
|
|
self.tooltipTimeout = setTimeout(function() {
|
|
self.showImagesPopup($trigger, anlageId);
|
|
}, 300);
|
|
});
|
|
|
|
$(document).on('mouseleave', '.kundenkarte-images-trigger', function() {
|
|
clearTimeout(self.tooltipTimeout);
|
|
self.tooltipTimeout = null;
|
|
|
|
self.hideTimeout = setTimeout(function() {
|
|
self.hideTooltip();
|
|
}, 100);
|
|
});
|
|
|
|
// Documents tooltip on hover
|
|
$(document).on('mouseenter', '.kundenkarte-docs-trigger', function(e) {
|
|
var $trigger = $(this);
|
|
var anlageId = $trigger.data('anlage-id');
|
|
|
|
if (!anlageId) return;
|
|
|
|
clearTimeout(self.hideTimeout);
|
|
self.hideTimeout = null;
|
|
|
|
self.tooltipTimeout = setTimeout(function() {
|
|
self.showDocsPopup($trigger, anlageId);
|
|
}, 300);
|
|
});
|
|
|
|
$(document).on('mouseleave', '.kundenkarte-docs-trigger', function() {
|
|
clearTimeout(self.tooltipTimeout);
|
|
self.tooltipTimeout = null;
|
|
|
|
self.hideTimeout = setTimeout(function() {
|
|
self.hideTooltip();
|
|
}, 100);
|
|
});
|
|
|
|
// Keep tooltip visible when hovering over it
|
|
$(document).on('mouseenter', '#kundenkarte-tooltip', function() {
|
|
clearTimeout(self.hideTimeout);
|
|
self.hideTimeout = null;
|
|
});
|
|
|
|
// Hide when leaving tooltip
|
|
$(document).on('mouseleave', '#kundenkarte-tooltip', function() {
|
|
self.hideTooltip();
|
|
});
|
|
|
|
// Select item
|
|
$(document).on('click', '.kundenkarte-tree-item', function(e) {
|
|
if ($(e.target).closest('.kundenkarte-tree-toggle, .kundenkarte-tree-actions, .kundenkarte-tree-files').length) {
|
|
return;
|
|
}
|
|
|
|
$('.kundenkarte-tree-item').removeClass('selected');
|
|
$(this).addClass('selected');
|
|
|
|
var anlageId = $(this).data('anlage-id');
|
|
if (anlageId) {
|
|
$(document).trigger('kundenkarte:element:selected', [anlageId]);
|
|
}
|
|
});
|
|
},
|
|
|
|
showTooltip: function($item, anlageId) {
|
|
var self = this;
|
|
|
|
// Get tooltip data from data attribute (faster than AJAX)
|
|
var tooltipDataStr = $item.attr('data-tooltip');
|
|
if (!tooltipDataStr) {
|
|
console.log('No tooltip data for anlage', anlageId);
|
|
return;
|
|
}
|
|
|
|
var data;
|
|
try {
|
|
data = JSON.parse(tooltipDataStr);
|
|
} catch(e) {
|
|
console.error('Failed to parse tooltip JSON:', e, tooltipDataStr);
|
|
return;
|
|
}
|
|
|
|
var html = self.buildTooltipHtml(data);
|
|
var $tooltip = $('#kundenkarte-tooltip');
|
|
|
|
if (!$tooltip.length) {
|
|
$tooltip = $('<div id="kundenkarte-tooltip" class="kundenkarte-tooltip"></div>');
|
|
$('body').append($tooltip);
|
|
}
|
|
|
|
$tooltip.html(html);
|
|
|
|
// Position tooltip
|
|
var offset = $item.offset();
|
|
var itemWidth = $item.outerWidth();
|
|
var windowWidth = $(window).width();
|
|
var scrollTop = $(window).scrollTop();
|
|
|
|
// First show to calculate width
|
|
$tooltip.css({ visibility: 'hidden', display: 'block' });
|
|
var tooltipWidth = $tooltip.outerWidth();
|
|
var tooltipHeight = $tooltip.outerHeight();
|
|
$tooltip.css({ visibility: '', display: '' });
|
|
|
|
var left = offset.left + itemWidth + 10;
|
|
if (left + tooltipWidth > windowWidth - 20) {
|
|
left = offset.left - tooltipWidth - 10;
|
|
}
|
|
if (left < 10) {
|
|
left = 10;
|
|
}
|
|
|
|
var top = offset.top;
|
|
// Prevent tooltip from going below viewport
|
|
if (top + tooltipHeight > scrollTop + $(window).height() - 20) {
|
|
top = scrollTop + $(window).height() - tooltipHeight - 20;
|
|
}
|
|
|
|
$tooltip.css({
|
|
top: top,
|
|
left: left
|
|
}).addClass('visible').show();
|
|
|
|
self.currentTooltip = $tooltip;
|
|
},
|
|
|
|
hideTooltip: function() {
|
|
clearTimeout(this.hideTimeout);
|
|
this.hideTimeout = null;
|
|
var $tooltip = $('#kundenkarte-tooltip');
|
|
if ($tooltip.length) {
|
|
$tooltip.removeClass('visible').hide();
|
|
}
|
|
this.currentTooltip = null;
|
|
},
|
|
|
|
showImagesPopup: function($trigger, anlageId) {
|
|
var self = this;
|
|
|
|
// Load images via AJAX - use absolute path
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/anlage_images.php',
|
|
data: { anlage_id: anlageId },
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (!response.images || response.images.length === 0) {
|
|
return;
|
|
}
|
|
|
|
var html = '<div class="kundenkarte-images-popup">';
|
|
html += '<div class="kundenkarte-images-grid">';
|
|
for (var i = 0; i < response.images.length; i++) {
|
|
var img = response.images[i];
|
|
html += '<a href="' + img.url + '" target="_blank" class="kundenkarte-images-thumb">';
|
|
html += '<img src="' + img.thumb + '" alt="' + self.escapeHtml(img.name) + '">';
|
|
html += '</a>';
|
|
}
|
|
html += '</div>';
|
|
html += '</div>';
|
|
|
|
var $tooltip = $('#kundenkarte-tooltip');
|
|
if (!$tooltip.length) {
|
|
$tooltip = $('<div id="kundenkarte-tooltip" class="kundenkarte-tooltip"></div>');
|
|
$('body').append($tooltip);
|
|
}
|
|
|
|
$tooltip.html(html);
|
|
|
|
// Position tooltip
|
|
var offset = $trigger.offset();
|
|
var windowWidth = $(window).width();
|
|
var scrollTop = $(window).scrollTop();
|
|
|
|
$tooltip.css({ visibility: 'hidden', display: 'block' });
|
|
var tooltipWidth = $tooltip.outerWidth();
|
|
var tooltipHeight = $tooltip.outerHeight();
|
|
$tooltip.css({ visibility: '', display: '' });
|
|
|
|
var left = offset.left + $trigger.outerWidth() + 10;
|
|
if (left + tooltipWidth > windowWidth - 20) {
|
|
left = offset.left - tooltipWidth - 10;
|
|
}
|
|
if (left < 10) left = 10;
|
|
|
|
var top = offset.top;
|
|
if (top + tooltipHeight > scrollTop + $(window).height() - 20) {
|
|
top = scrollTop + $(window).height() - tooltipHeight - 20;
|
|
}
|
|
|
|
$tooltip.css({ top: top, left: left }).addClass('visible').show();
|
|
self.currentTooltip = $tooltip;
|
|
}
|
|
});
|
|
},
|
|
|
|
showDocsPopup: function($trigger, anlageId) {
|
|
var self = this;
|
|
|
|
// Load documents via AJAX
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/anlage_docs.php',
|
|
data: { anlage_id: anlageId },
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (!response.docs || response.docs.length === 0) {
|
|
return;
|
|
}
|
|
|
|
// Visual document cards with icons
|
|
var html = '<div class="kundenkarte-docs-popup">';
|
|
html += '<div class="kundenkarte-docs-grid">';
|
|
for (var i = 0; i < response.docs.length; i++) {
|
|
var doc = response.docs[i];
|
|
var iconClass = doc.type === 'pdf' ? 'fa-file-pdf-o' : 'fa-file-text-o';
|
|
var iconColor = doc.type === 'pdf' ? '#e74c3c' : '#f39c12';
|
|
html += '<a href="' + doc.url + '" target="_blank" class="kundenkarte-docs-card">';
|
|
html += '<div class="kundenkarte-docs-card-icon" style="color:' + iconColor + '">';
|
|
html += '<i class="fa ' + iconClass + '"></i>';
|
|
html += '</div>';
|
|
html += '<div class="kundenkarte-docs-card-name">' + self.escapeHtml(doc.name) + '</div>';
|
|
html += '</a>';
|
|
}
|
|
html += '</div>';
|
|
html += '</div>';
|
|
|
|
var $tooltip = $('#kundenkarte-tooltip');
|
|
if (!$tooltip.length) {
|
|
$tooltip = $('<div id="kundenkarte-tooltip" class="kundenkarte-tooltip"></div>');
|
|
$('body').append($tooltip);
|
|
}
|
|
|
|
$tooltip.html(html);
|
|
|
|
// Position tooltip
|
|
var offset = $trigger.offset();
|
|
var windowWidth = $(window).width();
|
|
var scrollTop = $(window).scrollTop();
|
|
|
|
$tooltip.css({ visibility: 'hidden', display: 'block' });
|
|
var tooltipWidth = $tooltip.outerWidth();
|
|
var tooltipHeight = $tooltip.outerHeight();
|
|
$tooltip.css({ visibility: '', display: '' });
|
|
|
|
var left = offset.left + $trigger.outerWidth() + 10;
|
|
if (left + tooltipWidth > windowWidth - 20) {
|
|
left = offset.left - tooltipWidth - 10;
|
|
}
|
|
if (left < 10) left = 10;
|
|
|
|
var top = offset.top;
|
|
if (top + tooltipHeight > scrollTop + $(window).height() - 20) {
|
|
top = scrollTop + $(window).height() - tooltipHeight - 20;
|
|
}
|
|
|
|
$tooltip.css({ top: top, left: left }).addClass('visible').show();
|
|
self.currentTooltip = $tooltip;
|
|
}
|
|
});
|
|
},
|
|
|
|
buildTooltipHtml: function(data) {
|
|
var html = '<div class="kundenkarte-tooltip-header">';
|
|
html += '<span class="kundenkarte-tooltip-icon"><i class="fa ' + (data.picto || 'fa-cube') + '"></i></span>';
|
|
html += '<div>';
|
|
html += '<div class="kundenkarte-tooltip-title">' + this.escapeHtml(data.label || '') + '</div>';
|
|
html += '<div class="kundenkarte-tooltip-type">' + this.escapeHtml(data.type || data.type_label || '') + '</div>';
|
|
html += '</div></div>';
|
|
|
|
html += '<div class="kundenkarte-tooltip-fields">';
|
|
|
|
// Dynamic fields only (from PHP data-tooltip attribute)
|
|
if (data.fields) {
|
|
for (var key in data.fields) {
|
|
if (data.fields.hasOwnProperty(key)) {
|
|
var field = data.fields[key];
|
|
// Handle header fields as section titles (must span both grid columns)
|
|
if (field.type === 'header') {
|
|
html += '<span class="kundenkarte-tooltip-field-header" style="grid-column:1/-1;display:block;width:100%;font-weight:bold;margin-top:8px;padding-top:8px;border-top:1px solid #ddd;">' + this.escapeHtml(field.label) + '</span>';
|
|
} else if (field.value) {
|
|
html += '<span class="kundenkarte-tooltip-field-label">' + this.escapeHtml(field.label) + ':</span>';
|
|
html += '<span class="kundenkarte-tooltip-field-value">' + this.escapeHtml(field.value) + '</span>';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
html += '</div>';
|
|
|
|
// Notes (note_html is already sanitized and formatted with <br> by PHP)
|
|
if (data.note_html) {
|
|
html += '<div class="kundenkarte-tooltip-note" style="margin-top:10px;padding-top:10px;border-top:1px solid #eee;font-size:0.9em;color:#666;">';
|
|
html += '<i class="fa fa-sticky-note"></i><br>' + data.note_html;
|
|
html += '</div>';
|
|
}
|
|
|
|
// Images (from AJAX)
|
|
if (data.images && data.images.length > 0) {
|
|
html += '<div class="kundenkarte-tooltip-images">';
|
|
for (var i = 0; i < Math.min(data.images.length, 4); i++) {
|
|
html += '<img src="' + data.images[i].thumb_url + '" class="kundenkarte-tooltip-thumb" alt="">';
|
|
}
|
|
html += '</div>';
|
|
}
|
|
|
|
return html;
|
|
},
|
|
|
|
escapeHtml: function(text) {
|
|
if (!text) return '';
|
|
var div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
},
|
|
|
|
escapeHtmlPreservingBreaks: function(text) {
|
|
if (!text) return '';
|
|
// Convert <br> to newlines first (for old data with <br> tags)
|
|
text = text.replace(/<br\s*\/?>/gi, '\n');
|
|
return this.escapeHtml(text);
|
|
},
|
|
|
|
refresh: function(socId, systemId) {
|
|
var $container = $('.kundenkarte-tree[data-system="' + systemId + '"]');
|
|
if (!$container.length) return;
|
|
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/anlage_tree.php',
|
|
data: { socid: socId, system: systemId },
|
|
success: function(html) {
|
|
$container.html(html);
|
|
}
|
|
});
|
|
},
|
|
|
|
expandAll: function() {
|
|
$('.kundenkarte-tree-toggle').removeClass('collapsed');
|
|
$('.kundenkarte-tree-children').removeClass('collapsed');
|
|
},
|
|
|
|
collapseAll: function() {
|
|
$('.kundenkarte-tree-toggle').addClass('collapsed');
|
|
$('.kundenkarte-tree-children').addClass('collapsed');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Favorite Products Component
|
|
*/
|
|
KundenKarte.Favorites = {
|
|
init: function() {
|
|
this.bindEvents();
|
|
},
|
|
|
|
bindEvents: function() {
|
|
// Select all checkbox
|
|
$(document).on('change', '#kundenkarte-select-all', function() {
|
|
var checked = $(this).prop('checked');
|
|
$('.kundenkarte-favorites-table input[type="checkbox"][name="selected_products[]"]').prop('checked', checked);
|
|
KundenKarte.Favorites.updateGenerateButton();
|
|
});
|
|
|
|
// Individual checkbox
|
|
$(document).on('change', '.kundenkarte-favorites-table input[type="checkbox"][name="selected_products[]"]', function() {
|
|
KundenKarte.Favorites.updateGenerateButton();
|
|
});
|
|
|
|
// Save button click
|
|
$(document).on('click', '.kundenkarte-qty-save', function(e) {
|
|
e.preventDefault();
|
|
var $btn = $(this);
|
|
var favId = $btn.data('fav-id');
|
|
var $input = $('input.kundenkarte-favorites-qty[data-fav-id="' + favId + '"]');
|
|
var qtyStr = $input.val().replace(',', '.');
|
|
var qty = parseFloat(qtyStr);
|
|
|
|
if (!isNaN(qty) && qty > 0) {
|
|
// Limit to 2 decimal places
|
|
qty = Math.round(qty * 100) / 100;
|
|
|
|
// Format nicely
|
|
var display = (qty % 1 === 0) ? qty.toString() : qty.toFixed(2).replace(/\.?0+$/, '');
|
|
$input.val(display);
|
|
|
|
// Visual feedback
|
|
$btn.prop('disabled', true);
|
|
$btn.find('i').removeClass('fa-save').addClass('fa-spinner fa-spin');
|
|
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/favorite_update.php',
|
|
method: 'POST',
|
|
data: { id: favId, qty: qty, token: $('input[name="token"]').val() },
|
|
success: function() {
|
|
$btn.find('i').removeClass('fa-spinner fa-spin').addClass('fa-check').css('color', '#0a0');
|
|
setTimeout(function() {
|
|
$btn.find('i').removeClass('fa-check').addClass('fa-save').css('color', '');
|
|
$btn.prop('disabled', false);
|
|
}, 1500);
|
|
},
|
|
error: function() {
|
|
$btn.find('i').removeClass('fa-spinner fa-spin').addClass('fa-exclamation-triangle').css('color', '#c00');
|
|
setTimeout(function() {
|
|
$btn.find('i').removeClass('fa-exclamation-triangle').addClass('fa-save').css('color', '');
|
|
$btn.prop('disabled', false);
|
|
}, 2000);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
},
|
|
|
|
updateGenerateButton: function() {
|
|
var count = $('.kundenkarte-favorites-table input[type="checkbox"][name="selected_products[]"]:checked').length;
|
|
var $btn = $('#btn-generate-order');
|
|
|
|
if (count > 0) {
|
|
$btn.prop('disabled', false).text($btn.data('text').replace('%d', count));
|
|
} else {
|
|
$btn.prop('disabled', true).text($btn.data('text-none'));
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* System Tabs Component
|
|
*/
|
|
KundenKarte.SystemTabs = {
|
|
init: function() {
|
|
this.bindEvents();
|
|
},
|
|
|
|
bindEvents: function() {
|
|
$(document).on('click', '.kundenkarte-system-tab:not(.active):not(.kundenkarte-system-tab-add)', function() {
|
|
var systemId = $(this).data('system');
|
|
KundenKarte.SystemTabs.switchTo(systemId);
|
|
});
|
|
},
|
|
|
|
switchTo: function(systemId) {
|
|
$('.kundenkarte-system-tab').removeClass('active');
|
|
$('.kundenkarte-system-tab[data-system="' + systemId + '"]').addClass('active');
|
|
|
|
$('.kundenkarte-system-content').hide();
|
|
$('.kundenkarte-system-content[data-system="' + systemId + '"]').show();
|
|
|
|
// Update URL without reload
|
|
var url = new URL(window.location.href);
|
|
url.searchParams.set('system', systemId);
|
|
window.history.replaceState({}, '', url);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Icon Picker Component with Custom Icon Upload
|
|
*/
|
|
KundenKarte.IconPicker = {
|
|
// Common FontAwesome icons for installations/technical use
|
|
icons: [
|
|
// Electrical
|
|
'fa-bolt', 'fa-plug', 'fa-power-off', 'fa-charging-station', 'fa-battery-full', 'fa-battery-half',
|
|
'fa-car-battery', 'fa-solar-panel', 'fa-sun', 'fa-lightbulb', 'fa-toggle-on', 'fa-toggle-off',
|
|
// Network/Internet
|
|
'fa-wifi', 'fa-network-wired', 'fa-server', 'fa-database', 'fa-hdd', 'fa-ethernet',
|
|
'fa-broadcast-tower', 'fa-satellite-dish', 'fa-satellite', 'fa-signal', 'fa-rss',
|
|
// TV/Media
|
|
'fa-tv', 'fa-play-circle', 'fa-video', 'fa-film', 'fa-podcast', 'fa-music',
|
|
// Temperature/Climate
|
|
'fa-thermometer-half', 'fa-temperature-high', 'fa-temperature-low', 'fa-fire', 'fa-fire-alt',
|
|
'fa-snowflake', 'fa-wind', 'fa-fan', 'fa-air-freshener',
|
|
// Building/Structure
|
|
'fa-home', 'fa-building', 'fa-warehouse', 'fa-door-open', 'fa-door-closed', 'fa-archway',
|
|
// Devices/Hardware
|
|
'fa-microchip', 'fa-memory', 'fa-sim-card', 'fa-sd-card', 'fa-usb', 'fa-desktop', 'fa-laptop',
|
|
'fa-mobile-alt', 'fa-tablet-alt', 'fa-keyboard', 'fa-print', 'fa-fax',
|
|
// Security
|
|
'fa-shield-alt', 'fa-lock', 'fa-unlock', 'fa-key', 'fa-fingerprint', 'fa-eye', 'fa-video',
|
|
'fa-bell', 'fa-exclamation-triangle', 'fa-user-shield',
|
|
// Objects
|
|
'fa-cube', 'fa-cubes', 'fa-box', 'fa-boxes', 'fa-archive', 'fa-toolbox', 'fa-tools', 'fa-wrench',
|
|
'fa-cog', 'fa-cogs', 'fa-sliders-h',
|
|
// Layout
|
|
'fa-th', 'fa-th-large', 'fa-th-list', 'fa-grip-horizontal', 'fa-grip-vertical', 'fa-bars',
|
|
'fa-stream', 'fa-layer-group', 'fa-project-diagram', 'fa-share-alt', 'fa-sitemap',
|
|
// Arrows/Direction
|
|
'fa-exchange-alt', 'fa-arrows-alt', 'fa-expand', 'fa-compress', 'fa-random',
|
|
// Misc
|
|
'fa-tachometer-alt', 'fa-chart-line', 'fa-chart-bar', 'fa-chart-pie', 'fa-chart-area',
|
|
'fa-clock', 'fa-calendar', 'fa-tag', 'fa-tags', 'fa-bookmark', 'fa-star', 'fa-heart',
|
|
'fa-check', 'fa-times', 'fa-plus', 'fa-minus', 'fa-info-circle', 'fa-question-circle',
|
|
'fa-dot-circle', 'fa-circle', 'fa-square', 'fa-adjust'
|
|
],
|
|
|
|
customIcons: [],
|
|
currentInput: null,
|
|
currentTab: 'fontawesome',
|
|
|
|
init: function() {
|
|
this.createModal();
|
|
this.bindEvents();
|
|
},
|
|
|
|
createModal: function() {
|
|
if ($('#kundenkarte-icon-picker-modal').length) return;
|
|
|
|
var self = this;
|
|
var html = '<div id="kundenkarte-icon-picker-modal" class="kundenkarte-modal">';
|
|
html += '<div class="kundenkarte-modal-content" style="max-width:700px;">';
|
|
html += '<div class="kundenkarte-modal-header">';
|
|
html += '<h3>Icon auswählen</h3>';
|
|
html += '<span class="kundenkarte-modal-close">×</span>';
|
|
html += '</div>';
|
|
html += '<div class="kundenkarte-modal-body">';
|
|
|
|
// Tabs
|
|
html += '<div class="kundenkarte-icon-tabs" style="display:flex;gap:5px;margin-bottom:15px;border-bottom:2px solid #e0e0e0;padding-bottom:10px;">';
|
|
html += '<button type="button" class="button kundenkarte-icon-tab active" data-tab="fontawesome"><i class="fa fa-font-awesome"></i> Font Awesome</button>';
|
|
html += '<button type="button" class="button kundenkarte-icon-tab" data-tab="custom"><i class="fa fa-upload"></i> Eigene Icons</button>';
|
|
html += '</div>';
|
|
|
|
// Font Awesome Tab Content
|
|
html += '<div class="kundenkarte-icon-tab-content" data-tab="fontawesome">';
|
|
html += '<input type="text" id="kundenkarte-icon-search" class="flat" placeholder="Suchen..." style="width:100%;margin-bottom:10px;">';
|
|
html += '<div class="kundenkarte-icon-grid" id="kundenkarte-fa-icons">';
|
|
for (var i = 0; i < this.icons.length; i++) {
|
|
html += '<div class="kundenkarte-icon-item" data-icon="' + this.icons[i] + '" data-type="fa" title="' + this.icons[i] + '">';
|
|
html += '<i class="fa ' + this.icons[i] + '"></i>';
|
|
html += '</div>';
|
|
}
|
|
html += '</div>';
|
|
html += '</div>';
|
|
|
|
// Custom Icons Tab Content
|
|
html += '<div class="kundenkarte-icon-tab-content" data-tab="custom" style="display:none;">';
|
|
// Upload area
|
|
html += '<div class="kundenkarte-icon-upload-area" style="border:2px dashed #ccc;border-radius:8px;padding:20px;text-align:center;margin-bottom:15px;background:#f9f9f9;">';
|
|
html += '<i class="fa fa-cloud-upload" style="font-size:32px;color:#888;margin-bottom:10px;display:block;"></i>';
|
|
html += '<p style="margin:0 0 10px 0;">Icon hochladen (PNG, JPG, SVG, max 500KB)</p>';
|
|
html += '<input type="file" id="kundenkarte-icon-upload" accept=".png,.jpg,.jpeg,.gif,.svg,.webp" style="display:none;">';
|
|
html += '<button type="button" class="button" id="kundenkarte-icon-upload-btn"><i class="fa fa-plus"></i> Datei auswählen</button>';
|
|
html += '</div>';
|
|
// Custom icons grid
|
|
html += '<div class="kundenkarte-icon-grid" id="kundenkarte-custom-icons">';
|
|
html += '<p class="opacitymedium" style="grid-column:1/-1;text-align:center;">Lade eigene Icons...</p>';
|
|
html += '</div>';
|
|
html += '</div>';
|
|
|
|
html += '</div>';
|
|
html += '</div>';
|
|
html += '</div>';
|
|
|
|
$('body').append(html);
|
|
},
|
|
|
|
bindEvents: function() {
|
|
var self = this;
|
|
|
|
// Open picker
|
|
$(document).on('click', '.kundenkarte-icon-picker-btn', function(e) {
|
|
e.preventDefault();
|
|
var inputName = $(this).data('input');
|
|
self.currentInput = $('input[name="' + inputName + '"]');
|
|
self.open();
|
|
});
|
|
|
|
// Close modal
|
|
$(document).on('click', '.kundenkarte-modal-close, #kundenkarte-icon-picker-modal', function(e) {
|
|
if (e.target === this || $(e.target).hasClass('kundenkarte-modal-close')) {
|
|
self.close();
|
|
}
|
|
});
|
|
|
|
// Tab switching
|
|
$(document).on('click', '.kundenkarte-icon-tab', function() {
|
|
var tab = $(this).data('tab');
|
|
self.switchTab(tab);
|
|
});
|
|
|
|
// Select FA icon
|
|
$(document).on('click', '.kundenkarte-icon-item[data-type="fa"]', function() {
|
|
var icon = $(this).data('icon');
|
|
self.selectIcon(icon, 'fa');
|
|
});
|
|
|
|
// Select custom icon
|
|
$(document).on('click', '.kundenkarte-icon-item[data-type="custom"]', function() {
|
|
var iconUrl = $(this).data('icon');
|
|
self.selectIcon(iconUrl, 'custom');
|
|
});
|
|
|
|
// Search filter (FA only)
|
|
$(document).on('input', '#kundenkarte-icon-search', function() {
|
|
var search = $(this).val().toLowerCase();
|
|
$('#kundenkarte-fa-icons .kundenkarte-icon-item').each(function() {
|
|
var icon = $(this).data('icon').toLowerCase();
|
|
$(this).toggle(icon.indexOf(search) > -1);
|
|
});
|
|
});
|
|
|
|
// Upload button click
|
|
$(document).on('click', '#kundenkarte-icon-upload-btn', function() {
|
|
$('#kundenkarte-icon-upload').click();
|
|
});
|
|
|
|
// File selected
|
|
$(document).on('change', '#kundenkarte-icon-upload', function() {
|
|
var file = this.files[0];
|
|
if (file) {
|
|
self.uploadIcon(file);
|
|
}
|
|
});
|
|
|
|
// Delete custom icon
|
|
$(document).on('click', '.kundenkarte-icon-delete', function(e) {
|
|
e.stopPropagation();
|
|
var filename = $(this).data('filename');
|
|
self.showDeleteConfirm(filename);
|
|
});
|
|
|
|
// ESC key to close
|
|
$(document).on('keydown', function(e) {
|
|
if (e.key === 'Escape') {
|
|
self.close();
|
|
}
|
|
});
|
|
},
|
|
|
|
switchTab: function(tab) {
|
|
this.currentTab = tab;
|
|
$('.kundenkarte-icon-tab').removeClass('active');
|
|
$('.kundenkarte-icon-tab[data-tab="' + tab + '"]').addClass('active');
|
|
$('.kundenkarte-icon-tab-content').hide();
|
|
$('.kundenkarte-icon-tab-content[data-tab="' + tab + '"]').show();
|
|
|
|
if (tab === 'custom') {
|
|
this.loadCustomIcons();
|
|
}
|
|
},
|
|
|
|
loadCustomIcons: function() {
|
|
var self = this;
|
|
var $grid = $('#kundenkarte-custom-icons');
|
|
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/icon_upload.php',
|
|
data: { action: 'list' },
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
$grid.empty();
|
|
|
|
if (response.icons && response.icons.length > 0) {
|
|
self.customIcons = response.icons;
|
|
for (var i = 0; i < response.icons.length; i++) {
|
|
var icon = response.icons[i];
|
|
var html = '<div class="kundenkarte-icon-item kundenkarte-custom-icon-item" data-icon="' + icon.url + '" data-type="custom" title="' + icon.name + '" style="position:relative;">';
|
|
html += '<img src="' + icon.url + '" alt="' + icon.name + '" style="max-width:32px;max-height:32px;">';
|
|
html += '<span class="kundenkarte-icon-delete" data-filename="' + icon.filename + '" title="Löschen" style="position:absolute;top:-5px;right:-5px;background:#c00;color:#fff;border-radius:50%;width:16px;height:16px;font-size:10px;line-height:16px;text-align:center;cursor:pointer;display:none;">×</span>';
|
|
html += '</div>';
|
|
$grid.append(html);
|
|
}
|
|
} else {
|
|
$grid.html('<p class="opacitymedium" style="grid-column:1/-1;text-align:center;">Noch keine eigenen Icons hochgeladen.</p>');
|
|
}
|
|
},
|
|
error: function() {
|
|
$grid.html('<p class="warning" style="grid-column:1/-1;text-align:center;">Fehler beim Laden der Icons.</p>');
|
|
}
|
|
});
|
|
},
|
|
|
|
uploadIcon: function(file) {
|
|
var self = this;
|
|
var formData = new FormData();
|
|
formData.append('action', 'upload');
|
|
formData.append('icon', file);
|
|
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/icon_upload.php',
|
|
method: 'POST',
|
|
data: formData,
|
|
processData: false,
|
|
contentType: false,
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
self.loadCustomIcons();
|
|
// Auto-select uploaded icon
|
|
setTimeout(function() {
|
|
self.selectIcon(response.icon.url, 'custom');
|
|
}, 500);
|
|
} else {
|
|
alert('Fehler: ' + (response.error || 'Unbekannter Fehler'));
|
|
}
|
|
},
|
|
error: function(xhr) {
|
|
var msg = 'Upload fehlgeschlagen';
|
|
try {
|
|
var resp = JSON.parse(xhr.responseText);
|
|
msg = resp.error || msg;
|
|
} catch(e) {}
|
|
alert(msg);
|
|
}
|
|
});
|
|
|
|
// Reset input
|
|
$('#kundenkarte-icon-upload').val('');
|
|
},
|
|
|
|
deleteIcon: function(filename) {
|
|
var self = this;
|
|
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/icon_upload.php',
|
|
method: 'POST',
|
|
data: { action: 'delete', filename: filename },
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
self.loadCustomIcons();
|
|
} else {
|
|
alert('Fehler: ' + (response.error || 'Löschen fehlgeschlagen'));
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
showDeleteConfirm: function(filename) {
|
|
var self = this;
|
|
|
|
// Remove any existing confirm dialog
|
|
$('#kundenkarte-delete-confirm').remove();
|
|
|
|
// Create Dolibarr-style confirmation dialog
|
|
var html = '<div id="kundenkarte-delete-confirm" class="kundenkarte-confirm-overlay" style="position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:10001;display:flex;align-items:center;justify-content:center;">';
|
|
html += '<div class="kundenkarte-confirm-box" style="background:#fff;border-radius:6px;box-shadow:0 4px 20px rgba(0,0,0,0.3);max-width:400px;width:90%;">';
|
|
|
|
// Header (Dolibarr style)
|
|
html += '<div class="kundenkarte-confirm-header" style="background:linear-gradient(to bottom,#f8f8f8,#e8e8e8);border-bottom:1px solid #ccc;padding:12px 15px;border-radius:6px 6px 0 0;">';
|
|
html += '<span style="font-weight:bold;font-size:14px;"><i class="fa fa-exclamation-triangle" style="color:#f0ad4e;margin-right:8px;"></i>Bestätigung</span>';
|
|
html += '</div>';
|
|
|
|
// Body
|
|
html += '<div class="kundenkarte-confirm-body" style="padding:20px;text-align:center;">';
|
|
html += '<p style="margin:0 0 5px 0;font-size:14px;">Möchten Sie dieses Icon wirklich löschen?</p>';
|
|
html += '<p style="margin:0;color:#666;font-size:12px;"><code>' + this.escapeHtml(filename) + '</code></p>';
|
|
html += '</div>';
|
|
|
|
// Footer with buttons (Dolibarr style)
|
|
html += '<div class="kundenkarte-confirm-footer" style="background:#f5f5f5;border-top:1px solid #ddd;padding:12px 15px;text-align:center;border-radius:0 0 6px 6px;">';
|
|
html += '<button type="button" class="button" id="kundenkarte-confirm-yes" style="background:#c9302c;color:#fff;border:1px solid #ac2925;padding:6px 20px;margin-right:10px;cursor:pointer;"><i class="fa fa-check"></i> Ja, löschen</button>';
|
|
html += '<button type="button" class="button" id="kundenkarte-confirm-no" style="padding:6px 20px;cursor:pointer;"><i class="fa fa-times"></i> Abbrechen</button>';
|
|
html += '</div>';
|
|
|
|
html += '</div>';
|
|
html += '</div>';
|
|
|
|
$('body').append(html);
|
|
|
|
// Bind events
|
|
$('#kundenkarte-confirm-yes').on('click', function() {
|
|
$('#kundenkarte-delete-confirm').remove();
|
|
self.deleteIcon(filename);
|
|
});
|
|
|
|
$('#kundenkarte-confirm-no, #kundenkarte-delete-confirm').on('click', function(e) {
|
|
if (e.target === this) {
|
|
$('#kundenkarte-delete-confirm').remove();
|
|
}
|
|
});
|
|
|
|
// ESC to close
|
|
$(document).one('keydown.confirmDialog', function(e) {
|
|
if (e.key === 'Escape') {
|
|
$('#kundenkarte-delete-confirm').remove();
|
|
}
|
|
});
|
|
},
|
|
|
|
escapeHtml: function(text) {
|
|
if (!text) return '';
|
|
var div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
},
|
|
|
|
selectIcon: function(icon, type) {
|
|
if (this.currentInput) {
|
|
// For custom icons, store the URL with a prefix to distinguish
|
|
var value = (type === 'custom') ? 'img:' + icon : icon;
|
|
this.currentInput.val(value);
|
|
|
|
// Update preview
|
|
var $wrapper = this.currentInput.closest('.kundenkarte-icon-picker-wrapper');
|
|
var $preview = $wrapper.find('.kundenkarte-icon-preview');
|
|
if ($preview.length) {
|
|
if (type === 'custom') {
|
|
$preview.html('<img src="' + icon + '" style="max-width:20px;max-height:20px;">');
|
|
} else {
|
|
$preview.html('<i class="fa ' + icon + '"></i>');
|
|
}
|
|
}
|
|
}
|
|
this.close();
|
|
},
|
|
|
|
open: function() {
|
|
$('#kundenkarte-icon-search').val('');
|
|
$('#kundenkarte-fa-icons .kundenkarte-icon-item').show();
|
|
this.switchTab('fontawesome');
|
|
$('#kundenkarte-icon-picker-modal').addClass('visible');
|
|
},
|
|
|
|
close: function() {
|
|
$('#kundenkarte-icon-picker-modal').removeClass('visible');
|
|
this.currentInput = null;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Dynamic Fields Component
|
|
* Loads and renders type-specific fields when creating/editing anlagen
|
|
*/
|
|
KundenKarte.DynamicFields = {
|
|
init: function() {
|
|
var self = this;
|
|
var $typeSelect = $('select[name="fk_anlage_type"]');
|
|
var $container = $('#dynamic_fields');
|
|
|
|
if (!$typeSelect.length || !$container.length) return;
|
|
|
|
// Load fields when type changes
|
|
$typeSelect.on('change', function() {
|
|
self.loadFields($(this).val());
|
|
});
|
|
|
|
// Load initial fields if type is already selected
|
|
if ($typeSelect.val()) {
|
|
self.loadFields($typeSelect.val());
|
|
}
|
|
},
|
|
|
|
loadFields: function(typeId) {
|
|
var $container = $('#dynamic_fields');
|
|
if (!typeId) {
|
|
$container.html('');
|
|
return;
|
|
}
|
|
|
|
// Get anlage_id if editing or copying
|
|
var anlageId = $('input[name="anlage_id"]').val() || $('input[name="copy_from"]').val() || 0;
|
|
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/type_fields.php',
|
|
data: { type_id: typeId, anlage_id: anlageId },
|
|
dataType: 'json',
|
|
success: function(data) {
|
|
if (data.fields && data.fields.length > 0) {
|
|
var html = '';
|
|
|
|
data.fields.forEach(function(field) {
|
|
if (field.type === 'header') {
|
|
// Header row spans both columns with styling
|
|
html += '<tr class="liste_titre dynamic-field-row"><th colspan="2" style="background:#f0f0f0;padding:8px;">' + KundenKarte.DynamicFields.escapeHtml(field.label) + '</th></tr>';
|
|
} else {
|
|
html += '<tr class="dynamic-field-row"><td class="titlefield">' + KundenKarte.DynamicFields.escapeHtml(field.label);
|
|
if (field.required) html += ' <span class="fieldrequired">*</span>';
|
|
html += '</td><td>';
|
|
html += KundenKarte.DynamicFields.renderField(field);
|
|
html += '</td></tr>';
|
|
}
|
|
});
|
|
|
|
$container.html(html);
|
|
} else {
|
|
$container.html('');
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
renderField: function(field) {
|
|
var name = 'field_' + field.code;
|
|
var value = field.value || '';
|
|
var required = field.required ? ' required' : '';
|
|
|
|
switch (field.type) {
|
|
case 'text':
|
|
return '<input type="text" name="' + name + '" class="flat minwidth300" value="' + this.escapeHtml(value) + '"' + required + '>';
|
|
|
|
case 'textarea':
|
|
return '<textarea name="' + name + '" class="flat minwidth300" rows="3"' + required + '>' + this.escapeHtml(value) + '</textarea>';
|
|
|
|
case 'number':
|
|
var attrs = '';
|
|
if (field.options) {
|
|
var opts = field.options.split('|');
|
|
opts.forEach(function(opt) {
|
|
var parts = opt.split(':');
|
|
if (parts.length === 2) {
|
|
attrs += ' ' + parts[0] + '="' + parts[1] + '"';
|
|
}
|
|
});
|
|
}
|
|
return '<input type="number" name="' + name + '" class="flat" value="' + this.escapeHtml(value) + '"' + attrs + required + '>';
|
|
|
|
case 'select':
|
|
var html = '<select name="' + name + '" class="flat minwidth200"' + required + '>';
|
|
html += '<option value="">-- Auswählen --</option>';
|
|
if (field.options) {
|
|
var options = field.options.split('|');
|
|
options.forEach(function(opt) {
|
|
var selected = (opt === value) ? ' selected' : '';
|
|
html += '<option value="' + KundenKarte.DynamicFields.escapeHtml(opt) + '"' + selected + '>' + KundenKarte.DynamicFields.escapeHtml(opt) + '</option>';
|
|
});
|
|
}
|
|
html += '</select>';
|
|
return html;
|
|
|
|
case 'date':
|
|
var inputId = 'date_' + name.replace(/[^a-zA-Z0-9]/g, '_');
|
|
return '<input type="date" name="' + name + '" id="' + inputId + '" class="flat" value="' + this.escapeHtml(value) + '"' + required + '>' +
|
|
'<button type="button" class="button buttongen" style="margin-left:4px;padding:2px 6px;font-size:11px;" onclick="document.getElementById(\'' + inputId + '\').value = new Date().toISOString().split(\'T\')[0];" title="Heute">Heute</button>';
|
|
|
|
case 'checkbox':
|
|
var checked = (value === '1' || value === 'true' || value === 'yes') ? ' checked' : '';
|
|
return '<input type="checkbox" name="' + name + '" value="1"' + checked + '>';
|
|
|
|
default:
|
|
return '<input type="text" name="' + name + '" class="flat minwidth300" value="' + this.escapeHtml(value) + '"' + required + '>';
|
|
}
|
|
},
|
|
|
|
escapeHtml: function(text) {
|
|
if (!text) return '';
|
|
var div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Equipment Component
|
|
* Manages Hutschienen (DIN rail) components with SVG visualization
|
|
*/
|
|
KundenKarte.Equipment = {
|
|
TE_WIDTH: 50, // Width of one TE in pixels (wider for more space)
|
|
BLOCK_HEIGHT: 110, // Height of equipment block in pixels
|
|
currentCarrierId: null,
|
|
currentAnlageId: null,
|
|
currentSystemId: null,
|
|
draggedEquipment: null,
|
|
isSaving: false, // Prevent double-clicks
|
|
|
|
init: function(anlageId, systemId) {
|
|
if (!anlageId) return;
|
|
this.currentAnlageId = anlageId;
|
|
this.currentSystemId = systemId || 0;
|
|
this.bindEvents();
|
|
},
|
|
|
|
bindEvents: function() {
|
|
var self = this;
|
|
|
|
// Add panel button
|
|
$(document).on('click', '.kundenkarte-add-panel', function(e) {
|
|
e.preventDefault();
|
|
var anlageId = $(this).data('anlage-id');
|
|
self.showPanelDialog(anlageId);
|
|
});
|
|
|
|
// Edit panel
|
|
$(document).on('click', '.kundenkarte-panel-edit', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
var panelId = $(this).closest('.kundenkarte-panel').data('panel-id');
|
|
self.showPanelDialog(self.currentAnlageId, panelId);
|
|
});
|
|
|
|
// Delete panel
|
|
$(document).on('click', '.kundenkarte-panel-delete', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
var panelId = $(this).closest('.kundenkarte-panel').data('panel-id');
|
|
self.deletePanel(panelId);
|
|
});
|
|
|
|
// Quick-duplicate panel (+ button next to last panel)
|
|
$(document).on('click', '.kundenkarte-panel-quickadd', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
var panelId = $(this).data('panel-id');
|
|
self.duplicatePanel(panelId);
|
|
});
|
|
|
|
// Quick-duplicate carrier (+ button below last carrier)
|
|
$(document).on('click', '.kundenkarte-carrier-quickadd', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
var carrierId = $(this).data('carrier-id');
|
|
self.duplicateCarrier(carrierId);
|
|
});
|
|
|
|
// Add carrier button
|
|
$(document).on('click', '.kundenkarte-add-carrier', function(e) {
|
|
e.preventDefault();
|
|
var anlageId = $(this).data('anlage-id');
|
|
var panelId = $(this).data('panel-id') || 0;
|
|
self.showCarrierDialog(anlageId, null, panelId);
|
|
});
|
|
|
|
// Edit carrier
|
|
$(document).on('click', '.kundenkarte-carrier-edit', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
var carrierId = $(this).closest('.kundenkarte-carrier').data('carrier-id');
|
|
self.showCarrierDialog(self.currentAnlageId, carrierId);
|
|
});
|
|
|
|
// Delete carrier
|
|
$(document).on('click', '.kundenkarte-carrier-delete', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
var carrierId = $(this).closest('.kundenkarte-carrier').data('carrier-id');
|
|
self.deleteCarrier(carrierId);
|
|
});
|
|
|
|
// Add equipment (click on empty slot or + button)
|
|
$(document).on('click', '.kundenkarte-carrier-add-equipment, .kundenkarte-slot-empty', function(e) {
|
|
e.preventDefault();
|
|
var $carrier = $(this).closest('.kundenkarte-carrier');
|
|
var carrierId = $carrier.data('carrier-id');
|
|
var position = $(this).data('position') || null;
|
|
self.showEquipmentDialog(carrierId, null, position);
|
|
});
|
|
|
|
// Edit equipment (click on block)
|
|
$(document).on('click', '.kundenkarte-equipment-block', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
var equipmentId = $(this).data('equipment-id');
|
|
self.showEquipmentDialog(null, equipmentId);
|
|
});
|
|
|
|
// Quick-add slot (duplicate last equipment into next free position)
|
|
$(document).on('click', '.kundenkarte-slot-quickadd', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
var equipmentId = $(this).data('equipment-id');
|
|
self.duplicateEquipment(equipmentId);
|
|
});
|
|
|
|
// Delete equipment
|
|
$(document).on('click', '.kundenkarte-equipment-delete', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
var equipmentId = $(this).closest('.kundenkarte-equipment-block').data('equipment-id');
|
|
self.deleteEquipment(equipmentId);
|
|
});
|
|
|
|
// Add output connection
|
|
$(document).on('click', '.kundenkarte-add-output-btn', function(e) {
|
|
e.preventDefault();
|
|
var carrierId = $(this).data('carrier-id');
|
|
self.showOutputDialog(carrierId);
|
|
});
|
|
|
|
// Add rail connection
|
|
$(document).on('click', '.kundenkarte-add-rail-btn', function(e) {
|
|
e.preventDefault();
|
|
var carrierId = $(this).data('carrier-id');
|
|
self.showRailDialog(carrierId);
|
|
});
|
|
|
|
// Click on rail to edit
|
|
$(document).on('click', '.kundenkarte-rail', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
var connectionId = $(this).data('connection-id');
|
|
// Find carrier ID from connections container or carrier element
|
|
var carrierId = $(this).closest('.kundenkarte-connections-container').data('carrier-id') ||
|
|
$(this).closest('.kundenkarte-carrier').data('carrier-id');
|
|
self.showEditRailDialog(connectionId, carrierId);
|
|
});
|
|
|
|
// Click on output to edit
|
|
$(document).on('click', '.kundenkarte-output', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
var connectionId = $(this).data('connection-id');
|
|
// Find carrier ID from connections container or carrier element
|
|
var carrierId = $(this).closest('.kundenkarte-connections-container').data('carrier-id') ||
|
|
$(this).closest('.kundenkarte-carrier').data('carrier-id');
|
|
self.showEditOutputDialog(connectionId, carrierId);
|
|
});
|
|
|
|
// Click on connection to delete (generic connections)
|
|
$(document).on('click', '.kundenkarte-connection', function(e) {
|
|
e.preventDefault();
|
|
var connectionId = $(this).data('connection-id');
|
|
self.deleteConnection(connectionId);
|
|
});
|
|
|
|
// Drag & Drop events
|
|
$(document).on('dragstart', '.kundenkarte-equipment-block', function(e) {
|
|
self.draggedEquipment = $(this);
|
|
$(this).addClass('dragging');
|
|
e.originalEvent.dataTransfer.effectAllowed = 'move';
|
|
e.originalEvent.dataTransfer.setData('text/plain', $(this).data('equipment-id'));
|
|
});
|
|
|
|
$(document).on('dragend', '.kundenkarte-equipment-block', function() {
|
|
$(this).removeClass('dragging');
|
|
self.draggedEquipment = null;
|
|
$('.kundenkarte-slot-drop-target').removeClass('kundenkarte-slot-drop-target');
|
|
});
|
|
|
|
$(document).on('dragover', '.kundenkarte-slot-empty, .kundenkarte-carrier-slots', function(e) {
|
|
e.preventDefault();
|
|
e.originalEvent.dataTransfer.dropEffect = 'move';
|
|
});
|
|
|
|
$(document).on('dragenter', '.kundenkarte-slot-empty', function(e) {
|
|
e.preventDefault();
|
|
$(this).addClass('kundenkarte-slot-drop-target');
|
|
});
|
|
|
|
$(document).on('dragleave', '.kundenkarte-slot-empty', function() {
|
|
$(this).removeClass('kundenkarte-slot-drop-target');
|
|
});
|
|
|
|
$(document).on('drop', '.kundenkarte-slot-empty', function(e) {
|
|
e.preventDefault();
|
|
$(this).removeClass('kundenkarte-slot-drop-target');
|
|
|
|
if (self.draggedEquipment) {
|
|
var equipmentId = self.draggedEquipment.data('equipment-id');
|
|
var newPosition = $(this).data('position');
|
|
self.moveEquipment(equipmentId, newPosition);
|
|
}
|
|
});
|
|
|
|
// Equipment type change in dialog
|
|
$(document).on('change', '#equipment_type_id', function() {
|
|
self.loadEquipmentTypeFields($(this).val());
|
|
});
|
|
|
|
// Hover tooltip for equipment
|
|
$(document).on('mouseenter', '.kundenkarte-equipment-block', function() {
|
|
var $block = $(this);
|
|
var tooltipData = $block.attr('data-tooltip');
|
|
if (tooltipData) {
|
|
self.showEquipmentTooltip($block, JSON.parse(tooltipData));
|
|
}
|
|
});
|
|
|
|
$(document).on('mouseleave', '.kundenkarte-equipment-block', function() {
|
|
self.hideEquipmentTooltip();
|
|
});
|
|
},
|
|
|
|
loadCarriers: function(anlageId) {
|
|
var self = this;
|
|
var $container = $('.kundenkarte-equipment-container[data-anlage-id="' + anlageId + '"]');
|
|
|
|
if (!$container.length) return;
|
|
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment_carrier.php',
|
|
data: { action: 'list', anlage_id: anlageId },
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
self.renderCarriers($container, response.carriers);
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
renderCarriers: function($container, carriers) {
|
|
var self = this;
|
|
var html = '';
|
|
|
|
if (carriers.length === 0) {
|
|
html = '<div class="opacitymedium" style="padding:10px;">Keine Hutschienen vorhanden. Klicken Sie auf "Hutschiene hinzufügen".</div>';
|
|
} else {
|
|
carriers.forEach(function(carrier) {
|
|
html += self.renderCarrier(carrier);
|
|
});
|
|
}
|
|
|
|
$container.find('.kundenkarte-carriers-list').html(html);
|
|
},
|
|
|
|
renderCarrier: function(carrier) {
|
|
var self = this;
|
|
var totalWidth = carrier.total_te * this.TE_WIDTH;
|
|
|
|
var html = '<div class="kundenkarte-carrier" data-carrier-id="' + carrier.id + '">';
|
|
|
|
// Header
|
|
html += '<div class="kundenkarte-carrier-header">';
|
|
html += '<span class="kundenkarte-carrier-label">' + this.escapeHtml(carrier.label || 'Hutschiene') + '</span>';
|
|
html += '<span class="kundenkarte-carrier-info">' + carrier.used_te + '/' + carrier.total_te + ' TE belegt</span>';
|
|
html += '<span class="kundenkarte-carrier-actions">';
|
|
html += '<a href="#" class="kundenkarte-carrier-add-equipment" title="Equipment hinzufügen"><i class="fa fa-plus"></i></a>';
|
|
html += '<a href="#" class="kundenkarte-carrier-edit" title="Bearbeiten"><i class="fa fa-edit"></i></a>';
|
|
html += '<a href="#" class="kundenkarte-carrier-delete" title="Löschen"><i class="fa fa-trash"></i></a>';
|
|
html += '</span>';
|
|
html += '</div>';
|
|
|
|
// SVG Rail
|
|
html += '<div class="kundenkarte-carrier-svg-container" style="width:' + totalWidth + 'px;">';
|
|
html += '<svg class="kundenkarte-carrier-svg" width="' + totalWidth + '" height="' + this.BLOCK_HEIGHT + '" viewBox="0 0 ' + totalWidth + ' ' + this.BLOCK_HEIGHT + '">';
|
|
|
|
// Background grid (TE markers)
|
|
for (var i = 0; i <= carrier.total_te; i++) {
|
|
var x = i * this.TE_WIDTH;
|
|
html += '<line x1="' + x + '" y1="0" x2="' + x + '" y2="' + this.BLOCK_HEIGHT + '" stroke="#ddd" stroke-width="0.5"/>';
|
|
}
|
|
|
|
// Render equipment blocks
|
|
if (carrier.equipment) {
|
|
carrier.equipment.forEach(function(eq) {
|
|
html += self.renderEquipmentBlock(eq);
|
|
});
|
|
}
|
|
|
|
html += '</svg>';
|
|
|
|
// Clickable slots overlay (for adding equipment)
|
|
html += '<div class="kundenkarte-carrier-slots">';
|
|
var occupiedSlots = this.getOccupiedSlots(carrier.equipment);
|
|
for (var pos = 1; pos <= carrier.total_te; pos++) {
|
|
if (!occupiedSlots[pos]) {
|
|
var slotLeft = (pos - 1) * this.TE_WIDTH;
|
|
html += '<div class="kundenkarte-slot-empty" data-position="' + pos + '" style="left:' + slotLeft + 'px;width:' + this.TE_WIDTH + 'px;"></div>';
|
|
}
|
|
}
|
|
html += '</div>';
|
|
|
|
html += '</div>'; // svg-container
|
|
html += '</div>'; // carrier
|
|
|
|
return html;
|
|
},
|
|
|
|
renderEquipmentBlock: function(equipment) {
|
|
var x = (equipment.position_te - 1) * this.TE_WIDTH;
|
|
var width = equipment.width_te * this.TE_WIDTH;
|
|
var color = equipment.block_color || equipment.type_color || '#3498db';
|
|
var label = equipment.block_label || equipment.type_label_short || '';
|
|
|
|
// Build tooltip data
|
|
var tooltipData = {
|
|
label: equipment.label,
|
|
type: equipment.type_label,
|
|
fields: equipment.field_values || {}
|
|
};
|
|
|
|
var html = '<g class="kundenkarte-equipment-block" data-equipment-id="' + equipment.id + '" ';
|
|
html += 'data-tooltip=\'' + JSON.stringify(tooltipData).replace(/'/g, "'") + '\' ';
|
|
html += 'draggable="true" style="cursor:pointer;">';
|
|
|
|
// Block rectangle with rounded corners
|
|
html += '<rect x="' + (x + 1) + '" y="2" width="' + (width - 2) + '" height="' + (this.BLOCK_HEIGHT - 4) + '" ';
|
|
html += 'rx="3" ry="3" fill="' + color + '" stroke="#333" stroke-width="1"/>';
|
|
|
|
// Label text (centered)
|
|
var fontSize = width < 40 ? 11 : 14;
|
|
html += '<text x="' + (x + width/2) + '" y="' + (this.BLOCK_HEIGHT/2) + '" ';
|
|
html += 'text-anchor="middle" dominant-baseline="middle" ';
|
|
html += 'fill="#fff" font-size="' + fontSize + 'px" font-weight="bold">';
|
|
html += this.escapeHtml(label);
|
|
html += '</text>';
|
|
|
|
// Duplicate button (+) at the right edge
|
|
html += '</g>';
|
|
|
|
return html;
|
|
},
|
|
|
|
getOccupiedSlots: function(equipment) {
|
|
var slots = {};
|
|
if (equipment) {
|
|
equipment.forEach(function(eq) {
|
|
for (var i = 0; i < eq.width_te; i++) {
|
|
slots[eq.position_te + i] = true;
|
|
}
|
|
});
|
|
}
|
|
return slots;
|
|
},
|
|
|
|
// Panel dialog functions
|
|
showPanelDialog: function(anlageId, panelId) {
|
|
var self = this;
|
|
|
|
if ($('#kundenkarte-panel-dialog').length) return;
|
|
|
|
var isEdit = !!panelId;
|
|
var title = isEdit ? 'Feld bearbeiten' : 'Feld hinzufügen';
|
|
|
|
var panelData = { label: '' };
|
|
|
|
var showDialog = function(data) {
|
|
var html = '<div id="kundenkarte-panel-dialog" class="kundenkarte-modal">';
|
|
html += '<div class="kundenkarte-modal-content" style="max-width:400px;">';
|
|
html += '<div class="kundenkarte-modal-header"><h3>' + title + '</h3>';
|
|
html += '<span class="kundenkarte-modal-close">×</span></div>';
|
|
html += '<div class="kundenkarte-modal-body">';
|
|
html += '<table class="noborder" style="width:100%;">';
|
|
html += '<tr><td class="titlefield">Bezeichnung</td>';
|
|
html += '<td><input type="text" name="panel_label" class="flat minwidth200" placeholder="z.B. Feld 1" value="' + self.escapeHtml(data.label) + '"></td></tr>';
|
|
html += '</table>';
|
|
html += '</div>';
|
|
html += '<div class="kundenkarte-modal-footer">';
|
|
html += '<button type="button" class="button" id="panel-save"><i class="fa fa-save"></i> Speichern</button> ';
|
|
html += '<button type="button" class="button" id="panel-cancel">Abbrechen</button>';
|
|
html += '</div>';
|
|
html += '</div></div>';
|
|
|
|
$('body').append(html);
|
|
$('#kundenkarte-panel-dialog').addClass('visible');
|
|
|
|
$('#panel-save').on('click', function() {
|
|
var label = $('input[name="panel_label"]').val();
|
|
self.savePanel(anlageId, panelId, label);
|
|
});
|
|
|
|
$('#panel-cancel, .kundenkarte-modal-close').on('click', function() {
|
|
$('#kundenkarte-panel-dialog').remove();
|
|
});
|
|
|
|
$(document).on('keydown.panelDialog', function(e) {
|
|
if (e.key === 'Escape') {
|
|
$('#kundenkarte-panel-dialog').remove();
|
|
$(document).off('keydown.panelDialog');
|
|
}
|
|
});
|
|
};
|
|
|
|
if (isEdit) {
|
|
var $panel = $('.kundenkarte-panel[data-panel-id="' + panelId + '"]');
|
|
panelData.label = $panel.find('.kundenkarte-panel-label').text();
|
|
showDialog(panelData);
|
|
} else {
|
|
showDialog(panelData);
|
|
}
|
|
},
|
|
|
|
savePanel: function(anlageId, panelId, label) {
|
|
var self = this;
|
|
|
|
if (self.isSaving) return;
|
|
self.isSaving = true;
|
|
$('#panel-save').prop('disabled', true);
|
|
|
|
var action = panelId ? 'update' : 'create';
|
|
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment_panel.php',
|
|
method: 'POST',
|
|
data: {
|
|
action: action,
|
|
anlage_id: anlageId,
|
|
panel_id: panelId,
|
|
label: label,
|
|
token: $('input[name="token"]').val()
|
|
},
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
self.isSaving = false;
|
|
if (response.success) {
|
|
$('#kundenkarte-panel-dialog').remove();
|
|
location.reload();
|
|
} else {
|
|
$('#panel-save').prop('disabled', false);
|
|
alert('Fehler: ' + response.error);
|
|
}
|
|
},
|
|
error: function() {
|
|
self.isSaving = false;
|
|
$('#panel-save').prop('disabled', false);
|
|
alert('Netzwerkfehler');
|
|
}
|
|
});
|
|
},
|
|
|
|
deletePanel: function(panelId) {
|
|
var self = this;
|
|
self.showConfirmDialog('Feld löschen', 'Möchten Sie dieses Feld wirklich löschen? Alle Hutschienen und Equipment werden ebenfalls gelöscht.', function() {
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment_panel.php',
|
|
method: 'POST',
|
|
data: {
|
|
action: 'delete',
|
|
panel_id: panelId,
|
|
token: $('input[name="token"]').val()
|
|
},
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
location.reload();
|
|
} else {
|
|
self.showAlertDialog('Fehler', response.error);
|
|
}
|
|
},
|
|
error: function() {
|
|
self.showAlertDialog('Fehler', 'Netzwerkfehler');
|
|
}
|
|
});
|
|
});
|
|
},
|
|
|
|
duplicatePanel: function(panelId) {
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment_panel.php',
|
|
method: 'POST',
|
|
data: {
|
|
action: 'duplicate',
|
|
panel_id: panelId,
|
|
token: $('input[name="token"]').val()
|
|
},
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
location.reload();
|
|
} else {
|
|
alert('Fehler: ' + response.error);
|
|
}
|
|
},
|
|
error: function() {
|
|
alert('Netzwerkfehler');
|
|
}
|
|
});
|
|
},
|
|
|
|
duplicateCarrier: function(carrierId) {
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment_carrier.php',
|
|
method: 'POST',
|
|
data: {
|
|
action: 'duplicate',
|
|
carrier_id: carrierId,
|
|
token: $('input[name="token"]').val()
|
|
},
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
location.reload();
|
|
} else {
|
|
alert('Fehler: ' + response.error);
|
|
}
|
|
},
|
|
error: function() {
|
|
alert('Netzwerkfehler');
|
|
}
|
|
});
|
|
},
|
|
|
|
showCarrierDialog: function(anlageId, carrierId, panelId) {
|
|
var self = this;
|
|
|
|
// Prevent opening multiple dialogs
|
|
if ($('#kundenkarte-carrier-dialog').length) return;
|
|
|
|
var isEdit = !!carrierId;
|
|
var title = isEdit ? 'Hutschiene bearbeiten' : 'Hutschiene hinzufügen';
|
|
panelId = panelId || 0;
|
|
|
|
// Load carrier data if editing
|
|
var carrierData = { label: '', total_te: 12, fk_panel: panelId };
|
|
|
|
var showDialog = function(data, panels) {
|
|
var html = '<div id="kundenkarte-carrier-dialog" class="kundenkarte-modal">';
|
|
html += '<div class="kundenkarte-modal-content" style="max-width:400px;">';
|
|
html += '<div class="kundenkarte-modal-header"><h3>' + title + '</h3>';
|
|
html += '<span class="kundenkarte-modal-close">×</span></div>';
|
|
html += '<div class="kundenkarte-modal-body">';
|
|
html += '<input type="hidden" name="carrier_id" value="' + (carrierId || '') + '">';
|
|
html += '<table class="noborder" style="width:100%;">';
|
|
html += '<tr><td class="titlefield">Bezeichnung</td>';
|
|
html += '<td><input type="text" name="carrier_label" class="flat minwidth200" value="' + self.escapeHtml(data.label) + '"></td></tr>';
|
|
html += '<tr><td>Kapazität (TE)</td>';
|
|
html += '<td><input type="number" name="carrier_total_te" class="flat" min="1" max="72" value="' + data.total_te + '"></td></tr>';
|
|
|
|
// Panel dropdown (only show if panels exist)
|
|
if (panels && panels.length > 0) {
|
|
html += '<tr><td>Feld</td>';
|
|
html += '<td><select name="carrier_panel_id" class="flat minwidth200">';
|
|
html += '<option value="0">-- Kein Feld (direkt) --</option>';
|
|
panels.forEach(function(p) {
|
|
var selected = (data.fk_panel == p.id) ? ' selected' : '';
|
|
html += '<option value="' + p.id + '"' + selected + '>' + self.escapeHtml(p.label) + '</option>';
|
|
});
|
|
html += '</select></td></tr>';
|
|
}
|
|
|
|
html += '</table>';
|
|
html += '</div>';
|
|
html += '<div class="kundenkarte-modal-footer">';
|
|
html += '<button type="button" class="button" id="carrier-save"><i class="fa fa-save"></i> Speichern</button> ';
|
|
html += '<button type="button" class="button" id="carrier-cancel">Abbrechen</button>';
|
|
html += '</div>';
|
|
html += '</div></div>';
|
|
|
|
$('body').append(html);
|
|
$('#kundenkarte-carrier-dialog').addClass('visible');
|
|
|
|
// Save button
|
|
$('#carrier-save').on('click', function() {
|
|
var label = $('input[name="carrier_label"]').val();
|
|
var totalTe = parseInt($('input[name="carrier_total_te"]').val()) || 12;
|
|
var selectedPanelId = $('select[name="carrier_panel_id"]').val() || 0;
|
|
|
|
self.saveCarrier(anlageId, carrierId, label, totalTe, selectedPanelId);
|
|
});
|
|
|
|
// Close
|
|
$('#carrier-cancel, .kundenkarte-modal-close').on('click', function() {
|
|
$('#kundenkarte-carrier-dialog').remove();
|
|
});
|
|
|
|
$(document).on('keydown.carrierDialog', function(e) {
|
|
if (e.key === 'Escape') {
|
|
$('#kundenkarte-carrier-dialog').remove();
|
|
$(document).off('keydown.carrierDialog');
|
|
}
|
|
});
|
|
};
|
|
|
|
// Load panels for the anlage
|
|
var loadPanelsAndShow = function() {
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment_panel.php',
|
|
data: { action: 'list', anlage_id: self.currentAnlageId },
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
var panels = response.success ? response.panels : [];
|
|
showDialog(carrierData, panels);
|
|
},
|
|
error: function() {
|
|
showDialog(carrierData, []);
|
|
}
|
|
});
|
|
};
|
|
|
|
if (isEdit) {
|
|
// Fetch existing carrier data via AJAX
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment_carrier.php',
|
|
data: { action: 'get', carrier_id: carrierId },
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success && response.carrier) {
|
|
carrierData.label = response.carrier.label;
|
|
carrierData.total_te = response.carrier.total_te;
|
|
carrierData.fk_panel = response.carrier.fk_panel || 0;
|
|
}
|
|
loadPanelsAndShow();
|
|
},
|
|
error: function() {
|
|
// Fallback to DOM data
|
|
var $carrier = $('.kundenkarte-carrier[data-carrier-id="' + carrierId + '"]');
|
|
carrierData.label = $carrier.find('.kundenkarte-carrier-label').text();
|
|
var infoText = $carrier.find('.kundenkarte-carrier-info').text();
|
|
var match = infoText.match(/\/(\d+)/);
|
|
if (match) carrierData.total_te = parseInt(match[1]);
|
|
loadPanelsAndShow();
|
|
}
|
|
});
|
|
} else {
|
|
carrierData.fk_panel = panelId;
|
|
loadPanelsAndShow();
|
|
}
|
|
},
|
|
|
|
saveCarrier: function(anlageId, carrierId, label, totalTe, panelId) {
|
|
var self = this;
|
|
|
|
// Prevent double-click
|
|
if (self.isSaving) return;
|
|
self.isSaving = true;
|
|
$('#carrier-save').prop('disabled', true);
|
|
|
|
var action = carrierId ? 'update' : 'create';
|
|
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment_carrier.php',
|
|
method: 'POST',
|
|
data: {
|
|
action: action,
|
|
anlage_id: anlageId,
|
|
carrier_id: carrierId,
|
|
panel_id: panelId || 0,
|
|
label: label,
|
|
total_te: totalTe,
|
|
token: $('input[name="token"]').val()
|
|
},
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
self.isSaving = false;
|
|
if (response.success) {
|
|
$('#kundenkarte-carrier-dialog').remove();
|
|
// Reload page to get fresh PHP-rendered carriers
|
|
location.reload();
|
|
} else {
|
|
$('#carrier-save').prop('disabled', false);
|
|
alert('Fehler: ' + response.error);
|
|
}
|
|
},
|
|
error: function() {
|
|
self.isSaving = false;
|
|
$('#carrier-save').prop('disabled', false);
|
|
alert('Netzwerkfehler');
|
|
}
|
|
});
|
|
},
|
|
|
|
deleteCarrier: function(carrierId) {
|
|
var self = this;
|
|
|
|
self.showConfirmDialog('Hutschiene löschen', 'Möchten Sie diese Hutschiene wirklich löschen? Alle Equipment darauf wird ebenfalls gelöscht.', function() {
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment_carrier.php',
|
|
method: 'POST',
|
|
data: {
|
|
action: 'delete',
|
|
carrier_id: carrierId,
|
|
token: $('input[name="token"]').val()
|
|
},
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
location.reload();
|
|
} else {
|
|
self.showAlertDialog('Fehler', response.error);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
},
|
|
|
|
showEquipmentDialog: function(carrierId, equipmentId, position) {
|
|
var self = this;
|
|
|
|
// Prevent opening multiple dialogs
|
|
if ($('#kundenkarte-equipment-dialog').length) return;
|
|
|
|
var isEdit = !!equipmentId;
|
|
var title = isEdit ? 'Equipment bearbeiten' : 'Equipment hinzufügen';
|
|
|
|
// First, load equipment types
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment_type_fields.php',
|
|
data: { type_id: 0 }, // Get types list
|
|
dataType: 'json',
|
|
success: function() {
|
|
self.renderEquipmentDialog(carrierId, equipmentId, position, title, isEdit);
|
|
}
|
|
});
|
|
},
|
|
|
|
renderEquipmentDialog: function(carrierId, equipmentId, position, title, isEdit) {
|
|
var self = this;
|
|
|
|
var html = '<div id="kundenkarte-equipment-dialog" class="kundenkarte-modal">';
|
|
html += '<div class="kundenkarte-modal-content" style="max-width:500px;">';
|
|
html += '<div class="kundenkarte-modal-header"><h3>' + title + '</h3>';
|
|
html += '<span class="kundenkarte-modal-close">×</span></div>';
|
|
html += '<div class="kundenkarte-modal-body">';
|
|
html += '<input type="hidden" name="equipment_carrier_id" value="' + (carrierId || '') + '">';
|
|
html += '<input type="hidden" name="equipment_id" value="' + (equipmentId || '') + '">';
|
|
html += '<input type="hidden" name="equipment_position" value="' + (position || '') + '">';
|
|
html += '<table class="noborder" style="width:100%;">';
|
|
html += '<tr><td class="titlefield">Typ <span class="fieldrequired">*</span></td>';
|
|
html += '<td><select name="equipment_type_id" id="equipment_type_id" class="flat minwidth200">';
|
|
html += '<option value="">-- Typ wählen --</option>';
|
|
html += '</select></td></tr>';
|
|
html += '<tr><td>Bezeichnung</td>';
|
|
html += '<td><input type="text" name="equipment_label" class="flat minwidth200"></td></tr>';
|
|
html += '<tbody id="equipment-dynamic-fields"></tbody>';
|
|
html += '<tr class="protection-fields"><td colspan="2" style="padding-top:15px;border-top:1px solid #444;">';
|
|
html += '<strong><i class="fa fa-shield"></i> FI/RCD-Zuordnung</strong></td></tr>';
|
|
html += '<tr class="protection-fields"><td>Schutzeinrichtung</td>';
|
|
html += '<td><select name="equipment_fk_protection" id="equipment_fk_protection" class="flat minwidth200">';
|
|
html += '<option value="">-- Keine --</option>';
|
|
html += '</select></td></tr>';
|
|
html += '<tr class="protection-fields"><td>Schutzbezeichnung</td>';
|
|
html += '<td><input type="text" name="equipment_protection_label" class="flat minwidth200" placeholder="z.B. FI 40A/30mA"></td></tr>';
|
|
html += '</table>';
|
|
html += '</div>';
|
|
html += '<div class="kundenkarte-modal-footer">';
|
|
html += '<button type="button" class="button" id="equipment-save"><i class="fa fa-save"></i> Speichern</button> ';
|
|
if (isEdit) {
|
|
html += '<button type="button" class="button" id="equipment-delete" style="background:#c0392b;color:#fff;"><i class="fa fa-trash"></i> Löschen</button> ';
|
|
}
|
|
html += '<button type="button" class="button" id="equipment-cancel">Abbrechen</button>';
|
|
html += '</div>';
|
|
html += '</div></div>';
|
|
|
|
$('body').append(html);
|
|
$('#kundenkarte-equipment-dialog').addClass('visible');
|
|
|
|
// Load equipment types into select
|
|
self.loadEquipmentTypes(equipmentId);
|
|
|
|
// Save button
|
|
$('#equipment-save').on('click', function() {
|
|
self.saveEquipment();
|
|
});
|
|
|
|
// Delete button
|
|
$('#equipment-delete').on('click', function() {
|
|
$('#kundenkarte-equipment-dialog').remove();
|
|
self.showConfirmDialog('Equipment löschen', 'Möchten Sie dieses Equipment wirklich löschen?', function() {
|
|
self.deleteEquipment(equipmentId);
|
|
});
|
|
});
|
|
|
|
// Close
|
|
$('#equipment-cancel, .kundenkarte-modal-close').on('click', function() {
|
|
$('#kundenkarte-equipment-dialog').remove();
|
|
});
|
|
|
|
$(document).on('keydown.equipmentDialog', function(e) {
|
|
if (e.key === 'Escape') {
|
|
$('#kundenkarte-equipment-dialog').remove();
|
|
$(document).off('keydown.equipmentDialog');
|
|
}
|
|
});
|
|
},
|
|
|
|
loadEquipmentTypes: function(equipmentId) {
|
|
var self = this;
|
|
|
|
// Get equipment types from admin config (stored in page data or via AJAX)
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment.php',
|
|
data: { action: 'get_types', system_id: self.currentSystemId },
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.types) {
|
|
var $select = $('#equipment_type_id');
|
|
// Clear existing options except first placeholder
|
|
$select.find('option:not(:first)').remove();
|
|
response.types.forEach(function(type) {
|
|
$select.append('<option value="' + type.id + '">' + self.escapeHtml(type.label) + '</option>');
|
|
});
|
|
|
|
// Load protection devices
|
|
self.loadProtectionDevices();
|
|
|
|
// If editing, load equipment data
|
|
if (equipmentId) {
|
|
self.loadEquipmentData(equipmentId);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
loadProtectionDevices: function() {
|
|
var self = this;
|
|
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment.php',
|
|
data: { action: 'get_protection_devices', anlage_id: self.currentAnlageId },
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success && response.devices) {
|
|
var $select = $('#equipment_fk_protection');
|
|
$select.find('option:not(:first)').remove();
|
|
response.devices.forEach(function(device) {
|
|
$select.append('<option value="' + device.id + '">' + self.escapeHtml(device.display_label) + '</option>');
|
|
});
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
loadEquipmentData: function(equipmentId) {
|
|
var self = this;
|
|
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment.php',
|
|
data: { action: 'get', equipment_id: equipmentId },
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.equipment) {
|
|
var eq = response.equipment;
|
|
$('input[name="equipment_carrier_id"]').val(eq.fk_carrier);
|
|
$('input[name="equipment_label"]').val(eq.label);
|
|
$('input[name="equipment_position"]').val(eq.position_te);
|
|
$('#equipment_type_id').val(eq.type_id).trigger('change');
|
|
// Protection fields
|
|
if (eq.fk_protection) {
|
|
$('#equipment_fk_protection').val(eq.fk_protection);
|
|
}
|
|
if (eq.protection_label) {
|
|
$('input[name="equipment_protection_label"]').val(eq.protection_label);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
loadEquipmentTypeFields: function(typeId) {
|
|
var self = this;
|
|
var $container = $('#equipment-dynamic-fields');
|
|
var equipmentId = $('input[name="equipment_id"]').val();
|
|
|
|
if (!typeId) {
|
|
$container.html('');
|
|
return;
|
|
}
|
|
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment_type_fields.php',
|
|
data: { type_id: typeId, equipment_id: equipmentId },
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success && response.fields) {
|
|
var html = '';
|
|
response.fields.forEach(function(field) {
|
|
html += '<tr><td>' + self.escapeHtml(field.label);
|
|
if (field.required) html += ' <span class="fieldrequired">*</span>';
|
|
html += '</td><td>';
|
|
html += self.renderEquipmentField(field);
|
|
html += '</td></tr>';
|
|
});
|
|
$container.html(html);
|
|
} else {
|
|
$container.html('');
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
renderEquipmentField: function(field) {
|
|
var name = 'eq_field_' + field.code;
|
|
var value = field.value || '';
|
|
var required = field.required ? ' required' : '';
|
|
|
|
switch (field.type) {
|
|
case 'select':
|
|
var html = '<select name="' + name + '" class="flat"' + required + '>';
|
|
html += '<option value="">--</option>';
|
|
if (field.options) {
|
|
field.options.split('|').forEach(function(opt) {
|
|
var selected = (opt === value) ? ' selected' : '';
|
|
html += '<option value="' + KundenKarte.Equipment.escapeHtml(opt) + '"' + selected + '>' + KundenKarte.Equipment.escapeHtml(opt) + '</option>';
|
|
});
|
|
}
|
|
html += '</select>';
|
|
return html;
|
|
|
|
case 'number':
|
|
return '<input type="number" name="' + name + '" class="flat" value="' + this.escapeHtml(value) + '"' + required + '>';
|
|
|
|
default:
|
|
return '<input type="text" name="' + name + '" class="flat minwidth200" value="' + this.escapeHtml(value) + '"' + required + '>';
|
|
}
|
|
},
|
|
|
|
saveEquipment: function() {
|
|
var self = this;
|
|
|
|
// Prevent double-click
|
|
if (self.isSaving) return;
|
|
|
|
var carrierId = $('input[name="equipment_carrier_id"]').val();
|
|
var equipmentId = $('input[name="equipment_id"]').val();
|
|
var typeId = $('#equipment_type_id').val();
|
|
var position = $('input[name="equipment_position"]').val();
|
|
var label = $('input[name="equipment_label"]').val();
|
|
var fkProtection = $('#equipment_fk_protection').val();
|
|
var protectionLabel = $('input[name="equipment_protection_label"]').val();
|
|
|
|
if (!typeId) {
|
|
alert('Bitte wählen Sie einen Typ.');
|
|
return;
|
|
}
|
|
|
|
self.isSaving = true;
|
|
$('#equipment-save').prop('disabled', true);
|
|
|
|
// Collect field values
|
|
var fieldValues = {};
|
|
$('#equipment-dynamic-fields input, #equipment-dynamic-fields select').each(function() {
|
|
var name = $(this).attr('name');
|
|
if (name && name.startsWith('eq_field_')) {
|
|
var code = name.replace('eq_field_', '');
|
|
fieldValues[code] = $(this).val();
|
|
}
|
|
});
|
|
|
|
var action = equipmentId ? 'update' : 'create';
|
|
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment.php',
|
|
method: 'POST',
|
|
data: {
|
|
action: action,
|
|
carrier_id: carrierId,
|
|
equipment_id: equipmentId,
|
|
type_id: typeId,
|
|
label: label,
|
|
position_te: position,
|
|
fk_protection: fkProtection,
|
|
protection_label: protectionLabel,
|
|
field_values: JSON.stringify(fieldValues),
|
|
token: $('input[name="token"]').val()
|
|
},
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
self.isSaving = false;
|
|
if (response.success) {
|
|
$('#kundenkarte-equipment-dialog').remove();
|
|
// Reload page to get fresh data
|
|
location.reload();
|
|
} else {
|
|
$('#equipment-save').prop('disabled', false);
|
|
alert('Fehler: ' + response.error);
|
|
}
|
|
},
|
|
error: function() {
|
|
self.isSaving = false;
|
|
$('#equipment-save').prop('disabled', false);
|
|
alert('Netzwerkfehler');
|
|
}
|
|
});
|
|
},
|
|
|
|
duplicateEquipment: function(equipmentId) {
|
|
var self = this;
|
|
|
|
if (self.isSaving) return;
|
|
self.isSaving = true;
|
|
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment.php',
|
|
method: 'POST',
|
|
data: {
|
|
action: 'duplicate',
|
|
equipment_id: equipmentId,
|
|
token: $('input[name="token"]').val()
|
|
},
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
self.isSaving = false;
|
|
if (response.success) {
|
|
location.reload();
|
|
} else {
|
|
alert('Fehler: ' + response.error);
|
|
}
|
|
},
|
|
error: function() {
|
|
self.isSaving = false;
|
|
alert('Netzwerkfehler');
|
|
}
|
|
});
|
|
},
|
|
|
|
deleteEquipment: function(equipmentId) {
|
|
var self = this;
|
|
|
|
// No confirm here - handled by caller
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment.php',
|
|
method: 'POST',
|
|
data: {
|
|
action: 'delete',
|
|
equipment_id: equipmentId,
|
|
token: $('input[name="token"]').val()
|
|
},
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
location.reload();
|
|
} else {
|
|
self.showAlertDialog('Fehler', response.error);
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
moveEquipment: function(equipmentId, newPosition) {
|
|
var self = this;
|
|
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment.php',
|
|
method: 'POST',
|
|
data: {
|
|
action: 'move',
|
|
equipment_id: equipmentId,
|
|
position_te: newPosition,
|
|
token: $('input[name="token"]').val()
|
|
},
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
location.reload();
|
|
} else {
|
|
alert('Fehler: ' + response.error);
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
showEquipmentTooltip: function($block, data) {
|
|
var self = this;
|
|
|
|
var html = '<div class="kundenkarte-equipment-tooltip-header">';
|
|
html += '<strong>' + this.escapeHtml(data.type || '') + '</strong>';
|
|
if (data.label) {
|
|
html += '<br><span style="color:#666;">' + this.escapeHtml(data.label) + '</span>';
|
|
}
|
|
html += '</div>';
|
|
|
|
if (data.fields && Object.keys(data.fields).length > 0) {
|
|
html += '<div class="kundenkarte-equipment-tooltip-fields">';
|
|
for (var key in data.fields) {
|
|
if (data.fields[key]) {
|
|
html += '<div><span class="field-label">' + this.escapeHtml(key) + ':</span> ';
|
|
html += '<span class="field-value">' + this.escapeHtml(data.fields[key]) + '</span></div>';
|
|
}
|
|
}
|
|
html += '</div>';
|
|
}
|
|
|
|
var $tooltip = $('#kundenkarte-equipment-tooltip');
|
|
if (!$tooltip.length) {
|
|
$tooltip = $('<div id="kundenkarte-equipment-tooltip" class="kundenkarte-equipment-tooltip"></div>');
|
|
$('body').append($tooltip);
|
|
}
|
|
|
|
$tooltip.html(html);
|
|
|
|
// Position near the block
|
|
var offset = $block.offset ? $block.offset() : $(this).offset();
|
|
var rect = $block[0].getBoundingClientRect ? $block[0].getBoundingClientRect() : { right: 0, top: 0 };
|
|
|
|
$tooltip.css({
|
|
top: rect.top + window.scrollY + 10,
|
|
left: rect.right + window.scrollX + 10
|
|
}).addClass('visible');
|
|
},
|
|
|
|
hideEquipmentTooltip: function() {
|
|
$('#kundenkarte-equipment-tooltip').removeClass('visible');
|
|
},
|
|
|
|
// ==========================================
|
|
// CONNECTION METHODS (Stromverbindungen)
|
|
// ==========================================
|
|
|
|
showOutputDialog: function(carrierId) {
|
|
var self = this;
|
|
|
|
if ($('#kundenkarte-output-dialog').length) return;
|
|
|
|
// Get equipment on this carrier for dropdown
|
|
var $carrier = $('.kundenkarte-carrier[data-carrier-id="' + carrierId + '"]');
|
|
var equipmentOptions = [];
|
|
|
|
$carrier.find('.kundenkarte-equipment-block').each(function() {
|
|
var $block = $(this);
|
|
var tooltipData = $block.data('tooltip');
|
|
var id = $block.data('equipment-id');
|
|
var label = tooltipData ? (tooltipData.label || tooltipData.type || 'Equipment ' + id) : 'Equipment ' + id;
|
|
equipmentOptions.push({ id: id, label: label });
|
|
});
|
|
|
|
var html = '<div id="kundenkarte-output-dialog" class="kundenkarte-modal">';
|
|
html += '<div class="kundenkarte-modal-content" style="max-width:500px;">';
|
|
html += '<div class="kundenkarte-modal-header"><h3>Abgang hinzufügen</h3>';
|
|
html += '<span class="kundenkarte-modal-close">×</span></div>';
|
|
html += '<div class="kundenkarte-modal-body">';
|
|
html += '<div class="kundenkarte-connection-form">';
|
|
|
|
// Equipment selection
|
|
html += '<div class="form-row">';
|
|
html += '<div class="form-group">';
|
|
html += '<label>Von Equipment</label>';
|
|
html += '<select name="output_equipment_id" id="output_equipment_id" class="flat">';
|
|
equipmentOptions.forEach(function(eq) {
|
|
html += '<option value="' + eq.id + '">' + self.escapeHtml(eq.label) + '</option>';
|
|
});
|
|
html += '</select>';
|
|
html += '</div>';
|
|
html += '</div>';
|
|
|
|
// Phase selection
|
|
html += '<div class="form-row">';
|
|
html += '<div class="form-group">';
|
|
html += '<label>Phasen</label>';
|
|
html += '<div class="kundenkarte-phase-buttons">';
|
|
html += '<button type="button" class="kundenkarte-phase-btn phase-L1" data-phase="L1">L1</button>';
|
|
html += '<button type="button" class="kundenkarte-phase-btn phase-N" data-phase="N">N</button>';
|
|
html += '<button type="button" class="kundenkarte-phase-btn active" data-phase="L1N">L1+N</button>';
|
|
html += '<button type="button" class="kundenkarte-phase-btn" data-phase="L1L2L3">3P</button>';
|
|
html += '<button type="button" class="kundenkarte-phase-btn" data-phase="L1L2L3N">3P+N</button>';
|
|
html += '<button type="button" class="kundenkarte-phase-btn" data-phase="L1L2L3NPE">3P+N+PE</button>';
|
|
html += '</div>';
|
|
html += '<input type="hidden" name="output_connection_type" value="L1N">';
|
|
html += '</div>';
|
|
html += '</div>';
|
|
|
|
// Consumer label
|
|
html += '<div class="form-row">';
|
|
html += '<div class="form-group">';
|
|
html += '<label>Verbraucher</label>';
|
|
html += '<input type="text" name="output_consumer_label" placeholder="z.B. Küche Steckdosen">';
|
|
html += '</div>';
|
|
html += '</div>';
|
|
|
|
// Cable info
|
|
html += '<div class="form-row">';
|
|
html += '<div class="form-group">';
|
|
html += '<label>Kabeltyp</label>';
|
|
html += '<select name="output_cable_type">';
|
|
html += '<option value="">--</option>';
|
|
html += '<option value="NYM-J">NYM-J</option>';
|
|
html += '<option value="NYY-J">NYY-J</option>';
|
|
html += '<option value="H07V-K">H07V-K</option>';
|
|
html += '<option value="H07V-U">H07V-U</option>';
|
|
html += '<option value="H05VV-F">H05VV-F</option>';
|
|
html += '</select>';
|
|
html += '</div>';
|
|
html += '<div class="form-group">';
|
|
html += '<label>Querschnitt</label>';
|
|
html += '<select name="output_cable_section">';
|
|
html += '<option value="">--</option>';
|
|
html += '<option value="3x1.5">3x1.5</option>';
|
|
html += '<option value="3x2.5">3x2.5</option>';
|
|
html += '<option value="5x1.5">5x1.5</option>';
|
|
html += '<option value="5x2.5">5x2.5</option>';
|
|
html += '<option value="5x4">5x4</option>';
|
|
html += '<option value="5x6">5x6</option>';
|
|
html += '<option value="5x10">5x10</option>';
|
|
html += '</select>';
|
|
html += '</div>';
|
|
html += '</div>';
|
|
|
|
html += '</div>'; // form
|
|
html += '</div>'; // body
|
|
html += '<div class="kundenkarte-modal-footer">';
|
|
html += '<button type="button" class="button" id="output-save"><i class="fa fa-save"></i> Speichern</button> ';
|
|
html += '<button type="button" class="button" id="output-cancel">Abbrechen</button>';
|
|
html += '</div>';
|
|
html += '</div></div>';
|
|
|
|
$('body').append(html);
|
|
$('#kundenkarte-output-dialog').addClass('visible');
|
|
|
|
// Phase button handlers
|
|
$('#kundenkarte-output-dialog .kundenkarte-phase-btn').on('click', function() {
|
|
$('#kundenkarte-output-dialog .kundenkarte-phase-btn').removeClass('active');
|
|
$(this).addClass('active');
|
|
$('input[name="output_connection_type"]').val($(this).data('phase'));
|
|
});
|
|
|
|
// Save
|
|
$('#output-save').on('click', function() {
|
|
self.saveOutput(carrierId);
|
|
});
|
|
|
|
// Close
|
|
$('#output-cancel, .kundenkarte-modal-close').on('click', function() {
|
|
$('#kundenkarte-output-dialog').remove();
|
|
});
|
|
},
|
|
|
|
saveOutput: function(carrierId) {
|
|
var self = this;
|
|
|
|
var data = {
|
|
action: 'create_output',
|
|
carrier_id: carrierId,
|
|
equipment_id: $('select[name="output_equipment_id"]').val(),
|
|
connection_type: $('input[name="output_connection_type"]').val(),
|
|
output_label: $('input[name="output_consumer_label"]').val(),
|
|
medium_type: $('select[name="output_cable_type"]').val(),
|
|
medium_spec: $('select[name="output_cable_section"]').val(),
|
|
token: $('input[name="token"]').val()
|
|
};
|
|
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment_connection.php',
|
|
method: 'POST',
|
|
data: data,
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
$('#kundenkarte-output-dialog').remove();
|
|
location.reload();
|
|
} else {
|
|
alert('Fehler: ' + response.error);
|
|
}
|
|
},
|
|
error: function() {
|
|
alert('Netzwerkfehler');
|
|
}
|
|
});
|
|
},
|
|
|
|
showRailDialog: function(carrierId) {
|
|
var self = this;
|
|
|
|
if ($('#kundenkarte-rail-dialog').length) return;
|
|
|
|
// Get carrier info
|
|
var $carrier = $('.kundenkarte-carrier[data-carrier-id="' + carrierId + '"]');
|
|
var totalTE = 12;
|
|
var infoText = $carrier.find('.kundenkarte-carrier-info').text();
|
|
var match = infoText.match(/\/(\d+)/);
|
|
if (match) totalTE = parseInt(match[1]);
|
|
|
|
var html = '<div id="kundenkarte-rail-dialog" class="kundenkarte-modal">';
|
|
html += '<div class="kundenkarte-modal-content" style="max-width:450px;">';
|
|
html += '<div class="kundenkarte-modal-header"><h3>Sammelschiene hinzufügen</h3>';
|
|
html += '<span class="kundenkarte-modal-close">×</span></div>';
|
|
html += '<div class="kundenkarte-modal-body">';
|
|
html += '<div class="kundenkarte-connection-form">';
|
|
|
|
// Quick presets row
|
|
html += '<div class="form-row" style="margin-bottom:10px;">';
|
|
html += '<label style="width:100%;margin-bottom:5px;">Schnellauswahl:</label>';
|
|
html += '<div style="display:flex;flex-wrap:wrap;gap:5px;">';
|
|
// Electrical presets
|
|
html += '<button type="button" class="rail-preset-btn" data-type="L1" data-color="#8B4513" style="padding:3px 8px;font-size:11px;">L1</button>';
|
|
html += '<button type="button" class="rail-preset-btn" data-type="L1N" data-color="#3498db" style="padding:3px 8px;font-size:11px;">L1N</button>';
|
|
html += '<button type="button" class="rail-preset-btn" data-type="3P" data-color="#2c3e50" style="padding:3px 8px;font-size:11px;">3P</button>';
|
|
html += '<button type="button" class="rail-preset-btn" data-type="3P+N" data-color="#2c3e50" style="padding:3px 8px;font-size:11px;">3P+N</button>';
|
|
html += '<button type="button" class="rail-preset-btn" data-type="N" data-color="#0066cc" style="padding:3px 8px;font-size:11px;">N</button>';
|
|
html += '<button type="button" class="rail-preset-btn" data-type="PE" data-color="#27ae60" style="padding:3px 8px;font-size:11px;">PE</button>';
|
|
html += '|';
|
|
// Network presets
|
|
html += '<button type="button" class="rail-preset-btn" data-type="CAT6" data-color="#9b59b6" style="padding:3px 8px;font-size:11px;">CAT6</button>';
|
|
html += '<button type="button" class="rail-preset-btn" data-type="LWL" data-color="#f39c12" style="padding:3px 8px;font-size:11px;">LWL</button>';
|
|
html += '<button type="button" class="rail-preset-btn" data-type="Koax" data-color="#e74c3c" style="padding:3px 8px;font-size:11px;">Koax</button>';
|
|
html += '</div>';
|
|
html += '</div>';
|
|
|
|
// Connection type (free text)
|
|
html += '<div class="form-row">';
|
|
html += '<div class="form-group">';
|
|
html += '<label>Bezeichnung</label>';
|
|
html += '<input type="text" name="rail_connection_type" value="" placeholder="z.B. L1N, CAT6, BUS, Klingel">';
|
|
html += '</div>';
|
|
html += '<div class="form-group">';
|
|
html += '<label>Farbe</label>';
|
|
html += '<input type="color" name="rail_color" value="#3498db">';
|
|
html += '</div>';
|
|
html += '</div>';
|
|
|
|
// Position (above/below)
|
|
html += '<div class="form-row">';
|
|
html += '<div class="form-group">';
|
|
html += '<label>Position</label>';
|
|
html += '<select name="rail_position">';
|
|
html += '<option value="below">Unterhalb (Standard)</option>';
|
|
html += '<option value="above">Oberhalb</option>';
|
|
html += '</select>';
|
|
html += '</div>';
|
|
html += '</div>';
|
|
|
|
// TE range
|
|
html += '<div class="form-row">';
|
|
html += '<div class="form-group">';
|
|
html += '<label>Von TE</label>';
|
|
html += '<input type="number" name="rail_start_te" value="1" min="1" max="' + totalTE + '">';
|
|
html += '</div>';
|
|
html += '<div class="form-group">';
|
|
html += '<label>Bis TE</label>';
|
|
html += '<input type="number" name="rail_end_te" value="' + totalTE + '" min="1" max="' + totalTE + '">';
|
|
html += '</div>';
|
|
html += '</div>';
|
|
|
|
// Multi-phase rail options
|
|
html += '<div class="form-row">';
|
|
html += '<div class="form-group">';
|
|
html += '<label>Phasenschiene</label>';
|
|
html += '<select name="rail_phases">';
|
|
html += '<option value="">Einfache Linie</option>';
|
|
html += '<option value="L1">L1 (einphasig)</option>';
|
|
html += '<option value="L1N">L1+N (einphasig)</option>';
|
|
html += '<option value="3P">3P (L1/L2/L3)</option>';
|
|
html += '<option value="3P+N">3P+N (L1/L2/L3/N)</option>';
|
|
html += '</select>';
|
|
html += '</div>';
|
|
html += '</div>';
|
|
|
|
// Excluded positions (for FI switches etc.)
|
|
html += '<div class="form-row">';
|
|
html += '<div class="form-group" style="width:100%;">';
|
|
html += '<label>Ausgenommene TE (z.B. für FI)</label>';
|
|
html += '<input type="text" name="rail_excluded_te" value="" placeholder="z.B. 3,4,7 (kommagetrennt)">';
|
|
html += '<small style="color:#666;font-size:11px;">An diesen Positionen wird die Schiene unterbrochen</small>';
|
|
html += '</div>';
|
|
html += '</div>';
|
|
|
|
html += '</div>'; // form
|
|
html += '</div>'; // body
|
|
html += '<div class="kundenkarte-modal-footer">';
|
|
html += '<button type="button" class="button" id="rail-save"><i class="fa fa-save"></i> Speichern</button> ';
|
|
html += '<button type="button" class="button" id="rail-cancel">Abbrechen</button>';
|
|
html += '</div>';
|
|
html += '</div></div>';
|
|
|
|
$('body').append(html);
|
|
$('#kundenkarte-rail-dialog').addClass('visible');
|
|
|
|
// Preset button handlers
|
|
$('#kundenkarte-rail-dialog .rail-preset-btn').on('click', function() {
|
|
var type = $(this).data('type');
|
|
$('input[name="rail_connection_type"]').val(type);
|
|
$('input[name="rail_color"]').val($(this).data('color'));
|
|
|
|
// Auto-set rail_phases for electrical presets
|
|
var phasesSelect = $('select[name="rail_phases"]');
|
|
if (type === 'L1') phasesSelect.val('L1');
|
|
else if (type === 'L1N') phasesSelect.val('L1N');
|
|
else if (type === '3P') phasesSelect.val('3P');
|
|
else if (type === '3P+N') phasesSelect.val('3P+N');
|
|
else phasesSelect.val(''); // Simple line for N, PE, network cables
|
|
});
|
|
|
|
// Save
|
|
$('#rail-save').on('click', function() {
|
|
self.saveRail(carrierId);
|
|
});
|
|
|
|
// Close
|
|
$('#rail-cancel, .kundenkarte-modal-close').on('click', function() {
|
|
$('#kundenkarte-rail-dialog').remove();
|
|
});
|
|
},
|
|
|
|
saveRail: function(carrierId) {
|
|
var self = this;
|
|
|
|
// position_y: -1 = above equipment, 0+ = below equipment
|
|
var positionY = $('select[name="rail_position"]').val() === 'above' ? -1 : 0;
|
|
|
|
var data = {
|
|
action: 'create_rail',
|
|
carrier_id: carrierId,
|
|
connection_type: $('input[name="rail_connection_type"]').val(),
|
|
color: $('input[name="rail_color"]').val(),
|
|
rail_start_te: $('input[name="rail_start_te"]').val(),
|
|
rail_end_te: $('input[name="rail_end_te"]').val(),
|
|
rail_phases: $('select[name="rail_phases"]').val(),
|
|
excluded_te: $('input[name="rail_excluded_te"]').val(),
|
|
position_y: positionY,
|
|
token: $('input[name="token"]').val()
|
|
};
|
|
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment_connection.php',
|
|
method: 'POST',
|
|
data: data,
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
$('#kundenkarte-rail-dialog').remove();
|
|
location.reload();
|
|
} else {
|
|
alert('Fehler: ' + response.error);
|
|
}
|
|
},
|
|
error: function() {
|
|
alert('Netzwerkfehler');
|
|
}
|
|
});
|
|
},
|
|
|
|
showEditRailDialog: function(connectionId, carrierId) {
|
|
var self = this;
|
|
|
|
if ($('#kundenkarte-rail-dialog').length) return;
|
|
|
|
// Load connection data first
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment_connection.php',
|
|
data: { action: 'get', connection_id: connectionId },
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success && response.connection) {
|
|
self.renderEditRailDialog(connectionId, carrierId, response.connection);
|
|
} else {
|
|
alert('Fehler: Verbindung nicht gefunden');
|
|
}
|
|
},
|
|
error: function() {
|
|
alert('Netzwerkfehler');
|
|
}
|
|
});
|
|
},
|
|
|
|
renderEditRailDialog: function(connectionId, carrierId, conn) {
|
|
var self = this;
|
|
|
|
// Get carrier info
|
|
var $carrier = $('.kundenkarte-carrier[data-carrier-id="' + carrierId + '"]');
|
|
var totalTE = 12;
|
|
var infoText = $carrier.find('.kundenkarte-carrier-info').text();
|
|
var match = infoText.match(/\/(\d+)/);
|
|
if (match) totalTE = parseInt(match[1]);
|
|
|
|
var isAbove = conn.position_y < 0;
|
|
|
|
var html = '<div id="kundenkarte-rail-dialog" class="kundenkarte-modal">';
|
|
html += '<div class="kundenkarte-modal-content" style="max-width:450px;">';
|
|
html += '<div class="kundenkarte-modal-header"><h3>Sammelschiene bearbeiten</h3>';
|
|
html += '<span class="kundenkarte-modal-close">×</span></div>';
|
|
html += '<div class="kundenkarte-modal-body">';
|
|
html += '<input type="hidden" name="rail_connection_id" value="' + connectionId + '">';
|
|
html += '<div class="kundenkarte-connection-form">';
|
|
|
|
// Quick presets row
|
|
html += '<div class="form-row" style="margin-bottom:10px;">';
|
|
html += '<label style="width:100%;margin-bottom:5px;">Schnellauswahl:</label>';
|
|
html += '<div style="display:flex;flex-wrap:wrap;gap:5px;">';
|
|
html += '<button type="button" class="rail-preset-btn" data-type="L1" data-color="#8B4513" style="padding:3px 8px;font-size:11px;">L1</button>';
|
|
html += '<button type="button" class="rail-preset-btn" data-type="L1N" data-color="#3498db" style="padding:3px 8px;font-size:11px;">L1N</button>';
|
|
html += '<button type="button" class="rail-preset-btn" data-type="3P" data-color="#2c3e50" style="padding:3px 8px;font-size:11px;">3P</button>';
|
|
html += '<button type="button" class="rail-preset-btn" data-type="3P+N" data-color="#2c3e50" style="padding:3px 8px;font-size:11px;">3P+N</button>';
|
|
html += '<button type="button" class="rail-preset-btn" data-type="N" data-color="#0066cc" style="padding:3px 8px;font-size:11px;">N</button>';
|
|
html += '<button type="button" class="rail-preset-btn" data-type="PE" data-color="#27ae60" style="padding:3px 8px;font-size:11px;">PE</button>';
|
|
html += '|';
|
|
html += '<button type="button" class="rail-preset-btn" data-type="CAT6" data-color="#9b59b6" style="padding:3px 8px;font-size:11px;">CAT6</button>';
|
|
html += '<button type="button" class="rail-preset-btn" data-type="LWL" data-color="#f39c12" style="padding:3px 8px;font-size:11px;">LWL</button>';
|
|
html += '<button type="button" class="rail-preset-btn" data-type="Koax" data-color="#e74c3c" style="padding:3px 8px;font-size:11px;">Koax</button>';
|
|
html += '</div>';
|
|
html += '</div>';
|
|
|
|
// Connection type (free text)
|
|
html += '<div class="form-row">';
|
|
html += '<div class="form-group">';
|
|
html += '<label>Bezeichnung</label>';
|
|
html += '<input type="text" name="rail_connection_type" value="' + self.escapeHtml(conn.connection_type || '') + '" placeholder="z.B. L1N, CAT6, BUS, Klingel">';
|
|
html += '</div>';
|
|
html += '<div class="form-group">';
|
|
html += '<label>Farbe</label>';
|
|
html += '<input type="color" name="rail_color" value="' + (conn.color || '#3498db') + '">';
|
|
html += '</div>';
|
|
html += '</div>';
|
|
|
|
// Position (above/below)
|
|
html += '<div class="form-row">';
|
|
html += '<div class="form-group">';
|
|
html += '<label>Position</label>';
|
|
html += '<select name="rail_position">';
|
|
html += '<option value="below"' + (!isAbove ? ' selected' : '') + '>Unterhalb</option>';
|
|
html += '<option value="above"' + (isAbove ? ' selected' : '') + '>Oberhalb</option>';
|
|
html += '</select>';
|
|
html += '</div>';
|
|
html += '</div>';
|
|
|
|
// TE range
|
|
html += '<div class="form-row">';
|
|
html += '<div class="form-group">';
|
|
html += '<label>Von TE</label>';
|
|
html += '<input type="number" name="rail_start_te" value="' + (conn.rail_start_te || 1) + '" min="1" max="' + totalTE + '">';
|
|
html += '</div>';
|
|
html += '<div class="form-group">';
|
|
html += '<label>Bis TE</label>';
|
|
html += '<input type="number" name="rail_end_te" value="' + (conn.rail_end_te || totalTE) + '" min="1" max="' + totalTE + '">';
|
|
html += '</div>';
|
|
html += '</div>';
|
|
|
|
// Multi-phase rail options
|
|
html += '<div class="form-row">';
|
|
html += '<div class="form-group">';
|
|
html += '<label>Phasenschiene</label>';
|
|
html += '<select name="rail_phases">';
|
|
html += '<option value=""' + (!conn.rail_phases ? ' selected' : '') + '>Einfache Linie</option>';
|
|
html += '<option value="L1"' + (conn.rail_phases === 'L1' ? ' selected' : '') + '>L1 (einphasig)</option>';
|
|
html += '<option value="L1N"' + (conn.rail_phases === 'L1N' ? ' selected' : '') + '>L1+N (einphasig)</option>';
|
|
html += '<option value="3P"' + (conn.rail_phases === '3P' ? ' selected' : '') + '>3P (L1/L2/L3)</option>';
|
|
html += '<option value="3P+N"' + (conn.rail_phases === '3P+N' ? ' selected' : '') + '>3P+N (L1/L2/L3/N)</option>';
|
|
html += '</select>';
|
|
html += '</div>';
|
|
html += '</div>';
|
|
|
|
// Excluded positions (for FI switches etc.)
|
|
html += '<div class="form-row">';
|
|
html += '<div class="form-group" style="width:100%;">';
|
|
html += '<label>Ausgenommene TE (z.B. für FI)</label>';
|
|
html += '<input type="text" name="rail_excluded_te" value="' + self.escapeHtml(conn.excluded_te || '') + '" placeholder="z.B. 3,4,7 (kommagetrennt)">';
|
|
html += '<small style="color:#666;font-size:11px;">An diesen Positionen wird die Schiene unterbrochen</small>';
|
|
html += '</div>';
|
|
html += '</div>';
|
|
|
|
html += '</div>'; // form
|
|
html += '</div>'; // body
|
|
html += '<div class="kundenkarte-modal-footer">';
|
|
html += '<button type="button" class="button" id="rail-save"><i class="fa fa-save"></i> Speichern</button> ';
|
|
html += '<button type="button" class="button" id="rail-delete" style="background:#c0392b;color:#fff;"><i class="fa fa-trash"></i> Löschen</button> ';
|
|
html += '<button type="button" class="button" id="rail-cancel">Abbrechen</button>';
|
|
html += '</div>';
|
|
html += '</div></div>';
|
|
|
|
$('body').append(html);
|
|
$('#kundenkarte-rail-dialog').addClass('visible');
|
|
|
|
// Preset button handlers
|
|
$('#kundenkarte-rail-dialog .rail-preset-btn').on('click', function() {
|
|
var type = $(this).data('type');
|
|
$('input[name="rail_connection_type"]').val(type);
|
|
$('input[name="rail_color"]').val($(this).data('color'));
|
|
|
|
// Auto-set rail_phases for electrical presets
|
|
var phasesSelect = $('select[name="rail_phases"]');
|
|
if (type === 'L1') phasesSelect.val('L1');
|
|
else if (type === 'L1N') phasesSelect.val('L1N');
|
|
else if (type === '3P') phasesSelect.val('3P');
|
|
else if (type === '3P+N') phasesSelect.val('3P+N');
|
|
else phasesSelect.val(''); // Simple line for N, PE, network cables
|
|
});
|
|
|
|
// Save
|
|
$('#rail-save').on('click', function() {
|
|
self.updateRail(connectionId, carrierId);
|
|
});
|
|
|
|
// Delete
|
|
$('#rail-delete').on('click', function() {
|
|
$('#kundenkarte-rail-dialog').remove();
|
|
self.showConfirmDialog('Sammelschiene löschen', 'Möchten Sie diese Sammelschiene wirklich löschen?', function() {
|
|
self.doDeleteConnection(connectionId);
|
|
});
|
|
});
|
|
|
|
// Close
|
|
$('#rail-cancel, .kundenkarte-modal-close').on('click', function() {
|
|
$('#kundenkarte-rail-dialog').remove();
|
|
});
|
|
},
|
|
|
|
updateRail: function(connectionId, carrierId) {
|
|
var self = this;
|
|
|
|
var positionY = $('select[name="rail_position"]').val() === 'above' ? -1 : 0;
|
|
|
|
var data = {
|
|
action: 'update',
|
|
connection_id: connectionId,
|
|
carrier_id: carrierId,
|
|
connection_type: $('input[name="rail_connection_type"]').val(),
|
|
color: $('input[name="rail_color"]').val(),
|
|
rail_start_te: $('input[name="rail_start_te"]').val(),
|
|
rail_end_te: $('input[name="rail_end_te"]').val(),
|
|
rail_phases: $('select[name="rail_phases"]').val(),
|
|
excluded_te: $('input[name="rail_excluded_te"]').val(),
|
|
position_y: positionY,
|
|
is_rail: 1,
|
|
token: $('input[name="token"]').val()
|
|
};
|
|
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment_connection.php',
|
|
method: 'POST',
|
|
data: data,
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
$('#kundenkarte-rail-dialog').remove();
|
|
location.reload();
|
|
} else {
|
|
alert('Fehler: ' + response.error);
|
|
}
|
|
},
|
|
error: function() {
|
|
alert('Netzwerkfehler');
|
|
}
|
|
});
|
|
},
|
|
|
|
showEditOutputDialog: function(connectionId, carrierId) {
|
|
var self = this;
|
|
|
|
if ($('#kundenkarte-output-dialog').length) return;
|
|
|
|
// Load connection data first
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment_connection.php',
|
|
data: { action: 'get', connection_id: connectionId },
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success && response.connection) {
|
|
self.renderEditOutputDialog(connectionId, carrierId, response.connection);
|
|
} else {
|
|
alert('Fehler: Verbindung nicht gefunden');
|
|
}
|
|
},
|
|
error: function() {
|
|
alert('Netzwerkfehler');
|
|
}
|
|
});
|
|
},
|
|
|
|
renderEditOutputDialog: function(connectionId, carrierId, conn) {
|
|
var self = this;
|
|
|
|
var html = '<div id="kundenkarte-output-dialog" class="kundenkarte-modal">';
|
|
html += '<div class="kundenkarte-modal-content" style="max-width:450px;">';
|
|
html += '<div class="kundenkarte-modal-header"><h3>Abgang bearbeiten</h3>';
|
|
html += '<span class="kundenkarte-modal-close">×</span></div>';
|
|
html += '<div class="kundenkarte-modal-body">';
|
|
html += '<input type="hidden" name="output_connection_id" value="' + connectionId + '">';
|
|
html += '<input type="hidden" name="output_fk_source" value="' + (conn.fk_source || '') + '">';
|
|
html += '<div class="kundenkarte-connection-form">';
|
|
|
|
// Quick presets
|
|
html += '<div class="form-row" style="margin-bottom:10px;">';
|
|
html += '<label style="width:100%;margin-bottom:5px;">Schnellauswahl Typ:</label>';
|
|
html += '<div style="display:flex;flex-wrap:wrap;gap:5px;">';
|
|
html += '<button type="button" class="output-preset-btn" data-type="L1N" data-color="#3498db" style="padding:3px 8px;font-size:11px;">L1N</button>';
|
|
html += '<button type="button" class="output-preset-btn" data-type="3P" data-color="#2c3e50" style="padding:3px 8px;font-size:11px;">3P</button>';
|
|
html += '<button type="button" class="output-preset-btn" data-type="3P+N" data-color="#2c3e50" style="padding:3px 8px;font-size:11px;">3P+N</button>';
|
|
html += '|';
|
|
html += '<button type="button" class="output-preset-btn" data-type="CAT6" data-color="#9b59b6" style="padding:3px 8px;font-size:11px;">CAT6</button>';
|
|
html += '<button type="button" class="output-preset-btn" data-type="LWL" data-color="#f39c12" style="padding:3px 8px;font-size:11px;">LWL</button>';
|
|
html += '</div>';
|
|
html += '</div>';
|
|
|
|
// Output label (consumer)
|
|
html += '<div class="form-row">';
|
|
html += '<div class="form-group" style="flex:2;">';
|
|
html += '<label>Ziel/Verbraucher</label>';
|
|
html += '<input type="text" name="output_label" value="' + self.escapeHtml(conn.output_label || '') + '" placeholder="z.B. Küche Steckdosen, Bad Licht">';
|
|
html += '</div>';
|
|
html += '</div>';
|
|
|
|
// Connection type and color
|
|
html += '<div class="form-row">';
|
|
html += '<div class="form-group">';
|
|
html += '<label>Typ</label>';
|
|
html += '<input type="text" name="output_connection_type" value="' + self.escapeHtml(conn.connection_type || '') + '" placeholder="z.B. L1N">';
|
|
html += '</div>';
|
|
html += '<div class="form-group">';
|
|
html += '<label>Farbe</label>';
|
|
html += '<input type="color" name="output_color" value="' + (conn.color || '#3498db') + '">';
|
|
html += '</div>';
|
|
html += '</div>';
|
|
|
|
// Medium info
|
|
html += '<div class="form-row">';
|
|
html += '<div class="form-group">';
|
|
html += '<label>Kabeltyp</label>';
|
|
html += '<input type="text" name="output_medium_type" value="' + self.escapeHtml(conn.medium_type || '') + '" placeholder="z.B. NYM-J">';
|
|
html += '</div>';
|
|
html += '<div class="form-group">';
|
|
html += '<label>Querschnitt</label>';
|
|
html += '<input type="text" name="output_medium_spec" value="' + self.escapeHtml(conn.medium_spec || '') + '" placeholder="z.B. 3x1.5">';
|
|
html += '</div>';
|
|
html += '</div>';
|
|
|
|
// Length
|
|
html += '<div class="form-row">';
|
|
html += '<div class="form-group">';
|
|
html += '<label>Länge</label>';
|
|
html += '<input type="text" name="output_medium_length" value="' + self.escapeHtml(conn.medium_length || '') + '" placeholder="z.B. ca. 15m">';
|
|
html += '</div>';
|
|
html += '</div>';
|
|
|
|
html += '</div>'; // form
|
|
html += '</div>'; // body
|
|
html += '<div class="kundenkarte-modal-footer">';
|
|
html += '<button type="button" class="button" id="output-save"><i class="fa fa-save"></i> Speichern</button> ';
|
|
html += '<button type="button" class="button" id="output-delete" style="background:#c0392b;color:#fff;"><i class="fa fa-trash"></i> Löschen</button> ';
|
|
html += '<button type="button" class="button" id="output-cancel">Abbrechen</button>';
|
|
html += '</div>';
|
|
html += '</div></div>';
|
|
|
|
$('body').append(html);
|
|
$('#kundenkarte-output-dialog').addClass('visible');
|
|
|
|
// Preset button handlers
|
|
$('#kundenkarte-output-dialog .output-preset-btn').on('click', function() {
|
|
$('input[name="output_connection_type"]').val($(this).data('type'));
|
|
$('input[name="output_color"]').val($(this).data('color'));
|
|
});
|
|
|
|
// Save
|
|
$('#output-save').on('click', function() {
|
|
self.updateOutput(connectionId, carrierId);
|
|
});
|
|
|
|
// Delete
|
|
$('#output-delete').on('click', function() {
|
|
$('#kundenkarte-output-dialog').remove();
|
|
self.showConfirmDialog('Abgang löschen', 'Möchten Sie diesen Abgang wirklich löschen?', function() {
|
|
self.doDeleteConnection(connectionId);
|
|
});
|
|
});
|
|
|
|
// Close
|
|
$('#output-cancel, .kundenkarte-modal-close').on('click', function() {
|
|
$('#kundenkarte-output-dialog').remove();
|
|
});
|
|
},
|
|
|
|
updateOutput: function(connectionId, carrierId) {
|
|
var self = this;
|
|
|
|
var data = {
|
|
action: 'update',
|
|
connection_id: connectionId,
|
|
carrier_id: carrierId,
|
|
fk_source: $('input[name="output_fk_source"]').val(),
|
|
source_terminal: 'output',
|
|
connection_type: $('input[name="output_connection_type"]').val(),
|
|
color: $('input[name="output_color"]').val(),
|
|
output_label: $('input[name="output_label"]').val(),
|
|
medium_type: $('input[name="output_medium_type"]').val(),
|
|
medium_spec: $('input[name="output_medium_spec"]').val(),
|
|
medium_length: $('input[name="output_medium_length"]').val(),
|
|
is_rail: 0,
|
|
token: $('input[name="token"]').val()
|
|
};
|
|
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment_connection.php',
|
|
method: 'POST',
|
|
data: data,
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
$('#kundenkarte-output-dialog').remove();
|
|
location.reload();
|
|
} else {
|
|
alert('Fehler: ' + response.error);
|
|
}
|
|
},
|
|
error: function() {
|
|
alert('Netzwerkfehler');
|
|
}
|
|
});
|
|
},
|
|
|
|
deleteConnection: function(connectionId) {
|
|
var self = this;
|
|
|
|
self.showConfirmDialog('Verbindung löschen', 'Möchten Sie diese Verbindung wirklich löschen?', function() {
|
|
self.doDeleteConnection(connectionId);
|
|
});
|
|
},
|
|
|
|
// Internal function to delete connection without confirmation
|
|
doDeleteConnection: function(connectionId) {
|
|
var self = this;
|
|
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment_connection.php',
|
|
method: 'POST',
|
|
data: {
|
|
action: 'delete',
|
|
connection_id: connectionId,
|
|
token: $('input[name="token"]').val()
|
|
},
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
location.reload();
|
|
} else {
|
|
self.showAlertDialog('Fehler', response.error);
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
// Custom confirm dialog (replaces browser confirm())
|
|
showConfirmDialog: function(title, message, onConfirm, onCancel) {
|
|
var self = this;
|
|
|
|
// Remove any existing confirm dialog
|
|
$('#kundenkarte-confirm-dialog').remove();
|
|
|
|
var html = '<div id="kundenkarte-confirm-dialog" class="kundenkarte-modal">';
|
|
html += '<div class="kundenkarte-modal-content" style="max-width:400px;">';
|
|
html += '<div class="kundenkarte-modal-header"><h3>' + self.escapeHtml(title) + '</h3>';
|
|
html += '<span class="kundenkarte-modal-close">×</span></div>';
|
|
html += '<div class="kundenkarte-modal-body" style="padding:20px;">';
|
|
html += '<p style="margin:0;font-size:14px;">' + self.escapeHtml(message) + '</p>';
|
|
html += '</div>';
|
|
html += '<div class="kundenkarte-modal-footer" style="display:flex;gap:10px;justify-content:flex-end;">';
|
|
html += '<button type="button" class="button" id="confirm-yes" style="background:#c0392b;color:#fff;"><i class="fa fa-check"></i> Ja</button>';
|
|
html += '<button type="button" class="button" id="confirm-no"><i class="fa fa-times"></i> Nein</button>';
|
|
html += '</div>';
|
|
html += '</div></div>';
|
|
|
|
$('body').append(html);
|
|
$('#kundenkarte-confirm-dialog').addClass('visible');
|
|
|
|
// Focus yes button
|
|
$('#confirm-yes').focus();
|
|
|
|
// Yes button
|
|
$('#confirm-yes').on('click', function() {
|
|
$('#kundenkarte-confirm-dialog').remove();
|
|
if (typeof onConfirm === 'function') {
|
|
onConfirm();
|
|
}
|
|
});
|
|
|
|
// No button and close
|
|
$('#confirm-no, #kundenkarte-confirm-dialog .kundenkarte-modal-close').on('click', function() {
|
|
$('#kundenkarte-confirm-dialog').remove();
|
|
if (typeof onCancel === 'function') {
|
|
onCancel();
|
|
}
|
|
});
|
|
|
|
// ESC key
|
|
$(document).one('keydown.confirmDialog', function(e) {
|
|
if (e.key === 'Escape') {
|
|
$('#kundenkarte-confirm-dialog').remove();
|
|
if (typeof onCancel === 'function') {
|
|
onCancel();
|
|
}
|
|
} else if (e.key === 'Enter') {
|
|
$('#kundenkarte-confirm-dialog').remove();
|
|
if (typeof onConfirm === 'function') {
|
|
onConfirm();
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
// Custom alert dialog (replaces browser alert())
|
|
showAlertDialog: function(title, message, onClose) {
|
|
var self = this;
|
|
|
|
// Remove any existing alert dialog
|
|
$('#kundenkarte-alert-dialog').remove();
|
|
|
|
var html = '<div id="kundenkarte-alert-dialog" class="kundenkarte-modal">';
|
|
html += '<div class="kundenkarte-modal-content" style="max-width:400px;">';
|
|
html += '<div class="kundenkarte-modal-header"><h3>' + self.escapeHtml(title) + '</h3>';
|
|
html += '<span class="kundenkarte-modal-close">×</span></div>';
|
|
html += '<div class="kundenkarte-modal-body" style="padding:20px;">';
|
|
html += '<p style="margin:0;font-size:14px;">' + self.escapeHtml(message) + '</p>';
|
|
html += '</div>';
|
|
html += '<div class="kundenkarte-modal-footer" style="display:flex;justify-content:flex-end;">';
|
|
html += '<button type="button" class="button" id="alert-ok"><i class="fa fa-check"></i> OK</button>';
|
|
html += '</div>';
|
|
html += '</div></div>';
|
|
|
|
$('body').append(html);
|
|
$('#kundenkarte-alert-dialog').addClass('visible');
|
|
|
|
$('#alert-ok').focus();
|
|
|
|
var closeDialog = function() {
|
|
$('#kundenkarte-alert-dialog').remove();
|
|
$(document).off('keydown.alertDialog');
|
|
if (typeof onClose === 'function') {
|
|
onClose();
|
|
}
|
|
};
|
|
|
|
$('#alert-ok, #kundenkarte-alert-dialog .kundenkarte-modal-close').on('click', closeDialog);
|
|
|
|
$(document).on('keydown.alertDialog', function(e) {
|
|
if (e.key === 'Escape' || e.key === 'Enter') {
|
|
closeDialog();
|
|
}
|
|
});
|
|
},
|
|
|
|
escapeHtml: function(text) {
|
|
if (!text) return '';
|
|
var div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Connection Editor Component
|
|
* Interactive SVG-based connection editor with orthogonal routing
|
|
* Supports busbars, multi-phase connections, and drag-drop
|
|
*/
|
|
KundenKarte.ConnectionEditor = {
|
|
TE_WIDTH: 50,
|
|
BLOCK_HEIGHT: 110,
|
|
BUSBAR_HEIGHT: 25,
|
|
BUSBAR_SPACING: 8,
|
|
CONNECTION_AREA_HEIGHT: 120,
|
|
ENDPOINT_RADIUS: 6,
|
|
|
|
// Phase colors (German electrical standard)
|
|
PHASE_COLORS: {
|
|
'L1': '#8B4513', // Brown
|
|
'L2': '#000000', // Black
|
|
'L3': '#808080', // Gray
|
|
'N': '#0066cc', // Blue
|
|
'PE': '#27ae60', // Green-Yellow
|
|
'L1N': '#3498db', // Combined
|
|
'3P': '#2c3e50', // 3-phase
|
|
'3P+N': '#34495e' // 3-phase + neutral
|
|
},
|
|
|
|
// State
|
|
isExpanded: false,
|
|
currentCarrierId: null,
|
|
currentAnlageId: null,
|
|
connections: [],
|
|
busbars: [],
|
|
equipment: [],
|
|
dragState: null,
|
|
selectedConnection: null,
|
|
|
|
init: function(anlageId) {
|
|
console.log('ConnectionEditor.init called with anlageId:', anlageId);
|
|
if (!anlageId) {
|
|
console.log('ConnectionEditor: No anlageId, aborting');
|
|
return;
|
|
}
|
|
this.currentAnlageId = anlageId;
|
|
|
|
// Restore expanded state from localStorage
|
|
var savedState = localStorage.getItem('kundenkarte_connection_editor_expanded');
|
|
this.isExpanded = savedState === 'true';
|
|
console.log('ConnectionEditor: isExpanded =', this.isExpanded);
|
|
|
|
this.bindEvents();
|
|
console.log('ConnectionEditor: Events bound');
|
|
|
|
this.renderAllEditors();
|
|
console.log('ConnectionEditor: Editors rendered');
|
|
},
|
|
|
|
bindEvents: function() {
|
|
var self = this;
|
|
|
|
// Toggle editor visibility
|
|
$(document).on('click', '.kundenkarte-connection-editor-toggle', function(e) {
|
|
e.preventDefault();
|
|
var $editor = $(this).closest('.kundenkarte-carrier').find('.kundenkarte-connection-editor');
|
|
var carrierId = $(this).closest('.kundenkarte-carrier').data('carrier-id');
|
|
|
|
$editor.toggleClass('expanded');
|
|
$(this).find('i').toggleClass('fa-chevron-down fa-chevron-up');
|
|
|
|
// Save state
|
|
self.isExpanded = $editor.hasClass('expanded');
|
|
localStorage.setItem('kundenkarte_connection_editor_expanded', self.isExpanded);
|
|
|
|
if (self.isExpanded) {
|
|
self.loadAndRenderEditor(carrierId);
|
|
}
|
|
});
|
|
|
|
// Add busbar button
|
|
$(document).on('click', '.kundenkarte-add-busbar-btn', function(e) {
|
|
e.preventDefault();
|
|
var carrierId = $(this).data('carrier-id');
|
|
self.showBusbarDialog(carrierId);
|
|
});
|
|
|
|
// Add connection button
|
|
$(document).on('click', '.kundenkarte-add-conn-btn', function(e) {
|
|
e.preventDefault();
|
|
var carrierId = $(this).data('carrier-id');
|
|
self.showConnectionDialog(carrierId);
|
|
});
|
|
|
|
// SVG mouse events for drag-drop connections
|
|
$(document).on('mousedown', '.kundenkarte-endpoint', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
var $endpoint = $(this);
|
|
var carrierId = $endpoint.closest('.kundenkarte-connection-editor').data('carrier-id');
|
|
|
|
self.startDragConnection(e, $endpoint, carrierId);
|
|
});
|
|
|
|
$(document).on('mousemove', function(e) {
|
|
if (self.dragState) {
|
|
self.updateDragConnection(e);
|
|
}
|
|
});
|
|
|
|
$(document).on('mouseup', function(e) {
|
|
if (self.dragState) {
|
|
self.endDragConnection(e);
|
|
}
|
|
});
|
|
|
|
// Click on busbar to edit
|
|
$(document).on('click', '.kundenkarte-busbar-element', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
var connectionId = $(this).data('connection-id');
|
|
var carrierId = $(this).closest('.kundenkarte-connection-editor').data('carrier-id');
|
|
self.showEditBusbarDialog(connectionId, carrierId);
|
|
});
|
|
|
|
// Click on connection line to edit/delete
|
|
$(document).on('click', '.kundenkarte-connection-path', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
var connectionId = $(this).data('connection-id');
|
|
var carrierId = $(this).closest('.kundenkarte-connection-editor').data('carrier-id');
|
|
self.showEditConnectionDialog(connectionId, carrierId);
|
|
});
|
|
|
|
// Hover effects
|
|
$(document).on('mouseenter', '.kundenkarte-endpoint', function() {
|
|
$(this).addClass('hover');
|
|
});
|
|
$(document).on('mouseleave', '.kundenkarte-endpoint', function() {
|
|
$(this).removeClass('hover');
|
|
});
|
|
},
|
|
|
|
renderAllEditors: function() {
|
|
var self = this;
|
|
$('.kundenkarte-carrier').each(function() {
|
|
var carrierId = $(this).data('carrier-id');
|
|
var $editor = $(this).find('.kundenkarte-connection-editor');
|
|
|
|
// Editor is already rendered via PHP, just need to initialize state
|
|
if ($editor.length) {
|
|
if (self.isExpanded) {
|
|
$editor.addClass('expanded');
|
|
$(this).find('.kundenkarte-connection-editor-toggle i').removeClass('fa-chevron-down').addClass('fa-chevron-up');
|
|
self.loadAndRenderEditor(carrierId);
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
loadAndRenderEditor: function(carrierId) {
|
|
var self = this;
|
|
|
|
// Load equipment for this carrier
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment.php',
|
|
data: { action: 'list', carrier_id: carrierId },
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
self.equipment = response.equipment || [];
|
|
|
|
// Load connections
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment_connection.php',
|
|
data: { action: 'list', carrier_id: carrierId },
|
|
dataType: 'json',
|
|
success: function(connResponse) {
|
|
if (connResponse.success) {
|
|
self.connections = connResponse.connections || [];
|
|
self.busbars = self.connections.filter(function(c) { return c.is_rail == 1; });
|
|
self.renderEditor(carrierId);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
renderEditor: function(carrierId) {
|
|
var self = this;
|
|
var $editor = $('.kundenkarte-connection-editor[data-carrier-id="' + carrierId + '"]');
|
|
var $svg = $editor.find('.kundenkarte-connection-svg');
|
|
var totalTE = parseInt($editor.data('total-te')) || 12;
|
|
var totalWidth = totalTE * this.TE_WIDTH;
|
|
|
|
// Reset busbar positions
|
|
this.busbarPositions = {};
|
|
|
|
var svgContent = '';
|
|
|
|
// Defs for markers and gradients
|
|
svgContent += '<defs>';
|
|
svgContent += '<marker id="arrowhead-' + carrierId + '" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">';
|
|
svgContent += '<polygon points="0 0, 10 3.5, 0 7" fill="#888"/>';
|
|
svgContent += '</marker>';
|
|
|
|
// Phase gradients
|
|
Object.keys(this.PHASE_COLORS).forEach(function(phase) {
|
|
svgContent += '<linearGradient id="grad-' + phase + '-' + carrierId + '" x1="0%" y1="0%" x2="0%" y2="100%">';
|
|
svgContent += '<stop offset="0%" style="stop-color:' + self.PHASE_COLORS[phase] + ';stop-opacity:1" />';
|
|
svgContent += '<stop offset="100%" style="stop-color:' + self.darkenColor(self.PHASE_COLORS[phase], 20) + ';stop-opacity:1" />';
|
|
svgContent += '</linearGradient>';
|
|
});
|
|
svgContent += '</defs>';
|
|
|
|
// If no busbars exist, show a demo
|
|
if (this.busbars.length === 0 && this.equipment.length > 0) {
|
|
svgContent += this.renderDemoView(carrierId, totalTE, totalWidth);
|
|
$svg.html(svgContent);
|
|
$svg.attr('height', 200);
|
|
return;
|
|
}
|
|
|
|
// Background grid
|
|
for (var i = 0; i <= totalTE; i++) {
|
|
var x = i * this.TE_WIDTH;
|
|
svgContent += '<line x1="' + x + '" y1="0" x2="' + x + '" y2="' + this.CONNECTION_AREA_HEIGHT + '" stroke="#333" stroke-width="0.5" stroke-dasharray="2,2"/>';
|
|
}
|
|
|
|
// Render busbars
|
|
var busbarY = 15;
|
|
var busbarsByPosition = {};
|
|
|
|
this.busbars.forEach(function(busbar, index) {
|
|
var posKey = busbar.position_y < 0 ? 'above' : busbar.position_y;
|
|
if (!busbarsByPosition[posKey]) busbarsByPosition[posKey] = [];
|
|
busbarsByPosition[posKey].push(busbar);
|
|
});
|
|
|
|
Object.keys(busbarsByPosition).forEach(function(posKey) {
|
|
busbarsByPosition[posKey].forEach(function(busbar, idx) {
|
|
svgContent += self.renderBusbar(busbar, carrierId, busbarY + (idx * (self.BUSBAR_HEIGHT + self.BUSBAR_SPACING)));
|
|
});
|
|
busbarY += busbarsByPosition[posKey].length * (self.BUSBAR_HEIGHT + self.BUSBAR_SPACING);
|
|
});
|
|
|
|
// Render equipment endpoints
|
|
var equipmentY = busbarY + 20;
|
|
|
|
// Store equipment positions for connection routing
|
|
this.equipmentPositions = {};
|
|
this.equipment.forEach(function(eq) {
|
|
var width = eq.width_te * self.TE_WIDTH;
|
|
var centerX = (eq.position_te - 1) * self.TE_WIDTH + width / 2;
|
|
self.equipmentPositions[eq.id] = {
|
|
x: centerX,
|
|
left: (eq.position_te - 1) * self.TE_WIDTH,
|
|
right: eq.position_te * self.TE_WIDTH + (eq.width_te - 1) * self.TE_WIDTH,
|
|
inputY: equipmentY - 15,
|
|
outputY: equipmentY + 15,
|
|
centerY: equipmentY
|
|
};
|
|
});
|
|
|
|
// Render non-busbar connections FIRST (behind equipment)
|
|
// But route them around blocks, not through them
|
|
var regularConnections = this.connections.filter(function(c) { return c.is_rail != 1; });
|
|
|
|
// Connection layer - rendered below equipment layer
|
|
svgContent += '<g class="kundenkarte-connections-layer">';
|
|
regularConnections.forEach(function(conn, index) {
|
|
svgContent += self.renderOrthogonalConnection(conn, carrierId, equipmentY, index);
|
|
});
|
|
svgContent += '</g>';
|
|
|
|
// Equipment layer - rendered on top
|
|
svgContent += '<g class="kundenkarte-equipment-layer">';
|
|
this.equipment.forEach(function(eq) {
|
|
svgContent += self.renderEquipmentEndpoints(eq, equipmentY, carrierId);
|
|
});
|
|
svgContent += '</g>';
|
|
|
|
// Render drag preview line (hidden by default) - on top of everything
|
|
svgContent += '<path id="drag-preview-' + carrierId + '" class="kundenkarte-drag-preview" d="" fill="none" stroke="#3498db" stroke-width="2" stroke-dasharray="5,5" style="display:none;"/>';
|
|
|
|
$svg.html(svgContent);
|
|
$svg.attr('height', equipmentY + 60);
|
|
},
|
|
|
|
renderBusbar: function(busbar, carrierId, y) {
|
|
var self = this;
|
|
var startX = (busbar.rail_start_te - 1) * this.TE_WIDTH + 5;
|
|
var endX = busbar.rail_end_te * this.TE_WIDTH - 5;
|
|
var color = busbar.color || this.PHASE_COLORS[busbar.connection_type] || '#3498db';
|
|
var phases = busbar.rail_phases || '';
|
|
var excludedTE = (busbar.excluded_te || '').split(',').map(function(t) { return parseInt(t.trim()); }).filter(function(t) { return !isNaN(t); });
|
|
|
|
var html = '<g class="kundenkarte-busbar-element" data-connection-id="' + busbar.id + '">';
|
|
|
|
// Determine phase list
|
|
var phaseList = [];
|
|
if (phases === '3P+N') {
|
|
phaseList = ['L1', 'L2', 'L3', 'N'];
|
|
} else if (phases === '3P') {
|
|
phaseList = ['L1', 'L2', 'L3'];
|
|
} else if (phases === 'L1N') {
|
|
phaseList = ['L1', 'N'];
|
|
} else if (phases === 'L1') {
|
|
phaseList = ['L1'];
|
|
} else {
|
|
phaseList = [busbar.connection_type || 'L1'];
|
|
}
|
|
|
|
var phaseHeight = phaseList.length > 1 ? 6 : (this.BUSBAR_HEIGHT - 8);
|
|
var phaseSpacing = phaseList.length > 1 ? 2 : 0;
|
|
var totalPhaseHeight = phaseList.length * phaseHeight + (phaseList.length - 1) * phaseSpacing;
|
|
var phaseStartY = y + (this.BUSBAR_HEIGHT - totalPhaseHeight) / 2;
|
|
|
|
// Draw each phase line
|
|
phaseList.forEach(function(phase, idx) {
|
|
var phaseY = phaseStartY + idx * (phaseHeight + phaseSpacing);
|
|
var phaseColor = self.PHASE_COLORS[phase] || color;
|
|
|
|
// Draw the phase line with gaps for excluded TE
|
|
html += self.renderBusbarLine(startX, endX, phaseY, phaseHeight, phaseColor, excludedTE, carrierId, busbar.id, phase);
|
|
|
|
// Phase label on the left
|
|
html += '<text x="' + (startX - 3) + '" y="' + (phaseY + phaseHeight/2 + 3) + '" ';
|
|
html += 'text-anchor="end" fill="' + phaseColor + '" font-size="9" font-weight="bold">';
|
|
html += phase;
|
|
html += '</text>';
|
|
});
|
|
|
|
// Connection endpoints for EACH phase at EACH TE position
|
|
for (var te = busbar.rail_start_te; te <= busbar.rail_end_te; te++) {
|
|
if (excludedTE.indexOf(te) === -1) {
|
|
var epX = (te - 0.5) * this.TE_WIDTH;
|
|
|
|
phaseList.forEach(function(phase, idx) {
|
|
var epY = phaseStartY + idx * (phaseHeight + phaseSpacing) + phaseHeight;
|
|
var phaseColor = self.PHASE_COLORS[phase] || color;
|
|
|
|
html += '<circle class="kundenkarte-endpoint kundenkarte-busbar-endpoint kundenkarte-phase-endpoint" ';
|
|
html += 'data-type="busbar-phase" data-connection-id="' + busbar.id + '" data-te="' + te + '" data-phase="' + phase + '" ';
|
|
html += 'cx="' + epX + '" cy="' + epY + '" r="4" ';
|
|
html += 'fill="' + phaseColor + '" stroke="#fff" stroke-width="1"/>';
|
|
});
|
|
}
|
|
}
|
|
|
|
// Busbar type label (bottom right)
|
|
html += '<text x="' + (endX - 5) + '" y="' + (y + this.BUSBAR_HEIGHT - 2) + '" ';
|
|
html += 'text-anchor="end" fill="#888" font-size="8">';
|
|
html += this.escapeHtml(busbar.connection_type || '');
|
|
html += '</text>';
|
|
|
|
html += '</g>';
|
|
|
|
// Store busbar phase positions for connection routing
|
|
if (!this.busbarPositions) this.busbarPositions = {};
|
|
this.busbarPositions[busbar.id] = {
|
|
phases: phaseList,
|
|
startTE: busbar.rail_start_te,
|
|
endTE: busbar.rail_end_te,
|
|
y: y,
|
|
phaseStartY: phaseStartY,
|
|
phaseHeight: phaseHeight,
|
|
phaseSpacing: phaseSpacing
|
|
};
|
|
|
|
return html;
|
|
},
|
|
|
|
renderBusbarLine: function(startX, endX, y, height, color, excludedTE, carrierId, busbarId, phase) {
|
|
var html = '';
|
|
var segments = [];
|
|
var currentStart = startX;
|
|
var teWidth = this.TE_WIDTH;
|
|
|
|
// Calculate segments with gaps
|
|
for (var te = Math.ceil(startX / teWidth) + 1; te <= Math.floor(endX / teWidth); te++) {
|
|
if (excludedTE.indexOf(te) !== -1) {
|
|
if (currentStart < (te - 1) * teWidth + teWidth/2) {
|
|
segments.push({ start: currentStart, end: (te - 1) * teWidth + teWidth/4 });
|
|
}
|
|
currentStart = (te - 1) * teWidth + teWidth * 0.75;
|
|
}
|
|
}
|
|
segments.push({ start: currentStart, end: endX });
|
|
|
|
segments.forEach(function(seg) {
|
|
html += '<rect x="' + seg.start + '" y="' + y + '" width="' + (seg.end - seg.start) + '" height="' + height + '" ';
|
|
html += 'fill="' + color + '" rx="1" ry="1" class="kundenkarte-busbar-line" data-phase="' + (phase || '') + '"/>';
|
|
});
|
|
|
|
return html;
|
|
},
|
|
|
|
renderEquipmentEndpoints: function(eq, baseY, carrierId) {
|
|
var x = (eq.position_te - 0.5) * this.TE_WIDTH;
|
|
var width = eq.width_te * this.TE_WIDTH;
|
|
var centerX = (eq.position_te - 1) * this.TE_WIDTH + width / 2;
|
|
var color = eq.block_color || eq.type_color || '#3498db';
|
|
|
|
var html = '<g class="kundenkarte-equipment-endpoints" data-equipment-id="' + eq.id + '">';
|
|
|
|
// Equipment representation
|
|
html += '<rect x="' + ((eq.position_te - 1) * this.TE_WIDTH + 2) + '" y="' + (baseY - 8) + '" ';
|
|
html += 'width="' + (width - 4) + '" height="16" rx="3" ry="3" fill="' + color + '" stroke="#333" stroke-width="1"/>';
|
|
|
|
// Label
|
|
html += '<text x="' + centerX + '" y="' + (baseY + 4) + '" text-anchor="middle" fill="#fff" font-size="9" font-weight="bold">';
|
|
html += this.escapeHtml(eq.type_label_short || eq.label || '');
|
|
html += '</text>';
|
|
|
|
// Input endpoint (top)
|
|
html += '<circle class="kundenkarte-endpoint kundenkarte-equipment-input" ';
|
|
html += 'data-type="equipment-input" data-equipment-id="' + eq.id + '" ';
|
|
html += 'cx="' + centerX + '" cy="' + (baseY - 15) + '" r="' + this.ENDPOINT_RADIUS + '" ';
|
|
html += 'fill="#fff" stroke="' + color + '" stroke-width="2"/>';
|
|
|
|
// Output endpoint (bottom)
|
|
html += '<circle class="kundenkarte-endpoint kundenkarte-equipment-output" ';
|
|
html += 'data-type="equipment-output" data-equipment-id="' + eq.id + '" ';
|
|
html += 'cx="' + centerX + '" cy="' + (baseY + 15) + '" r="' + this.ENDPOINT_RADIUS + '" ';
|
|
html += 'fill="#fff" stroke="' + color + '" stroke-width="2"/>';
|
|
|
|
html += '</g>';
|
|
return html;
|
|
},
|
|
|
|
renderOrthogonalConnection: function(conn, carrierId, equipmentY, connectionIndex) {
|
|
var color = conn.color || this.PHASE_COLORS[conn.connection_type] || '#888888';
|
|
var sourcePos = this.equipmentPositions[conn.fk_source];
|
|
var targetPos = this.equipmentPositions[conn.fk_target];
|
|
|
|
if (!sourcePos || !targetPos) return '';
|
|
|
|
var sourceX = sourcePos.x;
|
|
var targetX = targetPos.x;
|
|
var sourceY = conn.source_terminal === 'input' ? sourcePos.inputY : sourcePos.outputY;
|
|
var targetY = conn.target_terminal === 'input' ? targetPos.inputY : targetPos.outputY;
|
|
|
|
// Create orthogonal path that routes AROUND blocks, not through them
|
|
var path = this.createOrthogonalPath(sourceX, sourceY, targetX, targetY, connectionIndex, equipmentY);
|
|
|
|
var html = '<g class="kundenkarte-connection-group" data-connection-id="' + conn.id + '">';
|
|
|
|
// Connection line with shadow for visibility
|
|
html += '<path class="kundenkarte-connection-path-shadow" ';
|
|
html += 'd="' + path + '" fill="none" stroke="rgba(0,0,0,0.3)" stroke-width="5" ';
|
|
html += 'stroke-linecap="round" stroke-linejoin="round"/>';
|
|
|
|
// Main connection line
|
|
html += '<path class="kundenkarte-connection-path" data-connection-id="' + conn.id + '" ';
|
|
html += 'd="' + path + '" fill="none" stroke="' + color + '" stroke-width="2.5" ';
|
|
html += 'stroke-linecap="round" stroke-linejoin="round"/>';
|
|
|
|
// Label if exists - positioned along the routing path
|
|
if (conn.output_label) {
|
|
var labelY = equipmentY + 35 + (connectionIndex * 12);
|
|
var labelX = (sourceX + targetX) / 2;
|
|
html += '<rect x="' + (labelX - 25) + '" y="' + (labelY - 8) + '" width="50" height="12" rx="2" fill="#1e1e1e" opacity="0.9"/>';
|
|
html += '<text x="' + labelX + '" y="' + (labelY + 2) + '" text-anchor="middle" fill="#ccc" font-size="9">';
|
|
html += this.escapeHtml(conn.output_label);
|
|
html += '</text>';
|
|
}
|
|
|
|
html += '</g>';
|
|
return html;
|
|
},
|
|
|
|
createOrthogonalPath: function(x1, y1, x2, y2, connectionIndex, equipmentY) {
|
|
// Offset for multiple connections to avoid overlap
|
|
var routeOffset = (connectionIndex || 0) * 8;
|
|
|
|
// Route connections BELOW the equipment blocks to keep them visible
|
|
var routeY = equipmentY + 30 + routeOffset;
|
|
|
|
// If source and target are the same terminal type, route around
|
|
var bothOutputs = y1 > equipmentY && y2 > equipmentY;
|
|
var bothInputs = y1 < equipmentY && y2 < equipmentY;
|
|
|
|
if (Math.abs(x1 - x2) < 10) {
|
|
// Vertically aligned - straight line
|
|
return 'M ' + x1 + ' ' + y1 + ' L ' + x2 + ' ' + y2;
|
|
}
|
|
|
|
if (bothOutputs) {
|
|
// Both from output - route below
|
|
return 'M ' + x1 + ' ' + y1 +
|
|
' L ' + x1 + ' ' + routeY +
|
|
' L ' + x2 + ' ' + routeY +
|
|
' L ' + x2 + ' ' + y2;
|
|
}
|
|
|
|
if (bothInputs) {
|
|
// Both from input - route above
|
|
var routeYAbove = equipmentY - 30 - routeOffset;
|
|
return 'M ' + x1 + ' ' + y1 +
|
|
' L ' + x1 + ' ' + routeYAbove +
|
|
' L ' + x2 + ' ' + routeYAbove +
|
|
' L ' + x2 + ' ' + y2;
|
|
}
|
|
|
|
// Mixed terminals (output to input or vice versa)
|
|
// Route to the side and then up/down
|
|
if (y1 > y2) {
|
|
// Going up (output to input)
|
|
return 'M ' + x1 + ' ' + y1 +
|
|
' L ' + x1 + ' ' + routeY +
|
|
' L ' + x2 + ' ' + routeY +
|
|
' L ' + x2 + ' ' + y2;
|
|
} else {
|
|
// Going down (input to output)
|
|
var routeYAbove2 = equipmentY - 30 - routeOffset;
|
|
return 'M ' + x1 + ' ' + y1 +
|
|
' L ' + x1 + ' ' + routeYAbove2 +
|
|
' L ' + x2 + ' ' + routeYAbove2 +
|
|
' L ' + x2 + ' ' + y2;
|
|
}
|
|
},
|
|
|
|
// Drag and drop connection creation
|
|
startDragConnection: function(e, $endpoint, carrierId) {
|
|
var $svg = $endpoint.closest('svg');
|
|
var offset = $svg.offset();
|
|
var svgPoint = this.getSVGPoint($svg[0], e.clientX, e.clientY);
|
|
|
|
this.dragState = {
|
|
carrierId: carrierId,
|
|
startEndpoint: $endpoint,
|
|
startX: parseFloat($endpoint.attr('cx')),
|
|
startY: parseFloat($endpoint.attr('cy')),
|
|
startType: $endpoint.data('type'),
|
|
startId: $endpoint.data('equipment-id') || $endpoint.data('connection-id'),
|
|
currentX: svgPoint.x,
|
|
currentY: svgPoint.y
|
|
};
|
|
|
|
$endpoint.addClass('dragging');
|
|
$('#drag-preview-' + carrierId).show();
|
|
},
|
|
|
|
updateDragConnection: function(e) {
|
|
if (!this.dragState) return;
|
|
|
|
var $svg = $('#drag-preview-' + this.dragState.carrierId).closest('svg');
|
|
var svgPoint = this.getSVGPoint($svg[0], e.clientX, e.clientY);
|
|
|
|
this.dragState.currentX = svgPoint.x;
|
|
this.dragState.currentY = svgPoint.y;
|
|
|
|
var path = this.createOrthogonalPath(
|
|
this.dragState.startX,
|
|
this.dragState.startY,
|
|
this.dragState.currentX,
|
|
this.dragState.currentY
|
|
);
|
|
|
|
$('#drag-preview-' + this.dragState.carrierId).attr('d', path);
|
|
},
|
|
|
|
endDragConnection: function(e) {
|
|
if (!this.dragState) return;
|
|
|
|
var $targetEndpoint = $(e.target).closest('.kundenkarte-endpoint');
|
|
|
|
if ($targetEndpoint.length && !$targetEndpoint.is(this.dragState.startEndpoint)) {
|
|
// Valid drop target
|
|
this.createConnection(
|
|
this.dragState.carrierId,
|
|
this.dragState.startType,
|
|
this.dragState.startId,
|
|
$targetEndpoint.data('type'),
|
|
$targetEndpoint.data('equipment-id') || $targetEndpoint.data('connection-id'),
|
|
$targetEndpoint.data('te')
|
|
);
|
|
}
|
|
|
|
// Cleanup
|
|
this.dragState.startEndpoint.removeClass('dragging');
|
|
$('#drag-preview-' + this.dragState.carrierId).hide().attr('d', '');
|
|
this.dragState = null;
|
|
},
|
|
|
|
getSVGPoint: function(svg, clientX, clientY) {
|
|
var pt = svg.createSVGPoint();
|
|
pt.x = clientX;
|
|
pt.y = clientY;
|
|
var svgP = pt.matrixTransform(svg.getScreenCTM().inverse());
|
|
return { x: svgP.x, y: svgP.y };
|
|
},
|
|
|
|
createConnection: function(carrierId, sourceType, sourceId, targetType, targetId, targetTE) {
|
|
var self = this;
|
|
|
|
var data = {
|
|
action: 'create',
|
|
carrier_id: carrierId,
|
|
token: $('input[name="token"]').val()
|
|
};
|
|
|
|
// Determine source and target based on types
|
|
if (sourceType.indexOf('equipment') !== -1) {
|
|
data.fk_source = sourceId;
|
|
data.source_terminal = sourceType === 'equipment-input' ? 'input' : 'output';
|
|
} else if (sourceType === 'busbar') {
|
|
data.fk_source = null;
|
|
data.source_terminal = 'busbar-' + sourceId;
|
|
}
|
|
|
|
if (targetType.indexOf('equipment') !== -1) {
|
|
data.fk_target = targetId;
|
|
data.target_terminal = targetType === 'equipment-input' ? 'input' : 'output';
|
|
} else if (targetType === 'busbar') {
|
|
data.fk_target = null;
|
|
data.target_terminal = 'busbar-' + targetId + '-te' + targetTE;
|
|
}
|
|
|
|
data.connection_type = 'L1N';
|
|
data.color = this.PHASE_COLORS['L1N'];
|
|
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment_connection.php',
|
|
method: 'POST',
|
|
data: data,
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
self.loadAndRenderEditor(carrierId);
|
|
} else {
|
|
alert('Fehler: ' + response.error);
|
|
}
|
|
},
|
|
error: function() {
|
|
alert('Netzwerkfehler');
|
|
}
|
|
});
|
|
},
|
|
|
|
// Dialogs
|
|
showBusbarDialog: function(carrierId) {
|
|
var self = this;
|
|
|
|
if ($('#kundenkarte-busbar-dialog').length) return;
|
|
|
|
var $editor = $('.kundenkarte-connection-editor[data-carrier-id="' + carrierId + '"]');
|
|
var totalTE = parseInt($editor.data('total-te')) || 12;
|
|
|
|
var html = '<div id="kundenkarte-busbar-dialog" class="kundenkarte-modal">';
|
|
html += '<div class="kundenkarte-modal-content" style="max-width:500px;">';
|
|
html += '<div class="kundenkarte-modal-header"><h3>Sammelschiene hinzufügen</h3>';
|
|
html += '<span class="kundenkarte-modal-close">×</span></div>';
|
|
html += '<div class="kundenkarte-modal-body">';
|
|
|
|
html += '<div class="kundenkarte-connection-form">';
|
|
|
|
// Quick presets
|
|
html += '<div class="form-row" style="margin-bottom:15px;">';
|
|
html += '<label style="width:100%;margin-bottom:8px;font-weight:bold;">Schnellauswahl:</label>';
|
|
html += '<div style="display:flex;flex-wrap:wrap;gap:8px;">';
|
|
|
|
var presets = [
|
|
{ type: 'L1', color: '#8B4513', phases: 'L1' },
|
|
{ type: 'L1N', color: '#3498db', phases: 'L1N' },
|
|
{ type: '3P', color: '#2c3e50', phases: '3P' },
|
|
{ type: '3P+N', color: '#34495e', phases: '3P+N' },
|
|
{ type: 'N', color: '#0066cc', phases: '' },
|
|
{ type: 'PE', color: '#27ae60', phases: '' }
|
|
];
|
|
|
|
presets.forEach(function(p) {
|
|
html += '<button type="button" class="busbar-preset-btn" data-type="' + p.type + '" data-color="' + p.color + '" data-phases="' + p.phases + '" ';
|
|
html += 'style="padding:8px 16px;background:' + p.color + ';color:#fff;border:none;border-radius:4px;cursor:pointer;font-weight:bold;">';
|
|
html += p.type + '</button>';
|
|
});
|
|
|
|
html += '</div></div>';
|
|
|
|
// Type and color
|
|
html += '<div class="form-row">';
|
|
html += '<div class="form-group"><label>Bezeichnung</label>';
|
|
html += '<input type="text" name="busbar_type" value="L1N" placeholder="z.B. L1N, 3P, PE"></div>';
|
|
html += '<div class="form-group"><label>Farbe</label>';
|
|
html += '<input type="color" name="busbar_color" value="#3498db"></div>';
|
|
html += '</div>';
|
|
|
|
// Range
|
|
html += '<div class="form-row">';
|
|
html += '<div class="form-group"><label>Von TE</label>';
|
|
html += '<input type="number" name="busbar_start" value="1" min="1" max="' + totalTE + '"></div>';
|
|
html += '<div class="form-group"><label>Bis TE</label>';
|
|
html += '<input type="number" name="busbar_end" value="' + totalTE + '" min="1" max="' + totalTE + '"></div>';
|
|
html += '</div>';
|
|
|
|
// Phases
|
|
html += '<div class="form-row">';
|
|
html += '<div class="form-group" style="width:100%;"><label>Phasendarstellung</label>';
|
|
html += '<select name="busbar_phases">';
|
|
html += '<option value="">Einfache Linie</option>';
|
|
html += '<option value="L1N">L1+N (2 Linien)</option>';
|
|
html += '<option value="3P">3P (L1/L2/L3)</option>';
|
|
html += '<option value="3P+N">3P+N (L1/L2/L3/N)</option>';
|
|
html += '</select></div>';
|
|
html += '</div>';
|
|
|
|
// Excluded
|
|
html += '<div class="form-row">';
|
|
html += '<div class="form-group" style="width:100%;"><label>Ausgenommene TE (kommagetrennt)</label>';
|
|
html += '<input type="text" name="busbar_excluded" placeholder="z.B. 3,4 für FI-Lücke"></div>';
|
|
html += '</div>';
|
|
|
|
html += '</div>';
|
|
html += '</div>';
|
|
html += '<div class="kundenkarte-modal-footer">';
|
|
html += '<button type="button" class="button" id="busbar-save"><i class="fa fa-save"></i> Erstellen</button> ';
|
|
html += '<button type="button" class="button" id="busbar-cancel">Abbrechen</button>';
|
|
html += '</div></div></div>';
|
|
|
|
$('body').append(html);
|
|
$('#kundenkarte-busbar-dialog').addClass('visible');
|
|
|
|
// Preset handlers
|
|
$('.busbar-preset-btn').on('click', function() {
|
|
$('input[name="busbar_type"]').val($(this).data('type'));
|
|
$('input[name="busbar_color"]').val($(this).data('color'));
|
|
$('select[name="busbar_phases"]').val($(this).data('phases'));
|
|
});
|
|
|
|
// Save
|
|
$('#busbar-save').on('click', function() {
|
|
self.saveBusbar(carrierId);
|
|
});
|
|
|
|
// Close
|
|
$('#busbar-cancel, .kundenkarte-modal-close').on('click', function() {
|
|
$('#kundenkarte-busbar-dialog').remove();
|
|
});
|
|
},
|
|
|
|
saveBusbar: function(carrierId) {
|
|
var self = this;
|
|
|
|
var data = {
|
|
action: 'create_rail',
|
|
carrier_id: carrierId,
|
|
connection_type: $('input[name="busbar_type"]').val(),
|
|
color: $('input[name="busbar_color"]').val(),
|
|
rail_start_te: $('input[name="busbar_start"]').val(),
|
|
rail_end_te: $('input[name="busbar_end"]').val(),
|
|
rail_phases: $('select[name="busbar_phases"]').val(),
|
|
excluded_te: $('input[name="busbar_excluded"]').val(),
|
|
position_y: 0,
|
|
token: $('input[name="token"]').val()
|
|
};
|
|
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment_connection.php',
|
|
method: 'POST',
|
|
data: data,
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
$('#kundenkarte-busbar-dialog').remove();
|
|
self.loadAndRenderEditor(carrierId);
|
|
} else {
|
|
alert('Fehler: ' + response.error);
|
|
}
|
|
},
|
|
error: function() {
|
|
alert('Netzwerkfehler');
|
|
}
|
|
});
|
|
},
|
|
|
|
showEditBusbarDialog: function(connectionId, carrierId) {
|
|
var self = this;
|
|
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment_connection.php',
|
|
data: { action: 'get', connection_id: connectionId },
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success && response.connection) {
|
|
self.renderEditBusbarDialog(connectionId, carrierId, response.connection);
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
renderEditBusbarDialog: function(connectionId, carrierId, conn) {
|
|
var self = this;
|
|
var $editor = $('.kundenkarte-connection-editor[data-carrier-id="' + carrierId + '"]');
|
|
var totalTE = parseInt($editor.data('total-te')) || 12;
|
|
|
|
var html = '<div id="kundenkarte-busbar-dialog" class="kundenkarte-modal">';
|
|
html += '<div class="kundenkarte-modal-content" style="max-width:500px;">';
|
|
html += '<div class="kundenkarte-modal-header"><h3>Sammelschiene bearbeiten</h3>';
|
|
html += '<span class="kundenkarte-modal-close">×</span></div>';
|
|
html += '<div class="kundenkarte-modal-body">';
|
|
|
|
html += '<div class="kundenkarte-connection-form">';
|
|
html += '<input type="hidden" name="busbar_id" value="' + connectionId + '">';
|
|
|
|
// Type and color
|
|
html += '<div class="form-row">';
|
|
html += '<div class="form-group"><label>Bezeichnung</label>';
|
|
html += '<input type="text" name="busbar_type" value="' + this.escapeHtml(conn.connection_type || '') + '"></div>';
|
|
html += '<div class="form-group"><label>Farbe</label>';
|
|
html += '<input type="color" name="busbar_color" value="' + (conn.color || '#3498db') + '"></div>';
|
|
html += '</div>';
|
|
|
|
// Range
|
|
html += '<div class="form-row">';
|
|
html += '<div class="form-group"><label>Von TE</label>';
|
|
html += '<input type="number" name="busbar_start" value="' + (conn.rail_start_te || 1) + '" min="1" max="' + totalTE + '"></div>';
|
|
html += '<div class="form-group"><label>Bis TE</label>';
|
|
html += '<input type="number" name="busbar_end" value="' + (conn.rail_end_te || totalTE) + '" min="1" max="' + totalTE + '"></div>';
|
|
html += '</div>';
|
|
|
|
// Phases
|
|
html += '<div class="form-row">';
|
|
html += '<div class="form-group" style="width:100%;"><label>Phasendarstellung</label>';
|
|
html += '<select name="busbar_phases">';
|
|
html += '<option value=""' + (!conn.rail_phases ? ' selected' : '') + '>Einfache Linie</option>';
|
|
html += '<option value="L1N"' + (conn.rail_phases === 'L1N' ? ' selected' : '') + '>L1+N</option>';
|
|
html += '<option value="3P"' + (conn.rail_phases === '3P' ? ' selected' : '') + '>3P</option>';
|
|
html += '<option value="3P+N"' + (conn.rail_phases === '3P+N' ? ' selected' : '') + '>3P+N</option>';
|
|
html += '</select></div>';
|
|
html += '</div>';
|
|
|
|
// Excluded
|
|
html += '<div class="form-row">';
|
|
html += '<div class="form-group" style="width:100%;"><label>Ausgenommene TE</label>';
|
|
html += '<input type="text" name="busbar_excluded" value="' + this.escapeHtml(conn.excluded_te || '') + '"></div>';
|
|
html += '</div>';
|
|
|
|
html += '</div>';
|
|
html += '</div>';
|
|
html += '<div class="kundenkarte-modal-footer">';
|
|
html += '<button type="button" class="button" id="busbar-save"><i class="fa fa-save"></i> Speichern</button> ';
|
|
html += '<button type="button" class="button" id="busbar-delete" style="background:#c0392b;color:#fff;"><i class="fa fa-trash"></i> Löschen</button> ';
|
|
html += '<button type="button" class="button" id="busbar-cancel">Abbrechen</button>';
|
|
html += '</div></div></div>';
|
|
|
|
$('body').append(html);
|
|
$('#kundenkarte-busbar-dialog').addClass('visible');
|
|
|
|
// Save
|
|
$('#busbar-save').on('click', function() {
|
|
self.updateBusbar(connectionId, carrierId);
|
|
});
|
|
|
|
// Delete
|
|
$('#busbar-delete').on('click', function() {
|
|
$('#kundenkarte-busbar-dialog').remove();
|
|
self.deleteConnection(connectionId, carrierId);
|
|
});
|
|
|
|
// Close
|
|
$('#busbar-cancel, .kundenkarte-modal-close').on('click', function() {
|
|
$('#kundenkarte-busbar-dialog').remove();
|
|
});
|
|
},
|
|
|
|
updateBusbar: function(connectionId, carrierId) {
|
|
var self = this;
|
|
|
|
var data = {
|
|
action: 'update',
|
|
connection_id: connectionId,
|
|
carrier_id: carrierId,
|
|
connection_type: $('input[name="busbar_type"]').val(),
|
|
color: $('input[name="busbar_color"]').val(),
|
|
rail_start_te: $('input[name="busbar_start"]').val(),
|
|
rail_end_te: $('input[name="busbar_end"]').val(),
|
|
rail_phases: $('select[name="busbar_phases"]').val(),
|
|
excluded_te: $('input[name="busbar_excluded"]').val(),
|
|
is_rail: 1,
|
|
position_y: 0,
|
|
token: $('input[name="token"]').val()
|
|
};
|
|
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment_connection.php',
|
|
method: 'POST',
|
|
data: data,
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
$('#kundenkarte-busbar-dialog').remove();
|
|
self.loadAndRenderEditor(carrierId);
|
|
} else {
|
|
alert('Fehler: ' + response.error);
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
showConnectionDialog: function(carrierId) {
|
|
var self = this;
|
|
|
|
if ($('#kundenkarte-conn-dialog').length) return;
|
|
|
|
var html = '<div id="kundenkarte-conn-dialog" class="kundenkarte-modal">';
|
|
html += '<div class="kundenkarte-modal-content" style="max-width:500px;">';
|
|
html += '<div class="kundenkarte-modal-header"><h3>Verbindung hinzufügen</h3>';
|
|
html += '<span class="kundenkarte-modal-close">×</span></div>';
|
|
html += '<div class="kundenkarte-modal-body">';
|
|
|
|
html += '<div class="kundenkarte-connection-form">';
|
|
|
|
// Source equipment
|
|
html += '<div class="form-row">';
|
|
html += '<div class="form-group" style="width:100%;"><label>Von (Quelle)</label>';
|
|
html += '<select name="conn_source">';
|
|
html += '<option value="">-- Auswählen --</option>';
|
|
self.equipment.forEach(function(eq) {
|
|
html += '<option value="' + eq.id + '">' + self.escapeHtml(eq.label || eq.type_label || 'Equipment ' + eq.id) + '</option>';
|
|
});
|
|
html += '</select></div>';
|
|
html += '</div>';
|
|
|
|
// Source terminal
|
|
html += '<div class="form-row">';
|
|
html += '<div class="form-group" style="width:100%;"><label>Anschluss (Quelle)</label>';
|
|
html += '<select name="conn_source_terminal">';
|
|
html += '<option value="output">Ausgang (unten)</option>';
|
|
html += '<option value="input">Eingang (oben)</option>';
|
|
html += '</select></div>';
|
|
html += '</div>';
|
|
|
|
// Target equipment
|
|
html += '<div class="form-row">';
|
|
html += '<div class="form-group" style="width:100%;"><label>Nach (Ziel)</label>';
|
|
html += '<select name="conn_target">';
|
|
html += '<option value="">-- Auswählen --</option>';
|
|
self.equipment.forEach(function(eq) {
|
|
html += '<option value="' + eq.id + '">' + self.escapeHtml(eq.label || eq.type_label || 'Equipment ' + eq.id) + '</option>';
|
|
});
|
|
html += '</select></div>';
|
|
html += '</div>';
|
|
|
|
// Target terminal
|
|
html += '<div class="form-row">';
|
|
html += '<div class="form-group" style="width:100%;"><label>Anschluss (Ziel)</label>';
|
|
html += '<select name="conn_target_terminal">';
|
|
html += '<option value="input">Eingang (oben)</option>';
|
|
html += '<option value="output">Ausgang (unten)</option>';
|
|
html += '</select></div>';
|
|
html += '</div>';
|
|
|
|
// Connection type
|
|
html += '<div class="form-row">';
|
|
html += '<div class="form-group"><label>Typ</label>';
|
|
html += '<input type="text" name="conn_type" value="L1N" placeholder="z.B. L1N, 3P"></div>';
|
|
html += '<div class="form-group"><label>Farbe</label>';
|
|
html += '<input type="color" name="conn_color" value="#3498db"></div>';
|
|
html += '</div>';
|
|
|
|
// Label
|
|
html += '<div class="form-row">';
|
|
html += '<div class="form-group" style="width:100%;"><label>Beschriftung (optional)</label>';
|
|
html += '<input type="text" name="conn_label" placeholder="z.B. Küche"></div>';
|
|
html += '</div>';
|
|
|
|
html += '</div>';
|
|
html += '</div>';
|
|
html += '<div class="kundenkarte-modal-footer">';
|
|
html += '<button type="button" class="button" id="conn-save"><i class="fa fa-save"></i> Erstellen</button> ';
|
|
html += '<button type="button" class="button" id="conn-cancel">Abbrechen</button>';
|
|
html += '</div></div></div>';
|
|
|
|
$('body').append(html);
|
|
$('#kundenkarte-conn-dialog').addClass('visible');
|
|
|
|
// Save
|
|
$('#conn-save').on('click', function() {
|
|
self.saveConnection(carrierId);
|
|
});
|
|
|
|
// Close
|
|
$('#conn-cancel, .kundenkarte-modal-close').on('click', function() {
|
|
$('#kundenkarte-conn-dialog').remove();
|
|
});
|
|
},
|
|
|
|
saveConnection: function(carrierId) {
|
|
var self = this;
|
|
|
|
var sourceId = $('select[name="conn_source"]').val();
|
|
var targetId = $('select[name="conn_target"]').val();
|
|
|
|
if (!sourceId || !targetId) {
|
|
alert('Bitte Quelle und Ziel auswählen');
|
|
return;
|
|
}
|
|
|
|
var data = {
|
|
action: 'create',
|
|
carrier_id: carrierId,
|
|
fk_source: sourceId,
|
|
source_terminal: $('select[name="conn_source_terminal"]').val(),
|
|
fk_target: targetId,
|
|
target_terminal: $('select[name="conn_target_terminal"]').val(),
|
|
connection_type: $('input[name="conn_type"]').val(),
|
|
color: $('input[name="conn_color"]').val(),
|
|
output_label: $('input[name="conn_label"]').val(),
|
|
is_rail: 0,
|
|
token: $('input[name="token"]').val()
|
|
};
|
|
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment_connection.php',
|
|
method: 'POST',
|
|
data: data,
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
$('#kundenkarte-conn-dialog').remove();
|
|
self.loadAndRenderEditor(carrierId);
|
|
} else {
|
|
alert('Fehler: ' + response.error);
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
showEditConnectionDialog: function(connectionId, carrierId) {
|
|
var self = this;
|
|
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment_connection.php',
|
|
data: { action: 'get', connection_id: connectionId },
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success && response.connection) {
|
|
self.renderEditConnectionDialog(connectionId, carrierId, response.connection);
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
renderEditConnectionDialog: function(connectionId, carrierId, conn) {
|
|
var self = this;
|
|
|
|
var html = '<div id="kundenkarte-conn-dialog" class="kundenkarte-modal">';
|
|
html += '<div class="kundenkarte-modal-content" style="max-width:500px;">';
|
|
html += '<div class="kundenkarte-modal-header"><h3>Verbindung bearbeiten</h3>';
|
|
html += '<span class="kundenkarte-modal-close">×</span></div>';
|
|
html += '<div class="kundenkarte-modal-body">';
|
|
|
|
html += '<div class="kundenkarte-connection-form">';
|
|
html += '<input type="hidden" name="conn_id" value="' + connectionId + '">';
|
|
|
|
// Connection type
|
|
html += '<div class="form-row">';
|
|
html += '<div class="form-group"><label>Typ</label>';
|
|
html += '<input type="text" name="conn_type" value="' + this.escapeHtml(conn.connection_type || '') + '"></div>';
|
|
html += '<div class="form-group"><label>Farbe</label>';
|
|
html += '<input type="color" name="conn_color" value="' + (conn.color || '#3498db') + '"></div>';
|
|
html += '</div>';
|
|
|
|
// Label
|
|
html += '<div class="form-row">';
|
|
html += '<div class="form-group" style="width:100%;"><label>Beschriftung</label>';
|
|
html += '<input type="text" name="conn_label" value="' + this.escapeHtml(conn.output_label || '') + '"></div>';
|
|
html += '</div>';
|
|
|
|
html += '</div>';
|
|
html += '</div>';
|
|
html += '<div class="kundenkarte-modal-footer">';
|
|
html += '<button type="button" class="button" id="conn-save"><i class="fa fa-save"></i> Speichern</button> ';
|
|
html += '<button type="button" class="button" id="conn-delete" style="background:#c0392b;color:#fff;"><i class="fa fa-trash"></i> Löschen</button> ';
|
|
html += '<button type="button" class="button" id="conn-cancel">Abbrechen</button>';
|
|
html += '</div></div></div>';
|
|
|
|
$('body').append(html);
|
|
$('#kundenkarte-conn-dialog').addClass('visible');
|
|
|
|
// Save
|
|
$('#conn-save').on('click', function() {
|
|
self.updateConnection(connectionId, carrierId, conn);
|
|
});
|
|
|
|
// Delete
|
|
$('#conn-delete').on('click', function() {
|
|
$('#kundenkarte-conn-dialog').remove();
|
|
self.deleteConnection(connectionId, carrierId);
|
|
});
|
|
|
|
// Close
|
|
$('#conn-cancel, .kundenkarte-modal-close').on('click', function() {
|
|
$('#kundenkarte-conn-dialog').remove();
|
|
});
|
|
},
|
|
|
|
updateConnection: function(connectionId, carrierId, originalConn) {
|
|
var self = this;
|
|
|
|
var data = {
|
|
action: 'update',
|
|
connection_id: connectionId,
|
|
carrier_id: carrierId,
|
|
fk_source: originalConn.fk_source,
|
|
source_terminal: originalConn.source_terminal,
|
|
fk_target: originalConn.fk_target,
|
|
target_terminal: originalConn.target_terminal,
|
|
connection_type: $('input[name="conn_type"]').val(),
|
|
color: $('input[name="conn_color"]').val(),
|
|
output_label: $('input[name="conn_label"]').val(),
|
|
is_rail: 0,
|
|
token: $('input[name="token"]').val()
|
|
};
|
|
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment_connection.php',
|
|
method: 'POST',
|
|
data: data,
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
$('#kundenkarte-conn-dialog').remove();
|
|
self.loadAndRenderEditor(carrierId);
|
|
} else {
|
|
alert('Fehler: ' + response.error);
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
deleteConnection: function(connectionId, carrierId) {
|
|
var self = this;
|
|
|
|
if (!confirm('Verbindung wirklich löschen?')) return;
|
|
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment_connection.php',
|
|
method: 'POST',
|
|
data: {
|
|
action: 'delete',
|
|
connection_id: connectionId,
|
|
token: $('input[name="token"]').val()
|
|
},
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
self.loadAndRenderEditor(carrierId);
|
|
} else {
|
|
alert('Fehler: ' + response.error);
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
// Demo view when no busbars exist
|
|
renderDemoView: function(carrierId, totalTE, totalWidth) {
|
|
var self = this;
|
|
var html = '';
|
|
var BUSBAR_Y = 15;
|
|
var BUSBAR_HEIGHT = this.BUSBAR_HEIGHT;
|
|
var phaseList = ['L1', 'L2', 'L3', 'N'];
|
|
var phaseHeight = 6;
|
|
var phaseSpacing = 2;
|
|
var totalPhaseHeight = phaseList.length * phaseHeight + (phaseList.length - 1) * phaseSpacing;
|
|
var phaseStartY = BUSBAR_Y + (BUSBAR_HEIGHT - totalPhaseHeight) / 2;
|
|
|
|
// Demo background overlay
|
|
html += '<rect x="0" y="0" width="' + totalWidth + '" height="200" fill="#1a1a2e" opacity="0.3"/>';
|
|
|
|
// Demo label
|
|
html += '<text x="' + (totalWidth / 2) + '" y="10" text-anchor="middle" fill="#888" font-size="10" font-style="italic">';
|
|
html += 'Demo-Ansicht - Klicken Sie auf "Sammelschiene" um eine echte Schiene hinzuzufügen';
|
|
html += '</text>';
|
|
|
|
// Background grid for TE positions
|
|
for (var i = 0; i <= totalTE; i++) {
|
|
var x = i * this.TE_WIDTH;
|
|
html += '<line x1="' + x + '" y1="15" x2="' + x + '" y2="180" stroke="#333" stroke-width="0.5" stroke-dasharray="2,2"/>';
|
|
// TE number label
|
|
if (i < totalTE) {
|
|
html += '<text x="' + (x + this.TE_WIDTH / 2) + '" y="190" text-anchor="middle" fill="#555" font-size="8">' + (i + 1) + '</text>';
|
|
}
|
|
}
|
|
|
|
// Render demo 3P+N busbar
|
|
html += '<g class="kundenkarte-demo-busbar" opacity="0.8">';
|
|
|
|
// Busbar background
|
|
html += '<rect x="20" y="' + BUSBAR_Y + '" width="' + (totalWidth - 40) + '" height="' + BUSBAR_HEIGHT + '" ';
|
|
html += 'fill="#2d2d44" rx="3" ry="3" stroke="#555" stroke-width="1" stroke-dasharray="4,2"/>';
|
|
|
|
// Draw each phase line
|
|
phaseList.forEach(function(phase, idx) {
|
|
var phaseY = phaseStartY + idx * (phaseHeight + phaseSpacing);
|
|
var phaseColor = self.PHASE_COLORS[phase];
|
|
|
|
// Phase line
|
|
html += '<rect x="25" y="' + phaseY + '" width="' + (totalWidth - 50) + '" height="' + phaseHeight + '" ';
|
|
html += 'fill="' + phaseColor + '" rx="1" ry="1" opacity="0.9"/>';
|
|
|
|
// Phase label on the left
|
|
html += '<text x="18" y="' + (phaseY + phaseHeight / 2 + 3) + '" ';
|
|
html += 'text-anchor="end" fill="' + phaseColor + '" font-size="9" font-weight="bold">';
|
|
html += phase;
|
|
html += '</text>';
|
|
|
|
// Connection endpoints for each TE
|
|
for (var te = 1; te <= totalTE; te++) {
|
|
var epX = (te - 0.5) * self.TE_WIDTH;
|
|
var epY = phaseY + phaseHeight;
|
|
|
|
html += '<circle class="kundenkarte-endpoint kundenkarte-demo-endpoint" ';
|
|
html += 'cx="' + epX + '" cy="' + epY + '" r="4" ';
|
|
html += 'fill="' + phaseColor + '" stroke="#fff" stroke-width="1" opacity="0.7"/>';
|
|
}
|
|
});
|
|
|
|
// Busbar type label
|
|
html += '<text x="' + (totalWidth - 25) + '" y="' + (BUSBAR_Y + BUSBAR_HEIGHT - 2) + '" ';
|
|
html += 'text-anchor="end" fill="#888" font-size="8">3P+N</text>';
|
|
|
|
html += '</g>'; // End demo busbar group
|
|
|
|
// Equipment visualization layer
|
|
var equipmentY = BUSBAR_Y + BUSBAR_HEIGHT + 40;
|
|
html += '<g class="kundenkarte-demo-equipment">';
|
|
|
|
// Draw existing equipment with connection endpoints
|
|
this.equipment.forEach(function(eq) {
|
|
var width = eq.width_te * self.TE_WIDTH;
|
|
var centerX = (eq.position_te - 1) * self.TE_WIDTH + width / 2;
|
|
var color = eq.block_color || eq.type_color || '#3498db';
|
|
|
|
// Equipment block
|
|
html += '<rect x="' + ((eq.position_te - 1) * self.TE_WIDTH + 2) + '" y="' + (equipmentY - 8) + '" ';
|
|
html += 'width="' + (width - 4) + '" height="16" rx="3" ry="3" fill="' + color + '" stroke="#333" stroke-width="1"/>';
|
|
|
|
// Label
|
|
html += '<text x="' + centerX + '" y="' + (equipmentY + 4) + '" text-anchor="middle" fill="#fff" font-size="9" font-weight="bold">';
|
|
html += self.escapeHtml(eq.type_label_short || eq.label || '');
|
|
html += '</text>';
|
|
|
|
// Input endpoint (top)
|
|
html += '<circle class="kundenkarte-endpoint kundenkarte-equipment-input" ';
|
|
html += 'data-type="equipment-input" data-equipment-id="' + eq.id + '" ';
|
|
html += 'cx="' + centerX + '" cy="' + (equipmentY - 15) + '" r="' + self.ENDPOINT_RADIUS + '" ';
|
|
html += 'fill="#fff" stroke="' + color + '" stroke-width="2"/>';
|
|
|
|
// Output endpoint (bottom)
|
|
html += '<circle class="kundenkarte-endpoint kundenkarte-equipment-output" ';
|
|
html += 'data-type="equipment-output" data-equipment-id="' + eq.id + '" ';
|
|
html += 'cx="' + centerX + '" cy="' + (equipmentY + 15) + '" r="' + self.ENDPOINT_RADIUS + '" ';
|
|
html += 'fill="#fff" stroke="' + color + '" stroke-width="2"/>';
|
|
|
|
// Draw demo connections from phases to equipment
|
|
// Distribute phases across equipment
|
|
var phaseIndex = (eq.position_te - 1) % 3; // Rotate through L1, L2, L3
|
|
var phase = phaseList[phaseIndex];
|
|
var phaseColor = self.PHASE_COLORS[phase];
|
|
var phaseY = phaseStartY + phaseIndex * (phaseHeight + phaseSpacing) + phaseHeight;
|
|
|
|
// Demo connection line
|
|
html += '<path class="kundenkarte-demo-connection" ';
|
|
html += 'd="M ' + centerX + ' ' + phaseY + ' L ' + centerX + ' ' + (equipmentY - 15) + '" ';
|
|
html += 'fill="none" stroke="' + phaseColor + '" stroke-width="2" stroke-dasharray="4,2" opacity="0.6"/>';
|
|
|
|
// Phase indicator text below equipment
|
|
html += '<text x="' + centerX + '" y="' + (equipmentY + 30) + '" text-anchor="middle" fill="' + phaseColor + '" font-size="8" font-weight="bold">';
|
|
html += phase;
|
|
html += '</text>';
|
|
});
|
|
|
|
html += '</g>'; // End demo equipment group
|
|
|
|
// Legend
|
|
html += '<g class="kundenkarte-demo-legend" transform="translate(10, 160)">';
|
|
html += '<text x="0" y="0" fill="#888" font-size="9">Legende:</text>';
|
|
|
|
phaseList.forEach(function(phase, idx) {
|
|
var x = 60 + idx * 50;
|
|
html += '<rect x="' + x + '" y="-8" width="12" height="8" fill="' + self.PHASE_COLORS[phase] + '" rx="1"/>';
|
|
html += '<text x="' + (x + 16) + '" y="0" fill="' + self.PHASE_COLORS[phase] + '" font-size="9" font-weight="bold">' + phase + '</text>';
|
|
});
|
|
|
|
html += '</g>';
|
|
|
|
return html;
|
|
},
|
|
|
|
// Helpers
|
|
darkenColor: function(color, percent) {
|
|
var num = parseInt(color.replace('#', ''), 16);
|
|
var amt = Math.round(2.55 * percent);
|
|
var R = (num >> 16) - amt;
|
|
var G = (num >> 8 & 0x00FF) - amt;
|
|
var B = (num & 0x0000FF) - amt;
|
|
return '#' + (0x1000000 + (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 +
|
|
(G < 255 ? G < 1 ? 0 : G : 255) * 0x100 +
|
|
(B < 255 ? B < 1 ? 0 : B : 255)).toString(16).slice(1);
|
|
},
|
|
|
|
escapeHtml: function(text) {
|
|
if (!text) return '';
|
|
var div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* SchematicEditor - Interaktiver Schaltplan-Editor
|
|
*
|
|
* Features:
|
|
* - Hutschienen als feste Zeilen
|
|
* - Equipment-Blöcke verschiebbar auf Hutschiene
|
|
* - Konfigurierbare Terminals pro Equipment-Typ
|
|
* - Klick-Verbindungen: Ausgang klicken → Eingang klicken
|
|
* - Rechtsklick zum Löschen
|
|
* - Orthogonale Verbindungspfade
|
|
*/
|
|
KundenKarte.SchematicEditor = {
|
|
// Constants
|
|
TE_WIDTH: 40,
|
|
RAIL_HEIGHT: 8,
|
|
RAIL_SPACING: 160, // Abstand zwischen Hutschienen
|
|
BLOCK_HEIGHT: 80,
|
|
TERMINAL_RADIUS: 6,
|
|
GRID_SIZE: 10,
|
|
TOP_MARGIN: 80, // Platz oben für Verbindungen
|
|
BOTTOM_MARGIN: 60, // Platz unten
|
|
PANEL_GAP: 40, // Abstand zwischen Panels
|
|
PANEL_PADDING: 20, // Innenabstand Panel
|
|
|
|
// Colors
|
|
COLORS: {
|
|
rail: '#666',
|
|
railBg: '#444',
|
|
block: '#3498db',
|
|
input: '#e74c3c',
|
|
output: '#27ae60',
|
|
connection: '#f1c40f',
|
|
selected: '#9b59b6',
|
|
hover: '#fff',
|
|
grid: '#2a2a2a',
|
|
panelBg: '#1e1e1e',
|
|
panelBorder: '#333'
|
|
},
|
|
|
|
PHASE_COLORS: {
|
|
'L1': '#8B4513',
|
|
'L2': '#1a1a1a',
|
|
'L3': '#666666',
|
|
'N': '#0066cc',
|
|
'PE': '#27ae60'
|
|
},
|
|
|
|
// State
|
|
anlageId: null,
|
|
panels: [],
|
|
carriers: [],
|
|
equipment: [],
|
|
connections: [],
|
|
selectedTerminal: null,
|
|
dragState: null,
|
|
isInitialized: false,
|
|
svgElement: null,
|
|
scale: 1,
|
|
|
|
// Default terminal configs for common types
|
|
// Terminals are bidirectional - no strict input/output
|
|
DEFAULT_TERMINALS: {
|
|
'LS': { terminals: [{id: 't1', label: '●', pos: 'top'}, {id: 't2', label: '●', pos: 'bottom'}] },
|
|
'FI': { terminals: [{id: 't1', label: 'L', pos: 'top'}, {id: 't2', label: 'N', pos: 'top'}, {id: 't3', label: 'L', pos: 'bottom'}, {id: 't4', label: 'N', pos: 'bottom'}] },
|
|
'FI4P': { terminals: [{id: 't1', label: 'L1', pos: 'top'}, {id: 't2', label: 'L2', pos: 'top'}, {id: 't3', label: 'L3', pos: 'top'}, {id: 't4', label: 'N', pos: 'top'}, {id: 't5', label: 'L1', pos: 'bottom'}, {id: 't6', label: 'L2', pos: 'bottom'}, {id: 't7', label: 'L3', pos: 'bottom'}, {id: 't8', label: 'N', pos: 'bottom'}] },
|
|
'SCHUETZ': { terminals: [{id: 't1', label: 'A1', pos: 'top'}, {id: 't2', label: '1', pos: 'top'}, {id: 't3', label: 'A2', pos: 'bottom'}, {id: 't4', label: '2', pos: 'bottom'}] },
|
|
'KLEMME': { terminals: [{id: 't1', label: '●', pos: 'top'}, {id: 't2', label: '●', pos: 'bottom'}] },
|
|
'LS3P': { terminals: [{id: 't1', label: 'L1', pos: 'top'}, {id: 't2', label: 'L2', pos: 'top'}, {id: 't3', label: 'L3', pos: 'top'}, {id: 't4', label: 'L1', pos: 'bottom'}, {id: 't5', label: 'L2', pos: 'bottom'}, {id: 't6', label: 'L3', pos: 'bottom'}] }
|
|
},
|
|
|
|
init: function(anlageId) {
|
|
if (!anlageId) return;
|
|
this.anlageId = anlageId;
|
|
this.bindEvents();
|
|
this.loadData();
|
|
},
|
|
|
|
bindEvents: function() {
|
|
var self = this;
|
|
|
|
// Terminal click - start/end connection
|
|
$(document).off('click.terminal').on('click.terminal', '.schematic-terminal', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
self.handleTerminalClick($(this));
|
|
});
|
|
|
|
// Block drag
|
|
$(document).off('mousedown.blockDrag').on('mousedown.blockDrag', '.schematic-block', function(e) {
|
|
if ($(e.target).hasClass('schematic-terminal')) return;
|
|
e.preventDefault();
|
|
self.startDragBlock($(this), e);
|
|
});
|
|
|
|
$(document).off('mousemove.blockDrag').on('mousemove.blockDrag', function(e) {
|
|
if (self.dragState && self.dragState.type === 'block') {
|
|
self.updateDragBlock(e);
|
|
}
|
|
});
|
|
|
|
$(document).off('mouseup.blockDrag').on('mouseup.blockDrag', function(e) {
|
|
if (self.dragState && self.dragState.type === 'block') {
|
|
self.endDragBlock(e);
|
|
}
|
|
});
|
|
|
|
// Connection right-click to delete
|
|
$(document).off('contextmenu.connection').on('contextmenu.connection', '.schematic-connection', function(e) {
|
|
e.preventDefault();
|
|
var connId = $(this).data('connection-id');
|
|
if (confirm('Verbindung löschen?')) {
|
|
self.deleteConnection(connId);
|
|
}
|
|
});
|
|
|
|
// Clear all connections
|
|
$(document).off('click.clearConns').on('click.clearConns', '.schematic-clear-connections', function(e) {
|
|
e.preventDefault();
|
|
if (confirm('Alle Verbindungen löschen?')) {
|
|
self.clearAllConnections();
|
|
}
|
|
});
|
|
|
|
// Escape to cancel selection
|
|
$(document).off('keydown.schematic').on('keydown.schematic', function(e) {
|
|
if (e.key === 'Escape' && self.selectedTerminal) {
|
|
self.cancelSelection();
|
|
}
|
|
});
|
|
},
|
|
|
|
loadData: function() {
|
|
var self = this;
|
|
|
|
// Load panels with carriers for this anlage
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment_panel.php',
|
|
data: { action: 'list_with_carriers', anlage_id: this.anlageId },
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
self.panels = response.panels || [];
|
|
// Flatten carriers from all panels
|
|
self.carriers = [];
|
|
self.panels.forEach(function(panel) {
|
|
if (panel.carriers) {
|
|
panel.carriers.forEach(function(c) {
|
|
c.panel_id = panel.id;
|
|
self.carriers.push(c);
|
|
});
|
|
}
|
|
});
|
|
self.loadEquipment();
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
loadEquipment: function() {
|
|
var self = this;
|
|
var promises = [];
|
|
|
|
this.equipment = [];
|
|
|
|
this.carriers.forEach(function(carrier) {
|
|
var promise = $.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment.php',
|
|
data: { action: 'list', carrier_id: carrier.id },
|
|
dataType: 'json'
|
|
}).then(function(response) {
|
|
if (response.success && response.equipment) {
|
|
response.equipment.forEach(function(eq) {
|
|
eq.carrier_id = carrier.id;
|
|
eq.panel_id = carrier.panel_id;
|
|
self.equipment.push(eq);
|
|
});
|
|
}
|
|
});
|
|
promises.push(promise);
|
|
});
|
|
|
|
$.when.apply($, promises).then(function() {
|
|
self.loadConnections();
|
|
});
|
|
},
|
|
|
|
loadConnections: function() {
|
|
var self = this;
|
|
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment_connection.php',
|
|
data: { action: 'list_all', anlage_id: this.anlageId },
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
self.connections = response.connections || [];
|
|
}
|
|
// Initialize canvas now that all data is loaded
|
|
if (!self.isInitialized) {
|
|
self.initCanvas();
|
|
} else {
|
|
self.render();
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
initCanvas: function() {
|
|
var self = this;
|
|
var $canvas = $('.schematic-editor-canvas');
|
|
|
|
if (!$canvas.length) return;
|
|
|
|
// Calculate canvas size based on panels
|
|
// Panels nebeneinander, Hutschienen pro Panel untereinander
|
|
var totalWidth = this.PANEL_PADDING;
|
|
var maxPanelHeight = 0;
|
|
|
|
this.panels.forEach(function(panel, panelIdx) {
|
|
var panelCarriers = self.carriers.filter(function(c) { return c.panel_id == panel.id; });
|
|
var maxTE = 12;
|
|
panelCarriers.forEach(function(c) {
|
|
if ((c.total_te || 12) > maxTE) maxTE = c.total_te;
|
|
});
|
|
|
|
var panelWidth = maxTE * self.TE_WIDTH + self.PANEL_PADDING * 2;
|
|
var panelHeight = self.TOP_MARGIN + panelCarriers.length * self.RAIL_SPACING + self.BOTTOM_MARGIN;
|
|
|
|
panel._x = totalWidth;
|
|
panel._width = panelWidth;
|
|
panel._height = panelHeight;
|
|
|
|
totalWidth += panelWidth + self.PANEL_GAP;
|
|
if (panelHeight > maxPanelHeight) maxPanelHeight = panelHeight;
|
|
});
|
|
|
|
totalWidth = Math.max(totalWidth, 400);
|
|
var totalHeight = Math.max(maxPanelHeight, 300);
|
|
|
|
// Fallback: wenn keine Panels, basierend auf Carriers
|
|
if (this.panels.length === 0 && this.carriers.length > 0) {
|
|
var fallbackMaxTE = 12;
|
|
this.carriers.forEach(function(c) {
|
|
if ((c.total_te || 12) > fallbackMaxTE) fallbackMaxTE = c.total_te;
|
|
});
|
|
totalWidth = fallbackMaxTE * self.TE_WIDTH + 150;
|
|
totalHeight = self.TOP_MARGIN + this.carriers.length * self.RAIL_SPACING + self.BOTTOM_MARGIN;
|
|
}
|
|
|
|
totalWidth = Math.max(totalWidth, 800);
|
|
totalHeight = Math.max(totalHeight, 400);
|
|
|
|
// Create SVG
|
|
var svg = '<svg class="schematic-svg" width="' + totalWidth + '" height="' + totalHeight + '" xmlns="http://www.w3.org/2000/svg">';
|
|
|
|
// Defs for markers and patterns
|
|
svg += '<defs>';
|
|
svg += '<pattern id="grid" width="' + this.GRID_SIZE + '" height="' + this.GRID_SIZE + '" patternUnits="userSpaceOnUse">';
|
|
svg += '<path d="M ' + this.GRID_SIZE + ' 0 L 0 0 0 ' + this.GRID_SIZE + '" fill="none" stroke="' + this.COLORS.grid + '" stroke-width="0.5"/>';
|
|
svg += '</pattern>';
|
|
svg += '<marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto"><polygon points="0 0, 10 3.5, 0 7" fill="#888"/></marker>';
|
|
svg += '</defs>';
|
|
|
|
// Background grid
|
|
svg += '<rect width="100%" height="100%" fill="url(#grid)"/>';
|
|
|
|
// Layers
|
|
svg += '<g class="schematic-rails-layer"></g>';
|
|
svg += '<g class="schematic-connections-layer"></g>';
|
|
svg += '<g class="schematic-blocks-layer"></g>';
|
|
svg += '<g class="schematic-terminals-layer"></g>';
|
|
|
|
// Connection preview line
|
|
svg += '<line class="schematic-connection-preview" x1="0" y1="0" x2="0" y2="0" stroke="#3498db" stroke-width="2" stroke-dasharray="5,5" style="display:none;"/>';
|
|
|
|
svg += '</svg>';
|
|
|
|
$canvas.html(svg);
|
|
this.svgElement = $canvas.find('.schematic-svg')[0];
|
|
this.isInitialized = true;
|
|
|
|
// Render content
|
|
this.render();
|
|
|
|
// Track mouse for connection preview
|
|
$canvas.on('mousemove', function(e) {
|
|
if (self.selectedTerminal) {
|
|
self.updateConnectionPreview(e);
|
|
}
|
|
});
|
|
},
|
|
|
|
render: function() {
|
|
if (!this.isInitialized) return;
|
|
|
|
this.renderRails();
|
|
this.renderBlocks();
|
|
this.renderConnections();
|
|
},
|
|
|
|
renderRails: function() {
|
|
var self = this;
|
|
var $layer = $(this.svgElement).find('.schematic-rails-layer');
|
|
$layer.empty();
|
|
|
|
var html = '';
|
|
|
|
// Render by panels (nebeneinander)
|
|
if (this.panels.length > 0) {
|
|
this.panels.forEach(function(panel) {
|
|
var panelX = panel._x || self.PANEL_PADDING;
|
|
var panelCarriers = self.carriers.filter(function(c) { return c.panel_id == panel.id; });
|
|
|
|
// Panel background
|
|
if (panel._width && panel._height) {
|
|
html += '<rect class="schematic-panel-bg" x="' + (panelX - 10) + '" y="' + (self.TOP_MARGIN - 30) + '" ';
|
|
html += 'width="' + (panel._width + 20) + '" height="' + (panel._height - self.TOP_MARGIN + 40) + '" ';
|
|
html += 'fill="' + self.COLORS.panelBg + '" stroke="' + self.COLORS.panelBorder + '" stroke-width="1" rx="4"/>';
|
|
|
|
// Panel label
|
|
html += '<text x="' + (panelX + panel._width / 2) + '" y="' + (self.TOP_MARGIN - 40) + '" ';
|
|
html += 'text-anchor="middle" fill="#aaa" font-size="12" font-weight="bold">';
|
|
html += self.escapeHtml(panel.label || 'Feld');
|
|
html += '</text>';
|
|
}
|
|
|
|
// Carriers untereinander im Panel
|
|
panelCarriers.forEach(function(carrier, carrierIdx) {
|
|
var y = self.TOP_MARGIN + carrierIdx * self.RAIL_SPACING;
|
|
var width = (carrier.total_te || 12) * self.TE_WIDTH;
|
|
var x = panelX + self.PANEL_PADDING;
|
|
|
|
// Store carrier position
|
|
carrier._y = y;
|
|
carrier._x = x;
|
|
|
|
// Rail background
|
|
html += '<rect class="schematic-rail-bg" x="' + x + '" y="' + (y - 5) + '" width="' + width + '" height="' + self.RAIL_HEIGHT + '" ';
|
|
html += 'fill="' + self.COLORS.railBg + '" rx="2"/>';
|
|
|
|
// Rail
|
|
html += '<rect class="schematic-rail" data-carrier-id="' + carrier.id + '" x="' + x + '" y="' + (y - 3) + '" width="' + width + '" height="' + (self.RAIL_HEIGHT - 4) + '" ';
|
|
html += 'fill="' + self.COLORS.rail + '" rx="1"/>';
|
|
|
|
// Rail label (links)
|
|
html += '<text x="' + (x - 5) + '" y="' + y + '" text-anchor="end" fill="#666" font-size="9">';
|
|
html += self.escapeHtml(carrier.label || 'H' + (carrierIdx + 1));
|
|
html += '</text>';
|
|
|
|
// TE markers
|
|
for (var te = 0; te <= (carrier.total_te || 12); te++) {
|
|
var teX = x + te * self.TE_WIDTH;
|
|
html += '<line x1="' + teX + '" y1="' + (y - 8) + '" x2="' + teX + '" y2="' + (y + 3) + '" stroke="#444" stroke-width="0.5"/>';
|
|
}
|
|
});
|
|
});
|
|
} else {
|
|
// Fallback: Carriers ohne Panels
|
|
this.carriers.forEach(function(carrier, idx) {
|
|
var y = self.TOP_MARGIN + idx * self.RAIL_SPACING;
|
|
var width = (carrier.total_te || 12) * self.TE_WIDTH;
|
|
var x = self.PANEL_PADDING + 50;
|
|
|
|
carrier._y = y;
|
|
carrier._x = x;
|
|
|
|
html += '<rect class="schematic-rail-bg" x="' + x + '" y="' + (y - 5) + '" width="' + width + '" height="' + self.RAIL_HEIGHT + '" ';
|
|
html += 'fill="' + self.COLORS.railBg + '" rx="2"/>';
|
|
|
|
html += '<rect class="schematic-rail" data-carrier-id="' + carrier.id + '" x="' + x + '" y="' + (y - 3) + '" width="' + width + '" height="' + (self.RAIL_HEIGHT - 4) + '" ';
|
|
html += 'fill="' + self.COLORS.rail + '" rx="1"/>';
|
|
|
|
html += '<text x="' + (x - 10) + '" y="' + y + '" text-anchor="end" fill="#888" font-size="11">';
|
|
html += self.escapeHtml(carrier.label || 'Hutschiene ' + (idx + 1));
|
|
html += '</text>';
|
|
|
|
for (var te = 0; te <= (carrier.total_te || 12); te++) {
|
|
var teX = x + te * self.TE_WIDTH;
|
|
html += '<line x1="' + teX + '" y1="' + (y - 10) + '" x2="' + teX + '" y2="' + (y + 5) + '" stroke="#555" stroke-width="0.5"/>';
|
|
}
|
|
});
|
|
}
|
|
|
|
$layer.html(html);
|
|
},
|
|
|
|
renderBlocks: function() {
|
|
var self = this;
|
|
var $layer = $(this.svgElement).find('.schematic-blocks-layer');
|
|
var $terminalLayer = $(this.svgElement).find('.schematic-terminals-layer');
|
|
$layer.empty();
|
|
$terminalLayer.empty();
|
|
|
|
var blockHtml = '';
|
|
var terminalHtml = '';
|
|
|
|
this.equipment.forEach(function(eq) {
|
|
var carrier = self.carriers.find(function(c) { return c.id == eq.carrier_id; });
|
|
if (!carrier) return;
|
|
|
|
var blockWidth = (eq.width_te || 1) * self.TE_WIDTH - 4;
|
|
var blockHeight = self.BLOCK_HEIGHT;
|
|
var x = carrier._x + (eq.position_te - 1) * self.TE_WIDTH + 2;
|
|
var y = carrier._y - blockHeight / 2 - 20;
|
|
|
|
// Store position
|
|
eq._x = x;
|
|
eq._y = y;
|
|
eq._width = blockWidth;
|
|
eq._height = blockHeight;
|
|
|
|
var color = eq.block_color || eq.type_color || self.COLORS.block;
|
|
|
|
// Block group
|
|
blockHtml += '<g class="schematic-block" data-equipment-id="' + eq.id + '" data-carrier-id="' + carrier.id + '" transform="translate(' + x + ',' + y + ')">';
|
|
|
|
// Block background with gradient
|
|
blockHtml += '<rect class="schematic-block-bg" width="' + blockWidth + '" height="' + blockHeight + '" ';
|
|
blockHtml += 'fill="' + color + '" stroke="#222" stroke-width="1" rx="3"/>';
|
|
|
|
// Label
|
|
var labelY = blockHeight / 2;
|
|
blockHtml += '<text x="' + (blockWidth / 2) + '" y="' + (labelY - 5) + '" text-anchor="middle" fill="#fff" font-size="10" font-weight="bold">';
|
|
blockHtml += self.escapeHtml(eq.type_label_short || eq.label || '');
|
|
blockHtml += '</text>';
|
|
|
|
// Additional info line
|
|
if (eq.label && eq.type_label_short) {
|
|
blockHtml += '<text x="' + (blockWidth / 2) + '" y="' + (labelY + 8) + '" text-anchor="middle" fill="rgba(255,255,255,0.7)" font-size="8">';
|
|
blockHtml += self.escapeHtml(eq.label);
|
|
blockHtml += '</text>';
|
|
}
|
|
|
|
blockHtml += '</g>';
|
|
|
|
// Terminals (bidirectional)
|
|
var terminals = self.getTerminals(eq);
|
|
var topTerminals = terminals.filter(function(t) { return t.pos === 'top'; });
|
|
var bottomTerminals = terminals.filter(function(t) { return t.pos === 'bottom'; });
|
|
|
|
var topSpacing = blockWidth / (topTerminals.length + 1);
|
|
var bottomSpacing = blockWidth / (bottomTerminals.length + 1);
|
|
|
|
// Top terminals
|
|
topTerminals.forEach(function(term, idx) {
|
|
var tx = x + topSpacing * (idx + 1);
|
|
var ty = y - 5;
|
|
var termColor = self.PHASE_COLORS[term.label] || '#3498db';
|
|
|
|
terminalHtml += '<g class="schematic-terminal" ';
|
|
terminalHtml += 'data-equipment-id="' + eq.id + '" data-terminal-id="' + term.id + '" ';
|
|
terminalHtml += 'transform="translate(' + tx + ',' + ty + ')">';
|
|
|
|
terminalHtml += '<circle r="' + self.TERMINAL_RADIUS + '" fill="' + termColor + '" stroke="#fff" stroke-width="1.5" class="schematic-terminal-circle"/>';
|
|
terminalHtml += '<text y="-10" text-anchor="middle" fill="#aaa" font-size="8">' + self.escapeHtml(term.label) + '</text>';
|
|
terminalHtml += '</g>';
|
|
});
|
|
|
|
// Bottom terminals
|
|
bottomTerminals.forEach(function(term, idx) {
|
|
var tx = x + bottomSpacing * (idx + 1);
|
|
var ty = y + blockHeight + 5;
|
|
var termColor = self.PHASE_COLORS[term.label] || '#27ae60';
|
|
|
|
terminalHtml += '<g class="schematic-terminal" ';
|
|
terminalHtml += 'data-equipment-id="' + eq.id + '" data-terminal-id="' + term.id + '" ';
|
|
terminalHtml += 'transform="translate(' + tx + ',' + ty + ')">';
|
|
|
|
terminalHtml += '<circle r="' + self.TERMINAL_RADIUS + '" fill="' + termColor + '" stroke="#fff" stroke-width="1.5" class="schematic-terminal-circle"/>';
|
|
terminalHtml += '<text y="15" text-anchor="middle" fill="#aaa" font-size="8">' + self.escapeHtml(term.label) + '</text>';
|
|
terminalHtml += '</g>';
|
|
});
|
|
});
|
|
|
|
$layer.html(blockHtml);
|
|
$terminalLayer.html(terminalHtml);
|
|
},
|
|
|
|
renderConnections: function() {
|
|
var self = this;
|
|
var $layer = $(this.svgElement).find('.schematic-connections-layer');
|
|
$layer.empty();
|
|
|
|
var html = '';
|
|
|
|
this.connections.forEach(function(conn, connIndex) {
|
|
if (conn.is_rail) return; // Skip busbars for now
|
|
|
|
var sourceEq = self.equipment.find(function(e) { return e.id == conn.fk_source; });
|
|
var targetEq = self.equipment.find(function(e) { return e.id == conn.fk_target; });
|
|
|
|
if (!sourceEq || !targetEq) return;
|
|
|
|
var sourceTerminals = self.getTerminals(sourceEq);
|
|
var targetTerminals = self.getTerminals(targetEq);
|
|
|
|
// Find terminal positions - use first matching or default
|
|
var sourceTermId = conn.source_terminal_id || 't2'; // default bottom
|
|
var targetTermId = conn.target_terminal_id || 't1'; // default top
|
|
|
|
var sourcePos = self.getTerminalPosition(sourceEq, sourceTermId, sourceTerminals);
|
|
var targetPos = self.getTerminalPosition(targetEq, targetTermId, targetTerminals);
|
|
|
|
if (!sourcePos || !targetPos) return;
|
|
|
|
// Add offset for multiple connections to avoid overlap
|
|
var routeOffset = connIndex * 8;
|
|
|
|
// Create orthogonal path
|
|
var path = self.createOrthogonalPath(sourcePos, targetPos, routeOffset);
|
|
var color = conn.color || self.PHASE_COLORS[conn.connection_type] || self.COLORS.connection;
|
|
|
|
// Connection shadow
|
|
html += '<path class="schematic-connection-shadow" d="' + path + '" fill="none" stroke="rgba(0,0,0,0.4)" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>';
|
|
|
|
// Connection line - clickable for editing
|
|
html += '<g class="schematic-connection-group" data-connection-id="' + conn.id + '">';
|
|
html += '<path class="schematic-connection" data-connection-id="' + conn.id + '" d="' + path + '" ';
|
|
html += 'fill="none" stroke="' + color + '" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>';
|
|
|
|
// Label on the connection
|
|
if (conn.output_label) {
|
|
// Calculate label position along the path
|
|
var labelX = (sourcePos.x + targetPos.x) / 2;
|
|
var labelY = sourcePos.isTop ? sourcePos.y - 50 - routeOffset : sourcePos.y + 50 + routeOffset;
|
|
|
|
var labelWidth = Math.min(conn.output_label.length * 6 + 10, 80);
|
|
html += '<rect x="' + (labelX - labelWidth/2) + '" y="' + (labelY - 8) + '" width="' + labelWidth + '" height="14" rx="2" fill="#1e1e1e" opacity="0.95"/>';
|
|
html += '<text x="' + labelX + '" y="' + (labelY + 3) + '" text-anchor="middle" fill="' + color + '" font-size="9" font-weight="bold">';
|
|
html += self.escapeHtml(conn.output_label);
|
|
html += '</text>';
|
|
}
|
|
|
|
// Connection type indicator
|
|
if (conn.connection_type && !conn.output_label) {
|
|
var typeX = (sourcePos.x + targetPos.x) / 2;
|
|
var typeY = (sourcePos.y + targetPos.y) / 2;
|
|
html += '<text x="' + typeX + '" y="' + typeY + '" text-anchor="middle" fill="' + color + '" font-size="8" font-weight="bold">';
|
|
html += conn.connection_type;
|
|
html += '</text>';
|
|
}
|
|
|
|
html += '</g>';
|
|
});
|
|
|
|
$layer.html(html);
|
|
},
|
|
|
|
getTerminals: function(eq) {
|
|
// Try to parse terminals_config from equipment type
|
|
if (eq.terminals_config) {
|
|
try {
|
|
var config = JSON.parse(eq.terminals_config);
|
|
// Handle both old format (inputs/outputs) and new format (terminals)
|
|
if (config.terminals) {
|
|
return config.terminals;
|
|
} else if (config.inputs || config.outputs) {
|
|
// Convert old format to new
|
|
var terminals = [];
|
|
if (config.inputs) {
|
|
config.inputs.forEach(function(t) {
|
|
terminals.push({ id: t.id, label: t.label, pos: 'top' });
|
|
});
|
|
}
|
|
if (config.outputs) {
|
|
config.outputs.forEach(function(t) {
|
|
terminals.push({ id: t.id, label: t.label, pos: 'bottom' });
|
|
});
|
|
}
|
|
return terminals;
|
|
}
|
|
} catch (e) {}
|
|
}
|
|
|
|
// Look up by type ref
|
|
var typeRef = (eq.type_ref || '').toUpperCase();
|
|
if (this.DEFAULT_TERMINALS[typeRef] && this.DEFAULT_TERMINALS[typeRef].terminals) {
|
|
return this.DEFAULT_TERMINALS[typeRef].terminals;
|
|
}
|
|
|
|
// Check for FI pattern
|
|
if (typeRef.indexOf('FI') !== -1 || typeRef.indexOf('RCD') !== -1) {
|
|
if (typeRef.indexOf('4P') !== -1) {
|
|
return this.DEFAULT_TERMINALS['FI4P'].terminals;
|
|
}
|
|
return this.DEFAULT_TERMINALS['FI'].terminals;
|
|
}
|
|
|
|
// Default: 1 top, 1 bottom
|
|
return this.DEFAULT_TERMINALS['LS'].terminals;
|
|
},
|
|
|
|
getTerminalPosition: function(eq, terminalId, terminals) {
|
|
// Find the terminal
|
|
var terminal = null;
|
|
var termIndex = 0;
|
|
var samePosList = [];
|
|
|
|
for (var i = 0; i < terminals.length; i++) {
|
|
if (terminals[i].id === terminalId) {
|
|
terminal = terminals[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!terminal) {
|
|
terminal = terminals[0] || { pos: 'top' };
|
|
}
|
|
|
|
// Get all terminals with same position
|
|
for (var j = 0; j < terminals.length; j++) {
|
|
if (terminals[j].pos === terminal.pos) {
|
|
if (terminals[j].id === terminalId) termIndex = samePosList.length;
|
|
samePosList.push(terminals[j]);
|
|
}
|
|
}
|
|
|
|
var isTop = terminal.pos === 'top';
|
|
var spacing = eq._width / (samePosList.length + 1);
|
|
var x = eq._x + spacing * (termIndex + 1);
|
|
var y = isTop ? (eq._y - 5) : (eq._y + eq._height + 5);
|
|
|
|
return { x: x, y: y, isTop: isTop };
|
|
},
|
|
|
|
createOrthogonalPath: function(source, target, routeOffset) {
|
|
var x1 = source.x, y1 = source.y;
|
|
var x2 = target.x, y2 = target.y;
|
|
routeOffset = routeOffset || 0;
|
|
|
|
// Constants for routing around blocks
|
|
var MARGIN = 25 + routeOffset; // Distance to keep from blocks
|
|
var VERTICAL_OFFSET = 40 + routeOffset; // How far to route around
|
|
|
|
// Both on top - route above
|
|
if (source.isTop && target.isTop) {
|
|
var routeY = Math.min(y1, y2) - VERTICAL_OFFSET;
|
|
return 'M ' + x1 + ' ' + y1 +
|
|
' L ' + x1 + ' ' + routeY +
|
|
' L ' + x2 + ' ' + routeY +
|
|
' L ' + x2 + ' ' + y2;
|
|
}
|
|
|
|
// Both on bottom - route below
|
|
if (!source.isTop && !target.isTop) {
|
|
var routeYBottom = Math.max(y1, y2) + VERTICAL_OFFSET;
|
|
return 'M ' + x1 + ' ' + y1 +
|
|
' L ' + x1 + ' ' + routeYBottom +
|
|
' L ' + x2 + ' ' + routeYBottom +
|
|
' L ' + x2 + ' ' + y2;
|
|
}
|
|
|
|
// One top, one bottom - route around the side
|
|
var dx = x2 - x1;
|
|
var dy = y2 - y1;
|
|
|
|
// If vertically aligned, go straight or with small offset
|
|
if (Math.abs(dx) < 20) {
|
|
return 'M ' + x1 + ' ' + y1 + ' L ' + x2 + ' ' + y2;
|
|
}
|
|
|
|
// Route to the side, then up/down, then to target
|
|
// Choose side based on relative position
|
|
var sideX = dx > 0 ? x1 + MARGIN : x1 - MARGIN;
|
|
|
|
// Top to bottom
|
|
if (source.isTop && !target.isTop) {
|
|
// Go up from source, across, down to target
|
|
var upY = y1 - VERTICAL_OFFSET;
|
|
var downY = y2 + VERTICAL_OFFSET;
|
|
|
|
if (Math.abs(y1 - y2) > 100) {
|
|
// Far apart - route around the shorter side
|
|
return 'M ' + x1 + ' ' + y1 +
|
|
' L ' + x1 + ' ' + upY +
|
|
' L ' + x2 + ' ' + upY +
|
|
' L ' + x2 + ' ' + y2;
|
|
} else {
|
|
// Close - route around
|
|
var midX = (x1 + x2) / 2 + (dx > 0 ? -MARGIN : MARGIN);
|
|
return 'M ' + x1 + ' ' + y1 +
|
|
' L ' + x1 + ' ' + upY +
|
|
' L ' + midX + ' ' + upY +
|
|
' L ' + midX + ' ' + downY +
|
|
' L ' + x2 + ' ' + downY +
|
|
' L ' + x2 + ' ' + y2;
|
|
}
|
|
}
|
|
|
|
// Bottom to top
|
|
if (!source.isTop && target.isTop) {
|
|
var sourceDownY = y1 + VERTICAL_OFFSET;
|
|
var targetUpY = y2 - VERTICAL_OFFSET;
|
|
|
|
if (Math.abs(y1 - y2) > 100) {
|
|
return 'M ' + x1 + ' ' + y1 +
|
|
' L ' + x1 + ' ' + sourceDownY +
|
|
' L ' + x2 + ' ' + sourceDownY +
|
|
' L ' + x2 + ' ' + y2;
|
|
} else {
|
|
var midX2 = (x1 + x2) / 2 + (dx > 0 ? -MARGIN : MARGIN);
|
|
return 'M ' + x1 + ' ' + y1 +
|
|
' L ' + x1 + ' ' + sourceDownY +
|
|
' L ' + midX2 + ' ' + sourceDownY +
|
|
' L ' + midX2 + ' ' + targetUpY +
|
|
' L ' + x2 + ' ' + targetUpY +
|
|
' L ' + x2 + ' ' + y2;
|
|
}
|
|
}
|
|
|
|
// Fallback - simple orthogonal
|
|
var midY = (y1 + y2) / 2;
|
|
return 'M ' + x1 + ' ' + y1 +
|
|
' L ' + x1 + ' ' + midY +
|
|
' L ' + x2 + ' ' + midY +
|
|
' L ' + x2 + ' ' + y2;
|
|
},
|
|
|
|
handleTerminalClick: function($terminal) {
|
|
var eqId = $terminal.data('equipment-id');
|
|
var termId = $terminal.data('terminal-id');
|
|
|
|
if (!this.selectedTerminal) {
|
|
// First click - select any terminal
|
|
this.selectedTerminal = {
|
|
element: $terminal,
|
|
equipmentId: eqId,
|
|
terminalId: termId
|
|
};
|
|
|
|
$terminal.find('.schematic-terminal-circle').attr('stroke', this.COLORS.selected).attr('stroke-width', '3');
|
|
$('.schematic-connection-preview').show();
|
|
|
|
this.showMessage('Jetzt Ziel-Terminal auswählen...', 'info');
|
|
} else {
|
|
// Second click - check if different equipment
|
|
if (eqId === this.selectedTerminal.equipmentId) {
|
|
this.showMessage('Bitte ein anderes Gerät auswählen', 'warning');
|
|
return;
|
|
}
|
|
|
|
// Show label dialog before creating connection
|
|
this.showConnectionLabelDialog(
|
|
this.selectedTerminal.equipmentId,
|
|
this.selectedTerminal.terminalId,
|
|
eqId,
|
|
termId
|
|
);
|
|
}
|
|
},
|
|
|
|
showConnectionLabelDialog: function(sourceEqId, sourceTermId, targetEqId, targetTermId) {
|
|
var self = this;
|
|
|
|
// Find equipment labels for context
|
|
var sourceEq = this.equipment.find(function(e) { return e.id == sourceEqId; });
|
|
var targetEq = this.equipment.find(function(e) { return e.id == targetEqId; });
|
|
|
|
var html = '<div id="schematic-conn-dialog" class="kundenkarte-modal visible">';
|
|
html += '<div class="kundenkarte-modal-content" style="max-width:450px;">';
|
|
html += '<div class="kundenkarte-modal-header"><h3>Verbindung erstellen</h3>';
|
|
html += '<span class="kundenkarte-modal-close">×</span></div>';
|
|
html += '<div class="kundenkarte-modal-body">';
|
|
|
|
html += '<div style="margin-bottom:15px;padding:10px;background:#2a2a2a;border-radius:4px;">';
|
|
html += '<strong style="color:#3498db;">' + this.escapeHtml((sourceEq ? sourceEq.label || sourceEq.type_label_short : 'Gerät')) + '</strong>';
|
|
html += ' <i class="fa fa-arrow-right" style="color:#888;margin:0 10px;"></i> ';
|
|
html += '<strong style="color:#27ae60;">' + this.escapeHtml((targetEq ? targetEq.label || targetEq.type_label_short : 'Gerät')) + '</strong>';
|
|
html += '</div>';
|
|
|
|
html += '<div class="form-group" style="margin-bottom:12px;">';
|
|
html += '<label style="display:block;margin-bottom:5px;color:#aaa;">Bezeichnung / Stromkreis</label>';
|
|
html += '<input type="text" id="conn-label" class="flat" style="width:100%;padding:8px;" placeholder="z.B. Küche Steckdosen, Bad Licht, L1">';
|
|
html += '</div>';
|
|
|
|
html += '<div class="form-group" style="margin-bottom:12px;">';
|
|
html += '<label style="display:block;margin-bottom:5px;color:#aaa;">Typ (optional)</label>';
|
|
html += '<div style="display:flex;gap:8px;flex-wrap:wrap;">';
|
|
|
|
var typePresets = ['L1', 'L2', 'L3', 'N', 'PE', 'L1N', '3P'];
|
|
typePresets.forEach(function(t) {
|
|
var color = self.PHASE_COLORS[t] || self.COLORS.connection;
|
|
html += '<button type="button" class="conn-type-btn" data-type="' + t + '" data-color="' + color + '" ';
|
|
html += 'style="padding:5px 12px;background:' + color + ';color:#fff;border:none;border-radius:3px;cursor:pointer;font-size:12px;">';
|
|
html += t + '</button>';
|
|
});
|
|
|
|
html += '</div>';
|
|
html += '<input type="hidden" id="conn-type" value="L1N">';
|
|
html += '<input type="hidden" id="conn-color" value="' + this.COLORS.connection + '">';
|
|
html += '</div>';
|
|
|
|
html += '<div class="form-group">';
|
|
html += '<label style="display:block;margin-bottom:5px;color:#aaa;">Kabel (optional)</label>';
|
|
html += '<div style="display:flex;gap:8px;">';
|
|
html += '<input type="text" id="conn-medium" class="flat" style="flex:1;padding:8px;" placeholder="z.B. NYM-J 3x1.5">';
|
|
html += '<input type="text" id="conn-length" class="flat" style="width:80px;padding:8px;" placeholder="Länge">';
|
|
html += '</div>';
|
|
html += '</div>';
|
|
|
|
html += '</div>';
|
|
html += '<div class="kundenkarte-modal-footer">';
|
|
html += '<button type="button" class="button" id="conn-save"><i class="fa fa-check"></i> Erstellen</button> ';
|
|
html += '<button type="button" class="button" id="conn-cancel">Abbrechen</button>';
|
|
html += '</div></div></div>';
|
|
|
|
$('body').append(html);
|
|
|
|
// Type preset buttons
|
|
$('.conn-type-btn').on('click', function() {
|
|
$('.conn-type-btn').css('outline', 'none');
|
|
$(this).css('outline', '2px solid #fff');
|
|
$('#conn-type').val($(this).data('type'));
|
|
$('#conn-color').val($(this).data('color'));
|
|
});
|
|
|
|
// Save
|
|
$('#conn-save').on('click', function() {
|
|
var label = $('#conn-label').val();
|
|
var connType = $('#conn-type').val();
|
|
var color = $('#conn-color').val();
|
|
var medium = $('#conn-medium').val();
|
|
var length = $('#conn-length').val();
|
|
|
|
self.createConnection(sourceEqId, sourceTermId, targetEqId, targetTermId, {
|
|
label: label,
|
|
type: connType,
|
|
color: color,
|
|
medium: medium,
|
|
length: length
|
|
});
|
|
|
|
$('#schematic-conn-dialog').remove();
|
|
self.cancelSelection();
|
|
});
|
|
|
|
// Cancel
|
|
$('#conn-cancel, .kundenkarte-modal-close').on('click', function() {
|
|
$('#schematic-conn-dialog').remove();
|
|
self.cancelSelection();
|
|
});
|
|
|
|
// Focus label input
|
|
setTimeout(function() { $('#conn-label').focus(); }, 100);
|
|
},
|
|
|
|
updateConnectionPreview: function(e) {
|
|
if (!this.selectedTerminal) return;
|
|
|
|
var $svg = $(this.svgElement);
|
|
var offset = $svg.offset();
|
|
var x = e.pageX - offset.left;
|
|
var y = e.pageY - offset.top;
|
|
|
|
var $terminal = this.selectedTerminal.element;
|
|
var transform = $terminal.attr('transform');
|
|
var match = transform.match(/translate\(([^,]+),([^)]+)\)/);
|
|
if (match) {
|
|
var startX = parseFloat(match[1]);
|
|
var startY = parseFloat(match[2]);
|
|
|
|
var $preview = $svg.find('.schematic-connection-preview');
|
|
$preview.attr({
|
|
x1: startX,
|
|
y1: startY,
|
|
x2: x,
|
|
y2: y
|
|
});
|
|
}
|
|
},
|
|
|
|
cancelSelection: function() {
|
|
if (this.selectedTerminal) {
|
|
this.selectedTerminal.element.find('.schematic-terminal-circle')
|
|
.attr('stroke', '#fff')
|
|
.attr('stroke-width', '1.5');
|
|
}
|
|
this.selectedTerminal = null;
|
|
$('.schematic-connection-preview').hide();
|
|
this.hideMessage();
|
|
},
|
|
|
|
createConnection: function(sourceEqId, sourceTermId, targetEqId, targetTermId, options) {
|
|
var self = this;
|
|
options = options || {};
|
|
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment_connection.php',
|
|
method: 'POST',
|
|
data: {
|
|
action: 'create',
|
|
fk_source: sourceEqId,
|
|
source_terminal_id: sourceTermId,
|
|
fk_target: targetEqId,
|
|
target_terminal_id: targetTermId,
|
|
connection_type: options.type || 'L1N',
|
|
color: options.color || self.COLORS.connection,
|
|
output_label: options.label || '',
|
|
medium_type: options.medium || '',
|
|
medium_length: options.length || '',
|
|
token: $('input[name="token"]').val()
|
|
},
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
self.showMessage('Verbindung erstellt!', 'success');
|
|
self.loadConnections();
|
|
} else {
|
|
self.showMessage('Fehler: ' + response.error, 'error');
|
|
}
|
|
},
|
|
error: function() {
|
|
self.showMessage('Netzwerkfehler', 'error');
|
|
}
|
|
});
|
|
},
|
|
|
|
deleteConnection: function(connId) {
|
|
var self = this;
|
|
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment_connection.php',
|
|
method: 'POST',
|
|
data: {
|
|
action: 'delete',
|
|
connection_id: connId,
|
|
token: $('input[name="token"]').val()
|
|
},
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
self.showMessage('Verbindung gelöscht', 'success');
|
|
self.loadConnections();
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
clearAllConnections: function() {
|
|
var self = this;
|
|
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment_connection.php',
|
|
method: 'POST',
|
|
data: {
|
|
action: 'clear_all',
|
|
anlage_id: this.anlageId,
|
|
token: $('input[name="token"]').val()
|
|
},
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
self.connections = [];
|
|
self.renderConnections();
|
|
self.showMessage('Alle Verbindungen gelöscht', 'success');
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
// Block dragging
|
|
startDragBlock: function($block, e) {
|
|
var eqId = $block.data('equipment-id');
|
|
var carrierId = $block.data('carrier-id');
|
|
var eq = this.equipment.find(function(e) { return e.id == eqId; });
|
|
var carrier = this.carriers.find(function(c) { return c.id == carrierId; });
|
|
|
|
if (!eq || !carrier) return;
|
|
|
|
var $svg = $(this.svgElement);
|
|
var offset = $svg.offset();
|
|
|
|
this.dragState = {
|
|
type: 'block',
|
|
equipment: eq,
|
|
carrier: carrier,
|
|
element: $block,
|
|
startX: e.pageX,
|
|
startY: e.pageY,
|
|
originalTE: eq.position_te
|
|
};
|
|
|
|
$block.addClass('dragging');
|
|
},
|
|
|
|
updateDragBlock: function(e) {
|
|
if (!this.dragState || this.dragState.type !== 'block') return;
|
|
|
|
var dx = e.pageX - this.dragState.startX;
|
|
var newTE = this.dragState.originalTE + Math.round(dx / this.TE_WIDTH);
|
|
|
|
// Clamp to valid range
|
|
var maxTE = (this.dragState.carrier.total_te || 12) - (this.dragState.equipment.width_te || 1) + 1;
|
|
newTE = Math.max(1, Math.min(newTE, maxTE));
|
|
|
|
// Preview position
|
|
var newX = this.dragState.carrier._x + (newTE - 1) * this.TE_WIDTH + 2;
|
|
var currentTransform = this.dragState.element.attr('transform');
|
|
var currentY = this.dragState.equipment._y;
|
|
|
|
this.dragState.element.attr('transform', 'translate(' + newX + ',' + currentY + ')');
|
|
this.dragState.newTE = newTE;
|
|
},
|
|
|
|
endDragBlock: function(e) {
|
|
if (!this.dragState || this.dragState.type !== 'block') return;
|
|
|
|
var newTE = this.dragState.newTE || this.dragState.originalTE;
|
|
|
|
if (newTE !== this.dragState.originalTE) {
|
|
this.updateEquipmentPosition(this.dragState.equipment.id, newTE);
|
|
}
|
|
|
|
this.dragState.element.removeClass('dragging');
|
|
this.dragState = null;
|
|
},
|
|
|
|
updateEquipmentPosition: function(eqId, newTE) {
|
|
var self = this;
|
|
|
|
$.ajax({
|
|
url: baseUrl + '/custom/kundenkarte/ajax/equipment.php',
|
|
method: 'POST',
|
|
data: {
|
|
action: 'update_position',
|
|
equipment_id: eqId,
|
|
position_te: newTE,
|
|
token: $('input[name="token"]').val()
|
|
},
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
// Update local data
|
|
var eq = self.equipment.find(function(e) { return e.id == eqId; });
|
|
if (eq) eq.position_te = newTE;
|
|
self.render();
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
showMessage: function(text, type) {
|
|
var $msg = $('.schematic-message');
|
|
if (!$msg.length) {
|
|
$msg = $('<div class="schematic-message"></div>');
|
|
$('.schematic-editor-canvas').prepend($msg);
|
|
}
|
|
|
|
$msg.removeClass('success error warning info').addClass(type).text(text).fadeIn();
|
|
|
|
if (type === 'success' || type === 'error') {
|
|
setTimeout(function() { $msg.fadeOut(); }, 2000);
|
|
}
|
|
},
|
|
|
|
hideMessage: function() {
|
|
$('.schematic-message').fadeOut();
|
|
},
|
|
|
|
escapeHtml: function(text) {
|
|
if (!text) return '';
|
|
var div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
};
|
|
|
|
// Initialize on DOM ready
|
|
$(document).ready(function() {
|
|
KundenKarte.Tree.init();
|
|
KundenKarte.Favorites.init();
|
|
KundenKarte.SystemTabs.init();
|
|
KundenKarte.IconPicker.init();
|
|
KundenKarte.DynamicFields.init();
|
|
|
|
// Initialize Equipment if container exists
|
|
var $equipmentContainer = $('.kundenkarte-equipment-container');
|
|
if ($equipmentContainer.length) {
|
|
var anlageId = $equipmentContainer.data('anlage-id');
|
|
var systemId = $equipmentContainer.data('system-id');
|
|
KundenKarte.Equipment.init(anlageId, systemId);
|
|
|
|
// Initialize Schematic Editor
|
|
KundenKarte.SchematicEditor.init(anlageId);
|
|
}
|
|
});
|
|
|
|
})();
|