Новый сервис pcp-tgu-ui-service: ТГУ-фронт на headless-auth nstart + платформенный CI/деплой
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user