uchill/front_material/app/(protected)/feedback/page.tsx

270 lines
8.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'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" data-tour="feedback-root">
{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>
);
}