uchill/front_material/components/homework/HomeworkPageContent.tsx

292 lines
11 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, useCallback, useMemo } from 'react';
import { useAuth } from '@/contexts/AuthContext';
import { useSelectedChild } from '@/contexts/SelectedChildContext';
import {
getHomework,
getHomeworkById,
type Homework,
} from '@/api/homework';
import { HomeworkDetailsModal } from './HomeworkDetailsModal';
import { SubmitHomeworkModal } from './SubmitHomeworkModal';
import { DashboardLayout } from '@/components/dashboard/ui';
import { LoadingSpinner } from '@/components/common/LoadingSpinner';
function formatDate(s: string | null): string {
if (!s) return '—';
const d = new Date(s);
return isNaN(d.getTime()) ? '—' : d.toLocaleDateString('ru-RU', { day: 'numeric', month: 'long', year: 'numeric' });
}
function formatTime(s: string | null): string {
if (!s) return '—';
const d = new Date(s);
return isNaN(d.getTime()) ? '—' : d.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' });
}
function getHomeworkStatus(hw: Homework): 'pending' | 'submitted' | 'returned' | 'reviewed' {
if (hw.status !== 'published') return 'pending';
if (hw.checked_submissions > 0 && hw.checked_submissions === hw.total_submissions) return 'reviewed';
if (hw.returned_submissions > 0 && hw.returned_submissions === hw.total_submissions) return 'returned';
if (hw.total_submissions > 0) return 'submitted';
return 'pending';
}
export function HomeworkPageContent() {
const { user } = useAuth();
const { selectedChild } = useSelectedChild();
const [homework, setHomework] = useState<Homework[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [selectedHomework, setSelectedHomework] = useState<Homework | null>(null);
const [detailsOpen, setDetailsOpen] = useState(false);
const [detailsLoading, setDetailsLoading] = useState(false);
const [submitId, setSubmitId] = useState<number | null>(null);
const [submitOpen, setSubmitOpen] = useState(false);
const loadHomework = useCallback(async () => {
try {
setLoading(true);
const res = await getHomework({
page_size: 1000,
...(user?.role === 'parent' && selectedChild?.id && { child_id: selectedChild.id }),
});
setHomework(res.results);
} catch (e) {
setError(e instanceof Error ? e.message : 'Ошибка загрузки');
} finally {
setLoading(false);
}
}, [user?.role, selectedChild?.id]);
useEffect(() => {
loadHomework();
}, [loadHomework]);
const userRole = user?.role ?? '';
const pending = useMemo(() => homework.filter((hw) => getHomeworkStatus(hw) === 'pending' && hw.status === 'published'), [homework]);
const submitted = useMemo(() => homework.filter((hw) => getHomeworkStatus(hw) === 'submitted'), [homework]);
const returned = useMemo(() => homework.filter((hw) => getHomeworkStatus(hw) === 'returned'), [homework]);
const reviewed = useMemo(() => homework.filter((hw) => getHomeworkStatus(hw) === 'reviewed'), [homework]);
/** Только для ментора: черновики «заполнить позже» — ожидают заполнения задания. */
const fillLater = useMemo(
() => (userRole === 'mentor' ? homework.filter((hw) => hw.fill_later === true) : []),
[homework, userRole]
);
/** Только для ментора: задания, у которых есть хотя бы одно решение с черновиком от ИИ. */
const aiDraft = useMemo(
() => (userRole === 'mentor' ? homework.filter((hw) => (hw.ai_draft_count ?? 0) > 0) : []),
[homework, userRole]
);
const handleViewDetails = useCallback(async (hw: Homework) => {
try {
setDetailsLoading(true);
const full = await getHomeworkById(hw.id);
setSelectedHomework(full);
setDetailsOpen(true);
} catch {
setSelectedHomework(hw);
setDetailsOpen(true);
} finally {
setDetailsLoading(false);
}
}, []);
const handleSubmit = useCallback((hw: Homework) => {
setSubmitId(hw.id);
setSubmitOpen(true);
}, []);
const HomeworkCard = ({
hw,
badge,
onView,
onSubmit,
}: {
hw: Homework;
badge: string;
onView: () => void;
onSubmit?: () => void;
}) => (
<div
className="ios26-panel"
style={{ padding: 16, cursor: 'pointer' }}
onClick={onView}
>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
<span
style={{
display: 'inline-block',
fontSize: 11,
fontWeight: 600,
padding: '4px 10px',
borderRadius: 8,
background: hw.is_overdue ? 'rgba(186,26,26,0.15)' : 'var(--md-sys-color-primary-container)',
color: hw.is_overdue ? 'var(--md-sys-color-error)' : 'var(--md-sys-color-primary)',
}}
>
{badge}
</span>
</div>
<h4 style={{ fontSize: 15, fontWeight: 600, margin: '0 0 8px 0', color: 'var(--md-sys-color-on-surface)' }}>
{hw.title}
</h4>
<div style={{ fontSize: 13, color: 'var(--md-sys-color-on-surface-variant)' }}>
{userRole === 'client' && (
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 4 }}>
<span className="material-symbols-outlined" style={{ fontSize: 16 }}>person</span>
{hw.mentor.first_name} {hw.mentor.last_name}
</div>
)}
{userRole === 'mentor' && hw.total_submissions > 0 && (
<div style={{ marginBottom: 4 }}>Решений: {hw.total_submissions}</div>
)}
{hw.deadline && (
<>
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 4 }}>
<span className="material-symbols-outlined" style={{ fontSize: 16 }}>calendar_today</span>
{formatDate(hw.deadline)} {formatTime(hw.deadline)}
</div>
</>
)}
{hw.student_score?.score != null && (
<div style={{ marginTop: 8, fontWeight: 600, color: 'var(--md-sys-color-primary)' }}>
Оценка: {hw.student_score.score} / 5
</div>
)}
</div>
{userRole === 'client' && onSubmit && getHomeworkStatus(hw) === 'pending' && (
<button
type="button"
style={{
marginTop: 12,
width: '100%',
padding: '10px 16px',
borderRadius: 12,
border: 'none',
background: 'var(--md-sys-color-primary)',
color: 'var(--md-sys-color-on-primary)',
fontSize: 14,
fontWeight: 600,
cursor: 'pointer',
}}
onClick={(e) => {
e.stopPropagation();
onSubmit();
}}
>
Сдать ДЗ
</button>
)}
</div>
);
const Column = ({
title,
count,
items,
getBadge,
}: {
title: string;
count: number;
items: Homework[];
getBadge: (hw: Homework) => string;
}) => (
<div className="ios26-feedback-column">
<h3 className="ios26-feedback-column__title">
{title} {count > 0 ? `(${count})` : ''}
</h3>
<div className="ios26-feedback-column__cards">
{items.map((hw) => (
<HomeworkCard
key={hw.id}
hw={hw}
badge={getBadge(hw)}
onView={() => handleViewDetails(hw)}
onSubmit={userRole === 'client' ? () => handleSubmit(hw) : undefined}
/>
))}
{items.length === 0 && (
<p style={{ fontSize: 14, color: 'var(--md-sys-color-on-surface-variant)', padding: 16 }}>
Нет заданий
</p>
)}
</div>
</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>
) : homework.length === 0 ? (
<p style={{ fontSize: 16, color: 'var(--md-sys-color-on-surface-variant)', padding: 24 }}>
Нет заданий
</p>
) : (
<div className="ios26-feedback-kanban ios26-homework-kanban">
{userRole === 'mentor' && fillLater.length > 0 && (
<Column title="Ожидают заполнения" count={fillLater.length} items={fillLater} getBadge={() => 'Заполнить позже'} />
)}
{pending.length > 0 && (
<Column title="Ожидают" count={pending.length} items={pending} getBadge={(hw) => (hw.is_overdue ? 'Просрочено' : 'Домашнее задание')} />
)}
{submitted.length > 0 && (
<Column title="На проверке" count={submitted.length} items={submitted} getBadge={() => 'На проверке'} />
)}
{userRole === 'mentor' && aiDraft.length > 0 && (
<Column title="Черновик от ИИ" count={aiDraft.length} items={aiDraft} getBadge={() => 'Черновик от ИИ'} />
)}
{returned.length > 0 && (
<Column title="На доработке" count={returned.length} items={returned} getBadge={() => 'На доработке'} />
)}
{reviewed.length > 0 && (
<Column title="Проверено" count={reviewed.length} items={reviewed} getBadge={() => 'Проверено'} />
)}
</div>
)}
<HomeworkDetailsModal
isOpen={detailsOpen}
homework={selectedHomework}
userRole={userRole}
childId={userRole === 'parent' ? selectedChild?.id ?? null : null}
onClose={() => {
setDetailsOpen(false);
setSelectedHomework(null);
}}
onSuccess={loadHomework}
/>
<SubmitHomeworkModal
isOpen={submitOpen}
homeworkId={submitId ?? 0}
onClose={() => {
setSubmitOpen(false);
setSubmitId(null);
}}
onSuccess={loadHomework}
/>
</DashboardLayout>
);
}