Files
2026-11-05 21:21:52 +00:00

161 lines
12 KiB
PHP

<?php
require_once __DIR__ . '/../includes/bootstrap.php';
$staff = require_admin_or_permission('manage_problems');
$pdo = get_db();
$errors = [];
$problemId = (int) ($_GET['id'] ?? $_POST['problem_id'] ?? 0);
$activeTab = (string) ($_GET['tab'] ?? 'list');
if (!in_array($activeTab, ['list', 'create'], true)) $activeTab = 'list';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
verify_csrf();
$action = (string) ($_POST['action'] ?? '');
$activeTab = $action === 'problem_create' ? 'create' : 'list';
try {
if ($action === 'problem_create') {
$pdo->beginTransaction();
$problemId = ticket_problem_create($pdo, $_POST, (int) $staff['id']);
$ticketNumber = trim((string) ($_POST['ticket_number'] ?? ''));
if ($ticketNumber !== '') {
$ticketStmt = $pdo->prepare('SELECT id FROM tickets WHERE ticket_number = ?');
$ticketStmt->execute([$ticketNumber]);
$ticketId = (int) $ticketStmt->fetchColumn();
if (!$ticketId) throw new InvalidArgumentException('Das angegebene Ticket wurde nicht gefunden.');
ticket_problem_link($pdo, $problemId, $ticketId, (int) $staff['id']);
log_activity($pdo, $ticketId, (int) $staff['id'], 'Mit ' . ticket_problem_number($problemId) . ' verknüpft');
}
$pdo->commit();
audit_log($staff['id'], 'Problem ' . ticket_problem_number($problemId) . ' erstellt');
flash('success', ticket_problem_number($problemId) . ' wurde angelegt.');
redirect('/staff/problems.php?tab=list&id=' . $problemId);
} elseif ($action === 'problem_update') {
if (!ticket_problem_update($pdo, $problemId, $_POST, (int) $staff['id'])) throw new InvalidArgumentException('Problem nicht gefunden.');
audit_log($staff['id'], 'Problem ' . ticket_problem_number($problemId) . ' aktualisiert');
flash('success', 'Problem wurde gespeichert.');
} elseif ($action === 'ticket_link') {
$ticketNumber = trim((string) ($_POST['ticket_number'] ?? ''));
$ticketStmt = $pdo->prepare('SELECT id FROM tickets WHERE ticket_number = ?');
$ticketStmt->execute([$ticketNumber]);
$ticketId = (int) $ticketStmt->fetchColumn();
if (!$ticketId) throw new InvalidArgumentException('Das angegebene Ticket wurde nicht gefunden.');
if (!ticket_problem_link($pdo, $problemId, $ticketId, (int) $staff['id'])) throw new InvalidArgumentException('Ticket oder Problem wurde nicht gefunden.');
log_activity($pdo, $ticketId, (int) $staff['id'], 'Mit ' . ticket_problem_number($problemId) . ' verknüpft');
audit_log($staff['id'], 'Ticket mit ' . ticket_problem_number($problemId) . ' verknüpft');
flash('success', 'Ticket wurde mit dem Problem verknüpft.');
} elseif ($action === 'ticket_unlink') {
$ticketId = (int) ($_POST['ticket_id'] ?? 0);
if (!ticket_problem_unlink($pdo, $problemId, $ticketId)) throw new InvalidArgumentException('Dieses Ticket ist mit dem Problem nicht verknüpft.');
log_activity($pdo, $ticketId, (int) $staff['id'], 'Verknüpfung mit ' . ticket_problem_number($problemId) . ' entfernt');
audit_log($staff['id'], 'Ticket von ' . ticket_problem_number($problemId) . ' getrennt');
flash('success', 'Ticket-Verknüpfung wurde entfernt.');
}
} catch (Throwable $e) {
if ($pdo->inTransaction()) $pdo->rollBack();
$errors[] = $e instanceof InvalidArgumentException ? $e->getMessage() : 'Änderung konnte nicht gespeichert werden.';
}
if (!$errors) redirect('/staff/problems.php' . ($problemId ? '?id=' . $problemId : ''));
}
$problems = ticket_problems($pdo);
$problem = $problemId ? ticket_problem_get($pdo, $problemId) : null;
if ($problemId && !$problem) $errors[] = 'Problem nicht gefunden.';
$linkedTickets = $problem ? ticket_problem_tickets($pdo, $problemId) : [];
$problemStatuses = ticket_problem_statuses();
$activePage = 'problems';
$pageTitle = 'Problemmanagement';
require __DIR__ . '/_header.php';
?>
<div class="pm-head">
<div>
<h1 style="margin:0">Problemmanagement</h1>
<p class="muted" style="margin:4px 0 0">Bündeln Sie Tickets mit derselben Ursache. Ursache, Workaround und dauerhafte Lösung werden zentral dokumentiert.</p>
</div>
<?php if ($activeTab === 'create' || $problem): ?>
<a class="btn secondary" href="/staff/problems.php">← Alle Probleme</a>
<?php else: ?>
<a class="btn" href="/staff/problems.php?tab=create">+ Neues Problem</a>
<?php endif; ?>
</div>
<?php foreach ($errors as $error): ?><div class="flash error"><?= e($error) ?></div><?php endforeach; ?>
<?php if ($activeTab === 'create'): ?>
<section class="card">
<h2 style="margin-top:0">Neues Problem erfassen</h2>
<p class="muted">Titel und erste Ticketnummer eintragen. Ursache und dauerhafte L&#246;sung k&#246;nnen danach erg&#228;nzt werden.</p>
<form method="post"><?= csrf_field() ?><input type="hidden" name="action" value="problem_create">
<label for="new_problem_title">Titel</label><input id="new_problem_title" type="text" name="title" maxlength="200" required placeholder="Kurze Problembeschreibung">
<label for="new_problem_ticket" style="margin-top:8px">Ticket verkn&#252;pfen (optional)</label><input id="new_problem_ticket" type="text" name="ticket_number" placeholder="Ticketnummer">
<label for="new_problem_description" style="margin-top:8px">Beschreibung / Auswirkungen</label><textarea id="new_problem_description" name="description" rows="3"></textarea>
<label for="new_problem_cause" style="margin-top:8px">Ursache (falls bekannt)</label><textarea id="new_problem_cause" name="root_cause" rows="3"></textarea>
<label for="new_problem_workaround" style="margin-top:8px">Workaround (optional)</label><textarea id="new_problem_workaround" name="workaround" rows="3"></textarea>
<button class="btn" type="submit" style="margin-top:12px">Problem anlegen</button>
</form>
</section>
<?php else: ?>
<?php if (!$problem): ?>
<?php $openCount = 0; foreach ($problems as $item) { if ($item['status'] !== 'resolved') $openCount++; } ?>
<section class="card" style="padding:0;overflow:hidden">
<?php if (!$problems): ?>
<div class="empty-state" style="padding:32px">Noch keine Probleme erfasst. <a href="/staff/problems.php?tab=create">Erstes Problem anlegen</a></div>
<?php else: ?>
<div class="muted" style="padding:14px 20px;border-bottom:1px solid var(--border,#e3e9ee)"><?= count($problems) ?> Probleme · <?= $openCount ?> offen</div>
<table>
<thead><tr><th>Nr.</th><th>Titel</th><th>Status</th><th>Tickets</th><th>Zuletzt geändert</th></tr></thead>
<tbody>
<?php foreach ($problems as $item):
$pColor = ['investigating' => '#dc2626', 'workaround' => '#d97706', 'monitoring' => '#2563eb', 'resolved' => '#16a34a'][$item['status']] ?? '#64748b';
$pUrl = '/staff/problems.php?id=' . (int) $item['id'];
?>
<tr>
<td style="white-space:nowrap"><a href="<?= $pUrl ?>" class="ticket-no"><?= e(ticket_problem_number((int) $item['id'])) ?></a></td>
<td><a href="<?= $pUrl ?>"><strong><?= e($item['title']) ?></strong></a>
<?php if (!empty($item['description'])): ?><div class="muted" style="font-size:.8rem;max-width:560px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"><?= e($item['description']) ?></div><?php endif; ?></td>
<td><span class="badge" style="background:<?= $pColor ?>"><?= e($problemStatuses[$item['status']] ?? $item['status']) ?></span></td>
<td><?= (int) $item['ticket_count'] ?></td>
<td class="muted" style="white-space:nowrap"><?= e(format_dt((string) ($item['updated_at'] ?? $item['created_at']))) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</section>
<?php else: ?>
<div class="pm-detail">
<main>
<section class="card">
<div style="display:flex;justify-content:space-between;align-items:start;gap:12px;flex-wrap:wrap">
<div><span class="muted"><?= e(ticket_problem_number($problemId)) ?></span><h2 style="margin:2px 0 12px"><?= e($problem['title']) ?></h2></div>
<span class="muted">Erstellt <?= e(format_dt((string) $problem['created_at'])) ?><?= $problem['creator_name'] ? ' von ' . e($problem['creator_name']) : '' ?></span>
</div>
<form method="post"><?= csrf_field() ?><input type="hidden" name="action" value="problem_update"><input type="hidden" name="problem_id" value="<?= $problemId ?>">
<label for="problem_title">Titel</label><input id="problem_title" type="text" name="title" maxlength="200" required value="<?= e($problem['title']) ?>">
<label for="problem_description" style="margin-top:8px">Beschreibung / Auswirkungen</label><textarea id="problem_description" name="description" rows="3"><?= e((string) ($problem['description'] ?? '')) ?></textarea>
<label for="problem_root_cause" style="margin-top:8px">Ursache</label><textarea id="problem_root_cause" name="root_cause" rows="3"><?= e((string) ($problem['root_cause'] ?? '')) ?></textarea>
<label for="problem_workaround" style="margin-top:8px">Workaround</label><textarea id="problem_workaround" name="workaround" rows="3"><?= e((string) ($problem['workaround'] ?? '')) ?></textarea>
<label for="problem_resolution" style="margin-top:8px">Dauerhafte Lösung</label><textarea id="problem_resolution" name="resolution" rows="3"><?= e((string) ($problem['resolution'] ?? '')) ?></textarea>
<div style="display:flex;gap:12px;align-items:end;flex-wrap:wrap;margin-top:8px"><div><label for="problem_status">Status</label><select id="problem_status" name="status"><?php foreach ($problemStatuses as $key => $label): ?><option value="<?= e($key) ?>" <?= $problem['status'] === $key ? 'selected' : '' ?>><?= e($label) ?></option><?php endforeach; ?></select></div><button class="btn" type="submit">Problem speichern</button></div>
</form>
</section>
</main>
<aside>
<section class="card">
<h3 style="margin-top:0">Verknüpfte Tickets (<?= count($linkedTickets) ?>)</h3>
<form method="post" style="display:flex;gap:8px;flex-wrap:wrap;align-items:end;margin-bottom:12px"><?= csrf_field() ?><input type="hidden" name="action" value="ticket_link"><input type="hidden" name="problem_id" value="<?= $problemId ?>"><div style="flex:1;min-width:200px"><label for="link_ticket_number">Ticketnummer</label><input id="link_ticket_number" type="text" name="ticket_number" required placeholder="z. B. 260001"></div><button class="btn secondary" type="submit">Ticket verknüpfen</button></form>
<?php if (!$linkedTickets): ?><p class="muted">Noch keine Tickets mit diesem Problem verknüpft.</p><?php endif; ?>
<?php foreach ($linkedTickets as $linked): ?>
<div style="display:flex;justify-content:space-between;gap:10px;align-items:center;border-top:1px solid var(--border,#e3e9ee);padding:9px 0">
<div><a href="/staff/ticket_view.php?n=<?= urlencode((string) $linked['ticket_number']) ?>"><strong>#<?= e($linked['ticket_number']) ?></strong> · <?= e($linked['subject']) ?></a><div class="muted" style="font-size:.78rem"><?= e($linked['requester_name']) ?> · <?= e(status_label((string) $linked['status'])[0]) ?></div></div>
<form method="post" onsubmit="return confirm('Ticket-Verknüpfung entfernen?')"><?= csrf_field() ?><input type="hidden" name="action" value="ticket_unlink"><input type="hidden" name="problem_id" value="<?= $problemId ?>"><input type="hidden" name="ticket_id" value="<?= (int) $linked['id'] ?>"><button class="btn secondary" type="submit">Trennen</button></form>
</div>
<?php endforeach; ?>
</section>
</aside>
</div>
<?php endif; ?>
<?php endif; ?>
<style>
.pm-head{display:flex;justify-content:space-between;align-items:flex-start;gap:16px;flex-wrap:wrap;margin-bottom:18px}
.pm-detail{display:grid;grid-template-columns:minmax(0,1fr) minmax(280px,400px);gap:16px;align-items:start}
@media(max-width:900px){.pm-detail{grid-template-columns:1fr}}
</style>
<?php require __DIR__ . '/_footer.php'; ?>