infrastruktur karte

This commit is contained in:
2026-02-16 11:57:24 +01:00
parent d1d89dd4e3
commit 12141485ae
7 changed files with 752 additions and 422 deletions

View File

@@ -2,12 +2,18 @@
/**
* app/modules/floor_infrastructure/list.php
*
* Übersicht über Patchpanels und Netzwerkbuchsen auf Stockwerken
* Uebersicht ueber Patchpanels und Netzwerkbuchsen auf Stockwerken.
*/
$floorId = (int)($_GET['floor_id'] ?? 0);
$floors = $sql->get("SELECT id, name, svg_path FROM floors ORDER BY name", "", []);
$floors = $sql->get(
"SELECT id, name, svg_path
FROM floors
ORDER BY name",
"",
[]
);
$where = '';
$types = '';
@@ -15,7 +21,7 @@ $params = [];
if ($floorId > 0) {
$where = "WHERE p.floor_id = ?";
$types = "i";
$types = 'i';
$params[] = $floorId;
}
@@ -30,10 +36,15 @@ $patchPanels = $sql->get(
);
$networkOutlets = $sql->get(
"SELECT o.*, r.name AS room_name, f.name AS floor_name, f.id AS floor_id
"SELECT o.id, o.room_id, o.name, o.x, o.y, o.comment,
r.name AS room_name, r.number AS room_number,
f.name AS floor_name, f.id AS floor_id,
GROUP_CONCAT(nop.name ORDER BY nop.name SEPARATOR ', ') AS port_names
FROM network_outlets o
LEFT JOIN rooms r ON r.id = o.room_id
LEFT JOIN floors f ON f.id = r.floor_id
LEFT JOIN network_outlet_ports nop ON nop.outlet_id = o.id
GROUP BY o.id
ORDER BY f.name, r.name, o.name",
"",
[]
@@ -50,81 +61,101 @@ foreach ($floors as $floor) {
];
}
$editorFloorId = $floorId;
if ($editorFloorId <= 0) {
foreach ($floorMap as $candidate) {
if ($candidate['svg_url'] !== '') {
$editorFloorId = (int)$candidate['id'];
break;
}
}
}
$editorFloor = $floorMap[$editorFloorId] ?? null;
$editorFloor = ($floorId > 0 && isset($floorMap[$floorId])) ? $floorMap[$floorId] : null;
$editorPatchPanels = [];
$editorOutlets = [];
if ($editorFloorId > 0) {
if ($editorFloor) {
foreach ($patchPanels as $panel) {
if ((int)$panel['floor_id'] === $editorFloorId) {
$editorPatchPanels[] = [
'id' => (int)$panel['id'],
'name' => (string)$panel['name'],
'floor_id' => (int)$panel['floor_id'],
'x' => (int)$panel['pos_x'],
'y' => (int)$panel['pos_y'],
'width' => max(1, (int)$panel['width']),
'height' => max(1, (int)$panel['height']),
'port_count' => (int)$panel['port_count'],
'comment' => (string)($panel['comment'] ?? '')
];
if ((int)$panel['floor_id'] !== $floorId) {
continue;
}
$editorPatchPanels[] = [
'id' => (int)$panel['id'],
'name' => (string)$panel['name'],
'x' => (int)$panel['pos_x'],
'y' => (int)$panel['pos_y'],
'width' => max(1, (int)$panel['width']),
'height' => max(1, (int)$panel['height']),
'port_count' => (int)$panel['port_count'],
'comment' => (string)($panel['comment'] ?? '')
];
}
foreach ($networkOutlets as $outlet) {
if ((int)$outlet['floor_id'] === $editorFloorId) {
$editorOutlets[] = [
'id' => (int)$outlet['id'],
'name' => (string)$outlet['name'],
'room_id' => (int)$outlet['room_id'],
'x' => (int)$outlet['x'],
'y' => (int)$outlet['y'],
'comment' => (string)($outlet['comment'] ?? '')
];
if ((int)$outlet['floor_id'] !== $floorId) {
continue;
}
$editorOutlets[] = [
'id' => (int)$outlet['id'],
'name' => (string)$outlet['name'],
'x' => (int)$outlet['x'],
'y' => (int)$outlet['y'],
'room_name' => (string)($outlet['room_name'] ?? ''),
'room_number' => (string)($outlet['room_number'] ?? ''),
'port_names' => (string)($outlet['port_names'] ?? ''),
'comment' => (string)($outlet['comment'] ?? '')
];
}
}
?>
<div class="floor-infra">
<link rel="stylesheet" href="/assets/css/floor-infrastructure-list.css">
<script src="/assets/js/floor-infrastructure-list.js" defer></script>
<h1>Stockwerksinfrastruktur</h1>
<div class="toolbar">
<a href="?module=floor_infrastructure&action=edit&type=patchpanel" class="button button-primary">
+ Patchpanel hinzufügen
+ Patchpanel hinzufuegen
</a>
<a href="?module=floor_infrastructure&action=edit&type=outlet" class="button">
+ Wandbuchse hinzufügen
+ Wandbuchse hinzufuegen
</a>
</div>
<form method="get" class="filter-form">
<form method="get" class="filter-form" id="infra-filter-form">
<input type="hidden" name="module" value="floor_infrastructure">
<input type="hidden" name="action" value="list">
<select name="floor_id">
<option value="">- Alle Stockwerke -</option>
<select name="floor_id" id="infra-floor-select">
<option value="">- Stockwerk waehlen -</option>
<?php foreach ($floors as $floor): ?>
<option value="<?php echo $floor['id']; ?>" <?php echo $floor['id'] === $floorId ? 'selected' : ''; ?>>
<?php echo htmlspecialchars($floor['name']); ?>
<option value="<?php echo (int)$floor['id']; ?>" <?php echo ((int)$floor['id'] === $floorId) ? 'selected' : ''; ?>>
<?php echo htmlspecialchars((string)$floor['name']); ?>
</option>
<?php endforeach; ?>
</select>
<button class="button">Filter</button>
<a href="?module=floor_infrastructure&action=list" class="button">Zurücksetzen</a>
<button class="button" type="submit">Filter</button>
<a href="?module=floor_infrastructure&action=list" class="button">Zuruecksetzen</a>
</form>
<section class="infra-plan">
<h2>Stockwerkskarte</h2>
<?php if ($floorId <= 0): ?>
<p class="empty-state">Bitte ein Stockwerk auswaehlen, um die Karte anzuzeigen.</p>
<?php elseif (!$editorFloor): ?>
<p class="empty-state">Gewaehltes Stockwerk wurde nicht gefunden.</p>
<?php elseif (($editorFloor['svg_url'] ?? '') === ''): ?>
<p class="empty-state">Das gewaehlte Stockwerk hat keinen SVG-Plan hinterlegt.</p>
<?php else: ?>
<p>
Read-only Vorschau fuer <strong><?php echo htmlspecialchars((string)$editorFloor['name']); ?></strong>.
Alle Objekte werden angezeigt; Hover zeigt Details.
</p>
<div id="infra-floor-canvas"
class="infra-floor-canvas"
data-patchpanels="<?php echo htmlspecialchars(json_encode($editorPatchPanels, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), ENT_QUOTES, 'UTF-8'); ?>"
data-outlets="<?php echo htmlspecialchars(json_encode($editorOutlets, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), ENT_QUOTES, 'UTF-8'); ?>">
<img src="<?php echo htmlspecialchars((string)$editorFloor['svg_url']); ?>" class="infra-floor-svg" alt="Stockwerksplan">
<svg id="infra-floor-overlay" class="infra-floor-overlay" viewBox="0 0 1 1" preserveAspectRatio="xMidYMid meet" aria-hidden="true"></svg>
</div>
<p class="floor-plan-hint">Blau: Patchpanel | Gruen: Wandbuchse. Hover zeigt Name, Raum und Ports (Browser-Tooltip).</p>
<?php endif; ?>
</section>
<section class="infra-section">
<h2>Patchpanels</h2>
<?php if (!empty($patchPanels)): ?>
@@ -134,7 +165,7 @@ if ($editorFloorId > 0) {
<th>Name</th>
<th>Stockwerk</th>
<th>Position</th>
<th>Größe</th>
<th>Groesse</th>
<th>Ports</th>
<th>Aktionen</th>
</tr>
@@ -142,13 +173,13 @@ if ($editorFloorId > 0) {
<tbody>
<?php foreach ($patchPanels as $panel): ?>
<tr>
<td><?php echo htmlspecialchars($panel['name']); ?></td>
<td><?php echo htmlspecialchars($panel['floor_name'] ?? '-'); ?></td>
<td><?php echo $panel['pos_x'] . ' x ' . $panel['pos_y']; ?></td>
<td><?php echo $panel['width'] . ' x ' . $panel['height']; ?></td>
<td><?php echo $panel['port_count']; ?></td>
<td><?php echo htmlspecialchars((string)$panel['name']); ?></td>
<td><?php echo htmlspecialchars((string)($panel['floor_name'] ?? '-')); ?></td>
<td><?php echo (int)$panel['pos_x'] . ' x ' . (int)$panel['pos_y']; ?></td>
<td><?php echo (int)$panel['width'] . ' x ' . (int)$panel['height']; ?></td>
<td><?php echo (int)$panel['port_count']; ?></td>
<td class="actions">
<a href="?module=floor_infrastructure&action=edit&type=patchpanel&id=<?php echo $panel['id']; ?>" class="button button-small">Bearbeiten</a>
<a href="?module=floor_infrastructure&action=edit&type=patchpanel&id=<?php echo (int)$panel['id']; ?>" class="button button-small">Bearbeiten</a>
</td>
</tr>
<?php endforeach; ?>
@@ -169,6 +200,7 @@ if ($editorFloorId > 0) {
<th>Stockwerk</th>
<th>Raum</th>
<th>Koordinaten</th>
<th>Ports</th>
<th>Kommentar</th>
<th>Aktionen</th>
</tr>
@@ -176,13 +208,23 @@ if ($editorFloorId > 0) {
<tbody>
<?php foreach ($networkOutlets as $outlet): ?>
<tr>
<td><?php echo htmlspecialchars($outlet['name']); ?></td>
<td><?php echo htmlspecialchars($outlet['floor_name'] ?? '-'); ?></td>
<td><?php echo htmlspecialchars($outlet['room_name'] ?? '-'); ?></td>
<td><?php echo $outlet['x'] . ' x ' . $outlet['y']; ?></td>
<td><?php echo htmlspecialchars($outlet['comment']); ?></td>
<td><?php echo htmlspecialchars((string)$outlet['name']); ?></td>
<td><?php echo htmlspecialchars((string)($outlet['floor_name'] ?? '-')); ?></td>
<td>
<?php
$roomLabel = (string)($outlet['room_name'] ?? '-');
$roomNumber = trim((string)($outlet['room_number'] ?? ''));
if ($roomNumber !== '') {
$roomLabel .= ' (' . $roomNumber . ')';
}
echo htmlspecialchars($roomLabel);
?>
</td>
<td><?php echo (int)$outlet['x'] . ' x ' . (int)$outlet['y']; ?></td>
<td><?php echo htmlspecialchars((string)($outlet['port_names'] ?? '-')); ?></td>
<td><?php echo htmlspecialchars((string)($outlet['comment'] ?? '')); ?></td>
<td class="actions">
<a href="?module=floor_infrastructure&action=edit&type=outlet&id=<?php echo $outlet['id']; ?>" class="button button-small">Bearbeiten</a>
<a href="?module=floor_infrastructure&action=edit&type=outlet&id=<?php echo (int)$outlet['id']; ?>" class="button button-small">Bearbeiten</a>
</td>
</tr>
<?php endforeach; ?>
@@ -192,154 +234,4 @@ if ($editorFloorId > 0) {
<p class="empty-state">Noch keine Wandbuchsen angelegt.</p>
<?php endif; ?>
</section>
<section class="infra-plan">
<h2>Stockwerksplan-Verortung</h2>
<?php if (!$editorFloor): ?>
<p class="empty-state">Kein Stockwerk für die Planansicht verfügbar.</p>
<?php elseif (($editorFloor['svg_url'] ?? '') === ''): ?>
<p class="empty-state">Das gewählte Stockwerk hat noch keinen SVG-Plan hinterlegt.</p>
<?php else: ?>
<p>Vorschau für <strong><?php echo htmlspecialchars($editorFloor['name']); ?></strong>. Positionen bearbeitest du über den jeweiligen „Bearbeiten“-Button.</p>
<div id="infra-floor-canvas"
class="infra-floor-canvas"
data-save-url="?module=floor_infrastructure&action=save"
data-floor-id="<?php echo (int)$editorFloor['id']; ?>">
<img src="<?php echo htmlspecialchars($editorFloor['svg_url']); ?>" class="infra-floor-svg" alt="Stockwerksplan">
</div>
<p class="floor-plan-hint">Patchpanels: blau, Wandbuchsen: grün. In dieser Ansicht sind Marker nicht verschiebbar.</p>
<?php endif; ?>
</section>
</div>
<style>
.floor-infra {
padding: 25px;
}
.toolbar {
display: flex;
gap: 10px;
margin-bottom: 15px;
}
.filter-form {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
margin-bottom: 25px;
}
.filter-form select {
padding: 8px 10px;
border-radius: 4px;
border: 1px solid #ccc;
}
.infra-section {
margin-bottom: 30px;
}
.infra-table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}
.infra-table th,
.infra-table td {
padding: 10px;
border-bottom: 1px solid #ddd;
}
.infra-plan {
padding: 15px;
background: #f7f7f7;
border: 1px dashed #ccc;
border-radius: 6px;
}
.infra-floor-canvas {
position: relative;
margin-top: 12px;
width: 100%;
min-height: 420px;
border: 1px solid #d4d4d4;
border-radius: 8px;
background: #fff;
overflow: hidden;
}
.infra-floor-svg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: contain;
pointer-events: none;
opacity: 0.85;
}
.infra-marker {
position: absolute;
top: 0;
left: 0;
user-select: none;
touch-action: none;
cursor: default;
z-index: 2;
pointer-events: none;
}
.infra-marker.patchpanel {
background: rgba(13, 110, 253, 0.25);
border: 2px solid #0d6efd;
border-radius: 6px;
}
.infra-marker.outlet {
width: 10px;
height: 10px;
background: rgba(25, 135, 84, 0.25);
border: 2px solid #198754;
border-radius: 4px;
}
.floor-plan-hint {
margin: 8px 0 0;
font-size: 0.85em;
color: #444;
}
.empty-state {
padding: 20px;
background: #fafafa;
border: 1px dashed #ccc;
border-radius: 6px;
}
.actions .button-small {
margin-right: 6px;
}
</style>
<?php if ($editorFloor && ($editorFloor['svg_url'] ?? '') !== ''): ?>
<script>
document.addEventListener('DOMContentLoaded', () => {
const canvas = document.getElementById('infra-floor-canvas');
if (!canvas) {
return;
}
const outletSize = 10;
const patchPanels = <?php echo json_encode($editorPatchPanels, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>;
const outlets = <?php echo json_encode($editorOutlets, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>;
const createMarker = (entry, type) => {
const marker = document.createElement('div');
marker.className = `infra-marker ${type}`;
marker.dataset.id = String(entry.id);
marker.dataset.type = type;
marker.title = entry.name || '';
if (type === 'patchpanel') {
marker.style.width = `${entry.width}px`;
marker.style.height = `${entry.height}px`;
}
marker.style.left = `${entry.x}px`;
marker.style.top = `${entry.y}px`;
canvas.appendChild(marker);
};
patchPanels.forEach((entry) => createMarker(entry, 'patchpanel'));
outlets.forEach((entry) => createMarker(entry, 'outlet'));
});
</script>
<?php endif; ?>