add features
This commit is contained in:
113
frontend/src/services/members.ts
Normal file
113
frontend/src/services/members.ts
Normal 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;
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user