Фронт не хранит карточку как истину: локально живут только правки курсанта, ещё не подтверждённые сервером. Распознанное сервером значение перекрывает набранное, эхо своей правки её подтверждает. Иначе автозаполнение и ручной ввод дерутся за одно поле — худший класс багов для отладки в последнюю ночь. Описание полей отдельно от отрисовки: по нему же карточка рисуется read-only на мониторе, пульте и АРМ ДДС. Сверка формы с контрактом встроена в make typecheck: все 40 полей домена на форме, а поле, добавленное в контракт и забытое в форме, теперь поймается само. Это пропущенный факт по вине интерфейса, а не курсанта. Открытый вопрос в карточке: нужно ли автозаполнение карточки из речи вообще — полнота КИО оценивается кодом E5, и если сервер впишет адрес сам, курсант перестанет за неё отвечать.
147 lines
6.4 KiB
TypeScript
147 lines
6.4 KiB
TypeScript
// Поля карточки информационного обмена. Это не дизайн формы, это норматив:
|
||
// docs/spec/NORMATIVES.md#карточка-информационного-обмена-кио
|
||
//
|
||
// Описание полей отдельно от отрисовки: по нему же рисуется карточка на внешнем
|
||
// мониторе, на пульте и на АРМ ДДС — там она read-only.
|
||
|
||
import type { DDSCode, IncidentType } from "@/shared/types/generated";
|
||
|
||
export type FieldKind = "text" | "number" | "bool" | "select";
|
||
|
||
export interface Field {
|
||
/** Путь в карточке: «floor», «fire.floors» — так же, как в `kio.patch`. */
|
||
path: string;
|
||
label: string;
|
||
kind: FieldKind;
|
||
options?: { value: string; label: string }[];
|
||
/** Заполняется системой: номер определяется автоматически, время — сервером. */
|
||
readOnly?: boolean;
|
||
}
|
||
|
||
export interface FieldGroup {
|
||
title: string;
|
||
fields: Field[];
|
||
/** Группа показывается только для этой службы. */
|
||
dds?: DDSCode;
|
||
}
|
||
|
||
const INCIDENT: { value: IncidentType; label: string }[] = [
|
||
{ value: "fire", label: "Пожар" },
|
||
{ value: "medical", label: "Медицинский вызов" },
|
||
{ value: "police", label: "Правонарушение" },
|
||
{ value: "gkh", label: "Авария ЖКХ" },
|
||
{ value: "era_glonass", label: "ДТП, автовызов" },
|
||
{ value: "other", label: "Прочее" },
|
||
];
|
||
|
||
const DDS: { value: DDSCode; label: string }[] = [
|
||
{ value: "01", label: "01 — МЧС" },
|
||
{ value: "02", label: "02 — полиция" },
|
||
{ value: "03", label: "03 — скорая помощь" },
|
||
{ value: "04", label: "04 — прочие службы" },
|
||
{ value: "gkh", label: "ЖКХ" },
|
||
];
|
||
|
||
export const GROUPS: FieldGroup[] = [
|
||
{
|
||
title: "Служебное",
|
||
fields: [
|
||
{ path: "registered_at", label: "Время регистрации", kind: "text", readOnly: true },
|
||
{ path: "response_status", label: "Статус реагирования", kind: "text", readOnly: true },
|
||
],
|
||
},
|
||
{
|
||
title: "Звонящий",
|
||
fields: [
|
||
{ path: "caller_number", label: "Номер телефона", kind: "text", readOnly: true },
|
||
{ path: "caller_name", label: "ФИО", kind: "text" },
|
||
{ path: "caller_contact", label: "Контактный телефон", kind: "text" },
|
||
{ path: "language", label: "Язык общения", kind: "text" },
|
||
],
|
||
},
|
||
{
|
||
title: "Место",
|
||
fields: [
|
||
{ path: "okato", label: "ОКАТО", kind: "text" },
|
||
{ path: "address", label: "Адрес", kind: "text" },
|
||
{ path: "street", label: "Улица", kind: "text" },
|
||
{ path: "building", label: "Дом", kind: "text" },
|
||
{ path: "entrance", label: "Подъезд", kind: "text" },
|
||
{ path: "floor", label: "Этаж", kind: "text" },
|
||
{ path: "intercom_code", label: "Код домофона", kind: "text" },
|
||
],
|
||
},
|
||
{
|
||
title: "Происшествие",
|
||
fields: [
|
||
{ path: "incident_type", label: "Тип происшествия", kind: "select", options: INCIDENT },
|
||
{ path: "description", label: "Описание", kind: "text" },
|
||
{ path: "victims_count", label: "Пострадавших", kind: "number" },
|
||
{ path: "is_emergency", label: "Признак ЧС", kind: "bool" },
|
||
{ path: "life_threat", label: "Угроза жизни", kind: "bool" },
|
||
{ path: "evacuation_needed", label: "Нужна эвакуация", kind: "bool" },
|
||
],
|
||
},
|
||
{
|
||
title: "Дежурно-диспетчерская служба",
|
||
fields: [
|
||
{ path: "dds", label: "Служба", kind: "select", options: DDS },
|
||
{ path: "dispatch_order_at", label: "Приказ на выезд", kind: "text", readOnly: true },
|
||
{ path: "arrival_at", label: "Прибытие", kind: "text", readOnly: true },
|
||
],
|
||
},
|
||
{
|
||
title: "Уточнения: пожар",
|
||
dds: "01",
|
||
fields: [
|
||
{ path: "fire.fire_nature", label: "Характер пожара", kind: "text" },
|
||
{ path: "fire.object_kind", label: "Объект", kind: "text" },
|
||
{ path: "fire.floors", label: "Этажность", kind: "number" },
|
||
{ path: "fire.gasified", label: "Газификация", kind: "bool" },
|
||
{ path: "fire.people_inside", label: "Люди в помещении", kind: "bool" },
|
||
{ path: "fire.smoke_spread", label: "Куда идёт дым", kind: "text" },
|
||
],
|
||
},
|
||
{
|
||
title: "Уточнения: полиция",
|
||
dds: "02",
|
||
fields: [
|
||
{ path: "police.offence_kind", label: "Вид правонарушения", kind: "text" },
|
||
{ path: "police.suspects", label: "Подозреваемые, приметы", kind: "text" },
|
||
{ path: "police.suspect_fled", label: "Нарушитель скрылся", kind: "bool" },
|
||
{ path: "police.vehicle", label: "Транспортное средство", kind: "text" },
|
||
],
|
||
},
|
||
{
|
||
title: "Уточнения: медицина",
|
||
dds: "03",
|
||
fields: [
|
||
{ path: "medical.reason", label: "Повод к вызову", kind: "text" },
|
||
{ path: "medical.conscious", label: "В сознании", kind: "bool" },
|
||
{ path: "medical.breathing", label: "Дышит", kind: "bool" },
|
||
{ path: "medical.can_move", label: "Может передвигаться", kind: "bool" },
|
||
{ path: "medical.age", label: "Возраст", kind: "number" },
|
||
],
|
||
},
|
||
{
|
||
title: "Уточнения: ЖКХ и прочие",
|
||
dds: "gkh",
|
||
fields: [
|
||
{ path: "utility.failure_kind", label: "Тип аварии", kind: "text" },
|
||
{ path: "utility.scale", label: "Масштаб", kind: "text" },
|
||
{ path: "utility.threat_to_residents", label: "Угроза жильцам", kind: "bool" },
|
||
],
|
||
},
|
||
];
|
||
|
||
/** Группы, которые показываются при выбранной службе. */
|
||
export function visibleGroups(dds: string | null | undefined): FieldGroup[] {
|
||
return GROUPS.filter((group) => !group.dds || group.dds === dds);
|
||
}
|
||
|
||
export function readValue(card: Record<string, unknown>, path: string): unknown {
|
||
return path.split(".").reduce<unknown>(
|
||
(value, part) => (value && typeof value === "object" ? (value as Record<string, unknown>)[part] : undefined),
|
||
card,
|
||
);
|
||
}
|