OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

form.php

19 KB
<?php
$isEdit = isset($topic) && $topic !== null;
$pageTitle = $isEdit ? 'Themengebiet bearbeiten' : 'Neues Themengebiet';

$this->layout('layouts/main', [
    'page_title' => $pageTitle,
    'breadcrumbs' => [], // Kein Breadcrumb
    'full_width' => true
]);

// Parent ID von verschiedenen Quellen ermitteln
$selectedParentId = $topic['parent_id'] ?? $parent['id'] ?? (isset($_GET['parent_id']) ? (int)$_GET['parent_id'] : null) ?? (isset($_POST['parent_id']) ? (int)$_POST['parent_id'] : null);
?>

<div class="topic-form-fullwidth">
    <!-- Content Header -->
    <div class="content-header">
        <div>
            <h1><?= $this->e($pageTitle) ?></h1>
            <ol class="breadcrumb">
                <li class="breadcrumb-item"><a href="/"><?= $this->te('Home') ?></a></li>
                <li class="breadcrumb-item"><a href="/topics"><?= $this->te('Themengebiete') ?></a></li>
                <?php if ($isEdit): ?>
                <li class="breadcrumb-item"><a href="/topics/<?= $topic['id'] ?>"><?= $this->e($topic['name']) ?></a></li>
                <li class="breadcrumb-item active"><?= $this->te('Bearbeiten') ?></li>
                <?php else: ?>
                <li class="breadcrumb-item active"><?= $this->te('Neues Themengebiet') ?></li>
                <?php endif; ?>
            </ol>
        </div>
    </div>

    <div class="form-body">
        <form action="<?= $isEdit ? '/topics/' . $topic['id'] : '/topics' ?>" method="POST" class="topic-form" enctype="multipart/form-data">
            <input type="hidden" name="_csrf" value="<?= $this->csrf() ?>">
            <?php if ($isEdit): ?>
                <input type="hidden" name="_method" value="PUT">
            <?php endif; ?>

            <div class="form-grid">
                <!-- Linke Spalte: Hauptformular -->
                <div class="form-main">
                    <!-- Grunddaten -->
                    <div class="form-card">
                        <div class="card-header">
                            <h2><span aria-hidden="true">&#128221;</span> <?= $this->te('Grunddaten') ?></h2>
                        </div>
                        <div class="card-body">
                            <div class="form-group">
                                <label for="name" class="form-label required"><?= $this->te('Name des Themengebiets') ?></label>
                                <input type="text" id="name" name="name" class="form-control"
                                       value="<?= $this->e($topic['name'] ?? $_POST['name'] ?? '') ?>"
                                       required maxlength="255" autofocus
                                       placeholder="<?= $this->te('z.B. Netzwerk, Server, Anwendungen...') ?>">
                                <small class="form-help"><?= $this->te('Der Name wird in der Navigation und &Uuml;bersicht angezeigt') ?></small>
                            </div>

                            <div class="form-group">
                                <label for="description" class="form-label"><?= $this->te('Beschreibung') ?></label>
                                <textarea id="description" name="description" class="form-control" rows="4"
                                          maxlength="1000"
                                          placeholder="<?= $this->te('Kurze Beschreibung des Themengebiets...') ?>"><?= $this->e($topic['description'] ?? $_POST['description'] ?? '') ?></textarea>
                                <small class="form-help"><?= $this->te('Optionale Beschreibung zur besseren Orientierung (max. 1000 Zeichen)') ?></small>
                            </div>
                        </div>
                    </div>

                    <!-- Einordnung -->
                    <div class="form-card">
                        <div class="card-header">
                            <h2><span aria-hidden="true">&#128193;</span> <?= $this->te('Einordnung') ?></h2>
                        </div>
                        <div class="card-body">
                            <div class="form-group">
                                <?php $gewaehlteStandorte = array_map('intval', $topic['location_ids'] ?? []); ?>
                                <label for="location_ids" class="form-label"><?= $this->te('Standorte') ?></label>
                                <select id="location_ids" name="location_ids[]" class="form-control form-select"
                                        multiple size="8">
                                    <?php foreach ($locations ?? [] as $loc): ?>
                                        <option value="<?= (int)$loc['id'] ?>"
                                            <?= in_array((int)$loc['id'], $gewaehlteStandorte, true) ? 'selected' : '' ?>>
                                            <?= str_repeat('&nbsp;&nbsp;', (int)($loc['_depth'] ?? 0)) . $this->e($loc['name']) ?>
                                        </option>
                                    <?php endforeach; ?>
                                </select>
                                <small class="form-help">
                                    <?= $this->te('Steuert, wer dieses Themengebiet sieht – im Backend wie im Self-Service-Portal. Nichts gew&auml;hlt = alle Standorte. Mehrere Standorte sind m&ouml;glich, wenn dieselbe Anleitung f&uuml;r mehrere Kunden gilt. Ein Oberstandort gilt auch f&uuml;r dessen Unterstandorte; untergeordnete Themengebiete ohne eigene Auswahl erben diese.') ?>
                                </small>
                            </div>

                            <div class="form-group">
                                <label for="parent_id" class="form-label"><?= $this->te('&Uuml;bergeordnetes Themengebiet') ?></label>
                                <select id="parent_id" name="parent_id" class="form-control form-select">
                                    <option value=""><?= $this->te('Kein &uuml;bergeordnetes Themengebiet (Root-Ebene)') ?></option>
                                    <?php foreach ($all_topics ?? [] as $t): ?>
                                        <?php
                                        // Aktuelles Topic und seine Kinder ausschlie&szlig;en bei Bearbeitung
                                        if ($isEdit && (
                                            $t['id'] === $topic['id'] ||
                                            str_starts_with($t['path'], $topic['path'] . '/')
                                        )) {
                                            continue;
                                        }
                                        $indent = str_repeat('&mdash; ', $t['depth']);
                                        $selected = ($t['id'] == $selectedParentId) ? 'selected' : '';
                                        ?>
                                        <option value="<?= $t['id'] ?>" <?= $selected ?>>
                                            <?= $indent ?><?= $this->e($t['name']) ?>
                                        </option>
                                    <?php endforeach; ?>
                                </select>
                                <small class="form-help"><?= $this->te('W&auml;hlen Sie ein &uuml;bergeordnetes Themengebiet f&uuml;r hierarchische Organisation') ?></small>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- Rechte Spalte: Sidebar -->
                <div class="form-sidebar">
                    <!-- Titelbild -->
                    <div class="form-card">
                        <div class="card-header">
                            <h2><span aria-hidden="true">&#128247;</span> <?= $this->te('Titelbild') ?></h2>
                        </div>
                        <div class="card-body">
                            <?php if ($isEdit && !empty($topic['image_path'])): ?>
                                <div class="current-image">
                                    <img src="/uploads/topics/<?= $this->e($topic['image_path']) ?>"
                                         alt="<?= $this->e($topic['name']) ?>"
                                         class="image-preview">
                                    <div class="image-actions">
                                        <label class="btn btn-sm btn-default">
                                            <input type="file" name="image" accept="image/jpeg,image/png,image/webp" class="hidden-input" id="image-input">
                                            <?= $this->te('&auml;ndern') ?>
                                        </label>
                                        <label class="delete-image-label">
                                            <input type="checkbox" name="delete_image" value="1">
                                            <?= $this->te('entfernen') ?>
                                        </label>
                                    </div>
                                </div>
                            <?php else: ?>
                                <div class="image-dropzone" id="image-dropzone">
                                    <input type="file" name="image" accept="image/jpeg,image/png,image/webp" class="hidden-input" id="image-input">
                                    <div class="dropzone-content">
                                        <span class="dropzone-icon" aria-hidden="true">&#128247;</span>
                                        <span class="dropzone-text"><?= $this->te('Bild hochladen') ?></span>
                                        <span class="dropzone-hint"><?= $this->te('JPG, PNG oder WebP') ?><br><?= $this->te('(max. 2MB)') ?></span>
                                    </div>
                                    <img src="" alt="" class="image-preview hidden" id="image-preview">
                                </div>
                            <?php endif; ?>
                            <small class="form-help"><?= $this->te('Wird anstelle des Ordner-Icons angezeigt') ?></small>
                        </div>
                    </div>

                    <!-- Aktionen -->
                    <div class="form-card actions-card">
                        <div class="card-body">
                            <button type="submit" class="btn btn-primary btn-block">
                                <i class="fas fa-save"></i>
                                <?= $isEdit ? 'Speichern' : 'Erstellen' ?>
                            </button>
                            <a href="<?= $isEdit ? '/topics/' . $topic['id'] : '/topics' ?>" class="btn btn-default btn-block">
                                <?= $this->te('Abbrechen') ?>
                            </a>
                        </div>
                    </div>

                </div>
            </div>
        </form>

        <!-- Gefahrenzone: Separates Formular AUSSERHALB des Hauptformulars -->
        <?php if ($isEdit): ?>
        <div class="form-grid" style="max-width: 1000px; margin: 0 auto;">
            <div></div>
            <div class="form-sidebar">
                <div class="form-card danger-card">
                    <div class="card-header">
                        <h2><span aria-hidden="true">&#9888;</span> <?= $this->te('Gefahrenzone') ?></h2>
                    </div>
                    <div class="card-body">
                        <?php $canDelete = ($topic['page_count'] ?? 0) === 0; ?>
                        <?php if (!$canDelete): ?>
                            <p class="danger-info">
                                <?= $this->t('<strong>L&ouml;schen nicht m&ouml;glich:</strong><br> Enth&auml;lt noch') ?> <?= $this->te('{anzahl} Seite(n).', ['anzahl' => $topic['page_count']]) ?>
                            </p>
                        <?php else: ?>
                            <p class="danger-info">
                                <?= $this->te('Das Themengebiet wird archiviert und ist nicht mehr sichtbar.') ?>
                            </p>
                        <?php endif; ?>
                        <form action="/topics/<?= $topic['id'] ?>" method="POST"
                              onsubmit="return confirm('Sind Sie sicher, dass Sie dieses Themengebiet l\u00f6schen m\u00f6chten?');">
                            <input type="hidden" name="_csrf" value="<?= $this->csrf() ?>">
                            <input type="hidden" name="_method" value="DELETE">
                            <button type="submit" class="btn btn-danger btn-block btn-sm" <?= !$canDelete ? 'disabled' : '' ?>>
                                <i class="fas fa-trash"></i> <?= $this->te('L&ouml;schen') ?>
                            </button>
                        </form>
                    </div>
                </div>
            </div>
        </div>
        <?php endif; ?>
    </div>
