# INTEGRATION — встраивание вкладки «Состояние группировки»

Цель: добавить дашборд новой вкладкой в существующий шелл (рядом с
`Планы / Карта / Комплексный план / Редактор / Заявки`), переключив встроенную
симуляцию на реальные данные.

---

## 1. Зависимости

```bash
npm i d3-geo topojson-client
# react / react-dom — уже есть в проекте
```

Версии, на которых собран прототип: `d3-geo@3`, `topojson-client@3`, `react@18`.

---

## 2. Положить модуль

Скопируйте папку `src/` в проект, например в `src/features/constellation/`.
Все импорты внутри — относительные, так что путь произвольный.

`dashboard.css` импортируется автоматически из `ConstellationDashboard.jsx`
(`import "./dashboard.css"`). Если используете CSS-модули/Tailwind — см. п.6.

---

## 3. Карта мира (TopoJSON)

`MapView.jsx` сейчас тянет карту с CDN:

```js
fetch("https://unpkg.com/world-atlas@2.0.2/land-110m.json")
```

Для прода **положите файл локально** и раздавайте со своего origin:

```js
// import landUrl from "../assets/land-110m.json?url";  // Vite
fetch(landUrl).then(r => r.json()).then(/* ... */)
```

Либо, если у вас уже есть карта (Leaflet/MapLibre/OpenLayers), рассмотрите
вариант рендерить слои группировки поверх неё — см. п.7.

---

## 4. Монтирование как вкладки

`src/index.js` отдаёт `default` = компонент `ConstellationDashboard` (без пропсов —
сейчас он сам строит данные из `data.js`/`sim.js`).

Пример с массивом табов (адаптируйте под ваш роутер):

```jsx
import ConstellationDashboard from "@/features/constellation";

const TABS = [
  { id: "plans",   label: "Планы",            element: <PlansView /> },
  { id: "map",     label: "Карта",            element: <MapTab /> },
  // ...
  { id: "constel", label: "Группировка",      element: <ConstellationDashboard /> }, // ← новая
];
```

или с react-router:

```jsx
<Route path="/constellation" element={<ConstellationDashboard />} />
```

### Важно про высоту
Корневой `.app` использует `height:100vh`, а `reference/styles.css` ставит
`body{overflow:hidden}`. Внутри вкладки с собственной шапкой это надо снять:

- замените в `dashboard.css` `.app{height:100vh}` → `.app{height:100%}` и дайте
  контейнеру вкладки реальную высоту (`height:100%` по цепочке родителей), **или**
- оставьте `100vh`, если вкладка занимает весь вьюпорт.
- правила для `body{…}` из `dashboard.css` лучше удалить/перенести, чтобы не влиять
  на остальное приложение (или сразу оскопить — см. п.6).

---

## 5. Контракты данных (главное)

Сейчас данные синтетические. Замена — в двух файлах: `data.js` (что у нас есть)
и `sim.js` (где это находится во времени/пространстве).

### 5.1 `data.js` — статический состав группировки

```ts
type Payload = "optic" | "radar";

interface Satellite {
  id: string;            // уникальный ключ
  name: string;          // "Ресурс-П №3"
  norad: number;
  payload: Payload;      // влияет на цвет и какие ROI снимает
  domain: "ОПТИКА" | "ЛОКАЦИЯ";   // верхняя группировка в списке/таймлайне
  group: string;         // подгруппа ("Ресурс-П", "Кондор-ФКА", …)
  color: string;         // = COL[payload], проставляется автоматически
  mem0: number;          // стартовая загрузка памяти, %
  orbit: { latAmp:number; period:number; phase:number; lon0:number }; // только для встроенной симуляции
}

interface Station { id:string; name:string; lon:number; lat:number; }

interface ROI {        // заявка на съёмку (полигон)
  id:string; name:string; payload:Payload;
  poly:[number,number][];   // [[lon,lat], …]  (без замыкающей точки)
}
```

Экспорты: `SATELLITES`, `STATIONS`, `ROIS`, `COL`.
Чтобы расширить до 20–30 КА и новых групп — просто добавляйте элементы; список,
сводка и таймлайн подхватывают автоматически (группировка идёт по `domain`→`group`).
Поле `orbit` нужно только встроенной симуляции (п.5.3).

