Nur das aktiv bearbeitete Objekt ist verschiebbar. Alle anderen Objekte auf derselben Etage werden als halbtransparente Referenz angezeigt. In der Listenansicht sind Marker nur noch Vorschau (nicht verschiebbar). Geänderte Dateien: edit.php Referenzdaten für Patchpanels und Wandbuchsen geladen. Diese Daten als data-* am Canvas hinterlegt. Hinweise im UI angepasst. Styles ergänzt: .floor-plan-marker.is-active .floor-plan-reference (+ Varianten für Panel/Outlet) Inline-Script entfernt und auf externe JS-Datei umgestellt (CSP-sicher): <script src="/assets/js/floor-infrastructure-edit.js" defer></script> floor-infrastructure-edit.js (neu) Drag/Drop nur für den aktiven Marker. Referenzmarker je Stockwerk rendern (halbtransparent, nicht interaktiv). Aktiven Marker aus Referenzmenge ausschließen. Referenzen bei Stockwerk-/Raumwechsel neu rendern. list.php Kartenansicht auf reine Vorschau umgestellt. Drag/Save-Logik entfernt. Marker per CSS nicht interaktiv (pointer-events: none, cursor: default). Hinweistext entsprechend angepasst.
346 lines
11 KiB
PHP
346 lines
11 KiB
PHP
<?php
|
|
/**
|
|
* app/modules/floor_infrastructure/list.php
|
|
*
|
|
* Übersicht über Patchpanels und Netzwerkbuchsen auf Stockwerken
|
|
*/
|
|
|
|
$floorId = (int)($_GET['floor_id'] ?? 0);
|
|
|
|
$floors = $sql->get("SELECT id, name, svg_path FROM floors ORDER BY name", "", []);
|
|
|
|
$where = '';
|
|
$types = '';
|
|
$params = [];
|
|
|
|
if ($floorId > 0) {
|
|
$where = "WHERE p.floor_id = ?";
|
|
$types = "i";
|
|
$params[] = $floorId;
|
|
}
|
|
|
|
$patchPanels = $sql->get(
|
|
"SELECT p.*, f.name AS floor_name
|
|
FROM floor_patchpanels p
|
|
LEFT JOIN floors f ON f.id = p.floor_id
|
|
$where
|
|
ORDER BY f.name, p.name",
|
|
$types,
|
|
$params
|
|
);
|
|
|
|
$networkOutlets = $sql->get(
|
|
"SELECT o.*, r.name AS room_name, f.name AS floor_name, f.id AS floor_id
|
|
FROM network_outlets o
|
|
LEFT JOIN rooms r ON r.id = o.room_id
|
|
LEFT JOIN floors f ON f.id = r.floor_id
|
|
ORDER BY f.name, r.name, o.name",
|
|
"",
|
|
[]
|
|
);
|
|
|
|
$floorMap = [];
|
|
foreach ($floors as $floor) {
|
|
$id = (int)$floor['id'];
|
|
$svgPath = trim((string)($floor['svg_path'] ?? ''));
|
|
$floorMap[$id] = [
|
|
'id' => $id,
|
|
'name' => (string)($floor['name'] ?? ''),
|
|
'svg_url' => $svgPath !== '' ? '/' . ltrim($svgPath, '/\\') : ''
|
|
];
|
|
}
|
|
|
|
$editorFloorId = $floorId;
|
|
if ($editorFloorId <= 0) {
|
|
foreach ($floorMap as $candidate) {
|
|
if ($candidate['svg_url'] !== '') {
|
|
$editorFloorId = (int)$candidate['id'];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
$editorFloor = $floorMap[$editorFloorId] ?? null;
|
|
$editorPatchPanels = [];
|
|
$editorOutlets = [];
|
|
|
|
if ($editorFloorId > 0) {
|
|
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'] ?? '')
|
|
];
|
|
}
|
|
}
|
|
|
|
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'] ?? '')
|
|
];
|
|
}
|
|
}
|
|
}
|
|
?>
|
|
|
|
<div class="floor-infra">
|
|
<h1>Stockwerksinfrastruktur</h1>
|
|
|
|
<div class="toolbar">
|
|
<a href="?module=floor_infrastructure&action=edit&type=patchpanel" class="button button-primary">
|
|
+ Patchpanel hinzufügen
|
|
</a>
|
|
<a href="?module=floor_infrastructure&action=edit&type=outlet" class="button">
|
|
+ Wandbuchse hinzufügen
|
|
</a>
|
|
</div>
|
|
|
|
<form method="get" class="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>
|
|
<?php foreach ($floors as $floor): ?>
|
|
<option value="<?php echo $floor['id']; ?>" <?php echo $floor['id'] === $floorId ? 'selected' : ''; ?>>
|
|
<?php echo htmlspecialchars($floor['name']); ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
|
|
<button class="button">Filter</button>
|
|
<a href="?module=floor_infrastructure&action=list" class="button">Zurücksetzen</a>
|
|
</form>
|
|
|
|
<section class="infra-section">
|
|
<h2>Patchpanels</h2>
|
|
<?php if (!empty($patchPanels)): ?>
|
|
<table class="infra-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Name</th>
|
|
<th>Stockwerk</th>
|
|
<th>Position</th>
|
|
<th>Größe</th>
|
|
<th>Ports</th>
|
|
<th>Aktionen</th>
|
|
</tr>
|
|
</thead>
|
|
<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 class="actions">
|
|
<a href="?module=floor_infrastructure&action=edit&type=patchpanel&id=<?php echo $panel['id']; ?>" class="button button-small">Bearbeiten</a>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
<?php else: ?>
|
|
<p class="empty-state">Noch keine Patchpanels definiert.</p>
|
|
<?php endif; ?>
|
|
</section>
|
|
|
|
<section class="infra-section">
|
|
<h2>Wandbuchsen</h2>
|
|
<?php if (!empty($networkOutlets)): ?>
|
|
<table class="infra-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Name</th>
|
|
<th>Stockwerk</th>
|
|
<th>Raum</th>
|
|
<th>Koordinaten</th>
|
|
<th>Kommentar</th>
|
|
<th>Aktionen</th>
|
|
</tr>
|
|
</thead>
|
|
<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 class="actions">
|
|
<a href="?module=floor_infrastructure&action=edit&type=outlet&id=<?php echo $outlet['id']; ?>" class="button button-small">Bearbeiten</a>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
<?php else: ?>
|
|
<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: 32px;
|
|
height: 32px;
|
|
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 = 32;
|
|
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; ?>
|