fix: строка добавленных служб на АРМ ДДС без пустого значения, пикер с лимитом 20 и без служб из notify

This commit is contained in:
GGlamer 2026-09-27 22:29:33 +03:00
commit 01e128c22d
2 changed files with 28 additions and 9 deletions

View file

@ -19,6 +19,7 @@ interface Props {
export function KioCard({ state, required = [], readOnly = false, referenceArm = false, onChange }: Props) { export function KioCard({ state, required = [], readOnly = false, referenceArm = false, onChange }: Props) {
const dds = display(state, "dds") as string | null; const dds = display(state, "dds") as string | null;
const incidentGroup = display(state, "incident_group") as number | null; const incidentGroup = display(state, "incident_group") as number | null;
const notify = (display(state, "notify") as string[] | null) ?? [];
const groups = visibleGroups(dds); const groups = visibleGroups(dds);
const section = (group: FieldGroup) => ( const section = (group: FieldGroup) => (
<section className={`kio-arm-section kio-arm-${group.area}`} key={group.title}> <section className={`kio-arm-section kio-arm-${group.area}`} key={group.title}>
@ -38,6 +39,7 @@ export function KioCard({ state, required = [], readOnly = false, referenceArm =
readOnly={readOnly || field.readOnly} readOnly={readOnly || field.readOnly}
referenceArm={referenceArm} referenceArm={referenceArm}
incidentGroup={incidentGroup} incidentGroup={incidentGroup}
notify={notify}
onChange={onChange} onChange={onChange}
/> />
))} ))}
@ -60,7 +62,7 @@ export function KioCard({ state, required = [], readOnly = false, referenceArm =
} }
function Row({ function Row({
field, value, pending, required, readOnly, referenceArm, incidentGroup, onChange, field, value, pending, required, readOnly, referenceArm, incidentGroup, notify, onChange,
}: { }: {
field: Field; field: Field;
value: unknown; value: unknown;
@ -69,6 +71,7 @@ function Row({
readOnly?: boolean; readOnly?: boolean;
referenceArm: boolean; referenceArm: boolean;
incidentGroup: number | null; incidentGroup: number | null;
notify: string[];
onChange?: (path: string, value: unknown) => void; onChange?: (path: string, value: unknown) => void;
}) { }) {
const empty = value === null || value === undefined || value === "" || (Array.isArray(value) && value.length === 0); const empty = value === null || value === undefined || value === "" || (Array.isArray(value) && value.length === 0);
@ -87,7 +90,7 @@ function Row({
{readOnly ? ( {readOnly ? (
<span className="kio-arm-readonly">{formatValue(value, field)}</span> <span className="kio-arm-readonly">{formatValue(value, field)}</span>
) : ( ) : (
<Input field={field} value={value} referenceArm={referenceArm} incidentGroup={incidentGroup} onChange={onChange} /> <Input field={field} value={value} referenceArm={referenceArm} incidentGroup={incidentGroup} notify={notify} onChange={onChange} />
)} )}
</div> </div>
</div> </div>
@ -95,12 +98,13 @@ function Row({
} }
function Input({ function Input({
field, value, referenceArm, incidentGroup, onChange, field, value, referenceArm, incidentGroup, notify, onChange,
}: { }: {
field: Field; field: Field;
value: unknown; value: unknown;
referenceArm: boolean; referenceArm: boolean;
incidentGroup: number | null; incidentGroup: number | null;
notify: string[];
onChange?: (path: string, value: unknown) => void; onChange?: (path: string, value: unknown) => void;
}) { }) {
const emit = (next: unknown) => onChange?.(field.path, next); const emit = (next: unknown) => onChange?.(field.path, next);
@ -144,7 +148,7 @@ function Input({
); );
} }
if (field.kind === "services") { if (field.kind === "services") {
return <ServicesPicker value={(value as string[]) ?? []} onChange={(next) => emit(next)} />; return <ServicesPicker value={(value as string[]) ?? []} notify={notify} onChange={(next) => emit(next)} />;
} }
if (field.kind === "signs") { if (field.kind === "signs") {
return <SignsPicker value={(value as string[]) ?? []} group={incidentGroup} onChange={(next) => emit(next)} />; return <SignsPicker value={(value as string[]) ?? []} group={incidentGroup} onChange={(next) => emit(next)} />;
@ -175,15 +179,24 @@ function Input({
); );
} }
/** Как `max_length` у `KIO.notify_extra`. */
const SERVICES_LIMIT = 20;
/** Ручное добавление служб из каталога — окно «Добавьте службы» боевого АРМ. /** Ручное добавление служб из каталога — окно «Добавьте службы» боевого АРМ.
* Список оповещения по признакам остаётся как есть: здесь только добавки. */ * Список оповещения по признакам остаётся как есть: здесь только добавки. */
function ServicesPicker({ value, onChange }: { value: string[]; onChange: (next: string[]) => void }) { function ServicesPicker({ value, notify, onChange }: {
value: string[]; notify: string[]; onChange: (next: string[]) => void;
}) {
const catalog = useServiceCatalog(); const catalog = useServiceCatalog();
const [query, setQuery] = useState(""); const [query, setQuery] = useState("");
const needle = query.trim().toLowerCase(); const needle = query.trim().toLowerCase();
const found = needle // Лимит и службы из списка оповещения — те же правила, что на сервере:
// лишняя служба отклонила бы весь пакет правок.
const full = value.length >= SERVICES_LIMIT;
const found = needle && !full
? (catalog.data?.services ?? []) ? (catalog.data?.services ?? [])
.filter((item) => !value.includes(item.name) && item.name.toLowerCase().includes(needle)) .filter((item) => !value.includes(item.name) && !notify.includes(item.name)
&& item.name.toLowerCase().includes(needle))
.slice(0, 8) .slice(0, 8)
: []; : [];
return ( return (
@ -194,7 +207,8 @@ function ServicesPicker({ value, onChange }: { value: string[]; onChange: (next:
onClick={() => onChange(value.filter((item) => item !== name))}>×</button> onClick={() => onChange(value.filter((item) => item !== name))}>×</button>
</span>)} </span>)}
</div>} </div>}
<input type="search" aria-label="Поиск службы в каталоге" placeholder="Поиск службы…" <input type="search" aria-label="Поиск службы в каталоге"
placeholder={full ? `Не больше ${SERVICES_LIMIT} служб` : "Поиск службы…"} disabled={full}
value={query} onChange={(event) => setQuery(event.target.value)} /> value={query} onChange={(event) => setQuery(event.target.value)} />
{found.length > 0 && <ul className="kio-services-found"> {found.length > 0 && <ul className="kio-services-found">
{found.map((item) => <li key={item.id}> {found.map((item) => <li key={item.id}>

View file

@ -47,6 +47,11 @@ function field(card: CardState, path: string): string {
const value = readPath(card.server, path); const value = readPath(card.server, path);
return value === null || value === undefined || value === "" ? "—" : String(value); return value === null || value === undefined || value === "" ? "—" : String(value);
} }
/** Список из карточки: `field()` не годится — `String([])` пустая строка, а не «—». */
function names(card: CardState, path: string): string[] {
const value = readPath(card.server, path);
return Array.isArray(value) ? value.map(String) : [];
}
function time(value: string): string { function time(value: string): string {
const parsed = new Date(value); const parsed = new Date(value);
return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleTimeString("ru-RU"); return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleTimeString("ru-RU");
@ -548,7 +553,7 @@ export function Dds() {
<div><b>{field(card, "description")}</b></div> <div><b>{field(card, "description")}</b></div>
<div>Класс: <b>{field(card, "incident_type")}</b></div> <div>Класс: <b>{field(card, "incident_type")}</b></div>
<div>Получатели карточки: <b>{station?.recipient_services?.join(", ") || field(card, "notify")}</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>} {names(card, "notify_extra").length > 0 && <div>Добавлены оператором: <b>{names(card, "notify_extra").join(", ")}</b></div>}
<div>Ваша ДДС: <b>{station?.managed_service || "служба не назначена"}</b></div></div> <div>Ваша ДДС: <b>{station?.managed_service || "служба не назначена"}</b></div></div>
</div> </div>
<div className="dds-case-stage"> <div className="dds-case-stage">