add features

This commit is contained in:
Matthias Hochmeister
2026-02-27 19:50:14 +01:00
parent c5e8337a69
commit 620bacc6b5
46 changed files with 14095 additions and 1 deletions

View File

@@ -0,0 +1,113 @@
import { api } from './api';
import {
MemberListItem,
MemberWithProfile,
MemberFilters,
MemberStats,
CreateMemberProfileData,
UpdateMemberProfileData,
} from '../types/member.types';
// ----------------------------------------------------------------
// Response envelope shapes
// ----------------------------------------------------------------
interface ApiListResponse<T> {
success: boolean;
data: T[];
meta: { total: number; page: number };
}
interface ApiItemResponse<T> {
success: boolean;
data: T;
}
// ----------------------------------------------------------------
// Service
// ----------------------------------------------------------------
/**
* Builds a URLSearchParams object from the filter object so query
* strings like ?status[]=aktiv&status[]=passiv are sent correctly.
*/
function buildParams(filters?: MemberFilters): URLSearchParams {
const params = new URLSearchParams();
if (!filters) return params;
if (filters.search) params.append('search', filters.search);
if (filters.page) params.append('page', String(filters.page));
if (filters.pageSize) params.append('pageSize', String(filters.pageSize));
filters.status?.forEach((s) => params.append('status[]', s));
filters.dienstgrad?.forEach((d) => params.append('dienstgrad[]', d));
return params;
}
export const membersService = {
/**
* Fetches a paginated, optionally filtered list of members.
*/
async getMembers(
filters?: MemberFilters
): Promise<{ items: MemberListItem[]; total: number; page: number }> {
const params = buildParams(filters);
const response = await api.get<ApiListResponse<MemberListItem>>(
`/api/members?${params.toString()}`
);
return {
items: response.data.data,
total: response.data.meta.total,
page: response.data.meta.page,
};
},
/**
* Fetches a single member with their full profile and rank history.
*/
async getMember(userId: string): Promise<MemberWithProfile> {
const response = await api.get<ApiItemResponse<MemberWithProfile>>(
`/api/members/${userId}`
);
return response.data.data;
},
/**
* Creates a new member profile for an existing auth user.
* Restricted to Kommandant/Admin (enforced server-side).
*/
async createMemberProfile(
userId: string,
data: CreateMemberProfileData
): Promise<MemberWithProfile> {
const response = await api.post<ApiItemResponse<MemberWithProfile>>(
`/api/members/${userId}/profile`,
data
);
return response.data.data;
},
/**
* Partially updates a member profile.
* Kommandant/Admin: full update.
* Own profile: limited fields only (enforced server-side).
*/
async updateMember(
userId: string,
data: UpdateMemberProfileData
): Promise<MemberWithProfile> {
const response = await api.patch<ApiItemResponse<MemberWithProfile>>(
`/api/members/${userId}`,
data
);
return response.data.data;
},
/**
* Fetches aggregate counts for the dashboard KPI widget.
*/
async getMemberStats(): Promise<MemberStats> {
const response = await api.get<ApiItemResponse<MemberStats>>('/api/members/stats');
return response.data.data;
},
};