// Пульт преподавателя. // // Компоновка от того, по чему его оценит действующий преподаватель: задать // сценарий и не вмешиваться, видеть работу курсанта вживую, получить готовый // разбор, показать группе (docs/arch/FRONTEND.md). import { useEffect, useRef, useState } from "react"; import { Debrief } from "@/features/debrief/Debrief"; import { Director } from "@/features/instructor/Director"; import { ScenarioEditor } from "@/features/instructor/ScenarioEditor"; import { useControl } from "@/features/instructor/useControl"; import { KioCard } from "@/features/kio-card/KioCard"; import { StandStatus } from "@/features/health/StandStatus"; import { ModeBanner } from "@/features/mode-banner/ModeBanner"; import { useObserve } from "@/features/observe/useObserve"; import { InterviewTimer, Timers } from "@/features/timers/Timers"; import { useScenarios, useSessions, useTrainees } from "@/shared/api/http"; import { sessionIdFromUrl } from "@/shared/api/session"; import type { SessionMode } from "@/shared/types/generated"; const MODES: { value: SessionMode; label: string }[] = [ { value: "training", label: "тренировочный" }, { value: "exam", label: "контрольный" }, { value: "self", label: "самостоятельный" }, ]; export function Instructor() { const [sessionId, setSessionId] = useState(sessionIdFromUrl()); const [scenarioId, setScenarioId] = useState(""); const [mode, setMode] = useState("training"); const [exercise, setExercise] = useState<"dds" | "call" | "card">("dds"); const [trainee, setTrainee] = useState(""); const [traineeId, setTraineeId] = useState(""); const [noteFor, setNoteFor] = useState(null); // Запускается только занятие, созданное этой кнопкой. Открытая из истории // ссылка на идущее занятие не должна запускать его заново и сбрасывать. const toStart = useRef(null); const [noteText, setNoteText] = useState(""); const scenarios = useScenarios(); const history = useSessions(); const trainees = useTrainees(); const session = useObserve(sessionId); const control = useControl(sessionId); useEffect(() => { if (scenarios.data?.length && !scenarioId) setScenarioId(scenarios.data[0].id); }, [scenarios.data, scenarioId]); function startLesson() { if (trainees.data?.length && !traineeId) return; const id = crypto.randomUUID(); // Номер занятия живёт в адресе: монитор и АРМ курсанта открываются ссылкой. window.history.replaceState(null, "", `/instructor?session=${id}`); toStart.current = id; setSessionId(id); } // Занятие запускается, когда канал управления открылся. useEffect(() => { if (!sessionId || control.status !== "open" || toStart.current !== sessionId) return; toStart.current = null; const selected = trainees.data?.find((item) => item.id === traineeId); control.start(scenarioId, mode, selected?.name || trainee || "Демо-курсант", exercise, undefined, selected?.id); }, [sessionId, control, scenarioId, mode, trainee, traineeId, trainees.data, exercise]); const links = sessionId ? { trainee: `${location.origin}/trainee?session=${sessionId}`, wall: `${location.origin}/wall?session=${sessionId}`, dds: `${location.origin}/dds?session=${sessionId}&role=dds_01`, } : null; return (

Рабочее место преподавателя

Занятие

Сценарий
Упражнение
Режим {MODES.map((item) => ( ))}
Курсант {trainees.data?.length ? ( ) : ( <> setTrainee(event.target.value)} /> Без учётной записи: личный отчёт курсанту недоступен. )}

{sessionId && (

{session.ended ? "занятие завершено" : ( )}

)} {sessionId && !session.ended && ( exercise === "call" && )} {links && (
АРМ курсантаоткрыть {exercise === "dds" ? "ДДС" : "112"}
Внешний монитороткрыть
АРМ ДДСоткрыть
)}

Ход занятия

{!sessionId &&

Занятие не запущено.

} {sessionId && ( <>

канал: {session.status} · подсказок использовано: {session.hintsUsed}

Транскрипт — клик по реплике добавляет пометку

{session.transcript.map((line) => ( setNoteFor(line.ref)} className="clickable"> ))} {session.transcript.length === 0 && }
{line.speaker === "caller" ? "звонящий" : "оператор"} {line.text} {session.notes.has(line.ref) && (
пометка: {session.notes.get(line.ref)}
)} {noteFor === line.ref && (
setNoteText(event.target.value)} />{" "}
)}
—

Нормативы

Карточка курсанта

{/* Только чтение: преподаватель управляет ситуацией, а не работой обучаемого. */} )}

История группы

профили курсантов

{history.data?.slice(0, 15).map((item) => ( ))} {!history.data?.length && }
{item.mode} · попытка {item.attempt} {item.scenario_id}
занятий ещё не было
{ setScenarioId(id); await scenarios.refetch(); }} /> {session.report && }
); }