'use client'; import React, { useState, useEffect, lazy, Suspense } from 'react'; const PdfFirstPagePreview = lazy(() => import('./PdfFirstPagePreview').then((m) => ({ default: m.PdfFirstPagePreview })) ); import { getMySubmission, getHomeworkSubmissions, gradeSubmission, returnSubmissionForRevision, deleteSubmission, checkSubmissionWithAi, type Homework, type HomeworkSubmission, type TokenUsage, } from '@/api/homework'; import { getBackendOrigin } from '@/lib/api-client'; import { SubmitHomeworkModal } from './SubmitHomeworkModal'; import { EditHomeworkDraftModal } from './EditHomeworkDraftModal'; interface HomeworkDetailsModalProps { isOpen: boolean; homework: Homework | null; userRole: string; /** Для родителя: user_id выбранного ребёнка — загрузить и показать его решение. */ childId?: string | null; onClose: () => void; onSuccess: () => void; } function formatDateTime(s: string | null): string { if (!s) return '—'; const d = new Date(s); return isNaN(d.getTime()) ? '—' : d.toLocaleString('ru-RU', { day: 'numeric', month: 'long', year: 'numeric', hour: '2-digit', minute: '2-digit' }); } function getFileUrl(path: string): string { if (!path) return ''; if (path.startsWith('http://') || path.startsWith('https://')) return path; const base = typeof window !== 'undefined' ? getBackendOrigin() : ''; return base + (path.startsWith('/') ? path : '/' + path); } const IMAGE_EXT = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg', 'ico']; /** Расширения документов, которые можно открыть внутри платформы (PDF — iframe, остальные — текст). */ const VIEWABLE_DOC_EXT: Record = { pdf: 'pdf', txt: 'text', py: 'text', md: 'text', json: 'text', csv: 'text', xml: 'text', html: 'text', js: 'text', ts: 'text', jsx: 'text', tsx: 'text', css: 'text', sql: 'text', yaml: 'text', yml: 'text', }; function isImageFilename(filename: string): boolean { const ext = (filename || '').split('.').pop()?.toLowerCase() || ''; return IMAGE_EXT.includes(ext); } function isImageUrl(url: string): boolean { const path = url.split('?')[0]; const name = path.split('/').pop() || ''; return isImageFilename(name); } function getDocViewType(url: string, filename?: string): 'pdf' | 'text' | null { const name = filename || url.split('?')[0].split('/').pop() || ''; const ext = name.split('.').pop()?.toLowerCase() || ''; return VIEWABLE_DOC_EXT[ext] ?? null; } /** Превью файла задания: для PDF — первая страница, для текста — первые строки + «Открыть». */ function AssignmentFilePreview({ url, label, type, onOpen, }: { url: string; label: string; type: 'pdf' | 'text'; onOpen: () => void; }) { const [mounted, setMounted] = useState(false); const [textPreview, setTextPreview] = useState(null); const [textLoading, setTextLoading] = useState(type === 'text'); useEffect(() => { setMounted(true); }, []); useEffect(() => { if (type !== 'text') return; setTextLoading(true); fetch(url, { credentials: 'include' }) .then((r) => (r.ok ? r.text() : '')) .then((t) => (t ? t.split('\n').slice(0, 8).join('\n') : '')) .then(setTextPreview) .catch(() => setTextPreview('')) .finally(() => setTextLoading(false)); }, [url, type]); const cardStyle: React.CSSProperties = { width: '100%', maxWidth: 320, padding: 14, borderRadius: 14, border: '1px solid var(--md-sys-color-outline-variant)', background: 'var(--md-sys-color-surface-variant)', textAlign: 'left', cursor: 'pointer', }; if (type === 'pdf') { const pdfPlaceholder = (
picture_as_pdf
); return ( ); } return ( ); } function DocumentViewerOverlay({ url, filename, type, onClose, }: { url: string; filename: string; type: 'pdf' | 'text'; onClose: () => void; }) { const [textContent, setTextContent] = useState(null); const [textError, setTextError] = useState(null); const [loading, setLoading] = useState(type === 'text'); useEffect(() => { if (type !== 'text') return; setLoading(true); setTextError(null); setTextContent(null); fetch(url, { credentials: 'include' }) .then((r) => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.text(); }) .then(setTextContent) .catch((e) => setTextError(e instanceof Error ? e.message : 'Не удалось загрузить файл')) .finally(() => setLoading(false)); }, [url, type]); return (
{filename}
e.stopPropagation()}> {type === 'pdf' && (