OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

edit.php

25 KB
<?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 &mdash; 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 &amp; 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&uuml;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&ouml;nnen Mitarbeitende dieses Ger&auml;t selbst reservieren &ndash; im Self-Service-Portal und &uuml;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&ouml;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&ouml;r hinzugef&uuml;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)">&#10005;</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&ouml;r hinzuf&uuml;gen') ?>
                            </button>
                            <p class="lde-hint"><?= $this->te('Jedes Zubeh&ouml;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&auml;t l&ouml;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 &amp; Typ') ?></div>
                                <div class="lde-help-text"><?= $this->te('W&auml;hlen Sie eine eindeutige Bezeichnung. Der Typ wird f&uuml;r Filter und Kalenderanzeige genutzt.') ?></div>
                            </div>
                            <div class="lde-help-item">
                                <div class="lde-help-label"><?= $this->te('Zubeh&ouml;r') ?></div>
                                <div class="lde-help-text"><?= $this->te('&Auml;nderungen am Zubeh&ouml;r gelten f&uuml;r zuk&uuml;nftige Ausleihen. Bereits laufende Ausleihen bleiben unber&uuml;hrt.') ?></div>
                            </div>
                            <div class="lde-help-item">
                                <div class="lde-help-label"><?= $this->te('Ger&auml;t l&ouml;schen') ?></div>
                                <div class="lde-help-text"><?= $this->te('Beim L&ouml;schen werden alle Ausleihvorg&auml;nge dieses Ger&auml;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)">&#10005;</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>