Files
netwatch/app/modules/floor_infrastructure/edit.php
fixclean 3bc5a2ca04 Verhalten im Infrastruktur-Modul ist jetzt wie gewünscht:
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.
2026-02-16 10:04:12 +01:00

423 lines
16 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, 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' => 140, 'height' => 40];
$defaultOutletSize = 32;
$showPanelPlacementFields = $type === 'patchpanel' && $selectedFloorId > 0;
if ($type === 'patchpanel') {
$panel['width'] = $panel['width'] ?? $defaultPanelSize['width'];
$panel['height'] = $panel['height'] ?? $defaultPanelSize['height'];
}
$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",
"",
[]
);
?>
<div class="floor-infra-edit">
<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>
<div id="panel-placement-fields" class="form-grid" <?php echo $showPanelPlacementFields ? '' : 'hidden'; ?>>
<div class="form-group">
<label>X</label>
<input type="number" name="pos_x" value="<?php echo $panel['pos_x'] ?? 0; ?>">
</div>
<div class="form-group">
<label>Y</label>
<input type="number" name="pos_y" value="<?php echo $panel['pos_y'] ?? 0; ?>">
</div>
<div class="form-group">
<label>Breite</label>
<input type="number" name="width" value="<?php echo $panel['width']; ?>" readonly title="Breite wird automatisch nach Standardwerten vorgegeben.">
</div>
<div class="form-group">
<label>Höhe</label>
<input type="number" name="height" value="<?php echo $panel['height']; ?>" readonly title="Höhe wird automatisch nach Standardwerten vorgegeben.">
</div>
</div>
<div id="panel-floor-plan-group" class="form-group" <?php echo $showPanelPlacementFields ? '' : 'hidden'; ?>>
<label>Stockwerkskarte</label>
<div class="floor-plan-block">
<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">
<div id="floor-plan-marker" class="floor-plan-marker panel-marker"
style="--marker-width: <?php echo $markerWidth; ?>px; --marker-height: <?php echo $markerHeight; ?>px;"></div>
</div>
<p class="floor-plan-hint">Nur das aktuell bearbeitete Patchpanel ist verschiebbar. Andere Objekte werden als Referenz halbtransparent angezeigt.</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']); ?>"
<?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>X</label>
<input type="number" name="x" value="<?php echo $outlet['x'] ?? 0; ?>">
</div>
<div class="form-group">
<label>Y</label>
<input type="number" name="y" value="<?php echo $outlet['y'] ?? 0; ?>">
</div>
<div class="form-group">
<label>Stockwerkskarte</label>
<div class="floor-plan-block">
<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">
<div id="floor-plan-marker" class="floor-plan-marker outlet-marker"
style="--marker-width: <?php echo $markerWidth; ?>px; --marker-height: <?php echo $markerHeight; ?>px;"></div>
</div>
<p class="floor-plan-hint">Nur die aktuell bearbeitete Wandbuchse ist verschiebbar. Andere Objekte werden als Referenz halbtransparent angezeigt.</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>
<?php
//TODO drag an drop auf der stockwerkskarte für die patchfelder und wandbuchsen. buchsen haben eine einheitliche größe, und sind quadratisch, patchfelder sind auch für sich einheitlich, sind rechteckig und breiter als hoch
//TODO style in css files einsortieren
?>
<style>
.floor-infra-edit {
padding: 25px;
max-width: 700px;
}
.infra-edit-form {
display: flex;
flex-direction: column;
gap: 15px;
}
.form-group {
display: flex;
flex-direction: column;
gap: 6px;
}
.form-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 12px;
}
.form-actions {
display: flex;
gap: 10px;
}
.info {
font-size: 0.9em;
color: #555;
}
.floor-plan-block {
display: flex;
flex-direction: column;
gap: 6px;
}
.floor-plan-canvas {
position: relative;
width: 100%;
min-height: 260px;
border: 1px solid #d4d4d4;
border-radius: 8px;
background-color: #fff;
background-image:
linear-gradient(90deg, rgba(0, 0, 0, 0.05) 1px, transparent 1px),
linear-gradient(rgba(0, 0, 0, 0.05) 1px, transparent 1px);
background-size: 40px 40px;
cursor: crosshair;
overflow: hidden;
}
.floor-plan-svg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: contain;
pointer-events: none;
z-index: 0;
opacity: 0.75;
border-radius: 6px;
}
.floor-plan-marker {
position: absolute;
top: 0;
left: 0;
width: var(--marker-width, 32px);
height: var(--marker-height, 32px);
transition: left 0.1s ease, top 0.1s ease;
touch-action: none;
z-index: 2;
}
.floor-plan-marker.is-active {
cursor: move;
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.8), 0 0 0 4px rgba(13, 110, 253, 0.35);
}
.floor-plan-marker.panel-marker {
background: rgba(13, 110, 253, 0.25);
border: 2px solid #0d6efd;
border-radius: 6px;
}
.floor-plan-marker.outlet-marker {
background: rgba(25, 135, 84, 0.25);
border: 2px solid #198754;
border-radius: 4px;
}
.floor-plan-reference {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
opacity: 0.35;
z-index: 1;
}
.floor-plan-reference.panel-marker {
background: rgba(13, 110, 253, 0.22);
border: 2px solid rgba(13, 110, 253, 0.7);
border-radius: 6px;
}
.floor-plan-reference.outlet-marker {
width: 32px;
height: 32px;
background: rgba(25, 135, 84, 0.22);
border: 2px solid rgba(25, 135, 84, 0.7);
border-radius: 4px;
}
.floor-plan-hint {
font-size: 0.85em;
color: #444;
margin: 0;
}
.floor-plan-position {
margin: 0;
font-size: 0.85em;
color: #666;
}
</style>
<script src="/assets/js/floor-infrastructure-edit.js" defer></script>