This commit is contained in:
2026-02-13 10:43:06 +01:00
parent 444c802756
commit d3ae285aba

View File

@@ -45,6 +45,20 @@ if ($type === 'outlet' && $id > 0) {
$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">
@@ -83,11 +97,28 @@ if ($type === 'outlet' && $id > 0) {
</div>
<div class="form-group">
<label>Breite</label>
<input type="number" name="width" value="<?php echo $panel['width'] ?? 0; ?>" required>
<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'] ?? 0; ?>" required>
<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>
@@ -101,7 +132,7 @@ if ($type === 'outlet' && $id > 0) {
<textarea name="comment"><?php echo htmlspecialchars($panel['comment'] ?? ''); ?></textarea>
</div>
<p class="info">TODO: Drag & Drop auf dem SVG-Plan erlauben.</p>
<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">
@@ -131,12 +162,29 @@ if ($type === 'outlet' && $id > 0) {
<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">TODO: Wandbuchsen gezielt auf dem Stockwerksplan platzieren.</p>
<p class="info">Wandbuchsen bleiben quadratisch, ihre Position wird über die Karte festgelegt.</p>
<?php endif; ?>
<div class="form-actions">
@@ -178,4 +226,158 @@ if ($type === 'outlet' && $id > 0) {
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>