/** * Карточка урока для Dashboard */ 'use client'; import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { useRouter } from 'next/navigation'; import { LessonPreview } from '@/api/dashboard'; import { createLiveKitRoom } from '@/api/livekit'; import { useAuth } from '@/contexts/AuthContext'; import { parseISOToUserTimezone } from '@/utils/timezone'; interface LessonCardProps { lesson: LessonPreview; showMentor?: boolean; showClient?: boolean; onClick?: () => void; } /** Подключение доступно за 10 минут до начала и до 15 минут после окончания */ function canJoinLesson(lesson: LessonPreview): boolean { if (!lesson.start_time || !lesson.end_time) return false; if (lesson.status === 'cancelled') return false; const now = new Date(); const startTime = new Date(lesson.start_time); const endTime = new Date(lesson.end_time); const allowedStart = new Date(startTime.getTime() - 10 * 60 * 1000); // за 10 минут до начала const allowedEnd = new Date(endTime.getTime() + 15 * 60 * 1000); // до 15 минут после окончания return now >= allowedStart && now <= allowedEnd; } export const LessonCard: React.FC = ({ lesson, showMentor = false, showClient = false, onClick, }) => { const router = useRouter(); const { user } = useAuth(); const [connectLoading, setConnectLoading] = useState(false); const [canJoin, setCanJoin] = useState(false); // Проверяем каждые 10 секунд, чтобы кнопка появилась вовремя (решение проблемы с кэшированием) useEffect(() => { const check = () => setCanJoin(canJoinLesson(lesson)); check(); const interval = setInterval(check, 10000); // 10 секунд return () => clearInterval(interval); }, [lesson.start_time, lesson.end_time, lesson.status]); const handleConnect = useCallback( async (e: React.MouseEvent) => { e.stopPropagation(); if (!canJoin || connectLoading) return; setConnectLoading(true); try { const lessonId = typeof lesson.id === 'string' ? parseInt(lesson.id, 10) : lesson.id; const res = await createLiveKitRoom(lessonId); router.push( `/livekit/${res.room_name}?lesson_id=${lesson.id}&token=${encodeURIComponent(res.access_token)}` ); } catch (err) { console.error('LiveKit connect error:', err); setConnectLoading(false); } }, [canJoin, connectLoading, lesson.id, router] ); // Парсим время с учётом timezone пользователя const { startParsed, endParsed } = useMemo(() => { return { startParsed: parseISOToUserTimezone(lesson.start_time, user?.timezone), endParsed: parseISOToUserTimezone(lesson.end_time, user?.timezone), }; }, [lesson.start_time, lesson.end_time, user?.timezone]); const getStatusColor = (status: string) => { switch (status) { case 'completed': // более серый фон для завершённых занятий return 'color-mix(in srgb, var(--md-sys-color-surface-variant) 70%, #000 10%)'; case 'in_progress': return 'var(--md-sys-color-tertiary)'; case 'cancelled': return 'var(--md-sys-color-error)'; case 'scheduled': default: return 'var(--md-sys-color-primary)'; } }; const getStatusTextColor = (status: string) => { switch (status) { case 'completed': return 'var(--md-sys-color-on-surface-variant)'; case 'in_progress': return 'var(--md-sys-color-on-tertiary)'; case 'cancelled': return 'var(--md-sys-color-on-error)'; case 'scheduled': default: return 'var(--md-sys-color-on-primary)'; } }; const getStatusText = (status: string) => { switch (status) { case 'completed': return 'Завершено'; case 'in_progress': return 'В процессе'; case 'cancelled': return 'Отменено'; default: return 'Запланировано'; } }; const statusColor = getStatusColor(lesson.status); const textColor = getStatusTextColor(lesson.status); return (
{ if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onClick(); } } : undefined} style={{ background: statusColor, borderRadius: '16px', padding: '16px', marginBottom: '12px', transition: 'all 0.2s ease', cursor: 'pointer', position: 'relative', }} onMouseEnter={(e) => { e.currentTarget.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)'; e.currentTarget.style.transform = 'translateY(-2px)'; }} onMouseLeave={(e) => { e.currentTarget.style.boxShadow = 'none'; e.currentTarget.style.transform = 'translateY(0)'; }} >

{lesson.title}

{lesson.subject && (

{lesson.subject}

)}
{getStatusText(lesson.status)}
{startParsed.dateObj.toLocaleDateString('ru-RU', { day: 'numeric', month: 'short' })} {' в '} {startParsed.time} {' - '} {endParsed.time}
{(showMentor && lesson.mentor) && (
Ментор: {lesson.mentor.first_name} {lesson.mentor.last_name}
)} {(showClient && lesson.client) && (
Ученик: {lesson.client.first_name} {lesson.client.last_name}
)} {canJoin && (lesson.status === 'scheduled' || lesson.status === 'in_progress') && ( )}
); };