Initial commit: OMSORG website + Mitarbeiter-App

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Felix Kemmler
2026-06-13 20:03:22 +02:00
co-authored by Claude Sonnet 4.6
commit 8beb0fcf52
103 changed files with 7384 additions and 0 deletions
+162
View File
@@ -0,0 +1,162 @@
<?php
require_once __DIR__ . '/../lib/auth.php';
require_once __DIR__ . '/../lib/db.php';
require_once __DIR__ . '/../lib/layout.php';
require_admin();
// Fetch all active users
$users = db()->query(
'SELECT id, name FROM users WHERE active = 1 ORDER BY name ASC'
)->fetchAll();
$now = new DateTimeImmutable('today');
$year = isset($_GET['year']) ? (int)$_GET['year'] : (int)$now->format('Y');
$month = isset($_GET['month']) ? (int)$_GET['month'] : (int)$now->format('n');
$year = max(2020, min(2099, $year));
$month = max(1, min(12, $month));
// Resolve selected user
$selected_id = isset($_GET['user_id']) ? (int)$_GET['user_id'] : 0;
$selected_user = null;
foreach ($users as $u) {
if ((int)$u['id'] === $selected_id) { $selected_user = $u; break; }
}
if (!$selected_user && !empty($users)) {
$selected_user = $users[0];
$selected_id = (int)$selected_user['id'];
}
$first = new DateTimeImmutable(sprintf('%04d-%02d-01', $year, $month));
$month_start = $first->format('Y-m-d');
$month_end = $first->modify('last day of this month')->format('Y-m-d');
$days = (int)$first->format('t');
$prev = $first->modify('-1 month');
$next = $first->modify('+1 month');
$prev_url = '?user_id=' . $selected_id . '&year=' . $prev->format('Y') . '&month=' . $prev->format('n');
$next_url = '?user_id=' . $selected_id . '&year=' . $next->format('Y') . '&month=' . $next->format('n');
// Load shifts
$schichten = [];
if ($selected_id) {
$stmt = db()->prepare(
'SELECT date, schicht FROM dienstplan WHERE user_id = ? AND date >= ? AND date <= ?'
);
$stmt->execute([$selected_id, $month_start, $month_end]);
foreach ($stmt->fetchAll() as $row) {
$schichten[$row['date']] = $row['schicht'];
}
}
// Load overlays
$overlays = [];
if ($selected_id) {
$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([$selected_id, $month_start, $month_end, $selected_id, $month_start, $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 >= $month_start && $key <= $month_end) {
$overlays[$key] = $row['typ'];
}
$cursor = $cursor->modify('+1 day');
}
}
}
$today_str = $now->format('Y-m-d');
$schicht_labels = ['frueh' => 'Früh', 'spaet' => 'Spät', 'nacht' => 'Nacht'];
$weekdays = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'];
$months_de = ['', 'Januar', 'Februar', 'März', 'April', 'Mai', 'Juni',
'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'];
layout_start('Dienstpläne', 'admin-dienstplan');
?>
<h1 class="main-title">Dienstpläne</h1>
<div class="dp-user-select-wrap">
<form method="get">
<input type="hidden" name="year" value="<?= $year ?>">
<input type="hidden" name="month" value="<?= $month ?>">
<label class="form-label" for="user_id">Mitarbeiter</label>
<select name="user_id" id="user_id" onchange="this.form.submit()">
<?php foreach ($users as $u): ?>
<option value="<?= (int)$u['id'] ?>" <?= (int)$u['id'] === $selected_id ? 'selected' : '' ?>>
<?= e($u['name']) ?>
</option>
<?php endforeach; ?>
</select>
</form>
</div>
<?php if ($selected_user): ?>
<div class="dp-nav">
<div style="display:flex;gap:10px;align-items:center">
<a href="<?= e($prev_url) ?>" class="dp-nav-btn">&larr;</a>
<h2 class="dp-nav-title"><?= e($months_de[$month] . ' ' . $year) ?></h2>
<a href="<?= e($next_url) ?>" class="dp-nav-btn">&rarr;</a>
</div>
<span style="color:var(--muted);font-size:.9rem"><?= e($selected_user['name']) ?></span>
</div>
<div class="dp-grid">
<?php foreach ($weekdays as $wd): ?>
<div class="dp-weekday"><?= e($wd) ?></div>
<?php endforeach; ?>
<?php
$lead = (int)$first->format('N') - 1;
for ($i = 0; $i < $lead; $i++): ?>
<div class="dp-cell dp-cell-empty"></div>
<?php endfor; ?>
<?php for ($d = 1; $d <= $days; $d++):
$date_key = sprintf('%04d-%02d-%02d', $year, $month, $d);
$is_today = ($date_key === $today_str);
$overlay = $overlays[$date_key] ?? null;
$schicht = $schichten[$date_key] ?? null;
$cell_cls = 'dp-cell' . ($is_today ? ' 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($schicht_labels[$schicht]) ?>
</span>
<?php elseif (!$overlay): ?>
<span class="dp-free"></span>
<?php endif; ?>
</div>
<?php endfor; ?>
<?php
$total = $lead + $days;
$trail = (7 - ($total % 7)) % 7;
for ($i = 0; $i < $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
layout_end();