Aktualna instancja online działa pod adresem: https://www.itgohome.com/dyzury-git1/.
To lekka aplikacja webowa do planowania dyżurów, rejestrowania nadgodzin, obsługi zastępstw i budowania zbiorczych podsumowań czasu dyżuru. Projekt został przygotowany jako prosty system do wdrożenia na zwykłym hostingu PHP/MySQL, bez frameworków, builda frontendowego i dodatkowych zależności serwerowych.
Aplikacja zastępuje pracę na arkuszach i rozproszonych zestawieniach. W praktyce obsługuje cztery główne obszary:
- plan dyżurów w widoku kalendarza,
- ewidencję nadgodzin i zgłoszeń,
- ewidencję zastępstw między pracownikami,
- podsumowanie godzin dyżuru netto.
System jest zoptymalizowany pod prostą obsługę:
- wybór pracownika z listy,
- szybkie przypisanie dyżuru do dnia i slotu,
- szybkie wyczyszczenie całego dnia,
- eksport danych do plików otwieranych w Excelu,
- awaryjny fallback diagnostyczny i demonstracyjny.
index.html- czysty HTML, CSS i JavaScript
- brak frameworka SPA, brak npm, brak bundlera
api.php- PHP 7.4+ / 8.x
- PDO
- JSON API
- MySQL / MariaDB
- schemat w
sql\schema.sql
Nager.Date APIdo pobierania świąt w Polsce
Architektura jest świadomie prosta:
index.htmlładuje interfejs, utrzymuje stan aplikacji i komunikuje się zapi.php.api.phpprzyjmuje żądaniaGET,POST,DELETE, wykonuje operacje na bazie i zwraca czysty JSON.- Baza przechowuje pracowników, dyżury, nadgodziny i zastępstwa.
- Frontend sam renderuje kalendarz, listy, tabele podsumowań i eksporty XLS.
Najważniejsza cecha wdrożeniowa: projekt nie wymaga procesu budowania. W praktyce oznacza to, że do uruchomienia wystarczą pliki statyczne, PHP i działająca baza MySQL.
Aplikacja pokazuje miesiąc w formie kalendarza. Dla każdego dnia:
- rozpoznaje, czy dzień jest roboczy, weekendowy lub świąteczny,
- pokazuje już przypisane sloty,
- pokazuje wolne sloty możliwe do obsadzenia,
- pozwala przypisać pojedynczy slot albo cały dzień,
- pozwala usunąć pojedynczy dyżur lub wyczyścić cały dzień.
Każdy wpis nadgodzin zawiera:
- pracownika,
- datę i godzinę rozpoczęcia,
- datę i godzinę zakończenia,
- liczbę zgłoszeń,
- opis czynności.
Wpisy można:
- dodawać,
- edytować,
- usuwać,
- filtrować per pracownik,
- oglądać w widoku miesięcznym lub kwartalnym,
- eksportować do XLS.
Moduł zastępstw zapisuje przekazanie fragmentu dyżuru od jednej osoby do drugiej. Każdy wpis ma:
- datę,
- godzinę od,
- godzinę do,
- osobę przekazującą,
- osobę przejmującą.
Zastępstwa wpływają na podsumowanie godzin dyżuru: czas jest odejmowany osobie oddającej i dodawany osobie przejmującej.
Podsumowanie wylicza dla pracownika:
- sumę godzin wynikających z przypisanych zmian,
- sumę nadgodzin,
- dyżur netto:
dyżur - nadgodziny, - łączną liczbę zgłoszeń.
Widoki podsumowania:
- miesiąc,
- kwartał,
- rok.
System korzysta z trzech slotów:
| Slot | Zakres | Minuty | Godziny dziesiętne |
|---|---|---|---|
night |
00:00-07:30 |
450 | 7.50 |
day |
07:30-15:05 |
455 | 7.58 |
evening |
15:05-23:59 |
534 | 8.92 |
- w dni robocze dostępne są tylko:
night,evening - w weekendy i święta dostępne są:
night,day,evening
Święta są pobierane z https://date.nager.at/api/v3/PublicHolidays/{rok}/PL.
Jeżeli pobranie świąt się nie powiedzie, aplikacja działa dalej, ale bez zewnętrznej listy świąt.
W bazie działa ograniczenie:
- jeden slot może wystąpić tylko raz dla danego dnia
Realizowane jest to przez:
- unikalny klucz
(shift_date, slot)w tabelishifts, - kontrolę po stronie API, która zwraca
409 Slot already assigned, jeżeli slot jest już zajęty.
Nadgodziny są zaokrąglane zawsze w górę do rozpoczętego bloku 30 minut:
- 1-30 minut =
0.5 h - 31-60 minut =
1.0 h - 61-90 minut =
1.5 h
Logika w backendzie:
$roundedHalfHours = ceil($minutes / 30) / 2.0;Podsumowanie nie traktuje zastępstwa jako osobnego typu dyżuru. Zamiast tego:
- odejmuje czas od
from_employee_id, - dodaje ten sam czas do
to_employee_id.
Dzięki temu raport odzwierciedla rzeczywiście wykonany dyżur.
Backend blokuje przypisania, które naruszają aktualny model danych:
- nie można przypisać do dyżuru pracownika nieaktywnego (
employees.active = 0), - nie można zapisać zastępstwa z osobą oddającą lub przejmującą, która jest nieaktywna,
- osoba oddająca w zastępstwie musi mieć w tym dniu przypisany slot dyżurowy pokrywający cały zakres
start_time-end_time.
Ograniczenie modelu: w obecnym schemacie nie ma tabel/pól opisujących kompetencje lub uprawnienia per typ zmiany, więc backend nie może walidować bardziej szczegółowych kwalifikacji niż status aktywności i faktyczne przypisanie do slotu.
index.html- cały frontend aplikacjiapi.php- backend REST/JSONlib/audit.php- prosty zapis audytu zmian CRUDconfig.sample.php- wzór konfiguracji bazysql\schema.sql- pełny schemat bazy
test-api.php- test połączenia z bazą i podstawowej odpowiedzi APItest-json.php- test czystości odpowiedzi JSONtest-insert.php- test zapisu do bazycheck-schema.php- diagnostyka schematu i kolumntest-audit-log.php- test zapisu wpisów audytowych (actor/action/before/after)
HOSTINGER-DEBUG.mdFIX-JSON-ERROR.mdFIX-MISSING-COLUMNS.mdFIX-ACCESS-DENIED.mdDEBUG-HTML-RESPONSE.mdQUICK-MIGRATION.mdQUICK-FIX.md
1-kalendarz.docx2-prace.exls.xlsxDYZURY_2025.xlsxsnapshots\
Przechowuje słownik pracowników.
| Kolumna | Typ | Opis |
|---|---|---|
id |
INT |
klucz główny |
name |
VARCHAR(255) |
imię i nazwisko, unikalne |
phone |
VARCHAR(50) |
numer telefonu |
color |
VARCHAR(20) |
kolor używany w UI |
avatar |
TEXT |
URL avatara |
active |
TINYINT(1) |
aktywność pracownika |
created_at |
TIMESTAMP |
data utworzenia |
Uwagi:
- pracownik nieaktywny pozostaje w danych historycznych,
- pracownik nieaktywny nie jest domyślnie pokazywany w listach wyboru nowych wpisów.
Przechowuje przypisane dyżury.
| Kolumna | Typ | Opis |
|---|---|---|
id |
INT |
klucz główny |
shift_date |
DATE |
data dyżuru |
slot |
ENUM |
night, day, evening |
employee_id |
INT |
FK do employees.id |
note |
TEXT |
notatka do wpisu |
created_at |
TIMESTAMP |
data utworzenia |
updated_at |
TIMESTAMP |
data modyfikacji |
Dodatkowo:
- unikalny klucz:
(shift_date, slot) - usunięcie pracownika usuwa jego dyżury przez
ON DELETE CASCADE
Przechowuje wpisy nadgodzin.
| Kolumna | Typ | Opis |
|---|---|---|
id |
INT |
klucz główny |
employee_id |
INT |
FK do employees.id |
started_at |
DATETIME |
początek |
ended_at |
DATETIME |
koniec |
rounded_hours |
DECIMAL(5,2) |
wynik po zaokrągleniu |
call_count |
INT |
liczba zgłoszeń |
description |
TEXT |
opis czynności |
created_at |
TIMESTAMP |
data utworzenia |
Przechowuje zastępstwa.
| Kolumna | Typ | Opis |
|---|---|---|
id |
INT |
klucz główny |
sub_date |
DATE |
data zastępstwa |
start_time |
TIME |
początek |
end_time |
TIME |
koniec |
from_employee_id |
INT |
osoba oddająca |
to_employee_id |
INT |
osoba przejmująca |
created_at |
TIMESTAMP |
data utworzenia |
Główny ekran składa się z kilku paneli:
- nawigacja po miesiącach,
- kalendarz dyżurów,
- formularz i lista nadgodzin,
- formularz i lista zastępstw,
- podsumowanie zbiorcze,
- panel administracyjny pracowników,
- przyciski eksportu.
Panele są zwijane i rozwijane po kliknięciu nagłówka.
Frontend:
- wyświetla przypisanych pracowników w formie badge z nazwą,
- oznacza dni wolne,
- pokazuje dostępne sloty,
- pozwala przypisać zmianę jednym kliknięciem,
- pozwala przypisać cały dzień wybranemu pracownikowi,
- pokazuje przy zmianie informację o zastępstwie.
Uwaga implementacyjna: słownik pracowników przechowuje color i avatar, ale aktualne odpowiedzi API dla shifts i overtime nie zwracają tych pól. W efekcie w części widoków interfejs operuje dziś głównie na nazwie pracownika, a nie na pełnych metadanych wizualnych.
Lista nadgodzin:
- grupuje wpisy per pracownik,
- sortuje wpisy chronologicznie,
- umożliwia edycję i usuwanie,
- pozwala filtrować listę po pracowniku.
Widoki nadgodzin:
- miesiąc,
- kwartał.
Lista zastępstw:
- sortuje wpisy po dacie i godzinie,
- pozwala edytować wpis,
- pozwala usuwać wpis,
- waliduje, że osoba oddająca i przyjmująca nie mogą być tą samą osobą,
- waliduje dodatni przedział czasu.
Frontend wspiera:
- dodanie nowego pracownika,
- edycję danych pracownika,
- archiwizację,
- przywrócenie pracownika.
Archiwizacja nie usuwa historycznych danych z raportów.
Podsumowanie wspiera trzy zakresy:
- miesiąc,
- kwartał,
- rok.
Dla zakresów kwartalnych i rocznych frontend dociąga dodatkowe miesiące przez API, a następnie przelicza zbiorcze dane po stronie klienta.
Dostępne są dwa eksporty:
dyzury-YYYY-MM.xlsnadgodziny-YYYY-MM.xls
Technicznie są to pliki HTML zapisane z nagłówkiem/BOM UTF-8 i rozszerzeniem .xls, tak aby otwierały się poprawnie w Excelu.
- datę,
- dzień tygodnia,
- zmianę,
- liczbę godzin w formacie
hh:mm, - pracownika,
- notatkę,
- informację o zastępstwach dopisaną do notatki.
- pracownika,
- datę,
- zakres godzin,
- czas realizacji,
- opis czynności.
API działa pod adresem:
- produkcja:
https://www.itgohome.com/dyzury-git1/api.php - lokalnie:
api.php
Backend ustawia:
Content-Type: application/jsonAccess-Control-Allow-Origin: *Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONSAccess-Control-Allow-Headers: Content-Type
Ważna cecha implementacyjna:
- skrypt czyści output buffer i wyłącza wyświetlanie błędów PHP, żeby nie psuć JSON-a.
W obszarze zapisów shifts i substitutions backend zwraca spójny format:
{
"error": "Czytelny komunikat błędu",
"error_type": "validation_error",
"field": "nazwa_pola"
}Uwagi:
errorpozostaje kompatybilne wstecz (frontend już je odczytuje),error_typepozwala frontendowi łatwo rozpoznać błąd walidacyjny,fieldwskazuje pole (lub listę pól rozdzieloną przecinkami), którego dotyczy walidacja.
Zwraca listę pracowników:
[
{
"id": 1,
"name": "Jan Kowalski",
"phone": "+48 600 000 000",
"color": "#4c6fff",
"avatar": null,
"active": 1
}
]Dodanie nowego pracownika albo aktualizacja istniejącego.
Przykładowe body:
{
"name": "Jan Kowalski",
"phone": "+48 600 000 000",
"color": "#4c6fff",
"avatar": null,
"active": 1
}Jeżeli przekazany jest id, wykonywana jest aktualizacja.
Pobiera zmiany dla miesiąca:
- parametr:
month=YYYY-MM
Zwracane pola:
idshift_dateslotemployee_idemployee_namenote
Dodaje dyżur.
Body:
{
"shift_date": "2026-02-08",
"slot": "night",
"employee_id": 1,
"note": null
}Odpowiedzi:
200 { "status": "saved" }400 { "error": "...", "error_type": "validation_error", "field": "..." }np. błędnyshift_date,employee_id,slot,note404 { "error": "...", "error_type": "validation_error", "field": "employee_id" }gdy wskazany pracownik nie istnieje409 { "error": "Slot already assigned", "error_type": "validation_error", "field": "shift_date,slot" }
Usuwa zmianę po id.
Pobiera wpisy nadgodzin dla miesiąca:
- parametr:
month=YYYY-MM
Zwracane pola:
idemployee_idemployee_namestarted_atended_atrounded_hourscall_countdescription
Dodaje albo aktualizuje wpis nadgodzin.
Body:
{
"employee_id": 1,
"started_at": "2026-02-03 18:30",
"ended_at": "2026-02-03 19:15",
"call_count": 2,
"description": "Awaria serwera"
}Jeżeli przekazany jest id, wykonywana jest aktualizacja.
Backend sam:
- normalizuje daty,
- liczy czas,
- oblicza
rounded_hours.
Usuwa wpis po id.
Pobiera zastępstwa dla miesiąca:
- parametr:
month=YYYY-MM
Dodaje albo aktualizuje zastępstwo.
Body:
{
"sub_date": "2026-02-08",
"start_time": "08:00",
"end_time": "12:00",
"from_employee_id": 1,
"to_employee_id": 2
}Jeżeli przekazany jest id, wykonywana jest aktualizacja.
Walidacje backendowe i frontendowe:
- obie osoby muszą być wybrane,
- osoby nie mogą być identyczne,
- zakres godzin musi być dodatni,
sub_datemusi mieć formatYYYY-MM-DD,start_timeiend_timemuszą mieć formatHH:MMalboHH:MM:SS,- wskazani pracownicy muszą istnieć,
- przy aktualizacji przekazane
idmusi wskazywać istniejące zastępstwo, - zastępstwo musi nachodzić na dyżur osoby przekazującej,
- zastępstwo nie może nachodzić na dyżur osoby przejmującej,
- zastępstwo nie może nachodzić na inne zastępstwo tej samej osoby.
- wskazani pracownicy muszą być aktywni (
employees.active = 1), - osoba oddająca musi mieć przypisany slot pokrywający cały zakres zastępstwa.
Błędy walidacyjne dla POST i DELETE mają spójny format:
400/404 { "error": "...", "error_type": "validation_error", "field": "..." }
Usuwa wpis po id.
Pobiera podsumowanie miesięczne.
- parametr:
month=YYYY-MM
Zwracane pola:
employee_idemployee_nameshift_hoursovertime_hoursnet_oncallcalls
W widokach kwartalnych i rocznych frontend nie ma osobnego endpointu zbiorczego. Zamiast tego dociąga miesiące i liczy agregację lokalnie.
Dedykowany interfejs raportowy (niezależny od UI operacyjnego).
Obsługiwane filtry:
month=YYYY-MM(domyślnie bieżący miesiąc, jeśli brak zakresu)start_date=YYYY-MM-DDend_date=YYYY-MM-DDemployee_id=<int>
Jeśli podane są start_date i end_date, mają priorytet nad month.
Zwracane dane:
filters:start_dateend_dateemployee_id
by_employee(agregacje per osoba):employee_idemployee_nameshift_countshift_hoursovertime_hoursnet_oncallcallssubstitutions_out_hourssubstitutions_in_hourssubstitutions_balance_hours
totals(agregacje zbiorcze):employeesshift_countshift_hoursovertime_hoursnet_oncall
callssubstitutions_out_hourssubstitutions_in_hourssubstitutions_balance_hours
Pobiera historię zmian dla dyżurów i zastępstw (maksymalnie 200 ostatnich wpisów).
Parametry (opcjonalne):
entity_type=shift|substitutionentity_id=<id obiektu>day=YYYY-MM-DD(filtrowanie po dacie obiektu:shift_date/sub_date)
Przykłady:
- historia dla konkretnego obiektu:
api.php?resource=history&entity_type=shift&entity_id=123 - historia dla dnia:
api.php?resource=history&day=2026-02-08
Zwracane pola:
identity_typeentity_idaction(create,update,delete)entity_dateactorbefore_stateafter_statecreated_at
- hosting z PHP
- dostęp do MySQL
- możliwość wgrania plików przez FTP lub panel hostingowy
Nie są wymagane:
- Composer
- Node.js
- proces budowania
Skopiuj:
config.sample.php->config.php
Uzupełnij:
return [
'dsn' => 'mysql:host=localhost;dbname=TWOJA_NAZWA_BAZY;charset=utf8mb4',
'user' => 'TWOJ_USER',
'password' => 'TWOJE_HASLO',
];Ważne dla Hostinger:
- używaj
host=localhost - nie dopisuj portu
:3306, jeśli hosting tego nie wymaga
Uruchom w bazie:
sql\schema.sql
Skrypt:
- tworzy wszystkie główne tabele (w tym
audit_log), - zakłada klucze obce,
- dodaje dane przykładowe przez
INSERT IGNORE.
Jeżeli baza już istnieje, a brakuje części kolumn, skorzystaj z:
sql\migrate-simple.sqlsql\migrate-add-columns.sqlFIX-MISSING-COLUMNS.md
Do wdrożenia wystarczą:
index.htmlapi.phpconfig.php
Opcjonalnie warto zostawić lokalnie lub tymczasowo wgrać pliki diagnostyczne do debugowania.
Minimalna checklista:
- Otwórz
index.html. - Sprawdź, czy ładuje się lista pracowników.
- Dodaj testowy dyżur.
- Dodaj testową nadgodzinę.
- Sprawdź eksport XLS.
- Sprawdź, czy podsumowanie aktualizuje się po zapisach.
- Przejdź do zakładki
Raportyi sprawdź, czy ekran jest odseparowany od panelu dnia. - Sprawdź filtrowanie raportu po zakresie dat (
Od daty/Do daty) oraz po pracowniku. - Sprawdź, czy metryki i tabela raportu aktualizują się po zastosowaniu filtrów.
- Sprawdź wydruk harmonogramu:
- otwórz podgląd wydruku (
Ctrl+P/Cmd+P), - potwierdź, że widoczne są: miesiąc, dni tygodnia, daty, osoby, zmiany i status dnia,
- potwierdź, że ukryte są kontrolki operacyjne (nawigacja, eksporty, przyciski akcji, sekcje zwijane),
- sprawdź czytelność układu zarówno dla bieżącego miesiąca, jak i po przejściu na inny miesiąc.
- Dodaj nowy dyżur i zapamiętaj jego
id(z odpowiedzi API lub po liście zmian). - Edytuj lub usuń zastępstwo.
- Otwórz panel Historia zmian w UI.
- Sprawdź filtr po dniu (
Dzień) i potwierdź, że wpisy dla wskazanej daty są widoczne. - Ustaw filtr typu (
Dyżury/Zastępstwa) orazID obiektui kliknij Odśwież. - Zweryfikuj, że lista pokazuje akcję (
Utworzenie/Edycja/Usunięcie) oraz stanPrzed/Pow czytelnej formie.
Tryb demo nie jest stałym, ręcznie przełączanym trybem pracy. W kodzie działa jako uzbrojony fallback.
- adres z
?demo=1pozwala na uruchomienie danych przykładowych, - dane przykładowe są używane dopiero wtedy, gdy API nie odpowiada lub zwraca błąd,
- jeśli API działa poprawnie, aplikacja nadal pracuje online na rzeczywistych danych.
Przykład:
https://www.itgohome.com/dyzury-git1/?demo=1
- frontend przechodzi w
state.offline = true - wyświetla badge
Tryb demonstracyjny (brak API) - ładuje zestaw przykładowych pracowników, dyżurów i nadgodzin
- część operacji zapisuje się tylko lokalnie w pamięci sesji przeglądarki
Najważniejsze formularze (Nadgodziny, Zastępstwa) zapisują robocze dane w localStorage.
Checklist:
- Otwórz aplikację i wpisz dane w formularzu
NadgodzinylubZastępstwa. - (Symulacja problemu sieci) w narzędziach deweloperskich przeglądarki ustaw tryb
Offlinei kliknij zapis. - Sprawdź, że pojawia się komunikat:
Brak synchronizacji: ... zapisany lokalnie. - Odśwież stronę.
- Sprawdź, że wpisane dane formularza nadal są obecne.
- Przywróć sieć i zapisz formularz ponownie.
- Po poprawnym zapisie komunikat o braku synchronizacji znika.
Zobacz:
DEBUG-HTML-RESPONSE.mdFIX-JSON-ERROR.md
Zobacz:
HOSTINGER-DEBUG.mdFIX-ACCESS-DENIED.md
Zobacz:
FIX-MISSING-COLUMNS.mdQUICK-MIGRATION.md
Zobacz:
QUICK-FIX.md
W aktualnym repozytorium dostępne są lekkie skrypty testowe (bez pełnego frameworka test runnera):
-
tests/run.php- automatyczne testy CRUD API (shifts,substitutions) -
test-summary.php(test logiki podsumowania: godziny, nadgodziny, zastępstwa, przypadki graniczne) -
lintowanie PHP (
php -l api.php,php -l config.sample.php), -
weryfikacja ręczna przez wywołania API i UI.
Obecna wersja jest prosta wdrożeniowo, ale ma też świadome ograniczenia:
- brak logowania i autoryzacji,
- API jest dostępne bez warstwy uprawnień,
- CORS jest ustawiony szeroko na
*, - brak CSRF protection,
- automatyczne testy obejmują podstawowy CRUD API dla
shiftsisubstitutions, - audit zmian obejmuje CRUD dyżurów i zastępstw (actor/timestamp/action/before/after),
- brak modelu kompetencji/uprawnień per slot lub typ zmiany (brak danych do pełnej walidacji kwalifikacji).
W praktyce oznacza to, że system najlepiej działa:
- w środowisku z ograniczonym dostępem,
- za warstwą organizacyjną lub sieciową,
- jako narzędzie zespołowe o kontrolowanym użyciu.
Na moment aktualizacji dokumentacji:
- frontend działa publicznie pod
https://www.itgohome.com/dyzury-git1/ - API odpowiada pod
https://www.itgohome.com/dyzury-git1/api.php - instancja produkcyjna zwraca dane pracowników przez endpoint
employees
README opisuje zarówno lokalny kod projektu, jak i aktualny model wdrożenia.
Na moment tej aktualizacji repozytorium ma już przygotowany materiał wejściowy do następnego etapu backendowego:
- zakończono Issue #3 — pełną inwentaryzację punktów zapisu dyżurów i zastępstw,
- dodano trwały plik
AGENT-STATUS.txt, który utrzymuje stan między sesjami, - wskazano wspólne miejsce pod centralną walidację: wejście do
api.phppo sparsowaniu JSON i przed wykonaniem SQL dlashiftsorazsubstitutions.
Najważniejsze ustalenia z zakończonej analizy:
- frontend zapisuje dyżury w sześciu miejscach (
assignFullDay,assignSlot,assignShift,quickAssignAvailable,clearDay,removeShift), - frontend zapisuje zastępstwa w dwóch miejscach (
submitSubstitution,deleteSubstitution), - backend ma już częściową walidację konfliktu slotów i brakujących pól, ale nadal brakuje centralnej walidacji reguł biznesowych,
- największe luki dotyczą walidacji formatu dat i godzin, listy dozwolonych slotów, dodatnich identyfikatorów, istnienia pracowników oraz spójnego przygotowania błędów.
Do bieżącego śledzenia stanu prac i kolejnych kroków służy AGENT-STATUS.txt.
Aktualny stan pokrycia testowego:
- brak testów automatycznych backendu,
- brak testów integracyjnych API (
shifts,substitutions,summary), - brak testów logiki obliczeń podsumowań i wpływu zastępstw,
- brak testów regresyjnych walidacji kolizji.
Najwyższe priorytety testowe:
- API dyżurów (
resource=shifts)
CRUD + walidacje wejścia + konflikt unikalnego slotu (409). - API zastępstw (
resource=substitutions)
CRUD + walidacja zakresów czasu + walidacja pracowników + przypadki aktualizacji. - API podsumowań (
resource=summary)
Poprawność agregacjishift_hours,overtime_hours,net_oncall,callsoraz wpływu zastępstw. - Testy walidacji kolizji i reguł domenowych
scenariusze graniczne (te same sloty, nakładające się zakresy, błędne daty/godziny, brakujące pola).
Do repozytorium dołączono pliki z dotychczasowego procesu pracy:
1-kalendarz.docx2-prace.exls.xlsxDYZURY_2025.xlsx
Mogą służyć jako:
- materiał porównawczy,
- wzorzec eksportów,
- punkt odniesienia przy dalszym rozwoju aplikacji.
Najbardziej naturalne kierunki rozwoju:
- dokończenie Issue #4 przez wydzielenie wspólnej warstwy walidacji dla zapisów dyżurów i zastępstw,
- logowanie użytkowników i role,
- historia zmian,
- eksport PDF lub wydruku,
- walidacja kolizji i ograniczeń po stronie backendu,
- osobny moduł raportów,
- lepszy tryb offline,
- testy automatyczne dla API i logiki podsumowań.
Poniżej mapa modułów i punktów odpowiedzialności dla dalszych etapów prac.
Frontend jest utrzymywany w index.html:
- konfiguracja i stan aplikacji:
API_BASE,shiftSlots,state, - komunikacja HTTP i obsługa błędów API:
fetchJson, - ładowanie danych:
loadEmployees,loadShifts,loadOvertime,loadSubstitutions,loadSummary,loadSummaryForView, - logika dyżurów (kalendarz):
assignFullDay,assignSlot,assignShift,quickAssignAvailable,clearDay,removeShift, - logika zastępstw:
submitSubstitution,deleteSubstitution,applySubstitutionsToSummary, - logika podsumowań:
loadSummary,computeLocalSummary,computeSummaryFromData, - eksport/raportowanie w obecnej wersji:
exportShiftsXls,exportOvertimeXls, - offline/fallback demo:
useSampleData,state.offline,setErrorBadge.
Backend działa jako monolit w api.php:
- warstwa transportu i kontraktu błędów JSON:
respond,errorResponse, - walidacja wejścia i reguł domenowych:
validateShiftWriteInput,validateSubstitutionWriteInput,validateEmployeeId,validateShiftDeleteInput,validateSubstitutionDeleteInput, - operacje CRUD API:
resource=employees,resource=shifts,resource=overtime,resource=substitutions,resource=summary,
- agregacja podsumowań i wpływ zastępstw: sekcja
resource=summary+timeDiffHours.
- dyżury: frontend (
assign*,clearDay,removeShift) + backend (resource=shifts), - zastępstwa: frontend (
submitSubstitution,deleteSubstitution) + backend (resource=substitutions), - podsumowania: backend (
resource=summary) oraz frontend (loadSummaryForView,computeSummaryFromDatadla kwartału/roku), - zapis danych: wszystkie
POST/DELETEwapi.phpi wywołaniafetchJson(...resource=...)windex.html, - walidacje: backendowe funkcje
validate*wapi.php(kluczowe miejsce centralizacji reguł), - raportowanie/eksport: frontendowe
exportShiftsXls,exportOvertimeXls(XLS jako HTML), - offline:
state.offline,useSampleData, lokalne fallbackiaddLocalShifti obsługa błędów wfetchJson.
- backend validation: rozszerzać centralne funkcje
validate*wapi.phpi wywoływać je przed SQL, - historia zmian (audit log): backend
api.phpprzy operacjachPOST/DELETE(shifts,overtime,substitutions,employees) + nowa tabela SQL i endpoint odczytu historii, - raporty: osobny endpoint backendowy (np. nowe
resource=reports) i osobny panel windex.htmlzamiast rozbudowywania istniejących eksportów, - eksport PDF/print: frontend (
index.html) przez dedykowany widok do druku +window.print()jako pierwszy krok; PDF generować po stronie przeglądarki lub serwera w kolejnym etapie, - ulepszenia offline: najpierw trwały cache i kolejka operacji po stronie frontend (np.
localStorage/IndexedDB), następnie synchronizacja po odzyskaniu połączenia.
- frontend: brak npm/bundlera/frameworka,
- backend: PHP + PDO + MySQL/MariaDB,
- baza:
sql/schema.sql, - zewnętrzne API świąt: Nager.Date.
Aktualny sposób uruchamiania:
- lint backendu:
find . -maxdepth 2 -type f -name '*.php' -print0 | xargs -0 -n1 php -l, - build: brak procesu build (projekt uruchamiany bez bundlowania),
- testy automatyczne: brak stałego mechanizmu testów w repo; bieżąca walidacja opiera się na diagnostyce API i ręcznej weryfikacji przepływów UI.
- domknąć i rozszerzać walidację backendową (jeden punkt wejścia reguł),
- dodać testy automatyczne API i logiki podsumowań,
- dodać historię zmian (audit log) dla wszystkich operacji zapisu,
- wydzielić moduł raportowania i zakresy raportów,
- wdrożyć print/PDF na stabilnym modelu danych raportowych,
- rozbudować offline (cache + synchronizacja konfliktów).
- monolityczny
index.htmliapi.phpzwiększają ryzyko regresji przy równoległych zmianach, - część agregacji podsumowań jest liczona po stronie klienta (kwartał/rok), co utrudnia spójność przy zmianach reguł,
- brak pełnych testów automatycznych utrudnia bezpieczne tempo wdrożeń,
- fallback demo/offline może maskować błędy API, jeśli nie będzie jasno raportowany,
- eksport XLS oparty na HTML nie pokrywa wymagań jakościowych dla PDF/print bez dodatkowego etapu projektowego.
Poniżej opis docelowego, prostego modelu audytu zmian przygotowany pod wdrożenie etapami (bez implementacji w tej zmianie).
Celem jest rejestrowanie wiarygodnej historii zmian dla kluczowych operacji:
- dyżury (
shifts): utworzenie, usunięcie, - zastępstwa (
substitutions): utworzenie, aktualizacja, usunięcie.
Każde zdarzenie audytowe musi zawierać:
actor— kto wykonał zmianę,event_at(timestamp) — kiedy zmiana została zapisana przez backend,operation_type— typ operacji,state_before— stan rekordu przed zmianą,state_after— stan rekordu po zmianie.
Definicja czasu zdarzenia: event_at oznacza czas wygenerowany przy INSERT rekordu audytu wewnątrz transakcji DB, a nie moment odebrania żądania HTTP. Przy rollbacku transakcji rekord audytu również nie zostaje utrwalony.
Nowa tabela: audit_events.
| Kolumna | Typ (propozycja) | Opis |
|---|---|---|
id |
BIGINT AUTO_INCREMENT |
klucz główny zdarzenia |
entity_type |
VARCHAR(32) |
np. shift, substitution |
entity_id |
INT |
identyfikator rekordu biznesowego |
operation_type |
VARCHAR(32) |
np. create, update, delete |
actor |
VARCHAR(255) |
identyfikator wykonawcy operacji |
event_at |
TIMESTAMP(3) (MySQL/MariaDB) |
czas zdarzenia po stronie backendu (UTC) |
state_before |
JSON/LONGTEXT |
snapshot przed zmianą |
state_after |
JSON/LONGTEXT |
snapshot po zmianie |
context |
JSON/LONGTEXT NULL |
opcjonalnie: request_id, ip, user_agent |
Indeksy minimalne:
INDEX idx_audit_entity (entity_type, entity_id, event_at),INDEX idx_audit_event_at (event_at),INDEX idx_audit_actor (actor, event_at).
Snapshot powinien przechowywać tylko pola istotne audytowo (bez danych pochodnych z joinów):
- dla
shift:id,shift_date,slot,employee_id,note, - dla
substitution:id,sub_date,start_time,end_time,from_employee_id,to_employee_id.
Uwaga: w aktualnym modelu substitutions nie mają pola note, więc snapshot obejmuje komplet realnie zapisywanych kolumn biznesowych tej encji.
Zasady:
create:state_before = null,state_after = nowy rekord,update: oba snapshoty obecne,delete:state_before = usuwany rekord,state_after = null.
Aktualnie aplikacja nie ma logowania użytkowników, więc actor musi być dostarczany etapowo:
- etap minimalny: wartość techniczna (
anonymouslubsystem) + opcjonalnie IP/User-Agent wcontext, - etap docelowy: jawny identyfikator użytkownika (np. nagłówek
X-Actorz warstwy proxy/SSO), - po wdrożeniu auth:
actormapowany do rzeczywistego konta.
To pozwala uruchomić audyt od razu, a później zwiększać wiarygodność identyfikacji.
Wymóg bezpieczeństwa: jeśli używany jest nagłówek X-Actor, backend musi ufać wyłącznie nagłówkowi nadpisanemu przez zaufaną warstwę pośrednią (proxy/SSO) i nie może akceptować tej wartości bezpośrednio od klienta publicznego.
Praktycznie oznacza to co najmniej jedną technikę odróżnienia ruchu zaufanego: preferowane mTLS między proxy a backendem albo podpisany nagłówek przekazywany wyłącznie w sieci wewnętrznej; lista dozwolonych adresów IP proxy (allowlist) może pełnić wyłącznie kontrolę uzupełniającą.
Miejsce zapisu: backend api.php, dokładnie w gałęziach write dla resource=shifts i resource=substitutions.
Wymaganie spójności:
- zapis biznesowy i wpis audytowy powinny być wykonane w jednej transakcji DB,
- dla
update/deletetrzeba odczytać stan „przed” tuż przed modyfikacją.
Minimalny wpływ na obecny kod:
- dodanie jednej funkcji pomocniczej
writeAuditEvent(...), - punktowe wywołania po udanym SQL (lub w transakcji),
- brak zmian kontraktu istniejących endpointów dla frontendu.
Etap A (MVP, niski risk):
- dodać tabelę
audit_events, - logować tylko
substitutions(create/update/delete), actortechniczny, snapshoty pełne dla zastępstw.
Etap B:
- rozszerzyć logowanie na
shifts(create/delete), - ujednolicić słownik
operation_typeientity_type, - dodać
request_idwcontextdla łatwiejszego śledzenia.
Etap C:
- dodać endpoint tylko do odczytu historii (np. filtrowanie po typie encji, zakresie dat, actorze),
- dodać podstawowy widok historii w UI (bez edycji).
Etap D (po auth):
- podmienić
actortechniczny na tożsamość użytkownika, - dodać politykę retencji/archiwizacji dla dużych wolumenów.
- „model obejmuje kluczowe operacje”: tak —
shiftsisubstitutionsCRUD w zakresie używanym przez aplikację, - „wiadomo gdzie zapisywać zdarzenia”: tak — write-pathy w
api.php+ jedna tabelaaudit_eventsw tej samej bazie, - „rozwiązanie możliwe do wdrożenia etapami”: tak — etapy A→D z rosnącą dojrzałością i bez dużego refaktoru.
| Wariant | Koszt wdrożenia | Ryzyko | Spójność z obecną architekturą | Utrzymanie |
|---|---|---|---|---|
| Print CSS (browser print) | niski | niski/średni (różnice między przeglądarkami) | bardzo wysoka (frontend już renderuje widok i nie wymaga procesu buildu) | łatwe |
| PDF po stronie frontend | średni | średni/wysoki (jakość paginacji, biblioteki JS, wydajność na dużych widokach) | średnia (nowe zależności, większa złożoność w index.html) |
średnie/trudniejsze |
| PDF po stronie backend | wysoki | średni (silnik PDF, infrastruktura serwerowa, większy zakres zmian API) | średnia/niższa (obecny backend to proste API JSON bez warstwy raportowej) | średnie (stabilny wynik, ale większy koszt operacyjny) |
MVP: Print CSS + akcja „Drukuj” dla widoku harmonogramu miesięcznego.
To jest najbezpieczniejszy i najmniej inwazyjny wariant względem obecnej architektury (index.html + api.php, bez procesu buildu). Pozwala szybko dostarczyć funkcję biznesową bez zmiany logiki backendu i bez dokładania ciężkiej biblioteki PDF.
Wariant docelowy: PDF po stronie backend (dedykowany endpoint raportowy).
W dłuższym horyzoncie da to najbardziej powtarzalny, kontrolowany rezultat (ten sam plik niezależnie od przeglądarki), łatwiejszy do archiwizacji i wysyłki. Wymaga jednak osobnego etapu architektonicznego i wdrożeniowego.
Dla MVP (Print CSS) wspierane powinny być:
- widok harmonogramu miesięcznego (kalendarz) jako zakres obowiązkowy,
- opcjonalnie podsumowanie miesięczne jako osobny widok do druku dopiero po stabilizacji kalendarza.
Poza zakresem MVP:
- generowanie „piksel-perfect” PDF,
- pełna zgodność każdej przeglądarki i każdej drukarki,
- eksport wszystkich sekcji aplikacji (nadgodziny, administracja, historia) w pierwszym etapie.
Główne ograniczenia Print CSS:
- różnice paginacji i marginesów między przeglądarkami,
- zależność od ustawień użytkownika (skala, nagłówki/stopki, orientacja),
- ograniczona kontrola nad podziałem tabel/kalendarza na strony.
Poniżej minimalny zakres funkcjonalny modułu raportów (MVP) jako osobnego obszaru aplikacji.
- osobna zakładka/sekcja
Raporty(oddzielona od widoku operacyjnego kalendarza), - jeden panel filtrów daty z gotowymi zakresami:
- miesiąc,
- kwartał,
- rok,
- zakres własny (
data od,data do),
- jedna tabela zbiorcza „Obciążenie per osoba” oraz podstawowe kafelki z metrykami.
W ramach wybranego zakresu dat raport pokazuje:
- Liczba dyżurów per osoba (oraz suma globalna),
- Liczba godzin dyżuru per osoba (z uwzględnieniem zastępstw),
- Liczba zastępstw:
- oddanych (
from_employee_id), - przejętych (
to_employee_id), - łącznie,
- oddanych (
- Obciążenie per osoba:
- udział procentowy godzin danej osoby w łącznej puli godzin dyżurowych zakresu,
- wraz z godzinami absolutnymi.
MVP wykorzystuje już istniejące tabele i reguły:
shifts— baza liczby dyżurów i godzin dyżurowych (slotynight/day/evening),substitutions— korekta czasu: odjęcie od osoby oddającej i dodanie osobie przejmującej,employees— nazwy i identyfikatory do grupowania,- (opcjonalnie w rozszerzeniu)
overtime_entries— do wskaźników netto, poza ścisłym MVP.
Zależności logiczne:
- mapowanie slotów na czas (jak w obecnym podsumowaniu:
slotDuration), - obliczenie czasu zastępstwa przez różnicę
start_time/end_time(jak w obecnymtimeDiffHours), - grupowanie po
employee_idi prezentacjaemployee_name.
Backend (MVP, rekomendowane):
- filtrowanie po zakresie dat,
- agregacje grupujące po pracowniku (liczniki i sumy godzin),
- korekta godzin o zastępstwa,
- zwrot gotowych metryk raportowych w jednym endpoincie raportowym.
Frontend (MVP, rekomendowane):
- wybór i walidacja zakresu filtrów daty,
- prezentacja tabeli/kafelków raportu,
- sortowanie i formatowanie (
hh:mm,%), - lekkie obliczenia prezentacyjne (np. procent udziału z wartości już zwróconych przez backend).
Taki podział ogranicza duplikowanie logiki biznesowej w UI, ułatwia testowanie agregacji i przygotowuje bazę pod kolejne etapy ([REPORTS] backend i [REPORTS] frontend).
Cel tego etapu to zaplanowanie prostego, bezpiecznego MVP synchronizacji po odzyskaniu połączenia, bez wdrażania pełnej, rozbudowanej replikacji.
Minimalny wariant:
- przechowywać kolejkę zmian po stronie przeglądarki w
localStorage, - jeden klucz techniczny, np.
dyzury2.pendingMutations.v1, - każda pozycja kolejki powinna mieć:
mutation_id(local UUID),resource(shifts/substitutions/ kolejne zasoby),method(POST/DELETE),payload,created_at(timestamp),sync_state(queued/syncing/failed/conflict/done),- opcjonalnie
base_fingerprint(np. znacznik wersji rekordu:updated_atalbo hash kluczowych pól, używany do wykrycia konfliktu).
Uzasadnienie: localStorage jest już dostępne bez dodatkowej infrastruktury, przetrwa odświeżenie strony i jest wystarczające dla podstawowego scenariusza single-user.
Minimalny model statusów:
- globalny stan aplikacji:
online-clean,online-pending,offline-pending, - status per zmiana w kolejce:
queued,syncing,done,failed,conflict.
Prezentacja w UI (bez chaosu):
- utrzymać istniejący badge połączenia jako główne źródło informacji,
- dodać krótki licznik odłożonych zmian (np.
Offline: 3 zmiany oczekujące na synchronizację), - nie dodawać dużego nowego panelu; ewentualną listę szczegółów pokazywać dopiero po kliknięciu „Szczegóły”.
- Zapis online nieudany z powodu sieci/API -> zmiana trafia do kolejki
queued. - UI pokazuje, że istnieją zmiany oczekujące.
- Po odzyskaniu połączenia aplikacja uruchamia pętlę synchronizacji FIFO.
- Każdy element:
queued->syncing,- sukces API ->
done(i usunięcie z kolejki), - błąd przejściowy ->
failed(z ponawianiem: maks. 3 próby, odstępy 5s -> 15s -> 30s), - błąd konfliktu (np.
409) ->conflict(wymaga decyzji użytkownika).
- Po opróżnieniu kolejki wraca stan
online-clean.
Najważniejsze ryzyka:
- równoległa edycja tego samego dnia/slotu przez innego użytkownika,
- usunięcie lub zmiana rekordu na serwerze zanim odłożona operacja się zsynchronizuje,
- duplikacja żądań po odświeżeniu i ponownej próbie.
Minimalne zabezpieczenia:
- opierać wykrywanie konfliktu na aktualnych odpowiedziach API (
409+ jawny komunikat), - dodać idempotencję po stronie klienta przez
mutation_idi oznaczanie elementu jakodonetylko po potwierdzonym sukcesie, - nie ukrywać konfliktu: oznaczać
conflicti zostawiać użytkownikowi prostą decyzję (ponów / odrzuć / odśwież dane).
- Task A (MVP kolejki): model danych kolejki + zapis/odczyt
localStorage+ licznik oczekujących zmian w badge. - Task B (silnik sync): automatyczny flush po powrocie online + retry błędów przejściowych.
- Task C (konflikty): obsługa
409, statusconflict, prosty UI decyzji użytkownika. - Task D (utwardzenie): telemetry/diagnostyka i scenariusze regresyjne offline/online.
- Karty / kontenery
section.panel(główne karty sekcji),.day(karta dnia w kalendarzu),.assign(karta przypisanej zmiany),.substitution-row(karta zastępstwa),.admin-card(karta pracownika w administracji).
- Statusy i znaczniki
.badge(np.#modeBadge),.pill(status pomocniczy, np. „Nieaktywny”),.substitution-chip(chip zastępstwa wewnątrz karty zmiany),.slot-pill(dostępne sloty do szybkiego przypisania).
- Alerty / komunikaty
- natywne
alert()iconfirm()w wielu akcjach CRUD, - tekstowe stany „muted” (
.muted) dla pustych list i podpowiedzi.
- natywne
- Formularze
- formularze: nadgodziny (
#overtimeForm) i zastępstwa (#subForm), - wejścia administracyjne (dodawanie/edycja pracownika),
- wiele layoutów formularzy budowanych inline przez
style.
- formularze: nadgodziny (
- Sekcje widoków dziennych
- nagłówek dnia
.day-header, - lista przypisań (
.assign), - sekcja „Dostępne” z
.slot-pill, - akcja całodniowa
.full-day-btn.
- nagłówek dnia
- Niespójne „karty” dla podobnych danych
- karta zmiany (
.assign) używa obramowania dashed i kompaktowego układu, - karta zastępstwa (
.substitution-row) ma pełne obramowanie i inny rytm spacingu, - obie reprezentują wpis dzienny, ale mają inny język wizualny.
- karta zmiany (
- Różnice między kartami zmian i zastępstw (najważniejsze)
- zmiana: akcja usuwania ukryta i pokazywana na hover (
.assign .delete-shift), - zastępstwo: akcje zawsze widoczne (
Edytuj,Usuń), - zmiana: treść oparta o slot i badge pracownika; zastępstwo: data+godziny+strzałka osoba→osoba,
- brak wspólnego komponentu bazowego „karta wpisu dnia”.
- zmiana: akcja usuwania ukryta i pokazywana na hover (
- Nadmiar wariantów małych znaczników
.badge,.pill,.slot-pill,.substitution-chip,.substitution-hoursrealizują podobną rolę, ale mają różne promienie, paddingi, kontrast i semantykę.
- Niespójne komunikaty błędów
- część akcji używa
alert('Nie udało się...'), część cichegoconsole.error, część ma bardziej rozbudowane komunikaty; brak jednego komponentu alert/toast.
- część akcji używa
- Rozproszona stylizacja formularzy
- wiele etykiet i kontenerów formularzy ma duplikowane style inline (
display:flex; flex-direction:column; gap:4px; font-weight:700;).
- wiele etykiet i kontenerów formularzy ma duplikowane style inline (
- Mieszanie styli globalnych i inline
- duża część odstępów i layoutów sekcji jest ustawiana inline, co utrudnia spójne zmiany i utrzymanie.
- Priorytet P1: wspólny komponent bazowy karty wpisu dnia
- zdefiniować wspólną klasę bazową (np.
entry-card) dla.assigni.substitution-row, - wyrównać obramowanie, radius, spacing i zachowanie akcji.
- zdefiniować wspólną klasę bazową (np.
- Priorytet P1: unifikacja statusów/chipów/badge
- wprowadzić wspólną bazę (np.
ui-chip) oraz warianty semantyczne (info,success,warning,danger), - stopniowo podpiąć
.badge,.pill,.substitution-chip,.substitution-hours.
- wprowadzić wspólną bazę (np.
- Priorytet P2: jeden system komunikatów użytkownika
- zastąpić rozproszone
alert()/confirm()lekkim komponentem alert/toast + potwierdzenia modalnego.
- zastąpić rozproszone
- Priorytet P2: porządek w formularzach
- wydzielić powtarzające się style etykiet/pól do klas (
form-row,form-field,form-actions), - ograniczyć style inline do przypadków wyjątkowych.
- wydzielić powtarzające się style etykiet/pól do klas (
- Priorytet P3: redukcja inline styles
- przenieść najczęściej powtarzane ustawienia spacing/layout do klas utility, bez zmiany logiki JS.
Takie uporządkowanie daje najpierw największy efekt wizualny (karty i znaczniki), a dopiero potem porządkuje mechanikę komunikatów i formularzy.
Poniższa checklista zamyka wdrożenie obejmujące: walidację backendową, API i podsumowania, redesign panelu dnia, historię zmian, raporty, eksport/print oraz offline.
Minimalny zestaw do uruchomienia po wdrożeniu:
- Lint backendu (PHP):
php -l api.phpphp -l config.sample.php
- Smoke API - odczyt listy pracowników:
curl -s "https://<HOST>/api.php?resource=employees"- oczekiwane: poprawny JSON (tablica obiektów lub pusta tablica), brak HTML/błędów PHP.
- Smoke API - odczyt dyżurów dla miesiąca:
curl -s "https://<HOST>/api.php?resource=shifts&month=YYYY-MM"- oczekiwane: poprawny JSON, brak błędu 500.
- Smoke API - podsumowanie miesięczne:
curl -s "https://<HOST>/api.php?resource=summary&month=YYYY-MM"- oczekiwane: poprawny JSON z polami agregacji (
shift_hours,overtime_hours,net_oncall,calls).
- Smoke walidacji backendu (negatywny przypadek):
- wysłać
POST /api.php?resource=shiftsz niepoprawnymslotlubshift_date - oczekiwane: kod 4xx i JSON z polem
error.
- wysłać
- Panel dnia (redesign):
- przełączanie miesięcy działa,
- karta dnia pokazuje zmiany, dostępne sloty i status dnia,
- przypisanie pojedynczego slotu i pełnego dnia działa,
- usunięcie pojedynczej zmiany i wyczyszczenie dnia działa.
- Walidacja biznesowa backend/frontend:
- brak możliwości zapisu duplikatu tego samego slotu dnia,
- błędne dane (np. pusty pracownik, zły format daty) zwracają czytelny błąd i nie psują UI.
- API i podsumowania:
- po dodaniu/edycji/usunięciu wpisów wartości podsumowań aktualizują się poprawnie,
- bilans netto (
dyżur - nadgodziny) jest spójny z danymi wejściowymi.
- Historia zmian:
- widoczna jest lista zmian dla dyżurów/zastępstw,
- po wykonaniu zapisu/usunięcia pojawia się nowy wpis historii,
- dane wpisu historii są czytelne (kto/co/kiedy, jeśli dostępne).
- Raporty:
- ekran raportów otwiera się i jest odseparowany od panelu dnia,
- filtrowanie (jeśli dostępne) działa i odświeża wynik,
- metryki raportowe są czytelne i zgodne z danymi bazowymi.
- Eksport / print:
- eksport danych do pliku działa i plik da się otworzyć,
- podgląd wydruku działa i nie zawiera zbędnych elementów UI,
- wydruk obejmuje właściwy zakres (panel harmonogramu/raportu).
- Offline / fallback:
- przy braku API widoczny jest status trybu offline/demo,
- aplikacja pozostaje używalna dla odczytu danych demonstracyjnych,
- po powrocie API aplikacja wraca do trybu online bez odświeżania krytycznych danych.
- Krytyczna ścieżka planowania dyżuru: dodanie zmiany -> widoczność w kalendarzu -> poprawne podsumowanie.
- Krytyczna ścieżka zastępstwa: dodanie zastępstwa -> korekta godzin oddającego/przejmującego -> poprawne agregacje.
- Krytyczna ścieżka raportowa: wybranie zakresu -> poprawne metryki -> zgodność z podsumowaniami.
- Krytyczna ścieżka audytowa: wykonanie operacji zapisu -> wpis pojawia się w historii zmian.
- Krytyczna ścieżka eksportu/print: wygenerowanie eksportu i wydruku bez utraty kluczowych danych.
- Krytyczna ścieżka odporności: chwilowy brak API nie blokuje pracy odczytowej i sygnalizuje tryb offline.
