395 lines
19 KiB
PHP
395 lines
19 KiB
PHP
<?php
|
|
/**
|
|
* app/modules/floor_infrastructure/edit.php
|
|
*
|
|
* Formular zum Anlegen/Bearbeiten von Patchpanels und Wandbuchsen
|
|
*/
|
|
|
|
$type = $_GET['type'] ?? 'patchpanel';
|
|
$id = (int)($_GET['id'] ?? 0);
|
|
|
|
$locations = $sql->get("SELECT id, name FROM locations ORDER BY name", "", []);
|
|
$buildings = $sql->get("SELECT id, name, location_id FROM buildings ORDER BY name", "", []);
|
|
$floors = $sql->get(
|
|
"SELECT f.*, b.name AS building_name, b.location_id, l.name AS location_name
|
|
FROM floors f
|
|
LEFT JOIN buildings b ON b.id = f.building_id
|
|
LEFT JOIN locations l ON l.id = b.location_id
|
|
ORDER BY l.name, b.name, f.level, f.name",
|
|
"",
|
|
[]
|
|
);
|
|
$rooms = $sql->get(
|
|
"SELECT r.id, r.name, r.floor_id, r.x, r.y, r.width, r.height, r.polygon_points,
|
|
f.name AS floor_name, f.svg_path, b.id AS building_id, l.id AS location_id
|
|
FROM rooms r
|
|
LEFT JOIN floors f ON f.id = r.floor_id
|
|
LEFT JOIN buildings b ON b.id = f.building_id
|
|
LEFT JOIN locations l ON l.id = b.location_id
|
|
ORDER BY l.name, b.name, f.level, f.name, r.name",
|
|
"",
|
|
[]
|
|
);
|
|
|
|
$locationMap = [];
|
|
foreach ($locations as $location) {
|
|
$locationMap[$location['id']] = $location['name'];
|
|
}
|
|
|
|
foreach ($floors as &$floor) {
|
|
$paths = trim((string)($floor['svg_path'] ?? ''));
|
|
$floor['svg_url'] = $paths !== '' ? '/' . ltrim($paths, '/\\') : '';
|
|
}
|
|
unset($floor);
|
|
|
|
foreach ($rooms as &$room) {
|
|
$roomPath = trim((string)($room['svg_path'] ?? ''));
|
|
$room['floor_svg_url'] = $roomPath !== '' ? '/' . ltrim($roomPath, '/\\') : '';
|
|
}
|
|
unset($room);
|
|
|
|
$floorIndex = [];
|
|
foreach ($floors as $floor) {
|
|
$floorIndex[$floor['id']] = $floor;
|
|
}
|
|
|
|
$panel = null;
|
|
$outlet = null;
|
|
$pageTitle = $type === 'outlet' ? 'Wandbuchse bearbeiten' : 'Patchpanel bearbeiten';
|
|
|
|
if ($type === 'patchpanel' && $id > 0) {
|
|
$panel = $sql->single(
|
|
"SELECT * FROM floor_patchpanels WHERE id = ?",
|
|
"i",
|
|
[$id]
|
|
);
|
|
if ($panel) {
|
|
$pageTitle = "Patchpanel bearbeiten: " . htmlspecialchars($panel['name']);
|
|
}
|
|
}
|
|
|
|
if ($type === 'outlet' && $id > 0) {
|
|
$outlet = $sql->single(
|
|
"SELECT * FROM network_outlets WHERE id = ?",
|
|
"i",
|
|
[$id]
|
|
);
|
|
if ($outlet) {
|
|
$pageTitle = "Wandbuchse bearbeiten: " . htmlspecialchars($outlet['name']);
|
|
}
|
|
}
|
|
|
|
$panel = $panel ?? [];
|
|
$outlet = $outlet ?? [];
|
|
|
|
$selectedLocationId = 0;
|
|
$selectedBuildingId = 0;
|
|
$selectedFloorId = 0;
|
|
if ($type === 'patchpanel') {
|
|
$selectedFloorId = (int)($panel['floor_id'] ?? 0);
|
|
if ($selectedFloorId && isset($floorIndex[$selectedFloorId])) {
|
|
$selectedBuildingId = (int)($floorIndex[$selectedFloorId]['building_id'] ?? 0);
|
|
$selectedLocationId = (int)($floorIndex[$selectedFloorId]['location_id'] ?? 0);
|
|
}
|
|
}
|
|
|
|
$defaultPanelSize = ['width' => 20, 'height' => 5];
|
|
$defaultOutletSize = 10;
|
|
$showPanelPlacementFields = $type === 'patchpanel' && $selectedFloorId > 0;
|
|
|
|
if ($type === 'patchpanel') {
|
|
$panel['width'] = $defaultPanelSize['width'];
|
|
$panel['height'] = $defaultPanelSize['height'];
|
|
$panel['pos_x'] = $panel['pos_x'] ?? 30;
|
|
$panel['pos_y'] = $panel['pos_y'] ?? 30;
|
|
} else {
|
|
$outlet['x'] = $outlet['x'] ?? 30;
|
|
$outlet['y'] = $outlet['y'] ?? 30;
|
|
}
|
|
|
|
$markerWidth = $type === 'patchpanel' ? $panel['width'] : $defaultOutletSize;
|
|
$markerHeight = $type === 'patchpanel' ? $panel['height'] : $defaultOutletSize;
|
|
|
|
$mapPatchPanels = $sql->get(
|
|
"SELECT id, floor_id, name, pos_x, pos_y, width, height
|
|
FROM floor_patchpanels
|
|
ORDER BY floor_id, name",
|
|
"",
|
|
[]
|
|
);
|
|
$mapOutlets = $sql->get(
|
|
"SELECT o.id, r.floor_id, o.name, o.x, o.y
|
|
FROM network_outlets o
|
|
JOIN rooms r ON r.id = o.room_id
|
|
ORDER BY r.floor_id, o.name",
|
|
"",
|
|
[]
|
|
);
|
|
|
|
$patchpanelPortOptions = $sql->get(
|
|
"SELECT
|
|
fpp.id,
|
|
fpp.name,
|
|
fp.name AS patchpanel_name,
|
|
fp.floor_id,
|
|
f.name AS floor_name,
|
|
EXISTS(
|
|
SELECT 1
|
|
FROM connections c
|
|
WHERE
|
|
((c.port_a_type = 'patchpanel' OR c.port_a_type = 'floor_patchpanel_ports') AND c.port_a_id = fpp.id)
|
|
OR
|
|
((c.port_b_type = 'patchpanel' OR c.port_b_type = 'floor_patchpanel_ports') AND c.port_b_id = fpp.id)
|
|
) AS is_occupied
|
|
FROM floor_patchpanel_ports fpp
|
|
JOIN floor_patchpanels fp ON fp.id = fpp.patchpanel_id
|
|
LEFT JOIN floors f ON f.id = fp.floor_id
|
|
ORDER BY f.name, fp.name, fpp.name",
|
|
"",
|
|
[]
|
|
);
|
|
|
|
$selectedBindPatchpanelPortId = 0;
|
|
if ($type === 'outlet' && $id > 0) {
|
|
$selectedBindPatchpanelPortId = (int)($sql->single(
|
|
"SELECT
|
|
CASE
|
|
WHEN (c.port_a_type = 'patchpanel' OR c.port_a_type = 'floor_patchpanel_ports') THEN c.port_a_id
|
|
WHEN (c.port_b_type = 'patchpanel' OR c.port_b_type = 'floor_patchpanel_ports') THEN c.port_b_id
|
|
ELSE 0
|
|
END AS patchpanel_port_id
|
|
FROM connections c
|
|
JOIN network_outlet_ports nop
|
|
ON (
|
|
((c.port_a_type = 'outlet' OR c.port_a_type = 'network_outlet_ports') AND c.port_a_id = nop.id)
|
|
OR
|
|
((c.port_b_type = 'outlet' OR c.port_b_type = 'network_outlet_ports') AND c.port_b_id = nop.id)
|
|
)
|
|
WHERE nop.outlet_id = ?
|
|
AND (
|
|
c.port_a_type = 'patchpanel' OR c.port_a_type = 'floor_patchpanel_ports'
|
|
OR
|
|
c.port_b_type = 'patchpanel' OR c.port_b_type = 'floor_patchpanel_ports'
|
|
)
|
|
ORDER BY c.id
|
|
LIMIT 1",
|
|
"i",
|
|
[$id]
|
|
)['patchpanel_port_id'] ?? 0);
|
|
}
|
|
?>
|
|
|
|
<div class="floor-infra-edit">
|
|
<link rel="stylesheet" href="/assets/css/floor-infrastructure-edit.css">
|
|
<h1><?php echo $pageTitle; ?></h1>
|
|
|
|
<form method="post" action="?module=floor_infrastructure&action=save" class="infra-edit-form">
|
|
<input type="hidden" name="type" value="<?php echo htmlspecialchars($type); ?>">
|
|
<input type="hidden" name="id" value="<?php echo $type === 'patchpanel' ? ($panel['id'] ?? $id) : ($outlet['id'] ?? $id); ?>">
|
|
|
|
<?php if ($type === 'patchpanel'): ?>
|
|
<div class="form-group">
|
|
<label>Name</label>
|
|
<input type="text" name="name" value="<?php echo htmlspecialchars($panel['name'] ?? ''); ?>" required>
|
|
</div>
|
|
|
|
<div class="form-grid">
|
|
<div class="form-group">
|
|
<label>Location</label>
|
|
<select id="panel-location-select">
|
|
<option value="">- Location wählen -</option>
|
|
<?php foreach ($locations as $location): ?>
|
|
<option value="<?php echo $location['id']; ?>" <?php echo $selectedLocationId === $location['id'] ? 'selected' : ''; ?>>
|
|
<?php echo htmlspecialchars($location['name']); ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Gebäude</label>
|
|
<select id="panel-building-select">
|
|
<option value="">- Gebäude wählen -</option>
|
|
<?php foreach ($buildings as $building): ?>
|
|
<?php $buildingLocation = $locationMap[$building['location_id']] ?? ''; ?>
|
|
<option value="<?php echo $building['id']; ?>"
|
|
data-location-id="<?php echo $building['location_id'] ?? 0; ?>"
|
|
<?php echo $selectedBuildingId === $building['id'] ? 'selected' : ''; ?>>
|
|
<?php echo htmlspecialchars($building['name'] . ($buildingLocation ? ' · ' . $buildingLocation : '')); ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Stockwerk</label>
|
|
<select id="panel-floor-select" name="floor_id" required>
|
|
<option value="">- Stockwerk wählen -</option>
|
|
<?php foreach ($floors as $floor): ?>
|
|
<option value="<?php echo $floor['id']; ?>"
|
|
data-building-id="<?php echo $floor['building_id'] ?? 0; ?>"
|
|
data-location-id="<?php echo $floor['location_id'] ?? 0; ?>"
|
|
data-svg-url="<?php echo htmlspecialchars($floor['svg_url']); ?>"
|
|
<?php echo $selectedFloorId === $floor['id'] ? 'selected' : ''; ?>>
|
|
<?php echo htmlspecialchars($floor['name']); ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<input type="hidden" name="pos_x" value="<?php echo (int)($panel['pos_x'] ?? 30); ?>">
|
|
<input type="hidden" name="pos_y" value="<?php echo (int)($panel['pos_y'] ?? 30); ?>">
|
|
<input type="hidden" name="width" value="<?php echo (int)$panel['width']; ?>">
|
|
<input type="hidden" name="height" value="<?php echo (int)$panel['height']; ?>">
|
|
|
|
<div id="panel-placement-fields" class="form-grid" <?php echo $showPanelPlacementFields ? '' : 'hidden'; ?>>
|
|
<p class="info">Position, Breite und Höhe werden über die Karte gesetzt.</p>
|
|
</div>
|
|
|
|
<div id="panel-floor-plan-group" class="form-group" <?php echo $showPanelPlacementFields ? '' : 'hidden'; ?>>
|
|
<label>Stockwerkskarte</label>
|
|
<div class="floor-plan-block">
|
|
<div class="floor-plan-toolbar">
|
|
<button type="button" class="button button-small" data-floor-plan-zoom="in">+</button>
|
|
<button type="button" class="button button-small" data-floor-plan-zoom="out">-</button>
|
|
<button type="button" class="button button-small" data-floor-plan-zoom="reset">Reset</button>
|
|
</div>
|
|
<div id="floor-plan-canvas" class="floor-plan-canvas"
|
|
data-marker-width="<?php echo $markerWidth; ?>"
|
|
data-marker-height="<?php echo $markerHeight; ?>"
|
|
data-marker-type="patchpanel"
|
|
data-x-field="pos_x"
|
|
data-y-field="pos_y"
|
|
data-active-id="<?php echo (int)($panel['id'] ?? 0); ?>"
|
|
data-reference-panels="<?php echo htmlspecialchars(json_encode($mapPatchPanels, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), ENT_QUOTES, 'UTF-8'); ?>"
|
|
data-reference-outlets="<?php echo htmlspecialchars(json_encode($mapOutlets, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), ENT_QUOTES, 'UTF-8'); ?>">
|
|
<img id="floor-plan-svg" class="floor-plan-svg" alt="Stockwerksplan">
|
|
<svg id="floor-plan-overlay" class="floor-plan-overlay" viewBox="0 0 1 1" preserveAspectRatio="xMidYMid meet" aria-hidden="true"></svg>
|
|
</div>
|
|
<p class="floor-plan-hint">Nur das aktuell bearbeitete Patchpanel ist verschiebbar. Andere Objekte werden als Referenz halbtransparent angezeigt. Neue Objekte starten bei Position 30 x 30. Zoom per Mausrad, verschieben mit Shift + Drag.</p>
|
|
<p class="floor-plan-position">Koordinate: <span id="floor-plan-position"></span></p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>Port-Anzahl</label>
|
|
<input type="number" name="port_count" value="<?php echo $panel['port_count'] ?? 0; ?>" min="0">
|
|
</div>
|
|
|
|
<p id="panel-floor-missing-hint" class="info" <?php echo $showPanelPlacementFields ? 'hidden' : ''; ?>>
|
|
Position, Größe und Stockwerkskarte werden erst angezeigt, sobald ein Stockwerk ausgewählt ist.
|
|
</p>
|
|
|
|
<div class="form-group">
|
|
<label>Kommentar</label>
|
|
<textarea name="comment"><?php echo htmlspecialchars($panel['comment'] ?? ''); ?></textarea>
|
|
</div>
|
|
|
|
<p class="info">Position und Größe folgen dem Drag-&-Drop auf dem Plan, damit alle Patchpanels einheitlich bleiben.</p>
|
|
|
|
<?php else: ?>
|
|
<div class="form-group">
|
|
<label>Name</label>
|
|
<input type="text" name="name" value="<?php echo htmlspecialchars($outlet['name'] ?? ''); ?>" required>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>Raum</label>
|
|
<select name="room_id" id="outlet-room-select" required>
|
|
<option value="">- Raum wählen -</option>
|
|
<?php foreach ($rooms as $room): ?>
|
|
<option value="<?php echo $room['id']; ?>"
|
|
data-floor-id="<?php echo $room['floor_id'] ?? 0; ?>"
|
|
data-floor-svg-url="<?php echo htmlspecialchars($room['floor_svg_url']); ?>"
|
|
data-room-x="<?php echo (int)($room['x'] ?? 0); ?>"
|
|
data-room-y="<?php echo (int)($room['y'] ?? 0); ?>"
|
|
data-room-width="<?php echo (int)($room['width'] ?? 0); ?>"
|
|
data-room-height="<?php echo (int)($room['height'] ?? 0); ?>"
|
|
data-room-polygon="<?php echo htmlspecialchars((string)($room['polygon_points'] ?? ''), ENT_QUOTES, 'UTF-8'); ?>"
|
|
<?php echo ($outlet['room_id'] ?? 0) === $room['id'] ? 'selected' : ''; ?>>
|
|
<?php echo htmlspecialchars($room['floor_name'] . ' / ' . $room['name']); ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="outlet-bind-patchpanel-port-id">Direkt mit Patchpanel-Port verbinden</label>
|
|
<select name="bind_patchpanel_port_id" id="outlet-bind-patchpanel-port-id">
|
|
<option value="">- Kein direkter Link -</option>
|
|
<?php foreach ($patchpanelPortOptions as $portOption): ?>
|
|
<?php
|
|
$portId = (int)($portOption['id'] ?? 0);
|
|
$isSelected = $selectedBindPatchpanelPortId === $portId;
|
|
$isOccupied = ((int)($portOption['is_occupied'] ?? 0) === 1);
|
|
$isDisabled = $isOccupied && !$isSelected;
|
|
$labelParts = array_filter([
|
|
(string)($portOption['floor_name'] ?? ''),
|
|
(string)($portOption['patchpanel_name'] ?? ''),
|
|
(string)($portOption['name'] ?? ''),
|
|
]);
|
|
$label = implode(' / ', $labelParts);
|
|
if ($isOccupied && !$isSelected) {
|
|
$label .= ' (belegt)';
|
|
}
|
|
?>
|
|
<option
|
|
value="<?php echo $portId; ?>"
|
|
data-floor-id="<?php echo (int)($portOption['floor_id'] ?? 0); ?>"
|
|
<?php echo $isSelected ? 'selected' : ''; ?>
|
|
<?php echo $isDisabled ? 'disabled' : ''; ?>>
|
|
<?php echo htmlspecialchars($label); ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
<small>Nur Ports vom gewaehlten Stockwerk sind auswaehlbar. Beim Speichern wird die Verbindung automatisch erstellt.</small>
|
|
</div>
|
|
|
|
<input type="hidden" name="x" value="<?php echo (int)($outlet['x'] ?? 30); ?>">
|
|
<input type="hidden" name="y" value="<?php echo (int)($outlet['y'] ?? 30); ?>">
|
|
|
|
<div class="form-group">
|
|
<label>Stockwerkskarte</label>
|
|
<div class="floor-plan-block">
|
|
<div class="floor-plan-toolbar">
|
|
<button type="button" class="button button-small" data-floor-plan-zoom="in">+</button>
|
|
<button type="button" class="button button-small" data-floor-plan-zoom="out">-</button>
|
|
<button type="button" class="button button-small" data-floor-plan-zoom="reset">Reset</button>
|
|
</div>
|
|
<div id="floor-plan-canvas" class="floor-plan-canvas"
|
|
data-marker-width="<?php echo $markerWidth; ?>"
|
|
data-marker-height="<?php echo $markerHeight; ?>"
|
|
data-marker-type="outlet"
|
|
data-x-field="x"
|
|
data-y-field="y"
|
|
data-active-id="<?php echo (int)($outlet['id'] ?? 0); ?>"
|
|
data-reference-panels="<?php echo htmlspecialchars(json_encode($mapPatchPanels, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), ENT_QUOTES, 'UTF-8'); ?>"
|
|
data-reference-outlets="<?php echo htmlspecialchars(json_encode($mapOutlets, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), ENT_QUOTES, 'UTF-8'); ?>">
|
|
<img id="floor-plan-svg" class="floor-plan-svg" alt="Stockwerksplan">
|
|
<svg id="floor-plan-overlay" class="floor-plan-overlay" viewBox="0 0 1 1" preserveAspectRatio="xMidYMid meet" aria-hidden="true"></svg>
|
|
</div>
|
|
<p class="floor-plan-hint">Nur die aktuell bearbeitete Wandbuchse ist verschiebbar. Blau = Patchpanel, Gruen = Dosen-Referenz, Orange = gewaehlter Raum. Netzwerkdosen sind immer 10 x 10. Zoom per Mausrad, verschieben mit Shift + Drag.</p>
|
|
<p class="floor-plan-position">Koordinate: <span id="floor-plan-position"></span></p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>Kommentar</label>
|
|
<textarea name="comment"><?php echo htmlspecialchars($outlet['comment'] ?? ''); ?></textarea>
|
|
</div>
|
|
|
|
<p class="info">Wandbuchsen bleiben quadratisch, ihre Position wird über die Karte festgelegt.</p>
|
|
<?php endif; ?>
|
|
|
|
<div class="form-actions">
|
|
<button type="submit" class="button button-primary">Speichern</button>
|
|
<a href="?module=floor_infrastructure&action=list" class="button">Abbrechen</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<script src="/assets/js/floor-infrastructure-edit.js" defer></script>
|
|
|
|
|
|
|
|
|