feat(sprint-5): Phase 2 — React Query API client layer

- @tanstack/react-query with QueryClientProvider in providers/index.tsx
- Typed api-client.ts fetch wrapper with ApiError class + apiDownload
- Service modules: members, distributions, stock, reports, dashboard, portal, staff
- Offline banner component (onlineManager subscription)
- API error boundary with retry button
- Loading skeleton components (card, table, chart, form, dashboard)
- i18n for error/loading states (de/en)
This commit is contained in:
Patrick Plate
2026-06-12 19:59:41 +02:00
parent 279f2f6de0
commit f42c166329
20 changed files with 2875 additions and 7 deletions
@@ -0,0 +1,83 @@
import { useQuery } from "@tanstack/react-query"
import { apiClient } from "@/lib/api-client"
// --- Types ---
export interface PortalDashboardData {
memberName: string
memberNumber: string
quotaStatus: {
dailyUsedGrams: number
dailyLimitGrams: number
monthlyUsedGrams: number
monthlyLimitGrams: number
isUnder21: boolean
}
lastDistribution?: {
strainName: string
amountGrams: number
recordedAt: string
}
}
export interface PortalHistoryEntry {
id: string
strainName: string
amountGrams: number
recordedAt: string
}
export interface PortalHistoryPage {
content: PortalHistoryEntry[]
totalElements: number
totalPages: number
number: number
size: number
}
export interface PortalProfileData {
firstName: string
lastName: string
email: string
phone?: string
dateOfBirth: string
memberNumber: string
memberSince: string
status: "ACTIVE" | "SUSPENDED" | "EXPELLED"
}
// --- Query Hooks ---
export function usePortalDashboardQuery() {
return useQuery({
queryKey: ["portal", "dashboard"],
queryFn: () => apiClient<PortalDashboardData>("/portal/dashboard"),
})
}
export function usePortalHistoryQuery(params?: {
page?: number
size?: number
month?: string
}) {
return useQuery({
queryKey: ["portal", "history", params],
queryFn: () =>
apiClient<PortalHistoryPage>("/portal/history", {
params: {
page: params?.page,
size: params?.size ?? 20,
month: params?.month || undefined,
},
}),
})
}
export function usePortalProfileQuery() {
return useQuery({
queryKey: ["portal", "profile"],
queryFn: () => apiClient<PortalProfileData>("/portal/profile"),
staleTime: 5 * 60 * 1000, // profile rarely changes
})
}