261 lines
8.5 KiB
TypeScript
261 lines
8.5 KiB
TypeScript
/**
|
|
* API модуль для расписания занятий
|
|
*/
|
|
|
|
import apiClient from '@/lib/api-client';
|
|
|
|
export interface Lesson {
|
|
id: string;
|
|
title: string;
|
|
subject?: string;
|
|
description?: string;
|
|
start_time: string;
|
|
end_time: string;
|
|
status?: 'scheduled' | 'in_progress' | 'completed' | 'cancelled';
|
|
mentor?: {
|
|
id: string;
|
|
first_name: string;
|
|
last_name: string;
|
|
email: string;
|
|
};
|
|
client?: {
|
|
id: string;
|
|
user?: {
|
|
id: string;
|
|
first_name: string;
|
|
last_name: string;
|
|
email: string;
|
|
};
|
|
};
|
|
client_name?: string;
|
|
mentor_notes?: string;
|
|
mentor_grade?: number;
|
|
school_grade?: number;
|
|
homework_text?: string;
|
|
price?: number;
|
|
meeting_url?: string;
|
|
duration?: number;
|
|
group?: number;
|
|
group_name?: string;
|
|
livekit_room_name?: string;
|
|
completed_at?: string;
|
|
}
|
|
|
|
/** Файл урока (для экрана завершения занятия) */
|
|
export interface LessonFile {
|
|
id: string | number;
|
|
lesson: string | number;
|
|
file?: string;
|
|
material?: string | number;
|
|
source?: 'uploaded' | 'material';
|
|
filename: string;
|
|
file_size?: number;
|
|
file_size_display?: string;
|
|
file_url?: string;
|
|
description?: string;
|
|
uploaded_by?: number;
|
|
uploaded_by_name?: string;
|
|
created_at?: string;
|
|
}
|
|
|
|
export interface CreateLessonFileData {
|
|
lesson: string;
|
|
file?: File;
|
|
material?: string;
|
|
filename?: string;
|
|
description?: string;
|
|
}
|
|
|
|
/**
|
|
* Получить список занятий
|
|
* Для родителя передать child_id (user_id ребёнка).
|
|
* Для ментора передать client_id (Client.id) — занятия конкретного студента.
|
|
*/
|
|
export async function getLessons(params?: {
|
|
start_date?: string;
|
|
end_date?: string;
|
|
status?: string;
|
|
child_id?: string;
|
|
client_id?: string;
|
|
}): Promise<{ results: Lesson[]; count?: number }> {
|
|
const queryParams = new URLSearchParams();
|
|
if (params?.start_date) queryParams.append('start_date', params.start_date);
|
|
if (params?.end_date) queryParams.append('end_date', params.end_date);
|
|
if (params?.status) queryParams.append('status', params.status);
|
|
if (params?.child_id) queryParams.append('child_id', params.child_id);
|
|
if (params?.client_id) queryParams.append('client_id', params.client_id);
|
|
|
|
const queryString = queryParams.toString();
|
|
const url = `/schedule/lessons/${queryString ? `?${queryString}` : ''}`;
|
|
const response = await apiClient.get<Lesson[] | { results: Lesson[]; count?: number }>(url);
|
|
|
|
if (Array.isArray(response.data)) {
|
|
return { results: response.data };
|
|
}
|
|
return response.data;
|
|
}
|
|
|
|
/** Ответ calendar API */
|
|
interface CalendarResponse {
|
|
success: boolean;
|
|
data: { start_date: string; end_date: string; lessons: Lesson[]; total: number };
|
|
}
|
|
|
|
/**
|
|
* Занятия для календаря (лёгкий endpoint по диапазону дат).
|
|
* Для родителя передать child_id (user_id ребёнка).
|
|
*/
|
|
export async function getLessonsCalendar(params: {
|
|
start_date: string;
|
|
end_date: string;
|
|
status?: string;
|
|
child_id?: string;
|
|
}): Promise<{ lessons: Lesson[] }> {
|
|
const q = new URLSearchParams({ start_date: params.start_date, end_date: params.end_date });
|
|
if (params.status) q.append('status', params.status);
|
|
if (params.child_id) q.append('child_id', params.child_id);
|
|
// cache: false — после создания/редактирования/удаления занятия интерфейс должен обновиться без перезагрузки
|
|
const res = await apiClient.get<CalendarResponse>(`/schedule/lessons/calendar/?${q}`, { cache: false });
|
|
const lessons = res.data?.data?.lessons;
|
|
return { lessons: Array.isArray(lessons) ? lessons : [] };
|
|
}
|
|
|
|
/**
|
|
* Получить занятие по ID
|
|
*/
|
|
export async function getLesson(id: string): Promise<Lesson> {
|
|
const response = await apiClient.get<Lesson>(`/schedule/lessons/${id}/`);
|
|
return response.data;
|
|
}
|
|
|
|
export interface CreateLessonData {
|
|
client: string;
|
|
title?: string;
|
|
description?: string;
|
|
start_time: string;
|
|
duration: number;
|
|
price?: number;
|
|
is_recurring?: boolean;
|
|
subject_id?: number;
|
|
mentor_subject_id?: number;
|
|
subject_name?: string;
|
|
}
|
|
|
|
export interface UpdateLessonData {
|
|
title?: string;
|
|
description?: string;
|
|
start_time?: string;
|
|
duration?: number;
|
|
price?: number;
|
|
/** Для завершённых занятий — можно изменить статус (cancelled и т.д.) */
|
|
status?: 'scheduled' | 'in_progress' | 'completed' | 'cancelled';
|
|
}
|
|
|
|
/**
|
|
* Создать занятие
|
|
*/
|
|
export async function createLesson(data: CreateLessonData): Promise<Lesson> {
|
|
const response = await apiClient.post<Lesson>('/schedule/lessons/', data);
|
|
return response.data;
|
|
}
|
|
|
|
/**
|
|
* Обновить занятие
|
|
*/
|
|
export async function updateLesson(id: string, data: UpdateLessonData): Promise<Lesson> {
|
|
const response = await apiClient.patch<Lesson>(`/schedule/lessons/${id}/`, data);
|
|
return response.data;
|
|
}
|
|
|
|
/**
|
|
* Удалить занятие
|
|
*/
|
|
export async function deleteLesson(id: string, deleteAllFuture = false): Promise<void> {
|
|
await apiClient.delete(`/schedule/lessons/${id}/`, {
|
|
data: { delete_all_future: deleteAllFuture },
|
|
});
|
|
}
|
|
|
|
/** Ответ API завершения занятия */
|
|
export interface CompleteLessonResponse {
|
|
success: boolean;
|
|
message?: string;
|
|
data?: Lesson;
|
|
}
|
|
|
|
/**
|
|
* Завершить занятие / обновить обратную связь.
|
|
* lessonFileIds — ID файлов урока, которые нужно привязать к ДЗ (только они попадут в «Файлы задания»).
|
|
*/
|
|
export async function completeLesson(
|
|
id: string,
|
|
notes?: string,
|
|
mentorGrade?: number,
|
|
schoolGrade?: number,
|
|
homeworkText?: string,
|
|
hasHomeworkFiles?: boolean,
|
|
lessonFileIds?: number[]
|
|
): Promise<CompleteLessonResponse> {
|
|
const body: Record<string, unknown> = {
|
|
notes: notes ?? '',
|
|
mentor_grade: mentorGrade,
|
|
school_grade: schoolGrade,
|
|
homework_text: homeworkText,
|
|
has_homework_files: hasHomeworkFiles,
|
|
};
|
|
if (lessonFileIds != null) {
|
|
body.lesson_file_ids = lessonFileIds;
|
|
}
|
|
const response = await apiClient.post<CompleteLessonResponse>(`/schedule/lessons/${id}/complete/`, body);
|
|
return response.data;
|
|
}
|
|
|
|
/**
|
|
* Получить файлы урока (для экрана завершения занятия).
|
|
*/
|
|
export async function getLessonFiles(lessonId: string): Promise<LessonFile[]> {
|
|
const response = await apiClient.get<LessonFile[] | { results: LessonFile[] }>(
|
|
`/schedule/lesson-files/?lesson=${lessonId}`
|
|
);
|
|
const data = response.data;
|
|
if (Array.isArray(data)) return data;
|
|
return (data as { results: LessonFile[] })?.results ?? [];
|
|
}
|
|
|
|
/**
|
|
* Создать файл урока (загрузка файла или привязка материала).
|
|
*/
|
|
export async function createLessonFile(data: CreateLessonFileData): Promise<LessonFile> {
|
|
const formData = new FormData();
|
|
formData.append('lesson', data.lesson);
|
|
if (data.file) formData.append('file', data.file);
|
|
if (data.material) formData.append('material', data.material);
|
|
if (data.filename) formData.append('filename', data.filename);
|
|
if (data.description) formData.append('description', data.description);
|
|
const response = await apiClient.post<LessonFile>('/schedule/lesson-files/', formData, {
|
|
headers: { 'Content-Type': 'multipart/form-data' },
|
|
});
|
|
return response.data;
|
|
}
|
|
|
|
/**
|
|
* Удалить файл урока.
|
|
*/
|
|
export async function deleteLessonFile(fileId: string): Promise<void> {
|
|
await apiClient.delete(`/schedule/lesson-files/${fileId}/`);
|
|
}
|
|
|
|
/**
|
|
* Прикрепить файл к уроку (для ДЗ при завершении занятия).
|
|
* Возвращает созданный LessonFile (нужен id для передачи в complete как lesson_file_ids).
|
|
*/
|
|
export async function uploadLessonFile(lessonId: number | string, file: File): Promise<LessonFile> {
|
|
const formData = new FormData();
|
|
formData.append('lesson', String(lessonId));
|
|
formData.append('file', file);
|
|
const response = await apiClient.post<LessonFile>('/schedule/lesson-files/', formData, {
|
|
headers: { 'Content-Type': 'multipart/form-data' },
|
|
});
|
|
return response.data;
|
|
}
|