Таймлайн ТГУ: по умолчанию только актуальная цепочка планов

Раньше таймлайн рисовал планы всех статусов, включая историю версий
(SUPERSEDED/EXPIRED/REJECTED), из-за чего активная цепочка тонула в
наложении старых планов. Теперь по умолчанию показываются только
актуальные планы (PLANNED/ISSUING/WAITING_DECISION/ACCEPTED), а историю
версий можно включить чекбоксом над таймлайном.
This commit is contained in:
Дмитрий Соловьев
2026-06-02 11:25:58 +03:00
parent e8f253ac6a
commit e0aa1ccf0e
3 changed files with 55 additions and 2 deletions
@@ -10,6 +10,7 @@ import { TguPlanningLayout } from "./TguPlanningLayout";
import { TguSidebar } from "./TguSidebar"; import { TguSidebar } from "./TguSidebar";
import { TguTimeline } from "./TguTimeline"; import { TguTimeline } from "./TguTimeline";
import { TguToolbar } from "./TguToolbar"; import { TguToolbar } from "./TguToolbar";
import { isActivePlanStatus } from "./tguStatus";
import { buildPlatformsForInterval, buildTimelineRows, mapPlans } from "./tguTimelineMapper"; import { buildPlatformsForInterval, buildTimelineRows, mapPlans } from "./tguTimelineMapper";
const DAY_MS = 24 * 60 * 60 * 1000; const DAY_MS = 24 * 60 * 60 * 1000;
@@ -28,6 +29,9 @@ export function TguPlanningPage() {
const [selectedSpacecraftId, setSelectedSpacecraftId] = useState<string>(); const [selectedSpacecraftId, setSelectedSpacecraftId] = useState<string>();
const [selectedPlanId, setSelectedPlanId] = useState<string>(); const [selectedPlanId, setSelectedPlanId] = useState<string>();
const [activeTab, setActiveTab] = useState<TguActiveTab>("timeline"); const [activeTab, setActiveTab] = useState<TguActiveTab>("timeline");
// По умолчанию на таймлайне видна только актуальная цепочка; история версий
// (SUPERSEDED/EXPIRED/REJECTED и т.п.) скрыта, чтобы не было визуальной каши.
const [showHistory, setShowHistory] = useState(false);
// Вкладки рендерятся в режиме keep-alive: каждую монтируем при первом открытии // Вкладки рендерятся в режиме keep-alive: каждую монтируем при первом открытии
// и дальше держим смонтированной (скрытой через display:none), чтобы её // и дальше держим смонтированной (скрытой через display:none), чтобы её
// внутреннее состояние — загруженные заявки, выбор, слои, скролл — не сбрасывалось // внутреннее состояние — загруженные заявки, выбор, слои, скролл — не сбрасывалось
@@ -98,9 +102,14 @@ export function TguPlanningPage() {
() => buildPlatformsForInterval(rawPlatforms, plans, appliedRange), () => buildPlatformsForInterval(rawPlatforms, plans, appliedRange),
[rawPlatforms, plans, appliedRange] [rawPlatforms, plans, appliedRange]
); );
// Планы, показываемые на таймлайне: по умолчанию только актуальная цепочка.
const timelinePlans = useMemo(
() => (showHistory ? plans : plans.filter((plan) => isActivePlanStatus(plan.status))),
[plans, showHistory]
);
const rows = useMemo( const rows = useMemo(
() => buildTimelineRows(plans, platforms, appliedRange, selectedSpacecraftId), () => buildTimelineRows(timelinePlans, platforms, appliedRange, selectedSpacecraftId),
[plans, platforms, appliedRange, selectedSpacecraftId] [timelinePlans, platforms, appliedRange, selectedSpacecraftId]
); );
const selectedPlan = useMemo(() => { const selectedPlan = useMemo(() => {
const plan = plans.find((item) => item.planId === selectedPlanId); const plan = plans.find((item) => item.planId === selectedPlanId);
@@ -181,6 +190,16 @@ export function TguPlanningPage() {
}} }}
/> />
<section className="tgu-center"> <section className="tgu-center">
<div className="tgu-timeline-controls">
<label className="tgu-timeline-controls__toggle">
<input
type="checkbox"
checked={showHistory}
onChange={(event) => setShowHistory(event.target.checked)}
/>
Показать историю версий (SUPERSEDED, EXPIRED и др.)
</label>
</div>
<TguTimeline <TguTimeline
rows={rows} rows={rows}
range={appliedRange} range={appliedRange}
@@ -14,6 +14,19 @@ export const STATUS_STYLES: TimelineStatusStyle[] = [
const STATUS_BY_NAME = new Map(STATUS_STYLES.map((style) => [style.status, style])); const STATUS_BY_NAME = new Map(STATUS_STYLES.map((style) => [style.status, style]));
/** Статусы актуальной цепочки: их показываем на таймлайне по умолчанию. */
const ACTIVE_PLAN_STATUSES = new Set<TguPlanStatus>([
"PLANNED",
"ISSUING",
"WAITING_DECISION",
"ACCEPTED"
]);
/** Проверяет, относится ли план к актуальной цепочке (а не к истории версий). */
export function isActivePlanStatus(status: TguPlanStatus): boolean {
return ACTIVE_PLAN_STATUSES.has(status);
}
export function statusStyle(status: TguPlanStatus): TimelineStatusStyle { export function statusStyle(status: TguPlanStatus): TimelineStatusStyle {
return ( return (
STATUS_BY_NAME.get(status) ?? { STATUS_BY_NAME.get(status) ?? {
@@ -346,6 +346,27 @@
background: #121516; background: #121516;
} }
.tgu-timeline-controls {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.6rem 0.85rem 0;
}
.tgu-timeline-controls__toggle {
display: inline-flex;
align-items: center;
gap: 0.4rem;
font-size: 0.8rem;
color: var(--muted, #9aa4ad);
cursor: pointer;
user-select: none;
}
.tgu-timeline-controls__toggle input {
cursor: pointer;
}
.tgu-timeline { .tgu-timeline {
min-width: 48rem; min-width: 48rem;
min-height: 100%; min-height: 100%;