OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

categories.php

15 KB
<?php
$this->layout('layouts/main', [
    'page_title' => 'Service-Kategorien',
    'breadcrumbs' => [],
    'full_width' => true,
    'current_route' => 'services',
]);

$categories = $categories ?? [];
$csrfToken = $this->csrf();
$view = $this;

// Grosse Farbpalette — 7 Reihen x 7 Farben = 49 Farben
$colorPalette = [
    // Rot-Toene
    '#fca5a5', '#f87171', '#ef4444', '#dc2626', '#b91c1c', '#991b1b', '#7f1d1d',
    // Orange/Amber
    '#fdba74', '#fb923c', '#f97316', '#ea580c', '#f59e0b', '#d97706', '#b45309',
    // Gelb/Lime
    '#fde047', '#facc15', '#eab308', '#ca8a04', '#a3e635', '#84cc16', '#65a30d',
    // Gruen
    '#86efac', '#4ade80', '#22c55e', '#16a34a', '#15803d', '#166534', '#14532d',
    // Cyan/Teal
    '#67e8f9', '#22d3ee', '#06b6d4', '#0891b2', '#5eead4', '#14b8a6', '#0d9488',
    // Blau
    '#93c5fd', '#60a5fa', '#3b82f6', '#2563eb', '#1d4ed8', '#1e40af', '#1e3a8a',
    // Violett/Pink
    '#c4b5fd', '#a78bfa', '#8b5cf6', '#7c3aed', '#f0abfc', '#e879f9', '#d946ef',
    // Grau/Slate
    '#cbd5e1', '#94a3b8', '#64748b', '#475569', '#334155', '#1e293b', '#0f172a',
];
?>

<div class="cat-fullwidth">
    <!-- Content Header -->
    <div class="content-header">
        <div>
            <h1><?= $this->te('Service-Kategorien') ?></h1>
            <ol class="breadcrumb">
                <li class="breadcrumb-item"><a href="/"><?= $this->te('Home') ?></a></li>
                <li class="breadcrumb-item"><a href="/services"><?= $this->te('IT-Services') ?></a></li>
                <li class="breadcrumb-item active"><?= $this->te('Kategorien') ?></li>
            </ol>
        </div>
    </div>

    <div class="cat-body">
        <div class="cat-container">

            <!-- Neue Kategorie -->
            <div class="cat-card">
                <div class="cat-card-header">
                    <h2><?= $this->te('&#10010; Neue Kategorie erstellen') ?></h2>
                </div>
                <div class="cat-card-body">
                    <form method="POST" action="/services/categories">
                        <input type="hidden" name="_csrf" value="<?= $csrfToken ?>">
                        <div class="cat-create-row">
                            <div class="cat-color-wrap">
                                <input type="hidden" name="color" id="new-cat-color" value="">
                                <button type="button" class="cat-color-trigger" onclick="togglePicker('new-cat')" title="<?= $this->te('Farbe ausw&auml;hlen') ?>">
                                    <span class="cat-swatch" id="new-cat-swatch"></span>
                                </button>
                                <div class="cat-picker" id="new-cat-picker">
                                    <div class="cat-picker-title"><?= $this->te('Farbe w&auml;hlen') ?></div>
                                    <div class="cat-picker-grid">
                                        <?php foreach ($colorPalette as $c): ?>
                                            <button type="button" class="cat-picker-dot" style="background:<?= $c ?>" onclick="pickColor('new-cat', '<?= $c ?>')" title="<?= $c ?>"></button>
                                        <?php endforeach; ?>
                                    </div>
                                    <div class="cat-picker-footer">
                                        <button type="button" class="cat-picker-reset" onclick="pickColor('new-cat', '')"><?= $this->te('Keine Farbe') ?></button>
                                    </div>
                                </div>
                            </div>
                            <input type="text" name="name" class="cat-input" placeholder="<?= $this->te('Kategoriename eingeben...') ?>" required>
                            <button type="submit" class="btn btn-primary"><?= $this->te('Erstellen') ?></button>
                        </div>
                    </form>
                </div>
            </div>

            <!-- Bestehende Kategorien -->
            <div class="cat-card">
                <div class="cat-card-header">
                    <h2><?= $this->te('&#128193; Bestehende Kategorien') ?></h2>
                    <span class="status-pill status-pill--neutral"><?= count($categories) ?></span>
                </div>
                <?php if (empty($categories)): ?>
                    <div class="cat-card-body">
                        <div class="empty-state" style="border:none;background:transparent;">
                            <div class="empty-icon">&#128193;</div>
                            <p><?= $this->te('Noch keine Kategorien vorhanden.') ?></p>
                            <p class="cat-empty-hint"><?= $this->te('Erstellen Sie oben die erste Kategorie, um Ihre Services zu gruppieren.') ?></p>
                        </div>
                    </div>
                <?php else: ?>
                    <div class="cat-list">
                        <?php foreach ($categories as $cat): ?>
                            <form method="POST" action="/services/categories/<?= $cat['id'] ?>" class="cat-row">
                                <input type="hidden" name="_csrf" value="<?= $csrfToken ?>">
                                <input type="hidden" name="_method" value="PUT">

                                <div class="cat-color-wrap">
                                    <input type="hidden" name="color" id="cat-<?= $cat['id'] ?>-color" value="<?= $view->e($cat['color'] ?? '') ?>">
                                    <button type="button" class="cat-color-trigger" onclick="togglePicker('cat-<?= $cat['id'] ?>')" title="<?= $this->te('Farbe &auml;ndern') ?>">
                                        <span class="cat-swatch" id="cat-<?= $cat['id'] ?>-swatch" <?php if (!empty($cat['color'])): ?>style="background:<?= $view->e($cat['color']) ?>"<?php endif; ?>></span>
                                    </button>
                                    <div class="cat-picker" id="cat-<?= $cat['id'] ?>-picker">
                                        <div class="cat-picker-title"><?= $this->te('Farbe w&auml;hlen') ?></div>
                                        <div class="cat-picker-grid">
                                            <?php foreach ($colorPalette as $c): ?>
                                                <button type="button" class="cat-picker-dot <?= ($cat['color'] ?? '') === $c ? 'active' : '' ?>" style="background:<?= $c ?>" onclick="pickColor('cat-<?= $cat['id'] ?>', '<?= $c ?>')" title="<?= $c ?>"></button>
                                            <?php endforeach; ?>
                                        </div>
                                        <div class="cat-picker-footer">
                                            <button type="button" class="cat-picker-reset" onclick="pickColor('cat-<?= $cat['id'] ?>', '')"><?= $this->te('Keine Farbe') ?></button>
                                        </div>
                                    </div>
                                </div>

                                <div class="cat-name-wrap">
                                    <input type="text" name="name" class="cat-input cat-input-inline" value="<?= $view->e($cat['name']) ?>" required>
                                </div>

                                <div class="cat-meta">
                                    <span class="cat-meta-count"><?= (int)($cat['service_count'] ?? 0) ?></span>
                                    <span class="cat-meta-label">Service<?= ((int)($cat['service_count'] ?? 0)) !== 1 ? 's' : '' ?></span>
                                </div>

                                <div class="cat-row-actions">
                                    <button type="submit" class="btn btn-primary btn-sm"><?= $this->te('Speichern') ?></button>
                                    <?php if (((int)($cat['service_count'] ?? 0)) === 0): ?>
                                        <button type="button" class="btn btn-outline-danger btn-sm" onclick="deleteCat(<?= $cat['id'] ?>, '<?= $csrfToken ?>')"><?= $this->te('L&ouml;schen') ?></button>
                                    <?php endif; ?>
                                </div>
                            </form>
                        <?php endforeach; ?>
                    </div>
                <?php endif; ?>
            </div>

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

