feat: add year filter to Coding queue

Add jahr parameter to the coding queue API endpoint and frontend
filter, allowing admins to filter the coding queue by year.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
CCS Admin 2026-02-27 17:37:04 +00:00
parent d5b357b60d
commit 2e41242c9e
3 changed files with 27 additions and 8 deletions

View file

@ -19,6 +19,7 @@ router = APIRouter()
@router.get("/queue", response_model=CaseListResponse) @router.get("/queue", response_model=CaseListResponse)
def coding_queue( def coding_queue(
fallgruppe: str | None = Query(None), fallgruppe: str | None = Query(None),
jahr: int | None = Query(None),
page: int = Query(1, ge=1), page: int = Query(1, ge=1),
per_page: int = Query(50, ge=1, le=200), per_page: int = Query(50, ge=1, le=200),
db: Session = Depends(get_db), db: Session = Depends(get_db),
@ -26,9 +27,9 @@ def coding_queue(
): ):
"""Return cases that need coding (gutachten=True, gutachten_typ=NULL). """Return cases that need coding (gutachten=True, gutachten_typ=NULL).
Admin only. Supports optional fallgruppe filter and pagination. Admin only. Supports optional fallgruppe and jahr filters with pagination.
""" """
cases, total = get_coding_queue(db, fallgruppe, page, per_page) cases, total = get_coding_queue(db, fallgruppe, jahr, page, per_page)
return CaseListResponse( return CaseListResponse(
items=[CaseResponse.model_validate(c) for c in cases], items=[CaseResponse.model_validate(c) for c in cases],
total=total, total=total,

View file

@ -14,6 +14,7 @@ settings = get_settings()
def get_coding_queue( def get_coding_queue(
db: Session, db: Session,
fallgruppe: str | None = None, fallgruppe: str | None = None,
jahr: int | None = None,
page: int = 1, page: int = 1,
per_page: int = 50, per_page: int = 50,
) -> tuple[list[Case], int]: ) -> tuple[list[Case], int]:
@ -32,6 +33,8 @@ def get_coding_queue(
) )
if fallgruppe: if fallgruppe:
query = query.filter(Case.fallgruppe == fallgruppe) query = query.filter(Case.fallgruppe == fallgruppe)
if jahr:
query = query.filter(Case.jahr == jahr)
total = query.count() total = query.count()
cases = ( cases = (

View file

@ -48,6 +48,9 @@ function getInitialFormState(c: Case): CodingFormState {
} }
export function CodingPage() { export function CodingPage() {
const currentYear = new Date().getFullYear()
const years = Array.from({ length: 5 }, (_, i) => currentYear - i)
const [jahr, setJahr] = useState<string>('__all__')
const [fallgruppe, setFallgruppe] = useState<string>('__all__') const [fallgruppe, setFallgruppe] = useState<string>('__all__')
const [page, setPage] = useState(1) const [page, setPage] = useState(1)
const [data, setData] = useState<CaseListResponse | null>(null) const [data, setData] = useState<CaseListResponse | null>(null)
@ -63,6 +66,7 @@ export function CodingPage() {
setLoading(true) setLoading(true)
const params: Record<string, string | number> = { page, per_page: 50 } const params: Record<string, string | number> = { page, per_page: 50 }
if (fallgruppe !== '__all__') params.fallgruppe = fallgruppe if (fallgruppe !== '__all__') params.fallgruppe = fallgruppe
if (jahr !== '__all__') params.jahr = Number(jahr)
api.get<CaseListResponse>('/coding/queue', { params, signal: controller.signal }) api.get<CaseListResponse>('/coding/queue', { params, signal: controller.signal })
.then((res) => { .then((res) => {
@ -84,7 +88,7 @@ export function CodingPage() {
}) })
return () => controller.abort() return () => controller.abort()
}, [page, fallgruppe]) }, [page, fallgruppe, jahr])
const updateFormField = (caseId: number, field: keyof CodingFormState, value: unknown) => { const updateFormField = (caseId: number, field: keyof CodingFormState, value: unknown) => {
setFormStates((prev) => ({ setFormStates((prev) => ({
@ -136,9 +140,9 @@ export function CodingPage() {
? data.items.filter((c) => savedIds.has(c.id) || c.gutachten_typ !== null).length ? data.items.filter((c) => savedIds.has(c.id) || c.gutachten_typ !== null).length
: 0 : 0
const grandTotal = data?.total ?? 0 const grandTotal = data?.total ?? 0
const activeFilterLabel = fallgruppe !== '__all__' const activeFilters: string[] = []
? FALLGRUPPEN_LABELS[fallgruppe] || fallgruppe if (fallgruppe !== '__all__') activeFilters.push(FALLGRUPPEN_LABELS[fallgruppe] || fallgruppe)
: null if (jahr !== '__all__') activeFilters.push(jahr)
return ( return (
<div className="p-6 space-y-4"> <div className="p-6 space-y-4">
@ -148,8 +152,8 @@ export function CodingPage() {
{!loading && data && ( {!loading && data && (
<p className="text-sm text-muted-foreground mt-0.5"> <p className="text-sm text-muted-foreground mt-0.5">
{grandTotal} {grandTotal === 1 ? 'Fall' : 'Fälle'} offen {grandTotal} {grandTotal === 1 ? 'Fall' : 'Fälle'} offen
{activeFilterLabel && ( {activeFilters.length > 0 && (
<> gefiltert nach <span className="font-medium text-foreground">{activeFilterLabel}</span></> <> gefiltert nach <span className="font-medium text-foreground">{activeFilters.join(', ')}</span></>
)} )}
</p> </p>
)} )}
@ -158,6 +162,17 @@ export function CodingPage() {
<Badge variant="outline" className="text-sm py-1 px-3"> <Badge variant="outline" className="text-sm py-1 px-3">
{codedCount} / {data?.items.length ?? 0} auf dieser Seite codiert {codedCount} / {data?.items.length ?? 0} auf dieser Seite codiert
</Badge> </Badge>
<Select value={jahr} onValueChange={(v) => { setJahr(v); setPage(1) }}>
<SelectTrigger className="w-[130px]">
<SelectValue placeholder="Jahr" />
</SelectTrigger>
<SelectContent>
<SelectItem value="__all__">Alle Jahre</SelectItem>
{years.map((y) => (
<SelectItem key={y} value={String(y)}>{y}</SelectItem>
))}
</SelectContent>
</Select>
<Select value={fallgruppe} onValueChange={(v) => { setFallgruppe(v); setPage(1) }}> <Select value={fallgruppe} onValueChange={(v) => { setFallgruppe(v); setPage(1) }}>
<SelectTrigger className="w-[180px]"> <SelectTrigger className="w-[180px]">
<SelectValue placeholder="Fallgruppe" /> <SelectValue placeholder="Fallgruppe" />