Files
dc-observatio/services/pcp-tgu-ops-ui/src/features/tgu-planning/TguPlanDetails.tsx
T
Дмитрий Соловьев 9fca4d4051 pcp-tgu интерфейс
2026-05-29 16:09:36 +03:00

103 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { TguPlanDecision } from "../../model/tguTypes";
import type { TguPlanUi } from "../../model/timelineTypes";
import { platformLabel } from "./tguTimelineMapper";
import { statusStyle } from "./tguStatus";
type TguPlanDetailsProps = {
plan?: TguPlanUi;
decisionInFlight: boolean;
notice?: string;
error?: string;
onDecision: (planId: string, decision: TguPlanDecision, reason?: string) => void;
onClose: () => void;
};
export function TguPlanDetails({ plan, decisionInFlight, notice, error, onDecision, onClose }: TguPlanDetailsProps) {
if (!plan) {
return (
<aside className="tgu-details tgu-details--empty">
<div className="tgu-details__placeholder">
<span className="tgu-details__placeholder-icon" aria-hidden="true" />
<span>Выберите plan bar на timeline.</span>
</div>
</aside>
);
}
const style = statusStyle(plan.status);
const duration = formatDuration(plan.endMs - plan.startMs);
const reject = () => {
const reason = window.prompt("Reason для REJECTED", "UI_TEST_REJECTED");
if (reason === null) return;
onDecision(plan.planId, "REJECTED", reason);
};
return (
<aside className="tgu-details">
<div className="tgu-details__header">
<div>
<div className="tgu-details__eyebrow">Выбранный план</div>
<h2>{plan.planId}</h2>
</div>
<button className="tgu-icon-button" type="button" onClick={onClose} aria-label="Закрыть детали">
x
</button>
</div>
<div className={`tgu-status-pill ${style.className}`}>{style.label}</div>
{notice && <div className="tgu-alert tgu-alert--success">{notice}</div>}
{error && <div className="tgu-alert tgu-alert--danger">{error}</div>}
<dl className="tgu-details-grid">
<Detail label="planId" value={plan.planId} mono />
<Detail label="spacecraftId" value={plan.spacecraftId} mono />
<Detail label="КА" value={platformLabel(plan.platform, plan.spacecraftId)} />
<Detail label="startTime" value={formatDateTime(plan.startTime)} mono />
<Detail label="endTime" value={formatDateTime(plan.endTime)} mono />
<Detail label="duration" value={duration} mono />
<Detail label="kppId" value={plan.kppId} mono />
<Detail label="status" value={plan.status} mono />
</dl>
{plan.status === "WAITING_DECISION" && (
<div className="tgu-details__actions">
<button
className="tgu-button tgu-button--accept"
type="button"
disabled={decisionInFlight}
onClick={() => onDecision(plan.planId, "ACCEPTED", "UI_TEST_ACCEPTED")}
>
Принять
</button>
<button className="tgu-button tgu-button--reject" type="button" disabled={decisionInFlight} onClick={reject}>
Отклонить
</button>
</div>
)}
</aside>
);
}
function Detail({ label, value, mono }: { label: string; value?: string; mono?: boolean }) {
return (
<div>
<dt>{label}</dt>
<dd className={mono ? "mono" : undefined}>{value || "-"}</dd>
</div>
);
}
function formatDateTime(value: string): string {
return new Date(value).toLocaleString("ru-RU");
}
function formatDuration(ms: number): string {
const minutes = Math.max(0, Math.round(ms / 60_000));
const hours = Math.floor(minutes / 60);
const restMinutes = minutes % 60;
if (hours === 0) return `${restMinutes} мин`;
return `${hours} ч ${restMinutes} мин`;
}