# Sprint 12 Implementation Plan: "Golden Test Standard" **Datum:** 18.06.2026 **Autor:** Patrick Plate / Lumen (Planner) **Status:** v1 **Basis:** cannamanage-sprint12-analysis.md --- ## Übersicht | Phase | Beschreibung | Aufwand | |-------|-------------|---------| | Phase 1 | Documents Page — React Query + Wire Actions | ~2.5h | | Phase 2 | Documents Page — UX Improvements | ~1h | | Phase 3 | Board Page — Wire All Actions | ~1.5h | | **Gesamt** | | **~5h** | --- ## Phase 1: Documents Page — React Query Integration + Action Wiring ### Step 1.1: Add React Query hooks to `services/documents.ts` **File:** `cannamanage-frontend/src/services/documents.ts` Add query hooks (pattern matches other services like `services/stock.ts`): ```typescript import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" // Club ID constant (same pattern as info-board) const CLUB_ID = "00000000-0000-0000-0000-000000000001" export function useDocumentsQuery(category?: DocumentCategory) { return useQuery({ queryKey: ["documents", category], queryFn: () => listDocuments(CLUB_ID, category), }) } export function useUploadDocumentMutation() { const queryClient = useQueryClient() return useMutation({ mutationFn: (params: { title: string category: DocumentCategory accessLevel: DocumentAccessLevel description: string | null file: File }) => uploadDocument( CLUB_ID, params.title, params.category, params.accessLevel, params.description, params.file ), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["documents"] }) }, }) } export function useDeleteDocumentMutation() { const queryClient = useQueryClient() return useMutation({ mutationFn: (id: string) => deleteDocument(id, CLUB_ID), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["documents"] }) }, }) } ``` ### Step 1.2: Rewrite Documents Page with React Query **File:** `cannamanage-frontend/src/app/(dashboard-layout)/documents/page.tsx` Key changes: 1. Replace `useState(mockDocuments)` with `useDocumentsQuery()` + mock fallback 2. Wire upload form: collect form state → call `uploadMutation.mutate()` 3. Wire download button: `onClick={() => handleDownload(doc.id, doc.filename)}` 4. Wire delete button: confirmation dialog → `deleteMutation.mutate(doc.id)` **Upload handler:** ```typescript const uploadMutation = useUploadDocumentMutation() function handleUpload() { const fileInput = document.getElementById("file") as HTMLInputElement const file = fileInput?.files?.[0] if (!file || !title || !category) { toast.error("Bitte alle Pflichtfelder ausfüllen") return } uploadMutation.mutate( { title, category, accessLevel, description: description || null, file }, { onSuccess: () => { toast.success("Dokument hochgeladen") setUploadOpen(false) resetForm() }, onError: () => toast.error("Upload fehlgeschlagen"), } ) } ``` **Download handler:** ```typescript async function handleDownload(docId: string, filename: string) { try { const blob = await downloadDocument(docId) const url = URL.createObjectURL(blob) const a = document.createElement("a") a.href = url a.download = filename document.body.appendChild(a) a.click() document.body.removeChild(a) URL.revokeObjectURL(url) toast.success("Download gestartet") } catch { toast.error("Download fehlgeschlagen") } } ``` **Delete handler with confirmation:** ```typescript const deleteMutation = useDeleteDocumentMutation() const [deleteTarget, setDeleteTarget] = useState(null) // In AlertDialog: function handleConfirmDelete() { if (!deleteTarget) return deleteMutation.mutate(deleteTarget.id, { onSuccess: () => { toast.success("Dokument gelöscht") setDeleteTarget(null) }, onError: () => toast.error("Löschen fehlgeschlagen"), }) } ``` ### Step 1.3: Add form state management to Upload Dialog Add controlled state for all form fields: ```typescript const [title, setTitle] = useState("") const [category, setCategory] = useState("") const [accessLevel, setAccessLevel] = useState("ALL_MEMBERS") const [description, setDescription] = useState("") ``` Wire each `` and `