feat(v3): restructure project + add complete frontend
- Restructure: move backend from new_project/ to backend/ - Add full React/TypeScript frontend (37 pages, 17 services, 16 type defs, 11 query hooks) - Add docs/ with SRS specs, user stories, and workflow documentation - Update .gitignore for new directory layout Workflows implemented: WF1 User Signup, WF2 Placement Test, WF3 Exam Configuration, WF4 General English Exam, WF5 Course Generation, WF6 Entity Student Onboarding, AI Course Generation, Adaptive Learning Engine UI, White-Label Branding, Score Release Made-with: Cursor
This commit is contained in:
59
src/services/users.service.ts
Normal file
59
src/services/users.service.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { api, API_BASE_URL } from "@/lib/api-client";
|
||||
import { asPaginated, asRecordData } from "@/lib/odoo-api";
|
||||
import type { User, PaginatedResponse, PaginationParams, ApiSuccessResponse } from "@/types";
|
||||
|
||||
export interface UserListParams extends PaginationParams {
|
||||
type?: string;
|
||||
entity_id?: number;
|
||||
}
|
||||
|
||||
function mapPortalUser(raw: Record<string, unknown>): User {
|
||||
return {
|
||||
id: raw.id as number,
|
||||
name: String(raw.name || ""),
|
||||
email: String(raw.email ?? raw.login ?? ""),
|
||||
login: String(raw.login ?? raw.email ?? ""),
|
||||
user_type: (raw.user_type as User["user_type"]) || "student",
|
||||
is_verified: Boolean(raw.is_verified ?? true),
|
||||
entities: Array.isArray(raw.entities) ? (raw.entities as User["entities"]) : [],
|
||||
classrooms: Array.isArray(raw.classrooms) ? (raw.classrooms as string[]) : [],
|
||||
phone: raw.phone as string | undefined,
|
||||
gender: raw.gender as string | undefined,
|
||||
student_id: raw.student_id as string | undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export const usersService = {
|
||||
async list(params: UserListParams): Promise<PaginatedResponse<User>> {
|
||||
const raw = await api.get<unknown>("/users/list", params as Record<string, string | number | boolean | undefined>);
|
||||
const p = asPaginated<Record<string, unknown>>(raw);
|
||||
return { ...p, items: p.items.map(mapPortalUser) };
|
||||
},
|
||||
|
||||
async getById(id: number): Promise<User> {
|
||||
const raw = await api.get<unknown>(`/users/${id}`);
|
||||
return mapPortalUser(asRecordData<Record<string, unknown>>(raw));
|
||||
},
|
||||
|
||||
async update(id: number, data: Partial<User>): Promise<User> {
|
||||
const raw = await api.patch<unknown>(`/users/update`, { id, ...data });
|
||||
return mapPortalUser(asRecordData<Record<string, unknown>>(raw));
|
||||
},
|
||||
|
||||
async create(data: Partial<User>): Promise<User> {
|
||||
const raw = await api.post<unknown>("/users/create", data);
|
||||
return mapPortalUser(asRecordData<Record<string, unknown>>(raw));
|
||||
},
|
||||
|
||||
async createBatch(data: { users: Partial<User>[] }): Promise<ApiSuccessResponse> {
|
||||
return api.post<ApiSuccessResponse>("/batch_users", data);
|
||||
},
|
||||
|
||||
async exportList(params: UserListParams): Promise<Blob> {
|
||||
const res = await fetch(`${API_BASE_URL}/users/export?${new URLSearchParams(params as Record<string, string>)}`, {
|
||||
headers: { Authorization: `Bearer ${localStorage.getItem("encoach_token") ?? ""}` },
|
||||
});
|
||||
if (!res.ok) throw new Error(`Export failed: ${res.status} ${res.statusText}`);
|
||||
return res.blob();
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user