diff --git a/ApiCore-Frontend/src/app/(app)/production-batches/[id]/page.tsx b/ApiCore-Frontend/src/app/(app)/production-batches/[id]/page.tsx index 95b75d6..d9b6db5 100644 --- a/ApiCore-Frontend/src/app/(app)/production-batches/[id]/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/production-batches/[id]/page.tsx @@ -15,6 +15,10 @@ import { DetailHeader, DetailQueryFallback } from "@/components/detail-header"; import { StockCard } from "@/components/stock/stock-card"; import { useAuth } from "@/lib/auth/auth-context"; import { EditProductionBatchDialog } from "./edit-production-batch-dialog"; +import { + RAW_MATERIAL_KIND_LABELS, + formatGrams, +} from "@/lib/raw-materials/labels"; const BACK = { backHref: "/production-batches", @@ -102,6 +106,25 @@ export default function ProductionBatchDetailPage() { {batch.notes ? (

Notiz: {batch.notes}

) : null} + {batch.inputs && batch.inputs.length > 0 ? ( +
+

Hergestellt aus

+ +
+ ) : null} ([]); + const { data: rawLots } = useQuery({ + queryKey: ["raw-material-lots"], + queryFn: () => fetchRawMaterialLots(), + }); + const availableLots = (rawLots ?? []).filter((lot) => lot.remainingCount > 0); + const updateInput = ( + index: number, + change: Partial<{ rawMaterialLotId: string; grams: string }>, + ) => + setInputs((current) => + current.map((row, i) => (i === index ? { ...row, ...change } : row)), + ); const [error, setError] = useState(null); const { data: products } = useQuery({ queryKey: ["products", { status: "ACTIVE" }], queryFn: () => fetchProducts({ status: "ACTIVE" }), }); - const manufactured = (products ?? []).filter((product) => product.kind === "MANUFACTURED"); + const manufactured = (products ?? []).filter( + (product) => product.kind === "MANUFACTURED", + ); const selected = manufactured.find((product) => product.id === productId); const mutation = useMutation({ @@ -50,8 +73,16 @@ export default function NewProductionBatchPage() { lotNumber: lotNumber.trim(), date: new Date(date).toISOString(), quantity: Number(quantity), - bestBeforeDate: bestBeforeDate ? new Date(bestBeforeDate).toISOString() : undefined, + bestBeforeDate: bestBeforeDate + ? new Date(bestBeforeDate).toISOString() + : undefined, notes: notes.trim() || undefined, + inputs: inputs + .filter((row) => row.rawMaterialLotId && Number(row.grams) > 0) + .map((row) => ({ + rawMaterialLotId: row.rawMaterialLotId, + quantity: Number(row.grams), + })), }), onSuccess: (batch) => { void queryClient.invalidateQueries({ queryKey: ["production-batches"] }); @@ -59,7 +90,8 @@ export default function NewProductionBatchPage() { router.push(`/production-batches/${batch.id}`); }, onError: (mutationError: Error) => { - const message = mutationError.message || "Charge konnte nicht angelegt werden"; + const message = + mutationError.message || "Charge konnte nicht angelegt werden"; setError(message); toast.error(message); }, @@ -84,12 +116,20 @@ export default function NewProductionBatchPage() { /> -
+
- setProductId(value ?? "")} + > - {selected?.name} + + {selected?.name} + {manufactured.map((product) => ( @@ -101,7 +141,8 @@ export default function NewProductionBatchPage() { {products && manufactured.length === 0 ? (

- Noch kein Artikel der Art „Hergestellte Ware“. Lege ihn unter Produkte an. + Noch kein Artikel der Art „Hergestellte Ware“. + Lege ihn unter Produkte an.

) : null}
@@ -138,7 +179,9 @@ export default function NewProductionBatchPage() { />
- + setBestBeforeDate(event.target.value)} />
+
+ + Verbrauchte Rohstoffe (optional) + +

+ Wird beim Anlegen vom Rohstoff abgebucht. So lässt sich später + nachverfolgen, aus welchem Wachs oder Propolis diese Charge + stammt. +

+ {inputs.map((row, index) => { + const lot = availableLots.find( + (candidate) => candidate.id === row.rawMaterialLotId, + ); + return ( +
+
+ + +
+
+ + + updateInput(index, { grams: event.target.value }) + } + /> +
+ +
+ ); + })} + + {rawLots && availableLots.length === 0 ? ( +

+ Kein Rohstoff mit Restbestand vorhanden. +

+ ) : null} +
-