feat(frontend): student dashboard course-plans, interactive workbook, media uploads, branches
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
This commit is contained in:
@@ -1,36 +1,127 @@
|
||||
import { api } from "@/lib/api-client";
|
||||
import type { Classroom, ClassroomCreateRequest, PaginatedResponse, PaginationParams, ApiSuccessResponse } from "@/types";
|
||||
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>> {
|
||||
return api.get<PaginatedResponse<Classroom>>("/groups", params as Record<string, string | number | boolean | undefined>);
|
||||
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> {
|
||||
return api.get<Classroom>(`/groups/${id}`);
|
||||
const raw = await api.get<unknown>(`/classrooms/${id}`);
|
||||
return asRecordData<Classroom>(raw);
|
||||
},
|
||||
|
||||
async create(data: ClassroomCreateRequest): Promise<Classroom> {
|
||||
return api.post<Classroom>("/groups", data);
|
||||
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>(`/groups/${id}`);
|
||||
return api.delete<ApiSuccessResponse>(`/classrooms/${id}`);
|
||||
},
|
||||
|
||||
async transfer(data: { student_ids: number[]; from_id: number; to_id: number }): Promise<ApiSuccessResponse> {
|
||||
return api.post<ApiSuccessResponse>("/groups/transfer", data);
|
||||
// ---- Roster ----------------------------------------------------------
|
||||
async listStudents(id: number): Promise<{ items: ClassroomStudent[]; total: number }> {
|
||||
return api.get(`/classrooms/${id}/students`);
|
||||
},
|
||||
|
||||
async addMembers(id: number, userIds: number[]): Promise<ApiSuccessResponse> {
|
||||
return api.post<ApiSuccessResponse>(`/groups/${id}/members`, { user_ids: userIds });
|
||||
/** 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 removeMembers(id: number, userIds: number[]): Promise<ApiSuccessResponse> {
|
||||
return api.post<ApiSuccessResponse>(`/groups/${id}/members/remove`, { user_ids: userIds });
|
||||
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`);
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user