26 KiB
State Management Refactoring — Implementation Plan
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Introduce TanStack Query for server-state management to eliminate boilerplate and add caching/invalidation across all pages.
Architecture: Add @tanstack/react-query with a QueryClientProvider wrapping the app. Create domain-specific hooks (useCases, useDashboard, etc.) that encapsulate query keys + API calls. Migrate pages from useState+useEffect to useQuery/useMutation. Existing AuthContext and UI-state patterns remain unchanged.
Tech Stack: React 19, TanStack Query v5, TypeScript, Vite, Axios
Task 1: Install TanStack Query and add QueryClientProvider
Files:
- Modify:
frontend/package.json - Modify:
frontend/src/App.tsx
Step 1: Install dependency
Run: cd /home/frontend/dak_c2s/frontend && pnpm add @tanstack/react-query
Step 2: Add QueryClientProvider to App.tsx
Replace the contents of frontend/src/App.tsx with:
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { AuthProvider } from '@/context/AuthContext'
import { ProtectedRoute } from '@/components/layout/ProtectedRoute'
import { AppLayout } from '@/components/layout/AppLayout'
import { LoginPage } from '@/pages/LoginPage'
import { RegisterPage } from '@/pages/RegisterPage'
import { DashboardPage } from '@/pages/DashboardPage'
import { CasesPage } from '@/pages/CasesPage'
import { ImportPage } from '@/pages/ImportPage'
import { IcdPage } from '@/pages/IcdPage'
import { CodingPage } from '@/pages/CodingPage'
import { ReportsPage } from '@/pages/ReportsPage'
import { AdminUsersPage } from '@/pages/AdminUsersPage'
import { AdminInvitationsPage } from '@/pages/AdminInvitationsPage'
import { AdminAuditPage } from '@/pages/AdminAuditPage'
import { DisclosuresPage } from '@/pages/DisclosuresPage'
import { AccountPage } from '@/pages/AccountPage'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 30_000,
retry: 1,
refetchOnWindowFocus: false,
},
},
})
function App() {
return (
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<AuthProvider>
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/register" element={<RegisterPage />} />
<Route path="/" element={<ProtectedRoute><AppLayout /></ProtectedRoute>}>
<Route index element={<Navigate to="/dashboard" replace />} />
<Route path="dashboard" element={<DashboardPage />} />
<Route path="cases" element={<CasesPage />} />
<Route path="import" element={<ProtectedRoute requireAdmin><ImportPage /></ProtectedRoute>} />
<Route path="icd" element={<IcdPage />} />
<Route path="coding" element={<ProtectedRoute requireAdmin><CodingPage /></ProtectedRoute>} />
<Route path="reports" element={<ReportsPage />} />
<Route path="account" element={<AccountPage />} />
<Route path="admin/users" element={<ProtectedRoute requireAdmin><AdminUsersPage /></ProtectedRoute>} />
<Route path="admin/invitations" element={<ProtectedRoute requireAdmin><AdminInvitationsPage /></ProtectedRoute>} />
<Route path="admin/audit" element={<ProtectedRoute requireAdmin><AdminAuditPage /></ProtectedRoute>} />
<Route path="admin/disclosures" element={<ProtectedRoute requireAdmin><DisclosuresPage /></ProtectedRoute>} />
</Route>
</Routes>
</AuthProvider>
</BrowserRouter>
</QueryClientProvider>
)
}
export default App
Step 3: Verify build
Run: cd /home/frontend/dak_c2s/frontend && pnpm build
Expected: Build succeeds without errors.
Step 4: Commit
cd /home/frontend/dak_c2s
git add frontend/package.json frontend/pnpm-lock.yaml frontend/src/App.tsx
git commit -m "feat: add TanStack Query with QueryClientProvider"
Task 2: Migrate DashboardPage
Files:
- Create:
frontend/src/hooks/useDashboard.ts - Modify:
frontend/src/pages/DashboardPage.tsx
Step 1: Create useDashboard hook
Create frontend/src/hooks/useDashboard.ts:
import { useQuery } from '@tanstack/react-query'
import api from '@/services/api'
import type { DashboardResponse } from '@/types'
export function useDashboard(jahr: number) {
return useQuery({
queryKey: ['dashboard', jahr],
queryFn: () => api.get<DashboardResponse>('/reports/dashboard', { params: { jahr } }).then(r => r.data),
})
}
Step 2: Update DashboardPage to use the hook
In frontend/src/pages/DashboardPage.tsx, replace the state + useEffect block (lines 33-43):
Remove:
const [data, setData] = useState<DashboardResponse | null>(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
setLoading(true)
api.get<DashboardResponse>('/reports/dashboard', { params: { jahr } })
.then((res) => setData(res.data))
.catch(() => setData(null))
.finally(() => setLoading(false))
}, [jahr])
Replace with:
const { data, isLoading: loading } = useDashboard(jahr)
Update imports: remove useEffect from react import, remove api import, add:
import { useDashboard } from '@/hooks/useDashboard'
Keep useState for jahr (UI state, not server state).
Step 3: Verify build
Run: cd /home/frontend/dak_c2s/frontend && pnpm build
Step 4: Commit
cd /home/frontend/dak_c2s
git add frontend/src/hooks/useDashboard.ts frontend/src/pages/DashboardPage.tsx
git commit -m "refactor: migrate DashboardPage to TanStack Query"
Task 3: Migrate DisclosuresPage
Files:
- Create:
frontend/src/hooks/useDisclosures.ts - Modify:
frontend/src/pages/DisclosuresPage.tsx
Step 1: Create useDisclosures hook
Create frontend/src/hooks/useDisclosures.ts:
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { getDisclosureRequests, reviewDisclosure } from '@/services/disclosureService'
export function useDisclosures(status: string = 'pending') {
return useQuery({
queryKey: ['disclosures', status],
queryFn: () => getDisclosureRequests(status),
})
}
export function useReviewDisclosure() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: ({ id, status }: { id: number; status: 'approved' | 'rejected' }) =>
reviewDisclosure(id, status),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['disclosures'] })
queryClient.invalidateQueries({ queryKey: ['notifications'] })
},
})
}
Step 2: Rewrite DisclosuresPage
Replace the full content of frontend/src/pages/DisclosuresPage.tsx:
import { Check, X } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { Skeleton } from '@/components/ui/skeleton'
import type { DisclosureRequest } from '@/types'
import { useDisclosures, useReviewDisclosure } from '@/hooks/useDisclosures'
export function DisclosuresPage() {
const { data: requests = [], isLoading: loading } = useDisclosures('pending')
const reviewMutation = useReviewDisclosure()
const handleReview = (id: number, status: 'approved' | 'rejected') => {
reviewMutation.mutate({ id, status })
}
return (
<div className="p-6 space-y-4">
<h1 className="text-2xl font-bold">Freigabe-Anfragen</h1>
{loading ? (
<div className="space-y-2">
{[1, 2, 3].map((i) => (
<Skeleton key={i} className="h-24 w-full" />
))}
</div>
) : requests.length === 0 ? (
<p className="text-muted-foreground">Keine offenen Anfragen.</p>
) : (
<div className="space-y-3">
{requests.map((dr: DisclosureRequest) => (
<Card key={dr.id}>
<CardHeader className="pb-2">
<div className="flex items-center justify-between">
<CardTitle className="text-base">
Fall {dr.fall_id || dr.case_id}
</CardTitle>
<Badge variant="outline">{dr.status}</Badge>
</div>
</CardHeader>
<CardContent className="space-y-2">
<p className="text-sm">
<span className="text-muted-foreground">Angefragt von:</span>{' '}
{dr.requester_username || `User #${dr.requester_id}`}
</p>
<p className="text-sm">
<span className="text-muted-foreground">Begründung:</span>{' '}
{dr.reason}
</p>
<p className="text-xs text-muted-foreground">
{new Date(dr.created_at).toLocaleString('de-DE')}
</p>
<div className="flex gap-2 pt-1">
<Button
size="sm"
onClick={() => handleReview(dr.id, 'approved')}
disabled={reviewMutation.isPending}
>
<Check className="size-4 mr-1" />
Genehmigen
</Button>
<Button
size="sm"
variant="destructive"
onClick={() => handleReview(dr.id, 'rejected')}
disabled={reviewMutation.isPending}
>
<X className="size-4 mr-1" />
Ablehnen
</Button>
</div>
</CardContent>
</Card>
))}
</div>
)}
</div>
)
}
Step 3: Verify build
Run: cd /home/frontend/dak_c2s/frontend && pnpm build
Step 4: Commit
cd /home/frontend/dak_c2s
git add frontend/src/hooks/useDisclosures.ts frontend/src/pages/DisclosuresPage.tsx
git commit -m "refactor: migrate DisclosuresPage to TanStack Query"
Task 4: Migrate AdminAuditPage
Files:
- Create:
frontend/src/hooks/useAuditLog.ts - Modify:
frontend/src/pages/AdminAuditPage.tsx
Step 1: Create useAuditLog hook
Create frontend/src/hooks/useAuditLog.ts:
import { useQuery } from '@tanstack/react-query'
import api from '@/services/api'
import type { AuditLogEntry } from '@/types'
export interface AuditLogFilters {
skip: number
limit: number
user_id?: string
action?: string
date_from?: string
date_to?: string
}
export function useAuditLog(filters: AuditLogFilters) {
const params: Record<string, string | number> = {
skip: filters.skip,
limit: filters.limit,
}
if (filters.user_id) params.user_id = filters.user_id
if (filters.action) params.action = filters.action
if (filters.date_from) params.date_from = filters.date_from
if (filters.date_to) params.date_to = filters.date_to
return useQuery({
queryKey: ['audit-log', params],
queryFn: () => api.get<AuditLogEntry[]>('/admin/audit-log', { params }).then(r => r.data),
})
}
Step 2: Update AdminAuditPage
In frontend/src/pages/AdminAuditPage.tsx:
Remove: import api from '@/services/api'
Add: import { useAuditLog } from '@/hooks/useAuditLog'
Replace the state + fetch logic (lines 16-18 + 30-46) with:
Remove these lines:
const [entries, setEntries] = useState<AuditLogEntry[]>([])
const [loading, setLoading] = useState(true)
and the fetchEntries function and its useEffect.
Replace with:
const { data: entries = [], isLoading: loading, refetch } = useAuditLog({
skip, limit, user_id: filterUserId, action: filterAction,
date_from: filterDateFrom, date_to: filterDateTo,
})
Update handleFilter to call refetch() instead of fetchEntries().
Update resetFilters to call refetch() instead of setTimeout(fetchEntries, 0).
Remove useEffect from imports if no longer used. Keep useState for filter UI state and expandedIds.
Step 3: Verify build
Run: cd /home/frontend/dak_c2s/frontend && pnpm build
Step 4: Commit
cd /home/frontend/dak_c2s
git add frontend/src/hooks/useAuditLog.ts frontend/src/pages/AdminAuditPage.tsx
git commit -m "refactor: migrate AdminAuditPage to TanStack Query"
Task 5: Migrate AdminUsersPage
Files:
- Create:
frontend/src/hooks/useUsers.ts - Modify:
frontend/src/pages/AdminUsersPage.tsx
Step 1: Create useUsers hook
Create frontend/src/hooks/useUsers.ts:
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import api from '@/services/api'
import type { UserResponse, CreateUserPayload, UpdateUserPayload } from '@/types'
export function useUsers() {
return useQuery({
queryKey: ['users'],
queryFn: () => api.get<UserResponse[]>('/admin/users', { params: { skip: 0, limit: 200 } }).then(r => r.data),
})
}
export function useCreateUser() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (data: CreateUserPayload) => api.post<UserResponse>('/admin/users', data).then(r => r.data),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['users'] }),
})
}
export function useUpdateUser() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: ({ id, data }: { id: number; data: UpdateUserPayload }) =>
api.put<UserResponse>(`/admin/users/${id}`, data).then(r => r.data),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['users'] }),
})
}
Step 2: Update AdminUsersPage
In frontend/src/pages/AdminUsersPage.tsx:
Remove: import api from '@/services/api'
Add: import { useUsers, useCreateUser, useUpdateUser } from '@/hooks/useUsers'
Replace state + fetch (lines 23-24, 42-52):
// Remove:
const [users, setUsers] = useState<UserResponse[]>([])
const [loading, setLoading] = useState(true)
const fetchUsers = () => { ... }
useEffect(() => { fetchUsers() }, [])
// Replace with:
const { data: users = [], isLoading: loading } = useUsers()
const createMutation = useCreateUser()
const updateMutation = useUpdateUser()
Update handleCreate (lines 55-69):
const handleCreate = async () => {
setCreateError('')
try {
await createMutation.mutateAsync(createForm)
setCreateOpen(false)
setCreateForm({ username: '', email: '', password: '', role: 'dak_mitarbeiter' })
} catch (err: unknown) {
const msg = (err as { response?: { data?: { detail?: string } } })?.response?.data?.detail
setCreateError(msg || 'Fehler beim Erstellen des Benutzers.')
}
}
Remove creating state, use createMutation.isPending instead.
Update handleEdit (lines 79-94) similarly with updateMutation.mutateAsync({ id: editUser.id, data: editForm }).
Remove editing state, use updateMutation.isPending instead.
Step 3: Verify build
Run: cd /home/frontend/dak_c2s/frontend && pnpm build
Step 4: Commit
cd /home/frontend/dak_c2s
git add frontend/src/hooks/useUsers.ts frontend/src/pages/AdminUsersPage.tsx
git commit -m "refactor: migrate AdminUsersPage to TanStack Query"
Task 6: Migrate useNotifications
Files:
- Modify:
frontend/src/hooks/useNotifications.ts
Step 1: Rewrite useNotifications with TanStack Query
Replace the full content of frontend/src/hooks/useNotifications.ts:
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import api from '@/services/api'
import type { Notification, NotificationList } from '@/types'
export function useNotifications() {
const queryClient = useQueryClient()
const { data, isLoading: loading } = useQuery({
queryKey: ['notifications'],
queryFn: () => api.get<NotificationList>('/notifications').then(r => r.data),
refetchInterval: 60_000,
})
const markAsReadMutation = useMutation({
mutationFn: (id: number) => api.put<Notification>(`/notifications/${id}/read`),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['notifications'] }),
})
const markAllAsReadMutation = useMutation({
mutationFn: () => api.put<{ marked_read: number }>('/notifications/read-all'),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['notifications'] }),
})
return {
notifications: data?.items ?? [],
unreadCount: data?.unread_count ?? 0,
loading,
markAsRead: (id: number) => markAsReadMutation.mutate(id),
markAllAsRead: () => markAllAsReadMutation.mutate(),
refresh: () => queryClient.invalidateQueries({ queryKey: ['notifications'] }),
}
}
The public API of this hook is identical — notifications, unreadCount, loading, markAsRead, markAllAsRead, refresh — so no consumers need to change.
Step 2: Verify build
Run: cd /home/frontend/dak_c2s/frontend && pnpm build
Step 3: Commit
cd /home/frontend/dak_c2s
git add frontend/src/hooks/useNotifications.ts
git commit -m "refactor: migrate useNotifications to TanStack Query"
Task 7: Migrate ReportsPage
Files:
- Create:
frontend/src/hooks/useReports.ts - Modify:
frontend/src/pages/ReportsPage.tsx
Step 1: Create useReports hook
Create frontend/src/hooks/useReports.ts:
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import api from '@/services/api'
import type { ReportMeta } from '@/types'
export function useReports() {
return useQuery({
queryKey: ['reports'],
queryFn: () => api.get<{ items: ReportMeta[]; total: number }>('/reports/list').then(r => r.data),
})
}
export function useGenerateReport() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: ({ jahr, kw }: { jahr: number; kw: number }) =>
api.post<ReportMeta>('/reports/generate', null, { params: { jahr, kw } }).then(r => r.data),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['reports'] }),
})
}
export function useDeleteReports() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (ids: number[]) => api.delete('/reports/delete', { data: ids }),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['reports'] }),
})
}
Step 2: Update ReportsPage
In frontend/src/pages/ReportsPage.tsx:
Remove: import api from '@/services/api'
Add: import { useReports, useGenerateReport, useDeleteReports } from '@/hooks/useReports'
Replace state + fetch logic (lines 22-54):
// Remove:
const [reports, setReports] = useState<ReportMeta[]>([])
const [totalReports, setTotalReports] = useState(0)
const [loading, setLoading] = useState(true)
const [generating, setGenerating] = useState(false)
const [deleting, setDeleting] = useState(false)
const fetchReports = () => { ... }
useEffect(() => { fetchReports() }, [])
// Replace with:
const { data: reportData, isLoading: loading } = useReports()
const reports = reportData?.items ?? []
const totalReports = reportData?.total ?? 0
const generateMutation = useGenerateReport()
const deleteMutation = useDeleteReports()
Update generateReport:
const generateReport = async () => {
setGenError('')
setGenSuccess('')
try {
const result = await generateMutation.mutateAsync({ jahr: genJahr, kw: genKw })
setGenSuccess(`Bericht für KW ${result.kw}/${result.jahr} wurde generiert.`)
} catch {
setGenError('Fehler beim Generieren des Berichts.')
}
}
Use generateMutation.isPending instead of generating state.
Update deleteSelected:
const deleteSelected = async () => {
if (selectedIds.size === 0) return
try {
await deleteMutation.mutateAsync(Array.from(selectedIds))
setSelectedIds(new Set())
} catch {
setGenError('Fehler beim Löschen der Berichte.')
}
}
Use deleteMutation.isPending instead of deleting state.
Remove useEffect from imports if no longer used.
Step 3: Verify build
Run: cd /home/frontend/dak_c2s/frontend && pnpm build
Step 4: Commit
cd /home/frontend/dak_c2s
git add frontend/src/hooks/useReports.ts frontend/src/pages/ReportsPage.tsx
git commit -m "refactor: migrate ReportsPage to TanStack Query"
Task 8: Migrate CasesPage (list + mutations)
Files:
- Create:
frontend/src/hooks/useCases.ts - Modify:
frontend/src/pages/CasesPage.tsx - Modify:
frontend/src/pages/cases/useInlineEdit.ts
Step 1: Create useCases hook
Create frontend/src/hooks/useCases.ts:
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) {
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)
},
})
}
export function usePendingIcdCases(page: number, perPage: number) {
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),
})
}
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'] })
},
})
}
Step 2: Update CasesPage
In frontend/src/pages/CasesPage.tsx:
Remove: import api from '@/services/api'
Add: import { useCases, usePendingIcdCases, useIcdUpdate } from '@/hooks/useCases'
Replace the fetch useEffect (lines 88-110) and state (lines 65-66) with:
// Remove:
const [data, setData] = useState<CaseListResponse | null>(null)
const [loading, setLoading] = useState(true)
// Remove the entire useEffect that fetches cases
// Replace with:
const filters: CaseFilters = {
page, per_page: perPage,
...(debouncedSearch ? { search: debouncedSearch } : {}),
...(jahr !== '__all__' ? { jahr: Number(jahr) } : {}),
...(fallgruppe !== '__all__' ? { fallgruppe } : {}),
...(hasIcd !== '__all__' ? { has_icd: hasIcd } : {}),
}
const casesQuery = pendingIcdOnly
? usePendingIcdCases(page, perPage)
: useCases(filters)
const data = casesQuery.data ?? null
const loading = casesQuery.isLoading
Note: Import type { CaseFilters } from @/hooks/useCases.
Remove handleCaseSaved function entirely (lines 120-131). TanStack Query cache invalidation handles this automatically via mutations.
Update CaseDetail props: remove onCaseSaved prop.
Step 3: Update useInlineEdit to use mutations
In frontend/src/pages/cases/useInlineEdit.ts:
Remove: import api from '@/services/api'
Add: import { useCaseUpdate, useKvnrUpdate } from '@/hooks/useCases'
Remove the onSaved parameter from the function signature:
export function useInlineEdit(caseData: Case): UseInlineEditReturn {
Add mutations at the top of the hook:
const caseUpdateMutation = useCaseUpdate()
const kvnrUpdateMutation = useKvnrUpdate()
In saveAll, replace direct API calls:
// Instead of: api.put<Case>(`/cases/${caseData.id}/kvnr`, { kvnr: kvnrVal?.trim() || null })
const res = await kvnrUpdateMutation.mutateAsync({ id: caseData.id, kvnr: kvnrVal?.trim() || null })
// Instead of: api.put<Case>(`/cases/${caseData.id}`, payload)
const res = await caseUpdateMutation.mutateAsync({ id: caseData.id, data: payload })
Remove the onSaved(lastResult) call — cache invalidation from mutations handles list refresh.
Step 4: Update CaseDetail to remove onCaseSaved
In CaseDetail component within CasesPage.tsx:
Remove onCaseSaved prop and its type. Update ICD save to use useIcdUpdate():
const icdMutation = useIcdUpdate()
const saveIcd = async () => {
if (!icdValue.trim()) return
try {
await icdMutation.mutateAsync({ id: caseData.id, icd: icdValue.trim() })
setIcdSuccess(true)
} catch {
setIcdError('Fehler beim Speichern des ICD-Codes.')
}
}
Use icdMutation.isPending instead of icdSaving state. Remove icdSaving useState.
Remove onCaseSaved from the <CaseDetail> JSX call.
Step 5: Verify build
Run: cd /home/frontend/dak_c2s/frontend && pnpm build
Step 6: Commit
cd /home/frontend/dak_c2s
git add frontend/src/hooks/useCases.ts frontend/src/pages/CasesPage.tsx frontend/src/pages/cases/useInlineEdit.ts
git commit -m "refactor: migrate CasesPage and useInlineEdit to TanStack Query"
Task 9: Build, test, deploy
Step 1: Build frontend
Run: cd /home/frontend/dak_c2s/frontend && pnpm build
Expected: Build succeeds.
Step 2: Push and merge
cd /home/frontend/dak_c2s
git push origin develop
git checkout main && git pull origin main && git merge develop && git push origin main
git checkout develop
Step 3: Deploy to Hetzner 1
ssh hetzner1 "cd /opt/dak-portal && git pull origin main"
ssh hetzner1 "cd /opt/dak-portal/frontend && pnpm install && pnpm build && cp -r dist/* /var/www/vhosts/complexcaresolutions.de/dak.complexcaresolutions.de/dist/"
Step 4: Smoke test
Verify in browser:
- Dashboard loads with caching (navigate away and back — no reload flash)
- Cases list loads, detail sheet opens
- Edit a case field → save → list refreshes automatically
- ICD save works
- Notifications badge updates
- Admin pages (Users, Audit, Disclosures) all work