Migrate omsorgapp to browser SPA, add Docker/CI build setup
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
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
- 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>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
e9e96a57dc
commit
598dfcd38a
@@ -0,0 +1,235 @@
|
||||
<?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 & 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();
|
||||
Reference in New Issue
Block a user