feat: Extend React/Java app to match Lua functionality
Backend: - Add Pretreatment record for pre-treatment data - Extend Sample with pretreatment, clinicalExamDate, internalNote - Extend TherapyRecommendation with detail fields (count, duration, dosage, location) - Add startvacVaccination and noAntibioticTreatment flags - Add null-safety defaults for MongoDB compatibility Frontend: - Add pretreatment fields to SampleRegistrationPage - Add special pathogens section to AnamnesisPage - Add therapy detail pickers to TherapyPage - Improve AntibiogramPage: full text labels, centered headers - Fix AdminDashboardPage TypeScript error in chart tooltip Styling: - Enlarge matrix buttons for S/I/R text - Add matrix-col class for centered table columns
This commit is contained in:
@@ -9,6 +9,13 @@ export type QuarterKey =
|
||||
| "LEFT_REAR"
|
||||
| "RIGHT_REAR";
|
||||
export type PathogenKind = "BACTERIAL" | "NO_GROWTH" | "CONTAMINATED" | "OTHER";
|
||||
|
||||
export interface Pretreatment {
|
||||
inUdderInjector: string | null;
|
||||
systemicAntibiotics: string | null;
|
||||
painMedication: string | null;
|
||||
dryOffTreatment: string | null;
|
||||
}
|
||||
export type SensitivityResult = "SENSITIVE" | "INTERMEDIATE" | "RESISTANT";
|
||||
export type MedicationCategory =
|
||||
| "IN_UDDER"
|
||||
@@ -145,6 +152,14 @@ export interface TherapyView {
|
||||
dryAntibioticNames: string[];
|
||||
farmerNote: string | null;
|
||||
internalNote: string | null;
|
||||
inUdderCount: string | null;
|
||||
inUdderDuration: string | null;
|
||||
systemicCount: string | null;
|
||||
systemicDuration: string | null;
|
||||
systemicDosage: string | null;
|
||||
systemicLocation: string | null;
|
||||
startvacVaccination: boolean | null;
|
||||
noAntibioticTreatment: boolean | null;
|
||||
}
|
||||
|
||||
export interface SampleDetail {
|
||||
@@ -176,6 +191,9 @@ export interface SampleDetail {
|
||||
antibiogramEditable: boolean;
|
||||
therapyEditable: boolean;
|
||||
completed: boolean;
|
||||
pretreatment: Pretreatment | null;
|
||||
clinicalExamDate: string | null;
|
||||
internalNote: string | null;
|
||||
}
|
||||
|
||||
export interface FarmerRow {
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
type TooltipItem,
|
||||
} from "chart.js";
|
||||
import { Bar } from "react-chartjs-2";
|
||||
import { apiGet } from "../lib/api";
|
||||
@@ -101,8 +102,9 @@ export default function AdminDashboardPage() {
|
||||
size: 13,
|
||||
},
|
||||
callbacks: {
|
||||
label: (context: { parsed: { y: number } }) => {
|
||||
return `${context.parsed.y} Proben`;
|
||||
label: (context: TooltipItem<"bar">) => {
|
||||
const value = context.parsed.y as number | null;
|
||||
return `${value ?? 0} Proben`;
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { apiGet, apiPut } from "../lib/api";
|
||||
import type { ActiveCatalogSummary, QuarterKey, QuarterView, SampleDetail } from "../lib/types";
|
||||
import type { ActiveCatalogSummary, PathogenKind, QuarterKey, QuarterView, SampleDetail } from "../lib/types";
|
||||
|
||||
type QuarterFormState = {
|
||||
pathogenBusinessKey: string;
|
||||
customPathogenName: string;
|
||||
cellCount: string;
|
||||
pathogenKind: PathogenKind | null;
|
||||
};
|
||||
|
||||
function quarterStateFromSample(sample: SampleDetail) {
|
||||
@@ -15,11 +16,18 @@ function quarterStateFromSample(sample: SampleDetail) {
|
||||
pathogenBusinessKey: quarter.pathogenBusinessKey ?? "",
|
||||
customPathogenName: quarter.customPathogenName ?? "",
|
||||
cellCount: quarter.cellCount ? String(quarter.cellCount) : "",
|
||||
pathogenKind: quarter.pathogenKind,
|
||||
};
|
||||
return accumulator;
|
||||
}, {});
|
||||
}
|
||||
|
||||
// Special pathogen options like in Lua
|
||||
const SPECIAL_PATHOGENS = [
|
||||
{ key: "NO_GROWTH", label: "Kein bakterielles Wachstum", kind: "NO_GROWTH" as PathogenKind },
|
||||
{ key: "CONTAMINATED", label: "Verunreinigte Probe", kind: "CONTAMINATED" as PathogenKind },
|
||||
];
|
||||
|
||||
export default function AnamnesisPage() {
|
||||
const { sampleId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
@@ -76,6 +84,20 @@ export default function AnamnesisPage() {
|
||||
return Boolean(quarterState?.pathogenBusinessKey || quarterState?.customPathogenName?.trim());
|
||||
}
|
||||
|
||||
function selectSpecialPathogen(quarterKey: QuarterKey, kind: PathogenKind) {
|
||||
updateQuarter(quarterKey, {
|
||||
pathogenBusinessKey: "",
|
||||
customPathogenName: "",
|
||||
pathogenKind: kind,
|
||||
});
|
||||
}
|
||||
|
||||
function isSpecialPathogenSelected(quarterKey: QuarterKey, kind: PathogenKind) {
|
||||
return quarterStates[quarterKey]?.pathogenKind === kind &&
|
||||
!quarterStates[quarterKey]?.pathogenBusinessKey &&
|
||||
!quarterStates[quarterKey]?.customPathogenName;
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
if (!sampleId || !sample) {
|
||||
return;
|
||||
@@ -117,6 +139,7 @@ export default function AnamnesisPage() {
|
||||
pathogenBusinessKey: "",
|
||||
customPathogenName: "",
|
||||
cellCount: "",
|
||||
pathogenKind: null,
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -168,7 +191,25 @@ export default function AnamnesisPage() {
|
||||
<div className="info-chip">Auffaelliges Viertel markiert</div>
|
||||
) : null}
|
||||
|
||||
<p className="required-label">Erreger</p>
|
||||
{/* Special pathogen options like in Lua */}
|
||||
<p className="required-label">Sonderfaelle</p>
|
||||
<div className="pathogen-grid">
|
||||
{SPECIAL_PATHOGENS.map((pathogen) => (
|
||||
<button
|
||||
key={pathogen.key}
|
||||
type="button"
|
||||
className={`pathogen-button ${
|
||||
isSpecialPathogenSelected(visibleQuarter.quarterKey, pathogen.kind) ? "is-selected" : ""
|
||||
}`}
|
||||
onClick={() => selectSpecialPathogen(visibleQuarter.quarterKey, pathogen.kind)}
|
||||
disabled={!sample.anamnesisEditable}
|
||||
>
|
||||
<strong>{pathogen.label}</strong>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<p className="required-label section-card__spacer">Erreger (Katalog)</p>
|
||||
<div className={`pathogen-grid ${showValidation && !quarterHasPathogen(visibleQuarter.quarterKey) ? "is-invalid" : ""}`}>
|
||||
{catalogs.pathogens.map((pathogen) => (
|
||||
<button
|
||||
@@ -181,6 +222,7 @@ export default function AnamnesisPage() {
|
||||
updateQuarter(visibleQuarter.quarterKey, {
|
||||
pathogenBusinessKey: pathogen.businessKey,
|
||||
customPathogenName: "",
|
||||
pathogenKind: null,
|
||||
})
|
||||
}
|
||||
disabled={!sample.anamnesisEditable}
|
||||
@@ -199,6 +241,7 @@ export default function AnamnesisPage() {
|
||||
updateQuarter(visibleQuarter.quarterKey, {
|
||||
customPathogenName: event.target.value,
|
||||
pathogenBusinessKey: "",
|
||||
pathogenKind: null,
|
||||
})
|
||||
}
|
||||
disabled={!sample.anamnesisEditable}
|
||||
@@ -221,8 +264,8 @@ export default function AnamnesisPage() {
|
||||
<div className="info-panel info-panel--spaced">
|
||||
<strong>Hinweis</strong>
|
||||
<p>
|
||||
Kein Wachstum oder verunreinigte Proben werden später automatisch vom
|
||||
Antibiogramm ausgeschlossen.
|
||||
Bei "Kein bakterielles Wachstum" oder "Verunreinigte Probe" wird das Antibiogramm
|
||||
übersprungen und direkt zur Therapie weitergeleitet.
|
||||
</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
@@ -167,9 +167,9 @@ export default function AntibiogramPage() {
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Antibiotikum</th>
|
||||
<th>S</th>
|
||||
<th>I</th>
|
||||
<th>R</th>
|
||||
<th className="matrix-col">Sensibel</th>
|
||||
<th className="matrix-col">Intermediär</th>
|
||||
<th className="matrix-col">Resistent</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -180,7 +180,7 @@ export default function AntibiogramPage() {
|
||||
<small className="table-subtext">{antibiotic.code ?? "ANT"}</small>
|
||||
</td>
|
||||
{(["SENSITIVE", "INTERMEDIATE", "RESISTANT"] as SensitivityResult[]).map((result) => (
|
||||
<td key={result}>
|
||||
<td key={result} className="matrix-col">
|
||||
<button
|
||||
type="button"
|
||||
className={`matrix-button ${
|
||||
@@ -191,7 +191,7 @@ export default function AntibiogramPage() {
|
||||
onClick={() => updateResult(group.referenceQuarter, antibiotic.businessKey, result)}
|
||||
disabled={!sample.antibiogramEditable}
|
||||
>
|
||||
{result === "SENSITIVE" ? "S" : result === "INTERMEDIATE" ? "I" : "R"}
|
||||
{result === "SENSITIVE" ? "Sensibel" : result === "INTERMEDIATE" ? "Intermediär" : "Resistent"}
|
||||
</button>
|
||||
</td>
|
||||
))}
|
||||
|
||||
@@ -18,6 +18,14 @@ const QUARTERS: { key: QuarterKey; label: string }[] = [
|
||||
{ key: "RIGHT_REAR", label: "Hinten rechts" },
|
||||
];
|
||||
|
||||
// Pretreatment options from Lua version
|
||||
const PRETREATMENT_OPTIONS = [
|
||||
{ key: "inUdderInjector", label: "Euterinjektor" },
|
||||
{ key: "systemicAntibiotics", label: "Systemische Antibiotika" },
|
||||
{ key: "painMedication", label: "Schmerzmittel" },
|
||||
{ key: "dryOffTreatment", label: "Trockensteller" },
|
||||
];
|
||||
|
||||
export default function SampleRegistrationPage() {
|
||||
const { sampleId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
@@ -32,6 +40,15 @@ export default function SampleRegistrationPage() {
|
||||
const [sampleKind, setSampleKind] = useState<SampleKind>("LACTATION");
|
||||
const [samplingMode, setSamplingMode] = useState<SamplingMode>("SINGLE_SITE");
|
||||
const [flaggedQuarters, setFlaggedQuarters] = useState<QuarterKey[]>([]);
|
||||
// New fields from Lua version
|
||||
const [pretreatment, setPretreatment] = useState<Record<string, string>>({
|
||||
inUdderInjector: "",
|
||||
systemicAntibiotics: "",
|
||||
painMedication: "",
|
||||
dryOffTreatment: "",
|
||||
});
|
||||
const [clinicalExamDate, setClinicalExamDate] = useState("");
|
||||
const [internalNote, setInternalNote] = useState("");
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [message, setMessage] = useState<string | null>(null);
|
||||
@@ -55,6 +72,17 @@ export default function SampleRegistrationPage() {
|
||||
setFlaggedQuarters(
|
||||
sample.quarters.filter((quarter) => quarter.flagged).map((quarter) => quarter.quarterKey),
|
||||
);
|
||||
// Load new fields
|
||||
if (sample.pretreatment) {
|
||||
setPretreatment({
|
||||
inUdderInjector: sample.pretreatment.inUdderInjector ?? "",
|
||||
systemicAntibiotics: sample.pretreatment.systemicAntibiotics ?? "",
|
||||
painMedication: sample.pretreatment.painMedication ?? "",
|
||||
dryOffTreatment: sample.pretreatment.dryOffTreatment ?? "",
|
||||
});
|
||||
}
|
||||
setClinicalExamDate(sample.clinicalExamDate ?? "");
|
||||
setInternalNote(sample.internalNote ?? "");
|
||||
} else {
|
||||
const dashboard = await apiGet<DashboardOverview>("/dashboard");
|
||||
setSampleNumber(dashboard.nextSampleNumber);
|
||||
@@ -78,6 +106,13 @@ export default function SampleRegistrationPage() {
|
||||
);
|
||||
}
|
||||
|
||||
function updatePretreatment(key: string, value: string) {
|
||||
setPretreatment((current) => ({
|
||||
...current,
|
||||
[key]: value,
|
||||
}));
|
||||
}
|
||||
|
||||
async function handleSubmit(event: FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
setShowValidation(true);
|
||||
@@ -101,6 +136,13 @@ export default function SampleRegistrationPage() {
|
||||
flaggedQuarters,
|
||||
userCode: user.displayName,
|
||||
userDisplayName: user.displayName,
|
||||
// New fields
|
||||
pretreatmentInUdderInjector: pretreatment.inUdderInjector || null,
|
||||
pretreatmentSystemicAntibiotics: pretreatment.systemicAntibiotics || null,
|
||||
pretreatmentPainMedication: pretreatment.painMedication || null,
|
||||
pretreatmentDryOffTreatment: pretreatment.dryOffTreatment || null,
|
||||
clinicalExamDate: clinicalExamDate || null,
|
||||
internalNote: internalNote || null,
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -126,7 +168,7 @@ export default function SampleRegistrationPage() {
|
||||
<p className="eyebrow">Neuanlage</p>
|
||||
<h3>Probe {sampleNumber ?? "..."}</h3>
|
||||
<p className="muted-text">
|
||||
Die Probenummer wird fortlaufend vergeben. Trockensteller lassen sich ueber den
|
||||
Die Probenummer wird fortlaufend vergeben. Trockensteller lassen sich über den
|
||||
Schalter Trockenstellerprobe markieren.
|
||||
</p>
|
||||
</div>
|
||||
@@ -258,6 +300,55 @@ export default function SampleRegistrationPage() {
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
{/* New section: Pretreatment from Lua version */}
|
||||
<section className="section-card">
|
||||
<p className="eyebrow">Vorbehandelt mit</p>
|
||||
<div className="field-grid field-grid--stacked">
|
||||
{PRETREATMENT_OPTIONS.map((option) => (
|
||||
<label key={option.key} className="field">
|
||||
<span>{option.label}</span>
|
||||
<input
|
||||
type="text"
|
||||
value={pretreatment[option.key]}
|
||||
onChange={(event) => updatePretreatment(option.key, event.target.value)}
|
||||
disabled={!editable}
|
||||
placeholder="Ohne Vorbehandlung"
|
||||
/>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* New section: Clinical Exam Date from Lua version */}
|
||||
<section className="form-grid">
|
||||
<article className="section-card">
|
||||
<p className="eyebrow">Klinische Untersuchung</p>
|
||||
<label className="field">
|
||||
<span>Untersuchungsdatum (TT.MM.JJJJ)</span>
|
||||
<input
|
||||
type="text"
|
||||
value={clinicalExamDate}
|
||||
onChange={(event) => setClinicalExamDate(event.target.value)}
|
||||
disabled={!editable}
|
||||
placeholder="z.B. 15.03.2024"
|
||||
/>
|
||||
</label>
|
||||
</article>
|
||||
|
||||
<article className="section-card">
|
||||
<p className="eyebrow">Interne Bemerkung</p>
|
||||
<label className="field">
|
||||
<span>Bemerkung zur Probe</span>
|
||||
<textarea
|
||||
value={internalNote}
|
||||
onChange={(event) => setInternalNote(event.target.value)}
|
||||
disabled={!editable}
|
||||
rows={3}
|
||||
/>
|
||||
</label>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<div className="page-actions">
|
||||
<button type="submit" className="accent-button" disabled={saving || !editable}>
|
||||
{saving ? "Speichern ..." : "Speichern"}
|
||||
|
||||
@@ -11,6 +11,12 @@ function medicationOptions(catalogs: ActiveCatalogSummary, category: MedicationC
|
||||
return catalogs.medications.filter((medication) => medication.category === category);
|
||||
}
|
||||
|
||||
// Options for dropdowns like in Lua version
|
||||
const COUNT_OPTIONS = ["1", "2", "3", "4", "5"];
|
||||
const DURATION_OPTIONS = ["1 Tag", "2 Tage", "3 Tage", "4 Tage", "5 Tage", "6 Tage", "7 Tage", "8 Tage", "10 Tage", "14 Tage"];
|
||||
const DOSAGE_OPTIONS = ["einmalig", "1 x", "2 x", "3 x"];
|
||||
const LOCATION_OPTIONS = ["i.m.", "i.v.", "s.c."];
|
||||
|
||||
export default function TherapyPage() {
|
||||
const { sampleId } = useParams();
|
||||
|
||||
@@ -26,6 +32,15 @@ export default function TherapyPage() {
|
||||
const [dryAntibioticKeys, setDryAntibioticKeys] = useState<string[]>([]);
|
||||
const [farmerNote, setFarmerNote] = useState("");
|
||||
const [internalNote, setInternalNote] = useState("");
|
||||
// New fields from Lua version
|
||||
const [inUdderCount, setInUdderCount] = useState("");
|
||||
const [inUdderDuration, setInUdderDuration] = useState("");
|
||||
const [systemicCount, setSystemicCount] = useState("");
|
||||
const [systemicDuration, setSystemicDuration] = useState("");
|
||||
const [systemicDosage, setSystemicDosage] = useState("");
|
||||
const [systemicLocation, setSystemicLocation] = useState("");
|
||||
const [startvacVaccination, setStartvacVaccination] = useState(false);
|
||||
const [noAntibioticTreatment, setNoAntibioticTreatment] = useState(false);
|
||||
const [message, setMessage] = useState<string | null>(null);
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
@@ -51,6 +66,15 @@ export default function TherapyPage() {
|
||||
setDryAntibioticKeys(sampleResponse.therapy?.dryAntibioticKeys ?? []);
|
||||
setFarmerNote(sampleResponse.therapy?.farmerNote ?? "");
|
||||
setInternalNote(sampleResponse.therapy?.internalNote ?? "");
|
||||
// Load new fields
|
||||
setInUdderCount(sampleResponse.therapy?.inUdderCount ?? "");
|
||||
setInUdderDuration(sampleResponse.therapy?.inUdderDuration ?? "");
|
||||
setSystemicCount(sampleResponse.therapy?.systemicCount ?? "");
|
||||
setSystemicDuration(sampleResponse.therapy?.systemicDuration ?? "");
|
||||
setSystemicDosage(sampleResponse.therapy?.systemicDosage ?? "");
|
||||
setSystemicLocation(sampleResponse.therapy?.systemicLocation ?? "");
|
||||
setStartvacVaccination(sampleResponse.therapy?.startvacVaccination ?? false);
|
||||
setNoAntibioticTreatment(sampleResponse.therapy?.noAntibioticTreatment ?? false);
|
||||
} catch (loadError) {
|
||||
setMessage((loadError as Error).message);
|
||||
}
|
||||
@@ -65,6 +89,14 @@ export default function TherapyPage() {
|
||||
setter(list.includes(value) ? list.filter((entry) => entry !== value) : [...list, value]);
|
||||
}
|
||||
|
||||
// Check if "Keine" (None) is selected for in-udder or systemic
|
||||
const noInUdderSelected = inUdderMedicationKeys.some(key =>
|
||||
catalogs?.medications.find(m => m.businessKey === key)?.name === "Keine"
|
||||
);
|
||||
const noSystemicSelected = systemicMedicationKeys.some(key =>
|
||||
catalogs?.medications.find(m => m.businessKey === key)?.name === "Keine"
|
||||
);
|
||||
|
||||
async function handleSave() {
|
||||
if (!sampleId) {
|
||||
return;
|
||||
@@ -83,6 +115,15 @@ export default function TherapyPage() {
|
||||
dryAntibioticKeys,
|
||||
farmerNote,
|
||||
internalNote,
|
||||
// New fields
|
||||
inUdderCount: inUdderCount || null,
|
||||
inUdderDuration: inUdderDuration || null,
|
||||
systemicCount: systemicCount || null,
|
||||
systemicDuration: systemicDuration || null,
|
||||
systemicDosage: systemicDosage || null,
|
||||
systemicLocation: systemicLocation || null,
|
||||
startvacVaccination,
|
||||
noAntibioticTreatment,
|
||||
});
|
||||
setSample(response);
|
||||
setMessage(response.completed ? "Probe gespeichert und abgeschlossen." : "Aenderung gespeichert.");
|
||||
@@ -168,7 +209,35 @@ export default function TherapyPage() {
|
||||
))}
|
||||
</div>
|
||||
|
||||
<label className="field">
|
||||
{/* In-Udder Details from Lua */}
|
||||
{!noInUdderSelected && inUdderMedicationKeys.length > 0 && (
|
||||
<div className="field-grid field-grid--2col section-card__spacer">
|
||||
<label className="field">
|
||||
<span>Anzahl</span>
|
||||
<select
|
||||
value={inUdderCount}
|
||||
onChange={(e) => setInUdderCount(e.target.value)}
|
||||
disabled={therapyLocked}
|
||||
>
|
||||
<option value="">-</option>
|
||||
{COUNT_OPTIONS.map(opt => <option key={opt} value={opt}>{opt}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
<label className="field">
|
||||
<span>Dauer</span>
|
||||
<select
|
||||
value={inUdderDuration}
|
||||
onChange={(e) => setInUdderDuration(e.target.value)}
|
||||
disabled={therapyLocked}
|
||||
>
|
||||
<option value="">-</option>
|
||||
{DURATION_OPTIONS.map(opt => <option key={opt} value={opt}>{opt}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<label className="field section-card__spacer">
|
||||
<span>Sonstiges</span>
|
||||
<textarea
|
||||
value={inUdderOther}
|
||||
@@ -202,7 +271,57 @@ export default function TherapyPage() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<label className="field">
|
||||
{/* Systemic Details from Lua */}
|
||||
{!noSystemicSelected && systemicMedicationKeys.length > 0 && (
|
||||
<div className="field-grid field-grid--2col section-card__spacer">
|
||||
<label className="field">
|
||||
<span>Anzahl</span>
|
||||
<select
|
||||
value={systemicCount}
|
||||
onChange={(e) => setSystemicCount(e.target.value)}
|
||||
disabled={therapyLocked}
|
||||
>
|
||||
<option value="">-</option>
|
||||
{COUNT_OPTIONS.map(opt => <option key={opt} value={opt}>{opt}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
<label className="field">
|
||||
<span>Dauer</span>
|
||||
<select
|
||||
value={systemicDuration}
|
||||
onChange={(e) => setSystemicDuration(e.target.value)}
|
||||
disabled={therapyLocked}
|
||||
>
|
||||
<option value="">-</option>
|
||||
{DURATION_OPTIONS.map(opt => <option key={opt} value={opt}>{opt}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
<label className="field">
|
||||
<span>Dosierung</span>
|
||||
<select
|
||||
value={systemicDosage}
|
||||
onChange={(e) => setSystemicDosage(e.target.value)}
|
||||
disabled={therapyLocked}
|
||||
>
|
||||
<option value="">-</option>
|
||||
{DOSAGE_OPTIONS.map(opt => <option key={opt} value={opt}>{opt}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
<label className="field">
|
||||
<span>Ort</span>
|
||||
<select
|
||||
value={systemicLocation}
|
||||
onChange={(e) => setSystemicLocation(e.target.value)}
|
||||
disabled={therapyLocked}
|
||||
>
|
||||
<option value="">-</option>
|
||||
{LOCATION_OPTIONS.map(opt => <option key={opt} value={opt}>{opt}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<label className="field section-card__spacer">
|
||||
<span>Sonstiges</span>
|
||||
<textarea
|
||||
value={systemicOther}
|
||||
@@ -254,6 +373,33 @@ export default function TherapyPage() {
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Additional Options from Lua */}
|
||||
{sample.sampleKind === "LACTATION" && (
|
||||
<section className="section-card">
|
||||
<p className="eyebrow">Sonstiges</p>
|
||||
<div className="field-grid">
|
||||
<label className="field field--checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={noAntibioticTreatment}
|
||||
onChange={(e) => setNoAntibioticTreatment(e.target.checked)}
|
||||
disabled={therapyLocked}
|
||||
/>
|
||||
<span>Keine Antibiose, gut ausmelken (evtl. Oxytocin), als letzte melken, strikte Hygiene</span>
|
||||
</label>
|
||||
<label className="field field--checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={startvacVaccination}
|
||||
onChange={(e) => setStartvacVaccination(e.target.checked)}
|
||||
disabled={therapyLocked}
|
||||
/>
|
||||
<span>Startvac-Impfung</span>
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
<section className="form-grid">
|
||||
<article className="section-card">
|
||||
<label className="field">
|
||||
|
||||
@@ -624,10 +624,17 @@ a {
|
||||
}
|
||||
|
||||
.matrix-button {
|
||||
width: 42px;
|
||||
min-width: 90px;
|
||||
width: auto;
|
||||
height: 42px;
|
||||
padding: 0 16px;
|
||||
border-radius: 12px;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.matrix-col {
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.eye-button {
|
||||
@@ -1489,3 +1496,71 @@ a {
|
||||
height: 72vh;
|
||||
}
|
||||
}
|
||||
|
||||
/* Additional styles for new Lua features */
|
||||
|
||||
/* Checkbox field styling */
|
||||
.field--checkbox {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.field--checkbox input[type="checkbox"] {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-top: 2px;
|
||||
flex-shrink: 0;
|
||||
accent-color: var(--accent);
|
||||
}
|
||||
|
||||
.field--checkbox span {
|
||||
font-size: 0.9rem;
|
||||
color: var(--text);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Special pathogen buttons (NO_GROWTH, CONTAMINATED) */
|
||||
.pathogen-button.special-pathogen {
|
||||
background: rgba(138, 101, 0, 0.1);
|
||||
border: 1px solid rgba(138, 101, 0, 0.2);
|
||||
}
|
||||
|
||||
.pathogen-button.special-pathogen.is-selected {
|
||||
background: linear-gradient(135deg, rgba(138, 101, 0, 0.25), rgba(138, 101, 0, 0.1));
|
||||
box-shadow: inset 0 0 0 1px rgba(138, 101, 0, 0.4);
|
||||
}
|
||||
|
||||
/* Therapy detail fields */
|
||||
.field-grid--2col {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.field-grid--2col {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Disabled state for therapy when "Keine" is selected */
|
||||
.choice-chip:disabled,
|
||||
.field input:disabled,
|
||||
.field select:disabled,
|
||||
.field textarea:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Pretreatment section styling */
|
||||
.pretreatment-grid {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.pretreatment-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user