Files
deskhub/DeskHub-Webseite/staff/help_topics.php
T
2026-10-19 18:06:00 +00:00

265 lines
14 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
require_once __DIR__ . '/../includes/bootstrap.php';
$admin = require_admin_or_permission('manage_help_topics');
$pdo = get_db();
$errors = [];
$departments = departments_for_select($pdo, array_map('intval', $pdo->query('SELECT department_id FROM help_topics')->fetchAll(PDO::FETCH_COLUMN)));
$priorities = $pdo->query('SELECT * FROM priorities ORDER BY sort_order')->fetchAll();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
verify_csrf();
$action = $_POST['action'] ?? '';
if ($action === 'create' || $action === 'update') {
$id = $action === 'update' ? (int) ($_POST['id'] ?? 0) : 0;
try {
help_topic_save($pdo, $id, [
'name' => $_POST['name'] ?? '', 'department_id' => $_POST['department_id'] ?? 0, 'priority_id' => $_POST['priority_id'] ?? 0,
'is_active' => ($_POST['status'] ?? 'active') === 'active', 'is_public' => ($_POST['type'] ?? 'public') === 'public',
'parent_id' => $_POST['parent_id'] ?? null, 'sla_plan_id' => $_POST['sla_plan_id'] ?? null,
'notes' => $_POST['notes'] ?? '', 'sort_order' => $_POST['sort_order'] ?? 0,
]);
audit_log($admin['id'], ($action === 'create' ? 'Hilfethema erstellt: ' : 'Hilfethema aktualisiert: ') . trim($_POST['name'] ?? ''));
flash('success', $action === 'create' ? 'Hilfethema erstellt.' : 'Änderungen gespeichert.');
redirect('/staff/help_topics.php');
} catch (AdminModelException $e) {
$errors[] = $e->getMessage();
}
}
if ($action === 'delete') {
$deleted = help_topic_delete($pdo, (int) $_POST['id']);
audit_log($admin['id'], "Hilfethema gelöscht: {$deleted}");
flash('success', 'Hilfethema gelöscht.');
redirect('/staff/help_topics.php');
}
}
$isNew = isset($_GET['new']);
$editId = (int) ($_GET['edit'] ?? 0);
$showForm = $isNew || $editId || ($errors && in_array($_POST['action'] ?? '', ['create', 'update'], true));
$editTopic = null;
if ($editId) {
$stmt = $pdo->prepare('SELECT * FROM help_topics WHERE id = ?');
$stmt->execute([$editId]);
$editTopic = $stmt->fetch() ?: null;
}
$topicFields = $globalFields = [];
$fieldTypeLabels = ['text' => 'Einzeiliger Text', 'textarea' => 'Mehrzeiliger Text', 'select' => 'Dropdown-Auswahl', 'checkbox' => 'Checkbox (Ja/Nein)', 'number' => 'Zahl', 'date' => 'Datum'];
if ($editTopic) {
$cfStmt = $pdo->prepare('SELECT * FROM custom_fields WHERE help_topic_id = ? ORDER BY sort_order, label');
$cfStmt->execute([$editTopic['id']]);
$topicFields = $cfStmt->fetchAll();
$globalFields = $pdo->query('SELECT * FROM custom_fields WHERE help_topic_id IS NULL AND department_id IS NULL AND is_active = 1 ORDER BY sort_order, label')->fetchAll();
}
$sortMode = ($_GET['sort'] ?? 'alpha') === 'manual' ? 'manual' : 'alpha';
$topics = $pdo->query(
'SELECT h.*, d.name AS department_name, p.name AS priority_name
FROM help_topics h
JOIN departments d ON d.id = h.department_id
JOIN priorities p ON p.id = h.priority_id
ORDER BY ' . ($sortMode === 'manual' ? 'h.sort_order, h.name' : 'h.name')
)->fetchAll();
// Anzeigename mit Elternpfad
$names = array_column($topics, 'name', 'id');
$parents = array_column($topics, 'parent_id', 'id');
$pathOf = function (int $id) use ($names, $parents): string {
$parts = []; $seen = [];
while ($id && isset($names[$id]) && !isset($seen[$id])) { $seen[$id] = true; array_unshift($parts, $names[$id]); $id = (int) ($parents[$id] ?? 0); }
return implode(' / ', $parts);
};
$fmt = fn(?string $dt) => $dt ? date('d.m.Y H:i', strtotime($dt)) : '–';
$pageTitle = 'Hilfethemen';
$activePage = 'help_topics';
require __DIR__ . '/_header.php';
?>
<?php foreach ($errors as $err): ?>
<div class="flash error"><?= e($err) ?></div>
<?php endforeach; ?>
<?php if ($showForm): ?>
<?php
$f = fn(string $k, $default = '') => $_POST[$k] ?? $default;
$v = fn(string $k, string $col, $default = '') => $f($k, $editTopic[$col] ?? $default);
$curStatus = $_POST ? ($_POST['status'] ?? 'active') : (($editTopic['is_active'] ?? 1) ? 'active' : 'inactive');
$curType = $_POST ? ($_POST['type'] ?? 'public') : (($editTopic['is_public'] ?? 1) ? 'public' : 'internal');
$curParent = (string) $v('parent_id', 'parent_id');
$excluded = $editTopic ? array_merge([(int) $editTopic['id']], help_topic_descendants($pdo, (int) $editTopic['id'])) : [];
?>
<h1>
<?= $editTopic ? 'Hilfethema aktualisieren' : 'Neues Hilfethema' ?>
<?php if ($editTopic): ?><span class="muted" style="font-size:1rem;font-weight:400">— <?= e($editTopic['name']) ?></span><?php endif; ?>
</h1>
<form method="post" class="card" style="padding:0">
<?= csrf_field() ?>
<input type="hidden" name="action" value="<?= $editTopic ? 'update' : 'create' ?>">
<?php if ($editTopic): ?><input type="hidden" name="id" value="<?= (int) $editTopic['id'] ?>"><?php endif; ?>
<div style="display:flex;gap:4px;padding:10px 12px 0;border-bottom:1px solid var(--border)">
<button type="button" class="tab-btn" data-tab="info">Hilfethemeninformationen</button>
<button type="button" class="tab-btn" data-tab="options">Optionen bei neuem Ticket</button>
<button type="button" class="tab-btn" data-tab="forms">Formulare</button>
</div>
<div class="tab-pane" data-pane="info" style="padding:16px 20px">
<label for="name">Thema *</label>
<input type="text" id="name" name="name" value="<?= e($v('name', 'name')) ?>" placeholder="z. B. Technisches Problem" required>
<label for="status">Status *</label>
<select id="status" name="status">
<option value="active" <?= $curStatus === 'active' ? 'selected' : '' ?>>Aktiv</option>
<option value="inactive" <?= $curStatus === 'inactive' ? 'selected' : '' ?>>Deaktiviert</option>
</select>
<label>Typ *</label>
<div style="display:flex;gap:18px;margin:4px 0 10px">
<label style="display:flex;align-items:center;gap:6px;margin:0"><input type="radio" name="type" value="public" style="width:auto" <?= $curType === 'public' ? 'checked' : '' ?>> Öffentlich</label>
<label style="display:flex;align-items:center;gap:6px;margin:0"><input type="radio" name="type" value="internal" style="width:auto" <?= $curType === 'internal' ? 'checked' : '' ?>> Vertraulich/Intern</label>
</div>
<p class="muted" style="font-size:.78rem;margin-top:-6px">Öffentliche Themen sehen auch Kunden im Portal; interne nur Mitarbeiter.</p>
<label for="parent_id">Übergeordnetes Thema</label>
<select id="parent_id" name="parent_id">
<option value="">— Oberste Hilfethemenebene —</option>
<?php foreach ($topics as $t): if (in_array((int) $t['id'], $excluded, true)) continue; ?>
<option value="<?= $t['id'] ?>" <?= $curParent === (string) $t['id'] ? 'selected' : '' ?>><?= e($pathOf((int) $t['id'])) ?></option>
<?php endforeach; ?>
</select>
<label for="notes">Interne Notizen <span class="muted" style="font-weight:400">– Sei offen, sie sind vertraulich</span></label>
<textarea id="notes" name="notes" rows="8"><?= e($v('notes', 'notes')) ?></textarea>
</div>
<div class="tab-pane" data-pane="options" style="padding:16px 20px;display:none">
<p class="muted" style="font-size:.82rem;margin-top:0">Diese Werte werden beim Anlegen eines Tickets mit diesem Thema automatisch gesetzt.</p>
<label for="department_id">Abteilung *</label>
<select name="department_id" id="department_id" required>
<?php foreach ($departments as $d): ?>
<option value="<?= $d['id'] ?>" <?= (string) $v('department_id', 'department_id') === (string) $d['id'] ? 'selected' : '' ?>><?= e($d['name']) ?></option>
<?php endforeach; ?>
</select>
<label for="priority_id">Priorität *</label>
<select name="priority_id" id="priority_id" required>
<?php foreach ($priorities as $p): ?>
<option value="<?= $p['id'] ?>" <?= (string) $v('priority_id', 'priority_id') === (string) $p['id'] ? 'selected' : '' ?>><?= e($p['name']) ?></option>
<?php endforeach; ?>
</select>
<label for="sla_plan_id">SLA-Plan</label>
<select name="sla_plan_id" id="sla_plan_id">
<option value="">— Abteilung/Priorität bestimmt die Zeiten —</option>
<?php foreach ($pdo->query('SELECT id, name, is_active FROM sla_plans ORDER BY name')->fetchAll() as $__sp): ?>
<option value="<?= $__sp['id'] ?>" <?= (string) $v('sla_plan_id', 'sla_plan_id') === (string) $__sp['id'] ? 'selected' : '' ?>><?= e($__sp['name']) ?><?= $__sp['is_active'] ? '' : ' (deaktiviert)' ?></option>
<?php endforeach; ?>
</select>
<label for="sort_order">Sortierung</label>
<input type="number" id="sort_order" name="sort_order" value="<?= e((string) $v('sort_order', 'sort_order', '0')) ?>">
<p class="muted" style="font-size:.78rem;margin-top:10px">Zusatzfelder pro Thema legst du unter <a href="/staff/custom_fields.php">Zusatzfelder</a> an.</p>
</div>
<div class="tab-pane" data-pane="forms" style="padding:16px 20px;display:none">
<h3 style="margin-top:0">Formulare</h3>
<?php if (!$editTopic): ?>
<p class="muted">Zusatzfelder lassen sich nach dem Erstellen des Hilfethemas zuordnen.</p>
<?php else: ?>
<p class="muted" style="margin-top:0">Diese Zusatzfelder erscheinen auf dem Ticketformular, sobald Kunden dieses Hilfethema wählen.</p>
<?php if (!$topicFields): ?>
<p class="muted">Für dieses Thema gibt es noch keine eigenen Felder.</p>
<?php else: ?>
<table>
<thead><tr><th>Bezeichnung</th><th>Typ</th><th>Pflicht</th><th>Aktiv</th><th></th></tr></thead>
<tbody>
<?php foreach ($topicFields as $cf): ?>
<tr>
<td><?= e($cf['label']) ?></td>
<td><?= e($fieldTypeLabels[$cf['field_type']] ?? $cf['field_type']) ?></td>
<td><?= $cf['is_required'] ? '✓' : '–' ?></td>
<td><?= $cf['is_active'] ? '✓' : '–' ?></td>
<td><a href="/staff/custom_fields.php?edit=<?= (int) $cf['id'] ?>">Bearbeiten</a></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<p style="margin-top:12px"><a class="btn secondary" href="/staff/custom_fields.php?topic=<?= (int) $editTopic['id'] ?>">+ Neues Feld für dieses Thema</a></p>
<?php if ($globalFields): ?>
<h4 style="border-bottom:1px dotted var(--border);padding-bottom:4px">Felder für alle Themen</h4>
<p class="muted" style="font-size:.82rem;margin-top:0">Gelten zusätzlich bei jedem Thema und werden unter <a href="/staff/custom_fields.php">Ticket-Felder</a> verwaltet.</p>
<ul style="margin:0 0 0 18px"><?php foreach ($globalFields as $cf): ?><li><?= e($cf['label']) ?><?= $cf['is_required'] ? ' *' : '' ?></li><?php endforeach; ?></ul>
<?php endif; ?>
<?php endif; ?>
</div>
<div class="btn-row" style="padding:0 20px 18px">
<button type="submit" class="btn">Änderungen speichern</button>
<a href="/staff/help_topics.php" class="btn secondary">Abbrechen</a>
</div>
</form>
<style>
.tab-btn{background:none;border:1px solid transparent;border-bottom:none;padding:8px 16px;cursor:pointer;border-radius:6px 6px 0 0;color:var(--muted)}
.tab-btn.active{background:var(--card-bg,#fff);border-color:var(--border);color:inherit;font-weight:600;position:relative;top:1px}
</style>
<script>
(function () {
var btns = document.querySelectorAll('.tab-btn'), panes = document.querySelectorAll('.tab-pane');
function show(t) {
btns.forEach(function (b) { b.classList.toggle('active', b.dataset.tab === t); });
panes.forEach(function (p) { p.style.display = p.dataset.pane === t ? '' : 'none'; });
}
btns.forEach(function (b) { b.addEventListener('click', function () { show(b.dataset.tab); }); });
show('info');
})();
</script>
<?php else: ?>
<div style="display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap">
<h1 style="margin:0">Hilfethemen</h1>
<div style="display:flex;gap:12px;align-items:center">
<form method="get" style="display:flex;align-items:center;gap:6px;margin:0">
<label for="sort" style="margin:0">Sortiermodus:</label>
<select id="sort" name="sort" onchange="this.form.submit()" style="width:auto">
<option value="alpha" <?= $sortMode === 'alpha' ? 'selected' : '' ?>>Alphabetisch</option>
<option value="manual" <?= $sortMode === 'manual' ? 'selected' : '' ?>>Manuell</option>
</select>
</form>
<a href="/staff/help_topics.php?new=1" class="btn">Neues Hilfethema hinzufügen</a>
</div>
</div>
<p class="muted">Hilfethemen erscheinen im Kundenportal als Auswahl bei der Ticket-Erstellung und setzen automatisch Abteilung und Priorität.</p>
<div class="card">
<table>
<thead><tr><th>Hilfethema</th><th>Status</th><th>Typ</th><th>Priorität</th><th>Abteilung</th><th>Zuletzt aktualisiert</th><th>Erstellt</th><th></th></tr></thead>
<tbody>
<?php foreach ($topics as $t): ?>
<tr>
<td><a href="/staff/help_topics.php?edit=<?= (int) $t['id'] ?>"><?= e($pathOf((int) $t['id'])) ?></a></td>
<td><strong><?= $t['is_active'] ? 'Aktiv' : 'Deaktiviert' ?></strong></td>
<td><?= ($t['is_public'] ?? 1) ? 'Öffentlich' : 'Intern' ?></td>
<td><?= e($t['priority_name']) ?></td>
<td><?= e($t['department_name']) ?></td>
<td class="muted" style="font-size:.82rem"><?= e($fmt($t['updated_at'] ?? null)) ?></td>
<td class="muted" style="font-size:.82rem"><?= e($fmt($t['created_at'] ?? null)) ?></td>
<td style="white-space:nowrap">
<a href="/staff/help_topics.php?edit=<?= (int) $t['id'] ?>">Bearbeiten</a> ·
<form method="post" style="display:inline" onsubmit="return confirm('Hilfethema wirklich löschen?');">
<?= csrf_field() ?>
<input type="hidden" name="action" value="delete">
<input type="hidden" name="id" value="<?= (int) $t['id'] ?>">
<button type="submit" style="background:none;border:none;color:#dc2626;cursor:pointer;padding:0;font-size:.9rem">Löschen</button>
</form>
</td>
</tr>
<?php endforeach; ?>
<?php if (!$topics): ?>
<tr><td colspan="8" class="muted">Noch keine Hilfethemen angelegt.</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<?php require __DIR__ . '/_footer.php'; ?>