feat(sprint8): Phase 4 — Dokumentenarchiv + Vorstandsverwaltung

Backend:
- V20 migration: documents table with category, access_level, file storage
- V21 migration: board_positions + board_members with term tracking
- Document entity + DocumentCategory/DocumentAccessLevel enums
- BoardPosition + BoardMember entities
- Extended AuditEventType (DOCUMENT_UPLOADED/DELETED, BOARD_MEMBER_ELECTED/REMOVED)
- Extended StaffPermission (MANAGE_DOCUMENTS)
- Extended NotificationType (BOARD_TERM_EXPIRING)
- DocumentService: upload, list, download, delete, storage usage
- BoardService: positions CRUD, elect/remove members, current/history
- DocumentController: multipart upload, filtered list, download, delete, portal
- BoardController: positions, elect, remove, current board, history, portal

Frontend:
- documents.ts + board.ts service layers
- Admin /documents page: grouped by category, upload dialog, filter, download/delete
- Admin /board page: current board cards, position management, elect member dialog
- Navigation: added Dokumente + Vorstand to sidebar
- i18n: documents.* + board.* keys in de.json + en.json
This commit is contained in:
Patrick Plate
2026-06-15 08:53:38 +02:00
parent b22702317a
commit e4698827ee
24 changed files with 1812 additions and 5 deletions
@@ -0,0 +1,90 @@
import { apiClient } from "@/lib/api-client"
export interface BoardPosition {
id: string
title: string
description: string | null
sortOrder: number
isActive: boolean
createdAt: string
}
export interface BoardMember {
id: string
clubId: string
positionId: string
memberId: string
electedAt: string
termStart: string
termEnd: string | null
isCurrent: boolean
electedInAssemblyId: string | null
createdAt: string
}
export interface CreatePositionRequest {
title: string
description?: string
sortOrder?: number
}
export interface ElectBoardMemberRequest {
positionId: string
memberId: string
electedAt: string
termStart: string
termEnd?: string
assemblyId?: string
}
export function createPosition(
clubId: string,
data: CreatePositionRequest
): Promise<BoardPosition> {
return apiClient<BoardPosition>(`/board/positions?clubId=${clubId}`, {
method: "POST",
body: data,
})
}
export function getPositions(clubId: string): Promise<BoardPosition[]> {
return apiClient<BoardPosition[]>(`/board/positions?clubId=${clubId}`)
}
export function updatePosition(
id: string,
data: Partial<CreatePositionRequest & { isActive: boolean }>
): Promise<BoardPosition> {
return apiClient<BoardPosition>(`/board/positions/${id}`, {
method: "PUT",
body: data,
})
}
export function electBoardMember(
clubId: string,
data: ElectBoardMemberRequest
): Promise<BoardMember> {
return apiClient<BoardMember>(`/board/members?clubId=${clubId}`, {
method: "POST",
body: data,
})
}
export function getCurrentBoard(clubId: string): Promise<BoardMember[]> {
return apiClient<BoardMember[]>(`/board?clubId=${clubId}`)
}
export function getBoardHistory(clubId: string): Promise<BoardMember[]> {
return apiClient<BoardMember[]>(`/board/history?clubId=${clubId}`)
}
export function removeBoardMember(id: string, clubId: string): Promise<void> {
return apiClient<void>(`/board/members/${id}?clubId=${clubId}`, {
method: "DELETE",
})
}
export function getPortalBoard(clubId: string): Promise<BoardMember[]> {
return apiClient<BoardMember[]>(`/portal/board?clubId=${clubId}`)
}