refactor: useControl.start принимает объект параметров вместо 14 позиционных
This commit is contained in:
parent
f6d0653e68
commit
3dc116534d
6 changed files with 118 additions and 33 deletions
|
|
@ -12,7 +12,9 @@
|
||||||
"check:a11y": "node scripts/check-accessible-controls.mjs",
|
"check:a11y": "node scripts/check-accessible-controls.mjs",
|
||||||
"test:ws-outbox": "node scripts/test-ws-outbox.mjs",
|
"test:ws-outbox": "node scripts/test-ws-outbox.mjs",
|
||||||
"test:dds-history": "node scripts/test-dds-history.mjs",
|
"test:dds-history": "node scripts/test-dds-history.mjs",
|
||||||
"test:dds-ack": "node scripts/test-dds-ack.mjs"
|
"test:dds-ack": "node scripts/test-dds-ack.mjs",
|
||||||
|
"test:session-report": "node scripts/test-session-report.mjs",
|
||||||
|
"test:scenario-start": "node scripts/test-scenario-start.mjs"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/react-query": "^5.103.1",
|
"@tanstack/react-query": "^5.103.1",
|
||||||
|
|
|
||||||
31
frontend/scripts/test-scenario-start.mjs
Normal file
31
frontend/scripts/test-scenario-start.mjs
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
|
||||||
|
import { scenarioStartMessage } from "../src/features/instructor/start.mjs";
|
||||||
|
|
||||||
|
const minimal = JSON.parse(JSON.stringify(scenarioStartMessage({
|
||||||
|
scenarioId: "s1", mode: "exam", trainee: "Иванов",
|
||||||
|
})));
|
||||||
|
assert.deepEqual(minimal, {
|
||||||
|
type: "scenario.start", scenario_id: "s1", mode: "exam", trainee: "Иванов",
|
||||||
|
group_id: null, trainee_id: null, dds_service: null, exercise: "dds", handoff_to_dds: false,
|
||||||
|
dds_arrival_interval_seconds: 60, dds_max_waiting: 3,
|
||||||
|
}, "умолчания прежние, пустые списки и критерии в сокет не уходят");
|
||||||
|
|
||||||
|
const criteria = { allowed_errors: 2 };
|
||||||
|
const full = scenarioStartMessage({
|
||||||
|
scenarioId: "s1", mode: "training", trainee: "Петров", exercise: "call", groupId: "g1",
|
||||||
|
traineeId: "t1", ddsService: "01", scenarioIds: ["s1", "s2"], randomScenarioIds: ["s3"],
|
||||||
|
ddsArrivalIntervalSeconds: 30, ddsMaxWaiting: 5, handoffToDds: true, criteria,
|
||||||
|
});
|
||||||
|
assert.deepEqual(full, {
|
||||||
|
type: "scenario.start", scenario_id: "s1", mode: "training", trainee: "Петров",
|
||||||
|
group_id: "g1", trainee_id: "t1", dds_service: "01", exercise: "call", handoff_to_dds: true,
|
||||||
|
scenario_ids: ["s1", "s2"], random_scenario_ids: ["s3"],
|
||||||
|
dds_arrival_interval_seconds: 30, dds_max_waiting: 5, criteria,
|
||||||
|
});
|
||||||
|
|
||||||
|
const empty = scenarioStartMessage({ scenarioId: "s1", mode: "exam", trainee: "И", scenarioIds: [], randomScenarioIds: [] });
|
||||||
|
assert.equal(empty.scenario_ids, undefined, "пустой список не отправляется");
|
||||||
|
assert.equal(empty.random_scenario_ids, undefined);
|
||||||
|
|
||||||
|
console.log("scenario.start: named options build the same socket message.");
|
||||||
36
frontend/src/features/instructor/start.d.mts
Normal file
36
frontend/src/features/instructor/start.d.mts
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import type { LessonCriteria, SessionMode } from "@/shared/types/generated";
|
||||||
|
|
||||||
|
export interface ScenarioStartOptions {
|
||||||
|
scenarioId: string;
|
||||||
|
mode: SessionMode;
|
||||||
|
trainee: string;
|
||||||
|
exercise?: "dds" | "call" | "card";
|
||||||
|
groupId?: string;
|
||||||
|
traineeId?: string;
|
||||||
|
ddsService?: string;
|
||||||
|
scenarioIds?: string[];
|
||||||
|
randomScenarioIds?: string[];
|
||||||
|
ddsArrivalIntervalSeconds?: number;
|
||||||
|
ddsMaxWaiting?: number;
|
||||||
|
handoffToDds?: boolean;
|
||||||
|
criteria?: LessonCriteria;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScenarioStartMessage {
|
||||||
|
type: "scenario.start";
|
||||||
|
scenario_id: string;
|
||||||
|
mode: SessionMode;
|
||||||
|
trainee: string;
|
||||||
|
group_id: string | null;
|
||||||
|
trainee_id: string | null;
|
||||||
|
dds_service: string | null;
|
||||||
|
exercise: "dds" | "call" | "card";
|
||||||
|
handoff_to_dds: boolean;
|
||||||
|
scenario_ids: string[] | undefined;
|
||||||
|
random_scenario_ids: string[] | undefined;
|
||||||
|
dds_arrival_interval_seconds: number;
|
||||||
|
dds_max_waiting: number;
|
||||||
|
criteria: LessonCriteria | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function scenarioStartMessage(options: ScenarioStartOptions): ScenarioStartMessage;
|
||||||
26
frontend/src/features/instructor/start.mjs
Normal file
26
frontend/src/features/instructor/start.mjs
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
// Сборка `scenario.start` из именованных параметров консоли преподавателя.
|
||||||
|
// Отдельный .mjs, чтобы состав сообщения проверялся node-скриптом без сборки.
|
||||||
|
|
||||||
|
/** @param {import("./start.d.mts").ScenarioStartOptions} options */
|
||||||
|
export function scenarioStartMessage({
|
||||||
|
scenarioId, mode, trainee, exercise = "dds", groupId, traineeId, ddsService,
|
||||||
|
scenarioIds, randomScenarioIds, ddsArrivalIntervalSeconds = 60, ddsMaxWaiting = 3,
|
||||||
|
handoffToDds = false, criteria,
|
||||||
|
}) {
|
||||||
|
return {
|
||||||
|
type: "scenario.start",
|
||||||
|
scenario_id: scenarioId,
|
||||||
|
mode,
|
||||||
|
trainee,
|
||||||
|
group_id: groupId || null,
|
||||||
|
trainee_id: traineeId || null,
|
||||||
|
dds_service: ddsService || null,
|
||||||
|
exercise,
|
||||||
|
handoff_to_dds: handoffToDds,
|
||||||
|
scenario_ids: scenarioIds?.length ? scenarioIds : undefined,
|
||||||
|
random_scenario_ids: randomScenarioIds?.length ? randomScenarioIds : undefined,
|
||||||
|
dds_arrival_interval_seconds: ddsArrivalIntervalSeconds,
|
||||||
|
dds_max_waiting: ddsMaxWaiting,
|
||||||
|
criteria,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -8,7 +8,8 @@
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
import { controlChannel, type ChannelStatus } from "@/shared/api/ws";
|
import { controlChannel, type ChannelStatus } from "@/shared/api/ws";
|
||||||
import type { LessonCriteria, SessionMode } from "@/shared/types/generated";
|
|
||||||
|
import { scenarioStartMessage, type ScenarioStartOptions } from "./start.mjs";
|
||||||
|
|
||||||
export function useControl(sessionId: string | null) {
|
export function useControl(sessionId: string | null) {
|
||||||
const [status, setStatus] = useState<ChannelStatus>("closed");
|
const [status, setStatus] = useState<ChannelStatus>("closed");
|
||||||
|
|
@ -37,30 +38,11 @@ export function useControl(sessionId: string | null) {
|
||||||
};
|
};
|
||||||
}, [sessionId]);
|
}, [sessionId]);
|
||||||
|
|
||||||
const start = useCallback(
|
const start = useCallback((targetSessionId: string, options: ScenarioStartOptions) => {
|
||||||
(targetSessionId: string, scenarioId: string, mode: SessionMode, trainee: string, exercise: "dds" | "call" | "card" = "dds", group?: string, traineeId?: string, ddsService?: string, scenarioIds?: string[], ddsArrivalIntervalSeconds = 60, ddsMaxWaiting = 3, handoffToDds = false, criteria?: LessonCriteria, randomScenarioIds?: string[]) => {
|
const current = channel.current;
|
||||||
const message = {
|
if (!current || current.sessionId !== targetSessionId) return false;
|
||||||
type: "scenario.start",
|
return current.socket.send(scenarioStartMessage(options));
|
||||||
scenario_id: scenarioId,
|
}, []);
|
||||||
mode,
|
|
||||||
trainee,
|
|
||||||
group_id: group || null,
|
|
||||||
trainee_id: traineeId || null,
|
|
||||||
dds_service: ddsService || null,
|
|
||||||
exercise,
|
|
||||||
handoff_to_dds: handoffToDds,
|
|
||||||
scenario_ids: scenarioIds?.length ? scenarioIds : undefined,
|
|
||||||
random_scenario_ids: randomScenarioIds?.length ? randomScenarioIds : undefined,
|
|
||||||
dds_arrival_interval_seconds: ddsArrivalIntervalSeconds,
|
|
||||||
dds_max_waiting: ddsMaxWaiting,
|
|
||||||
criteria,
|
|
||||||
} as const;
|
|
||||||
const current = channel.current;
|
|
||||||
if (!current || current.sessionId !== targetSessionId) return false;
|
|
||||||
return current.socket.send(message);
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const note = useCallback(
|
const note = useCallback(
|
||||||
(transcriptRef: string, text: string) =>
|
(transcriptRef: string, text: string) =>
|
||||||
|
|
|
||||||
|
|
@ -283,19 +283,27 @@ export function Instructor() {
|
||||||
&& !(exercise === "dds" && wholeTicket)
|
&& !(exercise === "dds" && wholeTicket)
|
||||||
? visibleScenarios.filter((item) => item.outcome === "card").map((item) => item.id)
|
? visibleScenarios.filter((item) => item.outcome === "card").map((item) => item.id)
|
||||||
: undefined;
|
: undefined;
|
||||||
const sent = control.start(sessionId, primaryScenarioId, mode, selected?.name || trainee || "Демо-курсант",
|
const sent = control.start(sessionId, {
|
||||||
exercise === "card_dds" ? "card" : exercise,
|
scenarioId: primaryScenarioId,
|
||||||
undefined, selected?.id, selected?.service ?? undefined,
|
mode,
|
||||||
scenarioIds && scenarioIds.length > 1 ? scenarioIds : undefined,
|
trainee: selected?.name || trainee || "Демо-курсант",
|
||||||
ddsArrivalIntervalSeconds, ddsMaxWaiting,
|
exercise: exercise === "card_dds" ? "card" : exercise,
|
||||||
exercise === "card_dds", {
|
traineeId: selected?.id,
|
||||||
|
ddsService: selected?.service ?? undefined,
|
||||||
|
scenarioIds: scenarioIds && scenarioIds.length > 1 ? scenarioIds : undefined,
|
||||||
|
randomScenarioIds,
|
||||||
|
ddsArrivalIntervalSeconds,
|
||||||
|
ddsMaxWaiting,
|
||||||
|
handoffToDds: exercise === "card_dds",
|
||||||
|
criteria: {
|
||||||
decision_time_limit_seconds: decisionTimeLimit,
|
decision_time_limit_seconds: decisionTimeLimit,
|
||||||
card_fill_time_limit_seconds: cardFillTimeLimit,
|
card_fill_time_limit_seconds: cardFillTimeLimit,
|
||||||
dds_card_work_time_limit_seconds: ddsCardWorkTimeLimit,
|
dds_card_work_time_limit_seconds: ddsCardWorkTimeLimit,
|
||||||
allowed_errors: allowedErrors,
|
allowed_errors: allowedErrors,
|
||||||
require_correct_grammar: requireCorrectGrammar,
|
require_correct_grammar: requireCorrectGrammar,
|
||||||
score_weights: lessonScoreWeights,
|
score_weights: lessonScoreWeights,
|
||||||
}, randomScenarioIds);
|
},
|
||||||
|
});
|
||||||
if (sent) toStart.current = null;
|
if (sent) toStart.current = null;
|
||||||
}, [sessionId, control, scenarioId, extraScenarioIds, mode, trainee, traineeId, trainees.data,
|
}, [sessionId, control, scenarioId, extraScenarioIds, mode, trainee, traineeId, trainees.data,
|
||||||
exercise, decisionTimeLimit, cardFillTimeLimit, ddsCardWorkTimeLimit, allowedErrors, requireCorrectGrammar, wholeTicket, selectedTicketScenarios,
|
exercise, decisionTimeLimit, cardFillTimeLimit, ddsCardWorkTimeLimit, allowedErrors, requireCorrectGrammar, wholeTicket, selectedTicketScenarios,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue