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
@@ -1,3 +1,5 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import OmsorgButton from "../../components/ui/OmsorgButton";
|
||||
import { useValueListItems } from "../../app/useValueListItems";
|
||||
|
||||
@@ -14,6 +16,17 @@ export const emptyFacilityForm = {
|
||||
billingPostalCode: "",
|
||||
billingCity: "",
|
||||
billingCountry: "",
|
||||
billingRate: "",
|
||||
nightSurchargePercent: "",
|
||||
saturdaySurchargePercent: "",
|
||||
sundaySurchargePercent: "",
|
||||
holidaySurchargePercent: "",
|
||||
travelCostRate: "",
|
||||
minimumHours: "",
|
||||
breakPolicy: "",
|
||||
billingInterval: "",
|
||||
paymentTermDays: "",
|
||||
individualAgreements: "",
|
||||
};
|
||||
|
||||
export function facilityToFormValues(facility) {
|
||||
@@ -30,10 +43,21 @@ export function facilityToFormValues(facility) {
|
||||
billingPostalCode: facility.billingPostalCode ?? "",
|
||||
billingCity: facility.billingCity ?? "",
|
||||
billingCountry: facility.billingCountry ?? "",
|
||||
billingRate: facility.billingRate ?? "",
|
||||
nightSurchargePercent: facility.nightSurchargePercent ?? "",
|
||||
saturdaySurchargePercent: facility.saturdaySurchargePercent ?? "",
|
||||
sundaySurchargePercent: facility.sundaySurchargePercent ?? "",
|
||||
holidaySurchargePercent: facility.holidaySurchargePercent ?? "",
|
||||
travelCostRate: facility.travelCostRate ?? "",
|
||||
minimumHours: facility.minimumHours ?? "",
|
||||
breakPolicy: facility.breakPolicy ?? "",
|
||||
billingInterval: facility.billingInterval ?? "",
|
||||
paymentTermDays: facility.paymentTermDays ?? "",
|
||||
individualAgreements: facility.individualAgreements ?? "",
|
||||
};
|
||||
}
|
||||
|
||||
export function facilityFormToPayload(form, { includeCrmStatus = false } = {}) {
|
||||
export function facilityFormToPayload(form, { includeCrmStatus = false, followUpDays = null } = {}) {
|
||||
const payload = {
|
||||
name: form.name.trim(),
|
||||
facilityType: form.facilityType || null,
|
||||
@@ -50,14 +74,52 @@ export function facilityFormToPayload(form, { includeCrmStatus = false } = {}) {
|
||||
|
||||
if (includeCrmStatus) {
|
||||
payload.crmStatus = form.crmStatus;
|
||||
payload.followUpDays = followUpDays;
|
||||
payload.billingRate = form.billingRate === "" ? null : Number(form.billingRate);
|
||||
payload.nightSurchargePercent = form.nightSurchargePercent === "" ? null : Number(form.nightSurchargePercent);
|
||||
payload.saturdaySurchargePercent = form.saturdaySurchargePercent === "" ? null : Number(form.saturdaySurchargePercent);
|
||||
payload.sundaySurchargePercent = form.sundaySurchargePercent === "" ? null : Number(form.sundaySurchargePercent);
|
||||
payload.holidaySurchargePercent = form.holidaySurchargePercent === "" ? null : Number(form.holidaySurchargePercent);
|
||||
payload.travelCostRate = form.travelCostRate === "" ? null : Number(form.travelCostRate);
|
||||
payload.minimumHours = form.minimumHours === "" ? null : Number(form.minimumHours);
|
||||
payload.breakPolicy = form.breakPolicy.trim() || null;
|
||||
payload.billingInterval = form.billingInterval || null;
|
||||
payload.paymentTermDays = form.paymentTermDays === "" ? null : Number(form.paymentTermDays);
|
||||
payload.individualAgreements = form.individualAgreements.trim() || null;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
export default function FacilityForm({ form, onChange, includeCrmStatus = false }) {
|
||||
export default function FacilityForm({ form, onChange, includeCrmStatus = false, currentCrmStatus = null }) {
|
||||
const { items: crmStatusOptions } = useValueListItems("CrmStatus");
|
||||
const { items: facilityTypes } = useValueListItems("FacilityType");
|
||||
const { items: billingIntervals } = useValueListItems("BillingInterval");
|
||||
const [transitions, setTransitions] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!includeCrmStatus) return;
|
||||
|
||||
let cancelled = false;
|
||||
window.omsorg.valueLists.listTransitions("CrmStatus").then((result) => {
|
||||
if (!cancelled) {
|
||||
setTransitions(result.ok ? result.data ?? [] : []);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [includeCrmStatus]);
|
||||
|
||||
const currentCrmStatusItem = crmStatusOptions.find((option) => option.value === currentCrmStatus);
|
||||
const selectableCrmStatusOptions = currentCrmStatusItem
|
||||
? crmStatusOptions.filter(
|
||||
(option) =>
|
||||
option.id === currentCrmStatusItem.id ||
|
||||
transitions.some((t) => t.fromItemId === currentCrmStatusItem.id && t.toItemId === option.id)
|
||||
)
|
||||
: crmStatusOptions;
|
||||
|
||||
function updateField(field) {
|
||||
return (event) => onChange({ ...form, [field]: event.target.value });
|
||||
@@ -74,7 +136,7 @@ export default function FacilityForm({ form, onChange, includeCrmStatus = false
|
||||
<label className="form-field">
|
||||
<span>CRM-Status</span>
|
||||
<select value={form.crmStatus} onChange={updateField("crmStatus")}>
|
||||
{crmStatusOptions.map((option) => (
|
||||
{selectableCrmStatusOptions.map((option) => (
|
||||
<option key={option.id} value={option.value}>
|
||||
{option.value}
|
||||
</option>
|
||||
@@ -151,6 +213,82 @@ export default function FacilityForm({ form, onChange, includeCrmStatus = false
|
||||
<input value={form.billingCountry} onChange={updateField("billingCountry")} />
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
{includeCrmStatus && (
|
||||
<fieldset className="form-field form-field--fieldset">
|
||||
<legend>Konditionen</legend>
|
||||
|
||||
<label className="form-field">
|
||||
<span>Verrechnungssatz (EUR/Std.)</span>
|
||||
<input type="number" min="0" step="0.01" value={form.billingRate} onChange={updateField("billingRate")} />
|
||||
</label>
|
||||
|
||||
<div className="form-row">
|
||||
<label className="form-field">
|
||||
<span>Nachtzuschlag (%)</span>
|
||||
<input type="number" min="0" step="0.1" value={form.nightSurchargePercent} onChange={updateField("nightSurchargePercent")} />
|
||||
</label>
|
||||
|
||||
<label className="form-field">
|
||||
<span>Samstagszuschlag (%)</span>
|
||||
<input type="number" min="0" step="0.1" value={form.saturdaySurchargePercent} onChange={updateField("saturdaySurchargePercent")} />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="form-row">
|
||||
<label className="form-field">
|
||||
<span>Sonntagszuschlag (%)</span>
|
||||
<input type="number" min="0" step="0.1" value={form.sundaySurchargePercent} onChange={updateField("sundaySurchargePercent")} />
|
||||
</label>
|
||||
|
||||
<label className="form-field">
|
||||
<span>Feiertagszuschlag (%)</span>
|
||||
<input type="number" min="0" step="0.1" value={form.holidaySurchargePercent} onChange={updateField("holidaySurchargePercent")} />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="form-row">
|
||||
<label className="form-field">
|
||||
<span>Fahrtkosten (EUR, Pauschale je Einsatz)</span>
|
||||
<input type="number" min="0" step="0.01" value={form.travelCostRate} onChange={updateField("travelCostRate")} />
|
||||
</label>
|
||||
|
||||
<label className="form-field">
|
||||
<span>Mindeststunden je Einsatz</span>
|
||||
<input type="number" min="0" step="0.5" value={form.minimumHours} onChange={updateField("minimumHours")} />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label className="form-field">
|
||||
<span>Pausenregelung</span>
|
||||
<input value={form.breakPolicy} onChange={updateField("breakPolicy")} />
|
||||
</label>
|
||||
|
||||
<div className="form-row">
|
||||
<label className="form-field">
|
||||
<span>Abrechnungsintervall</span>
|
||||
<select value={form.billingInterval} onChange={updateField("billingInterval")}>
|
||||
<option value="">— nicht angegeben —</option>
|
||||
{billingIntervals.map((option) => (
|
||||
<option key={option.id} value={option.value}>
|
||||
{option.value}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label className="form-field">
|
||||
<span>Zahlungsziel (Tage)</span>
|
||||
<input type="number" min="0" step="1" value={form.paymentTermDays} onChange={updateField("paymentTermDays")} />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label className="form-field">
|
||||
<span>Individuelle Vereinbarungen</span>
|
||||
<input value={form.individualAgreements} onChange={updateField("individualAgreements")} />
|
||||
</label>
|
||||
</fieldset>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user