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; }
.inv-wrap { padding:0; }
/* Karten und Formularfelder kommen aus app.css (.detail-card, .form-group, .form-control) */
.mfr-form-grid { display:grid; grid-template-columns:1.5fr 1fr 1fr; gap:0.75rem; }
.mfr-form-grid .form-group { margin-bottom:0; }
/* Image thumbnails */
.ti-thumbs { display:flex; flex-wrap:wrap; gap:0.3rem; margin-bottom:0.35rem; }
.ti-thumb { position:relative; display:inline-block; }
.ti-thumb-img { width:48px; height:48px; object-fit:cover; border-radius:0.35rem; border:1px solid var(--gray-200); display:block; cursor:pointer; }
.ti-thumb-img:hover { opacity:0.82; }
.ti-del-btn { position:absolute; top:-4px; right:-4px; width:15px; height:15px; border-radius:50%;
background:var(--red-600); color:var(--color-on-accent); border:none; cursor:pointer; font-size:0.6rem; line-height:15px;
text-align:center; padding:0; }
.ti-upload-label { display:inline-flex; align-items:center; padding:0.2rem 0.5rem;
border:1.5px dashed var(--gray-300); border-radius:0.4rem; font-size:0.74rem; font-weight:600;
color:var(--gray-500); cursor:pointer; transition:all 0.15s; white-space:nowrap; }
.ti-upload-label:hover { border-color:var(--color-primary); color:var(--color-primary); }
.ti-upload-label.loading { opacity:0.5; pointer-events:none; }
/* Edit row */
.type-edit-row { background:var(--sky-50); }
.type-edit-row td { padding:1rem 1.25rem; border-bottom:1px solid var(--gray-200); }
.type-edit-grid { display:flex; gap:0.6rem; flex-wrap:wrap; align-items:flex-end; }
.type-edit-grid .form-group { margin-bottom:0; }
/* Lightbox */
#ti-lightbox { display:flex; position:fixed; inset:0; background:rgba(0,0,0,0.85); z-index:9999; align-items:center; justify-content:center; cursor:pointer; display:none; }
#ti-lightbox img { max-width:90vw; max-height:90vh; border-radius:0.25rem; }
@media(max-width:900px) {
.mfr-form-grid { grid-template-columns:1fr 1fr; }
}
</style>
<div class="inv-wrap">
<!-- Content Header -->
<div class="content-header">
<div>
<h1><?= $this->te('Modelle / Typen') ?></h1>
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/"><?= $this->te('Home') ?></a></li>
<li class="breadcrumb-item"><a href="/inventory"><?= $this->te('Inventar') ?></a></li>
<li class="breadcrumb-item active"><?= $this->te('Modelle') ?></li>
</ol>
</div>
<div class="content-header-actions">
<a href="/inventory/manufacturers" class="btn btn-default"><?= $this->te('Hersteller') ?></a>
<a href="/inventory" class="btn btn-default"><?= $this->te('← Inventar') ?></a>
</div>
</div>
<div class="page-body">
<!-- Neues Modell anlegen -->
<div class="detail-card">
<div class="detail-card-header">
<span style="font-size:1.1rem;">➕</span>
<span class="detail-card-title"><?= $this->te('Neues Modell anlegen') ?></span>
</div>
<div class="detail-card-body">
<form method="POST" action="/inventory/types">
<input type="hidden" name="_csrf" value="<?= $this->csrf() ?>">
<div class="mfr-form-grid" style="margin-bottom:0.75rem;">
<div class="form-group">
<label><?= $this->te('Name *') ?></label>
<input type="text" name="name" class="form-control" placeholder="<?= $this->te('z.B. Latitude 3530') ?>" required maxlength="255">
</div>
<div class="form-group">
<label for="f-8c75-manufacturer_id"><?= $this->te('Hersteller *') ?></label>
<select id="f-8c75-manufacturer_id" name="manufacturer_id" class="form-control" required>
<option value=""><?= $this->te('— Hersteller wählen —') ?></option>
<?php foreach ($manufacturers as $m): ?>
<option value="<?= $m['id'] ?>"><?= $this->e($m['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label for="f-8c75-category_id"><?= $this->te('Kategorie') ?></label>
<select id="f-8c75-category_id" name="category_id" class="form-control">
<option value=""><?= $this->te('— optional —') ?></option>
<?php foreach ($categories as $cat): ?>
<option value="<?= $cat['id'] ?>"><?= $this->e($cat['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="mfr-form-grid" style="margin-bottom:0.75rem;">
<div class="form-group" style="grid-column:1/-1;">
<label><?= $this->te('Beschreibung') ?></label>
<input type="text" name="description" class="form-control" placeholder="<?= $this->te('Kurze Beschreibung') ?>" maxlength="500">
</div>
</div>
<div style="display:flex;gap:0.75rem;align-items:flex-end;">
<div class="form-group" style="flex:1;">
<label><?= $this->te('Technische Daten') ?></label>
<textarea name="specs" class="form-control" rows="2" placeholder="<?= $this->te('CPU, RAM, Display, …') ?>"></textarea>
</div>
<button type="submit" class="btn btn-primary"><?= $this->te('Anlegen') ?></button>
</div>
</form>
</div>
</div>
<!-- Modellliste -->
<div class="detail-card">
<div class="detail-card-header">
<span style="font-size:1.1rem;">📋</span>
<span class="detail-card-title"><?= $this->te('Modelle ({anzahl})', ['anzahl' => count($types)]) ?></span>
</div>
<?php if (empty($types)): ?>
<div class="empty-state"><?= $this->te('Noch keine Modelle vorhanden.') ?></div>
<?php else: ?>
<div style="overflow-x:auto;-webkit-overflow-scrolling:touch;">
<table class="data-table">
<thead>
<tr>
<?php foreach (['Name', 'Hersteller', 'Kategorie', 'Bilder', 'Assets', ''] as $th): ?>
<th scope="col"><?= $th ?></th>
<?php endforeach; ?>
</tr>
</thead>
<tbody>
<?php foreach ($types as $t):
$imgs = $type_images[(int)$t['id']] ?? [];
?>
<tr>
<td>
<strong><?= $this->e($t['name']) ?></strong>
<?php if ($t['description']): ?>
<small style="display:block;font-size:0.75rem;color:var(--gray-400);"><?= $this->e(mb_substr($t['description'], 0, 80)) ?></small>
<?php endif; ?>
</td>
<td style="font-size:0.85rem;"><?= $this->e($t['manufacturer_name']) ?></td>
<td style="font-size:0.82rem;color:var(--gray-500);"><?= $t['category_name'] ? $this->e($t['category_name']) : '—' ?></td>
<td style="vertical-align:top;padding-top:0.8rem;">
<div class="ti-thumbs" id="thumbs-<?= $t['id'] ?>">
<?php foreach ($imgs as $img): ?>
<div class="ti-thumb" id="img-<?= $img['id'] ?>">
<img src="/uploads/asset-types/<?= $this->e($img['filename']) ?>" alt=""
class="ti-thumb-img" onclick="openLightbox(this.src)">
<?php if (!empty($can_manage_images)): ?>
<button type="button" class="ti-del-btn"
onclick="deleteImage(<?= $img['id'] ?>, <?= $t['id'] ?>)"
title="<?= $this->te('Bild löschen') ?>">×</button>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
<?php if (!empty($can_manage_images)): ?>
<label class="ti-upload-label">
<input type="file" accept="image/jpeg,image/png,image/gif,image/webp"
style="display:none;" onchange="uploadImage(this, <?= $t['id'] ?>)">
<?= $this->te('+ Bild') ?>
</label>
<?php endif; ?>
</td>
<td style="font-size:0.85rem;"><?= (int)$t['asset_count'] ?></td>
<td>
<div style="display:flex;gap:0.4rem;">
<button onclick="toggleEdit('type-<?= $t['id'] ?>')" class="btn btn-xs btn-default"><?= $this->te('Bearbeiten') ?></button>
<?php if ((int)$t['asset_count'] === 0): ?>
<form method="POST" action="/inventory/types/<?= $t['id'] ?>/delete" style="margin:0;"
onsubmit="return confirm('Modell <?= addslashes($this->e($t['name'])) ?> löschen? Alle Bilder werden ebenfalls gelöscht.')">
<input type="hidden" name="_csrf" value="<?= $this->csrf() ?>">
<button aria-label="<?= $this->te('Löschen') ?>" type="submit" class="btn btn-xs btn-danger">✕</button>
</form>
<?php endif; ?>
</div>
</td>
</tr>
<tr id="type-<?= $t['id'] ?>" style="display:none;" class="type-edit-row">
<td colspan="6">
<form method="POST" action="/inventory/types/<?= $t['id'] ?>">
<input type="hidden" name="_csrf" value="<?= $this->csrf() ?>">
<input type="hidden" name="_method" value="PUT">
<div class="type-edit-grid">
<div class="form-group" style="flex:1.5;min-width:150px;">
<label><?= $this->te('Name *') ?></label>
<input type="text" name="name" class="form-control" value="<?= $this->e($t['name']) ?>" required maxlength="255">
</div>
<div class="form-group" style="min-width:150px;">
<label><?= $this->te('Hersteller *') ?></label>
<select name="manufacturer_id" class="form-control" required>
<?php foreach ($manufacturers as $m): ?>
<option value="<?= $m['id'] ?>" <?= (int)$t['manufacturer_id'] === (int)$m['id'] ? 'selected' : '' ?>><?= $this->e($m['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group" style="min-width:140px;">
<label><?= $this->te('Kategorie') ?></label>
<select name="category_id" class="form-control">
<option value=""><?= $this->te('— optional —') ?></option>
<?php foreach ($categories as $cat): ?>
<option value="<?= $cat['id'] ?>" <?= (int)$t['category_id'] === (int)$cat['id'] ? 'selected' : '' ?>><?= $this->e($cat['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group" style="flex:2;min-width:200px;">
<label><?= $this->te('Beschreibung') ?></label>
<input type="text" name="description" class="form-control" value="<?= $this->e($t['description'] ?? '') ?>" maxlength="500">
</div>
<div class="form-group" style="width:100%;flex-basis:100%;">
<label><?= $this->te('Technische Daten') ?></label>
<textarea name="specs" class="form-control" rows="2"><?= $this->e($t['specs'] ?? '') ?></textarea>
</div>
<div style="display:flex;gap:0.4rem;align-items:center;">
<button type="submit" class="btn btn-sm btn-primary"><?= $this->te('💾 Speichern') ?></button>
<button type="button" onclick="toggleEdit('type-<?= $t['id'] ?>')" class="btn btn-xs btn-default"><?= $this->te('Abbrechen') ?></button>
</div>
</div>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
</div>
<!-- Lightbox -->
<div id="ti-lightbox" onclick="this.style.display='none'">
<img id="ti-lb-img" src="" alt="" style="max-width:90vw;max-height:90vh;border-radius:0.25rem;">
</div>
<script>
var csrfMeta = document.querySelector('meta[name="csrf-token"]');
function getCsrf() { return csrfMeta ? csrfMeta.content : ''; }
function toggleEdit(id) {
var row = document.getElementById(id);
row.style.display = row.style.display === 'none' ? '' : 'none';
}
function uploadImage(input, typeId) {
if (!input.files || !input.files[0]) return;
var label = input.closest('label');
label.classList.add('loading');
var fd = new FormData();
fd.append('image', input.files[0]);
fetch('/admin/assets/types/' + typeId + '/images', {
method: 'POST',
headers: { 'X-CSRF-Token': getCsrf(), 'X-Requested-With': 'XMLHttpRequest' },
body: fd
})
.then(function(r) { return r.json(); })
.then(function(d) {
label.classList.remove('loading');
input.value = '';
if (!d.ok) { alert(d.error || 'Fehler beim Hochladen.'); return; }
var thumbs = document.getElementById('thumbs-' + typeId);
var div = document.createElement('div');
div.className = 'ti-thumb';
div.id = 'img-' + d.image_id;
div.innerHTML = '<img src="' + d.url + '" alt="" class="ti-thumb-img" onclick="openLightbox(this.src)">' +
'<button type="button" class="ti-del-btn" onclick="deleteImage(' + d.image_id + ',' + typeId + ')" title="Bild l\u00f6schen">\u00d7</button>';
thumbs.appendChild(div);
})
.catch(function(e) { label.classList.remove('loading'); input.value = ''; alert('Netzwerkfehler: ' + e.message); });
}
function deleteImage(imageId, typeId) {
if (!confirm('Bild wirklich l\u00f6schen?')) return;
fetch('/admin/assets/types/images/' + imageId + '/delete', {
method: 'POST',
headers: { 'X-CSRF-Token': getCsrf(), 'X-Requested-With': 'XMLHttpRequest', 'Content-Type': 'application/x-www-form-urlencoded' }
})
.then(function(r) { return r.json(); })
.then(function(d) {
if (d.ok) { var el = document.getElementById('img-' + imageId); if (el) el.remove(); }
else { alert(d.error || 'Fehler beim L\u00f6schen.'); }
})
.catch(function(e) { alert('Netzwerkfehler: ' + e.message); });
}
function openLightbox(src) {
var lb = document.getElementById('ti-lightbox');
document.getElementById('ti-lb-img').src = src;
lb.style.display = 'flex';
}
</script>