Consolidates the previously separate omsorgapp and omsorgCore repos (each had their own nested .git with GitHub history) plus the old root-level website/mitarbeiter-app into a single monorepo, matching the structure already documented in the root CLAUDE.md. Also moves the PHP employee app aside as omsorgWeb/mitarbeiter-app-legacy/ to serve as a template for a ground-up rewrite. Fixes .gitignore in the same pass: the config-secrets/uploads/data patterns were unanchored (relative to repo root, not depth-agnostic), so they silently stopped matching once the app moved under omsorgWeb/. Patterns are now **/-prefixed and cover both mitarbeiter-app and mitarbeiter-app-legacy, keeping DB/SMTP credentials and uploaded employee documents out of version control. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
163 lines
5.4 KiB
PHP
163 lines
5.4 KiB
PHP
<?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">←</a>
|
||
<h2 class="dp-nav-title"><?= e($months_de[$month] . ' ' . $year) ?></h2>
|
||
<a href="<?= e($next_url) ?>" class="dp-nav-btn">→</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();
|