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.
This commit is contained in:
2026-02-16 10:04:12 +01:00
parent 4efd54613a
commit 3bc5a2ca04
3 changed files with 357 additions and 342 deletions

View File

@@ -2,7 +2,7 @@
/**
* app/modules/floor_infrastructure/list.php
*
* Uebersicht ueber Patchpanels und Netzwerkbuchsen auf Stockwerken
* Übersicht über Patchpanels und Netzwerkbuchsen auf Stockwerken
*/
$floorId = (int)($_GET['floor_id'] ?? 0);
@@ -101,10 +101,10 @@ if ($editorFloorId > 0) {
<div class="toolbar">
<a href="?module=floor_infrastructure&action=edit&type=patchpanel" class="button button-primary">
+ Patchpanel hinzufuegen
+ Patchpanel hinzufügen
</a>
<a href="?module=floor_infrastructure&action=edit&type=outlet" class="button">
+ Wandbuchse hinzufuegen
+ Wandbuchse hinzufügen
</a>
</div>
@@ -122,7 +122,7 @@ if ($editorFloorId > 0) {
</select>
<button class="button">Filter</button>
<a href="?module=floor_infrastructure&action=list" class="button">Zuruecksetzen</a>
<a href="?module=floor_infrastructure&action=list" class="button">Zurücksetzen</a>
</form>
<section class="infra-section">
@@ -134,7 +134,7 @@ if ($editorFloorId > 0) {
<th>Name</th>
<th>Stockwerk</th>
<th>Position</th>
<th>Groesse</th>
<th>Größe</th>
<th>Ports</th>
<th>Aktionen</th>
</tr>
@@ -196,18 +196,18 @@ if ($editorFloorId > 0) {
<section class="infra-plan">
<h2>Stockwerksplan-Verortung</h2>
<?php if (!$editorFloor): ?>
<p class="empty-state">Kein Stockwerk fuer die Planansicht verfuegbar.</p>
<p class="empty-state">Kein Stockwerk für die Planansicht verfügbar.</p>
<?php elseif (($editorFloor['svg_url'] ?? '') === ''): ?>
<p class="empty-state">Das gewaehlte Stockwerk hat noch keinen SVG-Plan hinterlegt.</p>
<p class="empty-state">Das gewählte Stockwerk hat noch keinen SVG-Plan hinterlegt.</p>
<?php else: ?>
<p>Drag & Drop ist aktiv fuer <strong><?php echo htmlspecialchars($editorFloor['name']); ?></strong>. Aenderungen werden direkt gespeichert.</p>
<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: gruen. Ziehen und loslassen zum Speichern.</p>
<p class="floor-plan-hint">Patchpanels: blau, Wandbuchsen: grün. In dieser Ansicht sind Marker nicht verschiebbar.</p>
<?php endif; ?>
</section>
</div>
@@ -277,8 +277,9 @@ if ($editorFloorId > 0) {
left: 0;
user-select: none;
touch-action: none;
cursor: move;
cursor: default;
z-index: 2;
pointer-events: none;
}
.infra-marker.patchpanel {
background: rgba(13, 110, 253, 0.25);
@@ -292,9 +293,6 @@ if ($editorFloorId > 0) {
border: 2px solid #198754;
border-radius: 4px;
}
.infra-marker.is-saving {
opacity: 0.65;
}
.floor-plan-hint {
margin: 8px 0 0;
font-size: 0.85em;
@@ -319,95 +317,10 @@ document.addEventListener('DOMContentLoaded', () => {
return;
}
const saveUrl = canvas.dataset.saveUrl;
const floorId = Number(canvas.dataset.floorId || 0);
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 clamp = (value, min, max) => Math.min(max, Math.max(min, value));
const savePosition = async (entry, type, marker) => {
const form = new FormData();
if (type === 'patchpanel') {
form.append('type', 'patchpanel');
form.append('id', String(entry.id));
form.append('name', entry.name || '');
form.append('floor_id', String(floorId));
form.append('pos_x', String(entry.x));
form.append('pos_y', String(entry.y));
form.append('width', String(entry.width));
form.append('height', String(entry.height));
form.append('port_count', String(entry.port_count || 0));
form.append('comment', entry.comment || '');
} else {
form.append('type', 'outlet');
form.append('id', String(entry.id));
form.append('name', entry.name || '');
form.append('room_id', String(entry.room_id || 0));
form.append('x', String(entry.x));
form.append('y', String(entry.y));
form.append('comment', entry.comment || '');
}
marker.classList.add('is-saving');
try {
await fetch(saveUrl, {
method: 'POST',
body: form,
credentials: 'same-origin'
});
} finally {
marker.classList.remove('is-saving');
}
};
const bindDrag = (marker, entry, type) => {
let dragging = false;
let offsetX = 0;
let offsetY = 0;
marker.addEventListener('pointerdown', (event) => {
dragging = true;
const rect = marker.getBoundingClientRect();
offsetX = event.clientX - rect.left;
offsetY = event.clientY - rect.top;
marker.setPointerCapture(event.pointerId);
});
marker.addEventListener('pointermove', (event) => {
if (!dragging) {
return;
}
const rect = canvas.getBoundingClientRect();
const width = type === 'patchpanel' ? entry.width : outletSize;
const height = type === 'patchpanel' ? entry.height : outletSize;
const maxX = Math.max(0, rect.width - width);
const maxY = Math.max(0, rect.height - height);
entry.x = Math.round(clamp(event.clientX - rect.left - offsetX, 0, maxX));
entry.y = Math.round(clamp(event.clientY - rect.top - offsetY, 0, maxY));
marker.style.left = `${entry.x}px`;
marker.style.top = `${entry.y}px`;
});
const stopDrag = (event) => {
if (!dragging) {
return;
}
dragging = false;
if (marker.hasPointerCapture(event.pointerId)) {
marker.releasePointerCapture(event.pointerId);
}
savePosition(entry, type, marker);
};
marker.addEventListener('pointerup', stopDrag);
marker.addEventListener('pointercancel', stopDrag);
};
const createMarker = (entry, type) => {
const marker = document.createElement('div');
marker.className = `infra-marker ${type}`;
@@ -423,8 +336,6 @@ document.addEventListener('DOMContentLoaded', () => {
marker.style.left = `${entry.x}px`;
marker.style.top = `${entry.y}px`;
canvas.appendChild(marker);
bindDrag(marker, entry, type);
};
patchPanels.forEach((entry) => createMarker(entry, 'patchpanel'));