Files
intranet/web/intranet_repo/projects/adminpanel/admin_kalli.php
T

1073 lines
40 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php /* Name: admin_kalli.php
Beschreibung: Kalli-Terminarten-Verwaltung im Adminpanel
- Kategorien verwalten (CRUD)
- Terminarten pro Kategorie verwalten (CRUD) */
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/initialise.php';
// Admin-Check
$stmt = $pdo->prepare("SELECT \"Admin\" FROM user_permissions WHERE user_id = ?");
$stmt->execute([$_SESSION["user_id"]]);
$isAdmin = $stmt->fetchColumn();
if ((int)$isAdmin !== 1) {
header("Location: {$g_httpHomePath}");
exit();
}
// Berechtigungsrollen aus user_permissions laden
function getPermissionRoles(PDO $pdo): array {
$roles = [];
$result = $pdo->query("SELECT column_name FROM information_schema.columns WHERE table_name = 'user_permissions' AND table_schema = 'public'");
while ($column = $result->fetch(PDO::FETCH_ASSOC)) {
$field = $column['column_name'];
if (!in_array($field, ['p_id', 'user_id'], true)) {
$roles[] = $field;
}
}
return $roles;
}
$permissionRoles = getPermissionRoles($pdo);
// ── POST-Handler ──────────────────────────────────────────────────
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Kategorie anlegen
if (isset($_POST['addCategory'])) {
$name = trim($_POST['cat_name'] ?? '');
$role = trim($_POST['cat_permission_role'] ?? '');
if ($name !== '') {
$stmt = $pdo->prepare("INSERT INTO kalli_categories (name, permission_role, created_at, updated_at) VALUES (?, ?, NOW(), NOW())");
$stmt->execute([$name, $role]);
header("Location: admin_kalli.php?kalli_msg=1");
exit();
}
header("Location: admin_kalli.php?kalli_msg=5");
exit();
}
// Kategorie bearbeiten
if (isset($_POST['editCategory'])) {
$id = (int)($_POST['cat_id'] ?? 0);
$name = trim($_POST['cat_name'] ?? '');
$role = trim($_POST['cat_permission_role'] ?? '');
if ($id > 0 && $name !== '') {
$stmt = $pdo->prepare("UPDATE kalli_categories SET name = ?, permission_role = ?, updated_at = NOW() WHERE id = ?");
$stmt->execute([$name, $role, $id]);
header("Location: admin_kalli.php?kalli_msg=1");
exit();
}
header("Location: admin_kalli.php?kalli_msg=5");
exit();
}
// Kategorie löschen (CASCADE)
if (isset($_POST['deleteCategory'])) {
$id = (int)($_POST['cat_id'] ?? 0);
if ($id > 0) {
// Zuerst zugehörige Terminarten löschen
$stmt = $pdo->prepare("DELETE FROM kalli_appointment_types WHERE category_id = ?");
$stmt->execute([$id]);
$stmt = $pdo->prepare("DELETE FROM kalli_categories WHERE id = ?");
$stmt->execute([$id]);
header("Location: admin_kalli.php?kalli_msg=2");
exit();
}
header("Location: admin_kalli.php?kalli_msg=5");
exit();
}
// Terminart anlegen
if (isset($_POST['addType'])) {
$catId = (int)($_POST['type_category_id'] ?? 0);
$name = trim($_POST['type_name'] ?? '');
$displayLabel = trim($_POST['type_display_label'] ?? '');
$planungsstatus = trim($_POST['type_planungsstatus'] ?? 'fix');
$anzeigeziel = trim($_POST['type_anzeigeziel'] ?? 'intern');
$gruppenzuordnung = trim($_POST['type_gruppenzuordnung'] ?? '');
$anzeigezielOrt = trim($_POST['type_anzeigeziel_ort'] ?? '');
$anzeigezielNotiz = trim($_POST['type_anzeigeziel_notiz'] ?? '');
$anzeigezielVerantw = trim($_POST['type_anzeigeziel_verantwortlicher'] ?? '');
$defaultText = trim($_POST['type_default_text'] ?? '');
if ($catId > 0 && $name !== '') {
$stmt = $pdo->prepare("INSERT INTO kalli_appointment_types (category_id, name, display_label, planungsstatus, anzeigeziel, gruppenzuordnung, anzeigeziel_ort, anzeigeziel_notiz, anzeigeziel_verantwortlicher, default_text, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())");
$stmt->execute([$catId, $name, $displayLabel, $planungsstatus, $anzeigeziel, $gruppenzuordnung, $anzeigezielOrt, $anzeigezielNotiz, $anzeigezielVerantw, $defaultText]);
header("Location: admin_kalli.php?kalli_msg=3");
exit();
}
header("Location: admin_kalli.php?kalli_msg=5");
exit();
}
// Terminart bearbeiten
if (isset($_POST['editType'])) {
$id = (int)($_POST['type_id'] ?? 0);
$catId = (int)($_POST['type_category_id'] ?? 0);
$name = trim($_POST['type_name'] ?? '');
$displayLabel = trim($_POST['type_display_label'] ?? '');
$planungsstatus = trim($_POST['type_planungsstatus'] ?? 'fix');
$anzeigeziel = trim($_POST['type_anzeigeziel'] ?? 'intern');
$gruppenzuordnung = trim($_POST['type_gruppenzuordnung'] ?? '');
$anzeigezielOrt = trim($_POST['type_anzeigeziel_ort'] ?? '');
$anzeigezielNotiz = trim($_POST['type_anzeigeziel_notiz'] ?? '');
$anzeigezielVerantw = trim($_POST['type_anzeigeziel_verantwortlicher'] ?? '');
$defaultText = trim($_POST['type_default_text'] ?? '');
if ($id > 0 && $name !== '') {
$stmt = $pdo->prepare("UPDATE kalli_appointment_types SET category_id = ?, name = ?, display_label = ?, planungsstatus = ?, anzeigeziel = ?, gruppenzuordnung = ?, anzeigeziel_ort = ?, anzeigeziel_notiz = ?, anzeigeziel_verantwortlicher = ?, default_text = ?, updated_at = NOW() WHERE id = ?");
$stmt->execute([$catId, $name, $displayLabel, $planungsstatus, $anzeigeziel, $gruppenzuordnung, $anzeigezielOrt, $anzeigezielNotiz, $anzeigezielVerantw, $defaultText, $id]);
header("Location: admin_kalli.php?kalli_msg=3");
exit();
}
header("Location: admin_kalli.php?kalli_msg=5");
exit();
}
// Terminart löschen
if (isset($_POST['deleteType'])) {
$id = (int)($_POST['type_id'] ?? 0);
if ($id > 0) {
$stmt = $pdo->prepare("DELETE FROM kalli_appointment_types WHERE id = ?");
$stmt->execute([$id]);
header("Location: admin_kalli.php?kalli_msg=4");
exit();
}
header("Location: admin_kalli.php?kalli_msg=5");
exit();
}
}
// ── Daten laden ───────────────────────────────────────────────────
$categories = $pdo->query("SELECT * FROM kalli_categories ORDER BY sort_order ASC, name ASC")->fetchAll(PDO::FETCH_ASSOC);
$typesRaw = $pdo->query("SELECT * FROM kalli_appointment_types ORDER BY sort_order ASC, name ASC")->fetchAll(PDO::FETCH_ASSOC);
$typesByCategory = [];
foreach ($typesRaw as $t) {
$typesByCategory[(int)$t['category_id']][] = $t;
}
?>
<!DOCTYPE html>
<html lang="de" dir="ltr">
<head>
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/html_head.php'; ?>
<style>
.admin-users-shell {
padding: 18px;
background: linear-gradient(135deg, var(--gray-50) 0%, var(--theme-light, var(--nak-blue-light)) 100%);
min-height: 100%;
}
.admin-table-card {
overflow: hidden;
border: 1px solid rgba(120, 140, 160, 0.22);
background: rgba(255,255,255,0.86);
box-shadow: 0 20px 45px rgba(31, 38, 46, 0.10), 0 1px 0 rgba(255,255,255,0.9) inset;
}
.admin-list-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 16px 18px 12px 18px;
border-bottom: 1px solid rgba(120, 140, 160, 0.16);
background: linear-gradient(180deg, rgba(255,255,255,0.92), rgba(255,255,255,0.72));
}
.admin-list-title {
font-size: 1.05em;
font-weight: 800;
color: hsl(210, 20%, 15%);
}
.admin-list-hint {
font-size: 0.86em;
color: hsl(210, 15%, 46%);
}
.admin-list-add-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 7px 14px;
color: #FFFFFF;
background: linear-gradient(135deg, var(--gray-800), var(--gray-900));
border: 1px solid var(--gray-900);
border-radius: 0;
font-size: 0.86em;
font-weight: 850;
cursor: pointer;
text-decoration: none;
white-space: nowrap;
box-shadow: 0 4px 12px rgba(31,38,46,0.06);
}
.admin-list-add-btn:hover {
background: linear-gradient(135deg, var(--gray-900), #1a1a2e);
color: #FFFFFF;
text-decoration: none;
}
/* Accordion categories */
.kalli-category {
border-bottom: 1px solid rgba(120, 140, 160, 0.12);
}
.kalli-category:last-child {
border-bottom: none;
}
.kalli-cat-header {
display: flex;
align-items: center;
gap: 12px;
padding: 14px 18px;
cursor: pointer;
background: rgba(255,255,255,0.6);
transition: background 0.18s ease;
user-select: none;
}
.kalli-cat-header:hover {
background: rgba(255,255,255,0.92);
}
.kalli-cat-chevron {
width: 18px;
height: 18px;
flex-shrink: 0;
fill: none;
stroke: hsl(210, 16%, 46%);
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
transition: transform 0.22s ease;
}
.kalli-category.open .kalli-cat-chevron {
transform: rotate(90deg);
}
.kalli-cat-name {
font-size: 1em;
font-weight: 800;
color: hsl(210, 20%, 15%);
flex: 1;
min-width: 0;
}
.kalli-cat-meta {
display: flex;
align-items: center;
gap: 10px;
flex-shrink: 0;
}
.kalli-cat-role {
display: inline-flex;
align-items: center;
padding: 3px 8px;
font-size: 0.76em;
font-weight: 750;
color: hsl(210, 57%, 34%);
background: hsl(210, 57%, 94%);
border: 1px solid hsl(210, 57%, 88%);
}
.kalli-cat-count {
font-size: 0.82em;
font-weight: 700;
color: hsl(210, 14%, 46%);
}
.kalli-cat-actions {
display: flex;
gap: 4px;
}
.kalli-icon-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
padding: 0;
border: 1px solid transparent;
background: transparent;
cursor: pointer;
color: hsl(210, 14%, 46%);
transition: background 0.15s ease, color 0.15s ease;
}
.kalli-icon-btn:hover {
background: hsl(210, 12%, 94%);
color: hsl(210, 20%, 22%);
}
.kalli-icon-btn.danger:hover {
background: hsl(352, 72%, 95%);
color: var(--nak-red);
}
.kalli-icon-btn svg {
width: 16px;
height: 16px;
fill: none;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
}
/* Expanded types area */
.kalli-cat-body {
display: none;
padding: 0 18px 16px 18px;
background: hsl(210, 12%, 97%);
border-top: 1px solid rgba(120, 140, 160, 0.10);
}
.kalli-category.open .kalli-cat-body {
display: block;
}
.kalli-type-table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
margin-top: 10px;
}
.kalli-type-table thead th {
padding: 8px 10px;
color: hsl(210, 16%, 46%);
font-size: 0.72em;
font-weight: 800;
text-align: left;
letter-spacing: 0.04em;
text-transform: uppercase;
background: transparent;
border-bottom: 1px solid rgba(120, 140, 160, 0.16);
}
.kalli-type-table tbody tr {
background: rgba(255,255,255,0.8);
transition: background 0.15s ease;
}
.kalli-type-table tbody tr:hover {
background: #FFFFFF;
}
.kalli-type-table td {
padding: 10px 10px;
vertical-align: middle;
font-size: 0.88em;
font-weight: 600;
color: hsl(210, 20%, 22%);
border-bottom: 1px solid rgba(120, 140, 160, 0.08);
}
.kalli-type-table td:last-child {
text-align: right;
white-space: nowrap;
}
.kalli-enum-badge {
display: inline-flex;
align-items: center;
padding: 2px 7px;
font-size: 0.78em;
font-weight: 750;
color: hsl(210, 20%, 32%);
background: hsl(210, 12%, 92%);
border: 1px solid hsl(210, 12%, 86%);
}
.kalli-enum-badge.fix {
color: hsl(145, 50%, 28%);
background: hsl(145, 50%, 93%);
border-color: hsl(145, 50%, 82%);
}
.kalli-enum-badge.geplant {
color: hsl(35, 78%, 30%);
background: hsl(42, 90%, 93%);
border-color: hsl(42, 90%, 78%);
}
.kalli-enum-badge.intern {
color: hsl(210, 57%, 34%);
background: hsl(210, 57%, 94%);
border-color: hsl(210, 57%, 88%);
}
.kalli-add-type-btn {
display: inline-flex;
align-items: center;
gap: 5px;
margin-top: 10px;
padding: 6px 12px;
font-size: 0.82em;
font-weight: 800;
color: hsl(210, 20%, 28%);
background: #FFFFFF;
border: 1px solid hsl(210, 18%, 84%);
cursor: pointer;
transition: background 0.15s ease;
}
.kalli-add-type-btn:hover {
background: hsl(210, 57%, 96%);
}
.kalli-empty {
padding: 20px 18px;
text-align: center;
color: hsl(210, 14%, 46%);
font-size: 0.9em;
font-weight: 600;
}
.kalli-default-text-cell {
max-width: 180px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Modals */
.admin-modal-card {
position: relative;
width: min(720px, calc(100vw - 24px));
max-height: min(760px, calc(100vh - 24px));
overflow: auto;
padding: 0;
background: #FFFFFF;
box-shadow: 0 24px 70px rgba(0,0,0,0.36);
}
.admin-modal-hero {
position: relative;
padding: 24px;
color: #FFFFFF;
background:
radial-gradient(circle at 10% 0%, rgba(255,255,255,0.24), transparent 32%),
linear-gradient(135deg, var(--gray-800), var(--gray-900));
}
.admin-modal-title {
margin: 0 42px 6px 0;
font-size: 1.45em;
font-weight: 850;
color: #FFFFFF;
}
.admin-modal-subtitle {
color: rgba(255,255,255,0.86);
font-weight: 600;
}
.admin-modal-body {
padding: 22px 24px 24px 24px;
}
.admin-section-title {
margin: 0 0 10px 0;
font-size: 0.9em;
font-weight: 850;
letter-spacing: 0.04em;
text-transform: uppercase;
color: hsl(210, 18%, 36%);
}
.admin-modal-card .exit {
position: absolute;
top: 14px;
right: 14px;
width: 34px;
height: 34px;
border: 1px solid rgba(255,255,255,0.25);
background: rgba(255,255,255,0.16);
color: #FFFFFF;
font-size: 1.35em;
line-height: 1;
cursor: pointer;
}
.admin-add-mail-field {
width: 100%;
padding: 13px 14px;
border: 1px solid hsl(210, 18%, 84%);
border-radius: 0;
background: hsl(210, 12%, 98%);
color: hsl(210, 20%, 18%);
font-size: 1em;
font-weight: 650;
outline: none;
box-shadow: inset 0 1px 2px rgba(31,38,46,0.04);
transition: border-color 0.18s ease, box-shadow 0.18s ease, background 0.18s ease;
box-sizing: border-box;
}
.admin-add-mail-field:focus {
border-color: hsl(210, 57%, 55%);
background: #FFFFFF;
box-shadow: 0 0 0 4px hsla(210, 57%, 55%, 0.14);
}
select.admin-add-mail-field {
appearance: auto;
cursor: pointer;
}
textarea.admin-add-mail-field {
resize: vertical;
min-height: 80px;
font-family: var(--font-primary);
}
.admin-field-label {
display: block;
margin-bottom: 5px;
font-size: 0.78em;
font-weight: 800;
letter-spacing: 0.03em;
text-transform: uppercase;
color: hsl(210, 14%, 46%);
}
.admin-field-group {
margin-bottom: 14px;
}
.admin-field-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.admin-modal-actions {
display: flex;
justify-content: space-between;
gap: 10px;
border-top: 1px solid hsl(210, 18%, 90%);
padding-top: 16px;
}
.admin-modal-actions button {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 36px;
padding: 8px 13px;
border: 1px solid hsl(210, 18%, 84%);
background: #FFFFFF;
color: hsl(210, 20%, 22%);
font-size: 0.86em;
font-weight: 850;
cursor: pointer;
box-shadow: 0 4px 12px rgba(31,38,46,0.06);
}
.admin-modal-actions button:hover {
background: hsl(210, 57%, 96%);
}
.admin-save-button {
color: #FFFFFF !important;
background: linear-gradient(135deg, var(--nak-blue), var(--nak-blue-hover)) !important;
border-color: var(--nak-blue-hover) !important;
}
.admin-danger-button {
color: var(--nak-red) !important;
border-color: hsl(352, 72%, 88%) !important;
background: hsl(352, 72%, 97%) !important;
}
@media (max-width: 780px) {
.admin-users-shell { padding: 10px; }
.admin-list-header {
flex-direction: column;
align-items: flex-start;
padding: 14px;
gap: 8px;
}
.kalli-cat-header {
padding: 12px 14px;
flex-wrap: wrap;
}
.kalli-cat-meta {
flex-wrap: wrap;
}
.kalli-cat-role,
.kalli-cat-count {
display: none;
}
.kalli-type-table {
display: block;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.admin-modal-card {
width: calc(100vw - 12px);
max-height: calc(100dvh - 12px);
}
.admin-modal-hero { padding: 18px; }
.admin-modal-title { font-size: 1.15em; margin-right: 40px; }
.admin-modal-body { padding: 18px; }
.admin-field-row { grid-template-columns: 1fr; }
.admin-modal-actions { flex-direction: column-reverse; }
.admin-modal-actions button { width: 100%; }
}
</style>
</head>
<body class="theme-admin">
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/header.php'; ?>
<?php include $g_phpSnippetsPath.'overlay_message.php'; ?>
<div class="header_title ht_admin text_white unselectable" id="header_title">
<span class="header_title_text">Adminpanel</span>
</div>
<div class="content" id="fullsize">
<?php $adminActiveTab = 'kalli'; require __DIR__.'/_tabs.php'; ?>
<div class="admin-users-shell">
<div class="admin-table-card">
<div class="admin-list-header">
<div>
<span class="admin-list-title">Kalli – Terminarten</span>
<div class="admin-list-hint"><?= count($categories) ?> Kategorien, <?= count($typesRaw) ?> Terminarten</div>
</div>
<a href="#" class="admin-list-add-btn" onclick="openModal('addCategoryModal'); return false;">+ Neue Kategorie</a>
</div>
<?php if (empty($categories)): ?>
<div class="kalli-empty">Noch keine Kategorien vorhanden. Erstelle eine neue Kategorie, um Terminarten zu verwalten.</div>
<?php endif; ?>
<?php foreach ($categories as $cat): ?>
<?php $catTypes = $typesByCategory[(int)$cat['id']] ?? []; ?>
<div class="kalli-category" id="cat-<?= (int)$cat['id'] ?>">
<div class="kalli-cat-header" onclick="toggleCategory(<?= (int)$cat['id'] ?>)">
<svg class="kalli-cat-chevron" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"></polyline></svg>
<span class="kalli-cat-name"><?= htmlspecialchars($cat['name']) ?></span>
<div class="kalli-cat-meta">
<?php if (!empty($cat['permission_role'])): ?>
<span class="kalli-cat-role"><?= htmlspecialchars($cat['permission_role']) ?></span>
<?php endif; ?>
<span class="kalli-cat-count"><?= count($catTypes) ?> Terminart<?= count($catTypes) !== 1 ? 'en' : '' ?></span>
<div class="kalli-cat-actions" onclick="event.stopPropagation()">
<button type="button" class="kalli-icon-btn" title="Kategorie bearbeiten" onclick="openEditCategoryModal(<?= (int)$cat['id'] ?>, <?= htmlspecialchars(json_encode($cat['name']), ENT_QUOTES) ?>, <?= htmlspecialchars(json_encode($cat['permission_role'] ?? ''), ENT_QUOTES) ?>)">
<svg viewBox="0 0 24 24"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>
</button>
<button type="button" class="kalli-icon-btn danger" title="Kategorie löschen" onclick="openDeleteCategoryModal(<?= (int)$cat['id'] ?>, <?= htmlspecialchars(json_encode($cat['name']), ENT_QUOTES) ?>)">
<svg viewBox="0 0 24 24"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg>
</button>
</div>
</div>
</div>
<div class="kalli-cat-body">
<?php if (!empty($catTypes)): ?>
<table class="kalli-type-table">
<thead>
<tr>
<th>Name</th>
<th>Anzeige-Label</th>
<th>Planungsstatus</th>
<th>Anzeigeziel</th>
<th>Gruppe</th>
<th>Ort</th>
<th>Notiz</th>
<th>Verantw.</th>
<th>Standardtext</th>
<th></th>
</tr>
</thead>
<tbody>
<?php foreach ($catTypes as $type): ?>
<tr>
<td><?= htmlspecialchars($type['name']) ?></td>
<td><?= htmlspecialchars($type['display_label']) ?></td>
<td><span class="kalli-enum-badge <?= htmlspecialchars($type['planungsstatus']) ?>"><?= htmlspecialchars($type['planungsstatus']) ?></span></td>
<td><span class="kalli-enum-badge <?= htmlspecialchars($type['anzeigeziel']) ?>"><?= htmlspecialchars($type['anzeigeziel']) ?></span></td>
<td><?= htmlspecialchars($type['gruppenzuordnung']) ?></td>
<td><?= htmlspecialchars($type['anzeigeziel_ort'] ?? '') ?></td>
<td><?= htmlspecialchars($type['anzeigeziel_notiz'] ?? '') ?></td>
<td><?= htmlspecialchars($type['anzeigeziel_verantwortlicher'] ?? '') ?></td>
<td class="kalli-default-text-cell" title="<?= htmlspecialchars($type['default_text'] ?? '') ?>"><?= htmlspecialchars(mb_strimwidth($type['default_text'] ?? '', 0, 40, '…')) ?></td>
<td>
<button type="button" class="kalli-icon-btn" title="Terminart bearbeiten" onclick='openEditTypeModal(<?= json_encode($type, JSON_HEX_APOS | JSON_HEX_QUOT) ?>)'>
<svg viewBox="0 0 24 24"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>
</button>
<button type="button" class="kalli-icon-btn danger" title="Terminart löschen" onclick="openDeleteTypeModal(<?= (int)$type['id'] ?>, <?= htmlspecialchars(json_encode($type['name']), ENT_QUOTES) ?>)">
<svg viewBox="0 0 24 24"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg>
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php else: ?>
<div style="padding:10px 0;color:hsl(210,14%,46%);font-size:0.86em;">Keine Terminarten in dieser Kategorie.</div>
<?php endif; ?>
<button type="button" class="kalli-add-type-btn" onclick="openAddTypeModal(<?= (int)$cat['id'] ?>, <?= htmlspecialchars(json_encode($cat['name']), ENT_QUOTES) ?>)">+ Neue Terminart</button>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<!-- ═══ Modal: Kategorie hinzufügen ═══ -->
<div class="standard_overlay" id="addCategoryModal" onclick="if(event.target===this)closeModal('addCategoryModal')">
<div class="admin-modal-card">
<button type="button" class="exit" aria-label="Schließen" onclick="closeModal('addCategoryModal')">×</button>
<div class="admin-modal-hero">
<div class="admin-modal-title">Neue Kategorie</div>
<div class="admin-modal-subtitle">Kategorie für Kalli-Terminarten anlegen</div>
</div>
<div class="admin-modal-body">
<form method="post" action="">
<div class="admin-field-group">
<label class="admin-field-label">Name *</label>
<input type="text" name="cat_name" class="admin-add-mail-field" required placeholder="z. B. Gottesdienste">
</div>
<div class="admin-field-group">
<label class="admin-field-label">Berechtigungsrolle</label>
<select name="cat_permission_role" class="admin-add-mail-field">
<option value="">– Keine –</option>
<?php foreach ($permissionRoles as $role): ?>
<option value="<?= htmlspecialchars($role) ?>"><?= htmlspecialchars($role) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="admin-modal-actions">
<div><button type="button" onclick="closeModal('addCategoryModal')">Abbrechen</button></div>
<div><button type="submit" name="addCategory" value="1" class="admin-save-button">Kategorie speichern</button></div>
</div>
</form>
</div>
</div>
</div>
<!-- ═══ Modal: Kategorie bearbeiten ═══ -->
<div class="standard_overlay" id="editCategoryModal" onclick="if(event.target===this)closeModal('editCategoryModal')">
<div class="admin-modal-card">
<button type="button" class="exit" aria-label="Schließen" onclick="closeModal('editCategoryModal')">×</button>
<div class="admin-modal-hero">
<div class="admin-modal-title">Kategorie bearbeiten</div>
<div class="admin-modal-subtitle">Änderungen an der Kategorie vornehmen</div>
</div>
<div class="admin-modal-body">
<form method="post" action="">
<input type="hidden" name="cat_id" id="editCat_id">
<div class="admin-field-group">
<label class="admin-field-label">Name *</label>
<input type="text" name="cat_name" id="editCat_name" class="admin-add-mail-field" required>
</div>
<div class="admin-field-group">
<label class="admin-field-label">Berechtigungsrolle</label>
<select name="cat_permission_role" id="editCat_role" class="admin-add-mail-field">
<option value="">– Keine –</option>
<?php foreach ($permissionRoles as $role): ?>
<option value="<?= htmlspecialchars($role) ?>"><?= htmlspecialchars($role) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="admin-modal-actions">
<div><button type="button" onclick="closeModal('editCategoryModal')">Abbrechen</button></div>
<div><button type="submit" name="editCategory" value="1" class="admin-save-button">Änderungen speichern</button></div>
</div>
</form>
</div>
</div>
</div>
<!-- ═══ Modal: Kategorie löschen bestätigen ═══ -->
<div class="standard_overlay" id="deleteCategoryModal" onclick="if(event.target===this)closeModal('deleteCategoryModal')">
<div class="admin-modal-card">
<button type="button" class="exit" aria-label="Schließen" onclick="closeModal('deleteCategoryModal')">×</button>
<div class="admin-modal-hero" style="background:linear-gradient(135deg, hsl(352,65%,45%), hsl(352,65%,35%));">
<div class="admin-modal-title">Kategorie löschen</div>
<div class="admin-modal-subtitle">Achtung: Alle zugehörigen Terminarten werden ebenfalls gelöscht!</div>
</div>
<div class="admin-modal-body">
<p style="font-weight:650;color:hsl(210,20%,22%);margin:0 0 8px 0;">Soll die Kategorie <strong id="deleteCat_name_display"></strong> wirklich gelöscht werden?</p>
<p style="font-size:0.86em;color:hsl(210,14%,46%);margin:0 0 20px 0;">Dieser Vorgang kann nicht rückgängig gemacht werden.</p>
<form method="post" action="">
<input type="hidden" name="cat_id" id="deleteCat_id">
<div class="admin-modal-actions">
<div><button type="button" onclick="closeModal('deleteCategoryModal')">Abbrechen</button></div>
<div><button type="submit" name="deleteCategory" value="1" class="admin-danger-button">Endgültig löschen</button></div>
</div>
</form>
</div>
</div>
</div>
<!-- ═══ Modal: Terminart hinzufügen ═══ -->
<div class="standard_overlay" id="addTypeModal" onclick="if(event.target===this)closeModal('addTypeModal')">
<div class="admin-modal-card">
<button type="button" class="exit" aria-label="Schließen" onclick="closeModal('addTypeModal')">×</button>
<div class="admin-modal-hero">
<div class="admin-modal-title">Neue Terminart</div>
<div class="admin-modal-subtitle" id="addType_catLabel">Terminart anlegen</div>
</div>
<div class="admin-modal-body">
<form method="post" action="">
<input type="hidden" name="type_category_id" id="addType_catId">
<div class="admin-field-row">
<div class="admin-field-group">
<label class="admin-field-label">Name *</label>
<input type="text" name="type_name" class="admin-add-mail-field" required placeholder="z. B. Sonntagsgottesdienst">
</div>
<div class="admin-field-group">
<label class="admin-field-label">Anzeige-Label</label>
<input type="text" name="type_display_label" class="admin-add-mail-field" placeholder="z. B. SoGD">
</div>
</div>
<div class="admin-field-row">
<div class="admin-field-group">
<label class="admin-field-label">Planungsstatus</label>
<select name="type_planungsstatus" class="admin-add-mail-field">
<option value="fix">fix</option>
<option value="geplant">geplant</option>
</select>
</div>
<div class="admin-field-group">
<label class="admin-field-label">Anzeigeziel</label>
<select name="type_anzeigeziel" class="admin-add-mail-field">
<option value="intern">intern</option>
<option value="öffentlich">öffentlich</option>
</select>
</div>
</div>
<div class="admin-field-group">
<label class="admin-field-label">Gruppenzuordnung</label>
<input type="text" name="type_gruppenzuordnung" class="admin-add-mail-field" placeholder="z. B. AN ALLE,Musik">
</div>
<div class="admin-field-row">
<div class="admin-field-group">
<label class="admin-field-label">Anzeigeziel Ort</label>
<select name="type_anzeigeziel_ort" class="admin-add-mail-field">
<option value="">– Keine –</option>
<option value="intern">intern</option>
<option value="öffentlich">öffentlich</option>
</select>
</div>
<div class="admin-field-group">
<label class="admin-field-label">Anzeigeziel Notiz</label>
<select name="type_anzeigeziel_notiz" class="admin-add-mail-field">
<option value="">– Keine –</option>
<option value="intern">intern</option>
<option value="öffentlich">öffentlich</option>
</select>
</div>
</div>
<div class="admin-field-group">
<label class="admin-field-label">Anzeigeziel Verantwortlicher</label>
<select name="type_anzeigeziel_verantwortlicher" class="admin-add-mail-field">
<option value="">– Keine –</option>
<option value="intern">intern</option>
<option value="öffentlich">öffentlich</option>
</select>
</div>
<div class="admin-field-group">
<label class="admin-field-label">Standardtext</label>
<textarea name="type_default_text" class="admin-add-mail-field" rows="3" placeholder="Optionaler Standardtext für diese Terminart"></textarea>
</div>
<div class="admin-modal-actions">
<div><button type="button" onclick="closeModal('addTypeModal')">Abbrechen</button></div>
<div><button type="submit" name="addType" value="1" class="admin-save-button">Terminart speichern</button></div>
</div>
</form>
</div>
</div>
</div>
<!-- ═══ Modal: Terminart bearbeiten ═══ -->
<div class="standard_overlay" id="editTypeModal" onclick="if(event.target===this)closeModal('editTypeModal')">
<div class="admin-modal-card">
<button type="button" class="exit" aria-label="Schließen" onclick="closeModal('editTypeModal')">×</button>
<div class="admin-modal-hero">
<div class="admin-modal-title">Terminart bearbeiten</div>
<div class="admin-modal-subtitle">Änderungen an der Terminart vornehmen</div>
</div>
<div class="admin-modal-body">
<form method="post" action="">
<input type="hidden" name="type_id" id="editType_id">
<input type="hidden" name="type_category_id" id="editType_catId">
<div class="admin-field-row">
<div class="admin-field-group">
<label class="admin-field-label">Name *</label>
<input type="text" name="type_name" id="editType_name" class="admin-add-mail-field" required>
</div>
<div class="admin-field-group">
<label class="admin-field-label">Anzeige-Label</label>
<input type="text" name="type_display_label" id="editType_display_label" class="admin-add-mail-field">
</div>
</div>
<div class="admin-field-row">
<div class="admin-field-group">
<label class="admin-field-label">Planungsstatus</label>
<select name="type_planungsstatus" id="editType_planungsstatus" class="admin-add-mail-field">
<option value="fix">fix</option>
<option value="geplant">geplant</option>
</select>
</div>
<div class="admin-field-group">
<label class="admin-field-label">Anzeigeziel</label>
<select name="type_anzeigeziel" id="editType_anzeigeziel" class="admin-add-mail-field">
<option value="intern">intern</option>
<option value="öffentlich">öffentlich</option>
</select>
</div>
</div>
<div class="admin-field-group">
<label class="admin-field-label">Gruppenzuordnung</label>
<input type="text" name="type_gruppenzuordnung" id="editType_gruppenzuordnung" class="admin-add-mail-field">
</div>
<div class="admin-field-row">
<div class="admin-field-group">
<label class="admin-field-label">Anzeigeziel Ort</label>
<select name="type_anzeigeziel_ort" id="editType_anzeigeziel_ort" class="admin-add-mail-field">
<option value="">– Keine –</option>
<option value="intern">intern</option>
<option value="öffentlich">öffentlich</option>
</select>
</div>
<div class="admin-field-group">
<label class="admin-field-label">Anzeigeziel Notiz</label>
<select name="type_anzeigeziel_notiz" id="editType_anzeigeziel_notiz" class="admin-add-mail-field">
<option value="">– Keine –</option>
<option value="intern">intern</option>
<option value="öffentlich">öffentlich</option>
</select>
</div>
</div>
<div class="admin-field-group">
<label class="admin-field-label">Anzeigeziel Verantwortlicher</label>
<select name="type_anzeigeziel_verantwortlicher" id="editType_anzeigeziel_verantwortlicher" class="admin-add-mail-field">
<option value="">– Keine –</option>
<option value="intern">intern</option>
<option value="öffentlich">öffentlich</option>
</select>
</div>
<div class="admin-field-group">
<label class="admin-field-label">Standardtext</label>
<textarea name="type_default_text" id="editType_default_text" class="admin-add-mail-field" rows="3"></textarea>
</div>
<div class="admin-modal-actions">
<div><button type="button" onclick="closeModal('editTypeModal')">Abbrechen</button></div>
<div><button type="submit" name="editType" value="1" class="admin-save-button">Änderungen speichern</button></div>
</div>
</form>
</div>
</div>
</div>
<!-- ═══ Modal: Terminart löschen bestätigen ═══ -->
<div class="standard_overlay" id="deleteTypeModal" onclick="if(event.target===this)closeModal('deleteTypeModal')">
<div class="admin-modal-card">
<button type="button" class="exit" aria-label="Schließen" onclick="closeModal('deleteTypeModal')">×</button>
<div class="admin-modal-hero" style="background:linear-gradient(135deg, hsl(352,65%,45%), hsl(352,65%,35%));">
<div class="admin-modal-title">Terminart löschen</div>
<div class="admin-modal-subtitle">Soll diese Terminart wirklich gelöscht werden?</div>
</div>
<div class="admin-modal-body">
<p style="font-weight:650;color:hsl(210,20%,22%);margin:0 0 8px 0;">Soll die Terminart <strong id="deleteType_name_display"></strong> wirklich gelöscht werden?</p>
<p style="font-size:0.86em;color:hsl(210,14%,46%);margin:0 0 20px 0;">Dieser Vorgang kann nicht rückgängig gemacht werden.</p>
<form method="post" action="">
<input type="hidden" name="type_id" id="deleteType_id">
<div class="admin-modal-actions">
<div><button type="button" onclick="closeModal('deleteTypeModal')">Abbrechen</button></div>
<div><button type="submit" name="deleteType" value="1" class="admin-danger-button">Endgültig löschen</button></div>
</div>
</form>
</div>
</div>
</div>
</div><!-- content -->
<script>
// Modal helpers
function openModal(id) {
var m = document.getElementById(id);
if (m) m.style.display = 'flex';
}
function closeModal(id) {
var m = document.getElementById(id);
if (m) m.style.display = 'none';
}
// Accordion toggle
function toggleCategory(catId) {
var el = document.getElementById('cat-' + catId);
if (el) el.classList.toggle('open');
}
// Category modals
function openEditCategoryModal(id, name, role) {
document.getElementById('editCat_id').value = id;
document.getElementById('editCat_name').value = name;
document.getElementById('editCat_role').value = role;
openModal('editCategoryModal');
}
function openDeleteCategoryModal(id, name) {
document.getElementById('deleteCat_id').value = id;
document.getElementById('deleteCat_name_display').textContent = name;
openModal('deleteCategoryModal');
}
// Type modals
function openAddTypeModal(catId, catName) {
document.getElementById('addType_catId').value = catId;
document.getElementById('addType_catLabel').textContent = 'Terminart in „' + catName + '" anlegen';
openModal('addTypeModal');
}
function openEditTypeModal(type) {
document.getElementById('editType_id').value = type.id;
document.getElementById('editType_catId').value = type.category_id;
document.getElementById('editType_name').value = type.name || '';
document.getElementById('editType_display_label').value = type.display_label || '';
document.getElementById('editType_planungsstatus').value = type.planungsstatus || 'fix';
document.getElementById('editType_anzeigeziel').value = type.anzeigeziel || 'intern';
document.getElementById('editType_gruppenzuordnung').value = type.gruppenzuordnung || '';
document.getElementById('editType_anzeigeziel_ort').value = type.anzeigeziel_ort || '';
document.getElementById('editType_anzeigeziel_notiz').value = type.anzeigeziel_notiz || '';
document.getElementById('editType_anzeigeziel_verantwortlicher').value = type.anzeigeziel_verantwortlicher || '';
document.getElementById('editType_default_text').value = type.default_text || '';
openModal('editTypeModal');
}
function openDeleteTypeModal(id, name) {
document.getElementById('deleteType_id').value = id;
document.getElementById('deleteType_name_display').textContent = name;
openModal('deleteTypeModal');
}
// Escape to close all modals
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
document.querySelectorAll('.standard_overlay').forEach(function(m) {
m.style.display = 'none';
});
}
});
// Toast messages
(function() {
var params = new URLSearchParams(window.location.search);
var msg = params.get('kalli_msg');
if (!msg) return;
var messages = {
'1': { text: 'Kategorie gespeichert', type: 'success' },
'2': { text: 'Kategorie gelöscht', type: 'success' },
'3': { text: 'Terminart gespeichert', type: 'success' },
'4': { text: 'Terminart gelöscht', type: 'success' },
'5': { text: 'Fehler aufgetreten', type: 'error' }
};
var info = messages[msg];
if (!info) return;
// Build toast inline (same pattern as overlay_message.php)
var toast = document.createElement('div');
toast.className = 'nak-toast nak-toast--' + info.type;
toast.id = 'nakToast';
var iconSvg = info.type === 'success'
? '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><path d="M20 6L9 17l-5-5"/></svg>'
: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>';
toast.innerHTML = '<div class="nak-toast__icon">' + iconSvg + '</div>'
+ '<span class="nak-toast__text">' + info.text + '</span>'
+ '<button class="nak-toast__close" onclick="dismissNakToast()" aria-label="Schließen"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button>';
document.body.insertBefore(toast, document.body.firstChild);
requestAnimationFrame(function() { toast.classList.add('nak-toast--visible'); });
var timer = setTimeout(function() { if (typeof dismissNakToast === 'function') dismissNakToast(); }, 4000);
toast._timer = timer;
if (!window.dismissNakToast) {
window.dismissNakToast = function() {
var t = document.getElementById('nakToast');
if (!t || t.classList.contains('nak-toast--hiding')) return;
clearTimeout(t._timer);
t.classList.add('nak-toast--hiding');
t.classList.remove('nak-toast--visible');
setTimeout(function() { t.remove(); }, 350);
};
}
// Clean URL
var url = new URL(window.location);
url.searchParams.delete('kalli_msg');
window.history.replaceState({}, '', url);
})();
</script>
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/footer.php'; ?>
</body>
</html>