diff --git a/APP/src/app/features/members/models/member.model.ts b/APP/src/app/features/members/models/member.model.ts new file mode 100644 index 0000000..6ca6f8e --- /dev/null +++ b/APP/src/app/features/members/models/member.model.ts @@ -0,0 +1,84 @@ +export type MemberStatus = 'Member' | 'Visitor' | 'Inactive' | 'Former'; + +export interface MemberListItemDto { + id: number; + firstName_en: string; + lastName_en: string; + nickName: string | null; + firstName_zh: string | null; + lastName_zh: string | null; + status: MemberStatus; + email: string | null; + phoneCell: string | null; + joinDate: string | null; + linkedUserId: string | null; +} + +export interface MemberDto extends MemberListItemDto { + gender: string | null; + dateOfBirth: string | null; + baptismDate: string | null; + baptismChurch: string | null; + phoneHome: string | null; + address: string | null; + city: string | null; + state: string | null; + zipCode: string | null; + country: string; + photoBlobPath: string | null; + languagePreference: string; + notes: string | null; + familyUnitId: number | null; + createdAt: string; + updatedAt: string; +} + +export interface CreateMemberRequest { + firstName_en: string; + lastName_en: string; + nickName: string | null; + firstName_zh: string | null; + lastName_zh: string | null; + gender: string | null; + dateOfBirth: string | null; + baptismDate: string | null; + baptismChurch: string | null; + email: string | null; + phoneCell: string | null; + phoneHome: string | null; + address: string | null; + city: string | null; + state: string | null; + zipCode: string | null; + country: string; + status: string; + languagePreference: string; + joinDate: string | null; + notes: string | null; + familyUnitId: number | null; +} + +export type UpdateMemberRequest = CreateMemberRequest; + +export interface PagedResult { + items: T[]; + totalCount: number; + page: number; + pageSize: number; + totalPages: number; +} + +export interface MemberQueryParams { + page?: number; + pageSize?: number; + search?: string; + status?: string; + hasUser?: boolean; +} + +/** Display name: NickName (if present) else FirstName_en, plus LastName_en */ +export function memberDisplayName( + m: Pick +): string { + return `${m.nickName ?? m.firstName_en} ${m.lastName_en}`; +} diff --git a/APP/src/app/features/members/services/member-api.service.ts b/APP/src/app/features/members/services/member-api.service.ts new file mode 100644 index 0000000..2fef857 --- /dev/null +++ b/APP/src/app/features/members/services/member-api.service.ts @@ -0,0 +1,43 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpParams } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { ApiConfigService } from '../../../core/services/api-config.service'; +import { + MemberDto, MemberListItemDto, CreateMemberRequest, + UpdateMemberRequest, MemberQueryParams, PagedResult +} from '../models/member.model'; + +@Injectable({ providedIn: 'root' }) +export class MemberApiService { + private readonly endpoint: string; + + constructor(private http: HttpClient, apiConfig: ApiConfigService) { + this.endpoint = apiConfig.getApiUrl('members'); + } + + getPaged(params: MemberQueryParams = {}): Observable> { + let p = new HttpParams() + .set('page', params.page ?? 1) + .set('pageSize', params.pageSize ?? 20); + if (params.search !== undefined && params.search !== '') p = p.set('search', params.search); + if (params.status !== undefined && params.status !== '') p = p.set('status', params.status); + if (params.hasUser !== undefined) p = p.set('hasUser', params.hasUser); + return this.http.get>(this.endpoint, { params: p }); + } + + getById(id: number): Observable { + return this.http.get(`${this.endpoint}/${id}`); + } + + create(request: CreateMemberRequest): Observable<{ id: number }> { + return this.http.post<{ id: number }>(this.endpoint, request); + } + + update(id: number, request: UpdateMemberRequest): Observable { + return this.http.put(`${this.endpoint}/${id}`, request); + } + + delete(id: number): Observable { + return this.http.delete(`${this.endpoint}/${id}`); + } +} diff --git a/APP/src/app/features/users/models/user.model.ts b/APP/src/app/features/users/models/user.model.ts new file mode 100644 index 0000000..80b6e82 --- /dev/null +++ b/APP/src/app/features/users/models/user.model.ts @@ -0,0 +1,52 @@ +export interface UserListItemDto { + id: string; + email: string; + memberId: number | null; + memberDisplayName: string | null; + roles: string[]; + isActive: boolean; + languagePreference: string; + lastLoginAt: string | null; + createdAt: string; +} + +export type UserDto = UserListItemDto; + +export interface CreateUserRequest { + memberId: number; + email: string; + roles: string[]; + languagePreference: string; +} + +export interface CreateUserResult { + userId: string; + tempPassword: string; +} + +export interface UpdateUserRequest { + email: string; + roles: string[]; + isActive: boolean; + languagePreference: string; +} + +export interface UserQueryParams { + page?: number; + pageSize?: number; + search?: string; +} + +export interface PagedResult { + items: T[]; + totalCount: number; + page: number; + pageSize: number; + totalPages: number; +} + +export const ALL_ROLES = [ + 'super_admin','pastor','board_member','coworker_chair','ministry_leader', + 'district_leader','cell_leader','coworker','finance','secretary', + 'worship_leader','member','visitor' +] as const; diff --git a/APP/src/app/features/users/services/user-api.service.ts b/APP/src/app/features/users/services/user-api.service.ts new file mode 100644 index 0000000..2506e60 --- /dev/null +++ b/APP/src/app/features/users/services/user-api.service.ts @@ -0,0 +1,46 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpParams } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { ApiConfigService } from '../../../core/services/api-config.service'; +import { + UserDto, UserListItemDto, CreateUserRequest, CreateUserResult, + UpdateUserRequest, UserQueryParams, PagedResult +} from '../models/user.model'; + +@Injectable({ providedIn: 'root' }) +export class UserApiService { + private readonly endpoint: string; + + constructor(private http: HttpClient, apiConfig: ApiConfigService) { + this.endpoint = apiConfig.getApiUrl('users'); + } + + getPaged(params: UserQueryParams = {}): Observable> { + let p = new HttpParams() + .set('page', params.page ?? 1) + .set('pageSize', params.pageSize ?? 20); + if (params.search) p = p.set('search', params.search); + return this.http.get>(this.endpoint, { params: p }); + } + + getById(id: string): Observable { + return this.http.get(`${this.endpoint}/${id}`); + } + + createUser(request: CreateUserRequest): Observable { + return this.http.post(this.endpoint, request); + } + + update(id: string, request: UpdateUserRequest): Observable { + return this.http.put(`${this.endpoint}/${id}`, request); + } + + deactivate(id: string): Observable { + return this.http.delete(`${this.endpoint}/${id}`); + } + + resetPassword(id: string): Observable<{ tempPassword: string }> { + return this.http.post<{ tempPassword: string }>( + `${this.endpoint}/${id}/reset-password`, {}); + } +}