</div>

<script>
document.addEventListener('DOMContentLoaded', function() {
    const imageInput = document.getElementById('image-input');
    const dropzone = document.getElementById('image-dropzone');
    const preview = document.getElementById('image-preview');

    if (imageInput && dropzone) {
        // Neues Topic: Dropzone-Modus
        dropzone.addEventListener('click', function(e) {
            if (e.target !== imageInput) {
                imageInput.click();
            }
        });

        dropzone.addEventListener('dragover', function(e) {
            e.preventDefault();
            dropzone.classList.add('dragover');
        });

        dropzone.addEventListener('dragleave', function() {
            dropzone.classList.remove('dragover');
        });

        dropzone.addEventListener('drop', function(e) {
            e.preventDefault();
            dropzone.classList.remove('dragover');
            if (e.dataTransfer.files.length) {
                imageInput.files = e.dataTransfer.files;
                showPreview(e.dataTransfer.files[0]);
            }
        });

        imageInput.addEventListener('change', function() {
            if (this.files.length) {
                showPreview(this.files[0]);
            }
        });

        function showPreview(file) {
            if (!file.type.startsWith('image/')) return;
            const reader = new FileReader();
            reader.onload = function(e) {
                preview.src = e.target.result;
                preview.classList.remove('hidden');
                dropzone.querySelector('.dropzone-content').classList.add('hidden');
            };
            reader.readAsDataURL(file);
        }
    } else if (imageInput) {
        // Edit-Modus: Kein Dropzone, aber trotzdem Preview aktualisieren
        imageInput.addEventListener('change', function() {
            if (this.files.length) {
                var file = this.files[0];
                if (!file.type.startsWith('image/')) return;
                var reader = new FileReader();
                reader.onload = function(e) {
                    var existingPreview = document.querySelector('.current-image .image-preview');
                    if (existingPreview) {
                        existingPreview.src = e.target.result;
                    }
                };
                reader.readAsDataURL(file);
            }
        });
    }
});
</script>

