# dev-2026-04-21-schedule-ui.md

**Дата:** 2026-04-21  
**Статус:** DONE  
**ТЗ:** TZ-schedule-ui-enrichment.md

---

## Задача

Обогащение UI расписания данными из `flight_actual` и `mart.flights`:
- Новые колонки: ВПП, тип ВС, регистрация, длительность, взлёт/посадка факт
- Source badge для FR24-источников
- Track badge для RTL-SDR/FA треков
- Убрать дублирующийся столбец Трек и столбец Статус

---

## Выполненные изменения

### `main.py` — функция `schedule_data()`

1. **SQL переписан** с bare `FROM fr24_ext.schedule` на `FROM fr24_ext.schedule s`
   с двумя LEFT JOIN:
   - `LEFT JOIN fr24_ext.flight_actual fa ON fa.fr24_id = s.fr24_id AND s.fr24_id IS NOT NULL`
   - `LEFT JOIN fr24_mart.flights mf ON mf.flight_number = s.flight_number AND mf.flight_date = s.flight_date`

2. **Добавлены новые SELECT поля:**
   - `fa.runway_takeoff`, `fa.runway_landed`
   - `fa.actual_distance`
   - `fa.flight_time AS fa_flight_time`
   - `fa.reg AS registration`
   - `fa.operated_as`
   - `fa.category AS fa_category`
   - `mf.aircraft_type`
   - `mf.track_source`, `mf.track_points`
   - `s.source AS sched_source`

3. **Фикс ambiguity**: `_schedule_where()` генерирует WHERE без table-alias.
   После JOIN столбцы `flight_date` и `flight_number` становятся неоднозначными
   (есть в `flight_actual` и `mart.flights`).
   Решение: в `schedule_data()` применяется `re.sub()` для подстановки `s.` prefix
   перед этими колонками в safe_where строке.

4. **Новые поля в JSON-ответе:**
   - `runway_takeoff`, `runway_landed`
   - `actual_distance`
   - `flight_time_min` (из fa_flight_time)
   - `registration`
   - `aircraft_type`
   - `track_source`, `track_points`
   - `sched_source`
   - `duration_eff` = `fa_flight_time or duration_min`

---

### `schedule.html` — полная перепись

**Было:** 12 колонок, Трек дублировался в col 2 и col 12, был столбец Статус  
**Стало:** 13 колонок согласно ТЗ:

| # | Колонка | Данные |
|---|---------|--------|
| 1 | Дата | flight_date |
| 2 | Рейс | flight_number + cat badge + source badge |
| 3 | Авиакомпания | airline + registration (мелко) |
| 4 | ↑↓ | direction icon |
| 5 | Маршрут | thread_title или origin → destination |
| 6 | Аэропорт | airport_iata |
| 7 | По расп. | scheduled_at (MSK) |
| 8 | Взлёт факт | actual_takeoff (MSK) + delay badge |
| 9 | Посадка факт | actual_landed (MSK) + delay badge |
| 10 | Длит. | duration_eff в формате "2ч 35м" |
| 11 | ВПП | runway_takeoff → runway_landed |
| 12 | ВС | aircraft_type |
| 13 | Трек | ✈ FR24 + 🛰 RTL-SDR + FA badge |

**Добавлен CSS:**
- `.src-badge` + `.src-fr24` для badge источника данных

---

### `schedule.js` — полная перепись

**Новые функции:**
- `fmtDuration(min)` — форматирует минуты как "2ч 35м" или "45м"

**`renderTable()` переписана:**
- 13 колонок вместо 12
- `actTakeoffCell`: actual_takeoff + delayCell(delay_takeoff_min)
- `actLandedCell`: actual_landed + delayCell(delay_landed_min)
- `airlineCell`: airline + `<small>registration</small>` под ней
- `srcBadge`: показывается если `sched_source === 'fr24'`
- `trackCol`: ✈ (если fr24_id) + 🛰 (если track_source=rtlsdr) + FA (если track_source=fa)
- `runway`: `runway_takeoff → runway_landed` или "—"
- `acType`: aircraft_type
- `durationCell`: fmtDuration(duration_eff)

**`renderCards()` обновлена** — добавлены строки:
- Взлёт факт + задержка
- Посадка факт + задержка
- Длительность
- ВПП
- ВС / Борт (aircraft_type / registration)
- Трек (✈ FR24, 🛰 RTL-SDR, FA)

**`delayCell()`** — убран возврат "—" для null (теперь возвращает пустую строку,
чтобы не засорять ячейки когда задержки нет)

**colspan** везде обновлён с 12 на 13 (setLoading, showError, empty state)

---

## Деплой

Файлы нужно скопировать на VM в контейнер `fr24-api`:
```
/home/fr24/projects/fr24/frontend/main.py
/home/fr24/projects/fr24/frontend/static/schedule.html
/home/fr24/projects/fr24/frontend/static/schedule.js
```
После: `docker restart fr24-api`

---
