Files
Felix KemmlerandClaude Sonnet 5 598dfcd38a
Docker-Images bauen und veröffentlichen / build (, omsorgCore/Dockerfile, omsorgcore) (push) Successful in 3s
Docker-Images bauen und veröffentlichen / build (, omsorgWeb/Dockerfile, omsorgweb) (push) Failing after 4s
Docker-Images bauen und veröffentlichen / build (VITE_OMSORG_CORE_URL=${{ vars.OMSORG_CORE_PUBLIC_URL }}, omsorgapp/Dockerfile, omsorgapp) (push) Successful in 3s
Migrate omsorgapp to browser SPA, add Docker/CI build setup
- omsorgapp: drop Electron, run as a plain Vite/React browser app; refresh
  token moves to an HttpOnly cookie (omsorgCore), CORS added for the new
  browser origin, document download/preview switched to Blob-based browser
  APIs.
- Add Dockerfiles for omsorgCore, omsorgapp, and omsorgWeb, a docker-compose.yml
  wiring Postgres/MySQL/all three apps together, and a Gitea Actions workflow
  that builds and pushes images to the repo's container registry on push to
  main and on version tags.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 17:42:45 +02:00

236 lines
8.9 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
require_once __DIR__ . '/../lib/auth.php';
require_once __DIR__ . '/../lib/layout.php';
require_login();
if (!has_permission('Absences', 'View')) {
header('Location: dashboard.php');
exit;
}
$canCreate = has_permission('Absences', 'Create');
$canEdit = has_permission('Absences', 'Edit');
$error = '';
$success = false;
$editId = null;
$typesResult = omsorgcore_value_list_items(_omsorgcore_config(), $_SESSION['omsorgcore_access_token'], 'AbsenceType');
$types = $typesResult['ok'] ? $typesResult['data'] : [];
// Nicht auf den Anzeigetext "Eingereicht" hartkodieren - der ist über die Status-Verwaltung in
// omsorgapp umbenennbar (siehe omsorgCore/CLAUDE.md, AbsenceService.GetInitialStatusValueAsync).
$statusesResult = omsorgcore_value_list_items(_omsorgcore_config(), $_SESSION['omsorgcore_access_token'], 'AbsenceStatus');
$statuses = $statusesResult['ok'] ? $statusesResult['data'] : [];
$initialStatus = null;
foreach ($statuses as $statusItem) {
if (!empty($statusItem['isInitial'])) {
$initialStatus = $statusItem['value'];
break;
}
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
verify_csrf();
$mode = $_POST['mode'] ?? 'create';
$payload = [
'type' => $_POST['type'] ?? '',
'startDate' => $_POST['start_date'] ?? '',
'endDate' => $_POST['end_date'] ?? '',
'reason' => trim($_POST['reason'] ?? ''),
'substitute' => trim($_POST['substitute'] ?? ''),
'note' => trim($_POST['note'] ?? ''),
];
if ($payload['type'] === '' || $payload['startDate'] === '' || $payload['endDate'] === '') {
$error = 'Art, Beginn und Ende sind erforderlich.';
$editId = $mode === 'edit' ? ($_POST['absence_id'] ?? null) : null;
} elseif ($payload['endDate'] < $payload['startDate']) {
$error = 'Das Ende darf nicht vor dem Beginn liegen.';
$editId = $mode === 'edit' ? ($_POST['absence_id'] ?? null) : null;
} elseif ($mode === 'edit') {
if (!$canEdit) {
// Server lehnt das ohnehin über [RequirePermission(Absences, Edit)] ab - dieser Zweig
// greift nur, wenn jemand das Formular manuell postet, obwohl es clientseitig gar nicht
// angezeigt wurde.
$error = 'Keine Berechtigung, diesen Antrag zu bearbeiten.';
} else {
$absenceId = $_POST['absence_id'] ?? '';
$result = omsorgcore_absences_update(_omsorgcore_config(), $_SESSION['omsorgcore_access_token'], $absenceId, $payload);
if ($result['ok']) {
header('Location: urlaubsantrag.php?updated=1');
exit;
}
$editId = $absenceId;
$error = $result['status'] === 403
? 'Keine Berechtigung, diesen Antrag zu bearbeiten.'
: (is_string($result['data'] ?? null) ? $result['data'] : 'Antrag konnte nicht gespeichert werden.');
}
} elseif (!$canCreate) {
$error = 'Keine Berechtigung, einen Antrag zu stellen.';
} else {
$result = omsorgcore_absences_create(_omsorgcore_config(), $_SESSION['omsorgcore_access_token'], $payload);
if ($result['ok']) {
$success = true;
} elseif ($result['status'] === 403) {
$error = 'Keine Berechtigung, einen Antrag zu stellen.';
} else {
$error = is_string($result['data'] ?? null) ? $result['data'] : 'Antrag konnte nicht gespeichert werden.';
}
}
} elseif (isset($_GET['edit'])) {
$editId = $_GET['edit'];
}
$listResult = omsorgcore_absences_list(_omsorgcore_config(), $_SESSION['omsorgcore_access_token']);
$absences = $listResult['ok'] ? ($listResult['data']['items'] ?? []) : [];
$editAbsence = null;
if ($editId !== null) {
foreach ($absences as $absence) {
if ($absence['id'] === $editId) {
$editAbsence = $absence;
break;
}
}
// Fremder/ungültiger/schon entschiedener Antrag - Own-Scope-Filterung passiert serverseitig
// in omsorgcore_absences_list, hier nur zusätzlich: nicht (mehr) bearbeitbar zurück zu "Neuer Antrag".
if ($editAbsence === null || $editAbsence['status'] !== $initialStatus) {
$editId = null;
$editAbsence = null;
}
}
layout_start('Urlaub & Abwesenheit Mitarbeiter-App', 'urlaubsantrag');
?>
<h1 class="main-title">Urlaub &amp; Abwesenheit</h1>
<div class="split-layout">
<div class="glass" style="padding:24px">
<h2 style="font-family:'KindelSerif',Georgia,serif;font-size:1.3rem;margin:0 0 18px">Meine Anträge</h2>
<?php if (empty($absences)): ?>
<p>Noch keine Anträge eingereicht.</p>
<?php else: ?>
<?php foreach ($absences as $absence): ?>
<div style="padding:12px 0;border-bottom:1px solid rgba(0,0,0,.08)">
<b><?= e($absence['type']) ?></b> — <?= e($absence['status']) ?>
<div class="hint">
<?= e(substr($absence['startDate'], 0, 10)) ?> <?= e(substr($absence['endDate'], 0, 10)) ?>
</div>
<?php if (!empty($absence['adminNote'])): ?>
<div class="hint">Kommentar: <?= e($absence['adminNote']) ?></div>
<?php endif; ?>
<?php if ($canEdit && $absence['status'] === $initialStatus): ?>
<div style="margin-top:6px">
<a href="urlaubsantrag.php?edit=<?= e($absence['id']) ?>">Bearbeiten</a>
</div>
<?php endif; ?>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
<div class="glass" style="padding:24px">
<h2 style="font-family:'KindelSerif',Georgia,serif;font-size:1.3rem;margin:0 0 18px">
<?= $editAbsence ? 'Antrag bearbeiten' : 'Neuer Antrag' ?>
</h2>
<?php if ($success): ?>
<div class="hint" style="margin-bottom:16px">Antrag wurde eingereicht.</div>
<?php endif; ?>
<?php if (isset($_GET['updated'])): ?>
<div class="hint" style="margin-bottom:16px">Antrag wurde aktualisiert.</div>
<?php endif; ?>
<?php if ($error): ?>
<div class="error" style="margin-bottom:16px"><?= e($error) ?></div>
<?php endif; ?>
<?php if ($editAbsence): ?>
<form method="post">
<?= csrf_field() ?>
<input type="hidden" name="mode" value="edit">
<input type="hidden" name="absence_id" value="<?= e($editAbsence['id']) ?>">
<label>
Art
<select name="type" required>
<option value="">— auswählen —</option>
<?php foreach ($types as $type): ?>
<option value="<?= e($type['value']) ?>" <?= $type['value'] === $editAbsence['type'] ? 'selected' : '' ?>><?= e($type['value']) ?></option>
<?php endforeach; ?>
</select>
</label>
<label>
Beginn
<input type="date" name="start_date" value="<?= e(substr($editAbsence['startDate'], 0, 10)) ?>" required>
</label>
<label>
Ende
<input type="date" name="end_date" value="<?= e(substr($editAbsence['endDate'], 0, 10)) ?>" required>
</label>
<label>
Grund (optional)
<input type="text" name="reason" maxlength="500" value="<?= e($editAbsence['reason'] ?? '') ?>">
</label>
<label>
Vertretung (optional)
<input type="text" name="substitute" maxlength="200" value="<?= e($editAbsence['substitute'] ?? '') ?>">
</label>
<label>
Nachricht (optional)
<textarea name="note" maxlength="500"><?= e($editAbsence['note'] ?? '') ?></textarea>
</label>
<button type="submit" class="btn" style="width:100%;margin-top:6px">Änderungen speichern</button>
</form>
<p class="hint" style="text-align:center;margin-top:14px">
<a href="urlaubsantrag.php">Abbrechen</a>
</p>
<?php elseif ($canCreate): ?>
<form method="post">
<?= csrf_field() ?>
<input type="hidden" name="mode" value="create">
<label>
Art
<select name="type" required>
<option value="">— auswählen —</option>
<?php foreach ($types as $type): ?>
<option value="<?= e($type['value']) ?>"><?= e($type['value']) ?></option>
<?php endforeach; ?>
</select>
</label>
<label>
Beginn
<input type="date" name="start_date" required>
</label>
<label>
Ende
<input type="date" name="end_date" required>
</label>
<label>
Grund (optional)
<input type="text" name="reason" maxlength="500">
</label>
<label>
Vertretung (optional)
<input type="text" name="substitute" maxlength="200">
</label>
<label>
Nachricht (optional)
<textarea name="note" maxlength="500"></textarea>
</label>
<button type="submit" class="btn" style="width:100%;margin-top:6px">Antrag einreichen</button>
</form>
<?php else: ?>
<p class="hint">Keine Berechtigung, einen Antrag zu stellen.</p>
<?php endif; ?>
</div>
</div>
<?php
layout_end();