'use client'; import { useState, useEffect, useCallback, useRef } from 'react'; import { startOfDay, format, addDays, subDays, subMonths, addMonths, startOfMonth, endOfMonth, differenceInMinutes, } from 'date-fns'; import { ru } from 'date-fns/locale'; import { Calendar } from '@/components/calendar/calendar'; import { CheckLesson } from '@/components/checklesson/checklesson'; import { getLessonsCalendar, getLesson, createLesson, updateLesson, deleteLesson } from '@/api/schedule'; import { getStudents } from '@/api/students'; import { useAuth } from '@/contexts/AuthContext'; import { useSelectedChild } from '@/contexts/SelectedChildContext'; import { getSubjects, getMentorSubjects } from '@/api/subjects'; import { loadComponent } from '@/lib/material-components'; import { LoadingSpinner } from '@/components/common/LoadingSpinner'; import { ErrorDisplay } from '@/components/common/ErrorDisplay'; import type { CalendarLesson } from '@/components/calendar/calendar'; import type { CheckLessonFormData, CheckLessonProps } from '@/components/checklesson/checklesson'; import type { LessonPreview } from '@/api/dashboard'; import type { Student } from '@/api/students'; import type { Subject, MentorSubject } from '@/api/subjects'; export default function SchedulePage() { const { user } = useAuth(); const { selectedChild } = useSelectedChild(); const isMentor = user?.role === 'mentor'; const [selectedDate, setSelectedDate] = useState(startOfDay(new Date())); const [displayDate, setDisplayDate] = useState(startOfDay(new Date())); const [visibleMonth, setVisibleMonth] = useState(() => startOfMonth(new Date())); const [lessons, setLessons] = useState([]); const [lessonsLoading, setLessonsLoading] = useState(true); const [error, setError] = useState(null); const formDataLoadedRef = useRef(false); const hasLoadedLessonsOnceRef = useRef(false); // Форма const [isFormVisible, setIsFormVisible] = useState(false); const [isEditingMode, setIsEditingMode] = useState(false); const [formLoading, setFormLoading] = useState(false); const [formError, setFormError] = useState(null); const [formData, setFormData] = useState({ client: '', title: '', description: '', start_date: format(selectedDate, 'yyyy-MM-dd'), start_time: '14:00', duration: 60, price: undefined, is_recurring: false, }); const [selectedSubjectId, setSelectedSubjectId] = useState(null); const [selectedMentorSubjectId, setSelectedMentorSubjectId] = useState(null); const [editingLessonId, setEditingLessonId] = useState(null); // Компоненты Material Web const [buttonComponentsLoaded, setButtonComponentsLoaded] = useState(false); const [formComponentsLoaded, setFormComponentsLoaded] = useState(false); // Данные для формы const [students, setStudents] = useState([]); const [subjects, setSubjects] = useState([]); const [mentorSubjects, setMentorSubjects] = useState([]); const [lessonEditLoading, setLessonEditLoading] = useState(false); useEffect(() => { Promise.all([ loadComponent('elevated-card'), loadComponent('filled-button'), loadComponent('icon'), ]).then(() => { setButtonComponentsLoaded(true); }); Promise.all([ loadComponent('filled-button'), loadComponent('outlined-button'), loadComponent('text-field'), loadComponent('select'), loadComponent('switch'), ]).then(() => { setFormComponentsLoaded(true); }); }, []); useEffect(() => { if (!isFormVisible || formDataLoadedRef.current) return; (async () => { try { const [studentsResp, subjectsResp, mentorSubjectsResp] = await Promise.all([ getStudents({ page: 1, page_size: 200 }), getSubjects(), getMentorSubjects(), ]); setStudents(studentsResp.results || []); setSubjects(subjectsResp || []); setMentorSubjects(mentorSubjectsResp || []); formDataLoadedRef.current = true; } catch (err) { console.error('Error loading form data:', err); } })(); }, [isFormVisible]); const loadLessons = useCallback(async () => { const start = startOfMonth(subMonths(visibleMonth, 1)); const end = endOfMonth(addMonths(visibleMonth, 1)); const isInitial = !hasLoadedLessonsOnceRef.current; try { if (isInitial) setLessonsLoading(true); setError(null); const { lessons: lessonsData } = await getLessonsCalendar({ start_date: format(start, 'yyyy-MM-dd'), end_date: format(end, 'yyyy-MM-dd'), ...(selectedChild?.id && { child_id: selectedChild.id }), }); const mappedLessons: CalendarLesson[] = (lessonsData || []).map((lesson: any) => ({ id: lesson.id, title: lesson.title, start_time: lesson.start_time, end_time: lesson.end_time, status: lesson.status, client: lesson.client?.id, client_name: lesson.client_name ?? (lesson.client?.user ? `${lesson.client.user.first_name || ''} ${lesson.client.user.last_name || ''}`.trim() : undefined), subject: lesson.subject ?? lesson.subject_name ?? '', })); setLessons(mappedLessons); hasLoadedLessonsOnceRef.current = true; } catch (err: any) { console.error('Error loading lessons:', err); setError(err?.message || 'Ошибка загрузки занятий'); } finally { if (isInitial) setLessonsLoading(false); } }, [visibleMonth, selectedChild?.id]); useEffect(() => { loadLessons(); }, [loadLessons]); const handleMonthChange = useCallback((start: Date, _end: Date) => { const key = format(start, 'yyyy-MM'); if (key === format(visibleMonth, 'yyyy-MM')) return; setVisibleMonth(startOfMonth(start)); }, [visibleMonth]); const lessonsForSelectedDate: LessonPreview[] = lessons .filter((lesson) => { const lessonDate = startOfDay(new Date(lesson.start_time)); return lessonDate.getTime() === selectedDate.getTime(); }) .map((lesson) => ({ id: String(lesson.id), title: lesson.title || 'Занятие', subject: lesson.subject ?? '', start_time: lesson.start_time, end_time: lesson.end_time, status: (lesson.status || 'scheduled') as 'scheduled' | 'in_progress' | 'completed' | 'cancelled', client: lesson.client_name ? { id: String(lesson.client || ''), name: lesson.client_name, first_name: lesson.client_name.split(' ')[0] || lesson.client_name, last_name: lesson.client_name.split(' ').slice(1).join(' ') || '', } : undefined, })); const handleSelectSlot = (date: Date) => { const dayStart = startOfDay(date); setSelectedDate(dayStart); setDisplayDate(dayStart); setIsFormVisible(false); }; const handleSelectEvent = (lesson: { id: string }) => { if (isMentor) handleLessonClick(lesson); }; const handlePrevDay = () => { const prev = subDays(displayDate, 1); setDisplayDate(prev); setSelectedDate(startOfDay(prev)); setIsFormVisible(false); }; const handleNextDay = () => { const next = addDays(displayDate, 1); setDisplayDate(next); setSelectedDate(startOfDay(next)); setIsFormVisible(false); }; const handleAddLesson = () => { setIsEditingMode(false); setFormData({ client: '', title: '', description: '', start_date: format(selectedDate, 'yyyy-MM-dd'), start_time: '14:00', duration: 60, price: undefined, is_recurring: false, }); setSelectedSubjectId(null); setSelectedMentorSubjectId(null); setIsFormVisible(true); }; const handleLessonClick = (lesson: { id: string }) => { if (!isMentor) return; // Добавить/редактировать/просмотр — только для ментора setIsEditingMode(true); setIsFormVisible(true); setLessonEditLoading(true); setFormError(null); setEditingLessonId(lesson.id); (async () => { try { const details = await getLesson(String(lesson.id)); const start = new Date(details.start_time); const end = new Date(details.end_time); const safeStart = startOfDay(start); // синхронизируем правую панель с датой урока setSelectedDate(safeStart); setDisplayDate(safeStart); const duration = (() => { const mins = differenceInMinutes(end, start); return Number.isFinite(mins) && mins > 0 ? mins : 60; })(); setFormData({ client: details.client?.id ? String(details.client.id) : '', title: details.title ?? '', description: details.description ?? '', start_date: format(start, 'yyyy-MM-dd'), start_time: format(start, 'HH:mm'), duration, price: typeof details.price === 'number' ? details.price : undefined, is_recurring: !!(details as any).is_recurring, }); // пробуем выставить предмет по названию const subjName = (details as any).subject_name || (details as any).subject || ''; if (subjName) { const foundSubject = subjects.find((s) => s.name === subjName); const foundMentorSubject = mentorSubjects.find((s) => s.name === subjName); if (foundMentorSubject) { setSelectedSubjectId(null); setSelectedMentorSubjectId(foundMentorSubject.id); } else if (foundSubject) { setSelectedSubjectId(foundSubject.id); setSelectedMentorSubjectId(null); } else { setSelectedSubjectId(null); setSelectedMentorSubjectId(null); } } else { setSelectedSubjectId(null); setSelectedMentorSubjectId(null); } } catch (err: any) { console.error('Error loading lesson:', err); setFormError(err?.message || 'Не удалось загрузить данные занятия'); } finally { setLessonEditLoading(false); } })(); }; const handleSubjectChange = (subjectId: number | null, mentorSubjectId: number | null) => { setSelectedSubjectId(subjectId); setSelectedMentorSubjectId(mentorSubjectId); }; const getSubjectName = () => { if (selectedSubjectId) { const s = subjects.find((x) => x.id === selectedSubjectId); return s?.name ?? ''; } if (selectedMentorSubjectId) { const s = mentorSubjects.find((x) => x.id === selectedMentorSubjectId); return s?.name ?? ''; } return ''; }; const generateTitle = () => { const student = students.find((s) => String(s.id) === formData.client); const studentName = student ? `${student.user?.first_name || ''} ${student.user?.last_name || ''}`.trim() || student.user?.email : ''; const subjectName = getSubjectName(); if (studentName && subjectName) return `${subjectName} — ${studentName}`; if (studentName) return studentName; if (subjectName) return subjectName; return formData.title || 'Занятие'; }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setFormLoading(true); setFormError(null); try { if (!formData.client) { setFormError('Выберите ученика'); setFormLoading(false); return; } if (!selectedSubjectId && !selectedMentorSubjectId) { setFormError('Выберите предмет'); setFormLoading(false); return; } if (!formData.start_date || !formData.start_time) { setFormError('Укажите дату и время'); setFormLoading(false); return; } if (formData.price == null || formData.price < 0) { setFormError('Укажите стоимость занятия'); setFormLoading(false); return; } const startUtc = new Date(`${formData.start_date}T${formData.start_time}`).toISOString(); const title = generateTitle(); if (isEditingMode && editingLessonId) { await updateLesson(editingLessonId, { title, description: formData.description, start_time: startUtc, duration: formData.duration, price: formData.price, }); } else { const payload: any = { client: formData.client, title, description: formData.description, start_time: startUtc, duration: formData.duration, price: formData.price, is_recurring: formData.is_recurring, }; if (selectedSubjectId) payload.subject_id = selectedSubjectId; else if (selectedMentorSubjectId) payload.mentor_subject_id = selectedMentorSubjectId; await createLesson(payload); } setIsFormVisible(false); setEditingLessonId(null); loadLessons(); } catch (err: any) { const msg = err?.response?.data ? typeof err.response.data === 'object' ? Object.entries(err.response.data) .map(([k, v]) => `${k}: ${Array.isArray(v) ? v.join(', ') : v}`) .join('\n') : String(err.response.data) : err?.message || 'Ошибка сохранения занятия'; setFormError(msg); } finally { setFormLoading(false); } }; const handleDelete = async (deleteAllFuture: boolean) => { if (!editingLessonId) return; setFormLoading(true); setFormError(null); try { await deleteLesson(editingLessonId, deleteAllFuture); setIsFormVisible(false); setEditingLessonId(null); loadLessons(); } catch (err: any) { setFormError(err?.message || 'Ошибка удаления занятия'); } finally { setFormLoading(false); } }; const handleCancel = () => { setIsFormVisible(false); setIsEditingMode(false); setFormError(null); setEditingLessonId(null); }; return (
{error && }
); }