diff --git a/omsorgapp/src/components/ui/DecimalInput.jsx b/omsorgapp/src/components/ui/DecimalInput.jsx
new file mode 100644
index 0000000..014c881
--- /dev/null
+++ b/omsorgapp/src/components/ui/DecimalInput.jsx
@@ -0,0 +1,36 @@
+/**
+ * ==========================================================
+ * OMSORG BUSINESS CONTROLS PRO
+ * Component: DecimalInput
+ * Sprint: 0.2.0
+ * ==========================================================
+ */
+
+import { sanitizeDecimalInput } from "../../utils/germanDecimal";
+
+export default function DecimalInput({
+ value,
+ onChange,
+ placeholder,
+ required = false,
+ disabled = false,
+ id,
+}) {
+ function handleChange(event) {
+ onChange(sanitizeDecimalInput(event.target.value));
+ }
+
+ return (
+
+ );
+}
diff --git a/omsorgapp/src/modules/employees/ContractForm.jsx b/omsorgapp/src/modules/employees/ContractForm.jsx
index 459b1d2..94a548e 100644
--- a/omsorgapp/src/modules/employees/ContractForm.jsx
+++ b/omsorgapp/src/modules/employees/ContractForm.jsx
@@ -1,5 +1,7 @@
import OmsorgButton from "../../components/ui/OmsorgButton";
+import DecimalInput from "../../components/ui/DecimalInput";
import { useValueListItems } from "../../app/useValueListItems";
+import { parseGermanDecimal } from "../../utils/germanDecimal";
export const emptyContractForm = {
contractType: "",
@@ -36,8 +38,8 @@ export function contractFormToPayload(form, { employeeId, includeStatus = false
facilityId: null,
startDate: form.startDate,
endDate: form.endDate || null,
- weeklyHours: form.weeklyHours === "" ? null : Number(form.weeklyHours),
- hourlyWage: form.hourlyWage === "" ? null : Number(form.hourlyWage),
+ weeklyHours: parseGermanDecimal(form.weeklyHours),
+ hourlyWage: parseGermanDecimal(form.hourlyWage),
allowancesDescription: form.allowancesDescription.trim() || null,
overtimeRules: form.overtimeRules.trim() || null,
vacationDaysPerYear: form.vacationDaysPerYear === "" ? null : Number(form.vacationDaysPerYear),
@@ -59,6 +61,10 @@ export default function ContractForm({ form, onChange, includeStatus = false })
return (event) => onChange({ ...form, [field]: event.target.value });
}
+ function updateDecimalField(field) {
+ return (rawValue) => onChange({ ...form, [field]: rawValue });
+ }
+
return (
Verpflegungsmehraufwand
-
{facility.mealAllowanceRate != null ? `${facility.mealAllowanceRate} EUR (Pauschale je Einsatz)` : "—"}
+
{facility.mealAllowanceRate != null ? `${formatGermanDecimal(facility.mealAllowanceRate)} EUR (Pauschale je Einsatz)` : "—"}
Mindeststunden
-
{facility.minimumHours ?? "—"}
+
{facility.minimumHours != null ? formatGermanDecimal(facility.minimumHours) : "—"}
diff --git a/omsorgapp/src/modules/facilities/FacilityForm.jsx b/omsorgapp/src/modules/facilities/FacilityForm.jsx
index bff6ff9..0896a2c 100644
--- a/omsorgapp/src/modules/facilities/FacilityForm.jsx
+++ b/omsorgapp/src/modules/facilities/FacilityForm.jsx
@@ -1,7 +1,9 @@
import { useEffect, useState } from "react";
import OmsorgButton from "../../components/ui/OmsorgButton";
+import DecimalInput from "../../components/ui/DecimalInput";
import { useValueListItems } from "../../app/useValueListItems";
+import { parseGermanDecimal } from "../../utils/germanDecimal";
export const emptyFacilityForm = {
name: "",
@@ -79,16 +81,16 @@ export function facilityFormToPayload(form, { includeCrmStatus = false, followUp
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.billingRate = parseGermanDecimal(form.billingRate);
+ payload.nightSurchargePercent = parseGermanDecimal(form.nightSurchargePercent);
+ payload.saturdaySurchargePercent = parseGermanDecimal(form.saturdaySurchargePercent);
+ payload.sundaySurchargePercent = parseGermanDecimal(form.sundaySurchargePercent);
+ payload.holidaySurchargePercent = parseGermanDecimal(form.holidaySurchargePercent);
payload.travelCostMode = form.travelCostMode || "Pauschale";
- payload.travelCostRate = form.travelCostRate === "" ? null : Number(form.travelCostRate);
- payload.travelCostPerKm = form.travelCostPerKm === "" ? null : Number(form.travelCostPerKm);
- payload.mealAllowanceRate = form.mealAllowanceRate === "" ? null : Number(form.mealAllowanceRate);
- payload.minimumHours = form.minimumHours === "" ? null : Number(form.minimumHours);
+ payload.travelCostRate = parseGermanDecimal(form.travelCostRate);
+ payload.travelCostPerKm = parseGermanDecimal(form.travelCostPerKm);
+ payload.mealAllowanceRate = parseGermanDecimal(form.mealAllowanceRate);
+ payload.minimumHours = parseGermanDecimal(form.minimumHours);
payload.billingInterval = form.billingInterval || null;
payload.paymentTermDays = form.paymentTermDays === "" ? null : Number(form.paymentTermDays);
payload.individualAgreements = form.individualAgreements.trim() || null;
@@ -131,6 +133,10 @@ export default function FacilityForm({ form, onChange, includeCrmStatus = false,
return (event) => onChange({ ...form, [field]: event.target.value });
}
+ function updateDecimalField(field) {
+ return (rawValue) => onChange({ ...form, [field]: rawValue });
+ }
+
return (
@@ -226,30 +232,30 @@ export default function FacilityForm({ form, onChange, includeCrmStatus = false,
Verrechnungssatz (EUR/Std.)
-
+
Nachtzuschlag (%)
-
+
Samstagszuschlag (%)
-
+
Sonntagszuschlag (%)
-
+
Feiertagszuschlag (%)
-
+
@@ -264,7 +270,7 @@ export default function FacilityForm({ form, onChange, includeCrmStatus = false,
Mindeststunden je Einsatz
-
+
@@ -272,12 +278,12 @@ export default function FacilityForm({ form, onChange, includeCrmStatus = false,
{form.travelCostMode === "ProKilometer" ? (
Fahrtkosten (EUR/km)
-
+
) : (
Fahrtkosten (EUR, Pauschale je Einsatz)
-
+
)}
@@ -285,7 +291,7 @@ export default function FacilityForm({ form, onChange, includeCrmStatus = false,
Verpflegungsmehraufwand (EUR, Pauschale je Einsatz)
-
+
diff --git a/omsorgapp/src/utils/germanDecimal.js b/omsorgapp/src/utils/germanDecimal.js
new file mode 100644
index 0000000..3f8e420
--- /dev/null
+++ b/omsorgapp/src/utils/germanDecimal.js
@@ -0,0 +1,32 @@
+export function sanitizeDecimalInput(rawValue) {
+ if (!rawValue) return "";
+ const cleaned = rawValue.replace(/[^\d,.]/g, "");
+ let separatorIdx = -1;
+ let result = "";
+ for (let i = 0; i < cleaned.length; i++) {
+ if (cleaned[i] === "," || cleaned[i] === ".") {
+ if (separatorIdx === -1) {
+ separatorIdx = result.length;
+ result += cleaned[i];
+ }
+ } else {
+ result += cleaned[i];
+ }
+ }
+ return result;
+}
+
+export function parseGermanDecimal(value) {
+ if (value === "" || value == null) return null;
+ const trimmed = String(value).trim();
+ if (!trimmed) return null;
+ const normalized = trimmed.replace(",", ".");
+ if (!/^\d+(\.\d+)?$/.test(normalized)) return null;
+ return Number(normalized);
+}
+
+export function formatGermanDecimal(value, { fractionDigits } = {}) {
+ if (value == null) return "";
+ const str = fractionDigits != null ? Number(value).toFixed(fractionDigits) : String(value);
+ return str.replace(".", ",");
+}