feat: wire Documents + Board page buttons, add mock-mode dual operation

Sprint 12 Phase 1: Golden Test Standard
- Documents: React Query, upload/download/delete wired, category colors+icons, table min-widths, data-testid
- Board: React Query, create position/elect/remove wired, confirmation dialogs, data-testid
- Both pages: mock-mode fallback (works without backend)
This commit is contained in:
Patrick Plate
2026-06-18 14:43:00 +02:00
parent 90cdac7468
commit 6e25914074
4 changed files with 716 additions and 70 deletions
+59 -2
View File
@@ -1,5 +1,13 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
import { apiClient } from "@/lib/api-client"
// --- Constants ---
const CLUB_ID = "00000000-0000-0000-0000-000000000001"
// --- Types ---
export type DocumentCategory =
| "SATZUNG"
| "PROTOKOLL"
@@ -28,6 +36,16 @@ export interface StorageUsage {
bytesUsed: number
}
export interface UploadDocumentRequest {
title: string
category: DocumentCategory
accessLevel: DocumentAccessLevel
description: string | null
file: File
}
// --- Raw API functions ---
export async function uploadDocument(
clubId: string,
title: string,
@@ -90,14 +108,53 @@ export function getPortalDocuments(clubId: string): Promise<ClubDocument[]> {
return apiClient<ClubDocument[]>(`/portal/documents?clubId=${clubId}`)
}
// Helper: format file size
// --- React Query Hooks ---
export function useDocumentsQuery(category?: DocumentCategory) {
return useQuery({
queryKey: ["documents", CLUB_ID, category],
queryFn: () => listDocuments(CLUB_ID, category),
})
}
export function useUploadDocumentMutation() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (data: UploadDocumentRequest) =>
uploadDocument(
CLUB_ID,
data.title,
data.category,
data.accessLevel,
data.description,
data.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"] })
},
})
}
// --- Helper: format file size ---
export function formatFileSize(bytes: number): string {
if (bytes < 1024) return `${bytes} B`
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
}
// Category labels
// --- Category labels ---
export const categoryLabels: Record<DocumentCategory, string> = {
SATZUNG: "Satzung",
PROTOKOLL: "Protokoll",