lct-51: голосовой маршрут ДДС — вышестоящий начальник через WebRTC 6103 с записью
This commit is contained in:
parent
8ff51a8955
commit
bfaadd556c
9 changed files with 63 additions and 12 deletions
|
|
@ -12,12 +12,19 @@ const LABEL: Record<SipStatus, string> = {
|
|||
error: "ошибка",
|
||||
};
|
||||
|
||||
/** Абонент быстрого набора: пульт ДДС звонит бригаде и начальнику одной кнопкой. */
|
||||
export interface SipContact {
|
||||
label: string;
|
||||
number: string;
|
||||
}
|
||||
|
||||
export function SipPhone({
|
||||
defaultExtension = "6101", defaultTarget = "6102", title = "Локальный SIP/WebRTC",
|
||||
defaultExtension = "6101", defaultTarget = "6102", title = "Локальный SIP/WebRTC", contacts = [],
|
||||
}: {
|
||||
defaultExtension?: string;
|
||||
defaultTarget?: string;
|
||||
title?: string;
|
||||
contacts?: SipContact[];
|
||||
}) {
|
||||
const phone = useSipPhone();
|
||||
const [extension, setExtension] = useState(defaultExtension);
|
||||
|
|
@ -46,6 +53,10 @@ export function SipPhone({
|
|||
<label>Номер собеседника<input aria-label="Номер SIP собеседника" inputMode="numeric" value={target}
|
||||
disabled={inCall} onChange={(event) => setTarget(event.target.value)} /></label>
|
||||
{phone.status === "registered" && <button type="button" onClick={() => phone.dial(target)}>Позвонить</button>}
|
||||
{phone.status === "registered" && contacts.map((contact) => <button type="button" key={contact.number}
|
||||
onClick={() => { setTarget(contact.number); phone.dial(contact.number); }}>
|
||||
Позвонить: {contact.label} · {contact.number}
|
||||
</button>)}
|
||||
{phone.status === "incoming" && <button type="button" className="sip-answer" onClick={phone.answer}>Ответить · {phone.peer}</button>}
|
||||
{inCall && phone.status !== "incoming" && <span>{phone.peer || target}</span>}
|
||||
{inCall && <button type="button" className="sip-hangup" onClick={phone.hangup}>Завершить</button>}
|
||||
|
|
|
|||
|
|
@ -604,9 +604,11 @@ export function Dds() {
|
|||
</div><aside className="dds-right">
|
||||
{pane === "phone" && <section className="dds-panel dds-phone"><div className="dds-panel-title"><h2>IP-телефон</h2><span>SIP/WebRTC + учебный протокол</span></div>
|
||||
<Suspense fallback={<p className="dds-hint">Загружаем IP-телефон…</p>}>
|
||||
<SipPhone defaultExtension="6101" defaultTarget="6102" title="Реальный локальный канал ДДС" />
|
||||
<SipPhone defaultExtension="6101" defaultTarget="6102" title="Реальный локальный канал ДДС"
|
||||
contacts={[{ label: "старший группы", number: "6102" },
|
||||
{ label: "вышестоящий начальник", number: "6103" }]} />
|
||||
</Suspense>
|
||||
<p className="dds-hint">На втором рабочем месте откройте <a href="/phone" target="_blank" rel="noreferrer">телефон старшего группы</a> и зарегистрируйте 6102. Пароли 6101/6102 выдаёт локальный администратор командой <code>make sip-credentials</code>.</p>
|
||||
<p className="dds-hint">На втором рабочем месте откройте <a href="/phone" target="_blank" rel="noreferrer">телефон старшего группы</a> (6102); преподаватель в роли вышестоящего начальника — <a href="/phone?role=superior" target="_blank" rel="noreferrer">телефон 6103</a>. Пароли 6101–6103 выдаёт локальный администратор командой <code>make sip-credentials</code>.</p>
|
||||
<h3 className="dds-protocol-title">Учебный протокол доклада</h3>
|
||||
<p className="dds-hint">Связь используется при необходимости. Разговор и учебный доклад сохраняются в журнале, но не блокируют изменение статуса.</p>
|
||||
<label htmlFor="dds-crew">Дежурная бригада</label>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,22 @@
|
|||
import { SipPhone } from "@/features/sip/SipPhone";
|
||||
|
||||
// Второе рабочее место голосовой связи ДДС. Старший группы — по умолчанию;
|
||||
// вышестоящего начальника (?role=superior) играет преподаватель: это
|
||||
// обязательный маршрут по ответу заказчика (П.7).
|
||||
const ROLES = {
|
||||
crew: { title: "Телефон старшего группы", extension: "6102", heading: "IP-телефон старшего группы" },
|
||||
superior: { title: "Телефон вышестоящего начальника", extension: "6103", heading: "IP-телефон вышестоящего начальника" },
|
||||
} as const;
|
||||
|
||||
export function Phone() {
|
||||
const role = new URLSearchParams(window.location.search).get("role") === "superior" ? "superior" : "crew";
|
||||
const { title, extension, heading } = ROLES[role];
|
||||
return <main className="page phone-page">
|
||||
<h1>IP-телефон старшего группы</h1>
|
||||
<p>Откройте этот экран на втором рабочем месте, зарегистрируйте номер 6102 и примите вызов ДДС 6101.</p>
|
||||
<SipPhone defaultExtension="6102" defaultTarget="6101" title="Телефон старшего группы" />
|
||||
<h1>{heading}</h1>
|
||||
<p>Откройте этот экран на втором рабочем месте, зарегистрируйте номер {extension} и примите вызов ДДС 6101.</p>
|
||||
<SipPhone key={role} defaultExtension={extension} defaultTarget="6101" title={title} />
|
||||
<p className="ref">{role === "crew"
|
||||
? <>Роль вышестоящего начальника — <a href="/phone?role=superior">телефон 6103</a>.</>
|
||||
: <>Роль старшего группы — <a href="/phone">телефон 6102</a>.</>}</p>
|
||||
</main>;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue