Files
netwatch/app/modules/floor_infrastructure/edit.php
2026-02-13 10:43:06 +01:00

384 lines
13 KiB
PHP

<?php
/**
* app/modules/floor_infrastructure/edit.php
*
* Formular zum Anlegen/Bearbeiten von Patchpanels und Wandbuchsen
*/
//TODO die auswahl der stockwerke in gebäude gruppieren und gebäude in locations gruppieren
$type = $_GET['type'] ?? 'patchpanel';
$id = (int)($_GET['id'] ?? 0);
$floors = $sql->get("SELECT id, name FROM floors ORDER BY name", "", []);
$rooms = $sql->get(
"SELECT r.id, r.name, f.name AS floor_name
FROM rooms r
LEFT JOIN floors f ON f.id = r.floor_id
ORDER BY f.name, r.name",
"",
[]
);
$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 ?? [];
$defaultPanelSize = ['width' => 140, 'height' => 40];
$defaultOutletSize = 32;
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;
?>
<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-group">
<label>Stockwerk</label>
<select name="floor_id" required>
<option value="">- wählen -</option>
<?php foreach ($floors as $floor): ?>
<option value="<?php echo $floor['id']; ?>" <?php echo ($panel['floor_id'] ?? 0) === $floor['id'] ? 'selected' : ''; ?>>
<?php echo htmlspecialchars($floor['name']); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="form-grid">
<div class="form-group">
<label>X</label>
<input type="number" name="pos_x" value="<?php echo $panel['pos_x'] ?? 0; ?>" required>
</div>
<div class="form-group">
<label>Y</label>
<input type="number" name="pos_y" value="<?php echo $panel['pos_y'] ?? 0; ?>" required>
</div>
<div class="form-group">
<label>Breite</label>
<input type="number" name="width" value="<?php echo $panel['width']; ?>" required 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']; ?>" required readonly title="Höhe wird automatisch nach Standardwerten vorgegeben.">
</div>
</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="patchpanel"
data-x-field="pos_x"
data-y-field="pos_y">
<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">Ziehe das Patchpanel oder klicke auf den Plan, um die Position zu setzen.</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>
<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" required>
<option value="">- Raum wählen -</option>
<?php foreach ($rooms as $room): ?>
<option value="<?php echo $room['id']; ?>" <?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">
<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">Klicke oder ziehe die Wandbuchse auf dem Plan. Die Größe bleibt quadratisch.</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-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;
}
.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-hint {
font-size: 0.85em;
color: #444;
margin: 0;
}
.floor-plan-position {
margin: 0;
font-size: 0.85em;
color: #666;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', () => {
const canvas = document.getElementById('floor-plan-canvas');
const marker = document.getElementById('floor-plan-marker');
const positionLabel = document.getElementById('floor-plan-position');
if (!canvas || !marker) {
return;
}
const xFieldName = canvas.dataset.xField;
const yFieldName = canvas.dataset.yField;
const xField = xFieldName ? document.querySelector(`input[name="${xFieldName}"]`) : null;
const yField = yFieldName ? document.querySelector(`input[name="${yFieldName}"]`) : null;
if (!xField || !yField) {
return;
}
const markerWidth = Math.max(1, Number(canvas.dataset.markerWidth) || marker.offsetWidth);
const markerHeight = Math.max(1, Number(canvas.dataset.markerHeight) || marker.offsetHeight);
const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
const updatePositionLabel = (x, y) => {
if (positionLabel) {
positionLabel.textContent = `${Math.round(x)} x ${Math.round(y)}`;
}
};
const setMarkerPosition = (rawX, rawY) => {
const rect = canvas.getBoundingClientRect();
const maxX = Math.max(0, rect.width - markerWidth);
const maxY = Math.max(0, rect.height - markerHeight);
const left = clamp(rawX, 0, maxX);
const top = clamp(rawY, 0, maxY);
marker.style.left = `${left}px`;
marker.style.top = `${top}px`;
xField.value = Math.round(left);
yField.value = Math.round(top);
updatePositionLabel(left, top);
};
const updateFromInputs = () => {
setMarkerPosition(Number(xField.value) || 0, Number(yField.value) || 0);
};
updateFromInputs();
let dragging = false;
let offsetX = 0;
let offsetY = 0;
const startDrag = (clientX, clientY) => {
const markerRect = marker.getBoundingClientRect();
offsetX = clientX - markerRect.left;
offsetY = clientY - markerRect.top;
};
marker.addEventListener('pointerdown', (event) => {
event.preventDefault();
dragging = true;
startDrag(event.clientX, event.clientY);
marker.setPointerCapture(event.pointerId);
});
marker.addEventListener('pointermove', (event) => {
if (!dragging) {
return;
}
const rect = canvas.getBoundingClientRect();
setMarkerPosition(event.clientX - rect.left - offsetX, event.clientY - rect.top - offsetY);
});
const stopDrag = (event) => {
if (!dragging) {
return;
}
dragging = false;
if (marker.hasPointerCapture(event.pointerId)) {
marker.releasePointerCapture(event.pointerId);
}
};
['pointerup', 'pointercancel', 'pointerleave'].forEach((evt) => {
marker.addEventListener(evt, stopDrag);
});
canvas.addEventListener('pointerdown', (event) => {
if (event.target !== canvas) {
return;
}
const rect = canvas.getBoundingClientRect();
setMarkerPosition(event.clientX - rect.left - markerWidth / 2, event.clientY - rect.top - markerHeight / 2);
});
[xField, yField].forEach((input) => {
input.addEventListener('input', () => {
updateFromInputs();
});
});
window.addEventListener('resize', () => {
updateFromInputs();
});
});
</script>