129 lines
3.6 KiB
TypeScript
129 lines
3.6 KiB
TypeScript
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()}`
|
|
);
|
|
if (!response.data?.data) {
|
|
throw new Error('Invalid API response');
|
|
}
|
|
return {
|
|
items: response.data.data,
|
|
total: response.data.meta?.total ?? 0,
|
|
page: response.data.meta?.page ?? 1,
|
|
};
|
|
},
|
|
|
|
/**
|
|
* 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}`
|
|
);
|
|
if (!response.data?.data) {
|
|
throw new Error('Invalid API response');
|
|
}
|
|
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
|
|
);
|
|
if (!response.data?.data) {
|
|
throw new Error('Invalid API response');
|
|
}
|
|
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
|
|
);
|
|
if (!response.data?.data) {
|
|
throw new Error('Invalid API response');
|
|
}
|
|
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');
|
|
if (!response.data?.data) {
|
|
throw new Error('Invalid API response');
|
|
}
|
|
return response.data.data;
|
|
},
|
|
};
|