<script>
function togglePicker(prefix) {
    var picker = document.getElementById(prefix + '-picker');
    document.querySelectorAll('.cat-picker').forEach(function(p) {
        if (p !== picker) p.classList.remove('open');
    });
    picker.classList.toggle('open');
}

function pickColor(prefix, color) {
    document.getElementById(prefix + '-color').value = color;
    var swatch = document.getElementById(prefix + '-swatch');
    if (color) {
        swatch.style.background = color;
    } else {
        swatch.style.background = '';
        swatch.removeAttribute('style');
    }
    document.getElementById(prefix + '-picker').classList.remove('open');
}

document.addEventListener('click', function(e) {
    if (!e.target.closest('.cat-color-wrap')) {
        document.querySelectorAll('.cat-picker').forEach(function(p) {
            p.classList.remove('open');
        });
    }
});

function deleteCat(catId, csrf) {
    if (!confirm('Kategorie wirklich l\u00f6schen?')) return;
    var form = document.createElement('form');
    form.method = 'POST';
    form.action = '/services/categories/' + catId;
    form.innerHTML = '<input type="hidden" name="_csrf" value="' + csrf + '">'
        + '<input type="hidden" name="_method" value="DELETE">';
    document.body.appendChild(form);
    form.submit();
}
</script>

<style>
/* ============================================ */
/* Service-Kategorien — Vollstaendige Styles    */
/* (Alle Klassen selbst definiert, keine        */
/*  Abhaengigkeit von anderen Templates)        */
/* ============================================ */

/* Fullwidth Reset */
.cat-fullwidth {
}


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

/* Container */
.cat-container {
    max-width: 860px;
    margin: 0 auto;
}

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

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

.cat-card-header {
    padding: 1rem 1.25rem;
    background: var(--gray-50);
    border-bottom: 1px solid var(--gray-200);
    border-radius: 0.75rem 0.75rem 0 0;
    display: flex;
    align-items: center;
    justify-content: space-between;
}

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

.cat-card-body {
    padding: 1.25rem;
}


