173 lines
6.1 KiB
TypeScript
173 lines
6.1 KiB
TypeScript
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;
|
||
}
|
||
}
|