From d70558f1243ad39ad7915f6cbe65e3c5381aa985 Mon Sep 17 00:00:00 2001 From: patrick Date: Fri, 4 Sep 2026 09:04:20 +0200 Subject: [PATCH] =?UTF-8?q?Inline-Nachf=C3=BCll-Dialog=20nach=20Abweichung?= =?UTF-8?q?=20in=20der=20Kontrolle=20(Karte=2007)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt --- DEVLOG.md | 65 +++++++++++++++++++ frontend/src/offline/queue.ts | 18 ++++- frontend/src/pages/KontrollPage.tsx | 29 +++++++-- .../src/pages/kontrolle/NachfuellDialog.tsx | 36 ++++++++++ frontend/src/pages/kontrolle/PositionCard.tsx | 4 +- frontend/src/pages/kontrolle/useKontrolle.ts | 32 ++++++++- 6 files changed, 173 insertions(+), 11 deletions(-) create mode 100644 frontend/src/pages/kontrolle/NachfuellDialog.tsx diff --git a/DEVLOG.md b/DEVLOG.md index 7981c00..1730f61 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -1986,3 +1986,68 @@ Keine Commits in dieser Session. - frontend/src/pages/kontrolle/useKontrolle.ts | 19 +++++++++++++------ --- +## 2026-09-04 08:53 – 08:54 (1m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +- db245c5 PWA: Passt-Button, Fortschrittsanzeige, Ablaufdatum-Farbcodierung in Kontrolle + +### Geänderte Dateien +- DEVLOG.md | 279 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- frontend/src/pages/KontrollPage.tsx | 19 +++++ +- frontend/src/pages/kontrolle/PositionCard.tsx | 14 +++- + +--- +## 2026-09-04 08:54 – 08:55 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 279 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- frontend/src/pages/KontrollPage.tsx | 19 +++++ +- frontend/src/pages/kontrolle/PositionCard.tsx | 14 +++- + +--- +## 2026-09-04 08:55 – 08:55 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 279 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- frontend/src/pages/KontrollPage.tsx | 19 +++++ +- frontend/src/pages/kontrolle/PositionCard.tsx | 14 +++- + +--- +## 2026-09-04 08:55 – 08:59 (3m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 279 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- frontend/src/pages/KontrollPage.tsx | 19 +++++ +- frontend/src/pages/kontrolle/PositionCard.tsx | 14 +++- + +--- +## 2026-09-04 08:55 – 08:59 (3m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 279 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- frontend/src/pages/KontrollPage.tsx | 19 +++++ +- frontend/src/pages/kontrolle/PositionCard.tsx | 14 +++- + +--- diff --git a/frontend/src/offline/queue.ts b/frontend/src/offline/queue.ts index 4fc9289..b4e617e 100644 --- a/frontend/src/offline/queue.ts +++ b/frontend/src/offline/queue.ts @@ -31,6 +31,18 @@ function keyFor(kontrolleId: string, materialId: number): string { return `${kontrolleId}:${materialId}`; } +// Karte 07 (Sofort-Nachfüllung): die PWA soll direkt nach einer Abweichung +// fragen können "jetzt nachfüllen?", ohne ins Dashboard zu wechseln. Der +// Queue-Eintrag verschwindet bei Erfolg (siehe sendeEintrag), daher hier ein +// einmaliger Callback pro Key statt dauerhaftem State - reicht für den +// unmittelbaren Dialog in derselben Sitzung. +type ErgebnisListener = (fehlbestandId: string | null, abweichung: boolean) => void; +const ergebnisListener = new Map(); + +export function aufErgebnisWarten(kontrolleId: string, materialId: number, listener: ErgebnisListener): void { + ergebnisListener.set(keyFor(kontrolleId, materialId), listener); +} + export async function enqueue( kontrolleId: string, materialId: number, @@ -88,7 +100,11 @@ async function sendeEintrag(item: QueueItem): Promise { // Erfolgreich übertragen (idempotent - ein erneutes Senden desselben Werts // durch die Queue vor einem Absturz führt zu keinem doppelten Fehlbestand, // siehe Backend app/services/kontrolle.py erfasse_position) -> aus Queue entfernen. - void ergebnis; + const listener = ergebnisListener.get(item.key); + if (listener) { + listener(ergebnis.fehlbestand_id, ergebnis.abweichung); + ergebnisListener.delete(item.key); + } await del(item.key, store); } catch (error) { if (error instanceof ApiError && error.status >= 400 && error.status < 500) { diff --git a/frontend/src/pages/KontrollPage.tsx b/frontend/src/pages/KontrollPage.tsx index a5f135f..392f009 100644 --- a/frontend/src/pages/KontrollPage.tsx +++ b/frontend/src/pages/KontrollPage.tsx @@ -1,5 +1,6 @@ import { useNavigate, useParams } from "react-router-dom"; +import { NachfuellDialog } from "./kontrolle/NachfuellDialog"; import { PositionCard } from "./kontrolle/PositionCard"; import { useKontrolle } from "./kontrolle/useKontrolle"; @@ -15,12 +16,15 @@ export function KontrollPage() { wirdAbgeschlossen, abschlussFehler, alleUebertragen, + nachfuellDialog, starteKontrolle, eingabeAendern, positionSenden, fehlerErneutVersuchen, abschliessen, abbrechen, + jetztNachfuellen, + nachfuellDialogSchliessen, } = useKontrolle(objektId); async function handleAbschliessen() { @@ -91,13 +95,24 @@ export function KontrollPage() { )}
    {positionen.map((zustand) => ( - +
  • + + {nachfuellDialog?.materialId === zustand.position.material_id && ( + jetztNachfuellen(nachfuellDialog.fehlbestandId, menge)} + onSchliessen={nachfuellDialogSchliessen} + /> + )} +
  • ))}