### 5.2 `sim.js` — геометрия и расписание

Чистые функции, легко заменить на реальные источники:

| Функция | Что возвращает | Чем заменить в проде |
|---|---|---|
| `subSat(sat, tMs, epochMs)` | `{lon,lat}` подспутниковой точки | реальная эфемерида/SGP4 по TLE на время `t` |
| `subSolar(tMs)` | `{lon,lat}` подсолнечной точки | можно оставить (астрономия точная) |
| `inShadow(lon,lat,tMs)` | в тени ли точка | по вашей модели освещённости |
| `statusAt(sat,tMs,env)` | `{state, p, roi?, station?, prohibit?, eclipse}` | **главный хук**: верните статус из вашего плана |
| `buildSchedule(...)` | интервалы `capture/downlink/prohibit/shadow` по КА на домене | отдайте готовые интервалы из API планирования |
| `buildProhibits(...)` | окна запрета по КА | из вашего сервиса ограничений (ВНЗ, питание и т.п.) |

`state ∈ "operational" | "capture" | "downlink" | "prohibit"` (плюс флаг `eclipse`).
Приоритет в `statusAt`: запрет → съёмка → сброс → operational.

### 5.3 Минимальная интеграция с API (рекомендуемый путь)
1. **Состав** (`SATELLITES/STATIONS/ROIS`) — из ваших справочников/`pcp-request-service`.
2. **Положение** — замените тело `subSat()` на расчёт по TLE (напр. `satellite.js`/SGP4),
   `epochMs`/`period`/`latAmp`/`lon0` тогда не нужны.
3. **Расписание** — вместо `buildSchedule()` отдайте интервалы прямо из плана:
   объект `{ [satId]: { capture:[], downlink:[], prohibit:[], shadow:[] } }`,
   где интервал = `{ start:ms, end:ms, label?:string }`. Таймлайн и лента это рисуют как есть.
4. **Запреты** — `prohibit`-интервалы из сервиса ограничений (поле `reason` → подпись).
5. **Память борта** — если телеметрия известна, замените модельную динамику в RAF-цикле
   `ConstellationDashboard.jsx` (блок `memory dynamics`) на реальные значения по `satId`.

Домен времени задаётся в `buildEnv()` (`now-6h … now+18h`) — подгоните под горизонт плана.

---

## 6. Изоляция стилей (если нужно)
`dashboard.css` — глобальные классы (`.app`, `.hdr`, `.side`, …) и `:root`-переменные.
Если боитесь конфликтов:
- оберните корень во `<div className="cdash">…</div>` и добавьте префикс к селекторам
  (или прогоните через CSS-модули / `@scope`), **или**
- оставьте как есть, если имена классов не пересекаются с вашими.
Переменные в `:root` безобидны (с префиксом `--bg0` и т.п.), но при желании
перенесите их на `.cdash`.

---

## 7. (Опционально) рендер на вашей карте
Если в приложении уже есть карта (на скриншотах — тёмная equirectangular):
- слои **заявок (ROI)**, **станций**, **треков**, **полос обзора**, **линий сброса**
  можно отрисовать вашими средствами (GeoJSON-слой / canvas-overlay), переиспользуя
  геометрию из `sim.js` (`subSat`, footprint = окружность радиусом ~7–9° вокруг точки).
- тогда `MapView.jsx` нужен лишь как референс по слоям/цветам/порядку отрисовки.
- остальные части (`Header`, `SidePanel`, `Timeline`, RAF-часы/память) переиспользуются как есть.

---

## 8. Проверка после встраивания
- [ ] Вкладка открывается, карта рисует сушу/сетку, КА движутся при Play.
- [ ] Скорости ×1…×600 меняют ход времени; «Сейчас» включает LIVE.
- [ ] Скраб по таймлайну двигает плейхед и пересчитывает статусы/карту.
- [ ] Клик по КА (список/карта/таймлайн) ставит фокус и затемняет остальных.
- [ ] Полосы памяти меняются на съёмке/сбросе.
- [ ] Высота/скролл вкладки корректны (см. п.4 «про высоту»).
