Compare commits
3 Commits
444c802756
...
a3799dd8f5
| Author | SHA1 | Date | |
|---|---|---|---|
| a3799dd8f5 | |||
| b55b9729af | |||
| d3ae285aba |
4
BUGS.md
4
BUGS.md
@@ -1,7 +1,3 @@
|
|||||||
# gefundene bugs
|
# gefundene bugs
|
||||||
- [ ] device löschen geht nicht
|
- [ ] device löschen geht nicht
|
||||||
- [ ] device_types svg modul malen
|
|
||||||
- [ ] ports drag n drop funktioniert nicht
|
|
||||||
- [ ] device _type soll schon aus dem 19zoll und he größe einen initialees rechteck erzeugen, welches als device grundgerüst funktionieren soll.
|
|
||||||
- [ ] beim dev typ machen, klick auf obj typ button, dann durch drag and drop die diagonale ziehen mit loslassen fixieren
|
|
||||||
- [ ] TODO Design vereinheitlichen
|
- [ ] TODO Design vereinheitlichen
|
||||||
@@ -34,7 +34,6 @@ $sql = new SQL();
|
|||||||
* Helper laden
|
* Helper laden
|
||||||
* ========================= */
|
* ========================= */
|
||||||
require_once __DIR__ . '/lib/helpers.php';
|
require_once __DIR__ . '/lib/helpers.php';
|
||||||
// TODO: Globale Funktionen: escape, redirect, flash messages, etc.
|
|
||||||
|
|
||||||
/* =========================
|
/* =========================
|
||||||
* Optional: Fehlerbehandlung
|
* Optional: Fehlerbehandlung
|
||||||
|
|||||||
@@ -12,6 +12,11 @@
|
|||||||
* KEINE Business-Logik
|
* KEINE Business-Logik
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sitzungs-Keys
|
||||||
|
*/
|
||||||
|
const FLASH_SESSION_KEY = 'flash_messages';
|
||||||
|
|
||||||
/* =========================
|
/* =========================
|
||||||
* Output / Sicherheit
|
* Output / Sicherheit
|
||||||
* ========================= */
|
* ========================= */
|
||||||
@@ -24,10 +29,13 @@
|
|||||||
*/
|
*/
|
||||||
function e(?string $value): string
|
function e(?string $value): string
|
||||||
{
|
{
|
||||||
// TODO: htmlspecialchars mit ENT_QUOTES + UTF-8
|
if ($value === null) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||||
|
}
|
||||||
|
|
||||||
/* =========================
|
/* =========================
|
||||||
* Redirects
|
* Redirects
|
||||||
* ========================= */
|
* ========================= */
|
||||||
@@ -40,7 +48,10 @@ function e(?string $value): string
|
|||||||
*/
|
*/
|
||||||
function redirect(string $url, int $code = 302): void
|
function redirect(string $url, int $code = 302): void
|
||||||
{
|
{
|
||||||
// TODO: header("Location: ...")
|
if (!headers_sent()) {
|
||||||
|
header('Location: ' . $url, true, $code);
|
||||||
|
}
|
||||||
|
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +67,15 @@ function redirect(string $url, int $code = 302): void
|
|||||||
*/
|
*/
|
||||||
function flash(string $type, string $message): void
|
function flash(string $type, string $message): void
|
||||||
{
|
{
|
||||||
// TODO: In $_SESSION speichern
|
if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($_SESSION[FLASH_SESSION_KEY])) {
|
||||||
|
$_SESSION[FLASH_SESSION_KEY] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION[FLASH_SESSION_KEY][] = ['type' => $type, 'message' => $message];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,8 +85,14 @@ function flash(string $type, string $message): void
|
|||||||
*/
|
*/
|
||||||
function getFlashes(): array
|
function getFlashes(): array
|
||||||
{
|
{
|
||||||
// TODO: Aus Session lesen und löschen
|
if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||||
return [];
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
$messages = $_SESSION[FLASH_SESSION_KEY] ?? [];
|
||||||
|
unset($_SESSION[FLASH_SESSION_KEY]);
|
||||||
|
|
||||||
|
return $messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =========================
|
/* =========================
|
||||||
@@ -83,8 +108,7 @@ function getFlashes(): array
|
|||||||
*/
|
*/
|
||||||
function post(string $key, $default = null)
|
function post(string $key, $default = null)
|
||||||
{
|
{
|
||||||
// TODO: $_POST prüfen
|
return $_POST[$key] ?? $default;
|
||||||
return $default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -96,8 +120,7 @@ function post(string $key, $default = null)
|
|||||||
*/
|
*/
|
||||||
function get(string $key, $default = null)
|
function get(string $key, $default = null)
|
||||||
{
|
{
|
||||||
// TODO: $_GET prüfen
|
return $_GET[$key] ?? $default;
|
||||||
return $default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -107,8 +130,7 @@ function get(string $key, $default = null)
|
|||||||
*/
|
*/
|
||||||
function isPost(): bool
|
function isPost(): bool
|
||||||
{
|
{
|
||||||
// TODO: $_SERVER['REQUEST_METHOD']
|
return ($_SERVER['REQUEST_METHOD'] ?? '') === 'POST';
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =========================
|
/* =========================
|
||||||
@@ -123,8 +145,11 @@ function isPost(): bool
|
|||||||
*/
|
*/
|
||||||
function isEmpty($value): bool
|
function isEmpty($value): bool
|
||||||
{
|
{
|
||||||
// TODO: trim + empty
|
if (is_string($value)) {
|
||||||
return false;
|
return trim($value) === '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return empty($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =========================
|
/* =========================
|
||||||
@@ -139,10 +164,31 @@ function isEmpty($value): bool
|
|||||||
*/
|
*/
|
||||||
function url(string $path = ''): string
|
function url(string $path = ''): string
|
||||||
{
|
{
|
||||||
// TODO: Base-URL aus config.php
|
if ($path === '') {
|
||||||
|
$path = '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('~^([a-z]+:)?//~i', $path)) {
|
||||||
return $path;
|
return $path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$script = $_SERVER['SCRIPT_NAME'] ?? '';
|
||||||
|
$baseDir = rtrim(strtr(dirname($script), '\\\\', '/'), '/');
|
||||||
|
|
||||||
|
if ($baseDir === '.' || $baseDir === '\\\\') {
|
||||||
|
$baseDir = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$segment = ltrim($path, '/');
|
||||||
|
$prefix = $baseDir === '' ? '' : $baseDir;
|
||||||
|
|
||||||
|
if ($segment === '') {
|
||||||
|
return $prefix === '' ? '/' : $prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ($prefix === '' ? '' : $prefix) . '/' . $segment;
|
||||||
|
}
|
||||||
|
|
||||||
/* =========================
|
/* =========================
|
||||||
* Debug / Entwicklung
|
* Debug / Entwicklung
|
||||||
* ========================= */
|
* ========================= */
|
||||||
@@ -154,7 +200,9 @@ function url(string $path = ''): string
|
|||||||
*/
|
*/
|
||||||
function dd($value): void
|
function dd($value): void
|
||||||
{
|
{
|
||||||
// TODO: var_dump / print_r + exit
|
echo '<pre>';
|
||||||
|
var_dump($value);
|
||||||
|
echo '</pre>';
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,20 @@ if ($type === 'outlet' && $id > 0) {
|
|||||||
$pageTitle = "Wandbuchse bearbeiten: " . htmlspecialchars($outlet['name']);
|
$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">
|
<div class="floor-infra-edit">
|
||||||
@@ -83,11 +97,28 @@ if ($type === 'outlet' && $id > 0) {
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Breite</label>
|
<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>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Höhe</label>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -101,7 +132,7 @@ if ($type === 'outlet' && $id > 0) {
|
|||||||
<textarea name="comment"><?php echo htmlspecialchars($panel['comment'] ?? ''); ?></textarea>
|
<textarea name="comment"><?php echo htmlspecialchars($panel['comment'] ?? ''); ?></textarea>
|
||||||
</div>
|
</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: ?>
|
<?php else: ?>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -131,12 +162,29 @@ if ($type === 'outlet' && $id > 0) {
|
|||||||
<input type="number" name="y" value="<?php echo $outlet['y'] ?? 0; ?>">
|
<input type="number" name="y" value="<?php echo $outlet['y'] ?? 0; ?>">
|
||||||
</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="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">
|
<div class="form-group">
|
||||||
<label>Kommentar</label>
|
<label>Kommentar</label>
|
||||||
<textarea name="comment"><?php echo htmlspecialchars($outlet['comment'] ?? ''); ?></textarea>
|
<textarea name="comment"><?php echo htmlspecialchars($outlet['comment'] ?? ''); ?></textarea>
|
||||||
</div>
|
</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; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
@@ -178,4 +226,158 @@ if ($type === 'outlet' && $id > 0) {
|
|||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
color: #555;
|
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>
|
</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>
|
||||||
|
|||||||
Reference in New Issue
Block a user