Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
185 lines
6.4 KiB
PHP
185 lines
6.4 KiB
PHP
<?php
|
|
require_once __DIR__ . '/../lib/auth.php';
|
|
require_once __DIR__ . '/../lib/db.php';
|
|
require_once __DIR__ . '/../lib/layout.php';
|
|
require_login();
|
|
|
|
$user = current_user();
|
|
$user_id = $user['id'];
|
|
|
|
$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));
|
|
|
|
$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 = '?year=' . $prev->format('Y') . '&month=' . $prev->format('n');
|
|
$next_url = '?year=' . $next->format('Y') . '&month=' . $next->format('n');
|
|
|
|
$stmt = db()->prepare(
|
|
'SELECT date, schicht FROM dienstplan WHERE user_id = ? AND date >= ? AND date <= ?'
|
|
);
|
|
$stmt->execute([$user_id, $month_start, $month_end]);
|
|
$schichten = [];
|
|
foreach ($stmt->fetchAll() as $row) {
|
|
$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([$user_id, $month_start, $month_end, $user_id, $month_start, $month_end]);
|
|
$overlays = [];
|
|
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');
|
|
|
|
$ea_stmt = db()->prepare('SELECT ort FROM einsatzanweisung WHERE user_id = ?');
|
|
$ea_stmt->execute([$user_id]);
|
|
$ea_row = $ea_stmt->fetch();
|
|
$ea_ort = ($ea_row && $ea_row['ort'] !== '') ? $ea_row['ort'] : '';
|
|
|
|
$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('Dienstplan', 'dienstplan');
|
|
?>
|
|
|
|
<h1 class="main-title">Dienstplan</h1>
|
|
|
|
<?php if ($ea_ort !== ''): ?>
|
|
<p class="dp-ort">📍 Einsatzort: <strong><?= e($ea_ort) ?></strong></p>
|
|
<?php endif; ?>
|
|
|
|
<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 id="dp-status" style="font-size:.85rem;color:var(--muted);transition:opacity .4s"></span>
|
|
</div>
|
|
|
|
<div class="dp-grid-wrap">
|
|
<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] ?? 'leer';
|
|
$cell_cls = 'dp-cell';
|
|
if (!$overlay && $schicht !== 'leer') $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 if ($schicht !== 'leer'): ?>
|
|
<span class="dp-badge dp-badge-<?= e($schicht) ?>" style="opacity:.45">
|
|
<?= e($schicht_labels[$schicht]) ?>
|
|
</span>
|
|
<?php endif; ?>
|
|
<?php else: ?>
|
|
<select data-date="<?= e($date_key) ?>" class="dp-select">
|
|
<option value="leer" <?= $schicht === 'leer' ? 'selected' : '' ?>>Frei</option>
|
|
<option value="frueh" <?= $schicht === 'frueh' ? 'selected' : '' ?>>Früh</option>
|
|
<option value="spaet" <?= $schicht === 'spaet' ? 'selected' : '' ?>>Spät</option>
|
|
<option value="nacht" <?= $schicht === 'nacht' ? 'selected' : '' ?>>Nacht</option>
|
|
</select>
|
|
<?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>
|
|
</div>
|
|
|
|
<script>
|
|
(function(){
|
|
var YEAR = <?= (int)$year ?>;
|
|
var MONTH = <?= (int)$month ?>;
|
|
|
|
function colorCell(sel){
|
|
var cell = sel.closest('.dp-cell');
|
|
cell.classList.remove('dp-cell-frueh','dp-cell-spaet','dp-cell-nacht');
|
|
if(sel.value !== 'leer') cell.classList.add('dp-cell-' + sel.value);
|
|
}
|
|
|
|
var statusEl = document.getElementById('dp-status');
|
|
var statusTimer;
|
|
function showStatus(ok){
|
|
clearTimeout(statusTimer);
|
|
statusEl.style.opacity = '1';
|
|
statusEl.style.color = ok ? '#6eff9a' : '#ff9a9a';
|
|
statusEl.textContent = ok ? 'Gespeichert' : 'Fehler beim Speichern';
|
|
statusTimer = setTimeout(function(){ statusEl.style.opacity = '0'; }, 1800);
|
|
}
|
|
|
|
document.querySelectorAll('.dp-select').forEach(function(sel){
|
|
colorCell(sel);
|
|
sel.addEventListener('change', function(){
|
|
var date = this.dataset.date;
|
|
var schicht = this.value;
|
|
colorCell(this);
|
|
|
|
var body = new URLSearchParams();
|
|
body.append('year', YEAR);
|
|
body.append('month', MONTH);
|
|
body.append('schicht[' + date + ']', schicht);
|
|
body.append('_ajax', '1');
|
|
body.append('csrf_token', document.querySelector('meta[name="csrf-token"]').content);
|
|
|
|
fetch('../actions/save-dienstplan.php', {method:'POST', body:body})
|
|
.then(function(r){ return r.json(); })
|
|
.then(function(d){ showStatus(d.ok); })
|
|
.catch(function(){ showStatus(false); });
|
|
});
|
|
});
|
|
})();
|
|
</script>
|
|
<?php
|
|
layout_end();
|