Files
thetool/Layout/default/Patching/Index.php
2025-07-14 05:44:02 +00:00

452 lines
26 KiB
PHP

<?php
// --- Configuration ---
$pagination_baseurl = $this->getUrl($Mod,"Index");
$pagination_baseurl_params = ["filter" => $filter]; // Filters passed via GET
$pagination_entity_name = "Patchungen";
// Ensure $filter is always an array
if(!is_array($filter)) {
$filter = [];
}
// Helper function for safe output (assuming not already handled by framework/template engine)
function e($string) {
return htmlspecialchars($string ?? '', ENT_QUOTES, 'UTF-8');
}
$additionalHead = [
'<link rel="manifest" href="/assets/pwa/patching-manifest.json">'
];
?>
<?php include(realpath(dirname(__FILE__)."/../../$mfLayoutPackage")."/header.php"); ?>
<div class="row">
<div class="col-12">
<div class="page-title-box">
<div class="page-title-right">
<ol class="breadcrumb m-0">
<li class="breadcrumb-item"><a href="<?=e(self::getUrl("Dashboard"))?>"><?=e(MFAPPNAME_SLUG)?></a></li>
<li class="breadcrumb-item active">Patchungen</li>
</ol>
</div>
<h4 class="page-title">Patchungen</h4>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="card">
<div class="card-body mb-3">
<h4 class="header-title mb-3">Filter</h4>
<form method="get" action="<?=e(self::getUrl("Patching"))?>">
<div class="row">
<div class="col-12 col-md-6 col-lg-2 mb-3">
<label class="form-label" for="filter_network_id">Netzgebiet</label>
<select name="filter[network_id]" id="filter_network_id" class="form-control">
<option value=""></option>
<?php foreach($mynetworks as $fnet): ?>
<option value="<?=e($fnet->id)?>" <?=(isset($filter['network_id']) && $filter['network_id'] == $fnet->id) ? "selected='selected'" : ""?>><?=e($fnet->name)?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-12 col-md-6 col-lg-2 mb-3">
<label class="form-label" for="filter_pop_id">POP</label>
<select name="filter[pop_id]" id="filter_pop_id" class="form-control">
<option value=""></option>
<?php foreach($mynetworks as $fnet): ?>
<?php if(is_array($fnet->pops) && count($fnet->pops)): ?>
<optgroup label="<?=e($fnet->name)?>">
<?php foreach($fnet->pops as $pop): ?>
<option value="<?=e($pop->id)?>" <?=(isset($filter['pop_id']) && $filter['pop_id'] == $pop->id) ? "selected='selected'" : ""?>><?=e($pop->name)?></option>
<?php endforeach; ?>
</optgroup>
<?php endif; ?>
<?php endforeach; ?>
</select>
</div>
<div class="col-12 col-md-6 col-lg-2 mb-3">
<label class="form-label" for="filter_patched">Patchstatus</label>
<select name="filter[patched]" id="filter_patched" class="form-control">
<option value="0" <?=((int)($filter['patched'] ?? 0) < 1) ? "selected='selected'" : ""?>>Nicht gepatched</option>
<option value="1" <?=((int)($filter['patched'] ?? 0) == 1) ? "selected='selected'" : ""?>>Gepatched</option>
<option value="2" <?=((int)($filter['patched'] ?? 0) == 2) ? "selected='selected'" : ""?>>Vorpatchen</option>
</select>
</div>
<div class="col-12 col-md-6 col-lg-2 mb-3">
<label class="form-label" for="filter_hide_delayed_finish">Verzögerte Herstellung</label>
<select name="filter[hide_delayed_finish]" id="filter_hide_delayed_finish" class="form-control">
<option value="0" <?=(isset($filter["hide_delayed_finish"]) && $filter['hide_delayed_finish'] != 1) ? "selected='selected'" : ""?>>Anzeigen</option>
<option value="1" <?=(!isset($filter["hide_delayed_finish"]) || $filter['hide_delayed_finish'] == 1) ? "selected='selected'" : ""?>>Nicht anzeigen</option>
</select>
</div>
<div class="col-12 col-md-6 col-lg-1 mb-3">
<label class="form-label" for="filter_code">Objekt ID</label>
<input type="text" class="form-control" name="filter[code]" id="filter_code" value="<?=e($filter['code'] ?? '')?>" />
</div>
<div class="col-12 col-md-6 col-lg-3 mb-3">
<label class="form-label" for="filter_street">Straße</label>
<input type="text" class="form-control" name="filter[street]" id="filter_street" value="<?=e($filter['street'] ?? '')?>" />
</div>
</div>
<div class="row mt-2">
<div class="col-12">
<button type="submit" class="btn btn-primary">Filter anwenden</button>
<a class="btn btn-secondary" href="<?=e(self::getUrl("Patching"))?>">Filter zurücksetzen</a>
</div>
</div>
</form>
</div>
</div>
<div class="card">
<div class="card-body mb-3">
<h4 class="header-title">Patchungen</h4>
<?php // Include pagination controls - Top ?>
<?php include(realpath(dirname(__FILE__)."/../")."/tpl/pagination.php"); ?>
<?php include(realpath(dirname(__FILE__)."/../")."/tpl/pagination-summary.php"); ?>
<div class="table-responsive mt-3"> <?php // Added table-responsive wrapper and margin-top ?>
<table class="table table-striped table-hover">
<thead class="table-bordered"> <?php // Added thead and border class ?>
<tr>
<th colspan="4" class="text-center">Standort</th>
<th colspan="2" class="text-center">ODF</th>
<th colspan="5" class="text-center">Abschluss/Device</th>
<th>Aktion</th> <?php // Renamed last header ?>
</tr>
<tr>
<th>Netzgebiet</th>
<th class="text-nowrap">POP</th> <?php // Added text-nowrap for min-width effect ?>
<th class="text-nowrap">Kunde</th> <?php // Added text-nowrap ?>
<th>Standort</th> <?php // Allow wrapping ?>
<th class="text-nowrap">Patchposition ODF</th> <?php // Added text-nowrap ?>
<th style="min-width: 105px">ODF Port</th> <?php // Select handles resize ok ?>
<th style="min-width: 195px">Typ</th> <?php // Select handles resize ok ?>
<th style="min-width: 190px" class="text-nowrap">Splitter / Gerät</th> <?php // Added text-nowrap ?>
<th style="min-width: 90px" class="text-nowrap">Port</th> <?php // Added text-nowrap ?>
<th>Gepatched</th>
<th class="text-nowrap">Von</th> <?php // Added text-nowrap ?>
<th class="text-nowrap">Speichern</th> <?php // Added text-nowrap ?>
</tr>
</thead>
<tbody>
<?php foreach($terminations as $term): ?>
<?php
// --- Prepare complex data for cleaner output ---
// ODF Patch Position
$wfItems = $term->workflowitems ?? [];
$istSchrankValue = $wfItems["ist_schrank"]->value ?? null;
$schrankValue = $wfItems["schrank"]->value ?? null;
$istBaugruppeValue = $wfItems["ist_baugruppe"]->value ?? null;
$baugruppeValue = $wfItems["baugruppe"]->value ?? null;
$istModulValue = $wfItems["ist_modul"]->value ?? null;
$modulValue = $wfItems["modul"]->value ?? null;
$istPortsValue = $wfItems["ist_ports"]->value ?? null;
$portsValue = $wfItems["ports"]->value ?? null;
$schrank = ($istSchrankValue && $istSchrankValue->id) ? $istSchrankValue->value_string : ($schrankValue ? $schrankValue->value_string : '');
$baugruppe = ($istBaugruppeValue && $istBaugruppeValue->id) ? $istBaugruppeValue->value_string : ($baugruppeValue ? $baugruppeValue->value_string : '');
$modul = ($istModulValue && $istModulValue->id) ? $istModulValue->value_string : ($modulValue ? $modulValue->value_string : '');
$ports = ($istPortsValue && $istPortsValue->id) ? $istPortsValue->value_string : ($portsValue ? $portsValue->value_string : '');
$odfPositionTitle = "Schrank: " . e($schrank) . " / Einschub: " . e($baugruppe) . " / Modul: " . e($modul) . " / Port: " . e($ports);
$odfPositionDisplay = e($schrank) . " / " . e($baugruppe) . " / " . e($modul) . " / " . e($ports);
// Patched By Info
$patching = $term->patching ?? null;
$patcher = $patching ? ($patching->patcher ?? null) : null;
$patchedByTitle = '';
$patchedByDisplay = '';
if ($patching && $patching->patched == 1 && $patching->patched_by && $patcher) {
$patcherName = $patcher->name ?? 'Unbekannt';
$patcherCompany = $patcher->address ? $patcher->address->getCompanyOrName(true) : 'N/A';
$patchedDate = date("d.m.Y H:i", $patching->patched_date);
$patchedByTitle = "Gepatched: {$patchedDate} von " . e($patcherName) . " (" . e($patcherCompany) . ")";
$patchedByDisplay = $patcher->getAbbrName() ?? '';
}
// Customer Info
$customer = $term->order->owner ?? null;
$customerNumber = $customer ? $customer->customer_number : '';
$customerName = $customer ? $customer->getCompanyOrName() : '';
// Location Info
$building = $term->building ?? null;
$networkName = $building ? ($building->network->name ?? '') : '';
$popName = '';
$termPop = $term->getPop(); // Call potentially expensive method once
if ($termPop) {
$popName = $termPop->name ?? '';
} elseif ($building && $building->pop) {
$popName = $building->pop->name ?? '';
}
$street = $building->street ?? '';
$zip = $building->zip ?? '';
$city = $building->city ?? '';
// Linework Ports
$lineworkPorts = $term->getLineworkportPairs() ?? [];
$currentLineworkPort = $patching ? $patching->linework_ports : null;
// Device Info
$deviceType = $patching ? $patching->device_type : '';
$deviceName = $patching ? $patching->device_name : '';
$devicePort = $patching ? $patching->device_port : '';
$isPatched = $patching ? ($patching->patched == 1) : false;
?>
<tr class="align-middle"> <?php // Added align-middle ?>
<td><?=e($networkName)?></td>
<td><?=e($popName)?></td>
<td><?=e($customerNumber)?><br /><?=e($customerName)?></td>
<td>
<?=e($street)?><br />
<?=e($zip)?> <?=e($city)?>
</td>
<td class="text-mono text-primary" title="<?=$odfPositionTitle?>">
<?=$odfPositionDisplay?>
</td>
<td>
<select name="linework_port" form="term-form-<?=e($term->id)?>" class="form-control"> <?php // Removed -sm ?>
<?php foreach($lineworkPorts as $portGroup): ?>
<?php foreach($portGroup as $p): ?>
<option value="<?=e($p)?>" <?=($currentLineworkPort == $p) ? "selected='selected'" : ""?>><?=e($p)?></option>
<?php endforeach; ?>
<?php endforeach; ?>
</select>
</td>
<td>
<select name="device_type" form="term-form-<?=e($term->id)?>" class="form-control"> <?php // Removed -sm ?>
<option value=""></option>
<option value="splitter" <?=($deviceType == "splitter") ? "selected='selected'" : ""?>>Splitter</option>
<option value="pon" <?=($deviceType == "pon") ? "selected='selected'" : ""?>>Shared PON-Port</option>
<option value="switch" <?=($deviceType == "switch") ? "selected='selected'" : ""?>>Switch</option>
</select>
</td>
<td><input type="text" class="form-control device-name-input" form="term-form-<?=e($term->id)?>" name="device_name" value="<?=e($deviceName)?>" placeholder="Splitter / Gerät" /></td> <?php // Removed -sm ?>
<td><input type="text" class="form-control" form="term-form-<?=e($term->id)?>" name="device_port" value="<?=e($devicePort)?>" placeholder="Port" /></td> <?php // Removed -sm ?>
<td class="text-center"> <?php // Added text-center ?>
<input type="hidden" name="patched" value="0" form="term-form-<?=e($term->id)?>" /> <?php // Ensure 0 is sent if checkbox is unchecked ?>
<input type="checkbox" class="form-check-input" style="position:unset !important;height: 2em; width: 2em;" form="term-form-<?=e($term->id)?>" name="patched" value="1" <?=($isPatched) ? "checked='checked'" : ""?> /> <?php // Adjusted checkbox style slightly ?>
</td>
<td <?=!empty($patchedByTitle) ? 'title="' . $patchedByTitle . '"' : ''?>>
<?=e($patchedByDisplay)?>
</td>
<td>
<form method="post" id="term-form-<?=e($term->id)?>" action="<?=e(self::getUrl("Patching","save", ["s" => $pagination['start'] ?? 0, "filter" => $filter]))?>">
<input type="hidden" name="termination_id" value="<?=e($term->id)?>" />
<button type="submit" class="btn btn-primary">Speichern</button> <?php // Changed value to button text, removed -sm ?>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div> <?php // End table-responsive ?>
<?php // Include pagination controls - Bottom ?>
<?php include(realpath(dirname(__FILE__)."/../")."/tpl/pagination-summary.php"); ?>
<?php include(realpath(dirname(__FILE__)."/../")."/tpl/pagination.php"); ?>
</div>
</div>
</div>
</div>
<style>
@media (max-width: 768px) {
.pagination {
overflow-x: auto;
white-space: nowrap;
}
}
.select2-selection__rendered {
line-height: 38px !important;
}
.select2-container .select2-selection--single {
height: 38px !important;
}
.select2-selection__arrow {
height: 37px !important;
}
@media all and (display-mode: standalone) {
#topnav {
display: none !important;
}
.wrapper {
padding-top: 0 !important;
}
}
</style>
<script>
window.DEVICES = <?= json_encode($devices) ?>;
//window.DEVICES = window.DEVICES.filter(device => {
// return device.name.toLowerCase().includes('olt') || device.name.toLowerCase().includes('pon')
//});
</script>
<script>
/*$(function() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/Patching/sw', { scope: '/' })
.then(registration => {
console.log('Patching PWA Service Worker registered with scope:', registration.scope);
})
.catch(error => {
console.error('Patching PWA Service Worker registration failed:', error);
});
}
if (typeof $().select2 === 'undefined') {
console.error('Select2 library is not loaded.');
// Optionally display an error to the user or fall back gracefully
return; // Stop execution if Select2 is missing
}
const ipNumRegex = /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})[/-](\d+)$/;
// Helper: Find device object by IP
function findDeviceByIp(ip) {
if (!window.DEVICES || !Array.isArray(window.DEVICES)) {
console.error('DEVICES array not found or invalid.');
return null;
}
return window.DEVICES.find(device => device.ip === ip);
}
// Helper: Update the original hidden input field's value
function updateOriginalInput($originalInput, ip, number) {
const cleanNumber = String(number).trim(); // Ensure number is a string and trimmed
if (ip && cleanNumber) {
$originalInput.val(ip + '/' + cleanNumber).trigger('change'); // Update and trigger change
} else {
$originalInput.val('').trigger('change'); // Clear if incomplete and trigger change
}
}
// --- Initialize for each device name input ---
$('.device-name-input').each(function() {
const $originalInput = $(this);
const $td = $originalInput.closest('td'); // Get the parent cell for structure
let initialValue = $originalInput.val().trim();
// Create wrapper for the Select2 component (Select + Number Input)
// Hide it initially
const $selectWrapper = $('<div class="device-select-wrapper" style="display: none; align-items: center; gap: 5px;"></div>');
const $select = $('<select class="form-control device-select" style="flex-grow: 1; width: auto;"></select>'); // Let flexbox handle width
const $numberInput = $('<input type="number" class="form-control device-number" placeholder="Nr." style="width: 70px; flex-grow: 0; flex-shrink: 0;">'); // Fixed width number input
// Populate Select options
$select.append('<option value="">Gerät auswählen...</option>'); // Placeholder
if (window.DEVICES && Array.isArray(window.DEVICES)) {
window.DEVICES.forEach(device => {
// Display name and IP for clarity in dropdown
const displayText = `${device.name} (${device.ip})`;
$select.append($('<option></option>').val(device.ip).text(displayText));
});
} else {
console.warn("DEVICES array is missing or invalid for row:", $originalInput.attr('form'));
// Optionally add a disabled option indicating an error
$select.append('<option value="" disabled>Device list error</option>');
}
$selectWrapper.append($select).append($numberInput);
$td.append($selectWrapper); // Add the hidden wrapper to the cell
// Function to show Select2 component and hide original input
function showSelectComponent(ip, number) {
$originalInput.hide();
$select.val(ip || '').trigger('change.select2'); // Set value and update Select2 display
$numberInput.val(number || '');
$selectWrapper.css('display', 'flex'); // Use flex to show side-by-side
}
// Function to show original input and hide Select2 component
function showOriginalInput() {
$selectWrapper.hide();
$originalInput.show();
}
// Initial state determination
const match = initialValue.match(ipNumRegex);
if (match) {
const initialIp = match[1];
const initialNumber = match[2];
const foundDevice = findDeviceByIp(initialIp);
if (foundDevice) {
// Valid format and known device -> Show Select2 component
showSelectComponent(initialIp, initialNumber);
} else {
// Valid format but unknown device -> Show original input
showOriginalInput();
}
} else if (!initialValue) {
// Empty -> Show Select2 component (empty)
showSelectComponent(null, null);
} else {
// Invalid format -> Show original input
showOriginalInput();
}
// Initialize Select2 *after* adding options and inserting into DOM
$select.select2({
width: 'style', // Adjust width based on container
// dropdownParent: $td // Optional: Attach dropdown to cell if needed for positioning
});
// --- Event Listeners ---
// Update original input when Select2 or number changes
$select.on('change', function() {
const selectedIp = $(this).val();
const currentNumber = $numberInput.val();
updateOriginalInput($originalInput, selectedIp, currentNumber);
});
$numberInput.on('input change', function() { // Use 'input change' for better responsiveness
const currentIp = $select.val();
const selectedNumber = $(this).val();
updateOriginalInput($originalInput, currentIp, selectedNumber);
});
// Check original input on change/blur to potentially switch view
$originalInput.on('change blur', function() {
const currentValue = $(this).val().trim();
const currentMatch = currentValue.match(ipNumRegex);
if (currentMatch) {
const currentIp = currentMatch[1];
const currentNumber = currentMatch[2];
const device = findDeviceByIp(currentIp);
if (device) {
// Switched to valid/known format -> Show Select2
showSelectComponent(currentIp, currentNumber);
}
// else: Keep showing original input (valid format, but unknown device)
}
// else: Keep showing original input (invalid format)
});
}); // End .each loop
});*/
</script>
<?php include(realpath(dirname(__FILE__)."/../../$mfLayoutPackage")."/footer.php"); ?>