<style>
.topic-form-fullwidth {
}


/* Form Body */
.form-body {
    padding: 2rem;
    background: var(--slate-50);
    min-height: calc(100vh - 250px);
}

.form-grid {
    display: grid;
    grid-template-columns: 1fr 320px;
    gap: 1.5rem;
    max-width: 1000px;
    margin: 0 auto;
}

/* Form Cards */
.form-card {
    background: var(--color-bg);
    border-radius: 0.25rem;
    box-shadow: 0 1px 3px rgba(0,0,0,0.05);
    overflow: hidden;
    margin-bottom: 1.5rem;
}

.form-card:last-child {
    margin-bottom: 0;
}

.card-header {
    padding: 1rem 1.25rem;
    background: var(--gray-50);
    border-bottom: 1px solid var(--gray-200);
}

.card-header h2 {
    margin: 0;
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--gray-900);
}

.card-body {
    padding: 1.25rem;
}

/* Form Elements */
.form-group:last-child {
    margin-bottom: 0;
}

.form-label {
    display: block;
    font-weight: 500;
    color: var(--gray-700);
    margin-bottom: 0.5rem;
    font-size: 0.9rem;
}

.form-label.required::after {
    content: " *";
    color: var(--red-600);
}

textarea.form-control {
    resize: vertical;
    min-height: 100px;
}

