# Plan poprawek MO Gantt

Data: 2026-03-30  
Stan bazowy: commit `8c11ac8`  
Ostatni push: commit `a4e2aa5` (2026-03-30)

---

## Status implementacji

| # | Problem | Status | Commit |
|---|---------|--------|--------|
| P0 | Grupowanie workstations (Forming / Trimming separatory) | ✅ | `070435f` |
| P1 | Multi-lane stacking | ✅ | `0c84432` |
| P2 | Ukryj puste wiersze | ✅ (built-in) | — |
| P3 | Min-width + ukryj etykiety na wąskich barach | ✅ | `0c84432` |
| P4 | Karta MO grid layout | ✅ | `de13452` |
| P5 | Podświetlenie dziś + weekendy | ✅ | `de13452` |
| P6 | Dynamiczna wysokość wiersza | ✅ | `0c84432` |
| — | Fix: position:relative override CSS | ✅ | `998c1a4` |
| — | Fix: shift_start migration (brak kolumny w DB) | ✅ | `6ec5e44` |
| — | Feat: changeover gap między MO (per workstation) | ✅ | `803ac78` |
| — | Feat: qty_per_hr fallback (form→laser→cnc) + stale date clear | ✅ | `a4e2aa5` |
| — | Gantt: ukończone MO sortowane jako pierwsze (lane 0) | ✅ | `a4e2aa5` |

---

## Problem 0 — Kolejność i grupowanie workstations

**Jak robi to Timeline:**  
`PlanningTimelineService::loadMachines()` filtruje:
- `forming` → `LIKE '%form%'` → FM1, FM2, FM7…
- `trimming` → `LIKE '%trim%'` → LASER1, CNC2… (`label` zawiera "Trim")

Timeline ma **dwie oddzielne strony** (`?group=forming` / `?group=trimming`).  
MO Gantt pokazuje wszystkie workstations naraz → potrzebne grupowanie z nagłówkiem sekcji.

**Plan:**  
W `ajax/mo_gantt_data.php` — po załadowaniu workstations przypisz każdej `group`:
```php
// FM* → 'forming', LASER*/CNC* → 'trimming', reszta → 'other'
function classifyWs($ref) {
    if (stripos($ref, 'FM') === 0)    return 'forming';
    if (stripos($ref, 'LASER') === 0) return 'trimming';
    if (stripos($ref, 'CNC') === 0)   return 'trimming';
    return 'other';
}
```
Sortuj: najpierw `forming` (ORDER: ref ASC), potem `trimming` (ORDER: ref ASC).  
Dodaj do każdej maszyny pole `"group": "forming"|"trimming"`.

W `js/mo_gantt.js` — `renderTimeline()` przed renderowaniem wiersza sprawdza czy zmienił się `group` → wstawia separator:
```html
<div class="pl-tl-section-header">Forming</div>
<div class="pl-tl-section-header">Trimming / CNC / Laser</div>
```

**Zmiana w:** `ajax/mo_gantt_data.php`, `js/mo_gantt.js`, `css/timeline.css` (styl separatora)

---

## Problem 1 — Nakładające się bary (KRYTYCZNE)

Wszystkie MO na tym samym workstation mają `top: 4px` → leżą na sobie.  
Potrzebne **wielopasmowe (multi-lane)** ułożenie — każde MO na swoim pasie,
wysokość wiersza rośnie dynamicznie z liczbą pasów.

**Zmiana w:** `js/mo_gantt.js` → `renderMachineRow()`  
Algorytm: dla każdego MO znajdź pierwszy wolny pas (lane), gdzie żaden istniejący
bar się nie nakłada — klasyczny greedy interval scheduling.

```
BAR_HEIGHT = 24px
BAR_GAP    = 4px
LANE_H     = BAR_HEIGHT + BAR_GAP
row height = LANE_COUNT * LANE_H + TOP_PADDING + BOTTOM_PADDING
top        = TOP_PADDING + lane * LANE_H
```

---

## Problem 2 — Puste wiersze workstations bez MO w oknie

Workstations bez żadnego MO (lub MO poza zakresem dat) wyświetlają puste wiersze.

**Zmiana w:** `js/mo_gantt.js` → `renderTimeline()`  
Filtruj: jeśli `machine.jobs.length === 0` → pomiń wiersz (nie renderuj HTML).

