270 lines
9.1 KiB
TypeScript
270 lines
9.1 KiB
TypeScript
'use client';
|
||
|
||
import React, { useState, useEffect, useMemo } from 'react';
|
||
import { useAuth } from '@/contexts/AuthContext';
|
||
import { getLessons, type Lesson } from '@/api/schedule';
|
||
import { FeedbackModal } from '@/components/schedule/FeedbackModal';
|
||
import { DashboardLayout } from '@/components/dashboard/ui';
|
||
import { LoadingSpinner } from '@/components/common/LoadingSpinner';
|
||
|
||
function getSubjectName(lesson: Lesson): string {
|
||
if (typeof lesson.subject === 'string') return lesson.subject;
|
||
if (lesson.subject && typeof lesson.subject === 'object' && 'name' in lesson.subject) {
|
||
return (lesson.subject as { name: string }).name;
|
||
}
|
||
return (lesson as { subject_name?: string }).subject_name || 'Занятие';
|
||
}
|
||
|
||
function formatDate(s: string) {
|
||
return new Date(s).toLocaleDateString('ru-RU', { day: 'numeric', month: 'long', year: 'numeric' });
|
||
}
|
||
|
||
function formatTime(s: string) {
|
||
return new Date(s).toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' });
|
||
}
|
||
|
||
export default function FeedbackPage() {
|
||
const { user, loading: authLoading } = useAuth();
|
||
const [lessons, setLessons] = useState<Lesson[]>([]);
|
||
const [loading, setLoading] = useState(true);
|
||
const [error, setError] = useState<string | null>(null);
|
||
const [selectedLesson, setSelectedLesson] = useState<Lesson | null>(null);
|
||
const [showModal, setShowModal] = useState(false);
|
||
|
||
useEffect(() => {
|
||
if (user?.role !== 'mentor') return;
|
||
let cancelled = false;
|
||
(async () => {
|
||
try {
|
||
setLoading(true);
|
||
const res = await getLessons({ status: 'completed' });
|
||
const list = Array.isArray(res) ? res : res?.results || [];
|
||
if (!cancelled) setLessons(list);
|
||
} catch (e) {
|
||
if (!cancelled) setError(e instanceof Error ? e.message : 'Ошибка загрузки');
|
||
} finally {
|
||
if (!cancelled) setLoading(false);
|
||
}
|
||
})();
|
||
return () => { cancelled = true; };
|
||
}, [user?.role]);
|
||
|
||
const studentLessons = useMemo(
|
||
() => lessons.filter((l) => !(l as { group?: number }).group),
|
||
[lessons]
|
||
);
|
||
|
||
const getFeedbackStatus = (l: Lesson) => {
|
||
const has = !!(
|
||
l.mentor_grade ||
|
||
(l as { school_grade?: number }).school_grade ||
|
||
(l.mentor_notes && l.mentor_notes.trim().length > 0)
|
||
);
|
||
return has ? 'done' : 'todo';
|
||
};
|
||
|
||
const todoLessons = studentLessons.filter((l) => getFeedbackStatus(l) === 'todo');
|
||
const doneLessons = studentLessons.filter((l) => getFeedbackStatus(l) === 'done');
|
||
|
||
const openFeedback = (lesson: Lesson) => {
|
||
setSelectedLesson(lesson);
|
||
setShowModal(true);
|
||
};
|
||
|
||
const handleSuccess = async () => {
|
||
setSelectedLesson(null);
|
||
setShowModal(false);
|
||
try {
|
||
const res = await getLessons({ status: 'completed' });
|
||
const list = Array.isArray(res) ? res : res?.results || [];
|
||
setLessons(list);
|
||
} catch {
|
||
// ignore
|
||
}
|
||
};
|
||
|
||
const loadLessons = async () => {
|
||
try {
|
||
const res = await getLessons({ status: 'completed' });
|
||
const list = Array.isArray(res) ? res : res?.results || [];
|
||
setLessons(list);
|
||
} catch {
|
||
// ignore
|
||
}
|
||
};
|
||
|
||
if (authLoading) {
|
||
return (
|
||
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '50vh' }}>
|
||
<LoadingSpinner size="large" />
|
||
</div>
|
||
);
|
||
}
|
||
|
||
if (user?.role !== 'mentor') {
|
||
return (
|
||
<DashboardLayout>
|
||
<div
|
||
style={{
|
||
padding: 24,
|
||
textAlign: 'center',
|
||
color: 'var(--md-sys-color-on-surface-variant)',
|
||
}}
|
||
>
|
||
Страница доступна только менторам
|
||
</div>
|
||
</DashboardLayout>
|
||
);
|
||
}
|
||
|
||
const LessonCard = ({
|
||
lesson,
|
||
onFill,
|
||
}: {
|
||
lesson: Lesson;
|
||
onFill: () => void;
|
||
}) => {
|
||
const clientName =
|
||
typeof lesson.client === 'object' && lesson.client?.user
|
||
? `${lesson.client.user.first_name} ${lesson.client.user.last_name}`
|
||
: (lesson as { client_name?: string }).client_name || 'Студент';
|
||
|
||
return (
|
||
<div
|
||
className="ios26-panel"
|
||
style={{
|
||
padding: 16,
|
||
cursor: 'pointer',
|
||
transition: 'box-shadow 0.2s',
|
||
}}
|
||
onClick={onFill}
|
||
>
|
||
<span
|
||
style={{
|
||
display: 'inline-block',
|
||
fontSize: 11,
|
||
fontWeight: 600,
|
||
padding: '4px 10px',
|
||
borderRadius: 8,
|
||
background: 'var(--md-sys-color-primary-container)',
|
||
color: 'var(--md-sys-color-primary)',
|
||
marginBottom: 8,
|
||
}}
|
||
>
|
||
{getSubjectName(lesson)}
|
||
</span>
|
||
<h4 style={{ fontSize: 15, fontWeight: 600, margin: '0 0 8px 0', color: 'var(--md-sys-color-on-surface)' }}>
|
||
{lesson.title}
|
||
</h4>
|
||
<div style={{ fontSize: 13, color: 'var(--md-sys-color-on-surface-variant)' }}>
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 4 }}>
|
||
<span className="material-symbols-outlined" style={{ fontSize: 16 }}>person</span>
|
||
{clientName}
|
||
</div>
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 4 }}>
|
||
<span className="material-symbols-outlined" style={{ fontSize: 16 }}>calendar_today</span>
|
||
{formatDate(lesson.start_time)}
|
||
</div>
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||
<span className="material-symbols-outlined" style={{ fontSize: 16 }}>schedule</span>
|
||
{formatTime(lesson.start_time)} — {formatTime(lesson.end_time)}
|
||
</div>
|
||
{lesson.mentor_grade != null && (
|
||
<div style={{ marginTop: 8, fontWeight: 600, color: 'var(--md-sys-color-primary)' }}>
|
||
Оценка: {lesson.mentor_grade}/5
|
||
</div>
|
||
)}
|
||
</div>
|
||
<button
|
||
type="button"
|
||
style={{
|
||
marginTop: 12,
|
||
width: '100%',
|
||
padding: '10px 16px',
|
||
borderRadius: 12,
|
||
border: 'none',
|
||
background: lesson.mentor_notes || lesson.mentor_grade != null ? 'var(--md-sys-color-primary-container)' : 'var(--md-sys-color-primary)',
|
||
color: lesson.mentor_notes || lesson.mentor_grade != null ? 'var(--md-sys-color-primary)' : 'var(--md-sys-color-on-primary)',
|
||
fontSize: 14,
|
||
fontWeight: 600,
|
||
cursor: 'pointer',
|
||
}}
|
||
onClick={(e) => {
|
||
e.stopPropagation();
|
||
onFill();
|
||
}}
|
||
>
|
||
{lesson.mentor_notes || lesson.mentor_grade != null ? 'Редактировать' : 'Заполнить'}
|
||
</button>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
return (
|
||
<DashboardLayout className="ios26-dashboard ios26-feedback-page">
|
||
{error && (
|
||
<div
|
||
style={{
|
||
padding: 16,
|
||
marginBottom: 16,
|
||
background: 'rgba(186,26,26,0.1)',
|
||
borderRadius: 12,
|
||
color: 'var(--md-sys-color-error)',
|
||
}}
|
||
>
|
||
{error}
|
||
</div>
|
||
)}
|
||
|
||
{loading ? (
|
||
<div style={{ display: 'flex', justifyContent: 'center', padding: 48 }}>
|
||
<LoadingSpinner size="medium" />
|
||
</div>
|
||
) : (
|
||
<div className="ios26-feedback-kanban">
|
||
<div className="ios26-feedback-column">
|
||
<h3 className="ios26-feedback-column__title">
|
||
Ожидают {todoLessons.length > 0 ? `(${todoLessons.length})` : ''}
|
||
</h3>
|
||
<div className="ios26-feedback-column__cards">
|
||
{todoLessons.map((l) => (
|
||
<LessonCard key={l.id} lesson={l} onFill={() => openFeedback(l)} />
|
||
))}
|
||
{todoLessons.length === 0 && (
|
||
<p style={{ fontSize: 14, color: 'var(--md-sys-color-on-surface-variant)', padding: 16 }}>
|
||
Нет занятий
|
||
</p>
|
||
)}
|
||
</div>
|
||
</div>
|
||
<div className="ios26-feedback-column">
|
||
<h3 className="ios26-feedback-column__title">
|
||
Заполнено {doneLessons.length > 0 ? `(${doneLessons.length})` : ''}
|
||
</h3>
|
||
<div className="ios26-feedback-column__cards">
|
||
{doneLessons.map((l) => (
|
||
<LessonCard key={l.id} lesson={l} onFill={() => openFeedback(l)} />
|
||
))}
|
||
{doneLessons.length === 0 && (
|
||
<p style={{ fontSize: 14, color: 'var(--md-sys-color-on-surface-variant)', padding: 16 }}>
|
||
Нет занятий
|
||
</p>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
<FeedbackModal
|
||
isOpen={showModal}
|
||
lesson={selectedLesson}
|
||
onClose={() => {
|
||
setShowModal(false);
|
||
setSelectedLesson(null);
|
||
}}
|
||
onSuccess={handleSuccess}
|
||
/>
|
||
</DashboardLayout>
|
||
);
|
||
}
|