Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1399 lines
68 KiB
PHP
1399 lines
68 KiB
PHP
<?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">← 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> ·
|
||
<?= 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">
|
||
🗑 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 →</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">🗑</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'">×</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'">×</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'">×</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">←</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">→</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 & 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">⋮⋮</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 — max. 20 MB</span>
|
||
</label>
|
||
<button type="submit" class="btn">↑ 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 & 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">⋮⋮</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 — max. 20 MB</span>
|
||
</label>
|
||
<button type="submit" class="btn">↑ 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 →</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">
|
||
↓ <?= 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">↑ 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">🗑 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(); ?>
|