OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

types.php

17 KB
<?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') ?>">&#215;</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>