Neuer Editor
This commit is contained in:
parent
3de514308b
commit
6be50395e5
14 changed files with 4467 additions and 800 deletions
|
|
@ -374,7 +374,7 @@ if (in_array($action, array('create', 'edit'))) {
|
|||
}
|
||||
print '</select></td></tr>';
|
||||
|
||||
// Icon
|
||||
// FontAwesome Icon (fallback)
|
||||
print '<tr><td>'.$langs->trans('SystemPicto').'</td>';
|
||||
print '<td><div class="kundenkarte-icon-picker-wrapper">';
|
||||
print '<span class="kundenkarte-icon-preview">';
|
||||
|
|
@ -384,7 +384,39 @@ if (in_array($action, array('create', 'edit'))) {
|
|||
print '</span>';
|
||||
print '<input type="text" name="picto" class="flat minwidth200" value="'.dol_escape_htmltag($equipmentType->picto).'" placeholder="fa-bolt">';
|
||||
print '<button type="button" class="kundenkarte-icon-picker-btn" data-input="picto"><i class="fa fa-th"></i> '.$langs->trans('SelectIcon').'</button>';
|
||||
print '</div></td></tr>';
|
||||
print '</div>';
|
||||
print '<span class="opacitymedium small">Fallback-Icon wenn kein Schaltzeichen hochgeladen</span>';
|
||||
print '</td></tr>';
|
||||
|
||||
// Schaltzeichen Upload (SVG/PNG)
|
||||
print '<tr><td>'.$langs->trans('SchematicSymbol').'</td>';
|
||||
print '<td>';
|
||||
print '<div id="icon-upload-area" style="display:flex;align-items:center;gap:15px;">';
|
||||
|
||||
// Preview area
|
||||
print '<div id="icon-preview" style="width:80px;height:80px;border:2px dashed #ccc;border-radius:8px;display:flex;align-items:center;justify-content:center;background:#f8f8f8;">';
|
||||
if ($action == 'edit' && $equipmentType->icon_file) {
|
||||
$iconUrl = DOL_URL_ROOT.'/document.php?modulepart=kundenkarte&file=equipment_icons/'.urlencode($equipmentType->icon_file);
|
||||
print '<img src="'.$iconUrl.'" style="max-width:70px;max-height:70px;" alt="Icon">';
|
||||
} else {
|
||||
print '<span style="color:#999;font-size:11px;text-align:center;">Kein<br>Symbol</span>';
|
||||
}
|
||||
print '</div>';
|
||||
|
||||
// Upload controls
|
||||
print '<div>';
|
||||
print '<input type="file" id="icon-file-input" accept=".svg,.png" style="display:none;">';
|
||||
print '<button type="button" id="icon-upload-btn" class="button" onclick="document.getElementById(\'icon-file-input\').click();">';
|
||||
print '<i class="fa fa-upload"></i> SVG/PNG hochladen</button>';
|
||||
if ($action == 'edit' && $equipmentType->icon_file) {
|
||||
print ' <button type="button" id="icon-delete-btn" class="button" style="background:#e74c3c;border-color:#c0392b;color:#fff;">';
|
||||
print '<i class="fa fa-trash"></i> Löschen</button>';
|
||||
}
|
||||
print '<div class="opacitymedium small" style="margin-top:5px;">Normgerechte Symbole nach IEC 60617 / DIN EN 60617</div>';
|
||||
print '</div>';
|
||||
|
||||
print '</div>';
|
||||
print '</td></tr>';
|
||||
|
||||
// Position
|
||||
print '<tr><td>'.$langs->trans('Position').'</td>';
|
||||
|
|
@ -410,7 +442,8 @@ if (in_array($action, array('create', 'edit'))) {
|
|||
|
||||
print '</table>';
|
||||
|
||||
// JavaScript for terminal presets
|
||||
// JavaScript for terminal presets and icon upload
|
||||
$typeIdJs = $action == 'edit' ? $typeId : 0;
|
||||
print '<script>
|
||||
function setTerminals(type) {
|
||||
var configs = {
|
||||
|
|
@ -423,6 +456,80 @@ if (in_array($action, array('create', 'edit'))) {
|
|||
document.getElementById("terminals_config").value = JSON.stringify(configs[type], null, 2);
|
||||
}
|
||||
}
|
||||
|
||||
// Icon upload handling
|
||||
var typeId = '.$typeIdJs.';
|
||||
|
||||
document.getElementById("icon-file-input").addEventListener("change", function(e) {
|
||||
if (!typeId || typeId == 0) {
|
||||
alert("Bitte speichern Sie zuerst den Equipment-Typ bevor Sie ein Symbol hochladen.");
|
||||
return;
|
||||
}
|
||||
|
||||
var file = e.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
var formData = new FormData();
|
||||
formData.append("action", "upload");
|
||||
formData.append("type_id", typeId);
|
||||
formData.append("icon_file", file);
|
||||
formData.append("token", "'.newToken().'");
|
||||
|
||||
fetch("'.DOL_URL_ROOT.'/custom/kundenkarte/ajax/equipment_type_icon.php", {
|
||||
method: "POST",
|
||||
body: formData
|
||||
})
|
||||
.then(function(response) { return response.json(); })
|
||||
.then(function(data) {
|
||||
if (data.success) {
|
||||
var preview = document.getElementById("icon-preview");
|
||||
preview.innerHTML = \'<img src="\' + data.icon_url + \'?t=\' + Date.now() + \'" style="max-width:70px;max-height:70px;" alt="Icon">\';
|
||||
|
||||
// Add delete button if not present
|
||||
if (!document.getElementById("icon-delete-btn")) {
|
||||
var btn = document.createElement("button");
|
||||
btn.type = "button";
|
||||
btn.id = "icon-delete-btn";
|
||||
btn.className = "button";
|
||||
btn.style.cssText = "background:#e74c3c;border-color:#c0392b;color:#fff;margin-left:5px;";
|
||||
btn.innerHTML = \'<i class="fa fa-trash"></i> Löschen\';
|
||||
btn.onclick = deleteIcon;
|
||||
document.getElementById("icon-upload-btn").after(btn);
|
||||
}
|
||||
} else {
|
||||
alert("Fehler: " + data.error);
|
||||
}
|
||||
})
|
||||
.catch(function(err) {
|
||||
alert("Upload fehlgeschlagen: " + err);
|
||||
});
|
||||
|
||||
e.target.value = "";
|
||||
});
|
||||
|
||||
function deleteIcon() {
|
||||
if (!confirm("Symbol wirklich löschen?")) return;
|
||||
|
||||
fetch("'.DOL_URL_ROOT.'/custom/kundenkarte/ajax/equipment_type_icon.php", {
|
||||
method: "POST",
|
||||
headers: {"Content-Type": "application/x-www-form-urlencoded"},
|
||||
body: "action=delete&type_id=" + typeId + "&token='.newToken().'"
|
||||
})
|
||||
.then(function(response) { return response.json(); })
|
||||
.then(function(data) {
|
||||
if (data.success) {
|
||||
var preview = document.getElementById("icon-preview");
|
||||
preview.innerHTML = \'<span style="color:#999;font-size:11px;text-align:center;">Kein<br>Symbol</span>\';
|
||||
var delBtn = document.getElementById("icon-delete-btn");
|
||||
if (delBtn) delBtn.remove();
|
||||
} else {
|
||||
alert("Fehler: " + data.error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var delBtn = document.getElementById("icon-delete-btn");
|
||||
if (delBtn) delBtn.onclick = deleteIcon;
|
||||
</script>';
|
||||
|
||||
print '<div class="center" style="margin-top:20px;">';
|
||||
|
|
|
|||
|
|
@ -66,6 +66,9 @@ switch ($action) {
|
|||
'fk_carrier' => $equipment->fk_carrier,
|
||||
'type_id' => $equipment->fk_equipment_type,
|
||||
'type_label' => $equipment->type_label,
|
||||
'type_label_short' => $equipment->type_label_short,
|
||||
'type_color' => $equipment->type_color,
|
||||
'type_icon_file' => $equipment->type_icon_file,
|
||||
'label' => $equipment->label,
|
||||
'position_te' => $equipment->position_te,
|
||||
'width_te' => $equipment->width_te,
|
||||
|
|
@ -107,6 +110,10 @@ switch ($action) {
|
|||
$items = $equipment->fetchByCarrier($carrierId);
|
||||
$result = array();
|
||||
foreach ($items as $eq) {
|
||||
$iconUrl = '';
|
||||
if (!empty($eq->type_icon_file)) {
|
||||
$iconUrl = DOL_URL_ROOT.'/document.php?modulepart=kundenkarte&file=equipment_icons/'.urlencode($eq->type_icon_file);
|
||||
}
|
||||
$result[] = array(
|
||||
'id' => $eq->id,
|
||||
'type_id' => $eq->fk_equipment_type,
|
||||
|
|
@ -114,6 +121,8 @@ switch ($action) {
|
|||
'type_label_short' => $eq->type_label_short,
|
||||
'type_ref' => $eq->type_ref,
|
||||
'type_color' => $eq->type_color,
|
||||
'type_icon_file' => $eq->type_icon_file,
|
||||
'type_icon_url' => $iconUrl,
|
||||
'terminals_config' => $eq->terminals_config,
|
||||
'label' => $eq->label,
|
||||
'position_te' => $eq->position_te,
|
||||
|
|
@ -301,14 +310,21 @@ switch ($action) {
|
|||
if ($newEquipment->fetch($newId) > 0) {
|
||||
$response['equipment'] = array(
|
||||
'id' => $newEquipment->id,
|
||||
'fk_carrier' => $newEquipment->fk_carrier,
|
||||
'type_id' => $newEquipment->fk_equipment_type,
|
||||
'type_label' => $newEquipment->type_label,
|
||||
'type_label_short' => $newEquipment->type_label_short,
|
||||
'type_color' => $newEquipment->type_color,
|
||||
'type_ref' => $newEquipment->type_ref,
|
||||
'type_icon_file' => $newEquipment->type_icon_file,
|
||||
'terminals_config' => $newEquipment->terminals_config,
|
||||
'label' => $newEquipment->label,
|
||||
'position_te' => $newEquipment->position_te,
|
||||
'width_te' => $newEquipment->width_te,
|
||||
'block_label' => $newEquipment->getBlockLabel(),
|
||||
'block_color' => $newEquipment->getBlockColor()
|
||||
'block_color' => $newEquipment->getBlockColor(),
|
||||
'field_values' => $newEquipment->getFieldValues(),
|
||||
'fk_product' => $newEquipment->fk_product
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -153,6 +153,7 @@ switch ($action) {
|
|||
$connection->rail_end_te = GETPOSTINT('rail_end_te');
|
||||
$connection->fk_carrier = $carrierId;
|
||||
$connection->position_y = GETPOSTINT('position_y');
|
||||
$connection->path_data = GETPOST('path_data', 'nohtml');
|
||||
|
||||
$result = $connection->create($user);
|
||||
if ($result > 0) {
|
||||
|
|
@ -169,22 +170,24 @@ switch ($action) {
|
|||
break;
|
||||
}
|
||||
if ($connection->fetch($connectionId) > 0) {
|
||||
$connection->fk_source = GETPOSTINT('fk_source');
|
||||
$connection->source_terminal = GETPOST('source_terminal', 'alphanohtml') ?: 'output';
|
||||
$connection->fk_target = GETPOSTINT('fk_target');
|
||||
$connection->target_terminal = GETPOST('target_terminal', 'alphanohtml') ?: 'input';
|
||||
$connection->connection_type = GETPOST('connection_type', 'alphanohtml');
|
||||
$connection->color = GETPOST('color', 'alphanohtml');
|
||||
$connection->output_label = GETPOST('output_label', 'alphanohtml');
|
||||
$connection->medium_type = GETPOST('medium_type', 'alphanohtml');
|
||||
$connection->medium_spec = GETPOST('medium_spec', 'alphanohtml');
|
||||
$connection->medium_length = GETPOST('medium_length', 'alphanohtml');
|
||||
$connection->is_rail = GETPOSTINT('is_rail');
|
||||
$connection->rail_start_te = GETPOSTINT('rail_start_te');
|
||||
$connection->rail_end_te = GETPOSTINT('rail_end_te');
|
||||
$connection->rail_phases = GETPOST('rail_phases', 'alphanohtml');
|
||||
$connection->excluded_te = GETPOST('excluded_te', 'alphanohtml');
|
||||
$connection->position_y = GETPOSTINT('position_y');
|
||||
// Only update fields that are actually sent (preserve existing values)
|
||||
if (GETPOSTISSET('fk_source')) $connection->fk_source = GETPOSTINT('fk_source');
|
||||
if (GETPOSTISSET('source_terminal')) $connection->source_terminal = GETPOST('source_terminal', 'alphanohtml') ?: $connection->source_terminal;
|
||||
if (GETPOSTISSET('fk_target')) $connection->fk_target = GETPOSTINT('fk_target');
|
||||
if (GETPOSTISSET('target_terminal')) $connection->target_terminal = GETPOST('target_terminal', 'alphanohtml') ?: $connection->target_terminal;
|
||||
if (GETPOSTISSET('connection_type')) $connection->connection_type = GETPOST('connection_type', 'alphanohtml');
|
||||
if (GETPOSTISSET('color')) $connection->color = GETPOST('color', 'alphanohtml');
|
||||
if (GETPOSTISSET('output_label')) $connection->output_label = GETPOST('output_label', 'alphanohtml');
|
||||
if (GETPOSTISSET('medium_type')) $connection->medium_type = GETPOST('medium_type', 'alphanohtml');
|
||||
if (GETPOSTISSET('medium_spec')) $connection->medium_spec = GETPOST('medium_spec', 'alphanohtml');
|
||||
if (GETPOSTISSET('medium_length')) $connection->medium_length = GETPOST('medium_length', 'alphanohtml');
|
||||
if (GETPOSTISSET('is_rail')) $connection->is_rail = GETPOSTINT('is_rail');
|
||||
if (GETPOSTISSET('rail_start_te')) $connection->rail_start_te = GETPOSTINT('rail_start_te');
|
||||
if (GETPOSTISSET('rail_end_te')) $connection->rail_end_te = GETPOSTINT('rail_end_te');
|
||||
if (GETPOSTISSET('rail_phases')) $connection->rail_phases = GETPOST('rail_phases', 'alphanohtml');
|
||||
if (GETPOSTISSET('excluded_te')) $connection->excluded_te = GETPOST('excluded_te', 'alphanohtml');
|
||||
if (GETPOSTISSET('position_y')) $connection->position_y = GETPOSTINT('position_y');
|
||||
if (GETPOSTISSET('path_data')) $connection->path_data = GETPOST('path_data', 'nohtml');
|
||||
|
||||
$result = $connection->update($user);
|
||||
if ($result > 0) {
|
||||
|
|
@ -276,30 +279,59 @@ switch ($action) {
|
|||
require_once DOL_DOCUMENT_ROOT.'/custom/kundenkarte/class/equipmentcarrier.class.php';
|
||||
$carrierObj = new EquipmentCarrier($db);
|
||||
$carriers = $carrierObj->fetchByAnlage($anlageId);
|
||||
$carrierIds = array();
|
||||
foreach ($carriers as $carrier) {
|
||||
$carrierIds[] = (int)$carrier->id;
|
||||
}
|
||||
|
||||
$allConnections = array();
|
||||
foreach ($carriers as $carrier) {
|
||||
$connections = $connection->fetchByCarrier($carrier->id);
|
||||
foreach ($connections as $c) {
|
||||
$allConnections[] = array(
|
||||
'id' => $c->id,
|
||||
'fk_source' => $c->fk_source,
|
||||
'source_terminal' => $c->source_terminal,
|
||||
'source_terminal_id' => $c->source_terminal_id,
|
||||
'source_label' => $c->source_label,
|
||||
'fk_target' => $c->fk_target,
|
||||
'target_terminal' => $c->target_terminal,
|
||||
'target_terminal_id' => $c->target_terminal_id,
|
||||
'target_label' => $c->target_label,
|
||||
'connection_type' => $c->connection_type,
|
||||
'color' => $c->getColor(),
|
||||
'output_label' => $c->output_label,
|
||||
'medium_type' => $c->medium_type,
|
||||
'medium_spec' => $c->medium_spec,
|
||||
'medium_length' => $c->medium_length,
|
||||
'is_rail' => $c->is_rail,
|
||||
'fk_carrier' => $c->fk_carrier
|
||||
);
|
||||
|
||||
if (!empty($carrierIds)) {
|
||||
// Find all connections where source OR target equipment belongs to this anlage's carriers
|
||||
// This includes connections with fk_carrier=NULL
|
||||
$sql = "SELECT DISTINCT c.*,
|
||||
se.label as source_label, se.position_te as source_pos, se.width_te as source_width,
|
||||
te.label as target_label, te.position_te as target_pos
|
||||
FROM ".MAIN_DB_PREFIX."kundenkarte_equipment_connection c
|
||||
LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_equipment se ON c.fk_source = se.rowid
|
||||
LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_equipment te ON c.fk_target = te.rowid
|
||||
WHERE (c.fk_carrier IN (".implode(',', $carrierIds).")
|
||||
OR se.fk_carrier IN (".implode(',', $carrierIds).")
|
||||
OR te.fk_carrier IN (".implode(',', $carrierIds)."))
|
||||
AND c.status = 1";
|
||||
|
||||
$resql = $db->query($sql);
|
||||
if ($resql) {
|
||||
while ($obj = $db->fetch_object($resql)) {
|
||||
$allConnections[] = array(
|
||||
'id' => $obj->rowid,
|
||||
'fk_source' => $obj->fk_source,
|
||||
'source_terminal' => $obj->source_terminal,
|
||||
'source_terminal_id' => $obj->source_terminal_id,
|
||||
'source_label' => $obj->source_label,
|
||||
'source_pos' => $obj->source_pos,
|
||||
'source_width' => $obj->source_width,
|
||||
'fk_target' => $obj->fk_target,
|
||||
'target_terminal' => $obj->target_terminal,
|
||||
'target_terminal_id' => $obj->target_terminal_id,
|
||||
'target_label' => $obj->target_label,
|
||||
'target_pos' => $obj->target_pos,
|
||||
'connection_type' => $obj->connection_type,
|
||||
'color' => $obj->color ?: '#3498db',
|
||||
'output_label' => $obj->output_label,
|
||||
'medium_type' => $obj->medium_type,
|
||||
'medium_spec' => $obj->medium_spec,
|
||||
'medium_length' => $obj->medium_length,
|
||||
'is_rail' => $obj->is_rail,
|
||||
'rail_start_te' => $obj->rail_start_te,
|
||||
'rail_end_te' => $obj->rail_end_te,
|
||||
'rail_phases' => $obj->rail_phases,
|
||||
'position_y' => $obj->position_y,
|
||||
'fk_carrier' => $obj->fk_carrier,
|
||||
'path_data' => isset($obj->path_data) ? $obj->path_data : null
|
||||
);
|
||||
}
|
||||
$db->free($resql);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
148
ajax/equipment_type_icon.php
Normal file
148
ajax/equipment_type_icon.php
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 Alles Watt lauft
|
||||
*
|
||||
* AJAX handler for equipment type icon upload
|
||||
* Accepts SVG and PNG files for schematic symbols
|
||||
*/
|
||||
|
||||
$res = 0;
|
||||
if (!$res && file_exists("../../main.inc.php")) $res = @include "../../main.inc.php";
|
||||
if (!$res && file_exists("../../../main.inc.php")) $res = @include "../../../main.inc.php";
|
||||
if (!$res && file_exists("../../../../main.inc.php")) $res = @include "../../../../main.inc.php";
|
||||
if (!$res) die("Include of main fails");
|
||||
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
|
||||
dol_include_once('/kundenkarte/class/equipmenttype.class.php');
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// Security check
|
||||
if (!$user->admin && !$user->hasRight('kundenkarte', 'admin')) {
|
||||
echo json_encode(array('success' => false, 'error' => 'Access denied'));
|
||||
exit;
|
||||
}
|
||||
|
||||
$action = GETPOST('action', 'aZ09');
|
||||
$typeId = GETPOSTINT('type_id');
|
||||
|
||||
$response = array('success' => false);
|
||||
|
||||
// Directory for equipment type icons
|
||||
$uploadDir = DOL_DATA_ROOT.'/kundenkarte/equipment_icons/';
|
||||
|
||||
// Create directory if not exists
|
||||
if (!is_dir($uploadDir)) {
|
||||
dol_mkdir($uploadDir);
|
||||
}
|
||||
|
||||
switch ($action) {
|
||||
case 'upload':
|
||||
if (empty($_FILES['icon_file']) || $_FILES['icon_file']['error'] !== UPLOAD_ERR_OK) {
|
||||
$response['error'] = 'No file uploaded or upload error';
|
||||
break;
|
||||
}
|
||||
|
||||
$file = $_FILES['icon_file'];
|
||||
$fileName = dol_sanitizeFileName($file['name']);
|
||||
$fileExt = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
|
||||
|
||||
// Validate file type
|
||||
$allowedExtensions = array('svg', 'png');
|
||||
if (!in_array($fileExt, $allowedExtensions)) {
|
||||
$response['error'] = 'Invalid file type. Only SVG and PNG are allowed.';
|
||||
break;
|
||||
}
|
||||
|
||||
// Validate MIME type
|
||||
$mimeType = mime_content_type($file['tmp_name']);
|
||||
$allowedMimes = array('image/svg+xml', 'image/png', 'text/plain', 'text/xml', 'application/xml');
|
||||
if (!in_array($mimeType, $allowedMimes)) {
|
||||
$response['error'] = 'Invalid MIME type: '.$mimeType;
|
||||
break;
|
||||
}
|
||||
|
||||
// For SVG files, do basic security check
|
||||
if ($fileExt === 'svg') {
|
||||
$content = file_get_contents($file['tmp_name']);
|
||||
// Check for potentially dangerous content
|
||||
$dangerous = array('<script', 'javascript:', 'onload=', 'onerror=', 'onclick=', 'onmouseover=');
|
||||
foreach ($dangerous as $pattern) {
|
||||
if (stripos($content, $pattern) !== false) {
|
||||
$response['error'] = 'SVG contains potentially dangerous content';
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate unique filename
|
||||
$newFileName = 'icon_'.$typeId.'_'.dol_now().'.'.$fileExt;
|
||||
$destPath = $uploadDir.$newFileName;
|
||||
|
||||
// Move uploaded file
|
||||
if (move_uploaded_file($file['tmp_name'], $destPath)) {
|
||||
// Update database
|
||||
$equipmentType = new EquipmentType($db);
|
||||
if ($equipmentType->fetch($typeId) > 0) {
|
||||
// Delete old icon file if exists
|
||||
if ($equipmentType->icon_file && file_exists($uploadDir.$equipmentType->icon_file)) {
|
||||
unlink($uploadDir.$equipmentType->icon_file);
|
||||
}
|
||||
|
||||
$equipmentType->icon_file = $newFileName;
|
||||
$result = $equipmentType->update($user);
|
||||
|
||||
if ($result > 0) {
|
||||
$response['success'] = true;
|
||||
$response['icon_file'] = $newFileName;
|
||||
$response['icon_url'] = DOL_URL_ROOT.'/document.php?modulepart=kundenkarte&file=equipment_icons/'.$newFileName;
|
||||
} else {
|
||||
$response['error'] = 'Database update failed';
|
||||
// Remove uploaded file on DB error
|
||||
unlink($destPath);
|
||||
}
|
||||
} else {
|
||||
$response['error'] = 'Equipment type not found';
|
||||
unlink($destPath);
|
||||
}
|
||||
} else {
|
||||
$response['error'] = 'Failed to move uploaded file';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'delete':
|
||||
$equipmentType = new EquipmentType($db);
|
||||
if ($equipmentType->fetch($typeId) > 0) {
|
||||
if ($equipmentType->icon_file && file_exists($uploadDir.$equipmentType->icon_file)) {
|
||||
unlink($uploadDir.$equipmentType->icon_file);
|
||||
}
|
||||
$equipmentType->icon_file = '';
|
||||
$result = $equipmentType->update($user);
|
||||
if ($result > 0) {
|
||||
$response['success'] = true;
|
||||
} else {
|
||||
$response['error'] = 'Database update failed';
|
||||
}
|
||||
} else {
|
||||
$response['error'] = 'Equipment type not found';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'get':
|
||||
$equipmentType = new EquipmentType($db);
|
||||
if ($equipmentType->fetch($typeId) > 0) {
|
||||
$response['success'] = true;
|
||||
$response['icon_file'] = $equipmentType->icon_file;
|
||||
if ($equipmentType->icon_file) {
|
||||
$response['icon_url'] = DOL_URL_ROOT.'/document.php?modulepart=kundenkarte&file=equipment_icons/'.$equipmentType->icon_file;
|
||||
}
|
||||
} else {
|
||||
$response['error'] = 'Equipment type not found';
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$response['error'] = 'Unknown action';
|
||||
}
|
||||
|
||||
echo json_encode($response);
|
||||
$db->close();
|
||||
717
ajax/export_schematic_pdf.php
Normal file
717
ajax/export_schematic_pdf.php
Normal file
|
|
@ -0,0 +1,717 @@
|
|||
<?php
|
||||
/* Copyright (C) 2026 Alles Watt lauft
|
||||
*
|
||||
* PDF Export for Schematic Editor (Leitungslaufplan)
|
||||
* Following DIN EN 61082 (Document structure) and DIN EN 81346 (Reference designation)
|
||||
*/
|
||||
|
||||
$res = 0;
|
||||
if (!$res && file_exists("../../main.inc.php")) $res = @include "../../main.inc.php";
|
||||
if (!$res && file_exists("../../../main.inc.php")) $res = @include "../../../main.inc.php";
|
||||
if (!$res && file_exists("../../../../main.inc.php")) $res = @include "../../../../main.inc.php";
|
||||
if (!$res) die("Include of main fails");
|
||||
|
||||
require_once DOL_DOCUMENT_ROOT.'/core/lib/pdf.lib.php';
|
||||
require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
|
||||
dol_include_once('/kundenkarte/class/anlage.class.php');
|
||||
dol_include_once('/kundenkarte/class/equipment.class.php');
|
||||
dol_include_once('/kundenkarte/class/equipmentcarrier.class.php');
|
||||
dol_include_once('/kundenkarte/class/equipmentconnection.class.php');
|
||||
|
||||
$langs->loadLangs(array('companies', 'kundenkarte@kundenkarte'));
|
||||
|
||||
// Get parameters
|
||||
$anlageId = GETPOSTINT('anlage_id');
|
||||
$svgContent = GETPOST('svg_content', 'restricthtml');
|
||||
$format = GETPOST('format', 'alpha') ?: 'A4';
|
||||
$orientation = GETPOST('orientation', 'alpha') ?: 'L'; // L=Landscape, P=Portrait
|
||||
|
||||
// Security check
|
||||
if (!$user->hasRight('kundenkarte', 'read')) {
|
||||
accessforbidden();
|
||||
}
|
||||
|
||||
// Load Anlage data
|
||||
$anlage = new Anlage($db);
|
||||
if ($anlage->fetch($anlageId) <= 0) {
|
||||
die('Anlage not found');
|
||||
}
|
||||
|
||||
// Load company
|
||||
$societe = new Societe($db);
|
||||
$societe->fetch($anlage->fk_soc);
|
||||
|
||||
// Load carriers for this anlage
|
||||
$carrier = new EquipmentCarrier($db);
|
||||
$carriers = $carrier->fetchByAnlage($anlageId);
|
||||
|
||||
// Load equipment
|
||||
$equipment = new Equipment($db);
|
||||
$equipmentList = array();
|
||||
foreach ($carriers as $c) {
|
||||
$eqList = $equipment->fetchByCarrier($c->id);
|
||||
$equipmentList = array_merge($equipmentList, $eqList);
|
||||
}
|
||||
|
||||
// Load connections
|
||||
$connection = new EquipmentConnection($db);
|
||||
$connections = array();
|
||||
foreach ($carriers as $c) {
|
||||
$connList = $connection->fetchByCarrier($c->id);
|
||||
$connections = array_merge($connections, $connList);
|
||||
}
|
||||
|
||||
// Create PDF - Landscape A3 or A4 for schematic
|
||||
$pdf = pdf_getInstance();
|
||||
$pdf->SetCreator('Dolibarr - Kundenkarte Schaltplan');
|
||||
$pdf->SetAuthor($user->getFullName($langs));
|
||||
$pdf->SetTitle('Leitungslaufplan - '.$anlage->label);
|
||||
|
||||
// Page format
|
||||
if ($format == 'A3') {
|
||||
$pageWidth = 420;
|
||||
$pageHeight = 297;
|
||||
} else {
|
||||
$pageWidth = 297;
|
||||
$pageHeight = 210;
|
||||
}
|
||||
|
||||
if ($orientation == 'P') {
|
||||
$tmp = $pageWidth;
|
||||
$pageWidth = $pageHeight;
|
||||
$pageHeight = $tmp;
|
||||
}
|
||||
|
||||
$pdf->SetMargins(10, 10, 10);
|
||||
$pdf->SetAutoPageBreak(false);
|
||||
$pdf->AddPage($orientation, array($pageWidth, $pageHeight));
|
||||
|
||||
// ============================================
|
||||
// DIN EN 61082 / ISO 7200 Title Block (Schriftfeld)
|
||||
// Position: Bottom right corner
|
||||
// ============================================
|
||||
|
||||
$titleBlockWidth = 180;
|
||||
$titleBlockHeight = 56;
|
||||
$titleBlockX = $pageWidth - $titleBlockWidth - 10;
|
||||
$titleBlockY = $pageHeight - $titleBlockHeight - 10;
|
||||
|
||||
// Draw title block frame
|
||||
$pdf->SetDrawColor(0, 0, 0);
|
||||
$pdf->SetLineWidth(0.5);
|
||||
$pdf->Rect($titleBlockX, $titleBlockY, $titleBlockWidth, $titleBlockHeight);
|
||||
|
||||
// Title block grid - following DIN structure
|
||||
// Row heights from bottom: 8, 8, 8, 8, 8, 8, 8 = 56mm total
|
||||
$rowHeight = 8;
|
||||
$rows = 7;
|
||||
|
||||
// Column widths: 30 | 50 | 50 | 50 = 180mm
|
||||
$col1 = 30; // Labels
|
||||
$col2 = 50; // Company info
|
||||
$col3 = 50; // Document info
|
||||
$col4 = 50; // Revision info
|
||||
|
||||
// Draw horizontal lines
|
||||
for ($i = 1; $i < $rows; $i++) {
|
||||
$y = $titleBlockY + ($i * $rowHeight);
|
||||
$pdf->Line($titleBlockX, $y, $titleBlockX + $titleBlockWidth, $y);
|
||||
}
|
||||
|
||||
// Draw vertical lines
|
||||
$pdf->Line($titleBlockX + $col1, $titleBlockY, $titleBlockX + $col1, $titleBlockY + $titleBlockHeight);
|
||||
$pdf->Line($titleBlockX + $col1 + $col2, $titleBlockY, $titleBlockX + $col1 + $col2, $titleBlockY + $titleBlockHeight);
|
||||
$pdf->Line($titleBlockX + $col1 + $col2 + $col3, $titleBlockY, $titleBlockX + $col1 + $col2 + $col3, $titleBlockY + $titleBlockHeight);
|
||||
|
||||
// Fill in title block content
|
||||
$pdf->SetFont('dejavusans', '', 6);
|
||||
$pdf->SetTextColor(0, 0, 0);
|
||||
|
||||
// Row 1 (from top): Document title spanning full width
|
||||
$pdf->SetFont('dejavusans', 'B', 12);
|
||||
$pdf->SetXY($titleBlockX + 2, $titleBlockY + 1);
|
||||
$pdf->Cell($titleBlockWidth - 4, $rowHeight - 2, 'LEITUNGSLAUFPLAN', 0, 0, 'C');
|
||||
|
||||
// Row 2: Installation name
|
||||
$pdf->SetFont('dejavusans', 'B', 10);
|
||||
$pdf->SetXY($titleBlockX + 2, $titleBlockY + $rowHeight + 1);
|
||||
$pdf->Cell($titleBlockWidth - 4, $rowHeight - 2, $anlage->label, 0, 0, 'C');
|
||||
|
||||
// Row 3: Labels
|
||||
$pdf->SetFont('dejavusans', '', 6);
|
||||
$y = $titleBlockY + (2 * $rowHeight);
|
||||
$pdf->SetXY($titleBlockX + 1, $y + 1);
|
||||
$pdf->Cell($col1 - 2, 3, 'Erstellt', 0, 0, 'L');
|
||||
$pdf->SetXY($titleBlockX + $col1 + 1, $y + 1);
|
||||
$pdf->Cell($col2 - 2, 3, 'Kunde', 0, 0, 'L');
|
||||
$pdf->SetXY($titleBlockX + $col1 + $col2 + 1, $y + 1);
|
||||
$pdf->Cell($col3 - 2, 3, 'Projekt-Nr.', 0, 0, 'L');
|
||||
$pdf->SetXY($titleBlockX + $col1 + $col2 + $col3 + 1, $y + 1);
|
||||
$pdf->Cell($col4 - 2, 3, 'Blatt', 0, 0, 'L');
|
||||
|
||||
// Row 3: Values
|
||||
$pdf->SetFont('dejavusans', '', 8);
|
||||
$pdf->SetXY($titleBlockX + 1, $y + 4);
|
||||
$pdf->Cell($col1 - 2, 4, dol_print_date(dol_now(), 'day'), 0, 0, 'L');
|
||||
$pdf->SetXY($titleBlockX + $col1 + 1, $y + 4);
|
||||
$pdf->Cell($col2 - 2, 4, dol_trunc($societe->name, 25), 0, 0, 'L');
|
||||
$pdf->SetXY($titleBlockX + $col1 + $col2 + 1, $y + 4);
|
||||
$pdf->Cell($col3 - 2, 4, $anlage->ref ?: '-', 0, 0, 'L');
|
||||
$pdf->SetXY($titleBlockX + $col1 + $col2 + $col3 + 1, $y + 4);
|
||||
$pdf->Cell($col4 - 2, 4, '1 / 1', 0, 0, 'L');
|
||||
|
||||
// Row 4: More labels
|
||||
$y = $titleBlockY + (3 * $rowHeight);
|
||||
$pdf->SetFont('dejavusans', '', 6);
|
||||
$pdf->SetXY($titleBlockX + 1, $y + 1);
|
||||
$pdf->Cell($col1 - 2, 3, 'Bearbeiter', 0, 0, 'L');
|
||||
$pdf->SetXY($titleBlockX + $col1 + 1, $y + 1);
|
||||
$pdf->Cell($col2 - 2, 3, 'Adresse', 0, 0, 'L');
|
||||
$pdf->SetXY($titleBlockX + $col1 + $col2 + 1, $y + 1);
|
||||
$pdf->Cell($col3 - 2, 3, 'Anlage', 0, 0, 'L');
|
||||
$pdf->SetXY($titleBlockX + $col1 + $col2 + $col3 + 1, $y + 1);
|
||||
$pdf->Cell($col4 - 2, 3, 'Revision', 0, 0, 'L');
|
||||
|
||||
// Row 4: Values
|
||||
$pdf->SetFont('dejavusans', '', 8);
|
||||
$pdf->SetXY($titleBlockX + 1, $y + 4);
|
||||
$pdf->Cell($col1 - 2, 4, dol_trunc($user->getFullName($langs), 15), 0, 0, 'L');
|
||||
$pdf->SetXY($titleBlockX + $col1 + 1, $y + 4);
|
||||
$address = trim($societe->address.' '.$societe->zip.' '.$societe->town);
|
||||
$pdf->Cell($col2 - 2, 4, dol_trunc($address, 25), 0, 0, 'L');
|
||||
$pdf->SetXY($titleBlockX + $col1 + $col2 + 1, $y + 4);
|
||||
$pdf->Cell($col3 - 2, 4, $anlage->type_label ?: '-', 0, 0, 'L');
|
||||
$pdf->SetXY($titleBlockX + $col1 + $col2 + $col3 + 1, $y + 4);
|
||||
$pdf->Cell($col4 - 2, 4, 'A', 0, 0, 'L');
|
||||
|
||||
// Row 5: Equipment count
|
||||
$y = $titleBlockY + (4 * $rowHeight);
|
||||
$pdf->SetFont('dejavusans', '', 6);
|
||||
$pdf->SetXY($titleBlockX + 1, $y + 1);
|
||||
$pdf->Cell($col1 - 2, 3, 'Komponenten', 0, 0, 'L');
|
||||
$pdf->SetXY($titleBlockX + $col1 + 1, $y + 1);
|
||||
$pdf->Cell($col2 - 2, 3, 'Verbindungen', 0, 0, 'L');
|
||||
$pdf->SetXY($titleBlockX + $col1 + $col2 + 1, $y + 1);
|
||||
$pdf->Cell($col3 - 2, 3, 'Hutschienen', 0, 0, 'L');
|
||||
$pdf->SetXY($titleBlockX + $col1 + $col2 + $col3 + 1, $y + 1);
|
||||
$pdf->Cell($col4 - 2, 3, 'Format', 0, 0, 'L');
|
||||
|
||||
$pdf->SetFont('dejavusans', '', 8);
|
||||
$pdf->SetXY($titleBlockX + 1, $y + 4);
|
||||
$pdf->Cell($col1 - 2, 4, count($equipmentList), 0, 0, 'L');
|
||||
$pdf->SetXY($titleBlockX + $col1 + 1, $y + 4);
|
||||
$pdf->Cell($col2 - 2, 4, count($connections), 0, 0, 'L');
|
||||
$pdf->SetXY($titleBlockX + $col1 + $col2 + 1, $y + 4);
|
||||
$pdf->Cell($col3 - 2, 4, count($carriers), 0, 0, 'L');
|
||||
$pdf->SetXY($titleBlockX + $col1 + $col2 + $col3 + 1, $y + 4);
|
||||
$pdf->Cell($col4 - 2, 4, $format.' '.$orientation, 0, 0, 'L');
|
||||
|
||||
// Row 6: Norm reference
|
||||
$y = $titleBlockY + (5 * $rowHeight);
|
||||
$pdf->SetFont('dejavusans', '', 6);
|
||||
$pdf->SetXY($titleBlockX + 1, $y + 2);
|
||||
$pdf->Cell($titleBlockWidth - 2, 4, 'Erstellt nach DIN EN 61082 / DIN EN 81346', 0, 0, 'C');
|
||||
|
||||
// Row 7: Company info
|
||||
$y = $titleBlockY + (6 * $rowHeight);
|
||||
$pdf->SetFont('dejavusans', 'B', 7);
|
||||
$pdf->SetXY($titleBlockX + 1, $y + 2);
|
||||
$pdf->Cell($titleBlockWidth - 2, 4, $mysoc->name, 0, 0, 'C');
|
||||
|
||||
// ============================================
|
||||
// Draw the Schematic Content Area
|
||||
// ============================================
|
||||
|
||||
$schematicX = 10;
|
||||
$schematicY = 10;
|
||||
$schematicWidth = $pageWidth - 20;
|
||||
$schematicHeight = $titleBlockY - 15;
|
||||
|
||||
// Draw frame around schematic area
|
||||
$pdf->SetDrawColor(0, 0, 0);
|
||||
$pdf->SetLineWidth(0.3);
|
||||
$pdf->Rect($schematicX, $schematicY, $schematicWidth, $schematicHeight);
|
||||
|
||||
// If SVG content provided, embed it
|
||||
if (!empty($svgContent)) {
|
||||
// Clean SVG for TCPDF
|
||||
$svgContent = preg_replace('/<\?xml[^>]*\?>/', '', $svgContent);
|
||||
$svgContent = preg_replace('/<!DOCTYPE[^>]*>/', '', $svgContent);
|
||||
|
||||
// Try to embed SVG
|
||||
try {
|
||||
// Scale SVG to fit in schematic area
|
||||
$pdf->ImageSVG('@'.$svgContent, $schematicX + 2, $schematicY + 2, $schematicWidth - 4, $schematicHeight - 4, '', '', '', 0, false);
|
||||
} catch (Exception $e) {
|
||||
// SVG embedding failed - draw placeholder
|
||||
$pdf->SetFont('dejavusans', 'I', 10);
|
||||
$pdf->SetXY($schematicX + 10, $schematicY + 10);
|
||||
$pdf->Cell(0, 10, 'SVG konnte nicht eingebettet werden: '.$e->getMessage(), 0, 1);
|
||||
}
|
||||
} else {
|
||||
// Draw schematic manually if no SVG provided
|
||||
drawSchematicContent($pdf, $carriers, $equipmentList, $connections, $schematicX, $schematicY, $schematicWidth, $schematicHeight);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Add Wiring List on second page (Verdrahtungsliste)
|
||||
// ============================================
|
||||
|
||||
if (count($connections) > 0) {
|
||||
$pdf->AddPage($orientation, array($pageWidth, $pageHeight));
|
||||
|
||||
// Title
|
||||
$pdf->SetFont('dejavusans', 'B', 14);
|
||||
$pdf->SetXY(10, 10);
|
||||
$pdf->Cell(0, 8, 'VERDRAHTUNGSLISTE / KLEMMENPLAN', 0, 1, 'L');
|
||||
|
||||
$pdf->SetFont('dejavusans', '', 9);
|
||||
$pdf->SetXY(10, 18);
|
||||
$pdf->Cell(0, 5, $anlage->label.' - '.$societe->name, 0, 1, 'L');
|
||||
|
||||
// Table header
|
||||
$pdf->SetY(28);
|
||||
$pdf->SetFont('dejavusans', 'B', 8);
|
||||
$pdf->SetFillColor(220, 220, 220);
|
||||
|
||||
$colWidths = array(15, 35, 25, 35, 25, 25, 30, 30);
|
||||
$headers = array('Nr.', 'Von (Quelle)', 'Klemme', 'Nach (Ziel)', 'Klemme', 'Typ', 'Leitung', 'Bemerkung');
|
||||
|
||||
$x = 10;
|
||||
for ($i = 0; $i < count($headers); $i++) {
|
||||
$pdf->SetXY($x, 28);
|
||||
$pdf->Cell($colWidths[$i], 6, $headers[$i], 1, 0, 'C', true);
|
||||
$x += $colWidths[$i];
|
||||
}
|
||||
|
||||
// Table content
|
||||
$pdf->SetFont('dejavusans', '', 7);
|
||||
$y = 34;
|
||||
$lineNum = 1;
|
||||
|
||||
// Build equipment lookup
|
||||
$eqLookup = array();
|
||||
foreach ($equipmentList as $eq) {
|
||||
$eqLookup[$eq->id] = $eq;
|
||||
}
|
||||
|
||||
foreach ($connections as $conn) {
|
||||
// Skip rails/busbars in wiring list (they're separate)
|
||||
if ($conn->is_rail) continue;
|
||||
|
||||
$sourceName = '-';
|
||||
$sourceTerminal = $conn->source_terminal ?: '-';
|
||||
$targetName = '-';
|
||||
$targetTerminal = $conn->target_terminal ?: '-';
|
||||
|
||||
if ($conn->fk_source && isset($eqLookup[$conn->fk_source])) {
|
||||
$sourceName = $eqLookup[$conn->fk_source]->label ?: $eqLookup[$conn->fk_source]->type_label_short;
|
||||
}
|
||||
if ($conn->fk_target && isset($eqLookup[$conn->fk_target])) {
|
||||
$targetName = $eqLookup[$conn->fk_target]->label ?: $eqLookup[$conn->fk_target]->type_label_short;
|
||||
}
|
||||
|
||||
// Connection type / medium
|
||||
$connType = $conn->connection_type ?: '-';
|
||||
$medium = trim($conn->medium_type.' '.$conn->medium_spec);
|
||||
if (empty($medium)) $medium = '-';
|
||||
|
||||
$remark = $conn->output_label ?: '';
|
||||
|
||||
if ($y > $pageHeight - 25) {
|
||||
// New page
|
||||
$pdf->AddPage($orientation, array($pageWidth, $pageHeight));
|
||||
$y = 10;
|
||||
|
||||
// Repeat header
|
||||
$pdf->SetFont('dejavusans', 'B', 8);
|
||||
$x = 10;
|
||||
for ($i = 0; $i < count($headers); $i++) {
|
||||
$pdf->SetXY($x, $y);
|
||||
$pdf->Cell($colWidths[$i], 6, $headers[$i], 1, 0, 'C', true);
|
||||
$x += $colWidths[$i];
|
||||
}
|
||||
$y += 6;
|
||||
$pdf->SetFont('dejavusans', '', 7);
|
||||
}
|
||||
|
||||
$x = 10;
|
||||
$pdf->SetXY($x, $y);
|
||||
$pdf->Cell($colWidths[0], 5, $lineNum, 1, 0, 'C');
|
||||
$x += $colWidths[0];
|
||||
|
||||
$pdf->SetXY($x, $y);
|
||||
$pdf->Cell($colWidths[1], 5, dol_trunc($sourceName, 18), 1, 0, 'L');
|
||||
$x += $colWidths[1];
|
||||
|
||||
$pdf->SetXY($x, $y);
|
||||
$pdf->Cell($colWidths[2], 5, $sourceTerminal, 1, 0, 'C');
|
||||
$x += $colWidths[2];
|
||||
|
||||
$pdf->SetXY($x, $y);
|
||||
$pdf->Cell($colWidths[3], 5, dol_trunc($targetName, 18), 1, 0, 'L');
|
||||
$x += $colWidths[3];
|
||||
|
||||
$pdf->SetXY($x, $y);
|
||||
$pdf->Cell($colWidths[4], 5, $targetTerminal, 1, 0, 'C');
|
||||
$x += $colWidths[4];
|
||||
|
||||
$pdf->SetXY($x, $y);
|
||||
$pdf->Cell($colWidths[5], 5, $connType, 1, 0, 'C');
|
||||
$x += $colWidths[5];
|
||||
|
||||
$pdf->SetXY($x, $y);
|
||||
$pdf->Cell($colWidths[6], 5, dol_trunc($medium, 15), 1, 0, 'L');
|
||||
$x += $colWidths[6];
|
||||
|
||||
$pdf->SetXY($x, $y);
|
||||
$pdf->Cell($colWidths[7], 5, dol_trunc($remark, 15), 1, 0, 'L');
|
||||
|
||||
$y += 5;
|
||||
$lineNum++;
|
||||
}
|
||||
|
||||
// Add busbars section if any
|
||||
$busbars = array_filter($connections, function($c) { return $c->is_rail; });
|
||||
if (count($busbars) > 0) {
|
||||
$y += 10;
|
||||
if ($y > $pageHeight - 40) {
|
||||
$pdf->AddPage($orientation, array($pageWidth, $pageHeight));
|
||||
$y = 10;
|
||||
}
|
||||
|
||||
$pdf->SetFont('dejavusans', 'B', 10);
|
||||
$pdf->SetXY(10, $y);
|
||||
$pdf->Cell(0, 6, 'SAMMELSCHIENEN / PHASENSCHIENEN', 0, 1, 'L');
|
||||
$y += 8;
|
||||
|
||||
$pdf->SetFont('dejavusans', 'B', 8);
|
||||
$bbHeaders = array('Nr.', 'Bezeichnung', 'Typ', 'Von TE', 'Bis TE', 'Phasen', 'Ausnahmen');
|
||||
$bbWidths = array(15, 50, 30, 20, 20, 30, 50);
|
||||
|
||||
$x = 10;
|
||||
for ($i = 0; $i < count($bbHeaders); $i++) {
|
||||
$pdf->SetXY($x, $y);
|
||||
$pdf->Cell($bbWidths[$i], 6, $bbHeaders[$i], 1, 0, 'C', true);
|
||||
$x += $bbWidths[$i];
|
||||
}
|
||||
$y += 6;
|
||||
|
||||
$pdf->SetFont('dejavusans', '', 7);
|
||||
$bbNum = 1;
|
||||
foreach ($busbars as $bb) {
|
||||
$x = 10;
|
||||
$pdf->SetXY($x, $y);
|
||||
$pdf->Cell($bbWidths[0], 5, $bbNum, 1, 0, 'C');
|
||||
$x += $bbWidths[0];
|
||||
|
||||
$pdf->SetXY($x, $y);
|
||||
$pdf->Cell($bbWidths[1], 5, $bb->output_label ?: 'Sammelschiene '.$bbNum, 1, 0, 'L');
|
||||
$x += $bbWidths[1];
|
||||
|
||||
$pdf->SetXY($x, $y);
|
||||
$pdf->Cell($bbWidths[2], 5, $bb->connection_type ?: '-', 1, 0, 'C');
|
||||
$x += $bbWidths[2];
|
||||
|
||||
$pdf->SetXY($x, $y);
|
||||
$pdf->Cell($bbWidths[3], 5, $bb->rail_start_te ?: '-', 1, 0, 'C');
|
||||
$x += $bbWidths[3];
|
||||
|
||||
$pdf->SetXY($x, $y);
|
||||
$pdf->Cell($bbWidths[4], 5, $bb->rail_end_te ?: '-', 1, 0, 'C');
|
||||
$x += $bbWidths[4];
|
||||
|
||||
$pdf->SetXY($x, $y);
|
||||
$pdf->Cell($bbWidths[5], 5, $bb->rail_phases ?: '-', 1, 0, 'C');
|
||||
$x += $bbWidths[5];
|
||||
|
||||
$pdf->SetXY($x, $y);
|
||||
$pdf->Cell($bbWidths[6], 5, $bb->excluded_te ?: '-', 1, 0, 'L');
|
||||
|
||||
$y += 5;
|
||||
$bbNum++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Output PDF
|
||||
$filename = 'Leitungslaufplan_'.dol_sanitizeFileName($anlage->label).'_'.date('Y-m-d').'.pdf';
|
||||
$pdf->Output($filename, 'D');
|
||||
|
||||
/**
|
||||
* Draw schematic content directly in PDF
|
||||
* Shows only the actual equipment and connections from the database (what was drawn in the editor)
|
||||
*/
|
||||
function drawSchematicContent(&$pdf, $carriers, $equipment, $connections, $startX, $startY, $width, $height) {
|
||||
// Phase colors (DIN VDE compliant)
|
||||
$phaseColors = array(
|
||||
'L1' => array(139, 69, 19), // Brown
|
||||
'L2' => array(0, 0, 0), // Black
|
||||
'L3' => array(128, 128, 128), // Gray
|
||||
'N' => array(0, 102, 204), // Blue
|
||||
'PE' => array(0, 128, 0) // Green (simplified from green-yellow)
|
||||
);
|
||||
|
||||
// Layout constants
|
||||
$teWidth = 10; // mm per TE
|
||||
$equipmentStartY = $startY + 20;
|
||||
$blockWidth = 8;
|
||||
$blockHeight = 20;
|
||||
|
||||
// Calculate total width needed
|
||||
$maxTE = 0;
|
||||
foreach ($carriers as $carrier) {
|
||||
$maxTE = max($maxTE, $carrier->total_te ?: 12);
|
||||
}
|
||||
$contentWidth = min($width - 40, $maxTE * $teWidth + 40);
|
||||
$contentStartX = $startX + 20;
|
||||
|
||||
// ========================================
|
||||
// Draw equipment and connections
|
||||
// ========================================
|
||||
$carrierIndex = 0;
|
||||
foreach ($carriers as $carrier) {
|
||||
$carrierY = $equipmentStartY + $carrierIndex * 50;
|
||||
$carrierX = $contentStartX;
|
||||
$totalTE = $carrier->total_te ?: 12;
|
||||
|
||||
// Carrier label
|
||||
$pdf->SetFont('dejavusans', '', 6);
|
||||
$pdf->SetTextColor(100, 100, 100);
|
||||
$pdf->SetXY($carrierX - 15, $carrierY + $blockHeight / 2 - 2);
|
||||
$pdf->Cell(12, 4, $carrier->label ?: 'H'.($carrierIndex+1), 0, 0, 'R');
|
||||
|
||||
// Get equipment on this carrier
|
||||
$carrierEquipment = array_filter($equipment, function($eq) use ($carrier) {
|
||||
return $eq->fk_carrier == $carrier->id;
|
||||
});
|
||||
|
||||
// Get busbars for this carrier
|
||||
$carrierBusbars = array_filter($connections, function($c) use ($carrier) {
|
||||
return $c->is_rail && $c->fk_carrier == $carrier->id;
|
||||
});
|
||||
|
||||
// Sort equipment by position
|
||||
usort($carrierEquipment, function($a, $b) {
|
||||
return ($a->position_te ?: 1) - ($b->position_te ?: 1);
|
||||
});
|
||||
|
||||
// Draw each equipment
|
||||
foreach ($carrierEquipment as $eq) {
|
||||
$eqPosTE = $eq->position_te ?: 1;
|
||||
$eqWidthTE = $eq->width_te ?: 1;
|
||||
$eqX = $carrierX + ($eqPosTE - 1) * $teWidth;
|
||||
$eqWidth = $eqWidthTE * $teWidth - 2;
|
||||
|
||||
// Equipment block
|
||||
$color = $eq->type_color ?: '#3498db';
|
||||
list($r, $g, $b) = sscanf($color, "#%02x%02x%02x");
|
||||
$pdf->SetFillColor($r ?: 52, $g ?: 152, $b ?: 219);
|
||||
$pdf->Rect($eqX, $carrierY, $eqWidth, $blockHeight, 'F');
|
||||
|
||||
// Equipment label
|
||||
$pdf->SetFont('dejavusans', 'B', 5);
|
||||
$pdf->SetTextColor(255, 255, 255);
|
||||
$label = $eq->type_label_short ?: $eq->label;
|
||||
$pdf->SetXY($eqX, $carrierY + 3);
|
||||
$pdf->Cell($eqWidth, 4, dol_trunc($label, 8), 0, 0, 'C');
|
||||
|
||||
// Second line label
|
||||
if ($eq->label && $eq->type_label_short) {
|
||||
$pdf->SetFont('dejavusans', '', 4);
|
||||
$pdf->SetXY($eqX, $carrierY + 8);
|
||||
$pdf->Cell($eqWidth, 3, dol_trunc($eq->label, 10), 0, 0, 'C');
|
||||
}
|
||||
|
||||
$pdf->SetTextColor(0, 0, 0);
|
||||
|
||||
// Consumer label below equipment
|
||||
$pdf->SetFont('dejavusans', '', 5);
|
||||
$pdf->SetTextColor(80, 80, 80);
|
||||
$pdf->SetXY($eqX - 2, $carrierY + $blockHeight + 1);
|
||||
$consumerLabel = $eq->label ?: '';
|
||||
$pdf->Cell($eqWidth + 4, 4, dol_trunc($consumerLabel, 12), 0, 0, 'C');
|
||||
}
|
||||
|
||||
// Draw busbars (Phasenschienen) for this carrier
|
||||
foreach ($carrierBusbars as $busbar) {
|
||||
$busbarStartTE = $busbar->rail_start_te ?: 1;
|
||||
$busbarEndTE = $busbar->rail_end_te ?: $busbarStartTE;
|
||||
$busbarX = $carrierX + ($busbarStartTE - 1) * $teWidth;
|
||||
$busbarWidth = ($busbarEndTE - $busbarStartTE + 1) * $teWidth;
|
||||
$busbarY = $carrierY - 5;
|
||||
|
||||
// Busbar color
|
||||
$phase = $busbar->rail_phases ?: $busbar->connection_type ?: 'L1';
|
||||
$color = $phaseColors[$phase] ?? array(200, 100, 50);
|
||||
$pdf->SetFillColor($color[0], $color[1], $color[2]);
|
||||
$pdf->Rect($busbarX, $busbarY, $busbarWidth, 3, 'F');
|
||||
|
||||
// Draw vertical taps from busbar to equipment
|
||||
$pdf->SetDrawColor($color[0], $color[1], $color[2]);
|
||||
for ($te = $busbarStartTE; $te <= $busbarEndTE; $te++) {
|
||||
// Check if there's equipment at this TE
|
||||
$hasEquipment = false;
|
||||
foreach ($carrierEquipment as $eq) {
|
||||
$eqStart = $eq->position_te ?: 1;
|
||||
$eqEnd = $eqStart + ($eq->width_te ?: 1) - 1;
|
||||
if ($te >= $eqStart && $te <= $eqEnd) {
|
||||
$hasEquipment = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($hasEquipment) {
|
||||
$tapX = $carrierX + ($te - 1) * $teWidth + $teWidth / 2;
|
||||
$pdf->Line($tapX, $busbarY + 3, $tapX, $carrierY);
|
||||
}
|
||||
}
|
||||
|
||||
// Busbar label
|
||||
$pdf->SetFont('dejavusans', 'B', 5);
|
||||
$pdf->SetTextColor(255, 255, 255);
|
||||
$pdf->SetXY($busbarX, $busbarY - 0.5);
|
||||
$pdf->Cell($busbarWidth, 4, $phase, 0, 0, 'C');
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Draw Inputs (Anschlusspunkte) and Outputs (Abgänge)
|
||||
// ========================================
|
||||
|
||||
// Get non-rail connections for this carrier's equipment
|
||||
$carrierEqIds = array_map(function($eq) { return $eq->id; }, $carrierEquipment);
|
||||
|
||||
foreach ($connections as $conn) {
|
||||
if ($conn->is_rail) continue;
|
||||
|
||||
// ANSCHLUSSPUNKT (Input) - fk_source is NULL, fk_target exists
|
||||
if (empty($conn->fk_source) && !empty($conn->fk_target)) {
|
||||
// Find target equipment
|
||||
$targetEq = null;
|
||||
foreach ($carrierEquipment as $eq) {
|
||||
if ($eq->id == $conn->fk_target) {
|
||||
$targetEq = $eq;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$targetEq) continue;
|
||||
|
||||
$eqPosTE = $targetEq->position_te ?: 1;
|
||||
$eqWidthTE = $targetEq->width_te ?: 1;
|
||||
$eqCenterX = $carrierX + ($eqPosTE - 1) * $teWidth + ($eqWidthTE * $teWidth) / 2;
|
||||
$lineStartY = $carrierY - 18;
|
||||
$lineEndY = $carrierY;
|
||||
|
||||
// Phase color
|
||||
$phase = $conn->connection_type ?: 'L1';
|
||||
$color = $phaseColors[$phase] ?? array(100, 100, 100);
|
||||
$pdf->SetDrawColor($color[0], $color[1], $color[2]);
|
||||
$pdf->SetFillColor($color[0], $color[1], $color[2]);
|
||||
|
||||
// Vertical line from top
|
||||
$pdf->Line($eqCenterX, $lineStartY, $eqCenterX, $lineEndY);
|
||||
|
||||
// Circle at top (source indicator)
|
||||
$pdf->Circle($eqCenterX, $lineStartY, 1.5, 0, 360, 'F');
|
||||
|
||||
// Phase label
|
||||
$pdf->SetFont('dejavusans', 'B', 7);
|
||||
$pdf->SetTextColor($color[0], $color[1], $color[2]);
|
||||
$pdf->SetXY($eqCenterX - 5, $lineStartY - 5);
|
||||
$pdf->Cell(10, 4, $phase, 0, 0, 'C');
|
||||
|
||||
// Optional label
|
||||
if (!empty($conn->output_label)) {
|
||||
$pdf->SetFont('dejavusans', '', 5);
|
||||
$pdf->SetTextColor(100, 100, 100);
|
||||
$pdf->SetXY($eqCenterX + 3, $lineStartY - 4);
|
||||
$pdf->Cell(20, 3, dol_trunc($conn->output_label, 12), 0, 0, 'L');
|
||||
}
|
||||
}
|
||||
|
||||
// ABGANG (Output) - fk_source exists, fk_target is NULL
|
||||
if (!empty($conn->fk_source) && empty($conn->fk_target)) {
|
||||
// Find source equipment
|
||||
$sourceEq = null;
|
||||
foreach ($carrierEquipment as $eq) {
|
||||
if ($eq->id == $conn->fk_source) {
|
||||
$sourceEq = $eq;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$sourceEq) continue;
|
||||
|
||||
$eqPosTE = $sourceEq->position_te ?: 1;
|
||||
$eqWidthTE = $sourceEq->width_te ?: 1;
|
||||
$eqCenterX = $carrierX + ($eqPosTE - 1) * $teWidth + ($eqWidthTE * $teWidth) / 2;
|
||||
$lineStartY = $carrierY + $blockHeight;
|
||||
$lineLength = 18;
|
||||
$lineEndY = $lineStartY + $lineLength;
|
||||
|
||||
// Phase color
|
||||
$phase = $conn->connection_type ?: 'L1N';
|
||||
$color = $phaseColors[$phase] ?? $phaseColors['L1'] ?? array(139, 69, 19);
|
||||
$pdf->SetDrawColor($color[0], $color[1], $color[2]);
|
||||
$pdf->SetFillColor($color[0], $color[1], $color[2]);
|
||||
|
||||
// Vertical line going down
|
||||
$pdf->Line($eqCenterX, $lineStartY, $eqCenterX, $lineEndY);
|
||||
|
||||
// Arrow at end
|
||||
$pdf->Polygon(array(
|
||||
$eqCenterX - 1.5, $lineEndY - 2,
|
||||
$eqCenterX, $lineEndY,
|
||||
$eqCenterX + 1.5, $lineEndY - 2
|
||||
), 'F');
|
||||
|
||||
// Left label: Bezeichnung (rotated text not easy in TCPDF, use horizontal)
|
||||
if (!empty($conn->output_label)) {
|
||||
$pdf->SetFont('dejavusans', 'B', 5);
|
||||
$pdf->SetTextColor(0, 0, 0);
|
||||
$pdf->SetXY($eqCenterX - 15, $lineEndY + 1);
|
||||
$pdf->Cell(30, 3, dol_trunc($conn->output_label, 15), 0, 0, 'C');
|
||||
}
|
||||
|
||||
// Right label: Kabeltyp + Größe
|
||||
$cableInfo = trim(($conn->medium_type ?: '') . ' ' . ($conn->medium_spec ?: ''));
|
||||
if (!empty($cableInfo)) {
|
||||
$pdf->SetFont('dejavusans', '', 4);
|
||||
$pdf->SetTextColor(100, 100, 100);
|
||||
$pdf->SetXY($eqCenterX - 15, $lineEndY + 4);
|
||||
$pdf->Cell(30, 3, dol_trunc($cableInfo, 18), 0, 0, 'C');
|
||||
}
|
||||
|
||||
// Phase type
|
||||
$pdf->SetFont('dejavusans', 'B', 5);
|
||||
$pdf->SetTextColor($color[0], $color[1], $color[2]);
|
||||
$pdf->SetXY($eqCenterX - 5, $lineEndY + 7);
|
||||
$pdf->Cell(10, 3, $phase, 0, 0, 'C');
|
||||
}
|
||||
}
|
||||
|
||||
$carrierIndex++;
|
||||
}
|
||||
|
||||
$pdf->SetTextColor(0, 0, 0);
|
||||
|
||||
// ========================================
|
||||
// Legend - show phase colors used in busbars
|
||||
// ========================================
|
||||
$legendY = $startY + $height - 20;
|
||||
$pdf->SetFont('dejavusans', '', 6);
|
||||
$pdf->SetXY($startX + 5, $legendY);
|
||||
$pdf->Cell(0, 4, 'Phasenfarben nach DIN VDE:', 0, 1, 'L');
|
||||
|
||||
$legendX = $startX + 5;
|
||||
$phases = array('L1', 'L2', 'L3', 'N', 'PE');
|
||||
foreach ($phases as $idx => $phase) {
|
||||
$color = $phaseColors[$phase];
|
||||
$pdf->SetFillColor($color[0], $color[1], $color[2]);
|
||||
$pdf->Rect($legendX + $idx * 25, $legendY + 5, 8, 3, 'F');
|
||||
$pdf->SetTextColor($color[0], $color[1], $color[2]);
|
||||
$pdf->SetXY($legendX + $idx * 25 + 10, $legendY + 4);
|
||||
$pdf->Cell(12, 4, $phase, 0, 0, 'L');
|
||||
}
|
||||
|
||||
$pdf->SetTextColor(0, 0, 0);
|
||||
}
|
||||
|
|
@ -40,6 +40,7 @@ class Equipment extends CommonObject
|
|||
public $type_label_short;
|
||||
public $type_color;
|
||||
public $type_picto;
|
||||
public $type_icon_file; // SVG/PNG schematic symbol
|
||||
public $product_ref;
|
||||
public $product_label;
|
||||
public $protection_device_label; // Label of the protection device
|
||||
|
|
@ -135,7 +136,8 @@ class Equipment extends CommonObject
|
|||
public function fetch($id)
|
||||
{
|
||||
$sql = "SELECT e.*, t.label as type_label, t.label_short as type_label_short,";
|
||||
$sql .= " t.color as type_color, t.picto as type_picto,";
|
||||
$sql .= " t.ref as type_ref, t.color as type_color, t.picto as type_picto, t.icon_file as type_icon_file,";
|
||||
$sql .= " t.terminals_config as terminals_config,";
|
||||
$sql .= " p.ref as product_ref, p.label as product_label,";
|
||||
$sql .= " prot.label as protection_device_label";
|
||||
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as e";
|
||||
|
|
@ -168,8 +170,11 @@ class Equipment extends CommonObject
|
|||
|
||||
$this->type_label = $obj->type_label;
|
||||
$this->type_label_short = $obj->type_label_short;
|
||||
$this->type_ref = $obj->type_ref;
|
||||
$this->type_color = $obj->type_color;
|
||||
$this->type_picto = $obj->type_picto;
|
||||
$this->type_icon_file = $obj->type_icon_file;
|
||||
$this->terminals_config = $obj->terminals_config;
|
||||
$this->product_ref = $obj->product_ref;
|
||||
$this->product_label = $obj->product_label;
|
||||
$this->protection_device_label = $obj->protection_device_label;
|
||||
|
|
@ -266,7 +271,7 @@ class Equipment extends CommonObject
|
|||
$results = array();
|
||||
|
||||
$sql = "SELECT e.*, t.label as type_label, t.label_short as type_label_short,";
|
||||
$sql .= " t.ref as type_ref, t.color as type_color, t.picto as type_picto,";
|
||||
$sql .= " t.ref as type_ref, t.color as type_color, t.picto as type_picto, t.icon_file as type_icon_file,";
|
||||
$sql .= " t.terminals_config as terminals_config,";
|
||||
$sql .= " prot.label as protection_device_label";
|
||||
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as e";
|
||||
|
|
@ -301,6 +306,7 @@ class Equipment extends CommonObject
|
|||
$eq->type_ref = $obj->type_ref;
|
||||
$eq->type_color = $obj->type_color;
|
||||
$eq->type_picto = $obj->type_picto;
|
||||
$eq->type_icon_file = $obj->type_icon_file;
|
||||
$eq->terminals_config = $obj->terminals_config;
|
||||
$eq->protection_device_label = $obj->protection_device_label;
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ class EquipmentConnection extends CommonObject
|
|||
|
||||
public $fk_carrier;
|
||||
public $position_y = 0;
|
||||
public $path_data; // SVG path for manually drawn connections
|
||||
public $note_private;
|
||||
public $status = 1;
|
||||
|
||||
|
|
@ -88,7 +89,7 @@ class EquipmentConnection extends CommonObject
|
|||
$sql .= "entity, fk_source, source_terminal, source_terminal_id, fk_target, target_terminal, target_terminal_id,";
|
||||
$sql .= " connection_type, color, output_label,";
|
||||
$sql .= " medium_type, medium_spec, medium_length,";
|
||||
$sql .= " is_rail, rail_start_te, rail_end_te, rail_phases, excluded_te, fk_carrier, position_y,";
|
||||
$sql .= " is_rail, rail_start_te, rail_end_te, rail_phases, excluded_te, fk_carrier, position_y, path_data,";
|
||||
$sql .= " note_private, status, date_creation, fk_user_creat";
|
||||
$sql .= ") VALUES (";
|
||||
$sql .= ((int) $conf->entity);
|
||||
|
|
@ -111,6 +112,7 @@ class EquipmentConnection extends CommonObject
|
|||
$sql .= ", ".($this->excluded_te ? "'".$this->db->escape($this->excluded_te)."'" : "NULL");
|
||||
$sql .= ", ".($this->fk_carrier > 0 ? ((int) $this->fk_carrier) : "NULL");
|
||||
$sql .= ", ".((int) $this->position_y);
|
||||
$sql .= ", ".($this->path_data ? "'".$this->db->escape($this->path_data)."'" : "NULL");
|
||||
$sql .= ", ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "NULL");
|
||||
$sql .= ", ".((int) $this->status);
|
||||
$sql .= ", '".$this->db->idate($now)."'";
|
||||
|
|
@ -178,6 +180,7 @@ class EquipmentConnection extends CommonObject
|
|||
$this->excluded_te = $obj->excluded_te;
|
||||
$this->fk_carrier = $obj->fk_carrier;
|
||||
$this->position_y = $obj->position_y;
|
||||
$this->path_data = isset($obj->path_data) ? $obj->path_data : null;
|
||||
$this->note_private = $obj->note_private;
|
||||
$this->status = $obj->status;
|
||||
$this->date_creation = $this->db->jdate($obj->date_creation);
|
||||
|
|
@ -230,6 +233,7 @@ class EquipmentConnection extends CommonObject
|
|||
$sql .= ", excluded_te = ".($this->excluded_te ? "'".$this->db->escape($this->excluded_te)."'" : "NULL");
|
||||
$sql .= ", fk_carrier = ".($this->fk_carrier > 0 ? ((int) $this->fk_carrier) : "NULL");
|
||||
$sql .= ", position_y = ".((int) $this->position_y);
|
||||
$sql .= ", path_data = ".($this->path_data ? "'".$this->db->escape($this->path_data)."'" : "NULL");
|
||||
$sql .= ", note_private = ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "NULL");
|
||||
$sql .= ", status = ".((int) $this->status);
|
||||
$sql .= ", fk_user_modif = ".((int) $user->id);
|
||||
|
|
@ -325,6 +329,7 @@ class EquipmentConnection extends CommonObject
|
|||
$conn->excluded_te = $obj->excluded_te;
|
||||
$conn->fk_carrier = $obj->fk_carrier;
|
||||
$conn->position_y = $obj->position_y;
|
||||
$conn->path_data = isset($obj->path_data) ? $obj->path_data : null;
|
||||
$conn->status = $obj->status;
|
||||
|
||||
$conn->source_label = $obj->source_label;
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ class EquipmentType extends CommonObject
|
|||
public $terminals_config; // JSON config for terminals
|
||||
|
||||
public $picto;
|
||||
public $icon_file; // Uploaded SVG/PNG file for schematic symbol
|
||||
public $is_system;
|
||||
public $position;
|
||||
public $active;
|
||||
|
|
@ -76,7 +77,7 @@ class EquipmentType extends CommonObject
|
|||
$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
|
||||
$sql .= "entity, ref, label, label_short, description, fk_system,";
|
||||
$sql .= " width_te, color, fk_product, terminals_config,";
|
||||
$sql .= " picto, is_system, position, active,";
|
||||
$sql .= " picto, icon_file, is_system, position, active,";
|
||||
$sql .= " date_creation, fk_user_creat";
|
||||
$sql .= ") VALUES (";
|
||||
$sql .= "0"; // entity 0 = global
|
||||
|
|
@ -90,6 +91,7 @@ class EquipmentType extends CommonObject
|
|||
$sql .= ", ".($this->fk_product > 0 ? ((int) $this->fk_product) : "NULL");
|
||||
$sql .= ", ".($this->terminals_config ? "'".$this->db->escape($this->terminals_config)."'" : "NULL");
|
||||
$sql .= ", ".($this->picto ? "'".$this->db->escape($this->picto)."'" : "NULL");
|
||||
$sql .= ", ".($this->icon_file ? "'".$this->db->escape($this->icon_file)."'" : "NULL");
|
||||
$sql .= ", 0"; // is_system = 0 for user-created
|
||||
$sql .= ", ".((int) $this->position);
|
||||
$sql .= ", ".((int) ($this->active !== null ? $this->active : 1));
|
||||
|
|
@ -148,6 +150,7 @@ class EquipmentType extends CommonObject
|
|||
$this->fk_product = $obj->fk_product;
|
||||
$this->terminals_config = $obj->terminals_config;
|
||||
$this->picto = $obj->picto;
|
||||
$this->icon_file = $obj->icon_file;
|
||||
$this->is_system = $obj->is_system;
|
||||
$this->position = $obj->position;
|
||||
$this->active = $obj->active;
|
||||
|
|
@ -195,6 +198,7 @@ class EquipmentType extends CommonObject
|
|||
$sql .= ", fk_product = ".($this->fk_product > 0 ? ((int) $this->fk_product) : "NULL");
|
||||
$sql .= ", terminals_config = ".($this->terminals_config ? "'".$this->db->escape($this->terminals_config)."'" : "NULL");
|
||||
$sql .= ", picto = ".($this->picto ? "'".$this->db->escape($this->picto)."'" : "NULL");
|
||||
$sql .= ", icon_file = ".($this->icon_file ? "'".$this->db->escape($this->icon_file)."'" : "NULL");
|
||||
$sql .= ", position = ".((int) $this->position);
|
||||
$sql .= ", active = ".((int) $this->active);
|
||||
$sql .= ", fk_user_modif = ".((int) $user->id);
|
||||
|
|
@ -303,6 +307,7 @@ class EquipmentType extends CommonObject
|
|||
$type->color = $obj->color;
|
||||
$type->fk_product = $obj->fk_product;
|
||||
$type->picto = $obj->picto;
|
||||
$type->icon_file = $obj->icon_file;
|
||||
$type->is_system = $obj->is_system;
|
||||
$type->position = $obj->position;
|
||||
$type->active = $obj->active;
|
||||
|
|
|
|||
3624
js/kundenkarte.js
3624
js/kundenkarte.js
File diff suppressed because it is too large
Load diff
1
js/pathfinding.min.js
vendored
Normal file
1
js/pathfinding.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -1,3 +1,12 @@
|
|||
--
|
||||
-- Script run when an upgrade of Dolibarr is done. Whatever is the Dolibarr version.
|
||||
--
|
||||
|
||||
-- Add path_data column for manually drawn connection paths
|
||||
ALTER TABLE llx_kundenkarte_equipment_connection ADD COLUMN IF NOT EXISTS path_data TEXT AFTER position_y;
|
||||
|
||||
-- Add icon_file column for custom SVG/PNG schematic symbols
|
||||
ALTER TABLE llx_kundenkarte_equipment_type ADD COLUMN IF NOT EXISTS icon_file VARCHAR(255) AFTER picto;
|
||||
|
||||
-- Add terminals_config if not exists
|
||||
ALTER TABLE llx_kundenkarte_equipment_type ADD COLUMN IF NOT EXISTS terminals_config TEXT AFTER fk_product;
|
||||
|
|
|
|||
|
|
@ -37,6 +37,9 @@ CREATE TABLE llx_kundenkarte_equipment_connection (
|
|||
fk_carrier INTEGER, -- Carrier where this connection is rendered
|
||||
position_y INTEGER DEFAULT 0, -- Y offset for rendering (0=first row, 1=second row, etc.)
|
||||
|
||||
-- Manual path data (SVG path string for manually drawn connections)
|
||||
path_data TEXT, -- SVG path like "M 100 200 L 150 200 L 150 300"
|
||||
|
||||
note_private TEXT,
|
||||
status INTEGER DEFAULT 1,
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,11 @@ CREATE TABLE llx_kundenkarte_equipment_type
|
|||
-- Optionale Produkt-Verknuepfung
|
||||
fk_product integer DEFAULT NULL COMMENT 'Optionales Standard-Dolibarr-Produkt',
|
||||
|
||||
-- Terminal-Konfiguration (JSON)
|
||||
terminals_config text COMMENT 'JSON config for terminals',
|
||||
|
||||
picto varchar(64),
|
||||
icon_file varchar(255) COMMENT 'Uploaded SVG/PNG file for schematic symbol',
|
||||
is_system tinyint DEFAULT 0 NOT NULL,
|
||||
|
||||
position integer DEFAULT 0,
|
||||
|
|
|
|||
498
tabs/anlagen.php
498
tabs/anlagen.php
|
|
@ -292,7 +292,7 @@ if ($action == 'confirm_deletefile' && $confirm == 'yes' && $permissiontodelete)
|
|||
// Use Dolibarr standard button classes
|
||||
|
||||
$title = $langs->trans('TechnicalInstallations').' - '.$object->name;
|
||||
llxHeader('', $title, '', '', 0, 0, array('/kundenkarte/js/kundenkarte.js?v='.time()), array('/kundenkarte/css/kundenkarte.css?v='.time()));
|
||||
llxHeader('', $title, '', '', 0, 0, array('/kundenkarte/js/pathfinding.min.js', '/kundenkarte/js/kundenkarte.js?v='.time()), array('/kundenkarte/css/kundenkarte.css?v='.time()));
|
||||
|
||||
// Prepare tabs
|
||||
$head = societe_prepare_head($object);
|
||||
|
|
@ -548,91 +548,41 @@ if (empty($customerSystems)) {
|
|||
|
||||
// Equipment section (only if type can have equipment)
|
||||
if ($type->can_have_equipment) {
|
||||
print '<br><h4><i class="fa fa-microchip"></i> '.$langs->trans('Equipment').' (Hutschienen)</h4>';
|
||||
print '<br><h4><i class="fa fa-microchip"></i> '.$langs->trans('Equipment').' - Schaltplan</h4>';
|
||||
|
||||
// Equipment container
|
||||
// Equipment container - nur noch der SchematicEditor
|
||||
print '<div class="kundenkarte-equipment-container" data-anlage-id="'.$anlageId.'" data-system-id="'.$systemId.'">';
|
||||
|
||||
// Load panels for this Anlage
|
||||
$panelObj = new EquipmentPanel($db);
|
||||
$panels = $panelObj->fetchByAnlage($anlageId);
|
||||
|
||||
// Load carriers without panel (legacy or direct carriers)
|
||||
$carrierObj = new EquipmentCarrier($db);
|
||||
$directCarriers = array();
|
||||
$allCarriers = $carrierObj->fetchByAnlage($anlageId);
|
||||
foreach ($allCarriers as $c) {
|
||||
if (empty($c->fk_panel)) {
|
||||
$directCarriers[] = $c;
|
||||
}
|
||||
}
|
||||
|
||||
// Panel management header
|
||||
print '<div class="kundenkarte-equipment-header">';
|
||||
print '<h4>'.$langs->trans('Panels').' ('.$langs->trans('Fields').')</h4>';
|
||||
if ($permissiontoadd) {
|
||||
print '<a href="#" class="kundenkarte-add-panel" data-anlage-id="'.$anlageId.'">';
|
||||
print '<i class="fa fa-plus"></i> '.$langs->trans('AddPanel');
|
||||
print '</a>';
|
||||
}
|
||||
print '</div>';
|
||||
|
||||
// Panels container (horizontal scrolling)
|
||||
print '<div class="kundenkarte-panels-container">';
|
||||
|
||||
if (!empty($panels)) {
|
||||
foreach ($panels as $p) {
|
||||
print renderPanelHTML($p, $langs, $permissiontoadd, $permissiontodelete, $db);
|
||||
}
|
||||
// Quick-duplicate last panel button
|
||||
if ($permissiontoadd) {
|
||||
$lastPanel = end($panels);
|
||||
print '<div class="kundenkarte-panel-quickadd" data-panel-id="'.$lastPanel->id.'" data-anlage-id="'.$anlageId.'" title="'.$langs->trans('DuplicatePreviousPanel').'">';
|
||||
print '<i class="fa fa-plus"></i>';
|
||||
print '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// Direct carriers (without panel) - legacy support
|
||||
if (!empty($directCarriers)) {
|
||||
print '<div class="kundenkarte-panel kundenkarte-panel-direct">';
|
||||
print '<div class="kundenkarte-panel-header">';
|
||||
print '<span class="kundenkarte-panel-label">'.$langs->trans('DirectCarriers').'</span>';
|
||||
if ($permissiontoadd) {
|
||||
print '<a href="#" class="kundenkarte-add-carrier" data-anlage-id="'.$anlageId.'" data-panel-id="0">';
|
||||
print '<i class="fa fa-plus"></i>';
|
||||
print '</a>';
|
||||
}
|
||||
print '</div>';
|
||||
print '<div class="kundenkarte-carriers-list">';
|
||||
foreach ($directCarriers as $c) {
|
||||
$c->fetchEquipment();
|
||||
print renderCarrierHTML($c, $langs, $permissiontoadd, $permissiontodelete);
|
||||
}
|
||||
print '</div>';
|
||||
print '</div>';
|
||||
}
|
||||
|
||||
// Show "Add Panel" placeholder if no panels and no direct carriers
|
||||
if (empty($panels) && empty($directCarriers)) {
|
||||
print '<div class="opacitymedium" style="padding:20px;">';
|
||||
print $langs->trans('NoPanelsOrCarriers').' - ';
|
||||
print $langs->trans('AddPanelOrCarrier');
|
||||
print '</div>';
|
||||
}
|
||||
|
||||
print '</div>'; // .kundenkarte-panels-container
|
||||
|
||||
// Schematic Editor - Interactive Connection Editor (always expanded)
|
||||
print '<div class="schematic-editor-wrapper" style="margin-top:20px;">';
|
||||
// Schematic Editor - Hauptansicht
|
||||
print '<div class="schematic-editor-wrapper">';
|
||||
print '<div class="schematic-editor-header" style="display:flex;justify-content:space-between;align-items:center;padding:10px 15px;background:#252525;border:1px solid #333;border-radius:4px 4px 0 0;">';
|
||||
print '<div style="color:#3498db;">';
|
||||
print '<strong>'.$langs->trans('ConnectionEditor').'</strong> <span style="color:#888;font-size:0.85em;">(Klick auf Terminal → Klick auf Ziel-Terminal = Verbindung | Rechtsklick = Löschen)</span>';
|
||||
print '<strong>'.$langs->trans('SchematicEditor').'</strong> <span style="color:#888;font-size:0.85em;">(Klick auf Block = Bearbeiten | Drag = Verschieben | + = Hinzufügen)</span>';
|
||||
print '</div>';
|
||||
print '<div class="schematic-editor-actions" style="display:flex;gap:10px;">';
|
||||
print '<div class="schematic-editor-actions" style="display:flex;gap:10px;align-items:center;">';
|
||||
// Zoom controls
|
||||
print '<div class="schematic-zoom-controls" style="display:flex;gap:2px;align-items:center;background:#222;border-radius:3px;padding:2px;">';
|
||||
print '<button type="button" class="schematic-zoom-out" style="width:28px;height:28px;background:#333;border:1px solid #555;border-radius:3px;color:#fff;cursor:pointer;" title="Verkleinern (Ctrl+Scroll)"><i class="fa fa-minus"></i></button>';
|
||||
print '<span class="schematic-zoom-level" style="min-width:45px;text-align:center;color:#888;font-size:12px;">100%</span>';
|
||||
print '<button type="button" class="schematic-zoom-in" style="width:28px;height:28px;background:#333;border:1px solid #555;border-radius:3px;color:#fff;cursor:pointer;" title="Vergrößern (Ctrl+Scroll)"><i class="fa fa-plus"></i></button>';
|
||||
print '<button type="button" class="schematic-zoom-fit" style="width:28px;height:28px;background:#333;border:1px solid #555;border-radius:3px;color:#3498db;cursor:pointer;margin-left:3px;" title="Einpassen"><i class="fa fa-compress"></i></button>';
|
||||
print '<button type="button" class="schematic-zoom-reset" style="width:28px;height:28px;background:#333;border:1px solid #555;border-radius:3px;color:#888;cursor:pointer;" title="100%"><i class="fa fa-search"></i></button>';
|
||||
print '</div>';
|
||||
// Manual wire draw toggle
|
||||
print '<button type="button" class="schematic-wire-draw-toggle" style="padding:5px 10px;background:#333;border:1px solid #555;border-radius:3px;color:#27ae60;cursor:pointer;" title="Manueller Zeichenmodus: Leitungen selbst zeichnen mit Raster-Snap">';
|
||||
print '<i class="fa fa-pencil"></i> Manuell zeichnen';
|
||||
print '</button>';
|
||||
print '<button type="button" class="schematic-add-busbar" style="padding:5px 10px;background:#333;border:1px solid #555;border-radius:3px;color:#f39c12;cursor:pointer;" title="Phasenschiene hinzufügen">';
|
||||
print '<i class="fa fa-arrows-h"></i> Phasenschiene';
|
||||
print '</button>';
|
||||
print '<button type="button" class="schematic-clear-connections" style="padding:5px 10px;background:#333;border:1px solid #555;border-radius:3px;color:#e74c3c;cursor:pointer;">';
|
||||
print '<i class="fa fa-trash"></i> Alle Verbindungen löschen';
|
||||
print '</button>';
|
||||
// PDF Export button
|
||||
$pdfExportUrl = dol_buildpath('/kundenkarte/ajax/export_schematic_pdf.php', 1).'?anlage_id='.$anlageId.'&format=A4&orientation=L';
|
||||
print '<a href="'.$pdfExportUrl.'" target="_blank" class="schematic-export-pdf" style="padding:5px 10px;background:#333;border:1px solid #555;border-radius:3px;color:#3498db;cursor:pointer;text-decoration:none;display:inline-flex;align-items:center;gap:5px;" title="PDF Export (Leitungslaufplan nach DIN EN 61082)">';
|
||||
print '<i class="fa fa-file-pdf-o"></i> PDF Export';
|
||||
print '</a>';
|
||||
print '</div>';
|
||||
print '</div>';
|
||||
print '<div class="schematic-editor-canvas expanded" style="display:block;background:#1a1a1a;border:1px solid #333;border-top:none;border-radius:0 0 4px 4px;padding:15px;overflow:auto;">';
|
||||
|
|
@ -642,402 +592,14 @@ if (empty($customerSystems)) {
|
|||
|
||||
print '</div>'; // .kundenkarte-equipment-container
|
||||
|
||||
// Initialize Equipment JavaScript
|
||||
// Initialize SchematicEditor JavaScript
|
||||
print '<script>
|
||||
$(document).ready(function() {
|
||||
if (typeof KundenKarte !== "undefined") {
|
||||
if (KundenKarte.Equipment) {
|
||||
KundenKarte.Equipment.currentAnlageId = '.$anlageId.';
|
||||
}
|
||||
if (KundenKarte.ConnectionEditor) {
|
||||
KundenKarte.ConnectionEditor.init('.$anlageId.');
|
||||
}
|
||||
if (KundenKarte.SchematicEditor) {
|
||||
KundenKarte.SchematicEditor.init('.$anlageId.');
|
||||
}
|
||||
if (typeof KundenKarte !== "undefined" && KundenKarte.SchematicEditor) {
|
||||
KundenKarte.SchematicEditor.init('.$anlageId.');
|
||||
}
|
||||
});
|
||||
</script>';
|
||||
|
||||
// === Interactive Connection Editor (Prototype) ===
|
||||
// Pure SVG solution - no external libraries needed
|
||||
|
||||
print '<div class="kundenkarte-jsplumb-prototype" style="margin-top:20px;">';
|
||||
print '<div class="titre" style="cursor:pointer;" id="connection-editor-toggle">';
|
||||
print '<i class="fa fa-flask"></i> Interaktiver Verbindungseditor (Prototyp) ';
|
||||
print '<i class="fa fa-chevron-down" style="font-size:12px;"></i>';
|
||||
print '</div>';
|
||||
|
||||
print '<div id="connection-editor-container" style="display:none;margin-top:10px;padding:15px;background:#1a1a1a;border:1px solid #444;border-radius:6px;">';
|
||||
|
||||
print '<p style="color:#888;font-size:12px;margin-bottom:10px;">';
|
||||
print '<strong>Bedienung:</strong> Elemente mit der Maus ziehen. Klick auf Ausgang (gruen) dann auf Eingang (rot) um Verbindung zu erstellen. Rechtsklick auf Verbindung zum Loeschen.';
|
||||
print '</p>';
|
||||
|
||||
// Toolbar
|
||||
print '<div style="margin-bottom:10px;display:flex;gap:10px;flex-wrap:wrap;">';
|
||||
print '<button type="button" class="button" id="btn-clear-connections"><i class="fa fa-trash"></i> Alle Verbindungen loeschen</button>';
|
||||
print '<button type="button" class="button" id="btn-auto-layout"><i class="fa fa-magic"></i> Auto-Layout</button>';
|
||||
print '<span style="color:#666;font-size:11px;align-self:center;" id="connection-status">Bereit</span>';
|
||||
print '</div>';
|
||||
|
||||
// Canvas area
|
||||
print '<div id="connection-canvas" style="position:relative;width:100%;min-height:400px;background:linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);border:1px solid #555;border-radius:4px;overflow:visible;">';
|
||||
|
||||
// SVG layer for connections (drawn behind nodes)
|
||||
print '<svg id="connections-svg" style="position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:1;">';
|
||||
// Grid pattern
|
||||
print '<defs>';
|
||||
print '<pattern id="grid" width="20" height="20" patternUnits="userSpaceOnUse">';
|
||||
print '<path d="M 20 0 L 0 0 0 20" fill="none" stroke="#fff" stroke-width="0.5" opacity="0.1"/>';
|
||||
print '</pattern>';
|
||||
// Arrow marker for connections
|
||||
print '<marker id="arrow" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto" markerUnits="strokeWidth">';
|
||||
print '<path d="M0,0 L0,6 L9,3 z" fill="#3498db"/>';
|
||||
print '</marker>';
|
||||
print '</defs>';
|
||||
print '<rect width="100%" height="100%" fill="url(#grid)"/>';
|
||||
// Connection lines will be added here dynamically
|
||||
print '<g id="connection-lines"></g>';
|
||||
print '</svg>';
|
||||
|
||||
// Create draggable equipment blocks
|
||||
$blockY = 80;
|
||||
$nodeIndex = 0;
|
||||
if (!empty($allCarriers)) {
|
||||
foreach ($allCarriers as $jsCarrier) {
|
||||
$jsCarrier->fetchEquipment();
|
||||
$blockX = 50;
|
||||
if (!empty($jsCarrier->equipment)) {
|
||||
foreach ($jsCarrier->equipment as $eq) {
|
||||
$blockId = 'node-'.$eq->id;
|
||||
$color = $eq->type_color ?: '#3498db';
|
||||
$label = $eq->getBlockLabel() ?: $eq->type_label_short ?: 'EQ';
|
||||
print '<div id="'.$blockId.'" class="drag-node" data-node-id="'.$eq->id.'" ';
|
||||
print 'style="position:absolute;left:'.$blockX.'px;top:'.$blockY.'px;width:70px;height:90px;z-index:10;';
|
||||
print 'background:'.$color.';';
|
||||
print 'border:2px solid rgba(255,255,255,0.3);border-radius:8px;cursor:grab;';
|
||||
print 'display:flex;flex-direction:column;align-items:center;justify-content:center;';
|
||||
print 'color:#fff;font-size:12px;font-weight:bold;text-align:center;padding:5px;';
|
||||
print 'box-shadow:0 4px 15px rgba(0,0,0,0.3);transition:box-shadow 0.2s, transform 0.1s;">';
|
||||
// Input connector (top)
|
||||
print '<div class="connector connector-in" data-type="in" style="position:absolute;top:-8px;left:50%;transform:translateX(-50%);width:20px;height:20px;background:#e74c3c;border:3px solid #fff;border-radius:50%;cursor:crosshair;z-index:100;pointer-events:auto;" title="Eingang"></div>';
|
||||
print '<span style="margin-top:12px;">'.dol_escape_htmltag($label).'</span>';
|
||||
print '<span style="font-size:9px;opacity:0.7;">'.dol_escape_htmltag($eq->type_label_short ?: '').'</span>';
|
||||
// Output connector (bottom)
|
||||
print '<div class="connector connector-out" data-type="out" style="position:absolute;bottom:-8px;left:50%;transform:translateX(-50%);width:20px;height:20px;background:#27ae60;border:3px solid #fff;border-radius:50%;cursor:crosshair;z-index:100;pointer-events:auto;" title="Ausgang"></div>';
|
||||
print '</div>';
|
||||
$blockX += 90;
|
||||
$nodeIndex++;
|
||||
}
|
||||
}
|
||||
$blockY += 130;
|
||||
}
|
||||
}
|
||||
|
||||
// External input node
|
||||
print '<div id="node-external" class="drag-node" data-node-id="external" ';
|
||||
print 'style="position:absolute;left:50px;top:10px;width:120px;height:40px;z-index:10;';
|
||||
print 'background:linear-gradient(135deg, #27ae60 0%, #1e8449 100%);';
|
||||
print 'border:2px solid rgba(255,255,255,0.3);border-radius:8px;cursor:grab;';
|
||||
print 'display:flex;align-items:center;justify-content:center;';
|
||||
print 'color:#fff;font-size:12px;font-weight:bold;';
|
||||
print 'box-shadow:0 4px 15px rgba(0,0,0,0.3);">';
|
||||
print '<i class="fa fa-bolt" style="margin-right:5px;"></i> Einspeisung';
|
||||
print '<div class="connector connector-out" data-type="out" style="position:absolute;bottom:-8px;left:50%;transform:translateX(-50%);width:20px;height:20px;background:#27ae60;border:3px solid #fff;border-radius:50%;cursor:crosshair;z-index:100;pointer-events:auto;" title="Ausgang"></div>';
|
||||
print '</div>';
|
||||
|
||||
print '</div>'; // #connection-canvas
|
||||
|
||||
// JavaScript for drag & drop and connections
|
||||
print '<script>
|
||||
/* Connection Editor v2.3 - '.date('Y-m-d H:i:s').' - flexible Richtung */
|
||||
(function() {
|
||||
var initialized = false;
|
||||
var connections = [];
|
||||
var pendingConnection = null;
|
||||
|
||||
function init() {
|
||||
if (initialized) return;
|
||||
initialized = true;
|
||||
|
||||
var canvas = document.getElementById("connection-canvas");
|
||||
var nodes = canvas.querySelectorAll(".drag-node");
|
||||
var statusEl = document.getElementById("connection-status");
|
||||
|
||||
// VERSION MARKER - if you see this, cache is cleared
|
||||
console.log("=== CONNECTION EDITOR v2.0 ===");
|
||||
console.log("Initializing with " + nodes.length + " nodes");
|
||||
statusEl.textContent = "Editor v2.0 geladen";
|
||||
|
||||
// Make nodes draggable
|
||||
nodes.forEach(function(node) {
|
||||
var isDragging = false;
|
||||
var startX, startY, origX, origY;
|
||||
|
||||
node.addEventListener("mousedown", function(e) {
|
||||
if (e.target.classList.contains("connector")) return;
|
||||
isDragging = true;
|
||||
node.style.cursor = "grabbing";
|
||||
node.style.zIndex = 100;
|
||||
node.style.transform = "scale(1.05)";
|
||||
startX = e.clientX;
|
||||
startY = e.clientY;
|
||||
origX = node.offsetLeft;
|
||||
origY = node.offsetTop;
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
document.addEventListener("mousemove", function(e) {
|
||||
if (!isDragging) return;
|
||||
var dx = e.clientX - startX;
|
||||
var dy = e.clientY - startY;
|
||||
var newX = Math.max(0, Math.min(canvas.offsetWidth - node.offsetWidth, origX + dx));
|
||||
var newY = Math.max(0, Math.min(canvas.offsetHeight - node.offsetHeight, origY + dy));
|
||||
node.style.left = newX + "px";
|
||||
node.style.top = newY + "px";
|
||||
updateConnections();
|
||||
});
|
||||
|
||||
document.addEventListener("mouseup", function() {
|
||||
if (isDragging) {
|
||||
isDragging = false;
|
||||
node.style.cursor = "grab";
|
||||
node.style.zIndex = 10;
|
||||
node.style.transform = "scale(1)";
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Connector click handling
|
||||
var connectors = canvas.querySelectorAll(".connector");
|
||||
console.log("[DEBUG] Found " + connectors.length + " connectors");
|
||||
|
||||
connectors.forEach(function(connEl) {
|
||||
console.log("[DEBUG] Handler fuer Connector:", connEl.dataset.type, "auf Node", connEl.closest(".drag-node").dataset.nodeId);
|
||||
|
||||
// Flexibel: egal ob von oben oder unten eingespeist wird
|
||||
connEl.addEventListener("mousedown", function(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
var type = connEl.dataset.type;
|
||||
var nodeEl = connEl.closest(".drag-node");
|
||||
var nodeId = nodeEl.dataset.nodeId;
|
||||
|
||||
console.log("[DEBUG] Connector GEKLICKT: type=" + type + ", node=" + nodeId);
|
||||
|
||||
if (!pendingConnection) {
|
||||
// Erster Klick - starte Verbindung (egal ob Input oder Output)
|
||||
pendingConnection = { from: nodeEl, fromConn: connEl, fromType: type };
|
||||
statusEl.textContent = "Von " + nodeId + " (" + type + ") - klicke auf Ziel";
|
||||
statusEl.style.color = "#f39c12";
|
||||
connEl.style.boxShadow = "0 0 12px #f39c12";
|
||||
console.log("[DEBUG] Verbindung gestartet von " + nodeId);
|
||||
} else if (pendingConnection.from === nodeEl) {
|
||||
// Gleicher Node - abbrechen
|
||||
console.log("[DEBUG] Gleicher Node - abgebrochen");
|
||||
pendingConnection.fromConn.style.boxShadow = "";
|
||||
pendingConnection = null;
|
||||
statusEl.textContent = "Abgebrochen";
|
||||
statusEl.style.color = "#e74c3c";
|
||||
} else {
|
||||
// Zweiter Klick auf anderem Node - Verbindung erstellen
|
||||
console.log("[DEBUG] Erstelle Verbindung...");
|
||||
createConnection(pendingConnection.from, pendingConnection.fromConn, nodeEl, connEl);
|
||||
pendingConnection.fromConn.style.boxShadow = "";
|
||||
pendingConnection = null;
|
||||
statusEl.textContent = "Verbindung erstellt!";
|
||||
statusEl.style.color = "#27ae60";
|
||||
}
|
||||
setTimeout(function() {
|
||||
if (!pendingConnection) {
|
||||
statusEl.textContent = "Bereit";
|
||||
statusEl.style.color = "#666";
|
||||
}
|
||||
}, 1500);
|
||||
});
|
||||
|
||||
// Hover effects
|
||||
connEl.addEventListener("mouseenter", function() {
|
||||
connEl.style.transform = "translateX(-50%) scale(1.3)";
|
||||
});
|
||||
connEl.addEventListener("mouseleave", function() {
|
||||
connEl.style.transform = "translateX(-50%) scale(1)";
|
||||
});
|
||||
});
|
||||
|
||||
// Cancel pending connection on canvas click
|
||||
canvas.addEventListener("click", function(e) {
|
||||
if (!e.target.classList.contains("connector") && pendingConnection) {
|
||||
pendingConnection.fromConn.style.boxShadow = "";
|
||||
pendingConnection = null;
|
||||
statusEl.textContent = "Abgebrochen";
|
||||
statusEl.style.color = "#e74c3c";
|
||||
setTimeout(function() {
|
||||
statusEl.textContent = "Bereit";
|
||||
statusEl.style.color = "#666";
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
|
||||
// Clear all connections
|
||||
document.getElementById("btn-clear-connections").addEventListener("click", function() {
|
||||
connections.forEach(function(c) { if (c.path) c.path.remove(); });
|
||||
connections = [];
|
||||
statusEl.textContent = "Alle Verbindungen geloescht";
|
||||
statusEl.style.color = "#e74c3c";
|
||||
setTimeout(function() { statusEl.textContent = "Bereit"; statusEl.style.color = "#666"; }, 1500);
|
||||
});
|
||||
|
||||
// Auto layout
|
||||
document.getElementById("btn-auto-layout").addEventListener("click", function() {
|
||||
var x = 50, y = 80, row = 0;
|
||||
nodes.forEach(function(node, i) {
|
||||
if (node.dataset.nodeId === "external") {
|
||||
node.style.left = "50px";
|
||||
node.style.top = "10px";
|
||||
} else {
|
||||
node.style.left = x + "px";
|
||||
node.style.top = y + "px";
|
||||
x += 90;
|
||||
if ((i + 1) % 8 === 0) { x = 50; y += 130; }
|
||||
}
|
||||
});
|
||||
updateConnections();
|
||||
statusEl.textContent = "Layout angepasst";
|
||||
statusEl.style.color = "#3498db";
|
||||
setTimeout(function() { statusEl.textContent = "Bereit"; statusEl.style.color = "#666"; }, 1500);
|
||||
});
|
||||
}
|
||||
|
||||
function createConnection(fromNode, fromConn, toNode, toConn) {
|
||||
console.log("createConnection called");
|
||||
try {
|
||||
var svgNS = "http://www.w3.org/2000/svg";
|
||||
var linesGroup = document.getElementById("connection-lines");
|
||||
var canvas = document.getElementById("connection-canvas");
|
||||
|
||||
console.log("linesGroup:", linesGroup);
|
||||
console.log("canvas:", canvas);
|
||||
|
||||
// Create path element
|
||||
var path = document.createElementNS(svgNS, "path");
|
||||
path.setAttribute("stroke", "#3498db");
|
||||
path.setAttribute("stroke-width", "3");
|
||||
path.setAttribute("fill", "none");
|
||||
path.setAttribute("marker-end", "url(#arrow)");
|
||||
path.style.pointerEvents = "stroke";
|
||||
path.style.cursor = "pointer";
|
||||
|
||||
// Calculate path
|
||||
var pathD = calculatePath(fromConn, toConn, canvas);
|
||||
console.log("Path D:", pathD);
|
||||
path.setAttribute("d", pathD);
|
||||
|
||||
linesGroup.appendChild(path);
|
||||
console.log("Path added to SVG");
|
||||
|
||||
var conn = {
|
||||
from: fromNode,
|
||||
to: toNode,
|
||||
fromConn: fromConn,
|
||||
toConn: toConn,
|
||||
path: path
|
||||
};
|
||||
connections.push(conn);
|
||||
|
||||
// Hover effect
|
||||
path.addEventListener("mouseenter", function() {
|
||||
path.setAttribute("stroke", "#e74c3c");
|
||||
path.setAttribute("stroke-width", "4");
|
||||
});
|
||||
path.addEventListener("mouseleave", function() {
|
||||
path.setAttribute("stroke", "#3498db");
|
||||
path.setAttribute("stroke-width", "3");
|
||||
});
|
||||
|
||||
// Right-click to delete
|
||||
path.addEventListener("contextmenu", function(e) {
|
||||
e.preventDefault();
|
||||
var idx = connections.indexOf(conn);
|
||||
if (idx >= 0) {
|
||||
connections.splice(idx, 1);
|
||||
path.remove();
|
||||
document.getElementById("connection-status").textContent = "Verbindung geloescht";
|
||||
}
|
||||
});
|
||||
|
||||
console.log("Connection created:", fromNode.dataset.nodeId, "->", toNode.dataset.nodeId);
|
||||
} catch(err) {
|
||||
console.error("Error in createConnection:", err);
|
||||
}
|
||||
}
|
||||
|
||||
function calculatePath(fromConn, toConn, canvas) {
|
||||
var canvasRect = canvas.getBoundingClientRect();
|
||||
var fromRect = fromConn.getBoundingClientRect();
|
||||
var toRect = toConn.getBoundingClientRect();
|
||||
|
||||
console.log("Canvas rect:", canvasRect);
|
||||
console.log("From rect:", fromRect);
|
||||
console.log("To rect:", toRect);
|
||||
|
||||
// Get center points relative to canvas
|
||||
var x1 = fromRect.left + fromRect.width/2 - canvasRect.left;
|
||||
var y1 = fromRect.top + fromRect.height/2 - canvasRect.top;
|
||||
var x2 = toRect.left + toRect.width/2 - canvasRect.left;
|
||||
var y2 = toRect.top + toRect.height/2 - canvasRect.top;
|
||||
|
||||
console.log("Points: (" + x1 + "," + y1 + ") -> (" + x2 + "," + y2 + ")");
|
||||
|
||||
// Create curved path (bezier)
|
||||
var ctrlY1 = y1 + Math.abs(y2 - y1) * 0.4;
|
||||
var ctrlY2 = y2 - Math.abs(y2 - y1) * 0.4;
|
||||
|
||||
return "M " + x1 + " " + y1 + " C " + x1 + " " + ctrlY1 + ", " + x2 + " " + ctrlY2 + ", " + x2 + " " + y2;
|
||||
}
|
||||
|
||||
function updateConnections() {
|
||||
var canvas = document.getElementById("connection-canvas");
|
||||
connections.forEach(function(c) {
|
||||
if (c.path && c.fromConn && c.toConn) {
|
||||
var pathD = calculatePath(c.fromConn, c.toConn, canvas);
|
||||
c.path.setAttribute("d", pathD);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Toggle button
|
||||
document.getElementById("connection-editor-toggle").addEventListener("click", function() {
|
||||
var container = document.getElementById("connection-editor-container");
|
||||
if (container.style.display === "none") {
|
||||
container.style.display = "block";
|
||||
setTimeout(init, 150);
|
||||
} else {
|
||||
container.style.display = "none";
|
||||
}
|
||||
});
|
||||
|
||||
// Expose for external use
|
||||
window.ConnectionEditor = {
|
||||
getConnections: function() { return connections; },
|
||||
clear: function() {
|
||||
connections.forEach(function(c) { if (c.path) c.path.remove(); });
|
||||
connections = [];
|
||||
},
|
||||
refresh: function() { updateConnections(); }
|
||||
};
|
||||
})();
|
||||
</script>';
|
||||
|
||||
print '<div style="margin-top:10px;">';
|
||||
print '<button type="button" class="button" onclick="if(window.jsPlumbInstance) { window.jsPlumbInstance.deleteEveryConnection(); console.log(\'Connections cleared\'); }">Alle Verbindungen löschen</button> ';
|
||||
print '<button type="button" class="button" onclick="alert(\'Export-Funktion kommt später\');">Verbindungen exportieren</button>';
|
||||
print '</div>';
|
||||
|
||||
print '</div>'; // #jsplumb-container
|
||||
print '</div>'; // .kundenkarte-jsplumb-prototype
|
||||
}
|
||||
|
||||
// Action buttons
|
||||
|
|
|
|||
Loading…
Reference in a new issue