Upload via GUI (340 Dateien)
This commit is contained in:
@@ -86,6 +86,9 @@ $myWorkSessionStartedAt = $myWorkSessionStmt->fetchColumn() ?: null;
|
||||
$collabStmt = $pdo->prepare('SELECT * FROM ticket_collaborators WHERE ticket_id = ? ORDER BY created_at');
|
||||
$collabStmt->execute([$ticket['id']]);
|
||||
$collaborators = $collabStmt->fetchAll();
|
||||
$ticketProblems = ticket_problems_for_ticket($pdo, (int) $ticket['id']);
|
||||
$openProblems = ticket_problems($pdo, true);
|
||||
$resolutionReasons = ticket_resolution_reasons();
|
||||
|
||||
$departments = departments_for_select($pdo, [(int) $ticket['department_id']]);
|
||||
$priorities = $pdo->query('SELECT * FROM priorities ORDER BY sort_order')->fetchAll();
|
||||
@@ -164,16 +167,22 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
}
|
||||
}
|
||||
if ($banEmail) {
|
||||
$priorStmt = $pdo->prepare("SELECT id,status FROM tickets WHERE requester_email=? AND status NOT IN ('closed')");
|
||||
$priorStmt->execute([$banEmail]); $priorTickets = $priorStmt->fetchAll();
|
||||
$affected = $pdo->prepare(
|
||||
"UPDATE tickets SET status='closed', closed_at=?, updated_at=? WHERE requester_email=? AND status NOT IN ('closed')"
|
||||
"UPDATE tickets SET status='closed', resolution_code='spam', resolution_note='Absender gesperrt', closed_at=?, updated_at=? WHERE requester_email=? AND status NOT IN ('closed')"
|
||||
);
|
||||
$affected->execute([db_now(), db_now(), $banEmail]);
|
||||
ticket_sla_transition_rows($pdo, $priorTickets, 'closed', (int) $staff['id']);
|
||||
}
|
||||
if ($banDomain) {
|
||||
$priorStmt = $pdo->prepare("SELECT id,status FROM tickets WHERE " . sql_email_domain("requester_email") . "=? AND status NOT IN ('closed')");
|
||||
$priorStmt->execute([$banDomain]); $priorTickets = $priorStmt->fetchAll();
|
||||
$affected2 = $pdo->prepare(
|
||||
"UPDATE tickets SET status='closed', closed_at=?, updated_at=? WHERE " . sql_email_domain("requester_email") . "=? AND status NOT IN ('closed')"
|
||||
"UPDATE tickets SET status='closed', resolution_code='spam', resolution_note='Domain gesperrt', closed_at=?, updated_at=? WHERE " . sql_email_domain("requester_email") . "=? AND status NOT IN ('closed')"
|
||||
);
|
||||
$affected2->execute([db_now(), db_now(), $banDomain]);
|
||||
ticket_sla_transition_rows($pdo, $priorTickets, 'closed', (int) $staff['id']);
|
||||
}
|
||||
flash('success', 'Gesperrt und alle zugehörigen Tickets geschlossen.');
|
||||
redirect('/staff/ticket_view.php?n=' . urlencode($ticket['ticket_number']));
|
||||
@@ -235,6 +244,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$errors[] = 'Bitte geben Sie eine Nachricht ein.';
|
||||
} else {
|
||||
$isNote = $action === 'note' ? 1 : 0;
|
||||
$newStatus = !$isNote ? (string) ($_POST['set_status'] ?? 'answered') : (string) $ticket['status'];
|
||||
$resolutionCode = (string) ($_POST['resolution_code'] ?? '');
|
||||
$resolutionNote = trim(mb_substr((string) ($_POST['resolution_note'] ?? ''), 0, 2000));
|
||||
if (!$isNote && in_array($newStatus, ['resolved', 'closed'], true)) {
|
||||
$requirePerm('close_ticket');
|
||||
if (!isset(ticket_resolution_reasons()[$resolutionCode])) { flash('error', 'Bitte beim Schließen einen Abschlussgrund auswählen.'); redirect('/staff/ticket_view.php?n=' . urlencode($number)); }
|
||||
}
|
||||
$stmt = $pdo->prepare(
|
||||
'INSERT INTO ticket_threads (ticket_id, author_type, staff_id, message, is_internal_note, created_at)
|
||||
VALUES (?,?,?,?,?,?)'
|
||||
@@ -255,24 +271,20 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
}
|
||||
|
||||
if (!$isNote) {
|
||||
$newStatus = $_POST['set_status'] ?: 'answered';
|
||||
$updateFirstResponse = $ticket['first_responded_at'] === null;
|
||||
|
||||
// Auto-Zuweisung an antwortenden Mitarbeiter, falls Ticket noch niemandem zugewiesen ist
|
||||
$autoAssign = ($ticket['assigned_staff_id'] === null || $ticket['assigned_staff_id'] === '');
|
||||
$stmt = $pdo->prepare(
|
||||
'UPDATE tickets SET status = ?, updated_at = ?' .
|
||||
($updateFirstResponse ? ', first_responded_at = ?' : '') .
|
||||
($autoAssign ? ', assigned_staff_id = ?' : '') .
|
||||
' WHERE id = ?'
|
||||
);
|
||||
$args = [];
|
||||
$args[] = $newStatus;
|
||||
$args[] = $now;
|
||||
if ($updateFirstResponse) $args[] = $now;
|
||||
if ($autoAssign) $args[] = $staff['id'];
|
||||
$args[] = $ticket['id'];
|
||||
$stmt->execute($args);
|
||||
$resolvedAt = $newStatus === 'resolved' && empty($ticket['resolved_at']) ? $now : $ticket['resolved_at'];
|
||||
$closedAt = $newStatus === 'closed' && empty($ticket['closed_at']) ? $now : $ticket['closed_at'];
|
||||
$stmt = $pdo->prepare('UPDATE tickets SET status = ?, updated_at = ?, first_responded_at = COALESCE(first_responded_at, ?), assigned_staff_id = COALESCE(assigned_staff_id, ?), resolved_at = ?, closed_at = ?, resolution_code = ?, resolution_note = ? WHERE id = ?');
|
||||
$stmt->execute([
|
||||
$newStatus, $now, $now, (int) $staff['id'], $resolvedAt, $closedAt,
|
||||
in_array($newStatus, ['resolved', 'closed'], true) ? $resolutionCode : $ticket['resolution_code'],
|
||||
in_array($newStatus, ['resolved', 'closed'], true) ? ($resolutionNote !== '' ? $resolutionNote : $ticket['resolution_note']) : $ticket['resolution_note'],
|
||||
$ticket['id'],
|
||||
]);
|
||||
ticket_sla_status_transition($pdo, (int) $ticket['id'], (string) $ticket['status'], $newStatus, (int) $staff['id']);
|
||||
if ($autoAssign) {
|
||||
log_activity($pdo, $ticket['id'], $staff['id'], 'Automatisch zugewiesen (erste Antwort)');
|
||||
}
|
||||
@@ -491,14 +503,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
)->execute([$dst['id'], 'staff', $staff['id'], $note, $now]);
|
||||
|
||||
// Quell-Ticket schließen + merged_into setzen
|
||||
$pdo->prepare('UPDATE tickets SET status = ?, merged_into = ?, updated_at = ? WHERE id = ?')
|
||||
->execute(['closed', $dst['id'], $now, $src['id']]);
|
||||
$pdo->prepare('UPDATE tickets SET status = ?, merged_into = ?, resolution_code = ?, resolution_note = ?, updated_at = ? WHERE id = ?')
|
||||
->execute(['closed', $dst['id'], 'duplicate', 'Zusammengeführt mit Ticket #' . $dst['ticket_number'], $now, $src['id']]);
|
||||
|
||||
// Ziel-Ticket aktualisieren (updated_at)
|
||||
$pdo->prepare('UPDATE tickets SET updated_at = ? WHERE id = ?')
|
||||
->execute([$now, $dst['id']]);
|
||||
|
||||
$pdo->commit();
|
||||
ticket_sla_status_transition($pdo, (int) $src['id'], (string) $src['status'], 'closed', (int) $staff['id']);
|
||||
log_activity($pdo, $dst['id'], $staff['id'], "Ticket #{$src['ticket_number']} hierher zusammengeführt");
|
||||
log_activity($pdo, $src['id'], $staff['id'], "Zusammengeführt mit Ticket #{$dst['ticket_number']}");
|
||||
audit_log($staff['id'], "Merge: #{$src['ticket_number']} → #{$dst['ticket_number']}");
|
||||
@@ -512,6 +525,23 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
redirect('/staff/ticket_view.php?n=' . urlencode($number));
|
||||
}
|
||||
|
||||
if ($action === 'problem_link') {
|
||||
if ($staff['role'] !== 'admin') { flash('error', 'Nur Administratoren dürfen Tickets mit Problemen verknüpfen.'); redirect('/staff/ticket_view.php?n=' . urlencode($number)); }
|
||||
$problemIdToLink = (int) ($_POST['problem_id'] ?? 0);
|
||||
if (!ticket_problem_link($pdo, $problemIdToLink, (int) $ticket['id'], (int) $staff['id'])) flash('error', 'Problem konnte nicht gefunden werden.');
|
||||
else { log_activity($pdo, (int) $ticket['id'], (int) $staff['id'], 'Mit ' . ticket_problem_number($problemIdToLink) . ' verknüpft'); flash('success', 'Ticket mit Problem verknüpft.'); }
|
||||
redirect('/staff/ticket_view.php?n=' . urlencode($number));
|
||||
}
|
||||
|
||||
if ($action === 'problem_unlink') {
|
||||
if ($staff['role'] !== 'admin') { flash('error', 'Nur Administratoren dürfen Problem-Verknüpfungen ändern.'); redirect('/staff/ticket_view.php?n=' . urlencode($number)); }
|
||||
$problemIdToUnlink = (int) ($_POST['problem_id'] ?? 0);
|
||||
ticket_problem_unlink($pdo, $problemIdToUnlink, (int) $ticket['id']);
|
||||
log_activity($pdo, (int) $ticket['id'], (int) $staff['id'], 'Problemverknüpfung ' . ticket_problem_number($problemIdToUnlink) . ' entfernt');
|
||||
flash('success', 'Ticket vom Problem getrennt.');
|
||||
redirect('/staff/ticket_view.php?n=' . urlencode($number));
|
||||
}
|
||||
|
||||
if ($action === 'update_props') {
|
||||
$deptId = (int) $_POST['department_id'];
|
||||
if ($myDeptIds !== null && !in_array($deptId, $myDeptIds, true) && $deptId !== (int) $ticket['department_id']) {
|
||||
@@ -527,10 +557,23 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if ($assignedTo !== ($ticket['assigned_staff_id'] === null ? null : (int) $ticket['assigned_staff_id']) || $teamId !== ($ticket['team_id'] === null ? null : (int) $ticket['team_id'])) $requirePerm('assign_ticket');
|
||||
if ($status !== $ticket['status'] && in_array($status, ['resolved', 'closed'], true)) $requirePerm('close_ticket');
|
||||
|
||||
$resolutionCode = (string) ($_POST['resolution_code'] ?? $ticket['resolution_code'] ?? '');
|
||||
$resolutionNote = trim(mb_substr((string) ($_POST['resolution_note'] ?? $ticket['resolution_note'] ?? ''), 0, 2000));
|
||||
if ($status !== $ticket['status'] && in_array($status, ['resolved', 'closed'], true)) {
|
||||
if (!isset(ticket_resolution_reasons()[$resolutionCode])) { flash('error', 'Bitte beim Schließen einen Abschlussgrund auswählen.'); redirect('/staff/ticket_view.php?n=' . urlencode($number)); }
|
||||
}
|
||||
|
||||
$prio = null;
|
||||
foreach ($priorities as $p) { if ($p['id'] == $prioId) $prio = $p; }
|
||||
|
||||
$sla = $prio ? calc_sla_dates($prio, $ticket['created_at'], $deptId, !empty($ticket['help_topic_id']) ? (int) $ticket['help_topic_id'] : null) : null;
|
||||
$slaChanged = $deptId !== (int) $ticket['department_id'] || $prioId !== (int) $ticket['priority_id'];
|
||||
$sla = $prio && $slaChanged ? calc_sla_dates($prio, $ticket['created_at'], $deptId, !empty($ticket['help_topic_id']) ? (int) $ticket['help_topic_id'] : null) : null;
|
||||
if ($sla) {
|
||||
$priorPauses = ticket_sla_completed_pause_seconds($pdo, (int) $ticket['id']);
|
||||
foreach (['first_response_due_at', 'resolution_due_at'] as $deadline) if (!empty($sla[$deadline]) && $priorPauses > 0) $sla[$deadline] = ticket_sla_shift_deadline($sla[$deadline], $priorPauses);
|
||||
if (!empty($sla['first_response_due_at'])) ticket_sla_history_add($pdo, (int) $ticket['id'], 'adjusted', 'first_response', 'Neue Frist: ' . $sla['first_response_due_at'], (int) $staff['id']);
|
||||
if (!empty($sla['resolution_due_at'])) ticket_sla_history_add($pdo, (int) $ticket['id'], 'adjusted', 'resolution', 'Neue Frist: ' . $sla['resolution_due_at'], (int) $staff['id']);
|
||||
}
|
||||
|
||||
$resolvedAt = $ticket['resolved_at'];
|
||||
$closedAt = $ticket['closed_at'];
|
||||
@@ -539,14 +582,18 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
'UPDATE tickets SET department_id=?, priority_id=?, status=?, assigned_staff_id=?, team_id=?,
|
||||
first_response_due_at=?, resolution_due_at=?, resolved_at=?, closed_at=?, updated_at=? WHERE id=?'
|
||||
first_response_due_at=?, resolution_due_at=?, resolved_at=?, closed_at=?, resolution_code=?, resolution_note=?, updated_at=? WHERE id=?'
|
||||
);
|
||||
$stmt->execute([
|
||||
$deptId, $prioId, $status, $assignedTo, $teamId,
|
||||
$sla['first_response_due_at'] ?? $ticket['first_response_due_at'],
|
||||
$sla['resolution_due_at'] ?? $ticket['resolution_due_at'],
|
||||
$resolvedAt, $closedAt, $now, $ticket['id'],
|
||||
$resolvedAt, $closedAt,
|
||||
in_array($status, ['resolved', 'closed'], true) ? ($resolutionCode ?: $ticket['resolution_code']) : $ticket['resolution_code'],
|
||||
in_array($status, ['resolved', 'closed'], true) ? ($resolutionNote ?: $ticket['resolution_note']) : $ticket['resolution_note'],
|
||||
$now, $ticket['id'],
|
||||
]);
|
||||
ticket_sla_status_transition($pdo, (int) $ticket['id'], (string) $ticket['status'], $status, (int) $staff['id']);
|
||||
|
||||
if ($assignedTo !== (int) ($ticket['assigned_staff_id'] ?? 0) && $assignedTo !== null) {
|
||||
$agentName = '';
|
||||
@@ -648,7 +695,9 @@ $pageTitle = "Ticket #{$ticket['ticket_number']}";
|
||||
$activePage = 'tickets';
|
||||
require __DIR__ . '/_header.php';
|
||||
[$statusLabel, $statusColor] = status_label($ticket['status']);
|
||||
$isOverdue = $ticket['resolution_due_at'] && strtotime($ticket['resolution_due_at']) < time() && !in_array($ticket['status'], ['resolved','closed']);
|
||||
$slaPaused = !empty($ticket['sla_paused_at']) && ticket_sla_is_paused_status((string) $ticket['status']);
|
||||
$slaHistory = ticket_sla_history_for_ticket($pdo, $ticket);
|
||||
$isOverdue = !empty($ticket['is_overdue']) || (!$slaPaused && $ticket['resolution_due_at'] && strtotime($ticket['resolution_due_at']) < time() && !in_array($ticket['status'], ['resolved','closed']));
|
||||
// Bewertung laden
|
||||
$ticketRating = null;
|
||||
if (in_array($ticket['status'], ['resolved','closed'])) {
|
||||
@@ -1143,13 +1192,22 @@ $sourceLabel = $sourceLabels[$ticket['source']] ?? $ticket['source'];
|
||||
</div>
|
||||
<div>
|
||||
<label for="set_status">Status nach dem Senden</label>
|
||||
<select name="set_status" id="set_status">
|
||||
<select name="set_status" id="set_status" onchange="syncReplyResolutionFields()">
|
||||
<option value="answered">Beantwortet</option>
|
||||
<option value="pending">Wartet auf Kunde</option>
|
||||
<option value="resolved">Gelöst</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div id="reply-resolution-fields" style="display:none;margin-top:10px;padding:10px;border:1px solid var(--border-soft);border-radius:8px">
|
||||
<label for="reply_resolution_code">Abschlussgrund <span style="color:var(--danger)">*</span></label>
|
||||
<select id="reply_resolution_code" name="resolution_code"><?php foreach ($resolutionReasons as $code => $label): ?><option value="<?= e($code) ?>" <?= $code === 'fixed' ? 'selected' : '' ?>><?= e($label) ?></option><?php endforeach; ?></select>
|
||||
<label for="reply_resolution_note" style="margin-top:7px">Lösungshinweis (optional)</label><textarea id="reply_resolution_note" name="resolution_note" rows="2" maxlength="2000" placeholder="Kurze Notiz zur Lösung"></textarea>
|
||||
</div>
|
||||
<script>
|
||||
function syncReplyResolutionFields(){var status=document.getElementById('set_status'),box=document.getElementById('reply-resolution-fields'),reason=document.getElementById('reply_resolution_code');if(!status||!box||!reason)return;var required=['resolved','closed'].includes(status.value);box.style.display=required?'block':'none';reason.required=required;}
|
||||
syncReplyResolutionFields();
|
||||
</script>
|
||||
<div class="btn-row">
|
||||
<button type="submit" class="btn" onclick="document.getElementById('form-action').value='reply'">Öffentliche Antwort senden</button>
|
||||
<button type="submit" class="btn secondary" onclick="document.getElementById('form-action').value='note'">Nur interne Notiz speichern</button>
|
||||
@@ -1189,13 +1247,19 @@ $sourceLabel = $sourceLabels[$ticket['source']] ?? $ticket['source'];
|
||||
<?php foreach (['status'=>['Offen'=>'open','Wartet auf Kunde'=>'pending','Beantwortet'=>'answered','Pausiert'=>'on_hold','Gelöst'=>'resolved','Geschlossen'=>'closed']] as $k=>$opts): ?>
|
||||
<div>
|
||||
<label for="status" style="margin:0 0 4px; font-size:.71rem; font-weight:700; text-transform:uppercase; letter-spacing:.06em; color:var(--muted)">Status</label>
|
||||
<select name="status" id="status" style="font-size:.875rem; padding:7px 10px">
|
||||
<select name="status" id="status" onchange="syncTicketResolutionFields()" style="font-size:.875rem; padding:7px 10px">
|
||||
<?php foreach (['open'=>'Offen','pending'=>'Wartet auf Kunde','answered'=>'Beantwortet','on_hold'=>'Pausiert','resolved'=>'Gelöst','closed'=>'Geschlossen'] as $val=>$lbl): ?>
|
||||
<option value="<?= $val ?>" <?= $ticket['status']===$val?'selected':'' ?>><?= $lbl ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<div id="ticket-resolution-fields">
|
||||
<label for="resolution_code" style="margin:0 0 4px;font-size:.71rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--muted)">Abschlussgrund</label>
|
||||
<select name="resolution_code" id="resolution_code" data-ticket-was-terminal="<?= in_array($ticket['status'], ['resolved', 'closed'], true) ? '1' : '0' ?>" style="font-size:.875rem;padding:7px 10px"><option value="" <?= empty($ticket['resolution_code']) ? 'selected' : '' ?>>Grund auswählen</option><?php foreach ($resolutionReasons as $code => $label): ?><option value="<?= e($code) ?>" <?= ($ticket['resolution_code'] ?? '') === $code ? 'selected' : '' ?>><?= e($label) ?></option><?php endforeach; ?></select>
|
||||
<label for="resolution_note" style="margin:7px 0 4px;font-size:.71rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--muted)">Lösungshinweis (optional)</label>
|
||||
<input type="text" name="resolution_note" id="resolution_note" maxlength="2000" value="<?= e((string) ($ticket['resolution_note'] ?? '')) ?>" placeholder="Kurze Notiz zur Lösung">
|
||||
</div>
|
||||
<div>
|
||||
<label for="priority_id" style="margin:0 0 4px; font-size:.71rem; font-weight:700; text-transform:uppercase; letter-spacing:.06em; color:var(--muted)">Priorität</label>
|
||||
<select name="priority_id" id="priority_id" style="font-size:.875rem; padding:7px 10px">
|
||||
@@ -1236,25 +1300,49 @@ $sourceLabel = $sourceLabels[$ticket['source']] ?? $ticket['source'];
|
||||
</div>
|
||||
<button type="submit" class="btn" style="margin-top:12px">Speichern</button>
|
||||
</form>
|
||||
<script>
|
||||
function syncTicketResolutionFields(){var status=document.getElementById('status'),reason=document.getElementById('resolution_code');if(reason)reason.required=!!status&&['resolved','closed'].includes(status.value)&&reason.dataset.ticketWasTerminal!=='1';}
|
||||
syncTicketResolutionFields();
|
||||
</script>
|
||||
</div>
|
||||
|
||||
<div class="card" style="padding:14px 20px">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;gap:8px"><span style="font-size:.71rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--muted)">Zugehörige Probleme</span><?php if ($staff['role'] === 'admin'): ?><a class="btn secondary" style="padding:3px 8px;font-size:.75rem" href="/staff/problems.php">Verwalten</a><?php endif; ?></div>
|
||||
<?php if ($ticketProblems): foreach ($ticketProblems as $problemItem): ?>
|
||||
<div style="padding:8px 0;border-top:1px solid var(--border-soft);margin-top:8px"><a href="/staff/problems.php?id=<?= (int) $problemItem['id'] ?>"><strong><?= e(ticket_problem_number((int) $problemItem['id'])) ?> · <?= e($problemItem['title']) ?></strong></a><div class="muted" style="font-size:.78rem;margin-top:3px"><?= e(ticket_problem_statuses()[$problemItem['status']] ?? $problemItem['status']) ?></div><?php if (!empty($problemItem['workaround'])): ?><div style="font-size:.82rem;margin-top:5px"><strong>Workaround:</strong> <?= nl2br(e((string) $problemItem['workaround'])) ?></div><?php endif; ?><?php if ($staff['role'] === 'admin'): ?><form method="post" style="margin-top:6px"><?= csrf_field() ?><input type="hidden" name="action" value="problem_unlink"><input type="hidden" name="problem_id" value="<?= (int) $problemItem['id'] ?>"><button class="btn secondary" type="submit" style="padding:2px 8px;font-size:.75rem">Trennen</button></form><?php endif; ?></div>
|
||||
<?php endforeach; else: ?><p class="muted" style="font-size:.84rem">Dieses Ticket ist keinem bekannten Problem zugeordnet.</p><?php endif; ?>
|
||||
<?php if ($staff['role'] === 'admin' && $openProblems): ?><form method="post" style="display:flex;gap:6px;align-items:end;margin-top:8px"><?= csrf_field() ?><input type="hidden" name="action" value="problem_link"><div style="flex:1;min-width:150px"><label style="font-size:.75rem">Mit Problem verknüpfen</label><select name="problem_id" required><?php foreach ($openProblems as $problemOption): ?><option value="<?= (int) $problemOption['id'] ?>"><?= e(ticket_problem_number((int) $problemOption['id']) . ' · ' . $problemOption['title']) ?></option><?php endforeach; ?></select></div><button class="btn secondary" type="submit">Verknüpfen</button></form><?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- SLA + Individuelle Felder -->
|
||||
<div class="card" style="padding:16px 20px">
|
||||
<div style="font-size:.71rem; font-weight:700; text-transform:uppercase; letter-spacing:.06em; color:var(--muted); margin-bottom:10px">SLA-Fristen</div>
|
||||
<?php if ($slaPaused): ?><div style="padding:8px 10px;margin-bottom:12px;border-radius:7px;background:#f1f5f9;color:#475569;font-size:.82rem"><strong>SLA pausiert</strong> seit <?= e(format_dt((string) $ticket['sla_paused_at'])) ?> – die Fristen laufen bei „<?= e($statusLabel) ?>“ nicht weiter.</div><?php endif; ?>
|
||||
<div style="display:grid; grid-template-columns:1fr 1fr; gap:12px">
|
||||
<div>
|
||||
<div style="font-size:.7rem; color:var(--muted-lt); margin-bottom:2px">Erstreaktion</div>
|
||||
<?php $responseOverdue = $ticket['first_response_due_at'] && !$ticket['first_responded_at'] && strtotime($ticket['first_response_due_at']) < time(); ?>
|
||||
<?php $responseOverdue = !$slaPaused && $ticket['first_response_due_at'] && !$ticket['first_responded_at'] && strtotime($ticket['first_response_due_at']) < time(); ?>
|
||||
<div style="font-size:.8rem; font-weight:700; color:<?= $responseOverdue ? 'var(--danger)' : 'var(--ink)' ?>"><?= format_dt($ticket['first_response_due_at']) ?></div>
|
||||
<div style="font-size:.74rem; color:<?= $responseOverdue ? 'var(--danger)' : 'var(--muted)' ?>"><?= $ticket['first_responded_at'] ? '✓ beantwortet' : time_until($ticket['first_response_due_at']) ?></div>
|
||||
<div style="font-size:.74rem; color:<?= $responseOverdue ? 'var(--danger)' : 'var(--muted)' ?>"><?= $ticket['first_responded_at'] ? '✓ beantwortet' : ($slaPaused ? 'Pausiert' : time_until($ticket['first_response_due_at'])) ?></div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:.7rem; color:var(--muted-lt); margin-bottom:2px">Lösung</div>
|
||||
<?php $resolutionOverdue = $ticket['resolution_due_at'] && !$ticket['resolved_at'] && strtotime($ticket['resolution_due_at']) < time(); ?>
|
||||
<?php $resolutionOverdue = !$slaPaused && $ticket['resolution_due_at'] && !$ticket['resolved_at'] && strtotime($ticket['resolution_due_at']) < time(); ?>
|
||||
<div style="font-size:.8rem; font-weight:700; color:<?= $resolutionOverdue ? 'var(--danger)' : 'var(--ink)' ?>"><?= format_dt($ticket['resolution_due_at']) ?></div>
|
||||
<div style="font-size:.74rem; color:<?= $resolutionOverdue ? 'var(--danger)' : 'var(--muted)' ?>"><?= $ticket['resolved_at'] ? '✓ gelöst' : time_until($ticket['resolution_due_at']) ?></div>
|
||||
<div style="font-size:.74rem; color:<?= $resolutionOverdue ? 'var(--danger)' : 'var(--muted)' ?>"><?= $ticket['resolved_at'] ? '✓ gelöst' : ($slaPaused ? 'Pausiert' : time_until($ticket['resolution_due_at'])) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<details style="margin-top:12px;border-top:1px solid var(--border-soft);padding-top:9px">
|
||||
<summary style="cursor:pointer;font-size:.8rem;font-weight:700">SLA-Verlauf</summary>
|
||||
<?php if ($slaHistory): ?><div style="display:grid;gap:6px;margin-top:8px">
|
||||
<?php foreach ($slaHistory as $slaEvent):
|
||||
$eventLabels = ['started' => 'Frist gestartet', 'adjusted' => 'Frist angepasst', 'paused' => 'Frist pausiert', 'resumed' => 'Frist fortgesetzt', 'overdue' => 'Frist überschritten'];
|
||||
$deadlineLabels = ['first_response' => 'Erstreaktion', 'resolution' => 'Lösung'];
|
||||
?>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:8px;align-items:baseline;font-size:.76rem"><time class="muted" style="white-space:nowrap"><?= e(format_dt((string) $slaEvent['created_at'])) ?></time><strong><?= e($eventLabels[$slaEvent['event_type']] ?? $slaEvent['event_type']) ?><?= !empty($slaEvent['deadline_type']) ? ' · ' . e($deadlineLabels[$slaEvent['deadline_type']] ?? $slaEvent['deadline_type']) : '' ?></strong><span class="muted"><?= e((string) ($slaEvent['details'] ?? '')) ?><?= !empty($slaEvent['staff_name']) ? ' · ' . e($slaEvent['staff_name']) : '' ?></span></div>
|
||||
<?php endforeach; ?>
|
||||
</div><?php else: ?><p class="muted" style="font-size:.78rem">Noch keine SLA-Ereignisse erfasst.</p><?php endif; ?>
|
||||
</details>
|
||||
<?php if ($customFieldValues): ?>
|
||||
<div style="border-top:1px solid var(--border-soft); margin-top:12px; padding-top:12px">
|
||||
<div style="font-size:.71rem; font-weight:700; text-transform:uppercase; letter-spacing:.06em; color:var(--muted); margin-bottom:10px">Weitere Felder</div>
|
||||
|
||||
Reference in New Issue
Block a user