feat: Bearbeiten für alle Log-Einträge (Aufgaben, Durchsichten, Behandlungen, Fütterungen, Varroa-Messungen, Ernten, Schleuderungen, Abfüllungen)

Bisher liessen sich diese acht Objekttypen nur anlegen, nie nachträglich
korrigieren – Aufgaben hatten zwar schon einen PATCH-Endpunkt, aber
keine Bearbeiten-UI, die übrigen sieben hatten weder Backend- noch
Frontend-Unterstützung. Jetzt bekommt jeder Typ einen PATCH :id
Endpunkt (UpdateXDto per PartialType(PickType(...)) nach demselben
Muster wie UpdateColonyDto/UpdateApiaryDto, mit Audit-Log-Eintrag
inkl. Vorher-/Nachher-Stand) sowie eine Bearbeiten-UI:

- Aufgaben: Dialog auf der Aufgabenliste
- Durchsichten/Behandlungen/Fütterungen/Varroa-Messungen: Dialog pro
  Listeneintrag auf der Volk-Detailseite
- Ernten: Dialog auf der eigenen Ernte-Detailseite
- Schleuderungen: Dialog direkt in der Rückverfolgbarkeits-Karte der
  Honigcharge (keine eigene Detailseite nötig); Erntezeilen dort
  verlinken jetzt zur Ernte-Detailseite statt sie zu duplizieren
- Abfüllungen: Dialog auf der Abfüllungs-Detailseite, bewusst nur
  Datum/MHD/Losnummer editierbar – Glasgröße/-anzahl bleiben
  unveränderlich, weil sie bereits unumkehrbar die Honigcharge-Menge
  reduziert haben

Relationsstiftende Fremdschlüssel (colonyId, apiaryId/colonyIds,
harvestIds, honeyBatchId) bleiben überall bewusst nicht editierbar,
konsistent mit dem bestehenden apiaryId-Ausschluss bei UpdateColonyDto.
This commit is contained in:
Nick Szaunig committed 2026-08-25 04:28:37 +02:00
1 parent 63ab0e842d
commit 68f459001a
58 files changed
+1909 -60

No files matched your search

@@ -0,0 +1,97 @@
"use client";
import { FormEvent, useState } from "react";
import { Pencil } from "lucide-react";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { updateBottlingBatch } from "@/lib/bottling/api";
import { BottlingBatch } from "@/lib/bottling/types";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
export function BottlingEditDialog({ bottlingBatch }: { bottlingBatch: BottlingBatch }) {
const queryClient = useQueryClient();
const [open, setOpen] = useState(false);
const [date, setDate] = useState(bottlingBatch.date.slice(0, 10));
const [bestBeforeDate, setBestBeforeDate] = useState(
bottlingBatch.bestBeforeDate.slice(0, 10),
);
const [lotNumber, setLotNumber] = useState(bottlingBatch.lotNumber);
const mutation = useMutation({
mutationFn: () =>
updateBottlingBatch(bottlingBatch.id, {
date: new Date(date).toISOString(),
bestBeforeDate: new Date(bestBeforeDate).toISOString(),
lotNumber,
}),
onSuccess: () => {
void queryClient.invalidateQueries({
queryKey: ["bottling-batches", bottlingBatch.id],
});
setOpen(false);
},
});
function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
mutation.mutate();
}
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger
render={
<Button variant="outline" size="sm">
<Pencil className="size-3.5" />
Bearbeiten
</Button>
}
/>
<DialogContent>
<DialogHeader>
<DialogTitle>Abfüllung bearbeiten</DialogTitle>
</DialogHeader>
<form className="flex flex-col gap-4" onSubmit={handleSubmit}>
<div className="flex flex-col gap-2">
<Label htmlFor="bottling-edit-date">Datum</Label>
<Input
id="bottling-edit-date"
type="date"
value={date}
onChange={(event) => setDate(event.target.value)}
/>
</div>
<div className="flex flex-col gap-2">
<Label htmlFor="bottling-edit-best-before">Mindesthaltbarkeitsdatum</Label>
<Input
id="bottling-edit-best-before"
type="date"
value={bestBeforeDate}
onChange={(event) => setBestBeforeDate(event.target.value)}
/>
</div>
<div className="flex flex-col gap-2">
<Label htmlFor="bottling-edit-lot">Los-/Chargennummer</Label>
<Input
id="bottling-edit-lot"
required
value={lotNumber}
onChange={(event) => setLotNumber(event.target.value)}
/>
</div>
<Button type="submit" disabled={mutation.isPending}>
{mutation.isPending ? "Speichert…" : "Speichern"}
</Button>
</form>
</DialogContent>
</Dialog>
);
}
@@ -7,6 +7,7 @@ import { useQuery } from "@tanstack/react-query";
import { fetchBottlingBatch } from "@/lib/bottling/api";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { QrCodeDialog } from "@/components/qr-code-dialog";
import { BottlingEditDialog } from "./bottling-edit-dialog";
export default function BottlingBatchDetailPage() {
const params = useParams<{ id: string }>();
@@ -39,11 +40,14 @@ export default function BottlingBatchDetailPage() {
</span>
<CardTitle className="font-mono">Abfüllung {bottlingBatch.lotNumber}</CardTitle>
</div>
<QrCodeDialog
entityType="bottling-batches"
id={bottlingBatch.id}
label="QR-Code für Etikett"
/>
<div className="flex items-center gap-2">
<BottlingEditDialog bottlingBatch={bottlingBatch} />
<QrCodeDialog
entityType="bottling-batches"
id={bottlingBatch.id}
label="QR-Code für Etikett"
/>
</div>
</CardHeader>
<CardContent className="flex flex-col gap-2 text-sm">
<p>Honigcharge: {bottlingBatch.honeyBatch.batchNumber}</p>