Quelltext des aktuellen Standes
<?php
$this->layout('layouts/main', [
'page_title' => $page_title,
'current_route' => $current_route,
'full_width' => true,
]);
?>
<style>
a { text-decoration: none; }
a:hover { text-decoration: none; }
.lde-wrap { padding: 0; }
/* Form wrapper */
.lde-layout {
display: grid;
grid-template-columns: 1fr 280px;
gap: 1.5rem;
align-items: start;
}
/* Cards */
.lde-card {
background: var(--color-bg);
border: 1px solid var(--gray-200);
border-radius: 0.25rem;
overflow: hidden;
margin-bottom: 1.25rem;
}
.lde-card:last-child { margin-bottom: 0; }
/* Karte mit durchsuchbarem Dropdown darf nicht clippen, sonst verschwindet
die aufgeklappte Liste hinter dem Kartenrand. */
.lde-card--dropdown { overflow: visible; }
.lde-card--dropdown .lde-section-head { border-radius: 0.25rem 0.25rem 0 0; }
.lde-section-head {
display: flex; align-items: center; gap: 0.5rem;
font-size: 0.82rem; font-weight: 700; text-transform: uppercase;
letter-spacing: 0.06em; color: var(--gray-500);
padding: 0.9rem 1.25rem; border-bottom: 1px solid var(--gray-100);
background: var(--gray-50);
}
.lde-section-head svg { color: var(--amber-600); flex-shrink: 0; }
.lde-section-body { padding: 1.25rem; }
/* Fields */
.lde-row-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-bottom: 1rem; }
.lde-row-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 1rem; margin-bottom: 1rem; }
.lde-field { margin-bottom: 1rem; }
.lde-field:last-child { margin-bottom: 0; }
.lde-field label { display: block; font-size: 0.82rem; font-weight: 600; color: var(--gray-700); margin-bottom: 0.35rem; }
.lde-req { color: var(--red-500); }
.lde-field input[type=text],
.lde-field input[type=date],
.lde-field textarea,
.lde-field select {
width: 100%; padding: 0.55rem 0.75rem;
border: 1px solid var(--gray-300); border-radius: 0.25rem;
font-size: 0.88rem; outline: none; font-family: inherit;
transition: border-color 0.15s, box-shadow 0.15s;
background: var(--color-bg); box-sizing: border-box;
}
.lde-field input:focus,
.lde-field textarea:focus,
.lde-field select:focus {
border-color: var(--amber-600);
box-shadow: 0 0 0 3px rgba(217,119,6,0.12);
}
.lde-field textarea { resize: vertical; min-height: 100px; line-height: 1.6; }
.lde-hint { font-size: 0.75rem; color: var(--gray-400); margin-top: 0.3rem; line-height: 1.4; }
/* Image */
.lde-img-current {
display: flex; align-items: center; gap: 1rem;
background: var(--gray-50); border: 1px solid var(--gray-200); border-radius: 0.25rem;
padding: 0.75rem 1rem; margin-bottom: 0.75rem;
}
.lde-img-current img { width: 72px; height: 72px; object-fit: cover; border-radius: 0.4rem; border: 1px solid var(--gray-200); }
.lde-img-current-label { font-size: 0.82rem; color: var(--gray-500); }
.lde-img-drop {
border: 2px dashed var(--gray-300); border-radius: 0.25rem; padding: 1.5rem;
text-align: center; cursor: pointer; transition: border-color 0.15s, background 0.15s;
background: var(--gray-50);
}
.lde-img-drop:hover,
.lde-img-drop.dragover { border-color: var(--amber-600); background: var(--amber-50); }
.lde-img-drop-icon { display: block; margin: 0 auto 0.5rem; opacity: 0.4; }
.lde-img-drop-text { font-size: 0.82rem; color: var(--gray-400); }
.lde-img-preview { display: none; max-width: 160px; max-height: 120px; border-radius: 0.25rem; margin: 0 auto 0.75rem; }
.lde-img-preview.visible { display: block; }
/* Accessories */
.lde-acc-list { display: flex; flex-direction: column; gap: 0.5rem; }
.lde-acc-row { display: flex; gap: 0.5rem; align-items: center; }
.lde-acc-row input[type=text] {
flex: 1; padding: 0.5rem 0.75rem; border: 1px solid var(--gray-300);
border-radius: 0.25rem; font-size: 0.88rem; outline: none; font-family: inherit;
background: var(--color-bg); box-sizing: border-box;
}
.lde-acc-row input[type=text]:focus { border-color: var(--amber-600); box-shadow: 0 0 0 2px rgba(217,119,6,0.12); }
.lde-acc-row input.desc { flex: 0.6; font-size: 0.82rem; color: var(--gray-500); }
.lde-acc-remove {
background: none; border: none; color: var(--gray-300); cursor: pointer;
padding: 0.25rem; border-radius: 0.25rem; transition: color 0.15s, background 0.15s;
flex-shrink: 0;
}
.lde-acc-remove:hover { color: var(--red-500); background: var(--red-100); }
.lde-add-acc {
display: inline-flex; align-items: center; gap: 0.4rem; margin-top: 0.75rem;
font-size: 0.82rem; font-weight: 600; color: var(--amber-600);
background: none; border: none; cursor: pointer; padding: 0;
}
.lde-add-acc:hover { color: var(--amber-700); }
.lde-acc-empty {
font-size: 0.82rem; color: var(--gray-400); text-align: center;
padding: 1rem; border: 1.5px dashed var(--gray-200); border-radius: 0.25rem;
}
/* Submit bar */
.lde-submit-bar {
display: flex; gap: 0.75rem; justify-content: space-between; align-items: center;
padding: 1rem 1.25rem; background: var(--gray-50); border-top: 1px solid var(--gray-200);
}
.lde-submit-bar-right { display: flex; gap: 0.75rem; align-items: center; }
/* Sidebar */
.lde-help-card {
background: var(--color-bg); border: 1px solid var(--gray-200); border-radius: 0.25rem; overflow: hidden;
position: sticky; top: 1rem;
}
.lde-help-head {
display: flex; align-items: center; gap: 0.5rem;
font-size: 0.82rem; font-weight: 700; text-transform: uppercase;
letter-spacing: 0.06em; color: var(--gray-500);
padding: 0.9rem 1.25rem; border-bottom: 1px solid var(--gray-100); background: var(--gray-50);
}
.lde-help-body { padding: 1.25rem; }
.lde-help-item { margin-bottom: 1rem; }
.lde-help-item:last-child { margin-bottom: 0; }
.lde-help-label { font-size: 0.78rem; font-weight: 700; color: var(--gray-700); margin-bottom: 0.25rem; }
.lde-help-text { font-size: 0.78rem; color: var(--gray-500); line-height: 1.5; }
/* Auswahlfelder nutzen die zentrale durchsuchbare Dropdown-Komponente (app.js/app.css). */
@media (max-width: 768px) {
.lde-body { padding: 1.25rem 1rem 2rem; }
.lde-layout { grid-template-columns: 1fr; }
.lde-row-2, .lde-row-3 { grid-template-columns: 1fr; }
.lde-help-card { position: static; }
}
</style>
<div class="lde-wrap">
<!-- Content Header -->
<div class="content-header">
<div>
<h1><?= $this->t('Bearbeiten') ?>: <?= $this->e($device['title']) ?></h1>
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/"><?= $this->te('Home') ?></a></li>
<li class="breadcrumb-item"><a href="/loans"><?= $this->te('Leihgeräte') ?></a></li>
<li class="breadcrumb-item"><a href="/loans/devices"><?= $this->te('Geräte') ?></a></li>
<li class="breadcrumb-item"><a href="/loans/devices/<?= $device['id'] ?>"><?= $this->e($device['title']) ?></a></li>
<li class="breadcrumb-item active"><?= $this->te('Bearbeiten') ?></li>
</ol>
</div>
</div>
<!-- Body -->
<div class="page-body">
<form method="POST" action="/loans/devices/<?= $device['id'] ?>" enctype="multipart/form-data" id="deviceForm">
<input type="hidden" name="_method" value="PUT">
<input type="hidden" name="_csrf" value="<?= $this->csrf() ?>">
<div class="lde-layout">
<!-- Main column -->
<div class="lde-main">
<!-- Stammdaten -->
<div class="lde-card lde-card--dropdown">
<h2 class="lde-section-head">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg>
<?= $this->te('Stammdaten') ?>
</h2>
<div class="lde-section-body">
<div class="lde-row-2">
<div class="lde-field">
<label for="title"><?= $this->te('Bezeichnung') ?> <span class="lde-req">*</span></label>
<input type="text" id="title" name="title" value="<?= $this->e($device['title']) ?>" required maxlength="255">
</div>
<div class="lde-field">
<label for="type"><?= $this->te('Typ / Kategorie') ?> <span class="lde-req">*</span></label>
<input type="text" id="type" name="type" value="<?= $this->e($device['type']) ?>" list="type-options" required>
<datalist id="type-options">
<?php foreach ($types as $t): ?>
<option value="<?= $this->e($t['type']) ?>">
<?php endforeach; ?>
</datalist>
</div>
</div>
<div class="lde-row-3">
<div class="lde-field">
<label for="serial_number"><?= $this->te('Seriennummer') ?></label>
<input type="text" id="serial_number" name="serial_number" value="<?= $this->e($device['serial_number'] ?? '') ?>">
</div>
<div class="lde-field">
<label for="inventory_number"><?= $this->te('Inventarnummer') ?></label>
<input type="text" id="inventory_number" name="inventory_number" value="<?= $this->e($device['inventory_number'] ?? '') ?>">
</div>
</div>
<div class="lde-row-3">
<div class="lde-field">
<label for="storage_location"><?= $this->te('Lagerort') ?></label>
<input type="text" id="storage_location" name="storage_location" value="<?= $this->e($device['storage_location'] ?? '') ?>" placeholder="<?= $this->te('z. B. Schrank IT-1, Fach 3') ?>">
</div>
<div class="lde-field">
<label for="purchase_price"><?= $this->te('Anschaffungswert ({cur})', ['cur' => $this->currency()]) ?></label>
<input type="text" id="purchase_price" name="purchase_price" inputmode="decimal" value="<?= $device['purchase_price'] !== null && $device['purchase_price'] !== '' ? $this->e($this->number((float)$device['purchase_price'], 2)) : '' ?>" placeholder="z. B. 899,00">
</div>
<div class="lde-field">
<label for="purchase_date"><?= $this->te('Kaufdatum') ?></label>
<input type="date" id="purchase_date" name="purchase_date" value="<?= $this->e($device['purchase_date'] ?? '') ?>">
</div>
</div>
<div class="lde-field">
<label for="asset_id"><?= $this->te('Mit Inventar-Asset verknüpfen (optional)') ?></label>
<select id="asset_id" name="asset_id" class="form-control">
<option value=""><?= $this->te('– nicht verknüpft –') ?></option>
<?php foreach (($linkable_assets ?? []) as $a):
$label = $a['inventory_number'];
$extra = trim(($a['manufacturer_name'] ?? '') . ' ' . ($a['type_name'] ?? ''));
if ($extra !== '') { $label .= ' – ' . $extra; }
if (!empty($a['serial_number'])) { $label .= ' (S/N: ' . $a['serial_number'] . ')'; }
?>
<option value="<?= (int)$a['id'] ?>"
data-inv="<?= $this->e($a['inventory_number'] ?? '') ?>"
data-sn="<?= $this->e($a['serial_number'] ?? '') ?>"
<?= (int)($device['asset_id'] ?? 0) === (int)$a['id'] ? 'selected' : '' ?>><?= $this->e($label) ?></option>
<?php endforeach; ?>
</select>
<p class="lde-hint" style="margin-top:0.25rem;color:var(--gray-500);font-size:0.8rem;"><?= $this->te('Verknüpft das Leihgerät mit einem bestehenden Asset und markiert es dort als Leihgerät. Beim Verknüpfen werden Inventar- und Seriennummer automatisch übernommen.') ?></p>
</div>
</div>
</div>
<!-- Bild -->
<div class="lde-card">
<h2 class="lde-section-head">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="M21 15l-5-5L5 21"/></svg>
<?= $this->te('Bild (optional)') ?>
</h2>
<div class="lde-section-body">
<?php if (!empty($device['image_path'])): ?>
<div class="lde-img-current">
<img src="<?= $this->e($device['image_path']) ?>" alt="<?= $this->te('Aktuelles Bild') ?>">
<span class="lde-img-current-label"><?= $this->te('Aktuelles Bild — neues Bild hochladen um es zu ersetzen') ?></span>
</div>
<?php endif; ?>
<div class="lde-img-drop" id="imgDrop" onclick="document.getElementById('imageInput').click()">
<img class="lde-img-preview" id="imgPreview" src="" alt="<?= $this->te('Vorschau') ?>">
<svg class="lde-img-drop-icon" id="imgPlaceholder" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="#9ca3af" stroke-width="1.5"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="M21 15l-5-5L5 21"/></svg>
<p class="lde-img-drop-text" id="imgText">
<strong><?= !empty($device['image_path']) ? 'Neues Bild hochladen' : 'Klicken' ?></strong>
<?= !empty($device['image_path']) ? '' : ' oder Bild hier ablegen' ?>
</p>
<p class="lde-hint" style="margin-top:0.25rem;"><?= $this->te('JPEG, PNG, GIF, WebP') ?></p>
</div>
<input type="file" id="imageInput" name="image" accept="image/*" style="display:none">
</div>
</div>
<!-- Beschreibung & Notizen -->
<div class="lde-card">
<h2 class="lde-section-head">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
<?= $this->te('Beschreibung & Notizen') ?>
</h2>
<div class="lde-section-body">
<div class="lde-field">
<label for="description"><?= $this->te('Beschreibung') ?></label>
<textarea id="description" name="description" rows="3"><?= $this->e($device['description'] ?? '') ?></textarea>
</div>
<div class="lde-field">
<label for="notes"><?= $this->te('Interne Notizen') ?></label>
<textarea id="notes" name="notes" rows="3"><?= $this->e($device['notes'] ?? '') ?></textarea>
<p class="lde-hint"><?= $this->te('Nur für Mitarbeitende sichtbar.') ?></p>
</div>
<div class="lde-field" style="margin-top:0.75rem;">
<label style="display:flex;align-items:center;gap:0.5rem;font-weight:500;cursor:pointer;">
<input type="checkbox" name="wp_requestable" value="1" <?= !empty($device['wp_requestable']) ? 'checked' : '' ?> style="width:auto;margin:0;">
<?= $this->te('Im Self-Service anforderbar') ?>
</label>
<p class="lde-hint"><?= $this->te('Wenn aktiv, können Mitarbeitende dieses Gerät selbst reservieren – im Self-Service-Portal und über das WordPress-Plugin. Andernfalls wird es dort nur zur Information angezeigt.') ?></p>
</div>
</div>
</div>
<!-- Zubehör -->
<div class="lde-card">
<h2 class="lde-section-head">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20.24 12.24a6 6 0 00-8.49-8.49L5 10.5V19h8.5z"/><line x1="16" y1="8" x2="2" y2="22"/></svg>
<?= $this->te('Zubehör') ?>
</h2>
<div class="lde-section-body">
<div class="lde-acc-list" id="accList">
<?php if (empty($accessories)): ?>
<p class="lde-acc-empty" id="accEmpty"><?= $this->te('Noch kein Zubehör hinzugefügt.') ?></p>
<?php else: ?>
<?php foreach ($accessories as $acc): ?>
<div class="lde-acc-row">
<input type="text" name="accessory_name[]" value="<?= $this->e($acc['name']) ?>" placeholder="<?= $this->te('Bezeichnung') ?>" required>
<input type="text" name="accessory_desc[]" class="desc" value="<?= $this->e($acc['description'] ?? '') ?>" placeholder="<?= $this->te('Beschreibung (optional)') ?>">
<button type="button" class="lde-acc-remove" title="<?= $this->te('Entfernen') ?>" onclick="removeRow(this)">✕</button>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
<button type="button" class="lde-add-acc" onclick="addAccessory()">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M12 5v14M5 12h14"/></svg>
<?= $this->te('Zubehör hinzufügen') ?>
</button>
<p class="lde-hint"><?= $this->te('Jedes Zubehörteil erscheint beim Ausgeben als Checkbox.') ?></p>
</div>
<div class="lde-submit-bar">
<button type="button" class="btn btn-danger" onclick="deleteDevice()"><?= $this->te('Gerät löschen') ?></button>
<div class="lde-submit-bar-right">
<a href="/loans/devices/<?= $device['id'] ?>" class="btn btn-default"><?= $this->te('Abbrechen') ?></a>
<button type="submit" class="btn btn-primary">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="margin-right:0.3rem;"><path d="M20 6L9 17l-5-5"/></svg>
<?= $this->te('Speichern') ?>
</button>
</div>
</div>
</div>
</div>
<!-- Sidebar -->
<div class="lde-aside">
<div class="lde-help-card">
<div class="lde-help-head">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#d97706" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 015.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
<?= $this->te('Hinweise') ?>
</div>
<div class="lde-help-body">
<div class="lde-help-item">
<div class="lde-help-label"><?= $this->te('Bezeichnung & Typ') ?></div>
<div class="lde-help-text"><?= $this->te('Wählen Sie eine eindeutige Bezeichnung. Der Typ wird für Filter und Kalenderanzeige genutzt.') ?></div>
</div>
<div class="lde-help-item">
<div class="lde-help-label"><?= $this->te('Zubehör') ?></div>
<div class="lde-help-text"><?= $this->te('Änderungen am Zubehör gelten für zukünftige Ausleihen. Bereits laufende Ausleihen bleiben unberührt.') ?></div>
</div>
<div class="lde-help-item">
<div class="lde-help-label"><?= $this->te('Gerät löschen') ?></div>
<div class="lde-help-text"><?= $this->te('Beim Löschen werden alle Ausleihvorgänge dieses Geräts unwiderruflich entfernt.') ?></div>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
<!-- Delete form -->
<form id="deleteForm" method="POST" action="/loans/devices/<?= $device['id'] ?>" style="display:none;">
<input type="hidden" name="_method" value="DELETE">
<input type="hidden" name="_csrf" value="<?= $this->csrf() ?>">
</form>
<script>
// Image preview
(function() {
var input = document.getElementById('imageInput');
var preview = document.getElementById('imgPreview');
var icon = document.getElementById('imgPlaceholder');
var text = document.getElementById('imgText');
var drop = document.getElementById('imgDrop');
function showPreview(file) {
var reader = new FileReader();
reader.onload = function(e) {
preview.src = e.target.result;
preview.classList.add('visible');
icon.style.display = 'none';
text.textContent = file.name;
};
reader.readAsDataURL(file);
}
input.addEventListener('change', function() { if (this.files[0]) showPreview(this.files[0]); });
drop.addEventListener('dragover', function(e) { e.preventDefault(); drop.classList.add('dragover'); });
drop.addEventListener('dragleave', function() { drop.classList.remove('dragover'); });
drop.addEventListener('drop', function(e) {
e.preventDefault(); drop.classList.remove('dragover');
if (e.dataTransfer.files[0]) { input.files = e.dataTransfer.files; showPreview(e.dataTransfer.files[0]); }
});
})();
// Accessories
function addAccessory() {
var empty = document.getElementById('accEmpty');
if (empty) empty.remove();
var list = document.getElementById('accList');
var row = document.createElement('div');
row.className = 'lde-acc-row';
row.innerHTML =
'<input type="text" name="accessory_name[]" placeholder="Bezeichnung (z. B. Netzteil)" required>' +
'<input type="text" name="accessory_desc[]" class="desc" placeholder="Beschreibung (optional)">' +
'<button type="button" class="lde-acc-remove" title="Entfernen" onclick="removeRow(this)">✕</button>';
list.appendChild(row);
row.querySelector('input').focus();
}
function removeRow(btn) {
btn.closest('.lde-acc-row').remove();
if (document.querySelectorAll('.lde-acc-row').length === 0) {
var list = document.getElementById('accList');
var p = document.createElement('p');
p.className = 'lde-acc-empty';
p.id = 'accEmpty';
p.textContent = 'Noch kein Zubehör hinzugefügt.';
list.appendChild(p);
}
}
function deleteDevice() {
if (confirm('Gerät und alle Ausleihvorgänge unwiderruflich löschen?')) {
document.getElementById('deleteForm').submit();
}
}
// Beim Verknüpfen Inventar- und Seriennummer aus dem Asset übernehmen.
(function () {
var sel = document.getElementById('asset_id');
if (!sel) return;
sel.addEventListener('change', function () {
var opt = sel.options[sel.selectedIndex];
if (!opt || !opt.value) return; // "nicht verknüpft" → bestehende Werte behalten
var inv = opt.getAttribute('data-inv') || '';
var sn = opt.getAttribute('data-sn') || '';
var invField = document.getElementById('inventory_number');
var snField = document.getElementById('serial_number');
if (invField && inv !== '') { invField.value = inv; }
if (snField && sn !== '') { snField.value = sn; }
});
})();
</script>