/* ============================================================
Panels.jsx — header + left panel (ESM)
Exports: Header, SidePanel, STATUS_META, fmtDur.
============================================================ */
import React from "react";
import { COL } from "./data.js";
const STATUS_META = {
operational: { c: "#5cae7e", t: "НА ОРБИТЕ" },
capture: { c: "#d99a4e", t: "СЪЁМКА" },
downlink: { c: "#5b9bd5", t: "СБРОС" },
prohibit: { c: "#d9685f", t: "ЗАПРЕТ" },
};
// Время в проекте — локальное настенное (см. память project_tgu_ui_utc): часы, планы
// и flight-line идут в зоне браузера без UTC-семантики. Поэтому local-геттеры, не UTC.
function fmtClock(ms) {
const d = new Date(ms);
const p = (n) => String(n).padStart(2, "0");
return {
time: `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`,
date: `${p(d.getDate())}.${p(d.getMonth() + 1)}.${d.getFullYear()}`,
dow: ["ВС", "ПН", "ВТ", "СР", "ЧТ", "ПТ", "СБ"][d.getDay()],
};
}
function fmtDur(ms) {
const s = Math.max(0, Math.round(ms / 1000));
const m = Math.floor(s / 60), ss = s % 60;
return `${m}:${String(ss).padStart(2, "0")}`;
}
function Header({ simTime, playing, speed, live, onPlay, onSpeed, onNow, onStep }) {
const ck = fmtClock(simTime);
const speeds = [1, 10, 60, 600];
return (
);
}
function SummaryTiles({ counts }) {
const tiles = [
{ k: "capture", label: "СНИМАЮТ", n: counts.capture, c: STATUS_META.capture.c },
{ k: "downlink", label: "СБРОС", n: counts.downlink, c: STATUS_META.downlink.c },
{ k: "prohibit", label: "ЗАПРЕТ", n: counts.prohibit, c: STATUS_META.prohibit.c },
{ k: "eclipse", label: "В ТЕНИ", n: counts.eclipse, c: "#7d92c4" },
];
return (
ОПТИКА {counts.optic}
ЛОКАЦИЯ {counts.radar}
{counts.total} КА
);
}
function SatRow({ sat, status, mem, focus, onClick }) {
const meta = STATUS_META[status.state];
const memColor = mem > 92 ? "#d9685f" : mem > 78 ? "#d99a4e" : "#5fb3a3";
return (
{sat.name}
{status.eclipse && ☾}{meta.t}
NORAD {sat.norad}
{Math.round(mem)}%
);
}
function Roster({ sats, statuses, memory, focusId, onFocus, query }) {
const q = query.trim().toLowerCase();
const filtered = sats.filter((s) =>
!q || s.name.toLowerCase().includes(q) || String(s.norad).includes(q) || s.group.toLowerCase().includes(q));
const groups = [];
let curD = null, curG = null;
filtered.forEach((s) => {
if (s.domain !== curD) { groups.push({ type: "domain", label: s.domain, color: s.color }); curD = s.domain; curG = null; }
if (s.group !== curG) { groups.push({ type: "group", label: s.group }); curG = s.group; }
groups.push({ type: "sat", sat: s });
});
return (
{groups.map((g, i) => {
if (g.type === "domain") return
{g.label}
;
if (g.type === "group") return
{g.label}
;
const s = g.sat;
return
onFocus(focusId === s.id ? null : s.id)} />;
})}
);
}
function ActivityFeed({ events, onFocus, focusId }) {
return (
АКТИВНЫЕ СОБЫТИЯ {events.length}
{events.length === 0 &&
нет активных событий
}
{events.map((e) => (
onFocus(e.satId)}>
{e.type}{e.target}
{e.sat}ещё {e.remain}
))}
);
}
function SidePanel({ sats, statuses, memory, counts, events, focusId, onFocus, query, setQuery }) {
return (
);
}
export { Header, SidePanel, STATUS_META, fmtDur };