.form-select {
    appearance: none;
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e");
    background-position: right 0.75rem center;
    background-repeat: no-repeat;
    background-size: 1.25rem;
    padding-right: 2.5rem;
}

.form-help {
    display: block;
    margin-top: 0.375rem;
    font-size: 0.8rem;
    color: var(--gray-500);
}

/* Image Upload */
.image-dropzone {
    position: relative;
    border: 2px dashed var(--gray-300);
    border-radius: 0.25rem;
    padding: 1.5rem;
    text-align: center;
    cursor: pointer;
    transition: all 0.15s;
    background: var(--gray-50);
    margin-bottom: 0.5rem;
}

.image-dropzone:hover,
.image-dropzone.dragover {
    border-color: var(--color-primary);
    background: var(--blue-50);
}

.hidden-input {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
}

.dropzone-content {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.25rem;
}

.dropzone-icon {
    font-size: 2rem;
    opacity: 0.6;
}

.dropzone-text {
    font-weight: 500;
    color: var(--gray-700);
    font-size: 0.9rem;
}

.dropzone-hint {
    font-size: 0.75rem;
    color: var(--gray-400);
    line-height: 1.4;
}

.image-preview {
    max-width: 100%;
    max-height: 150px;
    border-radius: 0.25rem;
    margin: 0 auto;
    display: block;
}

.image-preview.hidden,
.dropzone-content.hidden {
    display: none;
}

.current-image {
    text-align: center;
    margin-bottom: 0.5rem;
}

.current-image .image-preview {
    width: 100%;
    height: 120px;
    object-fit: cover;
    border-radius: 0.25rem;
    margin-bottom: 0.75rem;
}

.image-actions {
    display: flex;
    justify-content: center;
    gap: 1rem;
    align-items: center;
}

.image-actions .hidden-input {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
}

.delete-image-label {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    font-size: 0.8rem;
    color: var(--red-600);
    cursor: pointer;
}

.delete-image-label input {
    width: 0.9rem;
    height: 0.9rem;
}

/* Actions Card */
.actions-card .card-body {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

/* Danger Card */
.danger-card {
    border: 1px solid var(--red-200);
}

.danger-card .card-header {
    background: var(--red-50);
}

.danger-card .card-header h2 {
    color: var(--red-600);
}

.danger-info {
    font-size: 0.85rem;
    color: var(--red-800);
    margin: 0 0 1rem 0;
    line-height: 1.5;
}

/* Buttons – lokale Überschreibungen für diesen Kontext */
.actions-card .btn-block + .btn-block {
    margin-top: 0;
}

/* Responsive */
@media (max-width: 900px) {
    .form-grid {
        grid-template-columns: 1fr;
    }

    .form-sidebar {
        order: -1;
    }
}

@media (max-width: 640px) {
    .form-body {
        padding: 1rem;
    }
}
</style>