diff --git a/frontend/src/pages/kontrolle/NachfuellDialog.tsx b/frontend/src/pages/kontrolle/NachfuellDialog.tsx new file mode 100644 index 0000000..d9c6afb --- /dev/null +++ b/frontend/src/pages/kontrolle/NachfuellDialog.tsx @@ -0,0 +1,36 @@ +import { useState } from "react"; + +interface Props { + materialName: string; + vorschlagMenge: string; + onNachfuellen: (menge: string) => void; + onSchliessen: () => void; +} + +/** Karte 07: nach einer Abweichung direkt "jetzt nachfüllen?" statt Umweg über Dashboard. */ +export function NachfuellDialog({ materialName, vorschlagMenge, onNachfuellen, onSchliessen }: Props) { + const [menge, setMenge] = useState(vorschlagMenge); + + return ( +
+
+ Fehlmenge bei {materialName} – jetzt aus Lager nachfüllen? +
+
+ setMenge(e.target.value)} + style={{ width: "6rem" }} + /> + + +
+
+ ); +} diff --git a/frontend/src/pages/kontrolle/PositionCard.tsx b/frontend/src/pages/kontrolle/PositionCard.tsx index e2a3cb2..1f2c1f0 100644 --- a/frontend/src/pages/kontrolle/PositionCard.tsx +++ b/frontend/src/pages/kontrolle/PositionCard.tsx @@ -26,7 +26,7 @@ export function PositionCard({ zustand, onEingabeAendern, onSenden, onErneutVers const passt = zustand.eingabe === zustand.position.sollmenge_effektiv; return ( -
  • +
    {zustand.material?.name ?? `Material ${zustand.position.material_id}`} @@ -90,6 +90,6 @@ export function PositionCard({ zustand, onEingabeAendern, onSenden, onErneutVers
    )} -
  • + ); } diff --git a/frontend/src/pages/kontrolle/useKontrolle.ts b/frontend/src/pages/kontrolle/useKontrolle.ts index d0450bb..14f4c01 100644 --- a/frontend/src/pages/kontrolle/useKontrolle.ts +++ b/frontend/src/pages/kontrolle/useKontrolle.ts @@ -2,7 +2,13 @@ import { useEffect, useMemo, useState } from "react"; import { apiRequest, ApiError } from "../../api/client"; import type { Kontrolle, Material, Objekt, Objektposition } from "../../api/types"; -import { enqueue, eintraegeFuerKontrolle, entferneFehlerEintrag, synchronisiereQueue } from "../../offline/queue"; +import { + aufErgebnisWarten, + enqueue, + eintraegeFuerKontrolle, + entferneFehlerEintrag, + synchronisiereQueue, +} from "../../offline/queue"; import { useOnlineStatus } from "../../offline/useOnlineStatus"; import type { PositionZustand, Sperrmeldung } from "./types"; @@ -22,6 +28,9 @@ export function useKontrolle(objektId: string | undefined) { const [startFehler, setStartFehler] = useState(null); const [wirdAbgeschlossen, setWirdAbgeschlossen] = useState(false); const [abschlussFehler, setAbschlussFehler] = useState(null); + const [nachfuellDialog, setNachfuellDialog] = useState<{ materialId: number; fehlbestandId: string } | null>( + null + ); useEffect(() => { if (!objektId) return; @@ -113,6 +122,11 @@ export function useKontrolle(objektId: string | undefined) { async function positionSenden(materialId: number, zustand: PositionZustand) { if (!kontrolle) return; + aufErgebnisWarten(kontrolle.id, materialId, (fehlbestandId, abweichung) => { + if (abweichung && fehlbestandId) { + setNachfuellDialog({ materialId, fehlbestandId }); + } + }); await enqueue(kontrolle.id, materialId, zustand.eingabe, { ablaufdatum: zustand.ablaufdatum || null, chargennummer: zustand.chargennummer || null, @@ -124,6 +138,19 @@ export function useKontrolle(objektId: string | undefined) { void synchronisiereQueue(); } + /** Karte 07: Sofort-Nachfüllung direkt aus dem Dialog nach einer Abweichung. */ + async function jetztNachfuellen(fehlbestandId: string, menge: string) { + await apiRequest(`/fehlbestaende/${fehlbestandId}/nachfuellungen`, { + method: "POST", + body: { menge }, + }); + setNachfuellDialog(null); + } + + function nachfuellDialogSchliessen() { + setNachfuellDialog(null); + } + async function fehlerErneutVersuchen(materialId: number, zustand: PositionZustand) { if (!kontrolle) return; await entferneFehlerEintrag(kontrolle.id, materialId); @@ -168,11 +195,14 @@ export function useKontrolle(objektId: string | undefined) { wirdAbgeschlossen, abschlussFehler, alleUebertragen, + nachfuellDialog, starteKontrolle, eingabeAendern, positionSenden, fehlerErneutVersuchen, abschliessen, abbrechen, + jetztNachfuellen, + nachfuellDialogSchliessen, }; }