/* Inputs */
.cat-input {
    width: 100%;
    padding: 0.625rem 0.875rem;
    border: 1px solid var(--gray-300);
    border-radius: 0.5rem;
    font-size: 0.95rem;
    color: var(--gray-900);
    background: var(--color-bg);
    outline: none;
    transition: border-color 0.15s, box-shadow 0.15s;
    font-family: inherit;
}

.cat-input:focus {
    border-color: var(--blue-600);
    box-shadow: 0 0 0 3px rgba(37,99,235,0.1);
}

.cat-input::placeholder {
    color: var(--gray-400);
}

.cat-input-inline {
    border-color: transparent;
    background: transparent;
    font-weight: 500;
    padding: 0.4rem 0.625rem;
}

.cat-input-inline:hover {
    border-color: var(--gray-200);
}

.cat-input-inline:focus {
    border-color: var(--blue-600);
    background: var(--color-bg);
    box-shadow: 0 0 0 3px rgba(37,99,235,0.1);
}
/* Neue Kategorie Row */
.cat-create-row {
    display: flex;
    align-items: center;
    gap: 0.75rem;
}

.cat-create-row .cat-input {
    flex: 1;
}

/* Color Trigger */
.cat-color-wrap {
    position: relative;
    flex-shrink: 0;
}

.cat-color-trigger {
    width: 38px;
    height: 38px;
    padding: 0;
    background: var(--gray-50);
    border: 1px solid var(--gray-300);
    border-radius: 0.5rem;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all 0.15s;
}

.cat-color-trigger:hover {
    border-color: var(--gray-400);
    background: var(--gray-100);
}

.cat-swatch {
    width: 22px;
    height: 22px;
    border-radius: 5px;
    background: var(--gray-200);
    border: 1px solid rgba(0,0,0,0.08);
    display: block;
}

/* Color Picker Dropdown */
.cat-picker {
    position: absolute;
    top: calc(100% + 8px);
    left: 0;
    z-index: 200;
    background: var(--color-bg);
    border: 1px solid var(--gray-200);
    border-radius: 0.75rem;
    box-shadow: 0 16px 48px rgba(0,0,0,0.2);
    padding: 1rem;
    width: 290px;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity 0.15s, visibility 0.15s;
}

.cat-picker.open {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
}

.cat-picker-title {
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--gray-500);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    margin-bottom: 0.625rem;
}

.cat-picker-grid {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 6px;
}

.cat-picker-dot {
    width: 30px;
    height: 30px;
    border-radius: 6px;
    border: 2px solid transparent;
    cursor: pointer;
    transition: all 0.1s;
    padding: 0;
}

.cat-picker-dot:hover {
    transform: scale(1.2);
    border-color: rgba(0,0,0,0.3);
    z-index: 1;
}

.cat-picker-dot.active {
    border-color: var(--gray-900);
    box-shadow: 0 0 0 2px var(--color-bg), 0 0 0 4px var(--gray-900);
    transform: scale(1.1);
}

.cat-picker-footer {
    margin-top: 0.625rem;
    padding-top: 0.5rem;
    border-top: 1px solid var(--gray-100);
}

.cat-picker-reset {
    width: 100%;
    padding: 0.4rem;
    background: var(--gray-100);
    border: 1px solid var(--gray-200);
    border-radius: 0.375rem;
    font-size: 0.8rem;
    color: var(--gray-500);
    cursor: pointer;
    transition: background 0.15s;
    font-family: inherit;
}

.cat-picker-reset:hover {
    background: var(--gray-200);
}

/* Kategorie-Liste */
.cat-list {
    /* KEIN overflow: hidden! */
}

.cat-row {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.875rem 1.25rem;
    border-bottom: 1px solid var(--gray-100);
    transition: background 0.1s;
}

.cat-row:last-child {
    border-bottom: none;
}

.cat-row:hover {
    background: var(--slate-50);
}

.cat-name-wrap {
    flex: 1;
    min-width: 0;
}

.cat-meta {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    flex-shrink: 0;
}

.cat-meta-count {
    font-size: 0.85rem;
    font-weight: 700;
    color: var(--gray-600);
    background: var(--gray-100);
    width: 28px;
    height: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
}

.cat-meta-label {
    font-size: 0.78rem;
    color: var(--gray-400);
}

.cat-row-actions {
    display: flex;
    gap: 0.5rem;
    flex-shrink: 0;
}


.cat-empty-hint {
    margin-top: 0.5rem !important;
    color: var(--gray-400) !important;
    font-size: 0.85rem !important;
}

/* Responsive */
@media (max-width: 640px) {
    .cat-body {
        padding: 1rem;
    }

    .cat-create-row {
        flex-direction: column;
        align-items: stretch;
    }

    .cat-create-row .cat-color-wrap {
        align-self: flex-start;
    }

    .cat-row {
        flex-wrap: wrap;
        gap: 0.5rem;
    }

    .cat-name-wrap {
        flex: 1 1 100%;
        order: 2;
    }

    .cat-meta {
        order: 3;
    }

    .cat-row-actions {
        order: 4;
        margin-left: auto;
    }

    .cat-picker {
        left: 0;
        transform: none;
    }
}
</style>