Quelltext des aktuellen Standes
<?php $this->layout('layouts/admin', [
'page_title' => 'Modelle / Typen',
'current_admin_page' => 'assets',
'breadcrumbs' => [['label' => 'Modelle / Typen']],
]); ?>
<!-- Neues Modell -->
<div class="card card-outline card-primary mb-3">
<div class="card-header">
<h3 class="card-title"><i class="fas fa-plus"></i> <?= $this->te('Neues Modell anlegen') ?></h3>
<p class="text-muted small mb-0"><?= $this->te('Modelle werden einem Hersteller zugeordnet und können einer Kategorie zugewiesen werden.') ?></p>
</div>
<div class="card-body">
<form action="/admin/assets/types" method="POST" class="form">
<input type="hidden" name="_csrf" value="<?= $this->csrf() ?>">
<div class="settings-grid" style="grid-template-columns:1fr 1fr 1fr;">
<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-621c-manufacturer_id"><?= $this->te('Hersteller *') ?></label>
<select id="f-621c-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-621c-category_id"><?= $this->te('Kategorie (optional)') ?></label>
<select id="f-621c-category_id" name="category_id" class="form-control">
<option value=""><?= $this->te('— Alle Kategorien —') ?></option>
<?php foreach ($categories as $cat): ?>
<option value="<?= $cat['id'] ?>"><?= $this->e($cat['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<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 class="form-group" style="grid-column:1/-1;">
<label><?= $this->te('Technische Daten') ?></label>
<textarea name="specs" class="form-control" rows="3" placeholder="<?= $this->te('CPU, RAM, Display, …') ?>"></textarea>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary"><?= $this->te('Modell anlegen') ?></button>
</div>
</div>
</form>
</div>
</div>
<!-- Liste -->
<div class="card card-outline card-primary mb-3">
<div class="card-header">
<h3 class="card-title"><i class="fas fa-list"></i> <?= $this->t('Modelle') ?> (<?= count($types) ?>)</h3>
<p class="text-muted small mb-0"><?= $this->te('Alle konfigurierten Modelle / Typen. Bilder können direkt hochgeladen werden.') ?></p>
</div>
<div class="card-body table-responsive p-0">
<?php if (empty($types)): ?>
<div style="padding:2rem;text-align:center;color:var(--gray-400);"><?= $this->te('Noch keine Modelle vorhanden.') ?></div>
<?php else: ?>
<div class="admin-table-wrap">
<table style="width:100%;border-collapse:collapse;font-size:0.875rem;min-width:400px;">
<thead>
<tr>
<?php foreach (['Name', 'Hersteller', 'Kategorie', 'Bilder', 'Assets', ''] as $th): ?>
<th scope="col" style="padding:0.65rem 1rem;text-align:left;font-size:0.72rem;font-weight:700;text-transform:uppercase;letter-spacing:0.04em;color:var(--gray-500);background:var(--gray-50);border-bottom:2px solid var(--gray-200);"><?= $th ?></th>
<?php endforeach; ?>
</tr>
</thead>
<tbody>
<?php foreach ($types as $t):
$imgs = $type_images[(int)$t['id']] ?? [];
?>
<tr>
<td style="padding:0.65rem 1rem;border-bottom:1px solid var(--gray-100);vertical-align:middle;">
<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="padding:0.65rem 1rem;border-bottom:1px solid var(--gray-100);vertical-align:middle;font-size:0.85rem;"><?= $this->e($t['manufacturer_name']) ?></td>
<td style="padding:0.65rem 1rem;border-bottom:1px solid var(--gray-100);vertical-align:middle;font-size:0.82rem;color:var(--gray-500);"><?= $t['category_name'] ? $this->e($t['category_name']) : '—' ?></td>
<td style="padding:0.65rem 1rem;border-bottom:1px solid var(--gray-100);vertical-align:top;padding-top:0.85rem;">
<!-- Vorhandene Bilder -->
<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)">
<button type="button" class="ti-del-btn"
onclick="deleteImage(<?= $img['id'] ?>, <?= $t['id'] ?>)"
title="<?= $this->te('Bild löschen') ?>">×</button>
</div>
<?php endforeach; ?>
</div>
<!-- Upload-Button -->
<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>
</td>
<td style="padding:0.65rem 1rem;border-bottom:1px solid var(--gray-100);vertical-align:middle;font-size:0.85rem;"><?= (int)$t['asset_count'] ?></td>
<td style="padding:0.65rem 1rem;border-bottom:1px solid var(--gray-100);vertical-align:middle;">
<div style="display:flex;gap:0.4rem;">
<a href="/admin/assets/types/<?= $t['id'] ?>/fields" class="btn btn-sm btn-secondary" title="<?= $this->te('Zusatzfelder verwalten') ?>"><?= $this->te('🔧 Felder') ?></a>
<button onclick="toggleTypeEdit(<?= $t['id'] ?>)" class="btn btn-sm btn-secondary"><?= $this->te('Bearbeiten') ?></button>
<?php if ((int)$t['asset_count'] === 0): ?>
<form method="POST" action="/admin/assets/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-sm btn-danger-outline">×</button>
</form>
<?php endif; ?>
</div>
</td>
</tr>
<!-- Bearbeiten-Zeile -->
<tr id="typeEdit-<?= $t['id'] ?>" style="display:none;background:var(--sky-50);">
<td colspan="6" style="padding:1rem 1.25rem;border-bottom:1px solid var(--gray-200);">
<form method="POST" action="/admin/assets/types/<?= $t['id'] ?>" style="display:flex;gap:0.75rem;flex-wrap:wrap;align-items:flex-end;">
<input type="hidden" name="_csrf" value="<?= $this->csrf() ?>">
<input type="hidden" name="_method" value="PUT">
<div style="display:flex;flex-direction:column;gap:0.25rem;flex:1;min-width:140px;">
<label style="font-size:0.75rem;font-weight:700;color:var(--gray-700);"><?= $this->te('Name') ?></label>
<input type="text" name="name" value="<?= $this->e($t['name']) ?>" class="form-control" required>
</div>
<div style="display:flex;flex-direction:column;gap:0.25rem;min-width:150px;">
<label style="font-size:0.75rem;font-weight:700;color:var(--gray-700);"><?= $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 style="display:flex;flex-direction:column;gap:0.25rem;min-width:150px;">
<label style="font-size:0.75rem;font-weight:700;color:var(--gray-700);"><?= $this->te('Kategorie') ?></label>
<select name="category_id" class="form-control">
<option value=""><?= $this->te('— Alle —') ?></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 style="display:flex;flex-direction:column;gap:0.25rem;flex:2;min-width:200px;">
<label style="font-size:0.75rem;font-weight:700;color:var(--gray-700);"><?= $this->te('Beschreibung') ?></label>
<input type="text" name="description" value="<?= $this->e($t['description'] ?? '') ?>" class="form-control">
</div>
<div style="display:flex;flex-direction:column;gap:0.25rem;width:100%;">
<label style="font-size:0.75rem;font-weight:700;color:var(--gray-700);"><?= $this->te('Technische Daten') ?></label>
<textarea name="specs" class="form-control" rows="2"><?= $this->e($t['specs'] ?? '') ?></textarea>
</div>
<button type="submit" class="btn btn-primary"><?= $this->te('💾 Speichern') ?></button>
<button type="button" onclick="toggleTypeEdit(<?= $t['id'] ?>)" class="btn btn-secondary"><?= $this->te('Abbrechen') ?></button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
</div>
<div class="card card-outline card-primary mb-3">
<div class="card-body">
<div style="display:flex;gap:0.75rem;flex-wrap:wrap;">
<a href="/admin/assets/categories" class="btn btn-default"><i class="fas fa-arrow-left"></i> <?= $this->te('Kategorien') ?></a>
<a href="/admin/assets/manufacturers" class="btn btn-default"><i class="fas fa-industry"></i> <?= $this->te('Hersteller') ?></a>
<a href="/inventory" class="btn btn-primary"><i class="fas fa-boxes"></i> <?= $this->te('Zum Inventar') ?></a>
</div>
</div>
</div>
<style>
.ti-thumbs { display:flex; flex-wrap:wrap; gap:0.35rem; margin-bottom:0.4rem; }
.ti-thumb { position:relative; display:inline-block; }
.ti-thumb-img { width:52px; height:52px; 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:-5px; right:-5px; width:16px; height:16px; border-radius:50%;
background:var(--red-600); color:var(--color-on-accent); border:none; cursor:pointer; font-size:0.65rem; line-height:16px;
text-align:center; padding:0; }
.ti-del-btn:hover { background:var(--red-700); }
.ti-upload-label { display:inline-flex; align-items:center; gap:0.2rem; padding:0.22rem 0.55rem;
border:1.5px dashed var(--gray-300); border-radius:0.4rem; font-size:0.75rem; font-weight:600;
color:var(--gray-500); cursor:pointer; transition:all 0.15s; white-space:nowrap; }
.ti-upload-label:hover { border-color:var(--blue-600); color:var(--blue-600); background:var(--sky-50); }
.ti-upload-label.loading { opacity:0.5; pointer-events:none; }
</style>
<script>
var csrf = document.querySelector('meta[name="csrf-token"]');
function getCsrf() { return csrf ? csrf.content : ''; }
function toggleTypeEdit(id) {
var row = document.getElementById('typeEdit-' + 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');
if (!lb) {
lb = document.createElement('div');
lb.id = 'ti-lightbox';
lb.style.cssText = 'display:flex;position:fixed;inset:0;background:rgba(0,0,0,0.85);z-index:9999;align-items:center;justify-content:center;cursor:pointer;';
lb.innerHTML = '<img style="max-width:90vw;max-height:90vh;border-radius:0.5rem;">';
lb.addEventListener('click', function() { lb.style.display = 'none'; });
document.body.appendChild(lb);
}
lb.querySelector('img').src = src;
lb.style.display = 'flex';
}
</script>