mirror of
https://github.com/complexcaresolutions/dak.c2s.git
synced 2026-03-17 18:23:42 +00:00
refactor: migrate CasesPage and useInlineEdit to TanStack Query
Replace manual useEffect data fetching with useCases/usePendingIcdCases query hooks. Replace direct API calls in useInlineEdit with useCaseUpdate and useKvnrUpdate mutations. Use useIcdUpdate for ICD saving. Remove onCaseSaved callback prop drilling — mutations auto-invalidate the cache. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
150be9183c
commit
657a1abcaf
3 changed files with 105 additions and 68 deletions
70
frontend/src/hooks/useCases.ts
Normal file
70
frontend/src/hooks/useCases.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import api from '@/services/api'
|
||||
import type { Case, CaseListResponse } from '@/types'
|
||||
|
||||
export interface CaseFilters {
|
||||
page: number
|
||||
per_page: number
|
||||
search?: string
|
||||
jahr?: number
|
||||
fallgruppe?: string
|
||||
has_icd?: string
|
||||
}
|
||||
|
||||
export function useCases(filters: CaseFilters, options?: { enabled?: boolean }) {
|
||||
return useQuery({
|
||||
queryKey: ['cases', filters],
|
||||
queryFn: () => {
|
||||
const params: Record<string, string | number> = {
|
||||
page: filters.page,
|
||||
per_page: filters.per_page,
|
||||
}
|
||||
if (filters.search) params.search = filters.search
|
||||
if (filters.jahr) params.jahr = filters.jahr
|
||||
if (filters.fallgruppe) params.fallgruppe = filters.fallgruppe
|
||||
if (filters.has_icd) params.has_icd = filters.has_icd
|
||||
return api.get<CaseListResponse>('/cases/', { params }).then(r => r.data)
|
||||
},
|
||||
enabled: options?.enabled ?? true,
|
||||
})
|
||||
}
|
||||
|
||||
export function usePendingIcdCases(page: number, perPage: number, options?: { enabled?: boolean }) {
|
||||
return useQuery({
|
||||
queryKey: ['cases', 'pending-icd', { page, per_page: perPage }],
|
||||
queryFn: () => api.get<CaseListResponse>('/cases/pending-icd', {
|
||||
params: { page, per_page: perPage },
|
||||
}).then(r => r.data),
|
||||
enabled: options?.enabled ?? true,
|
||||
})
|
||||
}
|
||||
|
||||
export function useCaseUpdate() {
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: ({ id, data }: { id: number; data: Record<string, unknown> }) =>
|
||||
api.put<Case>(`/cases/${id}`, data).then(r => r.data),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['cases'] }),
|
||||
})
|
||||
}
|
||||
|
||||
export function useKvnrUpdate() {
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: ({ id, kvnr }: { id: number; kvnr: string | null }) =>
|
||||
api.put<Case>(`/cases/${id}/kvnr`, { kvnr }).then(r => r.data),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['cases'] }),
|
||||
})
|
||||
}
|
||||
|
||||
export function useIcdUpdate() {
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: ({ id, icd }: { id: number; icd: string }) =>
|
||||
api.put<Case>(`/cases/${id}/icd`, { icd }).then(r => r.data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['cases'] })
|
||||
queryClient.invalidateQueries({ queryKey: ['dashboard'] })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
@ -2,9 +2,9 @@ import { useState, useEffect, useCallback, useRef } from 'react'
|
|||
import {
|
||||
Search, ChevronLeft, ChevronRight, Save, CheckCircle, XCircle, Pencil, X,
|
||||
} from 'lucide-react'
|
||||
import api from '@/services/api'
|
||||
import type { Case, CaseListResponse } from '@/types'
|
||||
import type { Case } from '@/types'
|
||||
import { useAuth } from '@/context/AuthContext'
|
||||
import { useCases, usePendingIcdCases, useIcdUpdate, type CaseFilters } from '@/hooks/useCases'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
|
|
@ -62,8 +62,6 @@ export function CasesPage({ pendingIcdOnly = false }: CasesPageProps) {
|
|||
const [hasIcd, setHasIcd] = useState<string>('__all__')
|
||||
const [page, setPage] = useState(1)
|
||||
const [perPage] = useState(50)
|
||||
const [data, setData] = useState<CaseListResponse | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [selectedCase, setSelectedCase] = useState<Case | null>(null)
|
||||
const [sheetOpen, setSheetOpen] = useState(false)
|
||||
|
||||
|
|
@ -84,30 +82,20 @@ export function CasesPage({ pendingIcdOnly = false }: CasesPageProps) {
|
|||
}
|
||||
}, [])
|
||||
|
||||
// Fetch cases
|
||||
useEffect(() => {
|
||||
setLoading(true)
|
||||
// Fetch cases via TanStack Query
|
||||
const filters: CaseFilters = {
|
||||
page, per_page: perPage,
|
||||
...(debouncedSearch ? { search: debouncedSearch } : {}),
|
||||
...(jahr !== '__all__' ? { jahr: Number(jahr) } : {}),
|
||||
...(fallgruppe !== '__all__' ? { fallgruppe } : {}),
|
||||
...(hasIcd !== '__all__' ? { has_icd: hasIcd } : {}),
|
||||
}
|
||||
|
||||
if (pendingIcdOnly) {
|
||||
api.get<CaseListResponse>('/cases/pending-icd', {
|
||||
params: { page, per_page: perPage },
|
||||
})
|
||||
.then((res) => setData(res.data))
|
||||
.catch(() => setData(null))
|
||||
.finally(() => setLoading(false))
|
||||
} else {
|
||||
const params: Record<string, string | number> = { page, per_page: perPage }
|
||||
if (debouncedSearch) params.search = debouncedSearch
|
||||
if (jahr !== '__all__') params.jahr = Number(jahr)
|
||||
if (fallgruppe !== '__all__') params.fallgruppe = fallgruppe
|
||||
if (hasIcd !== '__all__') params.has_icd = hasIcd
|
||||
|
||||
api.get<CaseListResponse>('/cases/', { params })
|
||||
.then((res) => setData(res.data))
|
||||
.catch(() => setData(null))
|
||||
.finally(() => setLoading(false))
|
||||
}
|
||||
}, [page, perPage, debouncedSearch, jahr, fallgruppe, hasIcd, pendingIcdOnly])
|
||||
const casesQuery = useCases(filters, { enabled: !pendingIcdOnly })
|
||||
const pendingQuery = usePendingIcdCases(page, perPage, { enabled: pendingIcdOnly })
|
||||
const activeQuery = pendingIcdOnly ? pendingQuery : casesQuery
|
||||
const data = activeQuery.data ?? null
|
||||
const loading = activeQuery.isLoading
|
||||
|
||||
const totalPages = data ? Math.ceil(data.total / perPage) : 0
|
||||
const years = Array.from({ length: 5 }, (_, i) => currentYear - i)
|
||||
|
|
@ -117,19 +105,6 @@ export function CasesPage({ pendingIcdOnly = false }: CasesPageProps) {
|
|||
setSheetOpen(true)
|
||||
}
|
||||
|
||||
const handleCaseSaved = (updated: Case) => {
|
||||
setSelectedCase(updated)
|
||||
// Refresh list
|
||||
setData((prev) =>
|
||||
prev
|
||||
? {
|
||||
...prev,
|
||||
items: prev.items.map((c) => (c.id === updated.id ? updated : c)),
|
||||
}
|
||||
: prev,
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6 space-y-4">
|
||||
<h1 className="text-2xl font-bold">
|
||||
|
|
@ -286,10 +261,7 @@ export function CasesPage({ pendingIcdOnly = false }: CasesPageProps) {
|
|||
<Sheet open={sheetOpen} onOpenChange={setSheetOpen}>
|
||||
<SheetContent className="w-full sm:max-w-lg overflow-y-auto">
|
||||
{selectedCase && (
|
||||
<CaseDetail
|
||||
caseData={selectedCase}
|
||||
onCaseSaved={handleCaseSaved}
|
||||
/>
|
||||
<CaseDetail caseData={selectedCase} />
|
||||
)}
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
|
|
@ -309,22 +281,16 @@ function StatusBadges({ c }: { c: Case }) {
|
|||
)
|
||||
}
|
||||
|
||||
function CaseDetail({
|
||||
caseData,
|
||||
onCaseSaved,
|
||||
}: {
|
||||
caseData: Case
|
||||
onCaseSaved: (updated: Case) => void
|
||||
}) {
|
||||
function CaseDetail({ caseData }: { caseData: Case }) {
|
||||
const { isAdmin } = useAuth()
|
||||
const {
|
||||
editing, formValues, saving, error, success,
|
||||
startEditing, cancelEditing, saveAll, setFieldValue, canEditField, isDirty,
|
||||
} = useInlineEdit(caseData, onCaseSaved)
|
||||
} = useInlineEdit(caseData)
|
||||
|
||||
// ICD has its own endpoint and state
|
||||
const icdMutation = useIcdUpdate()
|
||||
const [icdValue, setIcdValue] = useState(caseData.icd || '')
|
||||
const [icdSaving, setIcdSaving] = useState(false)
|
||||
const [icdError, setIcdError] = useState('')
|
||||
const [icdSuccess, setIcdSuccess] = useState(false)
|
||||
|
||||
|
|
@ -336,17 +302,13 @@ function CaseDetail({
|
|||
|
||||
const saveIcd = async () => {
|
||||
if (!icdValue.trim()) return
|
||||
setIcdSaving(true)
|
||||
setIcdError('')
|
||||
setIcdSuccess(false)
|
||||
try {
|
||||
const res = await api.put<Case>(`/cases/${caseData.id}/icd`, { icd: icdValue.trim() })
|
||||
onCaseSaved(res.data)
|
||||
await icdMutation.mutateAsync({ id: caseData.id, icd: icdValue.trim() })
|
||||
setIcdSuccess(true)
|
||||
} catch {
|
||||
setIcdError('Fehler beim Speichern des ICD-Codes.')
|
||||
} finally {
|
||||
setIcdSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -456,10 +418,10 @@ function CaseDetail({
|
|||
<Button
|
||||
size="sm"
|
||||
onClick={saveIcd}
|
||||
disabled={icdSaving || !icdValue.trim()}
|
||||
disabled={icdMutation.isPending || !icdValue.trim()}
|
||||
>
|
||||
<Save className="size-4 mr-1" />
|
||||
{icdSaving ? 'Speichern...' : 'Speichern'}
|
||||
{icdMutation.isPending ? 'Speichern...' : 'Speichern'}
|
||||
</Button>
|
||||
</div>
|
||||
{icdError && (
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useState, useCallback, useEffect } from 'react'
|
||||
import api from '@/services/api'
|
||||
import type { Case } from '@/types'
|
||||
import { useCaseUpdate, useKvnrUpdate } from '@/hooks/useCases'
|
||||
import { CASE_SECTIONS, type FieldConfig } from './fieldConfig'
|
||||
|
||||
type FormValues = Record<string, unknown>
|
||||
|
|
@ -43,8 +43,10 @@ function getDirtyFields(original: FormValues, current: FormValues): FormValues {
|
|||
|
||||
export function useInlineEdit(
|
||||
caseData: Case,
|
||||
onSaved: (updated: Case) => void,
|
||||
): UseInlineEditReturn {
|
||||
const caseUpdateMutation = useCaseUpdate()
|
||||
const kvnrUpdateMutation = useKvnrUpdate()
|
||||
|
||||
const [editing, setEditing] = useState(false)
|
||||
const [formValues, setFormValues] = useState<FormValues>(() => extractFormValues(caseData))
|
||||
const [originalValues, setOriginalValues] = useState<FormValues>(() => extractFormValues(caseData))
|
||||
|
|
@ -101,10 +103,11 @@ export function useInlineEdit(
|
|||
// Split: KVNR goes to its own endpoint (accessible by all users)
|
||||
if ('kvnr' in dirty) {
|
||||
const kvnrVal = dirty.kvnr as string | null
|
||||
const res = await api.put<Case>(`/cases/${caseData.id}/kvnr`, {
|
||||
const kvnrResult = await kvnrUpdateMutation.mutateAsync({
|
||||
id: caseData.id,
|
||||
kvnr: kvnrVal?.trim() || null,
|
||||
})
|
||||
lastResult = res.data
|
||||
lastResult = kvnrResult
|
||||
delete dirty.kvnr
|
||||
}
|
||||
|
||||
|
|
@ -119,12 +122,14 @@ export function useInlineEdit(
|
|||
payload[key] = value
|
||||
}
|
||||
}
|
||||
const res = await api.put<Case>(`/cases/${caseData.id}`, payload)
|
||||
lastResult = res.data
|
||||
const updateResult = await caseUpdateMutation.mutateAsync({
|
||||
id: caseData.id,
|
||||
data: payload,
|
||||
})
|
||||
lastResult = updateResult
|
||||
}
|
||||
|
||||
if (lastResult) {
|
||||
onSaved(lastResult)
|
||||
setSuccess(true)
|
||||
setEditing(false)
|
||||
}
|
||||
|
|
@ -138,7 +143,7 @@ export function useInlineEdit(
|
|||
} finally {
|
||||
setSaving(false)
|
||||
}
|
||||
}, [caseData.id, formValues, originalValues, onSaved])
|
||||
}, [caseData.id, formValues, originalValues, caseUpdateMutation, kvnrUpdateMutation])
|
||||
|
||||
const canEditField = useCallback(
|
||||
(field: FieldConfig, isAdmin: boolean): boolean => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue