lct-38: каталог 209 служб из «СЛУЖБЫ 112» для ручного добавления в карточку
This commit is contained in:
parent
b4420cea16
commit
3ce3e768e1
13 changed files with 1715 additions and 6 deletions
|
|
@ -1,7 +1,9 @@
|
|||
// Карточка КИО. Один и тот же компонент рисуется у курсанта, на пульте,
|
||||
// на внешнем мониторе и на АРМ ДДС — там в режиме `readOnly`.
|
||||
|
||||
import { useIncidentGroups, useSigns } from "@/shared/api/http";
|
||||
import { useState } from "react";
|
||||
|
||||
import { useIncidentGroups, useServiceCatalog, useSigns } from "@/shared/api/http";
|
||||
import { type CardState, display } from "./merge";
|
||||
import { type Field, type FieldGroup, visibleGroups } from "./fields";
|
||||
|
||||
|
|
@ -141,6 +143,9 @@ function Input({
|
|||
</select>
|
||||
);
|
||||
}
|
||||
if (field.kind === "services") {
|
||||
return <ServicesPicker value={(value as string[]) ?? []} onChange={(next) => emit(next)} />;
|
||||
}
|
||||
if (field.kind === "signs") {
|
||||
return <SignsPicker value={(value as string[]) ?? []} group={incidentGroup} onChange={(next) => emit(next)} />;
|
||||
}
|
||||
|
|
@ -170,6 +175,36 @@ function Input({
|
|||
);
|
||||
}
|
||||
|
||||
/** Ручное добавление служб из каталога — окно «Добавьте службы» боевого АРМ.
|
||||
* Список оповещения по признакам остаётся как есть: здесь только добавки. */
|
||||
function ServicesPicker({ value, onChange }: { value: string[]; onChange: (next: string[]) => void }) {
|
||||
const catalog = useServiceCatalog();
|
||||
const [query, setQuery] = useState("");
|
||||
const needle = query.trim().toLowerCase();
|
||||
const found = needle
|
||||
? (catalog.data?.services ?? [])
|
||||
.filter((item) => !value.includes(item.name) && item.name.toLowerCase().includes(needle))
|
||||
.slice(0, 8)
|
||||
: [];
|
||||
return (
|
||||
<div className="kio-services">
|
||||
{value.length > 0 && <div className="signs-selected" aria-label="Добавленные службы">
|
||||
{value.map((name) => <span key={name}>
|
||||
{name} <button type="button" aria-label={`Убрать ${name}`}
|
||||
onClick={() => onChange(value.filter((item) => item !== name))}>×</button>
|
||||
</span>)}
|
||||
</div>}
|
||||
<input type="search" aria-label="Поиск службы в каталоге" placeholder="Поиск службы…"
|
||||
value={query} onChange={(event) => setQuery(event.target.value)} />
|
||||
{found.length > 0 && <ul className="kio-services-found">
|
||||
{found.map((item) => <li key={item.id}>
|
||||
<button type="button" onClick={() => { onChange([...value, item.name]); setQuery(""); }}>{item.name}</button>
|
||||
</li>)}
|
||||
</ul>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function IncidentGroupPicker({ value, onChange }: { value: number | null; onChange: (next: number | null) => void }) {
|
||||
const page = useIncidentGroups();
|
||||
return <select aria-label="Категория происшествия" value={value ?? ""}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import type { DDSCode, IncidentType } from "@/shared/types/generated";
|
||||
|
||||
export type FieldKind = "text" | "textarea" | "number" | "bool" | "select" | "signs" | "group" | "list";
|
||||
export type FieldKind = "text" | "textarea" | "number" | "bool" | "select" | "signs" | "group" | "list" | "services";
|
||||
|
||||
export interface Field {
|
||||
/** Путь в карточке: «floor», «fire.floors» — так же, как в `kio.patch`. */
|
||||
|
|
@ -107,6 +107,8 @@ export const GROUPS: FieldGroup[] = [
|
|||
fields: [
|
||||
// Список оповещения оператор не выбирает: он производен от признаков.
|
||||
{ path: "notify", label: "Список оповещения", kind: "list", readOnly: true },
|
||||
// Как «Службы: +» в боевом АРМ: добавить можно, убрать автоматическую — нет.
|
||||
{ path: "notify_extra", label: "Дополнительно оповестить", kind: "services" },
|
||||
{ path: "dds", label: "Служба (устаревшее)", kind: "select", options: DDS },
|
||||
{ path: "dispatch_order_at", label: "Приказ на выезд", kind: "text", readOnly: true },
|
||||
{ path: "arrival_at", label: "Прибытие", kind: "text", readOnly: true },
|
||||
|
|
|
|||
|
|
@ -548,6 +548,7 @@ export function Dds() {
|
|||
<div><b>{field(card, "description")}</b></div>
|
||||
<div>Класс: <b>{field(card, "incident_type")}</b></div>
|
||||
<div>Получатели карточки: <b>{station?.recipient_services?.join(", ") || field(card, "notify")}</b></div>
|
||||
{field(card, "notify_extra") !== "—" && <div>Добавлены оператором: <b>{field(card, "notify_extra")}</b></div>}
|
||||
<div>Ваша ДДС: <b>{station?.managed_service || "служба не назначена"}</b></div></div>
|
||||
</div>
|
||||
<div className="dds-case-stage">
|
||||
|
|
|
|||
|
|
@ -230,6 +230,17 @@ export const useIncidentGroups = () => useQuery({
|
|||
queryFn: () => request<IncidentGroupsPage>("/api/ekp/groups"),
|
||||
});
|
||||
|
||||
export interface ServiceCatalogPage {
|
||||
source: string;
|
||||
services: { id: string; name: string; organization: string | null; service_level: string | null; source_reference: string }[];
|
||||
}
|
||||
|
||||
/** Каталог окна «Добавьте службы»: 209 служб, включая районные ДДС. */
|
||||
export const useServiceCatalog = () => useQuery({
|
||||
queryKey: ["ekp-services"], staleTime: Infinity,
|
||||
queryFn: () => request<ServiceCatalogPage>("/api/ekp/services"),
|
||||
});
|
||||
|
||||
export function useSigns(prefix: string[], group: number | null = null) {
|
||||
const params = new URLSearchParams();
|
||||
if (group !== null) params.set("group", String(group));
|
||||
|
|
|
|||
|
|
@ -346,6 +346,8 @@ export interface KIO {
|
|||
evacuation_needed?: boolean;
|
||||
/** Список оповещения */
|
||||
notify?: Array<string>;
|
||||
/** Дополнительно оповестить */
|
||||
notify_extra?: Array<string>;
|
||||
/** Устаревшее: одна служба. Заменено списком оповещения */
|
||||
dds?: DDSCode | null;
|
||||
dispatch_order_at?: string | null;
|
||||
|
|
|
|||
|
|
@ -783,6 +783,11 @@ button:disabled { opacity: 0.5; cursor: default; }
|
|||
.kio-arm-incident .signs select { flex: 1 1 180px; width: auto; max-width: 100%; padding: 7px 8px; border: 1px solid #acb5bc; background: white; }
|
||||
.kio-arm-ref .signs-selected { display: flex; flex: 1 1 100%; flex-wrap: wrap; gap: 5px; margin-top: 5px; }
|
||||
.kio-arm-ref .signs-selected span { padding: 7px 10px; background: #1876b4; color: white; font-size: 12px; font-weight: 700; }
|
||||
.kio-services { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
|
||||
.kio-services .signs-selected { display: flex; flex-wrap: wrap; gap: 5px; }
|
||||
.kio-services .signs-selected button { border: 0; background: transparent; color: inherit; cursor: pointer; padding: 0 0 0 4px; }
|
||||
.kio-services-found { list-style: none; margin: 0; padding: 0; border: 1px solid var(--line); max-height: 220px; overflow-y: auto; }
|
||||
.kio-services-found button { width: 100%; text-align: left; border: 0; border-bottom: 1px solid var(--line); background: var(--panel); padding: 6px 8px; cursor: pointer; }
|
||||
.kio-arm-choice { display: flex; flex-wrap: wrap; gap: 6px; padding: 5px 0; }
|
||||
.kio-arm-choice button { min-width: 49px; padding: 6px 11px; border: 1px solid #aeb6bd; background: white; font-size: 13px; font-weight: 700; }
|
||||
.kio-arm-choice .kio-arm-selected { border-color: #1979bb; background: #1979bb; color: white; }
|
||||
|
|
|
|||
Loading…
Reference in a new issue