Files
muh/frontend/src/pages/SampleRegistrationPage.tsx

269 lines
9.0 KiB
TypeScript

import { FormEvent, useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { apiGet, apiPost, apiPut } from "../lib/api";
import { useSession } from "../lib/session";
import type {
ActiveCatalogSummary,
DashboardOverview,
QuarterKey,
SampleDetail,
SampleKind,
SamplingMode,
} from "../lib/types";
const QUARTERS: { key: QuarterKey; label: string }[] = [
{ key: "LEFT_FRONT", label: "Vorne links" },
{ key: "RIGHT_FRONT", label: "Vorne rechts" },
{ key: "LEFT_REAR", label: "Hinten links" },
{ key: "RIGHT_REAR", label: "Hinten rechts" },
];
export default function SampleRegistrationPage() {
const { sampleId } = useParams();
const navigate = useNavigate();
const { user } = useSession();
const [catalogs, setCatalogs] = useState<ActiveCatalogSummary | null>(null);
const [sampleNumber, setSampleNumber] = useState<number | null>(null);
const [editable, setEditable] = useState(true);
const [farmerBusinessKey, setFarmerBusinessKey] = useState("");
const [cowNumber, setCowNumber] = useState("");
const [cowName, setCowName] = useState("");
const [sampleKind, setSampleKind] = useState<SampleKind>("LACTATION");
const [samplingMode, setSamplingMode] = useState<SamplingMode>("SINGLE_SITE");
const [flaggedQuarters, setFlaggedQuarters] = useState<QuarterKey[]>([]);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [message, setMessage] = useState<string | null>(null);
const [showValidation, setShowValidation] = useState(false);
useEffect(() => {
async function load() {
try {
const catalogResponse = await apiGet<ActiveCatalogSummary>("/catalogs/summary");
setCatalogs(catalogResponse);
if (sampleId) {
const sample = await apiGet<SampleDetail>(`/samples/${sampleId}`);
setSampleNumber(sample.sampleNumber);
setEditable(sample.registrationEditable);
setFarmerBusinessKey(sample.farmerBusinessKey);
setCowNumber(sample.cowNumber);
setCowName(sample.cowName ?? "");
setSampleKind(sample.sampleKind);
setSamplingMode(sample.samplingMode);
setFlaggedQuarters(
sample.quarters.filter((quarter) => quarter.flagged).map((quarter) => quarter.quarterKey),
);
} else {
const dashboard = await apiGet<DashboardOverview>("/dashboard");
setSampleNumber(dashboard.nextSampleNumber);
setFarmerBusinessKey(catalogResponse.farmers[0]?.businessKey ?? "");
}
} catch (loadError) {
setMessage((loadError as Error).message);
} finally {
setLoading(false);
}
}
void load();
}, [sampleId]);
function toggleFlaggedQuarter(quarterKey: QuarterKey) {
setFlaggedQuarters((current) =>
current.includes(quarterKey)
? current.filter((entry) => entry !== quarterKey)
: [...current, quarterKey],
);
}
async function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
setShowValidation(true);
if (!user) {
return;
}
if (!farmerBusinessKey || !cowNumber.trim()) {
setMessage("Landwirt und Kuh-Nummer sind erforderlich.");
return;
}
setSaving(true);
setMessage(null);
const payload = {
farmerBusinessKey,
cowNumber,
cowName,
sampleKind,
samplingMode,
flaggedQuarters,
userCode: user.displayName,
userDisplayName: user.displayName,
};
try {
const response = sampleId
? await apiPut<SampleDetail>(`/samples/${sampleId}/registration`, payload)
: await apiPost<SampleDetail>("/samples", payload);
navigate(`/samples/${response.id}/${response.routeSegment}`);
} catch (saveError) {
setMessage((saveError as Error).message);
} finally {
setSaving(false);
}
}
if (loading) {
return <div className="empty-state">Probe wird vorbereitet ...</div>;
}
return (
<form className={`page-stack ${showValidation ? "show-validation" : ""}`} onSubmit={handleSubmit}>
<section className="section-card section-card--hero">
<div>
<p className="eyebrow">Neuanlage</p>
<h3>Probe {sampleNumber ?? "..."}</h3>
<p className="muted-text">
Die Probenummer wird fortlaufend vergeben. Trockensteller lassen sich ueber den
Schalter Trockenstellerprobe markieren.
</p>
</div>
{!editable ? (
<div className="alert alert--warning">
Diese Probe ist bereits weiter im Ablauf. Stammdaten sind nicht mehr editierbar.
</div>
) : null}
{message ? <div className="alert alert--error">{message}</div> : null}
</section>
<section className="form-grid form-grid--stacked">
<article className="section-card">
<p className="eyebrow">Stammdaten</p>
<div className="field-grid field-grid--stacked">
<label className="field field--required">
<span>Landwirt</span>
<select
value={farmerBusinessKey}
onChange={(event) => setFarmerBusinessKey(event.target.value)}
disabled={!editable}
required
>
{catalogs?.farmers.map((farmer) => (
<option key={farmer.businessKey} value={farmer.businessKey}>
{farmer.name}
</option>
))}
</select>
</label>
<label className="field field--required">
<span>Kuh-Nummer</span>
<input
value={cowNumber}
onChange={(event) => setCowNumber(event.target.value)}
disabled={!editable}
required
/>
</label>
<label className="field">
<span>Kuh-Name</span>
<input
value={cowName}
onChange={(event) => setCowName(event.target.value)}
disabled={!editable}
/>
</label>
</div>
</article>
<article className="section-card">
<p className="eyebrow">Probentyp</p>
<div className="choice-row">
<button
type="button"
className={`choice-chip ${sampleKind === "LACTATION" ? "is-selected" : ""}`}
onClick={() => setSampleKind("LACTATION")}
disabled={!editable}
>
Laktationsprobe
</button>
<button
type="button"
className={`choice-chip ${sampleKind === "DRY_OFF" ? "is-selected" : ""}`}
onClick={() => setSampleKind("DRY_OFF")}
disabled={!editable}
>
Trockenstellerprobe
</button>
</div>
<p className="eyebrow section-card__spacer">Entnahmestelle</p>
<div className="choice-row">
<button
type="button"
className={`choice-chip ${samplingMode === "SINGLE_SITE" ? "is-selected" : ""}`}
onClick={() => setSamplingMode("SINGLE_SITE")}
disabled={!editable}
>
Einzelprobe
</button>
<button
type="button"
className={`choice-chip ${samplingMode === "FOUR_QUARTER" ? "is-selected" : ""}`}
onClick={() => setSamplingMode("FOUR_QUARTER")}
disabled={!editable}
>
4/4 Probe
</button>
<button
type="button"
className={`choice-chip ${samplingMode === "UNKNOWN_SITE" ? "is-selected" : ""}`}
onClick={() => setSamplingMode("UNKNOWN_SITE")}
disabled={!editable}
>
Unbek.
</button>
</div>
</article>
</section>
{samplingMode === "FOUR_QUARTER" ? (
<section className="section-card">
<div className="section-card__header">
<div>
<p className="eyebrow">Auffaellige Viertel</p>
<h3>Viertel markieren</h3>
</div>
</div>
<div className="quarter-grid">
{QUARTERS.map((quarter) => (
<button
key={quarter.key}
type="button"
className={`quarter-tile ${flaggedQuarters.includes(quarter.key) ? "is-flagged" : ""}`}
onClick={() => toggleFlaggedQuarter(quarter.key)}
disabled={!editable}
>
<span>{quarter.label}</span>
<strong>{flaggedQuarters.includes(quarter.key) ? "Auffällig" : "OK"}</strong>
</button>
))}
</div>
</section>
) : null}
<div className="page-actions">
<button type="submit" className="accent-button" disabled={saving || !editable}>
{saving ? "Speichern ..." : "Speichern"}
</button>
</div>
</form>
);
}