Files
omsorg/mitarbeiter-app/pages/admin.php
T
2026-06-13 20:03:22 +02:00

1399 lines
68 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__ . '/../lib/auth.php';
require_once __DIR__ . '/../lib/layout.php';
require_admin();
$me = current_username();
$tab_allowed = ['antraege', 'nutzer', 'dienstplan', 'downloads', 'fortbildung', 'stundennachweis', 'bewertungen', 'news', 'einsatzanweisung', 'login'];
$tab = in_array($_GET['tab'] ?? '', $tab_allowed, true) ? $_GET['tab'] : 'antraege';
$status_labels = [
'pending' => 'Ausstehend',
'accepted' => 'Akzeptiert',
'rejected' => 'Abgelehnt',
];
$type_labels = [
'urlaubsantrag' => 'Urlaubsantrag',
'abwesenheitsantrag' => 'Abwesenheitsantrag',
'benefitsantrag' => 'Benefitsantrag',
'werben' => 'Mitarbeiter werben',
'fortbildungsantrag' => 'Fortbildungsantrag',
'stundennachweis' => 'Stundennachweis',
];
/* ── Anträge ─────────────────────────────────────────────────────────── */
$detail_id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
$detail_type = $_GET['type'] ?? '';
$allowed_types = ['urlaubsantrag', 'abwesenheitsantrag', 'benefitsantrag', 'werben', 'fortbildungsantrag', 'stundennachweis'];
$req = null;
if ($detail_id > 0 && in_array($detail_type, $allowed_types, true) && $tab === 'antraege') {
$table = 'requests_' . $detail_type;
$stmt = db()->prepare(
"SELECT r.*, u.name AS user_name FROM $table r
JOIN users u ON u.id = r.user_id
WHERE r.id = ?"
);
$stmt->execute([$detail_id]);
$req = $stmt->fetch();
if (!$req) {
header('Location: admin.php');
exit;
}
$req['type'] = $detail_type;
}
$filter = $_GET['status'] ?? 'all';
$filter = in_array($filter, ['all', 'pending', 'accepted', 'rejected'], true) ? $filter : 'all';
$requests = [];
if ($tab === 'antraege' && !$req) {
$union_sql = "
SELECT id, user_id, status, admin_note, created_at, 'urlaubsantrag' AS type FROM requests_urlaubsantrag
UNION ALL
SELECT id, user_id, status, admin_note, created_at, 'abwesenheitsantrag' AS type FROM requests_abwesenheitsantrag
UNION ALL
SELECT id, user_id, status, admin_note, created_at, 'benefitsantrag' AS type FROM requests_benefitsantrag
UNION ALL
SELECT id, user_id, status, admin_note, created_at, 'werben' AS type FROM requests_werben
UNION ALL
SELECT id, user_id, status, admin_note, created_at, 'fortbildungsantrag' AS type FROM requests_fortbildungsantrag
UNION ALL
SELECT id, user_id, status, admin_note, created_at, 'stundennachweis' AS type FROM requests_stundennachweis
";
if ($filter === 'all') {
$sql = "SELECT r.*, u.name AS user_name
FROM ($union_sql) r
JOIN users u ON u.id = r.user_id
ORDER BY r.created_at DESC";
$requests = db()->query($sql)->fetchAll();
} else {
$sql = "SELECT r.*, u.name AS user_name
FROM ($union_sql) r
JOIN users u ON u.id = r.user_id
WHERE r.status = ?
ORDER BY r.created_at DESC";
$stmt = db()->prepare($sql);
$stmt->execute([$filter]);
$requests = $stmt->fetchAll();
}
}
/* ── Nutzer ──────────────────────────────────────────────────────────── */
$users = [];
if ($tab === 'nutzer') {
$users = db()->query('SELECT * FROM users ORDER BY created_at DESC')->fetchAll();
}
/* ── Downloads ───────────────────────────────────────────────────────── */
$downloads = [];
if ($tab === 'downloads') {
$downloads = db()->query('SELECT * FROM downloads ORDER BY sort_order ASC, id ASC')->fetchAll();
}
/* ── Fortbildung-Materialien ─────────────────────────────────────────── */
$fb_materials = [];
if ($tab === 'fortbildung') {
$fb_materials = db()->query('SELECT * FROM fortbildung_materials ORDER BY sort_order ASC, id ASC')->fetchAll();
}
/* ── News ────────────────────────────────────────────────────────────── */
$news_items = [];
if ($tab === 'news') {
$news_items = db()->query('SELECT * FROM news ORDER BY date DESC, id DESC')->fetchAll();
}
/* ── Einsatzanweisungen ──────────────────────────────────────────────── */
$ea_users = [];
if ($tab === 'einsatzanweisung') {
$ea_users = db()->query(
'SELECT u.id, u.name, u.username,
COALESCE(e.filename, \'\') AS filename,
COALESCE(e.original_name, \'\') AS original_name,
COALESCE(e.ort, \'\') AS ort,
COALESCE(e.uploaded_at, \'\') AS uploaded_at
FROM users u
LEFT JOIN einsatzanweisung e ON e.user_id = u.id
WHERE u.active = 1
ORDER BY u.name ASC'
)->fetchAll();
}
/* ── Stundennachweise ────────────────────────────────────────────────── */
$alle_stundennachweise = [];
if ($tab === 'stundennachweis') {
$alle_stundennachweise = db()->query(
'SELECT s.*, u.name AS user_name
FROM requests_stundennachweis s
JOIN users u ON u.id = s.user_id
ORDER BY s.monat DESC, s.created_at DESC'
)->fetchAll();
}
/* ── Einsatzbewertungen ──────────────────────────────────────────────── */
$alle_bewertungen = [];
if ($tab === 'bewertungen') {
$alle_bewertungen = db()->query(
'SELECT e.*, u.name AS user_name
FROM einsatzbewertungen e
JOIN users u ON u.id = e.user_id
ORDER BY e.created_at DESC'
)->fetchAll();
}
/* ── Dienstplan ──────────────────────────────────────────────────────── */
$dp_users = [];
$dp_selected = null;
$dp_selected_id = 0;
$dp_schichten = [];
$dp_overlays = [];
$dp_year = 0;
$dp_month = 0;
$dp_first = null;
$dp_days = 0;
$dp_prev_url = '';
$dp_next_url = '';
if ($tab === 'dienstplan') {
$dp_now = new DateTimeImmutable('today');
$dp_year = isset($_GET['year']) ? (int)$_GET['year'] : (int)$dp_now->format('Y');
$dp_month = isset($_GET['month']) ? (int)$_GET['month'] : (int)$dp_now->format('n');
$dp_year = max(2020, min(2099, $dp_year));
$dp_month = max(1, min(12, $dp_month));
$dp_users = db()->query(
'SELECT id, name FROM users WHERE active = 1 ORDER BY name ASC'
)->fetchAll();
$dp_selected_id = isset($_GET['user_id']) ? (int)$_GET['user_id'] : 0;
foreach ($dp_users as $u) {
if ((int)$u['id'] === $dp_selected_id) { $dp_selected = $u; break; }
}
if (!$dp_selected && !empty($dp_users)) {
$dp_selected = $dp_users[0];
$dp_selected_id = (int)$dp_selected['id'];
}
$dp_first = new DateTimeImmutable(sprintf('%04d-%02d-01', $dp_year, $dp_month));
$dp_month_start = $dp_first->format('Y-m-d');
$dp_month_end = $dp_first->modify('last day of this month')->format('Y-m-d');
$dp_days = (int)$dp_first->format('t');
$dp_prev = $dp_first->modify('-1 month');
$dp_next = $dp_first->modify('+1 month');
$dp_prev_url = 'admin.php?tab=dienstplan&user_id=' . $dp_selected_id . '&year=' . $dp_prev->format('Y') . '&month=' . $dp_prev->format('n');
$dp_next_url = 'admin.php?tab=dienstplan&user_id=' . $dp_selected_id . '&year=' . $dp_next->format('Y') . '&month=' . $dp_next->format('n');
if ($dp_selected_id) {
$stmt = db()->prepare(
'SELECT date, schicht FROM dienstplan WHERE user_id = ? AND date >= ? AND date <= ?'
);
$stmt->execute([$dp_selected_id, $dp_month_start, $dp_month_end]);
foreach ($stmt->fetchAll() as $row) {
$dp_schichten[$row['date']] = $row['schicht'];
}
$stmt = db()->prepare("
SELECT von, bis, 'urlaub' AS typ
FROM requests_urlaubsantrag
WHERE user_id = ? AND status = 'accepted' AND bis >= ? AND von <= ?
UNION ALL
SELECT von, bis, 'abwesenheit' AS typ
FROM requests_abwesenheitsantrag
WHERE user_id = ? AND status = 'accepted' AND bis >= ? AND von <= ?
");
$stmt->execute([$dp_selected_id, $dp_month_start, $dp_month_end,
$dp_selected_id, $dp_month_start, $dp_month_end]);
foreach ($stmt->fetchAll() as $row) {
$cursor = new DateTimeImmutable($row['von']);
$end = new DateTimeImmutable($row['bis']);
while ($cursor <= $end) {
$key = $cursor->format('Y-m-d');
if ($key >= $dp_month_start && $key <= $dp_month_end) {
$dp_overlays[$key] = $row['typ'];
}
$cursor = $cursor->modify('+1 day');
}
}
}
}
/* ── Login-Versuche ──────────────────────────────────────────────────── */
$login_attempts = [];
$login_total = 0;
$login_locked = [];
if ($tab === 'login') {
$login_total = (int)db()->query('SELECT COUNT(*) FROM login_attempts')->fetchColumn();
$login_attempts = db()->query(
'SELECT ip, username, attempted_at FROM login_attempts ORDER BY attempted_at DESC LIMIT 200'
)->fetchAll();
$cutoff = date('c', time() - 600);
$stmt = db()->prepare(
'SELECT ip, COUNT(*) AS cnt, MAX(attempted_at) AS last_at
FROM login_attempts
WHERE attempted_at >= ?
GROUP BY ip
HAVING COUNT(*) >= 5
ORDER BY cnt DESC'
);
$stmt->execute([$cutoff]);
$login_locked = $stmt->fetchAll();
}
$extra_head = <<<'CSS'
<style>
.admin-tabs{display:flex;gap:8px;margin-bottom:32px}
.admin-tab{
padding:10px 22px;border-radius:12px;border:1px solid rgba(170,240,250,.22);
background:rgba(255,255,255,.05);color:#ebfdff;font-weight:800;font-size:.95rem;
text-decoration:none;transition:background .15s,border-color .15s;
}
.admin-tab:hover{background:rgba(255,255,255,.1);border-color:rgba(170,240,250,.4)}
.admin-tab.active{
background:linear-gradient(135deg,rgba(32,225,234,.22),rgba(7,143,163,.18));
border-color:rgba(54,242,255,.45);color:#fff;
}
.modal-backdrop{
position:fixed;inset:0;background:rgba(0,0,0,.65);
display:flex;align-items:center;justify-content:center;z-index:100;
padding:20px;
}
.modal-box{
width:min(100%,480px);padding:32px;border-radius:22px;
background:linear-gradient(145deg,rgba(0,35,51,.95),rgba(8,105,122,.45));
border:1px solid rgba(170,240,250,.42);
box-shadow:0 22px 70px rgba(0,0,0,.5);
max-height:90vh;overflow-y:auto;
}
.modal-box h3{margin:0 0 20px;font-family:'KindelSerif',Georgia,serif;font-size:1.4rem}
.modal-close{
float:right;cursor:pointer;background:none;border:none;
color:var(--muted);font-size:1.4rem;line-height:1;padding:0;margin-top:-4px;
}
.users-table{width:100%;border-collapse:collapse;margin-top:20px}
.users-table th{
text-align:left;padding:10px 12px;color:var(--cyan2);
font-size:.78rem;text-transform:uppercase;letter-spacing:.08em;
border-bottom:1px solid rgba(170,240,250,.2);font-weight:900;
}
.users-table td{padding:11px 12px;border-bottom:1px solid rgba(170,240,250,.08);vertical-align:middle}
.users-table tr:hover td{background:rgba(255,255,255,.03)}
.badge-active{background:rgba(16,215,230,.15);border:1px solid rgba(54,242,255,.55);color:var(--cyan2)}
.badge-inactive{background:rgba(255,60,60,.15);border:1px solid rgba(255,100,100,.55);color:#ff8080}
.btn-sm{
display:inline-flex;align-items:center;gap:6px;
padding:7px 13px;border-radius:9px;border:1px solid rgba(255,255,255,.2);
background:rgba(255,255,255,.08);color:#fff;font-weight:800;font-size:.82rem;
cursor:pointer;font:inherit;text-decoration:none;transition:background .15s;
}
.btn-sm:hover{background:rgba(255,255,255,.15)}
.btn-sm-danger{border-color:rgba(255,100,100,.4);background:rgba(255,60,60,.1);color:#ff8080}
.btn-sm-danger:hover{background:rgba(255,60,60,.22)}
.btn-sm:disabled{opacity:.35;cursor:not-allowed}
@media(max-width:860px){
.admin-tabs{flex-wrap:wrap;gap:6px;margin-bottom:20px}
.admin-tab{padding:8px 14px;font-size:.85rem}
.users-table{display:block;overflow-x:auto;-webkit-overflow-scrolling:touch}
}
@media(max-width:480px){
.btn-sm{padding:11px 13px}
.modal-box{padding:20px 16px}
}
</style>
CSS;
layout_start('OMSORG Verwaltung', 'admin', $extra_head);
?>
<h1 class="main-title">Verwaltung</h1>
<p class="main-sub" style="margin-bottom:28px">Anträge und Nutzerverwaltung.</p>
<div class="admin-tabs">
<a href="admin.php" class="admin-tab <?= $tab === 'antraege' ? 'active' : '' ?>">Anträge</a>
<a href="admin.php?tab=nutzer" class="admin-tab <?= $tab === 'nutzer' ? 'active' : '' ?>">Nutzer</a>
<a href="admin.php?tab=dienstplan" class="admin-tab <?= $tab === 'dienstplan' ? 'active' : '' ?>">Dienstpläne</a>
<a href="admin.php?tab=downloads" class="admin-tab <?= $tab === 'downloads' ? 'active' : '' ?>">Downloads</a>
<a href="admin.php?tab=fortbildung" class="admin-tab <?= $tab === 'fortbildung' ? 'active' : '' ?>">Fortbildung</a>
<a href="admin.php?tab=stundennachweis" class="admin-tab <?= $tab === 'stundennachweis' ? 'active' : '' ?>">Stundennachweise</a>
<a href="admin.php?tab=bewertungen" class="admin-tab <?= $tab === 'bewertungen' ? 'active' : '' ?>">Bewertungen</a>
<a href="admin.php?tab=news" class="admin-tab <?= $tab === 'news' ? 'active' : '' ?>">News</a>
<a href="admin.php?tab=einsatzanweisung" class="admin-tab <?= $tab === 'einsatzanweisung' ? 'active' : '' ?>">Einsatzanweisungen</a>
<a href="admin.php?tab=login" class="admin-tab <?= $tab === 'login' ? 'active' : '' ?>">Login-Versuche</a>
</div>
<?php if ($tab === 'antraege'): ?>
<?php if ($req): ?>
<!-- ── Detail-Ansicht ──────────────────────────────────── -->
<a href="admin.php<?= $filter !== 'all' ? '?status=' . e($filter) : '' ?>"
style="color:var(--cyan2);font-weight:800;font-size:.9rem">&larr; Zurück zur Liste</a>
<h2 style="font-family:'KindelSerif',Georgia,serif;margin:20px 0 4px"><?= e($type_labels[$req['type']] ?? $req['type']) ?></h2>
<p class="main-sub">
Von <b><?= e($req['user_name']) ?></b> &middot;
<?= e(date('d.m.Y H:i', strtotime($req['created_at']))) ?>
</p>
<?php if (isset($_GET['updated'])): ?>
<div class="success" style="margin-top:20px">Status aktualisiert.</div>
<?php endif; ?>
<div class="glass detail-card" style="margin-top:24px;max-width:560px">
<?php switch ($req['type']):
case 'urlaubsantrag': ?>
<div class="detail-field">
<div class="detail-field-label">Von</div>
<div class="detail-field-value"><?= e(date('d.m.Y', strtotime($req['von']))) ?></div>
</div>
<div class="detail-field">
<div class="detail-field-label">Bis</div>
<div class="detail-field-value"><?= e(date('d.m.Y', strtotime($req['bis']))) ?></div>
</div>
<div class="detail-field">
<div class="detail-field-label">Vertretung</div>
<div class="detail-field-value"><?= $req['vertretung'] !== '' ? e($req['vertretung']) : '<span style="color:var(--muted)"></span>' ?></div>
</div>
<div class="detail-field">
<div class="detail-field-label">Nachricht</div>
<div class="detail-field-value"><?= $req['nachricht'] !== '' ? nl2br(e($req['nachricht'])) : '<span style="color:var(--muted)"></span>' ?></div>
</div>
<?php break;
case 'abwesenheitsantrag':
$grund_labels = [
'arztbesuch' => 'Arztbesuch',
'behoerdengang' => 'Behördengang',
'sonderurlaub' => 'Sonderurlaub',
'elternzeit_pflegezeit' => 'Elternzeit / Pflegezeit',
'sonstiges' => 'Sonstiges',
]; ?>
<div class="detail-field">
<div class="detail-field-label">Von</div>
<div class="detail-field-value"><?= e(date('d.m.Y', strtotime($req['von']))) ?></div>
</div>
<div class="detail-field">
<div class="detail-field-label">Bis</div>
<div class="detail-field-value"><?= e(date('d.m.Y', strtotime($req['bis']))) ?></div>
</div>
<div class="detail-field">
<div class="detail-field-label">Grund</div>
<div class="detail-field-value"><?= e($grund_labels[$req['grund']] ?? $req['grund']) ?></div>
</div>
<div class="detail-field">
<div class="detail-field-label">Vertretung</div>
<div class="detail-field-value"><?= $req['vertretung'] !== '' ? e($req['vertretung']) : '<span style="color:var(--muted)"></span>' ?></div>
</div>
<div class="detail-field">
<div class="detail-field-label">Nachricht</div>
<div class="detail-field-value"><?= $req['nachricht'] !== '' ? nl2br(e($req['nachricht'])) : '<span style="color:var(--muted)"></span>' ?></div>
</div>
<?php break;
case 'benefitsantrag':
$benefit_labels = [
'massage' => 'Massage',
'fitnessstudio' => 'Fitnessstudio',
'tankgutschein' => 'Tankgutschein',
'fortbildung' => 'Fortbildung',
]; ?>
<div class="detail-field">
<div class="detail-field-label">Benefit</div>
<div class="detail-field-value"><?= e($benefit_labels[$req['benefit']] ?? $req['benefit']) ?></div>
</div>
<div class="detail-field">
<div class="detail-field-label">Nachricht</div>
<div class="detail-field-value"><?= $req['nachricht'] !== '' ? nl2br(e($req['nachricht'])) : '<span style="color:var(--muted)"></span>' ?></div>
</div>
<?php break;
case 'werben':
$qual_labels = [
'praktikum' => 'Praktikum',
'aushilfe' => 'Aushilfe',
'festanstellung' => 'Festanstellung',
]; ?>
<div class="detail-field">
<div class="detail-field-label">Name des Kandidaten</div>
<div class="detail-field-value"><?= e($req['name']) ?></div>
</div>
<div class="detail-field">
<div class="detail-field-label">E-Mail</div>
<div class="detail-field-value"><?= e($req['email']) ?></div>
</div>
<div class="detail-field">
<div class="detail-field-label">Qualifikation</div>
<div class="detail-field-value"><?= e($qual_labels[$req['qualifikation']] ?? $req['qualifikation']) ?></div>
</div>
<div class="detail-field">
<div class="detail-field-label">Nachricht</div>
<div class="detail-field-value"><?= $req['nachricht'] !== '' ? nl2br(e($req['nachricht'])) : '<span style="color:var(--muted)"></span>' ?></div>
</div>
<?php break;
case 'fortbildungsantrag':
$anliegen_labels = [
'wbl_pdl' => 'Anfrage WBL/PDL Weiterbildung',
'fortbildung' => 'Fortbildung anfragen',
'pflichtschulung' => 'Pflichtschulung',
]; ?>
<div class="detail-field">
<div class="detail-field-label">Anliegen</div>
<div class="detail-field-value"><?= e($anliegen_labels[$req['anliegen']] ?? $req['anliegen']) ?></div>
</div>
<div class="detail-field">
<div class="detail-field-label">Thema</div>
<div class="detail-field-value"><?= e($req['thema']) ?></div>
</div>
<div class="detail-field">
<div class="detail-field-label">Nachricht</div>
<div class="detail-field-value"><?= $req['nachricht'] !== '' ? nl2br(e($req['nachricht'])) : '<span style="color:var(--muted)"></span>' ?></div>
</div>
<?php if ($req['original_name'] !== ''): ?>
<div class="detail-field">
<div class="detail-field-label">Anhang</div>
<div class="detail-field-value">
<a href="upload-serve.php?file=<?= e(rawurlencode($req['filename'])) ?>&name=<?= e(rawurlencode($req['original_name'])) ?>"
style="color:var(--cyan2)"><?= e($req['original_name']) ?></a>
</div>
</div>
<?php endif; ?>
<?php break;
case 'stundennachweis': ?>
<div class="detail-field">
<div class="detail-field-label">Monat</div>
<div class="detail-field-value"><?= e(date('F Y', strtotime($req['monat'] . '-01'))) ?></div>
</div>
<div class="detail-field">
<div class="detail-field-label">Nachweis</div>
<div class="detail-field-value">
<a href="upload-serve.php?file=<?= e(rawurlencode($req['filename'])) ?>&name=<?= e(rawurlencode($req['original_name'])) ?>"
style="color:var(--cyan2)"><?= e($req['original_name']) ?></a>
</div>
</div>
<?php break;
endswitch; ?>
<div class="detail-field" style="margin-top:8px">
<div class="detail-field-label">Status</div>
<div><span class="badge badge-<?= e($req['status']) ?>"><?= e($status_labels[$req['status']] ?? $req['status']) ?></span></div>
</div>
<?php if ($req['admin_note'] !== ''): ?>
<div class="detail-field">
<div class="detail-field-label">Admin-Notiz</div>
<div class="detail-field-value"><?= nl2br(e($req['admin_note'])) ?></div>
</div>
<?php endif; ?>
</div>
<div class="glass detail-card" style="margin-top:16px;max-width:560px">
<h3 style="margin:0 0 16px;font-family:'KindelSerif',Georgia,serif">Entscheidung</h3>
<form action="../actions/admin-action.php" method="post" style="gap:12px">
<?= csrf_field() ?>
<input type="hidden" name="request_id" value="<?= (int)$req['id'] ?>">
<input type="hidden" name="type" value="<?= e($req['type']) ?>">
<label>
Admin-Notiz <span style="color:var(--muted);font-weight:400">(optional)</span>
<textarea name="admin_note" rows="3"><?= e($req['admin_note']) ?></textarea>
</label>
<div style="display:flex;gap:10px;margin-top:4px">
<button type="submit" name="action" value="accepted" class="btn">Akzeptieren</button>
<button type="submit" name="action" value="rejected" class="btn btn-danger">Ablehnen</button>
</div>
</form>
</div>
<form method="post" action="../actions/admin-action.php" style="margin-top:12px;max-width:560px"
onsubmit="return confirm('Antrag wirklich löschen? Dies kann nicht rückgängig gemacht werden.')">
<?= csrf_field() ?>
<input type="hidden" name="request_id" value="<?= (int)$req['id'] ?>">
<input type="hidden" name="type" value="<?= e($req['type']) ?>">
<input type="hidden" name="action" value="delete">
<button type="submit" class="btn-sm btn-sm-danger" style="width:100%;justify-content:center;padding:11px">
&#x1F5D1; Antrag löschen
</button>
</form>
<?php else: ?>
<!-- ── Listen-Ansicht ──────────────────────────────────── -->
<?php if (isset($_GET['error'])): ?>
<div class="error" style="margin-bottom:20px"><?= e(urldecode($_GET['error'])) ?></div>
<?php endif; ?>
<?php if (isset($_GET['deleted'])): ?>
<div class="success" style="margin-bottom:20px">Antrag wurde gelöscht.</div>
<?php endif; ?>
<div class="status-tabs">
<?php foreach (['all' => 'Alle', 'pending' => 'Ausstehend', 'accepted' => 'Akzeptiert', 'rejected' => 'Abgelehnt'] as $k => $l): ?>
<a href="admin.php<?= $k !== 'all' ? '?status=' . $k : '' ?>"
class="status-tab <?= $filter === $k ? 'active' : '' ?>"><?= $l ?></a>
<?php endforeach; ?>
</div>
<?php if (empty($requests)): ?>
<p style="color:var(--muted)">Keine Anträge gefunden.</p>
<?php else: ?>
<table class="requests-table">
<thead>
<tr>
<th>Mitarbeiter</th>
<th>Typ</th>
<th>Datum</th>
<th>Status</th>
<th></th>
</tr>
</thead>
<tbody>
<?php foreach ($requests as $row): ?>
<tr>
<td style="font-weight:800"><?= e($row['user_name']) ?></td>
<td><?= e($type_labels[$row['type']] ?? $row['type']) ?></td>
<td style="color:var(--muted);font-size:.9rem"><?= e(date('d.m.Y', strtotime($row['created_at']))) ?></td>
<td><span class="badge badge-<?= e($row['status']) ?>"><?= e($status_labels[$row['status']] ?? $row['status']) ?></span></td>
<td style="display:flex;gap:8px;align-items:center">
<a href="admin.php?type=<?= e($row['type']) ?>&id=<?= (int)$row['id'] ?><?= $filter !== 'all' ? '&status=' . e($filter) : '' ?>"
style="color:var(--cyan2);font-weight:800;font-size:.88rem">Details &rarr;</a>
<form method="post" action="../actions/admin-action.php"
onsubmit="return confirm('Antrag wirklich löschen? Dies kann nicht rückgängig gemacht werden.')">
<?= csrf_field() ?>
<input type="hidden" name="request_id" value="<?= (int)$row['id'] ?>">
<input type="hidden" name="type" value="<?= e($row['type']) ?>">
<input type="hidden" name="action" value="delete">
<button type="submit" class="btn-sm btn-sm-danger" title="Löschen">&#x1F5D1;</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<?php endif; ?>
<?php elseif ($tab === 'nutzer'): ?>
<!-- ═══════════════════════════════════════════════════════════ -->
<!-- TAB: NUTZER -->
<!-- ═══════════════════════════════════════════════════════════ -->
<?php if (isset($_GET['user_msg'])): ?>
<?php if (isset($_GET['user_ok'])): ?>
<div class="success" style="margin-bottom:20px"><?= e(urldecode($_GET['user_msg'])) ?></div>
<?php else: ?>
<div class="error" style="margin-bottom:20px"><?= e(urldecode($_GET['user_msg'])) ?></div>
<?php endif; ?>
<?php endif; ?>
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px">
<p style="color:var(--muted);margin:0"><?= count($users) ?> Nutzer</p>
<button class="btn" style="padding:10px 20px;font-size:.9rem"
onclick="document.getElementById('modal-create').style.display='flex'">
+ Neuen Nutzer
</button>
</div>
<table class="users-table">
<thead>
<tr>
<th>Name</th>
<th>Benutzername</th>
<th>Rolle</th>
<th>E-Mail</th>
<th>Aktiv</th>
<th>Erstellt</th>
<th></th>
</tr>
</thead>
<tbody>
<?php foreach ($users as $u): ?>
<tr>
<td style="font-weight:800"><?= e($u['name']) ?></td>
<td style="color:var(--muted);font-size:.9rem"><?= e($u['username']) ?></td>
<td>
<span class="badge" style="background:rgba(170,240,250,.1);border:1px solid rgba(170,240,250,.3);color:var(--cyan2)">
<?= $u['role'] === 'admin' ? 'Admin' : 'Nutzer' ?>
</span>
</td>
<td style="font-size:.88rem;color:var(--muted)"><?= e($u['email'] ?: '') ?></td>
<td>
<span class="badge <?= (int)$u['active'] ? 'badge-active' : 'badge-inactive' ?>">
<?= (int)$u['active'] ? 'Aktiv' : 'Inaktiv' ?>
</span>
</td>
<td style="color:var(--muted);font-size:.85rem"><?= e(date('d.m.Y', strtotime($u['created_at']))) ?></td>
<td style="white-space:nowrap">
<button class="btn-sm" onclick="openEditModal(<?= (int)$u['id'] ?>)">Bearbeiten</button>
<button class="btn-sm" style="margin-left:6px" onclick="openPwModal(<?= (int)$u['id'] ?>)">Passwort</button>
<form method="post" action="../actions/admin-action.php" style="display:inline;margin-left:6px"
onsubmit="return confirm('Nutzer <?= e(addslashes($u['name'])) ?> wirklich löschen?')">
<?= csrf_field() ?>
<input type="hidden" name="_user_action" value="delete_user">
<input type="hidden" name="user_id" value="<?= (int)$u['id'] ?>">
<button type="submit" class="btn-sm btn-sm-danger"
<?= $u['username'] === $me ? 'disabled title="Eigenen Account kann man nicht löschen"' : '' ?>>
Löschen
</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php foreach ($users as $u): ?>
<div id="modal-edit-<?= (int)$u['id'] ?>" class="modal-backdrop" style="display:none"
onclick="if(event.target===this)this.style.display='none'">
<div class="modal-box glass">
<button class="modal-close" onclick="document.getElementById('modal-edit-<?= (int)$u['id'] ?>').style.display='none'">&times;</button>
<h3>Nutzer bearbeiten</h3>
<form method="post" action="../actions/admin-action.php">
<?= csrf_field() ?>
<input type="hidden" name="_user_action" value="edit_user">
<input type="hidden" name="user_id" value="<?= (int)$u['id'] ?>">
<label>Name <input type="text" name="name" value="<?= e($u['name']) ?>" required></label>
<label>E-Mail <input type="email" name="email" value="<?= e($u['email']) ?>"></label>
<label>Telefon <input type="text" name="telefon" value="<?= e($u['telefon']) ?>"></label>
<label>Rolle
<select name="role">
<option value="user" <?= $u['role'] === 'user' ? 'selected' : '' ?>>Nutzer</option>
<option value="admin" <?= $u['role'] === 'admin' ? 'selected' : '' ?>>Admin</option>
</select>
</label>
<label>Status
<select name="active">
<option value="1" <?= (int)$u['active'] ? 'selected' : '' ?>>Aktiv</option>
<option value="0" <?= !(int)$u['active'] ? 'selected' : '' ?>>Inaktiv</option>
</select>
</label>
<button type="submit" class="btn" style="margin-top:8px">Speichern</button>
</form>
</div>
</div>
<?php endforeach; ?>
<?php foreach ($users as $u): ?>
<div id="modal-pw-<?= (int)$u['id'] ?>" class="modal-backdrop" style="display:none"
onclick="if(event.target===this)this.style.display='none'">
<div class="modal-box glass">
<button class="modal-close" onclick="document.getElementById('modal-pw-<?= (int)$u['id'] ?>').style.display='none'">&times;</button>
<h3>Passwort ändern</h3>
<p style="color:var(--muted);margin:0 0 20px;font-size:.92rem"><?= e($u['name']) ?> (<?= e($u['username']) ?>)</p>
<form method="post" action="../actions/admin-action.php">
<?= csrf_field() ?>
<input type="hidden" name="_user_action" value="change_password">
<input type="hidden" name="user_id" value="<?= (int)$u['id'] ?>">
<label>Neues Passwort
<input type="password" name="new_password" required minlength="12" placeholder="Mindestens 12 Zeichen">
</label>
<button type="submit" class="btn" style="margin-top:8px">Passwort setzen</button>
</form>
</div>
</div>
<?php endforeach; ?>
<div id="modal-create" class="modal-backdrop" style="display:none"
onclick="if(event.target===this)this.style.display='none'">
<div class="modal-box glass">
<button class="modal-close" onclick="document.getElementById('modal-create').style.display='none'">&times;</button>
<h3>Neuen Nutzer anlegen</h3>
<form method="post" action="../actions/admin-action.php">
<?= csrf_field() ?>
<input type="hidden" name="_user_action" value="add_user">
<label>Benutzername <input type="text" name="username" required placeholder="z.B. max.mustermann"></label>
<label>Name <input type="text" name="name" required placeholder="Max Mustermann"></label>
<label>E-Mail <input type="email" name="email" placeholder="max@example.com"></label>
<label>Telefon <input type="text" name="telefon" placeholder="+49 …"></label>
<label>Rolle
<select name="role">
<option value="user">Nutzer</option>
<option value="admin">Admin</option>
</select>
</label>
<label>Passwort
<input type="password" name="password" required minlength="12" placeholder="Mindestens 12 Zeichen">
</label>
<button type="submit" class="btn" style="margin-top:8px">Nutzer erstellen</button>
</form>
</div>
</div>
<?php elseif ($tab === 'dienstplan'): ?>
<!-- ═══════════════════════════════════════════════════════════ -->
<!-- TAB: DIENSTPLÄNE -->
<!-- ═══════════════════════════════════════════════════════════ -->
<?php
$dp_schicht_labels = ['frueh' => 'Früh', 'spaet' => 'Spät', 'nacht' => 'Nacht'];
$dp_weekdays = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'];
$dp_months_de = ['','Januar','Februar','März','April','Mai','Juni',
'Juli','August','September','Oktober','November','Dezember'];
$dp_today_str = (new DateTimeImmutable('today'))->format('Y-m-d');
?>
<div class="dp-user-select-wrap">
<form method="get">
<input type="hidden" name="tab" value="dienstplan">
<input type="hidden" name="year" value="<?= $dp_year ?>">
<input type="hidden" name="month" value="<?= $dp_month ?>">
<label class="form-label" for="dp_user_id">Mitarbeiter</label>
<select name="user_id" id="dp_user_id" onchange="this.form.submit()">
<?php foreach ($dp_users as $u): ?>
<option value="<?= (int)$u['id'] ?>" <?= (int)$u['id'] === $dp_selected_id ? 'selected' : '' ?>>
<?= e($u['name']) ?>
</option>
<?php endforeach; ?>
</select>
</form>
</div>
<?php if ($dp_selected): ?>
<div class="dp-nav">
<div style="display:flex;gap:10px;align-items:center">
<a href="<?= e($dp_prev_url) ?>" class="dp-nav-btn">&larr;</a>
<h2 class="dp-nav-title"><?= e($dp_months_de[$dp_month] . ' ' . $dp_year) ?></h2>
<a href="<?= e($dp_next_url) ?>" class="dp-nav-btn">&rarr;</a>
</div>
<span style="color:var(--muted);font-size:.9rem"><?= e($dp_selected['name']) ?></span>
</div>
<div class="dp-grid">
<?php foreach ($dp_weekdays as $wd): ?>
<div class="dp-weekday"><?= e($wd) ?></div>
<?php endforeach; ?>
<?php
$dp_lead = (int)$dp_first->format('N') - 1;
for ($i = 0; $i < $dp_lead; $i++): ?>
<div class="dp-cell dp-cell-empty"></div>
<?php endfor; ?>
<?php for ($d = 1; $d <= $dp_days; $d++):
$dk = sprintf('%04d-%02d-%02d', $dp_year, $dp_month, $d);
$is_today = ($dk === $dp_today_str);
$overlay = $dp_overlays[$dk] ?? null;
$schicht = $dp_schichten[$dk] ?? null;
$cell_cls = 'dp-cell';
if ($schicht) $cell_cls .= ' dp-cell-' . $schicht;
if ($is_today) $cell_cls .= ' dp-cell-today';
?>
<div class="<?= $cell_cls ?>">
<span class="dp-day-num"><?= $d ?></span>
<?php if ($overlay): ?>
<span class="dp-badge dp-overlay-<?= e($overlay) ?>">
<?= $overlay === 'urlaub' ? 'Urlaub' : 'Abwesenheit' ?>
</span>
<?php endif; ?>
<?php if ($schicht): ?>
<span class="dp-badge dp-badge-<?= e($schicht) ?>">
<?= e($dp_schicht_labels[$schicht]) ?>
</span>
<?php elseif (!$overlay): ?>
<span class="dp-free"></span>
<?php endif; ?>
</div>
<?php endfor; ?>
<?php
$dp_total = $dp_lead + $dp_days;
$dp_trail = (7 - ($dp_total % 7)) % 7;
for ($i = 0; $i < $dp_trail; $i++): ?>
<div class="dp-cell dp-cell-empty"></div>
<?php endfor; ?>
</div>
<?php else: ?>
<p style="color:var(--muted)">Keine aktiven Mitarbeiter vorhanden.</p>
<?php endif; ?>
<?php elseif ($tab === 'downloads'): ?>
<!-- ═══════════════════════════════════════════════════════════ -->
<!-- TAB: DOWNLOADS -->
<!-- ═══════════════════════════════════════════════════════════ -->
<?php if (isset($_GET['ok'])): ?>
<div class="success" style="margin-bottom:20px">
<?= $_GET['ok'] === 'added' ? 'Download erfolgreich hochgeladen.' : 'Download gelöscht.' ?>
</div>
<?php elseif (isset($_GET['err'])): ?>
<div class="error" style="margin-bottom:20px">
<?php $errs = [
'notitle' => 'Bitte einen Titel eingeben.',
'nofile' => 'Bitte eine Datei auswählen.',
'badext' => 'Dateityp nicht erlaubt.',
'badmime' => 'Dateityp nicht erlaubt.',
'toobig' => 'Datei ist zu groß (max. 20 MB).',
'upload' => 'Fehler beim Hochladen.',
'invalid' => 'Ungültige Anfrage.',
]; echo e($errs[$_GET['err']] ?? 'Ein Fehler ist aufgetreten.'); ?>
</div>
<?php endif; ?>
<div class="page-split">
<div class="page-split-list">
<h2>Vorhandene Downloads</h2>
<p style="color:var(--muted);font-size:.88rem;margin:0 0 14px">
Reihenfolge per Drag &amp; Drop ändern — wird automatisch gespeichert.
<span id="dl-reorder-status" style="margin-left:10px;font-weight:800"></span>
</p>
<?php if (empty($downloads)): ?>
<p style="color:var(--muted)">Noch keine Downloads vorhanden.</p>
<?php else: ?>
<ul id="dl-sort-list" style="list-style:none;margin:0;padding:0">
<?php foreach ($downloads as $dl): ?>
<li class="dl-sort-item" data-id="<?= (int)$dl['id'] ?>">
<span class="dl-drag-handle">&#8942;&#8942;</span>
<div class="dl-item-info">
<div class="dl-item-title"><?= e($dl['title']) ?></div>
<div class="dl-item-filename"><?= e($dl['original_name']) ?></div>
</div>
<form method="post" action="../actions/downloads-action.php"
onsubmit="return confirm('Download wirklich löschen?')"
style="margin:0;display:inline">
<?= csrf_field() ?>
<input type="hidden" name="_action" value="delete">
<input type="hidden" name="download_id" value="<?= (int)$dl['id'] ?>">
<button type="submit" class="btn-sm btn-sm-danger">Löschen</button>
</form>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</div>
<div class="page-split-form glass" style="padding:28px">
<h2>Neuer Download</h2>
<form method="post" action="../actions/downloads-action.php"
enctype="multipart/form-data">
<?= csrf_field() ?>
<input type="hidden" name="_action" value="add">
<label>Titel <input type="text" name="title" required placeholder="z. B. Stundennachweis"></label>
<label>Beschreibung <textarea name="description" rows="3" placeholder="Kurze Beschreibung (optional)"></textarea></label>
<label>Datei
<input type="file" name="file" required
accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.zip">
<span class="hint">PDF, Word, Excel, PowerPoint, TXT, ZIP &mdash; max. 20 MB</span>
</label>
<button type="submit" class="btn">&#8593; Hochladen</button>
</form>
</div>
</div>
<script>
(function () {
var list = document.getElementById('dl-sort-list');
if (!list) return;
var dragged = null;
list.querySelectorAll('[data-id]').forEach(function (el) {
el.setAttribute('draggable', 'true');
});
list.addEventListener('dragstart', function (e) {
dragged = e.target.closest('[data-id]');
if (!dragged) return;
setTimeout(function () { dragged.style.opacity = '0.45'; }, 0);
});
list.addEventListener('dragend', function () {
if (dragged) dragged.style.opacity = '';
list.querySelectorAll('.drag-over').forEach(function (el) {
el.classList.remove('drag-over');
});
dragged = null;
});
list.addEventListener('dragover', function (e) {
e.preventDefault();
var target = e.target.closest('[data-id]');
if (!target || target === dragged) return;
list.querySelectorAll('.drag-over').forEach(function (el) { el.classList.remove('drag-over'); });
var rect = target.getBoundingClientRect();
var after = e.clientY > rect.top + rect.height / 2;
list.insertBefore(dragged, after ? target.nextSibling : target);
target.classList.add('drag-over');
});
list.addEventListener('drop', function (e) {
e.preventDefault();
list.querySelectorAll('.drag-over').forEach(function (el) { el.classList.remove('drag-over'); });
var ids = Array.from(list.querySelectorAll('[data-id]')).map(function (el) {
return parseInt(el.getAttribute('data-id'), 10);
});
var status = document.getElementById('dl-reorder-status');
fetch('../actions/downloads-reorder.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' },
body: JSON.stringify({ order: ids })
})
.then(function (r) { return r.json(); })
.then(function (d) {
status.textContent = d.ok ? 'Reihenfolge gespeichert ✓' : 'Fehler beim Speichern';
status.style.color = d.ok ? '#6eff9a' : '#ff8080';
setTimeout(function () { status.textContent = ''; }, 2500);
})
.catch(function () {
status.textContent = 'Fehler beim Speichern';
status.style.color = '#ff8080';
});
});
}());
</script>
<?php elseif ($tab === 'fortbildung'): ?>
<!-- ═══════════════════════════════════════════════════════════ -->
<!-- TAB: FORTBILDUNG -->
<!-- ═══════════════════════════════════════════════════════════ -->
<?php if (isset($_GET['ok'])): ?>
<div class="success" style="margin-bottom:20px">
<?= $_GET['ok'] === 'added' ? 'Unterlage erfolgreich hochgeladen.' : 'Unterlage gelöscht.' ?>
</div>
<?php elseif (isset($_GET['err'])): ?>
<div class="error" style="margin-bottom:20px">
<?php $errs = [
'notitle' => 'Bitte einen Titel eingeben.',
'nofile' => 'Bitte eine Datei auswählen.',
'badext' => 'Dateityp nicht erlaubt.',
'badmime' => 'Dateityp nicht erlaubt.',
'toobig' => 'Datei ist zu groß (max. 20 MB).',
'upload' => 'Fehler beim Hochladen.',
'invalid' => 'Ungültige Anfrage.',
]; echo e($errs[$_GET['err']] ?? 'Ein Fehler ist aufgetreten.'); ?>
</div>
<?php endif; ?>
<div class="page-split">
<div class="page-split-list">
<h2>Vorhandene Unterlagen</h2>
<p style="color:var(--muted);font-size:.88rem;margin:0 0 14px">
Reihenfolge per Drag &amp; Drop ändern — wird automatisch gespeichert.
<span id="fb-reorder-status" style="margin-left:10px;font-weight:800"></span>
</p>
<?php if (empty($fb_materials)): ?>
<p style="color:var(--muted)">Noch keine Unterlagen vorhanden.</p>
<?php else: ?>
<ul id="fb-sort-list" style="list-style:none;margin:0;padding:0">
<?php foreach ($fb_materials as $m): ?>
<li class="dl-sort-item" data-id="<?= (int)$m['id'] ?>">
<span class="dl-drag-handle">&#8942;&#8942;</span>
<div class="dl-item-info">
<div class="dl-item-title"><?= e($m['title']) ?></div>
<div class="dl-item-filename"><?= e($m['original_name']) ?></div>
</div>
<form method="post" action="../actions/fortbildung-material-action.php"
onsubmit="return confirm('Unterlage wirklich löschen?')"
style="margin:0;display:inline">
<?= csrf_field() ?>
<input type="hidden" name="_action" value="delete">
<input type="hidden" name="material_id" value="<?= (int)$m['id'] ?>">
<button type="submit" class="btn-sm btn-sm-danger">Löschen</button>
</form>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</div>
<div class="page-split-form glass" style="padding:28px">
<h2>Neue Unterlage</h2>
<form method="post" action="../actions/fortbildung-material-action.php"
enctype="multipart/form-data">
<?= csrf_field() ?>
<input type="hidden" name="_action" value="add">
<label>Titel <input type="text" name="title" required placeholder="z. B. Anmeldeformular Fortbildung"></label>
<label>Beschreibung <textarea name="description" rows="3" placeholder="Kurze Beschreibung (optional)"></textarea></label>
<label>Datei
<input type="file" name="file" required
accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.zip">
<span class="hint">PDF, Word, Excel, PowerPoint, TXT, ZIP &mdash; max. 20 MB</span>
</label>
<button type="submit" class="btn">&#8593; Hochladen</button>
</form>
</div>
</div>
<script>
(function () {
var list = document.getElementById('fb-sort-list');
if (!list) return;
var dragged = null;
list.querySelectorAll('[data-id]').forEach(function (el) {
el.setAttribute('draggable', 'true');
});
list.addEventListener('dragstart', function (e) {
dragged = e.target.closest('[data-id]');
if (!dragged) return;
setTimeout(function () { dragged.style.opacity = '0.45'; }, 0);
});
list.addEventListener('dragend', function () {
if (dragged) dragged.style.opacity = '';
list.querySelectorAll('.drag-over').forEach(function (el) {
el.classList.remove('drag-over');
});
dragged = null;
});
list.addEventListener('dragover', function (e) {
e.preventDefault();
var target = e.target.closest('[data-id]');
if (!target || target === dragged) return;
list.querySelectorAll('.drag-over').forEach(function (el) { el.classList.remove('drag-over'); });
var rect = target.getBoundingClientRect();
var after = e.clientY > rect.top + rect.height / 2;
list.insertBefore(dragged, after ? target.nextSibling : target);
target.classList.add('drag-over');
});
list.addEventListener('drop', function (e) {
e.preventDefault();
list.querySelectorAll('.drag-over').forEach(function (el) { el.classList.remove('drag-over'); });
var ids = Array.from(list.querySelectorAll('[data-id]')).map(function (el) {
return parseInt(el.getAttribute('data-id'), 10);
});
var status = document.getElementById('fb-reorder-status');
fetch('../actions/fortbildung-material-reorder.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' },
body: JSON.stringify({ order: ids })
})
.then(function (r) { return r.json(); })
.then(function (d) {
status.textContent = d.ok ? 'Reihenfolge gespeichert ✓' : 'Fehler beim Speichern';
status.style.color = d.ok ? '#6eff9a' : '#ff8080';
setTimeout(function () { status.textContent = ''; }, 2500);
})
.catch(function () {
status.textContent = 'Fehler beim Speichern';
status.style.color = '#ff8080';
});
});
}());
</script>
<?php elseif ($tab === 'stundennachweis'): ?>
<!-- ═══════════════════════════════════════════════════════════ -->
<!-- TAB: STUNDENNACHWEISE -->
<!-- ═══════════════════════════════════════════════════════════ -->
<h2 style="font-family:'KindelSerif',Georgia,serif;margin:0 0 20px">Alle Stundennachweise</h2>
<?php if (empty($alle_stundennachweise)): ?>
<p style="color:var(--muted)">Noch keine Stundennachweise eingereicht.</p>
<?php else: ?>
<table class="requests-table">
<thead>
<tr>
<th>Mitarbeiter</th>
<th>Monat</th>
<th>Datei</th>
<th>Eingereicht</th>
<th>Status</th>
<th></th>
</tr>
</thead>
<tbody>
<?php foreach ($alle_stundennachweise as $s): ?>
<tr>
<td style="font-weight:800"><?= e($s['user_name']) ?></td>
<td><?= e(date('F Y', strtotime($s['monat'] . '-01'))) ?></td>
<td>
<a href="upload-serve.php?file=<?= e(rawurlencode($s['filename'])) ?>&name=<?= e(rawurlencode($s['original_name'])) ?>"
style="color:var(--cyan2)"><?= e($s['original_name']) ?></a>
</td>
<td style="color:var(--muted);font-size:.88rem;white-space:nowrap">
<?= e(date('d.m.Y', strtotime($s['created_at']))) ?>
</td>
<td><span class="badge badge-<?= e($s['status']) ?>"><?= e($status_labels[$s['status']] ?? $s['status']) ?></span></td>
<td>
<a href="admin.php?tab=antraege&type=stundennachweis&id=<?= (int)$s['id'] ?>"
style="color:var(--cyan2);font-weight:800;font-size:.88rem">Details &rarr;</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<?php elseif ($tab === 'bewertungen'): ?>
<!-- ═══════════════════════════════════════════════════════════ -->
<!-- TAB: EINSATZBEWERTUNGEN -->
<!-- ═══════════════════════════════════════════════════════════ -->
<style>
.star-display-sm{color:#f5c518;letter-spacing:1px;font-size:1rem}
.star-display-sm .empty{color:rgba(170,240,250,.2)}
</style>
<h2 style="font-family:'KindelSerif',Georgia,serif;margin:0 0 20px">Alle Einsatzbewertungen</h2>
<?php if (empty($alle_bewertungen)): ?>
<p style="color:var(--muted)">Noch keine Bewertungen vorhanden.</p>
<?php else: ?>
<table class="requests-table">
<thead>
<tr>
<th>Mitarbeiter</th>
<th>Einsatzort</th>
<th>Zeitraum</th>
<th>Bewertung</th>
<th>Wieder?</th>
<th>Feedback</th>
<th>Datum</th>
</tr>
</thead>
<tbody>
<?php foreach ($alle_bewertungen as $b): ?>
<tr>
<td style="font-weight:800"><?= e($b['user_name']) ?></td>
<td><?= e($b['einsatzort']) ?></td>
<td style="color:var(--muted);font-size:.88rem;white-space:nowrap">
<?= e(date('d.m.Y', strtotime($b['von']))) ?> <br>
<?= e(date('d.m.Y', strtotime($b['bis']))) ?>
</td>
<td>
<span class="star-display-sm">
<?php for ($i = 1; $i <= 5; $i++): ?>
<span<?= $i > $b['bewertung'] ? ' class="empty"' : '' ?>>★</span>
<?php endfor; ?>
</span>
</td>
<td><?= (int)$b['wieder'] ? '<span style="color:#6eff9a;font-weight:800">Ja</span>' : '<span style="color:var(--muted)">Nein</span>' ?></td>
<td style="max-width:240px;font-size:.88rem;color:var(--muted)">
<?= $b['feedback'] !== '' ? nl2br(e($b['feedback'])) : '' ?>
</td>
<td style="color:var(--muted);font-size:.85rem;white-space:nowrap">
<?= e(date('d.m.Y', strtotime($b['created_at']))) ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<?php elseif ($tab === 'news'): ?>
<!-- ═══════════════════════════════════════════════════════════ -->
<!-- TAB: NEWS -->
<!-- ═══════════════════════════════════════════════════════════ -->
<?php if (isset($_GET['ok'])): ?>
<div class="success" style="margin-bottom:20px">
<?= $_GET['ok'] === 'added' ? 'News veröffentlicht.' : 'News gelöscht.' ?>
</div>
<?php elseif (isset($_GET['err'])): ?>
<div class="error" style="margin-bottom:20px">Bitte Titel und Text ausfüllen.</div>
<?php endif; ?>
<div class="page-split">
<div class="page-split-list">
<h2>Veröffentlichte News</h2>
<?php if (empty($news_items)): ?>
<p style="color:var(--muted)">Noch keine News vorhanden.</p>
<?php else: ?>
<?php foreach ($news_items as $n): ?>
<div class="glass" style="padding:20px;border-radius:18px;margin-bottom:14px">
<div style="display:flex;justify-content:space-between;align-items:flex-start;gap:12px">
<div>
<div style="color:var(--cyan2);font-size:.82rem;font-weight:900;margin-bottom:4px">
<?= e(date('d.m.Y', strtotime($n['date']))) ?>
</div>
<strong style="font-size:1.05rem"><?= e($n['title']) ?></strong>
<p style="margin:8px 0 0;color:var(--muted);line-height:1.55;font-size:.93rem">
<?= nl2br(e($n['text'])) ?>
</p>
</div>
<form method="post" action="../actions/news-action.php"
onsubmit="return confirm('News wirklich löschen?')"
style="margin:0;flex-shrink:0">
<?= csrf_field() ?>
<input type="hidden" name="_action" value="delete">
<input type="hidden" name="news_id" value="<?= (int)$n['id'] ?>">
<button type="submit" class="btn-sm btn-sm-danger">Löschen</button>
</form>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
<div class="page-split-form glass" style="padding:28px">
<h2>News anlegen</h2>
<form method="post" action="../actions/news-action.php">
<?= csrf_field() ?>
<input type="hidden" name="_action" value="add">
<label>Titel <input type="text" name="title" required placeholder="Betreff der Ankündigung"></label>
<label>Datum <input type="date" name="date" value="<?= date('Y-m-d') ?>"></label>
<label>Text <textarea name="text" required placeholder="Inhalt der Ankündigung…" rows="5"></textarea></label>
<button type="submit" class="btn">Veröffentlichen</button>
</form>
</div>
</div>
<?php elseif ($tab === 'einsatzanweisung'): ?>
<!-- ═══════════════════════════════════════════════════════════ -->
<!-- TAB: EINSATZANWEISUNGEN -->
<!-- ═══════════════════════════════════════════════════════════ -->
<?php if (isset($_GET['ok'])): ?>
<div class="success" style="margin-bottom:20px"><?= e(urldecode($_GET['ok'])) ?></div>
<?php elseif (isset($_GET['err'])): ?>
<div class="error" style="margin-bottom:20px"><?= e(urldecode($_GET['err'])) ?></div>
<?php endif; ?>
<h2 style="font-family:'KindelSerif',Georgia,serif;margin:0 0 20px">Einsatzanweisungen</h2>
<p style="color:var(--muted);font-size:.9rem;margin:0 0 24px">
Pro Mitarbeiter kann eine PDF hinterlegt und ein Einsatzort gesetzt werden.
Der Einsatzort wird im Dienstplan des Mitarbeiters angezeigt.
</p>
<?php if (empty($ea_users)): ?>
<p style="color:var(--muted)">Keine aktiven Mitarbeiter vorhanden.</p>
<?php else: ?>
<table class="users-table">
<thead>
<tr>
<th>Mitarbeiter</th>
<th>Einsatzort</th>
<th>PDF</th>
<th>PDF hochladen</th>
</tr>
</thead>
<tbody>
<?php foreach ($ea_users as $u): ?>
<tr>
<td style="font-weight:800">
<?= e($u['name']) ?>
<br><span style="color:var(--muted);font-size:.82rem;font-weight:400"><?= e($u['username']) ?></span>
</td>
<td>
<form method="post" action="../actions/einsatzanweisung-action.php"
style="display:flex;gap:8px;align-items:center;margin:0">
<?= csrf_field() ?>
<input type="hidden" name="_action" value="update_ort">
<input type="hidden" name="target_user_id" value="<?= (int)$u['id'] ?>">
<input type="text" name="ort" value="<?= e($u['ort']) ?>"
placeholder="Einsatzort eingeben…"
style="width:180px;padding:8px 12px;font-size:.88rem">
<button type="submit" class="btn-sm">Speichern</button>
</form>
</td>
<td>
<?php if ($u['filename'] !== ''): ?>
<a href="einsatzanweisung-serve.php?user_id=<?= (int)$u['id'] ?>"
style="color:var(--cyan2);font-weight:800;font-size:.88rem">
&#8595; <?= e($u['original_name']) ?>
</a>
<?php if ($u['uploaded_at'] !== ''): ?>
<br><span style="color:var(--muted);font-size:.78rem">
<?= e(date('d.m.Y', strtotime($u['uploaded_at']))) ?>
</span>
<?php endif; ?>
<?php else: ?>
<span style="color:var(--muted)"></span>
<?php endif; ?>
</td>
<td>
<form method="post" action="../actions/einsatzanweisung-action.php"
enctype="multipart/form-data"
style="display:flex;gap:8px;align-items:center;margin:0">
<?= csrf_field() ?>
<input type="hidden" name="_action" value="upload_pdf">
<input type="hidden" name="target_user_id" value="<?= (int)$u['id'] ?>">
<input type="file" name="pdf" accept=".pdf" required
style="font-size:.82rem;max-width:220px">
<button type="submit" class="btn-sm">&#8593; Hochladen</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<?php elseif ($tab === 'login'): ?>
<!-- ═══════════════════════════════════════════════════════════ -->
<!-- TAB: LOGIN-VERSUCHE -->
<!-- ═══════════════════════════════════════════════════════════ -->
<?php if (isset($_GET['cleared'])): ?>
<div class="success" style="margin-bottom:20px">Login-Versuche wurden gelöscht.</div>
<?php endif; ?>
<div style="display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;margin-bottom:8px">
<div>
<h2 style="font-family:'KindelSerif',Georgia,serif;margin:0 0 4px">Fehlgeschlagene Login-Versuche</h2>
<p style="color:var(--muted);margin:0;font-size:.9rem">
<?= (int)$login_total ?> protokollierte Versuche<?= $login_total > 200 ? ' (jüngste 200 angezeigt)' : '' ?>.
Eine IP wird nach 5 Fehlversuchen für 10 Minuten gesperrt.
</p>
</div>
<?php if ($login_total > 0): ?>
<form method="post" action="../actions/admin-action.php"
onsubmit="return confirm('Wirklich alle Login-Versuche löschen? Dadurch werden auch aktive Sperren aufgehoben.')"
style="margin:0;flex-shrink:0">
<?= csrf_field() ?>
<input type="hidden" name="_login_action" value="clear">
<button type="submit" class="btn-sm btn-sm-danger">&#x1F5D1; Tabelle leeren</button>
</form>
<?php endif; ?>
</div>
<?php if (!empty($login_locked)): ?>
<div class="glass" style="padding:16px 20px;border-radius:16px;margin:16px 0;border:1px solid rgba(255,100,100,.4)">
<strong style="color:#ff8080">Aktuell gesperrt (letzte 10 Min.):</strong>
<span style="color:var(--muted);font-size:.9rem">
<?php foreach ($login_locked as $i => $l): ?>
<?= $i ? ' · ' : ' ' ?><code><?= e($l['ip'] ?: '') ?></code> (<?= (int)$l['cnt'] ?>×)
<?php endforeach; ?>
</span>
</div>
<?php endif; ?>
<?php if (empty($login_attempts)): ?>
<p style="color:var(--muted)">Keine Login-Versuche protokolliert.</p>
<?php else: ?>
<?php $locked_ips = array_column($login_locked, 'ip'); ?>
<table class="users-table">
<thead>
<tr>
<th>Zeitpunkt</th>
<th>Benutzername</th>
<th>IP-Adresse</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<?php foreach ($login_attempts as $a): ?>
<tr>
<td style="white-space:nowrap"><?= e(date('d.m.Y H:i:s', strtotime($a['attempted_at']))) ?></td>
<td style="font-weight:800"><?= $a['username'] !== '' ? e($a['username']) : '<span style="color:var(--muted)"></span>' ?></td>
<td style="color:var(--muted);font-size:.9rem"><code><?= e($a['ip'] ?: '') ?></code></td>
<td>
<?php if (in_array($a['ip'], $locked_ips, true)): ?>
<span class="badge badge-inactive">Gesperrt</span>
<?php else: ?>
<span style="color:var(--muted)"></span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<?php endif; ?>
<script>
function openEditModal(id) { document.getElementById('modal-edit-' + id).style.display = 'flex'; }
function openPwModal(id) { document.getElementById('modal-pw-' + id).style.display = 'flex'; }
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
document.querySelectorAll('.modal-backdrop').forEach(function(m) { m.style.display = 'none'; });
}
});
</script>
<?php layout_end(); ?>