---

## Problem 3 — Bary zbyt wąskie, etykiety ucięte ("M...")

MO trwające kilka godzin = kilka pikseli → etykieta ginie.

**Zmiana w:** `js/mo_gantt.js`
- `min-width: 48px` na barze (CSS)
- Jeśli `widthPx < HIDE_LABEL_PX (60px)` → nie wstawiaj tekstu w bara, tylko tooltip

**Zmiana w:** `css/timeline.css`
- `.pl-tl-job-gantt-bar { min-width: 48px; }`

---

## Problem 4 — Karta MO — złe wyrównanie kolumn

Etykiety i wartości nachodzą na siebie; brak wyrównania.

**Zmiana w:** `css/timeline.css`
```css
.pl-job-card-row {
  display: grid;
  grid-template-columns: 110px 1fr;
  gap: 4px 8px;
}
.pl-job-card-label { text-transform: uppercase; font-size: 10px; color: #888; }
.pl-job-card-value { font-weight: 500; color: #222; }
```

---

## Problem 5 — Brak wizualnych wskaźników osi czasu

- Brak podświetlenia kolumny "dziś"
- Brak cieniowania weekendów (So/Nd)
- Brak pionowej linii "aktualny moment"

**Zmiana w:** `js/mo_gantt.js` → `renderTimeline()`
- Dodaj `data-today="1"` na header-day i day-cell gdzie `date === today`
- Dodaj `data-weekend="1"` gdzie `day.getDay() === 0 || 6`

**Zmiana w:** `css/timeline.css`
```css
.pl-tl-day-cell[data-today]       { background: rgba(25,168,122,0.07); }
.pl-tl-header-day[data-today]     { color: #19a87a; font-weight: 700; }
.pl-tl-day-cell[data-weekend]     { background: rgba(0,0,0,0.025); }
.pl-tl-header-day[data-weekend]   { color: #aaa; }
```

Linia "teraz" (optional, phase 2): absolutna pozycja w `.pl-tl-gantt-bars-container`
na podstawie `(Date.now() - axisStartMs) / MS_DAY * dayWidth`.

---

## Problem 6 — Rozmiar i proporcje wierszy

Aktualnie `height` wiersza jest stała (CSS). Po wprowadzeniu multi-lane
wysokość musi być dynamiczna (ustawiana inline przez JS).

**Zmiana w:** `js/mo_gantt.js`
```js
var rowHeightPx = TOP_PAD + laneCount * LANE_H + BOT_PAD;
html += '<div ... style="height:' + rowHeightPx + 'px">';
```

---

## Kolejność implementacji

| # | Priorytet | Zmiana |
|---|-----------|--------|
| 1 | KRYTYCZNY | Multi-lane stacking (Problem 1 + 6) |
| 2 | WYSOKI    | Ukryj puste wiersze (Problem 2) |
| 3 | WYSOKI    | Podświetlenie dziś + weekendy (Problem 5) |
| 4 | ŚREDNI    | Naprawa karty MO grid layout (Problem 4) |
| 5 | ŚREDNI    | Min-width + etykiety (Problem 3) |

---

## Changeover gap (zaimplementowane `803ac78`)

Kolumna `changeover_minutes` w `llx_planning_ws_config`. Silnik kalkulacji po zakończeniu każdego MO konsumuje gap z pozostałej pojemności dnia — następne MO nie może się zacząć podczas przezbrojenia.

**Konfiguracja:** Admin → Planning Setup → kolumna „Changeover (min)"
- Forming (FM*): `60` min
- Trimming/Laser/CNC: `30` min

---

## Qty per hour fallback (zaimplementowane `a4e2aa5`)

Dla każdego workstation `rateField` = primary (np. `qty_per_hour_laser` dla LASER1).  
Jeśli primary = 0/NULL → fallback kolejno: `form` → `laser` → `cnc`.  
Jeśli żadne pole nie ma wartości → MO pominięte, daty czyszczone do `NULL` (brak stale dates z poprzednich kalkulacji).

---

## Pliki do zmiany

- `js/mo_gantt.js` — cała logika renderowania
- `css/timeline.css` — style kart, barów, dziś/weekend
