Initial commit: OMSORG website + Mitarbeiter-App
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
<?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();
|
||||
Reference in New Issue
Block a user