# 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:

```tsx
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**

```bash
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`:

```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:
```tsx
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:
```tsx
const { data, isLoading: loading } = useDashboard(jahr)
```

Update imports: remove `useEffect` from react import, remove `api` import, add:
```tsx
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**

```bash
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`:

```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`:

```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**

```bash
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`:

```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:
```tsx
const [entries, setEntries] = useState<AuditLogEntry[]>([])
const [loading, setLoading] = useState(true)
```
and the `fetchEntries` function and its `useEffect`.

Replace with:
```tsx
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**

```bash
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`:

```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):
```tsx
// 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):
```tsx
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**

```bash
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`:

```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**

```bash
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`:

```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):
```tsx
// 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`:
```tsx
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`:
```tsx
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**

```bash
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`:

```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:

```tsx
// 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:

```tsx
export function useInlineEdit(caseData: Case): UseInlineEditReturn {
```

Add mutations at the top of the hook:
```tsx
const caseUpdateMutation = useCaseUpdate()
const kvnrUpdateMutation = useKvnrUpdate()
```

In `saveAll`, replace direct API calls:
```tsx
// 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()`:

```tsx
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**

```bash
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**

```bash
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**

```bash
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:
1. Dashboard loads with caching (navigate away and back — no reload flash)
2. Cases list loads, detail sheet opens
3. Edit a case field → save → list refreshes automatically
4. ICD save works
5. Notifications badge updates
6. Admin pages (Users, Audit, Disclosures) all work
