Quelltext des aktuellen Standes
<?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('✚ 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ä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ä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('📁 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">📁</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 ä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ä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ö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>