fix: improve CodingPage filter UX and prevent race conditions

- Show grand total count (data.total) instead of items.length
- Display active fallgruppe filter label prominently
- Add AbortController to cancel stale requests on rapid filter changes
- Clarify badge to show "X / Y auf dieser Seite codiert"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
CCS Admin 2026-02-27 17:28:23 +00:00
parent 35b9d5a7c9
commit d5b357b60d

View file

@ -59,11 +59,12 @@ export function CodingPage() {
// Fetch coding queue
useEffect(() => {
const controller = new AbortController()
setLoading(true)
const params: Record<string, string | number> = { page, per_page: 50 }
if (fallgruppe !== '__all__') params.fallgruppe = fallgruppe
api.get<CaseListResponse>('/coding/queue', { params })
api.get<CaseListResponse>('/coding/queue', { params, signal: controller.signal })
.then((res) => {
setData(res.data)
// Initialize form states for each case
@ -75,8 +76,14 @@ export function CodingPage() {
setSavedIds(new Set())
setErrors({})
})
.catch(() => setData(null))
.finally(() => setLoading(false))
.catch(() => {
if (!controller.signal.aborted) setData(null)
})
.finally(() => {
if (!controller.signal.aborted) setLoading(false)
})
return () => controller.abort()
}, [page, fallgruppe])
const updateFormField = (caseId: number, field: keyof CodingFormState, value: unknown) => {
@ -124,19 +131,32 @@ export function CodingPage() {
}
}
// Count coded cases
// Count coded cases on current page
const codedCount = data
? data.items.filter((c) => savedIds.has(c.id) || c.gutachten_typ !== null).length
: 0
const totalCount = data?.items.length ?? 0
const grandTotal = data?.total ?? 0
const activeFilterLabel = fallgruppe !== '__all__'
? FALLGRUPPEN_LABELS[fallgruppe] || fallgruppe
: null
return (
<div className="p-6 space-y-4">
<div className="flex flex-wrap items-center justify-between gap-4">
<h1 className="text-2xl font-bold">Coding-Warteschlange</h1>
<div>
<h1 className="text-2xl font-bold">Coding-Warteschlange</h1>
{!loading && data && (
<p className="text-sm text-muted-foreground mt-0.5">
{grandTotal} {grandTotal === 1 ? 'Fall' : 'Fälle'} offen
{activeFilterLabel && (
<> gefiltert nach <span className="font-medium text-foreground">{activeFilterLabel}</span></>
)}
</p>
)}
</div>
<div className="flex items-center gap-3">
<Badge variant="outline" className="text-sm py-1 px-3">
{codedCount} von {totalCount} Fälle codiert
{codedCount} / {data?.items.length ?? 0} auf dieser Seite codiert
</Badge>
<Select value={fallgruppe} onValueChange={(v) => { setFallgruppe(v); setPage(1) }}>
<SelectTrigger className="w-[180px]">
@ -152,11 +172,11 @@ export function CodingPage() {
</div>
{/* Progress bar */}
{data && totalCount > 0 && (
{data && grandTotal > 0 && (
<div className="h-2 w-full rounded-full bg-muted overflow-hidden">
<div
className="h-full rounded-full bg-primary transition-all duration-300"
style={{ width: `${(codedCount / totalCount) * 100}%` }}
style={{ width: `${(codedCount / (data.items.length || 1)) * 100}%` }}
/>
</div>
)}