172 lines
7.4 KiB
PHP
172 lines
7.4 KiB
PHP
<?php
|
||
/**
|
||
* staff/ticket_print.php
|
||
* Saubere Druckansicht eines Tickets – ohne Navigation, optimiert für Druck/PDF.
|
||
*/
|
||
require_once __DIR__ . '/../includes/bootstrap.php';
|
||
$staff = require_staff_login();
|
||
$pdo = get_db();
|
||
|
||
$number = $_GET['n'] ?? '';
|
||
$stmt = $pdo->prepare(
|
||
'SELECT t.*, p.name AS priority_name, p.color AS priority_color,
|
||
d.name AS department_name, s.name AS staff_name
|
||
FROM tickets t
|
||
JOIN priorities p ON p.id = t.priority_id
|
||
JOIN departments d ON d.id = t.department_id
|
||
LEFT JOIN staff s ON s.id = t.assigned_staff_id
|
||
WHERE t.ticket_number = ?'
|
||
);
|
||
$stmt->execute([$number]);
|
||
$ticket = $stmt->fetch();
|
||
|
||
if (!$ticket) {
|
||
http_response_code(404);
|
||
die('Ticket nicht gefunden.');
|
||
}
|
||
if (!staff_can_access_department($staff, (int) $ticket['department_id'])) {
|
||
http_response_code(403);
|
||
die('Sie haben keinen Zugriff auf Tickets dieser Abteilung.');
|
||
}
|
||
|
||
// Thread laden
|
||
$thread = $pdo->prepare(
|
||
'SELECT tt.*, s.name AS staff_name, u.name AS user_name
|
||
FROM ticket_threads tt
|
||
LEFT JOIN staff s ON s.id = tt.staff_id
|
||
LEFT JOIN users u ON u.id = tt.user_id
|
||
WHERE tt.ticket_id = ? AND tt.is_internal_note = 0
|
||
ORDER BY tt.created_at ASC'
|
||
);
|
||
$thread->execute([$ticket['id']]);
|
||
$messages = $thread->fetchAll();
|
||
|
||
// Anhänge
|
||
$attStmt = $pdo->prepare('SELECT * FROM ticket_attachments WHERE thread_id = ?');
|
||
|
||
$siteName = get_setting('display_name', config()['site']['name'] ?? 'DeskHub');
|
||
[$statusLabel] = status_label($ticket['status']);
|
||
?>
|
||
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>Ticket #<?= e($ticket['ticket_number']) ?> – <?= e($ticket['subject']) ?></title>
|
||
<style>
|
||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; font-size: 11pt; color: #1e293b; background: #fff; padding: 20mm; }
|
||
|
||
.print-header { display: flex; justify-content: space-between; align-items: flex-start; padding-bottom: 12px; border-bottom: 2px solid #0f6f6a; margin-bottom: 16px; }
|
||
.print-header .logo { font-size: 18pt; font-weight: 800; color: #0f6f6a; }
|
||
.print-header .meta { text-align: right; font-size: 9pt; color: #64748b; }
|
||
|
||
.ticket-title { font-size: 16pt; font-weight: 700; margin-bottom: 12px; }
|
||
|
||
.info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 24px; margin-bottom: 20px; padding: 12px 16px; background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 6px; }
|
||
.info-row { display: flex; gap: 8px; font-size: 9.5pt; }
|
||
.info-label { font-weight: 600; color: #64748b; min-width: 100px; }
|
||
.info-value { color: #1e293b; }
|
||
|
||
.section-title { font-size: 10pt; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: #64748b; margin: 20px 0 10px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px; }
|
||
|
||
.message { border: 1px solid #e2e8f0; border-radius: 6px; margin-bottom: 12px; break-inside: avoid; }
|
||
.message-header { display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; background: #f8fafc; border-bottom: 1px solid #e2e8f0; font-size: 9pt; }
|
||
.message-header .author { font-weight: 600; }
|
||
.message-header .date { color: #64748b; }
|
||
.message-header.staff-header { background: #e6f4f3; }
|
||
.message-body { padding: 10px 12px; font-size: 10pt; line-height: 1.6; }
|
||
|
||
.attachments { padding: 6px 12px 8px; border-top: 1px solid #e2e8f0; font-size: 9pt; color: #64748b; }
|
||
|
||
.print-footer { margin-top: 24px; padding-top: 10px; border-top: 1px solid #e2e8f0; font-size: 8.5pt; color: #94a3b8; display: flex; justify-content: space-between; }
|
||
|
||
.no-print { }
|
||
@media print {
|
||
body { padding: 10mm; }
|
||
.no-print { display: none !important; }
|
||
@page { margin: 15mm; }
|
||
}
|
||
</style>
|
||
<?= site_icon_links() ?>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- Drucken-Button (nur am Bildschirm) -->
|
||
<div class="no-print" style="position:fixed;top:16px;right:16px;display:flex;gap:8px;z-index:99">
|
||
<button onclick="window.print()" style="padding:8px 18px;background:#0f6f6a;color:#fff;border:none;border-radius:6px;cursor:pointer;font-size:.88rem;font-weight:600">🖨 Drucken / Als PDF speichern</button>
|
||
<a href="/staff/ticket_view.php?n=<?= urlencode($number) ?>" style="padding:8px 16px;border:1px solid #e2e8f0;border-radius:6px;text-decoration:none;color:#64748b;font-size:.88rem;background:#fff">← Zurück</a>
|
||
</div>
|
||
|
||
<!-- Kopfzeile -->
|
||
<div class="print-header">
|
||
<div class="logo"><?= e($siteName) ?></div>
|
||
<div class="meta">
|
||
Gedruckt: <?= date('d.m.Y H:i') ?><br>
|
||
von: <?= e($staff['name']) ?>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Ticket-Titel -->
|
||
<div class="ticket-title"><?= e($ticket['subject']) ?></div>
|
||
|
||
<!-- Ticket-Info -->
|
||
<div class="info-grid">
|
||
<div class="info-row"><span class="info-label">Ticket-Nr.:</span><span class="info-value">#<?= e($ticket['ticket_number']) ?></span></div>
|
||
<div class="info-row"><span class="info-label">Status:</span><span class="info-value"><?= e($statusLabel) ?></span></div>
|
||
<div class="info-row"><span class="info-label">Absender:</span><span class="info-value"><?= e($ticket['requester_name']) ?> <<?= e($ticket['requester_email']) ?>></span></div>
|
||
<div class="info-row"><span class="info-label">Priorität:</span><span class="info-value"><?= e($ticket['priority_name']) ?></span></div>
|
||
<div class="info-row"><span class="info-label">Abteilung:</span><span class="info-value"><?= e($ticket['department_name']) ?></span></div>
|
||
<div class="info-row"><span class="info-label">Zugewiesen:</span><span class="info-value"><?= e($ticket['staff_name'] ?? '– nicht zugewiesen –') ?></span></div>
|
||
<div class="info-row"><span class="info-label">Erstellt:</span><span class="info-value"><?= date('d.m.Y H:i', strtotime($ticket['created_at'])) ?></span></div>
|
||
<?php if ($ticket['resolution_due_at']): ?>
|
||
<div class="info-row"><span class="info-label">Fällig bis:</span><span class="info-value"><?= date('d.m.Y H:i', strtotime($ticket['resolution_due_at'])) ?></span></div>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<!-- Nachrichten -->
|
||
<div class="section-title">Verlauf (<?= count($messages) ?> Nachricht<?= count($messages) !== 1 ? 'en' : '' ?>)</div>
|
||
|
||
<?php foreach ($messages as $msg):
|
||
$isStaff = $msg['author_type'] === 'staff';
|
||
$author = $isStaff ? ($msg['staff_name'] ?? 'Mitarbeiter') : ($msg['user_name'] ?? $ticket['requester_name']);
|
||
$body = $msg['message'] ?? '';
|
||
$isHtml = message_is_html($body);
|
||
|
||
$attStmt->execute([$msg['id']]);
|
||
$atts = $attStmt->fetchAll();
|
||
?>
|
||
<div class="message">
|
||
<div class="message-header <?= $isStaff ? 'staff-header' : '' ?>">
|
||
<span class="author"><?= e($author) ?><?= $isStaff ? ' (Mitarbeiter)' : ' (Kunde)' ?></span>
|
||
<span class="date"><?= date('d.m.Y H:i', strtotime($msg['created_at'])) ?></span>
|
||
</div>
|
||
<div class="message-body">
|
||
<?php if ($isHtml): ?>
|
||
<?= sanitize_staff_html($body) ?>
|
||
<?php else: ?>
|
||
<?php
|
||
$text = trim($body);
|
||
foreach (preg_split('/\n{2,}/', $text) as $p) {
|
||
$p = trim($p);
|
||
if ($p !== '') echo '<p style="margin:0 0 8px">'.nl2br(e($p)).'</p>';
|
||
}
|
||
?>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php if ($atts): ?>
|
||
<div class="attachments">
|
||
📎 Anhänge: <?= implode(', ', array_map(fn($a) => e($a['original_name']), $atts)) ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
|
||
<!-- Fußzeile -->
|
||
<div class="print-footer">
|
||
<span><?= e($siteName) ?> – Ticket #<?= e($ticket['ticket_number']) ?></span>
|
||
<span>Druckdatum: <?= date('d.m.Y H:i') ?></span>
|
||
</div>
|
||
|
||
</body>
|
||
</html>
|