Ported from monorepo v4 commit 3b62075d (frontend/* portion). - StudentDashboard: surface assigned AI course plans alongside enrollments; enrolled-courses stat now counts plans + enrollments - InteractiveWorkbook + PlanReader components for live exercise solving - AdminCoursePlanDetail: media drawer with upload buttons (audio/image/video), hidden file inputs, upload mutation - AdminBranches page (entity-scoped LMS branches) + sidebar entry - coursePlan / lms / classrooms services + types updated for new endpoints (assignments, branches, classroom_ext, workbook attempts) - i18n: studentDash.myCoursePlans / studentDash.noCoursePlans (en + ar) Made-with: Cursor
128 lines
4.0 KiB
TypeScript
128 lines
4.0 KiB
TypeScript
import { api } from "@/lib/api-client";
|
|
import { asPaginated, asRecordData } from "@/lib/odoo-api";
|
|
import type {
|
|
Classroom,
|
|
ClassroomAssignCourseRequest,
|
|
ClassroomBatchSummary,
|
|
ClassroomCourse,
|
|
ClassroomCreateRequest,
|
|
ClassroomMember,
|
|
ClassroomSection,
|
|
ClassroomStudent,
|
|
PaginatedResponse,
|
|
PaginationParams,
|
|
ApiSuccessResponse,
|
|
} from "@/types";
|
|
|
|
export interface ClassroomListParams extends PaginationParams {
|
|
entity_id?: number;
|
|
branch_id?: number;
|
|
search?: string;
|
|
active?: boolean;
|
|
}
|
|
|
|
export const classroomsService = {
|
|
async list(params?: ClassroomListParams): Promise<PaginatedResponse<Classroom>> {
|
|
const raw = await api.get<unknown>(
|
|
"/classrooms",
|
|
params as Record<string, string | number | boolean | undefined>,
|
|
);
|
|
return asPaginated<Classroom>(raw);
|
|
},
|
|
|
|
async getById(id: number): Promise<Classroom> {
|
|
const raw = await api.get<unknown>(`/classrooms/${id}`);
|
|
return asRecordData<Classroom>(raw);
|
|
},
|
|
|
|
async create(data: ClassroomCreateRequest): Promise<Classroom> {
|
|
const raw = await api.post<unknown>("/classrooms", data);
|
|
return asRecordData<Classroom>(raw);
|
|
},
|
|
|
|
async update(id: number, data: Partial<ClassroomCreateRequest>): Promise<Classroom> {
|
|
const raw = await api.patch<unknown>(`/classrooms/${id}`, data);
|
|
return asRecordData<Classroom>(raw);
|
|
},
|
|
|
|
async delete(id: number): Promise<ApiSuccessResponse> {
|
|
return api.delete<ApiSuccessResponse>(`/classrooms/${id}`);
|
|
},
|
|
|
|
// ---- Roster ----------------------------------------------------------
|
|
async listStudents(id: number): Promise<{ items: ClassroomStudent[]; total: number }> {
|
|
return api.get(`/classrooms/${id}/students`);
|
|
},
|
|
|
|
/** Replace or add to roster. ``mode='set'`` overwrites; ``mode='add'`` appends. */
|
|
async setStudents(
|
|
id: number,
|
|
studentIds: number[],
|
|
mode: "set" | "add" = "set",
|
|
): Promise<Classroom> {
|
|
const raw = await api.post<unknown>(`/classrooms/${id}/students`, {
|
|
student_ids: studentIds,
|
|
mode,
|
|
});
|
|
return asRecordData<Classroom>(raw);
|
|
},
|
|
|
|
async removeStudents(id: number, studentIds: number[]): Promise<Classroom> {
|
|
const raw = await api.delete<unknown>(`/classrooms/${id}/students`, {
|
|
student_ids: studentIds,
|
|
});
|
|
return asRecordData<Classroom>(raw);
|
|
},
|
|
|
|
// ---- Homeroom teachers ----------------------------------------------
|
|
async listTeachers(id: number): Promise<{ items: ClassroomMember[]; total: number }> {
|
|
return api.get(`/classrooms/${id}/teachers`);
|
|
},
|
|
|
|
async setTeachers(
|
|
id: number,
|
|
teacherIds: number[],
|
|
mode: "set" | "add" = "set",
|
|
): Promise<Classroom> {
|
|
const raw = await api.post<unknown>(`/classrooms/${id}/teachers`, {
|
|
teacher_ids: teacherIds,
|
|
mode,
|
|
});
|
|
return asRecordData<Classroom>(raw);
|
|
},
|
|
|
|
// ---- Courses + cascade ----------------------------------------------
|
|
async listCourses(id: number): Promise<{ items: ClassroomCourse[]; total: number }> {
|
|
return api.get(`/classrooms/${id}/courses`);
|
|
},
|
|
|
|
async listSections(
|
|
id: number,
|
|
params?: { course_id?: number; active?: boolean },
|
|
): Promise<{ items: ClassroomSection[]; total: number }> {
|
|
return api.get(
|
|
`/classrooms/${id}/sections`,
|
|
params as Record<string, string | number | boolean | undefined>,
|
|
);
|
|
},
|
|
|
|
/** Assign a course → server creates (or reuses) the canonical batch and
|
|
* enrolls every classroom student into it. */
|
|
async assignCourse(
|
|
id: number,
|
|
payload: ClassroomAssignCourseRequest,
|
|
): Promise<{ data: Classroom; batch: ClassroomBatchSummary }> {
|
|
return api.post(`/classrooms/${id}/assign-course`, payload);
|
|
},
|
|
|
|
async detachCourse(id: number, courseId: number): Promise<Classroom> {
|
|
const raw = await api.delete<unknown>(`/classrooms/${id}/courses/${courseId}`);
|
|
return asRecordData<Classroom>(raw);
|
|
},
|
|
|
|
// ---- Batches --------------------------------------------------------
|
|
async listBatches(id: number): Promise<{ items: ClassroomBatchSummary[]; total: number }> {
|
|
return api.get(`/classrooms/${id}/batches`);
|
|
},
|
|
};
|