Новый сервис pcp-tgu-ui-service: ТГУ-фронт на headless-auth nstart + платформенный CI/деплой

This commit is contained in:
Дмитрий Соловьев
2026-06-09 13:35:13 +03:00
parent 0b617594fd
commit 505bf1f31c
129 changed files with 21640 additions and 0 deletions
@@ -0,0 +1,172 @@
import { useEffect, useState } from "react";
import type { RequestAtPoint } from "../tgu-map-2d/Tgu2DMapRequestsPanel";
import { formatLocalDateTime, parseLocal } from "../../utils/localTime";
type Props = {
requests: RequestAtPoint[];
selectedPolygonId?: string;
deleting: boolean;
deleteError?: string;
onHover: (polygonId: string | undefined) => void;
onSelect: (polygonId: string) => void;
onDelete: (requestId: string) => void;
onClose: () => void;
};
export function RequestDetailsPanel({
requests,
selectedPolygonId,
deleting,
deleteError,
onHover,
onSelect,
onDelete,
onClose
}: Props) {
const selectedEntry = selectedPolygonId
? requests.find((r) => r.polygon.id === selectedPolygonId)
: requests.length === 1
? requests[0]
: undefined;
// Второй шаг подтверждения удаления. Сбрасываем при смене выбранной заявки,
// чтобы открытый запрос на удаление не «переезжал» на другую заявку.
const [confirmingDelete, setConfirmingDelete] = useState(false);
useEffect(() => {
setConfirmingDelete(false);
}, [selectedEntry?.item.id]);
return (
<aside className="tgu-map-requests-panel">
<div className="tgu-panel-heading">
<span>Заявки в точке ({requests.length})</span>
<button className="tgu-link-button" type="button" onClick={onClose}>
Закрыть
</button>
</div>
{requests.length > 1 && (
<div className="tgu-map-requests-list">
{requests.map(({ polygon, item }) => (
<button
key={polygon.id}
className={`tgu-map-request-row ${polygon.id === selectedEntry?.polygon.id ? "is-selected" : ""}`}
type="button"
onMouseEnter={() => onHover(polygon.id)}
onMouseLeave={() => onHover(undefined)}
onClick={() => onSelect(polygon.id)}
>
<i className={`tgu-map-request-row__type req-${item.surveyType.toLowerCase()}`} />
<span className="tgu-map-request-row__body">
<span className="tgu-map-request-row__name">{item.name || item.id}</span>
<span className="tgu-map-request-row__meta">
{formatDateTime(item.beginDateTime)} · {item.surveyType} · {item.status}
</span>
</span>
</button>
))}
</div>
)}
{selectedEntry && (
<div className="tgu-map-request-details" style={{ flex: 1 }}>
<div className="tgu-map-panel__label">Детали заявки</div>
<dl className="tgu-map-request-details__grid">
<dt>ID</dt>
<dd className="mono">{selectedEntry.item.id}</dd>
<dt>Название</dt>
<dd>{selectedEntry.item.name || "—"}</dd>
<dt>Статус</dt>
<dd>{selectedEntry.item.status}</dd>
<dt>Тип съёмки</dt>
<dd>{surveyTypeLabel(selectedEntry.item.surveyType)}</dd>
<dt>Начало</dt>
<dd className="mono">{formatDateTime(selectedEntry.item.beginDateTime)}</dd>
<dt>Конец</dt>
<dd className="mono">{formatDateTime(selectedEntry.item.endDateTime)}</dd>
<dt>Важность</dt>
<dd className="mono">{selectedEntry.item.importance}</dd>
<dt>Покрытие</dt>
<dd className="mono">
{selectedEntry.item.coveragePercent != null ? `${selectedEntry.item.coveragePercent.toFixed(1)} %` : "—"}
</dd>
<dt>КПП</dt>
<dd className="mono">{selectedEntry.item.kpp?.join(", ") || "—"}</dd>
<dt>Приоритет передачи</dt>
<dd>{selectedEntry.item.highPriorityTransmit ? "Да" : "Нет"}</dd>
<dt>Создано</dt>
<dd className="mono">{formatDateTime(selectedEntry.item.createdAt)}</dd>
<dt>Изменено</dt>
<dd className="mono">{formatDateTime(selectedEntry.item.updatedAt)}</dd>
</dl>
{deleteError && <div className="tgu-alert tgu-alert--danger tgu-alert--compact">{deleteError}</div>}
{confirmingDelete ? (
<div className="tgu-requests-confirm">
<div className="tgu-requests-confirm__title">Удалить заявку?</div>
<div className="tgu-requests-confirm__subtitle">{selectedEntry.item.name || selectedEntry.item.id}</div>
<div className="tgu-requests-form__actions">
<button
className="tgu-button tgu-button--reject"
type="button"
disabled={deleting}
onClick={() => onDelete(selectedEntry.item.id)}
>
{deleting ? "Удаление..." : "Да, удалить"}
</button>
<button
className="tgu-button"
type="button"
disabled={deleting}
onClick={() => setConfirmingDelete(false)}
>
Нет, не удалять
</button>
</div>
</div>
) : (
<div className="tgu-requests-form__actions">
<button
className="tgu-button tgu-button--reject"
type="button"
disabled={deleting}
onClick={() => setConfirmingDelete(true)}
>
Удалить заявку
</button>
</div>
)}
</div>
)}
</aside>
);
}
function formatDateTime(value: string): string {
return formatLocalDateTime(parseLocal(value));
}
function surveyTypeLabel(surveyType: string): string {
switch (surveyType) {
case "OPTICS":
return "Оптическая";
case "RSA":
return "Радиолокационная (РСА)";
case "COMBINED":
return "Комбинированная";
default:
return surveyType;
}
}