122 lines
3.9 KiB
TypeScript
122 lines
3.9 KiB
TypeScript
/**
|
|
* Блок «Календарь занятий» — обёртка над LessonsCalendar.
|
|
* Используется в Dashboard и других страницах.
|
|
*/
|
|
|
|
'use client';
|
|
|
|
import React from 'react';
|
|
import { format, startOfDay } from 'date-fns';
|
|
import { LessonsCalendar } from '@/components/dashboard/LessonsCalendar';
|
|
import { LoadingSpinner } from '@/components/common/LoadingSpinner';
|
|
|
|
export interface CalendarLesson {
|
|
id: number | string;
|
|
title?: string;
|
|
start_time: string;
|
|
end_time: string;
|
|
status?: string;
|
|
client?: number;
|
|
client_name?: string;
|
|
mentor_name?: string;
|
|
subject?: string;
|
|
}
|
|
|
|
export interface CalendarProps {
|
|
/** Занятия для отображения в календаре */
|
|
lessons: CalendarLesson[];
|
|
/** Идёт загрузка занятий */
|
|
lessonsLoading?: boolean;
|
|
/** Выбранная дата (подсветка в календаре) */
|
|
selectedDate: Date;
|
|
/** Клик по ячейке дня или по слоту */
|
|
onSelectSlot?: (date: Date) => void;
|
|
/** Клик по событию (занятию) */
|
|
onSelectEvent?: (lesson: { id: string }) => void;
|
|
/** Смена видимого месяца (start/end месяца) */
|
|
onMonthChange?: (start: Date, end: Date) => void;
|
|
/** Ментор — показывает ученика; студент — показывает предмет и ментора */
|
|
isMentor?: boolean;
|
|
/** Часовой пояс пользователя (например, 'UTC+8') */
|
|
userTimezone?: string;
|
|
}
|
|
|
|
export const Calendar: React.FC<CalendarProps> = ({
|
|
lessons,
|
|
lessonsLoading = false,
|
|
selectedDate,
|
|
onSelectSlot,
|
|
onSelectEvent,
|
|
onMonthChange,
|
|
isMentor = true,
|
|
userTimezone,
|
|
}) => {
|
|
const mappedLessons = React.useMemo(
|
|
() =>
|
|
lessons.map((lesson) => {
|
|
if (isMentor && lesson.client_name) {
|
|
return {
|
|
id: String(lesson.id),
|
|
title: lesson.title || 'Занятие',
|
|
start_time: lesson.start_time,
|
|
end_time: lesson.end_time,
|
|
status: (lesson.status || 'scheduled') as 'scheduled' | 'in_progress' | 'completed' | 'cancelled',
|
|
client: {
|
|
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(' ') || '',
|
|
},
|
|
};
|
|
}
|
|
const subject = lesson.subject || 'Занятие';
|
|
const mentorName = lesson.mentor_name || '';
|
|
const displayTitle = mentorName ? `${subject} — ${mentorName}` : subject;
|
|
return {
|
|
id: String(lesson.id),
|
|
title: displayTitle,
|
|
start_time: lesson.start_time,
|
|
end_time: lesson.end_time,
|
|
status: (lesson.status || 'scheduled') as 'scheduled' | 'in_progress' | 'completed' | 'cancelled',
|
|
client: undefined,
|
|
};
|
|
}),
|
|
[lessons, isMentor]
|
|
);
|
|
|
|
return (
|
|
<div
|
|
className="ios-glass-panel"
|
|
style={{
|
|
borderRadius: '20px',
|
|
padding: '24px',
|
|
height: '100%',
|
|
minHeight: 0,
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
}}
|
|
>
|
|
{lessonsLoading && lessons.length === 0 ? (
|
|
<LoadingSpinner size="medium" />
|
|
) : (
|
|
<LessonsCalendar
|
|
lessons={mappedLessons}
|
|
selectedDate={selectedDate}
|
|
userTimezone={userTimezone}
|
|
loading={lessonsLoading}
|
|
onSelectSlot={(date) => {
|
|
try {
|
|
const d = startOfDay(date);
|
|
if (!Number.isNaN(d.getTime())) onSelectSlot?.(d);
|
|
} catch {
|
|
/* игнор невалидной даты */
|
|
}
|
|
}}
|
|
onSelectEvent={onSelectEvent}
|
|
onMonthChange={onMonthChange}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|