Files
encoach_backend_new_v3/frontend/src/services/lms.service.ts
Yamen Ahmad f1c4953a63 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
2026-04-10 17:26:42 +04:00

226 lines
8.5 KiB
TypeScript

import { api } from "@/lib/api-client";
import { asPaginated, asRecordData } from "@/lib/odoo-api";
import type { User } from "@/types/auth";
import type {
Course,
Batch,
TimetableSession,
AttendanceRecord,
GradeRecord,
CourseCreateRequest,
PaginatedResponse,
PaginationParams,
ApiSuccessResponse,
LmsStudentRecord,
LmsTeacherRecord,
LmsStudentCreateRequest,
LmsTeacherCreateRequest,
} from "@/types";
function mapCourseRaw(r: Record<string, unknown>): Course {
const status = (r.status as Course["status"]) || "draft";
const enrolled = Number(r.student_count ?? 0);
const maxCap = Number(r.max_capacity ?? 0);
return {
id: r.id as number,
title: (r.title as string) || (r.name as string) || "",
code: (r.code as string) || "",
subject_id: Array.isArray(r.subject_ids) && (r.subject_ids as number[]).length ? (r.subject_ids as number[])[0] : undefined,
subject_name: Array.isArray(r.subject_names) ? String((r.subject_names as string[])[0] || "") : "",
instructor_id: 0,
instructor_name: "",
description: (r.description as string) || "",
level: "Beginner",
modules: [],
enrolled,
max_capacity: maxCap,
status,
start_date: "",
end_date: "",
};
}
function mapBatchRaw(r: Record<string, unknown>): Batch {
const st = String(r.status || "draft");
const statusMap: Record<string, Batch["status"]> = {
draft: "upcoming",
active: "active",
completed: "completed",
};
return {
id: r.id as number,
name: String(r.name || ""),
course_id: Number(r.course_id || 0),
course_name: String(r.course_name || ""),
teacher_id: 0,
teacher_name: "",
student_ids: [],
student_count: 0,
start_date: String(r.start_date || ""),
end_date: String(r.end_date || ""),
schedule: "",
status: statusMap[st] || "upcoming",
capacity: Number(r.max_students ?? 0),
};
}
/** Map OpenEduCat student for screens that still expect `User` (admin tables). */
export function lmsStudentToUser(s: LmsStudentRecord): User {
const active = s.status === "active";
return {
id: s.id,
name: s.name,
email: s.email || "",
login: s.email || "",
user_type: "student",
phone: s.phone || undefined,
gender: s.gender || undefined,
student_id: String(s.id),
is_verified: active,
entities:
s.batch_id != null
? [{ id: s.batch_id, name: s.batch_name || "Batch" }]
: [],
classrooms: [],
};
}
export function lmsTeacherToUser(t: LmsTeacherRecord): User {
return {
id: t.id,
name: t.name,
email: t.email || "",
login: t.email || "",
user_type: "teacher",
phone: t.phone || undefined,
gender: t.gender || undefined,
is_verified: true,
entities: t.department_id ? [{ id: t.department_id, name: t.department_name || "Department" }] : [],
classrooms: [],
};
}
export const lmsService = {
async listCourses(params?: PaginationParams & { status?: string }): Promise<PaginatedResponse<Course>> {
const raw = await api.get<unknown>("/courses", params as Record<string, string | number | boolean | undefined>);
const p = asPaginated<Record<string, unknown>>(raw);
return { ...p, items: p.items.map(mapCourseRaw) };
},
async getCourse(id: number): Promise<Course> {
const raw = await api.get<unknown>(`/courses/${id}`);
return mapCourseRaw(asRecordData<Record<string, unknown>>(raw));
},
async createCourse(data: CourseCreateRequest): Promise<Course> {
const raw = await api.post<unknown>("/courses", {
name: data.title,
code: data.code,
description: data.description,
max_capacity: data.max_capacity,
status: "draft",
});
return mapCourseRaw(asRecordData<Record<string, unknown>>(raw));
},
async updateCourse(id: number, data: Partial<CourseCreateRequest>): Promise<Course> {
const body: Record<string, unknown> = {};
if (data.title != null) body.name = data.title;
if (data.code != null) body.code = data.code;
if (data.description != null) body.description = data.description;
if (data.max_capacity != null) body.max_capacity = data.max_capacity;
const raw = await api.patch<unknown>(`/courses/${id}`, body);
return mapCourseRaw(asRecordData<Record<string, unknown>>(raw));
},
async deleteCourse(id: number): Promise<ApiSuccessResponse> {
return api.delete<ApiSuccessResponse>(`/courses/${id}`);
},
async aiGenerateCourse(data: { title: string; subject_id?: number; level?: string }): Promise<{ outline: unknown }> {
return api.post("/courses/ai-generate", data);
},
async listBatches(params?: PaginationParams): Promise<PaginatedResponse<Batch>> {
const raw = await api.get<unknown>("/batches", params as Record<string, string | number | boolean | undefined>);
const p = asPaginated<Record<string, unknown>>(raw);
return { ...p, items: p.items.map(mapBatchRaw) };
},
async getBatch(id: number): Promise<Batch> {
const raw = await api.get<unknown>(`/batches/${id}`);
return mapBatchRaw(asRecordData<Record<string, unknown>>(raw));
},
async createBatch(data: Partial<Batch>): Promise<Batch> {
const raw = await api.post<unknown>("/batches", data);
return mapBatchRaw(asRecordData<Record<string, unknown>>(raw));
},
async updateBatch(id: number, data: Partial<Batch>): Promise<Batch> {
const raw = await api.patch<unknown>(`/batches/${id}`, data);
return mapBatchRaw(asRecordData<Record<string, unknown>>(raw));
},
async deleteBatch(id: number): Promise<ApiSuccessResponse> {
return api.delete<ApiSuccessResponse>(`/batches/${id}`);
},
async listStudents(params?: PaginationParams & { search?: string; batch_id?: number }): Promise<PaginatedResponse<LmsStudentRecord>> {
const raw = await api.get<unknown>("/students", params as Record<string, string | number | boolean | undefined>);
return asPaginated<LmsStudentRecord>(raw);
},
async getStudent(id: number): Promise<LmsStudentRecord> {
const raw = await api.get<unknown>(`/students/${id}`);
return asRecordData<LmsStudentRecord>(raw);
},
async createStudent(data: LmsStudentCreateRequest): Promise<LmsStudentRecord> {
const raw = await api.post<unknown>("/students", data);
return asRecordData<LmsStudentRecord>(raw);
},
async updateStudent(id: number, data: Partial<LmsStudentCreateRequest & { status?: string }>): Promise<LmsStudentRecord> {
const raw = await api.patch<unknown>(`/students/${id}`, data);
return asRecordData<LmsStudentRecord>(raw);
},
async listTeachers(params?: PaginationParams & { search?: string }): Promise<PaginatedResponse<LmsTeacherRecord>> {
const raw = await api.get<unknown>("/teachers", params as Record<string, string | number | boolean | undefined>);
return asPaginated<LmsTeacherRecord>(raw);
},
async createTeacher(data: LmsTeacherCreateRequest): Promise<LmsTeacherRecord> {
const raw = await api.post<unknown>("/teachers", data);
return asRecordData<LmsTeacherRecord>(raw);
},
async getTimetable(params?: { course_id?: number; teacher_id?: number; batch_id?: number }): Promise<TimetableSession[]> {
const raw = await api.get<{ data: TimetableSession[] } | TimetableSession[]>("/timetable", params as Record<string, string | number | boolean | undefined>);
return Array.isArray(raw) ? raw : (raw as { data: TimetableSession[] }).data ?? [];
},
async createTimetableSession(data: Partial<TimetableSession>): Promise<TimetableSession> {
return api.post<TimetableSession>("/timetable", data);
},
async deleteTimetableSession(id: number): Promise<ApiSuccessResponse> {
return api.delete<ApiSuccessResponse>(`/timetable/${id}`);
},
async getAttendance(params?: { course_id?: number; student_id?: number; date?: string }): Promise<AttendanceRecord[]> {
const raw = await api.get<{ data: AttendanceRecord[] } | AttendanceRecord[]>("/attendance", params as Record<string, string | number | boolean | undefined>);
return Array.isArray(raw) ? raw : (raw as { data: AttendanceRecord[] }).data ?? [];
},
async recordAttendance(data: { course_id: number; date: string; records: { student_id: number; status: string }[] }): Promise<ApiSuccessResponse> {
return api.post<ApiSuccessResponse>("/attendance", data);
},
async getGrades(params?: { course_id?: number; student_id?: number }): Promise<GradeRecord[]> {
const raw = await api.get<{ data: GradeRecord[] } | GradeRecord[]>("/grades", params as Record<string, string | number | boolean | undefined>);
return Array.isArray(raw) ? raw : (raw as { data: GradeRecord[] }).data ?? [];
},
};