411 lines
16 KiB
PHP
411 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 = 10;
|
|
$showPanelPlacementFields = $type === 'patchpanel' && $selectedFloorId > 0;
|
|
|
|
if ($type === 'patchpanel') {
|
|
$panel['width'] = $panel['width'] ?? $defaultPanelSize['width'];
|
|
$panel['height'] = $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",
|
|
"",
|
|
[]
|
|
);
|
|
?>
|
|
|
|
<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>
|
|
|
|
<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 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. Neue Objekte starten bei Position 30 x 30.</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>
|
|
|
|
<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 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. Netzwerkdosen sind immer 10 x 10.</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: 10px;
|
|
height: 10px;
|
|
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>
|
|
|