203 lines
9.4 KiB
PHP
203 lines
9.4 KiB
PHP
<?php
|
||
require_once __DIR__ . '/../includes/bootstrap.php';
|
||
$staff = require_staff_login();
|
||
$pdo = get_db();
|
||
|
||
$errors = [];
|
||
$allStaff = $pdo->query('SELECT id, name FROM staff WHERE is_active = 1 ORDER BY name')->fetchAll();
|
||
|
||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||
verify_csrf();
|
||
$action = $_POST['action'] ?? '';
|
||
|
||
if ($action === 'create') {
|
||
$title = trim($_POST['title'] ?? '');
|
||
$desc = trim($_POST['description'] ?? '');
|
||
$assignTo = ($_POST['assigned_to'] ?? '') !== '' ? (int)$_POST['assigned_to'] : null;
|
||
$dueAt = trim($_POST['due_at'] ?? '') ?: null;
|
||
$ticketId = ($_POST['ticket_id'] ?? '') !== '' ? (int)$_POST['ticket_id'] : null;
|
||
|
||
if ($title === '') $errors[] = 'Bitte einen Titel angeben.';
|
||
if (!$errors) {
|
||
$pdo->prepare('INSERT INTO tasks (ticket_id,title,description,assigned_to,due_at,created_by,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?)')
|
||
->execute([$ticketId, $title, $desc ?: null, $assignTo, $dueAt, $staff['id'], db_now(), db_now()]);
|
||
flash('success', 'Aufgabe erstellt.');
|
||
if ($ticketId) {
|
||
$tn = $pdo->prepare('SELECT ticket_number FROM tickets WHERE id=?'); $tn->execute([$ticketId]);
|
||
redirect('/staff/ticket_view.php?n=' . urlencode($tn->fetchColumn()));
|
||
}
|
||
redirect('/staff/tasks.php');
|
||
}
|
||
}
|
||
|
||
if ($action === 'update_status') {
|
||
$id = (int) $_POST['id'];
|
||
$status = in_array($_POST['status']??'', ['open','in_progress','done']) ? $_POST['status'] : 'open';
|
||
$pdo->prepare('UPDATE tasks SET status=?, updated_at=? WHERE id=?')->execute([$status, db_now(), $id]);
|
||
// Kein open redirect: Referer nur wenn er auf /staff/ zeigt
|
||
$ref = $_SERVER['HTTP_REFERER'] ?? '';
|
||
$safePath = (parse_url($ref, PHP_URL_HOST) === ($_SERVER['HTTP_HOST'] ?? '') && strpos(parse_url($ref, PHP_URL_PATH) ?? '', '/staff/') === 0)
|
||
? $ref : '/staff/tasks.php';
|
||
redirect($safePath);
|
||
}
|
||
|
||
if ($action === 'delete') {
|
||
$pdo->prepare('DELETE FROM tasks WHERE id=?')->execute([(int)$_POST['id']]);
|
||
flash('success', 'Aufgabe gelöscht.');
|
||
redirect('/staff/tasks.php');
|
||
}
|
||
}
|
||
|
||
$filter = $_GET['filter'] ?? 'my'; // my, all, open, done
|
||
$where = [];
|
||
$params = [];
|
||
|
||
if ($filter === 'my') {
|
||
$where[] = 'ta.assigned_to = ?'; $params[] = $staff['id'];
|
||
} elseif ($filter === 'open') {
|
||
$where[] = "ta.status != 'done'";
|
||
} elseif ($filter === 'done') {
|
||
$where[] = "ta.status = 'done'";
|
||
}
|
||
|
||
$sql = 'SELECT ta.*, s.name AS assigned_name, s2.name AS creator_name,
|
||
t.ticket_number, t.subject AS ticket_subject
|
||
FROM tasks ta
|
||
LEFT JOIN staff s ON s.id = ta.assigned_to
|
||
LEFT JOIN staff s2 ON s2.id = ta.created_by
|
||
LEFT JOIN tickets t ON t.id = ta.ticket_id'
|
||
. ($where ? ' WHERE ' . implode(' AND ', $where) : '')
|
||
. ' ORDER BY ta.due_at IS NULL, ta.due_at ASC, ta.created_at DESC LIMIT 200';
|
||
$stmt = $pdo->prepare($sql);
|
||
$stmt->execute($params);
|
||
$tasks = $stmt->fetchAll();
|
||
|
||
$statusLabels = ['open'=>'Offen','in_progress'=>'In Arbeit','done'=>'Erledigt'];
|
||
$statusColors = ['open'=>'#2563eb','in_progress'=>'#d97706','done'=>'#16a34a'];
|
||
|
||
$pageTitle = 'Aufgaben'; $activePage = 'tasks';
|
||
require __DIR__ . '/_header.php';
|
||
?>
|
||
<div class="topbar">
|
||
<h1>Aufgaben</h1>
|
||
<button type="button" class="btn" onclick="document.getElementById('new-task-form').style.display=document.getElementById('new-task-form').style.display==='none'?'':'none'">+ Neue Aufgabe</button>
|
||
</div>
|
||
|
||
<?php foreach ($errors as $e): ?><div class="flash error"><?= e($e) ?></div><?php endforeach; ?>
|
||
|
||
<!-- Neue Aufgabe -->
|
||
<div id="new-task-form" class="card" style="display:none; margin-bottom:20px">
|
||
<h3>Neue Aufgabe</h3>
|
||
<form method="post">
|
||
<?= csrf_field() ?>
|
||
<input type="hidden" name="action" value="create">
|
||
<div class="field-row">
|
||
<div style="flex:2">
|
||
<label for="title">Titel <span style="color:var(--danger)">*</span></label>
|
||
<input type="text" id="title" name="title" placeholder="Was ist zu tun?" required>
|
||
</div>
|
||
<div>
|
||
<label for="due_at">Fällig bis</label>
|
||
<input type="datetime-local" id="due_at" name="due_at">
|
||
</div>
|
||
</div>
|
||
<div class="field-row">
|
||
<div>
|
||
<label for="assigned_to">Zuweisen an</label>
|
||
<select name="assigned_to" id="assigned_to">
|
||
<option value="<?= $staff['id'] ?>" selected><?= e($staff['name']) ?> (ich)</option>
|
||
<?php foreach ($allStaff as $s): if ($s['id'] == $staff['id']) continue; ?>
|
||
<option value="<?= $s['id'] ?>"><?= e($s['name']) ?></option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label for="ticket_id_text">Ticket verknüpfen (Ticket-Nr., optional)</label>
|
||
<input type="text" id="ticket_id_text" placeholder="z.B. 260622-1234" oninput="lookupTicket(this.value)">
|
||
<input type="hidden" name="ticket_id" id="ticket_id_hidden">
|
||
<div id="ticket_lookup_result" style="font-size:.78rem; color:var(--muted); margin-top:2px"></div>
|
||
</div>
|
||
</div>
|
||
<label for="description">Beschreibung (optional)</label>
|
||
<textarea id="description" name="description" style="min-height:80px"></textarea>
|
||
<div class="btn-row">
|
||
<button type="submit" class="btn">Erstellen</button>
|
||
<button type="button" class="btn secondary" onclick="document.getElementById('new-task-form').style.display='none'">Abbrechen</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<!-- Filter-Tabs -->
|
||
<div style="display:flex; gap:0; border-bottom:2px solid var(--border); margin-bottom:16px">
|
||
<?php foreach (['my'=>'Meine','all'=>'Alle','open'=>'Offen','done'=>'Erledigt'] as $f=>$lbl): ?>
|
||
<a href="/staff/tasks.php?filter=<?= $f ?>"
|
||
style="padding:8px 18px; font-size:.88rem; font-weight:600; text-decoration:none; border-bottom:3px solid <?= $filter===$f?'var(--primary)':'transparent' ?>; margin-bottom:-2px; color:<?= $filter===$f?'var(--primary)':'var(--muted)' ?>">
|
||
<?= $lbl ?>
|
||
</a>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<table>
|
||
<thead><tr><th>Aufgabe</th><th>Verknüpftes Ticket</th><th>Zugewiesen</th><th>Fällig</th><th>Status</th><th></th></tr></thead>
|
||
<tbody>
|
||
<?php foreach ($tasks as $task):
|
||
$overdue = $task['due_at'] && $task['status'] !== 'done' && strtotime($task['due_at']) < time();
|
||
?>
|
||
<tr class="<?= $overdue ? 'is-overdue' : '' ?>">
|
||
<td>
|
||
<strong style="<?= $task['status']==='done' ? 'text-decoration:line-through;color:var(--muted)' : '' ?>"><?= e($task['title']) ?></strong>
|
||
<?php if ($task['description']): ?><br><span class="muted" style="font-size:.78rem"><?= e(mb_strimwidth($task['description'], 0, 80, '…')) ?></span><?php endif; ?>
|
||
</td>
|
||
<td>
|
||
<?php if ($task['ticket_number']): ?>
|
||
<a href="/staff/ticket_view.php?n=<?= e($task['ticket_number']) ?>" class="ticket-no">#<?= e($task['ticket_number']) ?></a><br>
|
||
<span class="muted" style="font-size:.76rem"><?= e(mb_strimwidth($task['ticket_subject']??'', 0, 40, '…')) ?></span>
|
||
<?php else: ?><span class="muted">–</span><?php endif; ?>
|
||
</td>
|
||
<td><?= e($task['assigned_name'] ?? '–') ?></td>
|
||
<td class="muted" style="white-space:nowrap; <?= $overdue ? 'color:var(--danger)' : '' ?>">
|
||
<?= $task['due_at'] ? format_dt($task['due_at']) : '–' ?>
|
||
</td>
|
||
<td>
|
||
<form method="post" style="display:inline">
|
||
<?= csrf_field() ?>
|
||
<input type="hidden" name="action" value="update_status">
|
||
<input type="hidden" name="id" value="<?= $task['id'] ?>">
|
||
<select name="status" onchange="this.form.submit()" style="padding:3px 6px; font-size:.78rem; border-radius:5px">
|
||
<?php foreach ($statusLabels as $sv=>$sl): ?>
|
||
<option value="<?= $sv ?>" <?= $task['status']===$sv?'selected':'' ?>><?= $sl ?></option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</form>
|
||
</td>
|
||
<td>
|
||
<form method="post" style="display:inline" onsubmit="return confirm('Aufgabe löschen?')">
|
||
<?= csrf_field() ?>
|
||
<input type="hidden" name="action" value="delete">
|
||
<input type="hidden" name="id" value="<?= $task['id'] ?>">
|
||
<button type="submit" style="background:none;border:none;color:#dc2626;cursor:pointer;padding:0;font-size:.85rem">✕</button>
|
||
</form>
|
||
</td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
<?php if (!$tasks): ?><tr><td colspan="6" class="muted" style="text-align:center;padding:28px">Keine Aufgaben gefunden.</td></tr><?php endif; ?>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<script>
|
||
function lookupTicket(num) {
|
||
var n = num.trim();
|
||
var hidden = document.getElementById('ticket_id_hidden');
|
||
var result = document.getElementById('ticket_lookup_result');
|
||
if (!n) { hidden.value=''; result.textContent=''; return; }
|
||
fetch('/staff/_ticket_lookup_ajax.php?n=' + encodeURIComponent(n))
|
||
.then(r=>r.json()).then(d=>{
|
||
if (d.id) { hidden.value=d.id; result.textContent='✓ ' + d.subject; result.style.color='var(--success)'; }
|
||
else { hidden.value=''; result.textContent='Ticket nicht gefunden'; result.style.color='var(--danger)'; }
|
||
}).catch(()=>{hidden.value='';result.textContent='';});
|
||
}
|
||
</script>
|
||
<?php require __DIR__ . '/_footer.php'; ?>
|