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

- 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:
Felix Kemmler
2026-08-10 17:42:45 +02:00
co-authored by Claude Sonnet 5
parent e9e96a57dc
commit 598dfcd38a
461 changed files with 59753 additions and 2859 deletions
@@ -1,11 +1,19 @@
import { useState } from "react";
import { Pencil } from "lucide-react";
import { Pencil, Trash2 } from "lucide-react";
import OmsorgCard from "../../components/OmsorgCard";
import OmsorgButton from "../../components/ui/OmsorgButton";
import { useAuth } from "../../app/AuthContext";
import EditFacilityDialog from "./EditFacilityDialog";
import FacilityContactsList from "./FacilityContactsList";
import FacilityQualificationRatesList from "./FacilityQualificationRatesList";
function formatFollowUpDueDate(followUpDueDate) {
if (!followUpDueDate) {
return "—";
}
return new Date(followUpDueDate).toLocaleDateString("de-DE");
}
function formatAddress(street, postalCode, city, country) {
const line = [postalCode, city].filter(Boolean).join(" ");
@@ -21,7 +29,23 @@ function formatAddress(street, postalCode, city, country) {
export default function FacilityDetailPanel({ facility, onUpdated }) {
const { hasPermission } = useAuth();
const canEdit = hasPermission("Facilities", "Edit");
const canDelete = hasPermission("Facilities", "Delete");
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
async function handleDelete() {
if (!window.confirm(`${facility.name} wirklich löschen?`)) {
return;
}
setIsDeleting(true);
const result = await window.omsorg.facilities.delete(facility.id);
setIsDeleting(false);
if (result.ok) {
onUpdated?.(null);
}
}
if (!facility) {
return (
@@ -48,6 +72,12 @@ export default function FacilityDetailPanel({ facility, onUpdated }) {
Bearbeiten
</OmsorgButton>
)}
{canDelete && (
<OmsorgButton variant="secondary" icon={Trash2} onClick={handleDelete} disabled={isDeleting}>
Löschen
</OmsorgButton>
)}
</div>
<div className="employee-detail-grid">
@@ -85,8 +115,59 @@ export default function FacilityDetailPanel({ facility, onUpdated }) {
)}
</p>
</div>
{facility.followUpDueDate && (
<div>
<strong>Wiedervorlage fällig am</strong>
<p>{formatFollowUpDueDate(facility.followUpDueDate)}</p>
</div>
)}
<div>
<strong>Verrechnungssatz</strong>
<p>{facility.billingRate != null ? `${facility.billingRate} EUR/Std.` : "—"}</p>
</div>
<div>
<strong>Zuschläge</strong>
<p>
Nacht {facility.nightSurchargePercent ?? "—"}% · Sa {facility.saturdaySurchargePercent ?? "—"}% ·
So {facility.sundaySurchargePercent ?? "—"}% · Feiertag {facility.holidaySurchargePercent ?? "—"}%
</p>
</div>
<div>
<strong>Fahrtkosten</strong>
<p>{facility.travelCostRate != null ? `${facility.travelCostRate} EUR (Pauschale je Einsatz)` : "—"}</p>
</div>
<div>
<strong>Mindeststunden</strong>
<p>{facility.minimumHours ?? "—"}</p>
</div>
<div>
<strong>Pausenregelung</strong>
<p>{facility.breakPolicy ?? "—"}</p>
</div>
<div>
<strong>Abrechnungsintervall</strong>
<p>{facility.billingInterval ?? "—"}</p>
</div>
<div>
<strong>Zahlungsziel</strong>
<p>{facility.paymentTermDays != null ? `${facility.paymentTermDays} Tage` : "—"}</p>
</div>
<div>
<strong>Individuelle Vereinbarungen</strong>
<p>{facility.individualAgreements ?? "—"}</p>
</div>
</div>
<FacilityQualificationRatesList facilityId={facility.id} />
<FacilityContactsList facilityId={facility.id} />
</div>