diff --git a/FaKrosnoManagement/generate_docs.py b/FaKrosnoManagement/generate_docs.py
new file mode 100644
index 0000000..4958b80
--- /dev/null
+++ b/FaKrosnoManagement/generate_docs.py
@@ -0,0 +1,841 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Skrypt generujący dokumentację PDF projektu FaKrosno Management.
+Uruchomienie: python3 generate_docs.py
+"""
+
+from fpdf import FPDF
+import os
+
+FONT_PATH = "/Library/Fonts/Arial Unicode.ttf"
+OUTPUT_FILE = os.path.join(os.path.dirname(__file__), "dokumentacja_fakrosno.pdf")
+
+# ──────────────────────────────────────────────────────────────────────────────
+# Pomocnicze struktury danych – opis każdego pliku z komentarzami
+# ──────────────────────────────────────────────────────────────────────────────
+
+DOCS = [
+
+# ═══════════════════════════════════════════════════════════════════
+("PUNKT WEJŚCIA I ROUTING APLIKACJI", None, None),
+# ═══════════════════════════════════════════════════════════════════
+
+("main.tsx", "Główny punkt wejścia aplikacji (odpowiednik index.html + bootstrap w Blazorze)", [
+ ("1", "import { StrictMode } from 'react'",
+ "Importuje tryb ścisły React'a – włącza dodatkowe ostrzeżenia podczas programowania."),
+ ("2", "import { createRoot } from 'react-dom/client'",
+ "Importuje funkcję tworzącą korzeń aplikacji React w drzewie DOM (React 18+)."),
+ ("3", "import { BrowserRouter } from 'react-router-dom'",
+ "Importuje router URL-owy: śledzi adres przeglądarki i aktualizuje widok bez przeładowania strony."),
+ ("4", "import { QueryClient, QueryClientProvider } from '@tanstack/react-query'",
+ "QueryClient – zarządza pamięcią podręczną zapytań HTTP. QueryClientProvider – udostępnia go całej aplikacji."),
+ ("5", "import { ReactQueryDevtools } from '@tanstack/react-query-devtools'",
+ "Narzędzie deweloperskie do podglądu stanu zapytań (widoczne tylko w trybie development)."),
+ ("6", "import App from './App'",
+ "Główny komponent aplikacji zawierający definicję tras (routingu)."),
+ ("7", "import { initSyncfusion } from '@/lib/syncfusion'",
+ "Importuje funkcję inicjalizującą bibliotekę tabel/siatek Syncfusion z polską lokalizacją."),
+ ("8", "import { Toaster } from '@/components/ui/Toaster'",
+ "Komponent wyświetlający powiadomienia (np. 'Zapisano', 'Błąd') w rogu ekranu."),
+ ("9", "import { ErrorBoundary } from '@/components/ErrorBoundary'",
+ "Komponent-otoczka chwytający nieobsłużone błędy React i pokazujący przyjazny komunikat zamiast białego ekranu."),
+ ("10", "import './index.css'",
+ "Globalne style CSS (Tailwind) – definiuje bazowe reguły wyglądu całej strony."),
+ ("12", "initSyncfusion()",
+ "Wywołuje inicjalizację Syncfusion – rejestruje klucz licencyjny i ustawia język polski."),
+ ("14-22", "const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 30_000, retry: 1, refetchOnWindowFocus: false } } })",
+ "Tworzy klienta zarządzającego pamięcią podręczną danych. Ustawienia: dane są 'świeże' przez 30 sekund, przy błędzie ponawia raz, NIE odświeża po powrocie do zakładki przeglądarki."),
+ ("24-25", "const rootElement = document.getElementById('root')",
+ "Szuka w HTML elementu
– to kontener, w którym wyrenderuje się cała aplikacja React. Jeśli nie istnieje, rzuca błędem."),
+ ("27-39", "createRoot(rootElement).render(...)",
+ "Renderuje całą aplikację: StrictMode (ostrzeżenia dev) → ErrorBoundary (siatka bezpieczeństwa) → QueryClientProvider (cache HTTP) → BrowserRouter (routing URL) → App (strony) + Toaster (powiadomienia) + ReactQueryDevtools (narzędzie dev)."),
+]),
+
+("App.tsx", "Definicja wszystkich tras (stron) aplikacji – odpowiednik routingu w Blazorze", [
+ ("1-6", "Importy bazowe",
+ "lazy – wczytuje strony dopiero gdy są potrzebne (oszczędza czas ładowania). Suspense – pokazuje 'kółko ładowania' gdy strona się wczytuje. Routes/Route – definiują mapę adresów URL → strony."),
+ ("9-45", "const LoginPage = lazy(...), const RegisterPage = lazy(...) ...",
+ "Definiuje 15 stron aplikacji jako 'leniwie ładowane'. Oznacza to, że kod strony (np. SchedulerPage.tsx) jest pobierany z serwera dopiero gdy użytkownik po raz pierwszy wejdzie na daną stronę – nie przy starcie aplikacji."),
+ ("47-101", "export default function App()",
+ "Główna funkcja aplikacji. Renderuje NavigatorBridge (obsługa przekierowań z kodu JavaScript) oraz drzewo tras:"),
+ ("56-58", "Route path='/login', '/register', '/unauthorized'",
+ "Trasy publiczne – dostępne bez logowania. Każda ładuje odpowiednią stronę."),
+ ("61-95", "Route element={
}",
+ "Trasy chronione – ProtectedRoute sprawdza czy użytkownik jest zalogowany. Jeśli nie – przekierowuje na /login. W środku AppShell (szablon z bocznym menu i nagłówkiem)."),
+ ("63-76", "Route index, Route path='ScheduleOrder/:id', 'Products', 'Warehouse/...'",
+ "Strony dostępne dla zalogowanych pracowników: harmonogramy, produkty, magazyn i listy pakowe."),
+ ("79-93", "Route element={
}",
+ "Trasy tylko dla administratorów: zamówienia klienta, EDI, tłumaczenia, zarządzanie użytkownikami, harmonogram zadań."),
+ ("97", "Route path='*'",
+ "Trasa 'złap wszystko' – jeśli adres URL nie pasuje do żadnej trasy, wyświetla stronę 404."),
+]),
+
+# ═══════════════════════════════════════════════════════════════════
+("WARSTWA API – KOMUNIKACJA Z SERWEREM", None, None),
+# ═══════════════════════════════════════════════════════════════════
+
+("src/api/client.ts", "Bazowy klient HTTP – fundament całej komunikacji z serwerem .NET", [
+ ("1-4", "Importy",
+ "axios – biblioteka do zapytań HTTP (jak fetch, ale z dodatkowymi funkcjami). Importuje stałe, sklep autoryzacji i system powiadomień."),
+ ("7", "export type FieldErrors = Record
",
+ "Typ opisujący błędy walidacji pól formularza zwracane przez serwer (słownik: nazwa_pola → lista_błędów)."),
+ ("9-19", "export class ApiError extends Error",
+ "Niestandardowa klasa błędu API. Przechowuje: message (treść błędu), status (kod HTTP np. 404), fieldErrors (błędy pól formularza z serwera). Używana w całej aplikacji do obsługi błędów."),
+ ("25-32", "let navigator, registerNavigator, navigate",
+ "Mechanizm przekierowań: gdy Axios złapie błąd 401/403, musi przekierować użytkownika na /login lub /unauthorized. Nie może bezpośrednio używać React Router (jest poza komponentem), więc przechowuje referencję do funkcji navigate, którą rejestruje komponent NavigatorBridge."),
+ ("34-37", "export const apiClient = axios.create(...)",
+ "Tworzy instancję Axios z domyślnym adresem serwera API (np. http://localhost:5001) i nagłówkiem Accept: application/json."),
+ ("40-46", "apiClient.interceptors.request.use(...)",
+ "Interceptor żądań (uruchamiany przed KAŻDYM zapytaniem HTTP): pobiera token JWT ze sklepu i dołącza go do nagłówka Authorization: Bearer TOKEN. Dzięki temu każde zapytanie jest automatycznie autoryzowane."),
+ ("52-60", "export function reqConfig(...)",
+ "Buduje obiekt konfiguracyjny dla Axios: opcjonalnie dodaje signal (do anulowania zapytania) i params (parametry URL np. ?id=123). Zwraca tylko zdefiniowane właściwości."),
+ ("63-66", "export async function apiGet(...)",
+ "Uproszczona funkcja GET: wysyła zapytanie i zwraca od razu dane (data) zamiast całego obiektu odpowiedzi Axios."),
+ ("75-79", "function extractMessage(...)",
+ "Wyciąga czytelny komunikat błędu z odpowiedzi serwera: obsługuje zarówno JSON (ProblemDetails z .NET) jak i zwykły tekst."),
+ ("82-120", "apiClient.interceptors.response.use(...)",
+ "Interceptor odpowiedzi – centralna obsługa błędów HTTP:\n• 401 Unauthorized: wylogowuje użytkownika i przekierowuje na /login ('Sesja wygasła')\n• 403 Forbidden: przekierowuje na /unauthorized ('Brak uprawnień')\n• 422/400: błędy walidacji formularza\n• 5xx: błąd serwera – pokazuje toast 'Błąd serwera'\n• status 0: brak połączenia z siecią"),
+]),
+
+("src/api/users.ts", "Funkcje API dla użytkowników i autoryzacji", [
+ ("4-10", "export async function login(...)",
+ "Wysyła login i hasło do serwera POST /api/Users/login. Zwraca token JWT i datę wygaśnięcia."),
+ ("12-18", "export interface RegisterPayload",
+ "Interfejs opisujący dane potrzebne do rejestracji: login, email, imię, nazwisko, hasło."),
+ ("21-23", "export async function register(...)",
+ "Wysyła dane rejestracji do POST /api/Users/register. Hasło jest wysyłane jako tekst (szyfrowane po stronie serwera przez BCrypt)."),
+ ("26-28", "export async function changePassword(...)",
+ "Zmienia hasło zalogowanego użytkownika. POST /api/Users/change-password. Serwer identyfikuje użytkownika z tokenu JWT."),
+ ("30-32", "export async function getUsers(...)",
+ "Pobiera listę wszystkich użytkowników. GET /api/Users."),
+ ("34-36", "export async function getUserByUsername(...)",
+ "Pobiera konkretnego użytkownika po nazwie loginu. GET /api/Users/by-username/?username=..."),
+ ("38-40", "export async function addUser(...)",
+ "Dodaje nowego użytkownika. POST /api/Users."),
+ ("42-52", "export interface TemporaryPasswordResponse, AdminCreateUserPayload",
+ "Typy dla operacji administratora: odpowiedź z tymczasowym hasłem oraz dane nowego użytkownika."),
+ ("55-63", "export async function adminCreateUser(...)",
+ "Administrator tworzy nowego użytkownika. Serwer generuje tymczasowe hasło i zwraca je jednorazowo. POST /api/Users/admin-create."),
+ ("66-72", "export async function adminResetPassword(...)",
+ "Administrator resetuje hasło użytkownika. Serwer generuje nowe tymczasowe hasło i zwraca je jednorazowo. POST /api/Users/admin-reset-password."),
+ ("74-76", "export async function updateUser(...)",
+ "Aktualizuje dane użytkownika. PUT /api/Users."),
+ ("78-80", "export async function deleteUser(...)",
+ "Usuwa użytkownika po jego identyfikatorze (rowPointer). DELETE /api/Users/?id=..."),
+]),
+
+("src/api/customerOrders.ts", "Funkcje API dla zamówień klientów (Syteline)", [
+ ("4-6", "getCustomerOrders(signal?)",
+ "Pobiera listę wszystkich zamówień klientów z Syteline. GET /api/CustomerOrders. Signal to token anulowania – gdy użytkownik opuści stronę, zapytanie jest przerywane."),
+ ("8-16", "getCustomerOrderByNumber(customerOrderNumber, signal?)",
+ "Pobiera szczegóły jednego zamówienia po jego numerze (używane na stronie szczegółów zamówienia). GET /api/CustomerOrders/by-order-number/?customerOrderNumber=..."),
+ ("19-27", "getCustomerOrderByCoNumber(customerOrderNumber, signal?)",
+ "Pobiera zamówienie po numerze CO z Syteline (używane przy tworzeniu listy pakowej). GET /api/CustomerOrders/by-co-number/?customerOrderNumber=..."),
+]),
+
+("src/api/ediCustomerOrders.ts", "Funkcje API dla zamówień EDI", [
+ ("5-7", "getEdiCustomerOrders(signal?)",
+ "Pobiera wszystkie zamówienia EDI. GET /api/EdiCustomerOrders."),
+ ("9-17", "getEdiCustomerOrderByNumber(customerOrderNumber, signal?)",
+ "Pobiera jedno zamówienie EDI po numerze. GET /api/EdiCustomerOrders/by-order-number/?customerOrderNumber=..."),
+ ("25-44", "sendEdiOrderToSyteline(rowPointer, displayNumber)",
+ "Wysyła zamówienie EDI do systemu Syteline. POST /api/EdiCustomerOrders/send-to-syteline. Jeśli operacja się powiodła – zwraca status=1 z numerem zamówienia. Jeśli błąd – próbuje pobrać logi błędów z serwera i zwraca status=0 z wiadomością błędu."),
+]),
+
+("src/api/ediTranslations.ts", "Funkcje API dla powiązań zamówień EDI z Syteline", [
+ ("4-11", "getEdiTranslations(signal?)",
+ "Pobiera listę wszystkich powiązań (mapowań) zamówień EDI z zamówieniami Syteline. GET /api/EdiCustomerOrdersTranslations."),
+ ("13-15", "deleteEdiTranslation(id)",
+ "Usuwa konkretne powiązanie po ID. DELETE /api/EdiCustomerOrdersTranslations/?id=..."),
+]),
+
+("src/api/errorLog.ts", "Funkcje API dla logów błędów", [
+ ("9-17", "getErrorLogs(customerOrderNumber, signal?)",
+ "Pobiera logi błędów dla danego zamówienia. UWAGA: zgodnie z komentarzem w kodzie, ta funkcja uderza w ten sam endpoint co zamówienia klientów – to znany błąd z oryginalnej aplikacji Blazor, zachowany dla zachowania zgodności."),
+]),
+
+("src/api/functions.ts", "Funkcje API dla uprawnień/funkcji systemowych", [
+ ("4-6", "getFunctions(signal?)", "Pobiera listę wszystkich funkcji systemowych. GET /api/Functions."),
+ ("8-10", "addFunction(fn)", "Dodaje nową funkcję. POST /api/Functions."),
+ ("12-14", "updateFunction(fn)", "Aktualizuje istniejącą funkcję. PUT /api/Functions."),
+ ("16-18", "deleteFunction(id)", "Usuwa funkcję po ID. DELETE /api/Functions/?id=..."),
+]),
+
+("src/api/products.ts", "Funkcje API dla produktów", [
+ ("4-9", "getProductsByIndex(indexName, signal?)",
+ "Wyszukuje produkty po indeksie/nazwie. GET /api/Product/by-index?indexName=... Używane na stronie Produkty."),
+ ("11-13", "updateProduct(product)",
+ "Aktualizuje dane produktu (np. kod FA). PUT /api/Product."),
+]),
+
+("src/api/roles.ts", "Funkcje API dla ról użytkowników", [
+ ("4-6", "getRoles(signal?)", "Pobiera listę wszystkich ról. GET /api/Roles."),
+ ("8-10", "addRole(role)", "Dodaje nową rolę. POST /api/Roles."),
+ ("12-14", "updateRole(role)", "Aktualizuje rolę. PUT /api/Roles."),
+ ("16-18", "deleteRole(id)", "Usuwa rolę. DELETE /api/Roles/?id=..."),
+]),
+
+("src/api/scheduleOrders.ts", "Funkcje API dla harmonogramów zamówień (DELFOR)", [
+ ("4-6", "getScheduleOrders(signal?)",
+ "Pobiera listę wszystkich harmonogramów DELFOR (zamówień cyklicznych). GET /api/ScheduleOrders."),
+ ("8-13", "getScheduleOrder(scheduleOrderId, signal?)",
+ "Pobiera jeden harmonogram ze szczegółami (pozycje i daty). GET /api/ScheduleOrders/{id}."),
+]),
+
+("src/api/scheduler.ts", "Funkcje API dla harmonogramu zadań automatycznych (Hangfire)", [
+ ("4-6", "getTaskSchedulers(signal?)", "Pobiera listę zaplanowanych zadań. GET /api/HangfireJobs/."),
+ ("8-10", "addTaskScheduler(task)", "Dodaje nowe zadanie cykliczne. POST /api/HangfireJobs/add."),
+ ("12-14", "updateTaskScheduler(task)", "Aktualizuje zadanie. POST /api/HangfireJobs/update."),
+ ("16-18", "deleteTaskScheduler(task)", "Usuwa zadanie. POST /api/HangfireJobs/delete."),
+]),
+
+("src/api/warehouse.ts", "Funkcje API dla modułu magazynowego (WZ i listy pakowe)", [
+ ("13-15", "getWzHeaderById(id, signal?)", "Pobiera nagłówek dokumentu WZ po ID (GUID). GET /api/WzHeader/by-id?id=..."),
+ ("17-19", "getAllClients(signal?)", "Pobiera listę klientów magazynowych. GET /api/WzClient."),
+ ("21-30", "getAllClientWzs(customerNumber, customerSequence, signal?)",
+ "Pobiera transakcje materiałowe (dokumenty WZ) dla konkretnego klienta. GET /api/WzHeader/by-customer-number."),
+ ("32-41", "getAllClientWzHeaders(customerNumber, customerSequence, signal?)",
+ "Pobiera nagłówki list pakowych dla klienta. GET /api/WzHeader/all-wz-headers."),
+ ("43-45", "createWzHeader(header)", "Tworzy nowy nagłówek dokumentu WZ. POST /api/WzHeader."),
+ ("47-49", "addEmailsToWzHeader(id, emailAddresses)",
+ "Zapisuje adresy e-mail przy nagłówku WZ (do wysyłki). POST /api/WzHeader/add-emails?id=..."),
+ ("51-59", "getCustomerOrder(customerOrderNumber, signal?)",
+ "Pobiera zamówienie klienta po numerze CO (używane przy tworzeniu wierszy WZ). GET /api/CustomerOrders/by-co-number."),
+ ("61-67", "getItem(itemNumber, customerNumber, signal?)",
+ "Pobiera powiązanie pozycja–klient (numer klienta, EAN13, kod FA). GET /api/ItemCust."),
+ ("69-87", "getWzRowsMeyleByHeaderId / getWzRowsMarelliByHeaderId",
+ "Pobierają wiersze listy pakowej dla Meyle lub Marelli po ID nagłówka."),
+ ("89-95", "createWzRowsMeyle / createWzRowsMarelli",
+ "Tworzą nowe wiersze listy pakowej (Meyle: POST /api/WzRowMeyle, Marelli: POST /api/WzRowMarelli)."),
+ ("97-103", "updateWzRowsMeyle / updateWzRowsMarelli",
+ "Aktualizują istniejące wiersze listy pakowej."),
+ ("106-124", "getTransactionModels(signal?)",
+ "Pobiera transakcje materiałowe z numerem karty kontrolnej. Grupuje wyniki w słownik: klucz = numer_karty_kontrolnej → lista transakcji. Używane w skanerowaniu kart kontrolnych przy tworzeniu listy pakowej."),
+ ("126-134", "generateXlsForMeyle / generateXlsForMarelli",
+ "Wywołuje generowanie pliku Excel dla listy pakowej Meyle lub Marelli na serwerze. GET /api/ExcelGenerator/generate-meyle lub generate-marelli."),
+]),
+
+# ═══════════════════════════════════════════════════════════════════
+("MAGAZYN STANU (STORE) – GLOBALNE DANE APLIKACJI", None, None),
+# ═══════════════════════════════════════════════════════════════════
+
+("src/store/authStore.ts", "Magazyn autoryzacji – zarządza sesją zalogowanego użytkownika", [
+ ("1-3", "Importy",
+ "zustand – biblioteka do zarządzania globalnym stanem (lżejsza alternatywa dla Redux). persist – middleware zapisujące stan w localStorage (dane przetrwają odświeżenie strony). jwtDecode – dekoduje token JWT bez weryfikacji podpisu."),
+ ("6-12", "interface JwtClaims",
+ "Opisuje pola dostępne w tokenie JWT: sub (ID użytkownika), unique_name (nazwa), exp (czas wygaśnięcia w Unix timestamp). Pole [claim: string]: unknown pozwala na dodatkowe niestandardowe pola."),
+ ("14-19", "export interface AuthUser",
+ "Dane zalogowanego użytkownika dostępne w aplikacji: name (wyświetlana nazwa) i claims (surowe dane z tokenu JWT)."),
+ ("21-28", "interface AuthState",
+ "Struktura stanu autoryzacji: token (JWT), user (dane użytkownika), isAuthenticated (bool), setToken (funkcja zapisu), logout (funkcja wylogowania)."),
+ ("30-43", "function decode(token)",
+ "Prywatna funkcja dekodująca token JWT: sprawdza czy token nie wygasł (exp * 1000 < teraz?). Wyciąga nazwę użytkownika z kilku możliwych pól JWT. Zwraca null jeśli token nieważny lub wygasły."),
+ ("51-82", "export const useAuthStore = create(...)(...)",
+ "Tworzy globalny magazyn stanu autoryzacji z persist (zapisywanie w localStorage pod kluczem 'authToken'). Zawiera: setToken – zapisuje token, dekoduje go, jeśli nieważny czyści stan; logout – czyści token i dane użytkownika; onRehydrateStorage – przy starcie aplikacji weryfikuje zapisany token (czy nie wygasł)."),
+ ("85-87", "export function getAuthToken()",
+ "Odczytuje token JWT poza komponentem React (używane przez interceptor Axios). Wzorzec: useAuthStore.getState().token."),
+]),
+
+("src/store/toastStore.ts", "Magazyn powiadomień (toastów) – system komunikatów dla użytkownika", [
+ ("3", "export type ToastVariant",
+ "Typ wariantu powiadomienia: success (zielony), error (czerwony), info (niebieski), warning (żółty)."),
+ ("5-10", "export interface Toast",
+ "Struktura powiadomienia: id (unikalny identyfikator), variant (typ), title (tytuł), description (opcjonalny opis)."),
+ ("18-30", "export const useToastStore = create(...)",
+ "Magazyn powiadomień: toasts – lista aktywnych powiadomień; push – dodaje nowe powiadomienie, generuje UUID, po 5 sekundach automatycznie je usuwa; dismiss – usuwa powiadomienie po kliknięciu X."),
+ ("32-42", "export const toast = { success, error, info, warning }",
+ "Imperatywne API do wyświetlania powiadomień poza komponentami React (np. w interceptorze Axios). Przykład: toast.error('Błąd serwera', 'Spróbuj ponownie później')."),
+]),
+
+("src/store/uiStore.ts", "Magazyn interfejsu użytkownika – stan wizualny aplikacji", [
+ ("5-14", "interface UiState",
+ "Stan UI: sidebarOpen (czy boczne menu jest otwarte na mobile), setSidebarOpen/toggleSidebar (kontrola menu), selectedWarehouseClientId (ostatnio wybrany klient w module magazynowym – zapamiętywany w localStorage)."),
+ ("16-30", "export const useUiStore = create(...)(...)",
+ "Magazyn z persist: selectedWarehouseClientId jest zapisywany w localStorage (użytkownik nie musi wybierać klienta przy każdej wizycie). Stan menu mobilnego NIE jest zapisywany (zawsze zamknięte po odświeżeniu)."),
+]),
+
+# ═══════════════════════════════════════════════════════════════════
+("BIBLIOTEKA POMOCNICZA (LIB)", None, None),
+# ═══════════════════════════════════════════════════════════════════
+
+("src/lib/cn.ts", "Łączenie klas CSS", [
+ ("2-4", "export function cn(...values)",
+ "Funkcja łącząca klasy CSS Tailwind. Przyjmuje dowolną liczbę argumentów (string lub false/null/undefined), filtruje wartości fałszywe i łączy pozostałe spacjami. Przykład: cn('px-4', isActive && 'bg-blue-600', undefined) → 'px-4 bg-blue-600'."),
+]),
+
+("src/lib/combinations.ts", "Algorytm znajdowania kombinacji sum (backtracking)", [
+ ("6-33", "export function findCombinations(records, targetSum, getQuantity)",
+ "Implementuje algorytm backtrackingu do znajdowania podzbiorów rekordów, których sumy ilości są równe zadanej wartości docelowej. Używany w skanerowaniu palet przy tworzeniu list pakowych. Port z oryginalnej funkcji Blazor FindCombinations. Parametry: records – lista elementów, targetSum – szukana suma, getQuantity – funkcja pobierająca ilość z elementu. Zwraca tablicę tablic – każda to zestaw elementów sumujący się do targetSum."),
+]),
+
+("src/lib/constants.ts", "Stałe konfiguracyjne aplikacji", [
+ ("2-3", "API_BASE_URL",
+ "Adres bazowy API .NET. Pobiera wartość ze zmiennej środowiskowej VITE_API_BASE_URL (z pliku .env) lub używa domyślnego http://localhost:5001."),
+ ("6", "AUTH_TOKEN_STORAGE_KEY = 'authToken'",
+ "Klucz localStorage do przechowywania tokenu JWT. Zachowany z oryginalnej aplikacji Blazor (Blazored.LocalStorage)."),
+ ("9", "UI_STORAGE_KEY = 'fakrosno-ui'",
+ "Klucz localStorage do przechowywania ustawień UI (wybrany klient magazynu)."),
+ ("15-16", "SYNCFUSION_LICENSE_KEY",
+ "Klucz licencyjny do biblioteki Syncfusion (tabele, siatki). Skopiowany z oryginalnej aplikacji Blazor."),
+]),
+
+("src/lib/format.ts", "Funkcje formatowania dat i liczb", [
+ ("8-12", "parseApiDate(value)",
+ "Parsuje datę z formatu ISO 8601 (jak zwraca API .NET) do obiektu Date JavaScript. Zwraca null dla pustych wartości lub nieprawidłowych dat. Zawsze używaj tej funkcji zamiast new Date(str) dla dat z API."),
+ ("15-18", "formatDate(value, pattern?)",
+ "Formatuje datę do wyświetlenia. Domyślny format: dd.MM.yyyy (np. 15.06.2024). Zwraca 'N/A' dla null/undefined."),
+ ("21-24", "formatDateTime(value)",
+ "Formatuje datę z godziną: yyyy-MM-dd HH:mm:ss (np. 2024-06-15 14:30:00). Zwraca 'N/A' dla null."),
+ ("27-29", "toApiDate(value)", "Konwertuje obiekt Date do formatu ISO 8601 do wysłania do API."),
+ ("32-34", "toApiDateOnly(value)", "Konwertuje datę do formatu yyyy-MM-dd (tylko data, bez czasu)."),
+ ("40-43", "formatDecimal(value, fractionDigits?)",
+ "Formatuje liczbę dziesiętną z zadaną precyzją (domyślnie 2 miejsca po przecinku). Używa biblioteki decimal.js, która eliminuje błędy zaokrąglania liczb zmiennoprzecinkowych. Zwraca 'N/A' dla null."),
+]),
+
+("src/lib/queryKeys.ts", "Klucze pamięci podręcznej zapytań HTTP (TanStack Query)", [
+ ("2-21", "export const queryKeys = { ... }",
+ "Scentralizowany słownik kluczy cache dla wszystkich typów zapytań. TanStack Query identyfikuje zapytania po kluczu – jeśli dwa miejsca w aplikacji używają tego samego klucza, dzielą jeden cache. Przykład: queryKeys.scheduleOrders → ['scheduleOrders'], queryKeys.scheduleOrder(5) → ['scheduleOrders', 5]. Pozwala łatwo unieważnić cache po operacjach zapisu (invalidateQueries)."),
+]),
+
+("src/lib/status.ts", "Tłumaczenie kodów statusów zamówień", [
+ ("5-20", "export function translateStatus(status)",
+ "Tłumaczy skrótowe kody statusów Syteline na czytelne opisy po polsku: O→Zamówione, S→Zatrzymane, P→Planowane, C→Zakończone, F→Wypełnione. Funkcja pomocnicza po stronie klienta (serwer zazwyczaj zwraca już przetłumaczony status w polu translatedStatus)."),
+]),
+
+("src/lib/syncfusion.ts", "Inicjalizacja biblioteki tabel Syncfusion", [
+ ("4", "let registered = false",
+ "Flaga zapobiegająca wielokrotnemu wywoływaniu inicjalizacji (singleton)."),
+ ("7-37", "export function initSyncfusion()",
+ "Jednorazowo inicjalizuje Syncfusion: registerLicense – rejestruje klucz licencyjny; setCulture('pl') – ustawia język polski; L10n.load({ pl: {...} }) – ładuje polskie tłumaczenia interfejsu siatek: 'Brak rekordów do wyświetlenia', 'Zapisz', 'Anuluj', etykiety stron itp."),
+]),
+
+# ═══════════════════════════════════════════════════════════════════
+("ROUTER – NAWIGACJA I OCHRONA TRAS", None, None),
+# ═══════════════════════════════════════════════════════════════════
+
+("src/router/navConfig.ts", "Konfiguracja elementów menu bocznego", [
+ ("13-21", "export interface NavItem",
+ "Opisuje element menu: label (widoczna etykieta), to (docelowy URL), icon (ikona z biblioteki Lucide), admin (czy tylko dla administratora), end (czy dopasowanie URL musi być dokładne)."),
+ ("23-32", "export const navItems: NavItem[]",
+ "Lista wszystkich pozycji menu: Harmonogramy (strona główna /), Produkty, Magazyn – dla wszystkich użytkowników. Zamówienia klienta, Zamówienia EDI, Tłumaczenia, Użytkownicy, Harmonogram zadań – tylko dla administratorów (admin: true)."),
+]),
+
+("src/router/NavigatorBridge.tsx", "Mostek łączący React Router z klientem API", [
+ ("6-12", "export function NavigatorBridge()",
+ "Komponent renderujący pusty element (null – nic nie widać na ekranie). Jego jedynym zadaniem jest zarejestrowanie funkcji navigate z React Router w kliencie Axios. Dzięki temu, gdy Axios złapie błąd 401, może przekierować użytkownika na /login bez pełnego przeładowania strony. Montowany raz w App.tsx."),
+]),
+
+("src/router/ProtectedRoute.tsx", "Ochrona tras przed nieautoryzowanym dostępem", [
+ ("5-15", "function getRoles()",
+ "Wyciąga role użytkownika z zdekodowanego tokenu JWT. Obsługuje zarówno pojedynczą rolę (string) jak i tablicę ról (string[]). Szuka w standardowym polu Microsoft JWT lub w polu role/roles."),
+ ("17-24", "function isAdmin()",
+ "Sprawdza czy użytkownik ma rolę 'admin' lub 'administrator'. Jeśli JWT nie zawiera żadnych ról (stary format API), zakłada że użytkownik jest adminem (dla wstecznej zgodności z Blazorem)."),
+ ("27-34", "export function ProtectedRoute()",
+ "Komponent chroniący trasy: sprawdza isAuthenticated ze sklepu. Jeśli nie zalogowany – przekierowuje na /login, zapamiętując obecny adres URL (by po zalogowaniu wrócić). Jeśli zalogowany – renderuje Outlet (zawartość trasy)."),
+ ("37-47", "export function AdminRoute()",
+ "Komponent chroniący trasy administracyjne: wymaga zarówno zalogowania jak i roli admina. Jeśli brak zalogowania – /login. Jeśli brak uprawnień – /unauthorized."),
+]),
+
+# ═══════════════════════════════════════════════════════════════════
+("KOMPONENTY INTERFEJSU UŻYTKOWNIKA", None, None),
+# ═══════════════════════════════════════════════════════════════════
+
+("src/components/ErrorBoundary.tsx", "Komponent chwytający błędy React (Error Boundary)", [
+ ("13-44", "export class ErrorBoundary extends Component",
+ "Klasowy komponent React (jedyna forma obsługi błędów w React). getDerivedStateFromError – przechwytuje błąd i zapisuje go w stanie. componentDidCatch – loguje błąd do konsoli. render – jeśli wystąpił błąd: pokazuje 'Coś poszło nie tak' z przyciskami 'Odśwież stronę' i 'Spróbuj ponownie'. Jeśli nie ma błędu – renderuje children (normalna zawartość). Zamontowany na szczycie drzewa komponentów w main.tsx oraz wewnątrz AppShell dla każdej strony."),
+]),
+
+("src/components/layout/AppShell.tsx", "Główny szablon aplikacji po zalogowaniu", [
+ ("8-24", "export function AppShell()",
+ "Buduje układ strony: flex h-screen (zajmuje pełen ekran). Sidebar (boczne menu) + div z Header (nagłówek) i main (treść strony). Wewnątrz main: ErrorBoundary + Suspense (z animacją ładowania PageSkeleton) + Outlet (tu renderuje się aktualna strona)."),
+]),
+
+("src/components/layout/Header.tsx", "Górny pasek nawigacyjny", [
+ ("7-45", "export function Header()",
+ "Pasek nagłówka: po lewej – przycisk hamburger (tylko na mobile, toggle bocznego menu) + tytuł 'System zarządzania zamówieniami'. Po prawej – ikona użytkownika z jego nazwą + przycisk 'Wyloguj' (wywołuje logout() i przekierowuje na /login)."),
+]),
+
+("src/components/layout/PageHeader.tsx", "Nagłówek strony z tytułem i akcjami", [
+ ("3-21", "export function PageHeader({ title, description, actions })",
+ "Prosty komponent nagłówka strony: tytuł (h1), opcjonalny opis (p), opcjonalne przyciski akcji (np. 'Powrót', 'Nowy'). Responsywny: na mobile wierszowo, na desktop kolumnowo z przyciskami po prawej."),
+]),
+
+("src/components/layout/Sidebar.tsx", "Boczny panel nawigacyjny", [
+ ("6-82", "export function Sidebar()",
+ "Sidebar wyświetla listę pozycji menu z navConfig.ts. Dzieli je na dwie sekcje: 'Główne' i 'Administracja'. Każda pozycja to NavLink – automatycznie dodaje klasę bg-blue-600 gdy URL pasuje (aktywna strona). Na desktop: stały panel 256px. Na mobile: wysuwana szuflada (fixed, full-height) z ciemnym tłem – kliknięcie tła zamyka menu."),
+]),
+
+("src/components/ui/Badge.tsx", "Komponent etykietki/plakietki", [
+ ("14-25", "export function Badge({ tone, children })",
+ "Wyświetla małą zaokrągloną plakietkę z tekstem. Pięć kolorów (tone): gray, blue, green, red, amber. Przykład użycia: 5 – niebieska plakietka z liczbą 5 (używana przy liczbie pozycji harmonogramu)."),
+]),
+
+("src/components/ui/Button.tsx", "Komponent przycisku", [
+ ("8-12", "export interface ButtonProps",
+ "Rozszerzenie standardowych właściwości przycisku HTML o: variant (styl), size (rozmiar), loading (stan ładowania – pokazuje kółko obrotu)."),
+ ("14-20", "variantClasses, sizeClasses",
+ "Słowniki klas CSS dla każdego wariantu i rozmiaru. Warianty: primary (niebieski), secondary (biały z ramką), danger (czerwony), success (zielony), ghost (przezroczysty)."),
+ ("28-50", "export const Button = forwardRef(...)",
+ "Komponent przycisku z forwardRef (można przekazać ref do DOM). Gdy loading=true: wyłącza kliknięcie i pokazuje obracającą się ikonę Loader2. Łączy klasy CSS na podstawie wariantu i rozmiaru."),
+]),
+
+("src/components/ui/Card.tsx", "Komponenty karty/panelu", [
+ ("4-30", "Card, CardHeader, CardTitle, CardContent, CardFooter",
+ "Zestaw komponentów tworzących kartę: Card – kontener z zaokrąglonymi rogami i cieniem. CardHeader – nagłówek z dolną linią. CardTitle – niebieski tytuł h2. CardContent – obszar treści z paddingiem. CardFooter – stopka z górną linią (np. '© 2024 FA Krosno')."),
+]),
+
+("src/components/ui/EmptyState.tsx", "Komponent pustego stanu", [
+ ("4-25", "export function EmptyState({ icon, title, message, action })",
+ "Wyświetlany gdy lista danych jest pusta lub wystąpił błąd ładowania. Pokazuje ikonę (domyślnie skrzynkę Inbox), tytuł i opcjonalny opis. Przykład: 'Brak harmonogramów', 'Nie ma jeszcze żadnych zamówień'."),
+]),
+
+("src/components/ui/Input.tsx", "Komponent pola tekstowego formularza", [
+ ("9-43", "export const Input = forwardRef(...)",
+ "Pole tekstowe z automatycznym ID (useId), etykietą i obsługą błędu walidacji. Gdy error jest podany: czerwona ramka + komunikat błędu + aria-describedby (dostępność). Używany w formularzach logowania, rejestracji itp."),
+]),
+
+("src/components/ui/Modal.tsx", "Komponent okna modalnego (dialogu)", [
+ ("17-93", "export function Modal({ open, onClose, title, children, footer })",
+ "Okno modalne z pełną obsługą dostępności: pułapka focusu (Tab/Shift+Tab porusza tylko wewnątrz modala), Escape zamyka, kliknięcie tła zamyka. Po zamknięciu focus wraca do poprzedniego elementu. Renderowany przez createPortal (poza normalnym drzewem DOM). Zawiera: nagłówek z tytułem i X, treść (children), stopkę z przyciskami."),
+]),
+
+("src/components/ui/Skeleton.tsx", "Komponenty szkieletowego ładowania (loading state)", [
+ ("3-10", "Skeleton", "Animowany szary prostokąt (pulsowanie) – placeholder dla ładujących się treści."),
+ ("13-23", "GridSkeleton({ rows })", "Skeleton tabeli: nagłówek + N wierszy. Wyświetlany podczas ładowania danych do siatek."),
+ ("26-34", "PageSkeleton()", "Skeleton całej strony: nagłówek + duży blok treści. Używany jako fallback Suspense w AppShell."),
+ ("38-49", "FullPageSpinner()", "Kółko obrotu na pełnym ekranie – używany gdy lazy-loaded strony się wczytują (przed AppShell)."),
+]),
+
+("src/components/ui/Toaster.tsx", "Komponent wyświetlający powiadomienia", [
+ ("5-10", "const config",
+ "Słownik konfiguracji dla każdego wariantu powiadomienia: ikona i kolory tła/tekstu."),
+ ("12-51", "export function Toaster()",
+ "Kontener powiadomień: stały, w prawym dolnym rogu (fixed bottom-4 right-4). Nasłuchuje na zmiany w toastStore. Każde powiadomienie: ikona + tytuł + opis + przycisk X. Znika automatycznie po 5 sekundach lub po kliknięciu X."),
+]),
+
+# ═══════════════════════════════════════════════════════════════════
+("TYPY DANYCH API", None, None),
+# ═══════════════════════════════════════════════════════════════════
+
+("src/types/api/common.ts", "Wspólne typy opakowań odpowiedzi API", [
+ ("7-12", "interface PagedResult",
+ "Wynik stronicowania: items (lista elementów), totalCount (łączna liczba), pageNumber, pageSize."),
+ ("14-19", "interface ApiResponse",
+ "Ogólna odpowiedź API: data (dane lub null), success (bool), message (komunikat), errors (błędy walidacji)."),
+ ("21-24", "interface ValidationError", "Błąd walidacji: field (pole formularza) i message (treść błędu)."),
+ ("26", "type SortOrder = 'asc' | 'desc'", "Typ kierunku sortowania."),
+ ("29", "type OrderStatusCode",
+ "Typ kodów statusów zamówień Syteline: O, S, P, C, F lub dowolny inny string."),
+]),
+
+("src/types/api/faKrosno.ts", "Typy DTO z modelu danych FaKrosno (EF)", [
+ ("8-12", "PurchaserDto", "Nabywca: id, purchaserCode (kod nabywcy), purchaserDesc (opis)."),
+ ("15-21", "RecipientDto", "Odbiorca: id, purchaserID (powiązany nabywca), recipientCode, recipientDesc. Zawiera zagnieżdżony obiekt PurchaserDto."),
+ ("24-31", "ProductDto", "Produkt: id, recipientID, recipientIdx (indeks odbiorcy), faIdx (indeks FA), recipientName. Zawiera RecipientDto."),
+ ("34-42", "ScheduleOrderMiscDto", "Dodatkowe dane harmonogramu: type, value, label, display (czy wyświetlać). Pole text = '{Label}: {Value}' generowane przez serwer."),
+ ("44-64", "ScheduleOrderDetailMiscDto, ScheduleOrderDetailDetailMiscDto",
+ "Analogiczne dodatkowe dane dla pozycji i szczegółów pozycji harmonogramu."),
+ ("67-81", "ScheduleOrderDetailDetailDto", "Szczegół pozycji harmonogramu DELFOR: ilość (qty), zakres dat (dateFrom–dateTo), typ harmonogramu (sccType), status, data wysyłki."),
+ ("87-99", "ScheduleOrderDetailDto", "Pozycja harmonogramu: kody produktów (SC/SH), numer zamówienia, odbiorca/nabywca. Zawiera tablicę ScheduleOrderDetailDetailDto."),
+ ("105-120", "ScheduleOrderDto", "Pełny harmonogram DELFOR: numer PO, identyfikator zamówienia, odbiorca, data aktualizacji, typ dokumentu. Zawiera pozycje i dane miscellaneous."),
+]),
+
+("src/types/api/syteline.ts", "Typy DTO z modelu danych Syteline (ERP)", [
+ ("9-39", "CustomerOrderLineItemDto",
+ "Pozycja-zwolnienie zamówienia klienta z Syteline: numery CO/linia/zwolnienie, pozycja, ilość, cena, koszt, kody EDI, daty (due/release/promise)."),
+ ("42-64", "CustomerOrderLineDto",
+ "Linia zamówienia: pozycja, łączna ilość (blanketQty), ceny, daty, kody EDI dla adresu/typu pudełka/przeznaczenia. Zawiera tablicę CustomerOrderLineItemDto."),
+ ("67-94", "CustomerOrderDto",
+ "Pełne zamówienie klienta: numer CO, klient, kontakt, data, status, magazyn, kody EDI (gate/recipient/seller/sender/buyer). Zawiera linie i tłumaczenia EDI."),
+ ("97-124", "EdiCustomerOrderLineItemDto",
+ "Pozycja-zwolnienie zamówienia EDI: podobna do CustomerOrderLineItemDto ale z polami EDI-specific (routingCode, deliveryCallNumber, unloadingPoint, palletCode, palletNumber)."),
+ ("127-146", "EdiCustomerOrderLineDto", "Linia zamówienia EDI: pozycja, ilość, cena, typ pudełka, adres, przeznaczenie."),
+ ("149-177", "EdiCustomerOrderDto",
+ "Pełne zamówienie EDI: numer, klient, daty, status, kody EDI, slOrderNumber (numer w Syteline po wysłaniu), sentToSl ('TAK'/'NIE')."),
+ ("183-197", "EdiCustomerOrderTranslateDto",
+ "Powiązanie (tłumaczenie) zamówienia EDI z zamówieniem Syteline i harmonogramem DELFOR: coEdiOrder, coRowPointer, coCoNum (nr Syteline), ediCoCoNum (nr EDI)."),
+ ("200-212", "ErrorLogDto", "Log błędu z Syteline: numer transakcji, numer błędu, komunikat, daty."),
+ ("215-227", "WzRowMeyleDto", "Wiersz listy pakowej Meyle: ID, nagłówek, numer zamówienia, pozycja, ilość, numer palety, nr WZ, numer części."),
+ ("230-242", "WzRowMarelliDto", "Wiersz listy pakowej Marelli: ID, nagłówek, typ, pozycja, numer inżyniera, ilość, numer zamówienia."),
+ ("245-253", "WzHeaderDto", "Nagłówek dokumentu WZ: ID, klient, data, adresy email, numery WZ. Zawiera wiersze Meyle i Marelli."),
+ ("256-264", "WzClientDto", "Klient magazynowy: ID, numer klienta, sekwencja, nazwa, skrócona nazwa, logo Base64."),
+ ("267-280", "MaterialTransactionDto", "Transakcja materiałowa z Syteline: grupa, numer transakcji, pozycja, data, ilość, magazyn, numer zamówienia, numer karty kontrolnej."),
+ ("283-292", "ItemCustDto", "Powiązanie pozycja–klient: numer pozycji FA, numer klienta, numer pozycji klienta (custItem), kod EAN13."),
+]),
+
+("src/types/api/ordersManagement.ts", "Typy DTO z modelu zarządzania zamówieniami", [
+ ("8-12", "RoleDto", "Rola użytkownika: id, name (nazwa), rowPointer (GUID identyfikator)."),
+ ("15-20", "FunctionDto", "Funkcja systemowa (uprawnienie): id, roleId (do jakiej roli należy), name, rowPointer."),
+ ("26-30", "UserRoleDto", "Powiązanie użytkownik–rola: userId, roleId, rowPointer."),
+ ("37-55", "UserDto",
+ "Pełny obiekt użytkownika systemu: login, passwordHash (UWAGA BEZPIECZEŃSTWA: serwer nie powinien zwracać hasha hasła), isTemporaryPassword, isActive, daty aktywności, email, imię, nazwisko, data utworzenia, liczba nieudanych logowań, blokada konta, lista ról."),
+ ("61-67", "TaskSchedulerDetailDto", "Wpis historii uruchomień zadania: data uruchomienia (jobRunDate), log (treść logu)."),
+ ("70-82", "TaskSchedulerDto", "Zaplanowane zadanie automatyczne: nazwa, ścieżka, wyrażenie cron (cronOptions), daty aktywności, ostatnie i następne uruchomienie, historia uruchomień."),
+]),
+
+("src/types/api/local.ts", "Lokalne typy modeli odpowiedzi API", [
+ ("7-10", "LoginResponseDto", "Odpowiedź na żądanie logowania: token (JWT lub null) i expires (data wygaśnięcia)."),
+ ("13-18", "ResponseModel", "Ogólny model odpowiedzi: status (1=sukces, 0=błąd), identifier (numer zamówienia), message (wiadomość błędu), externalIdentifier (zewnętrzny numer)."),
+ ("24-28", "TransactionModel", "Model wartościowy dla skanowania kart kontrolnych: numer części, numer pozycji, ilość."),
+]),
+
+# ═══════════════════════════════════════════════════════════════════
+("FUNKCJONALNOŚCI (FEATURES) – STRONY APLIKACJI", None, None),
+# ═══════════════════════════════════════════════════════════════════
+
+("src/features/auth/schemas/login.schema.ts", "Schemat walidacji formularza logowania", [
+ ("3-6", "loginSchema = z.object(...)",
+ "Schemat Zod walidujący dane logowania: login (wymagany, min 1 znak), password (wymagany, min 1 znak). Biblioteka Zod automatycznie generuje błędy walidacji po polsku."),
+ ("8", "type LoginFormValues = z.infer",
+ "TypeScript automatycznie wyprowadza typ z schematu Zod – nie trzeba pisać interfejsu osobno."),
+]),
+
+("src/features/auth/schemas/register.schema.ts", "Schemat walidacji formularza rejestracji", [
+ ("3-15", "registerSchema = z.object(...).refine(...)",
+ "Schemat rejestracji: login (1-50 znaków), email (prawidłowy adres, 1-100 znaków), firstName/lastName (1-50 znaków), password (6-100 znaków), confirmPassword. Warunek dodatkowy (refine): password musi być identyczne z confirmPassword – jeśli nie, błąd 'Hasła nie są zgodne' pojawia się przy polu confirmPassword."),
+]),
+
+("src/features/auth/schemas/changePassword.schema.ts", "Schemat walidacji zmiany hasła", [
+ ("3-11", "changePasswordSchema = z.object(...).refine(...)",
+ "Schemat zmiany tymczasowego hasła: newPassword (6-100 znaków) i confirmPassword z weryfikacją zgodności."),
+]),
+
+("src/features/auth/hooks/useLogin.ts", "Hook logiki logowania", [
+ ("25-73", "export function useLogin(onAuthenticated)",
+ "Niestandardowy hook React zarządzający procesem logowania. Używa useMutation z TanStack Query. Kroki: 1) Wywołuje loginRequest (POST /api/Users/login). 2) Jeśli sukces – zapisuje token JWT w authStore (setToken). 3) Pobiera dane użytkownika (getUserByUsername) by sprawdzić czy ma tymczasowe hasło. 4) Jeśli tymczasowe – ustawia requiresPasswordChange=true (pojawia się formularz zmiany hasła). 5) Jeśli normalne – wywołuje onAuthenticated() (przekierowanie na właściwą stronę). Mapuje błędy API na czytelne komunikaty po polsku."),
+]),
+
+("src/features/auth/hooks/useRegister.ts", "Hook logiki rejestracji", [
+ ("11-26", "export function useRegister(onSuccess)",
+ "Hook obsługujący rejestrację konta. Wysyła dane do POST /api/Users/register. Po sukcesie: pokazuje toast 'Konto utworzone, możesz się zalogować' i wywołuje onSuccess() (przekierowanie na /login)."),
+]),
+
+("src/features/auth/hooks/useChangeTemporaryPassword.ts", "Hook zmiany tymczasowego hasła", [
+ ("19-45", "export function useChangeTemporaryPassword(onDone)",
+ "Hook obsługujący pierwszą zmianę hasła po zalogowaniu z hasłem tymczasowym. Wysyła nowe hasło do POST /api/Users/change-password (serwer identyfikuje użytkownika z tokenu JWT). Po sukcesie: pokazuje toast 'Hasło zmienione', wylogowuje użytkownika (logout()), wywołuje onDone() (powrót do /login)."),
+]),
+
+("src/features/auth/components/LoginPage.tsx", "Strona logowania", [
+ ("22-101", "export default function LoginPage()",
+ "Strona logowania. Używa react-hook-form z zodResolver (walidacja przez loginSchema). Sprawdza czy użytkownik jest już zalogowany (wtedy przekierowuje). Jeśli requiresPasswordChange – pokazuje ChangePasswordView zamiast formularza logowania. Formularz: pola Login i Hasło, przycisk 'Zaloguj się' z animacją ładowania, komunikat błędu (np. 'Nieprawidłowy login lub hasło'), link do rejestracji."),
+ ("105-167", "function ChangePasswordView()",
+ "Ekran wyświetlany przy pierwszym logowaniu gdy hasło jest tymczasowe. Formularz: pola 'Nowe hasło' i 'Potwierdź hasło', przycisk 'Zmień hasło'. Po pomyślnej zmianie – wylogowanie i powrót do /login."),
+]),
+
+("src/features/auth/components/RegisterPage.tsx", "Strona rejestracji", [
+ ("12-94", "export default function RegisterPage()",
+ "Strona rejestracji nowego konta. Formularz z polami: Login, Email, Imię, Nazwisko, Hasło, Powtórz hasło. Walidacja przez registerSchema (Zod). Po pomyślnej rejestracji: przekierowanie na /login. Wyświetla błędy z serwera (np. 'Login jest zajęty')."),
+]),
+
+("src/features/auth/components/UnauthorizedPage.tsx", "Strona braku dostępu (403)", [
+ ("4-17", "export default function UnauthorizedPage()",
+ "Prosta strona wyświetlana gdy użytkownik nie ma uprawnień do zasobu (403). Ikona tarczy z wykrzyknikiem, tekst 'Brak dostępu' i link powrotu do strony głównej."),
+]),
+
+("src/features/auth/components/NotFoundPage.tsx", "Strona 404", [
+ ("4-15", "export default function NotFoundPage()",
+ "Strona 404 wyświetlana gdy adres URL nie istnieje. Ikona kompasu, tekst '404 – Nie znaleziono' i link powrotu do strony głównej."),
+]),
+
+("src/features/schedule-orders/ScheduleOrdersPage.tsx", "Strona listy harmonogramów DELFOR", [
+ ("12", "const FILTER_STORAGE_KEY = 'scheduleOrdersGridFilter'",
+ "Klucz localStorage do zapisywania filtrów tabeli – użytkownik nie traci ustawionych filtrów po odświeżeniu."),
+ ("13-20", "const query = useQuery(...)",
+ "Pobiera listę harmonogramów z API. select – sortuje od najnowszych (malejąco wg lastUpdateDate)."),
+ ("22-48", "return (...)",
+ "Warunkowe renderowanie: ładowanie → GridSkeleton (animacja), błąd → EmptyState 'Błąd ładowania', brak danych → EmptyState 'Brak harmonogramów', dane → Card z ScheduleOrdersTreeGrid."),
+]),
+
+("src/features/schedule-orders/ScheduleOrdersTreeGrid.tsx", "Trzypoziomowa tabela harmonogramów DELFOR", [
+ ("73-115", "function DetailDetailGrid(detail)",
+ "Poziom 3 – tabela szczegółów-szczegółów harmonogramu. Kolumny: daty Od/Do, ilość, typ qty, opis, status. Wiersze z typem qty w liście HIGHLIGHT_QTY_TYPES (54, 83, 84) są podświetlone na czerwono. Dwuklik nawiguje do szczegółów harmonogramu."),
+ ("122-179", "function ScheduleOrderDetailsGrid(order)",
+ "Poziom 2 – tabela pozycji harmonogramu. Leniwie ładuje pełne dane harmonogramu (useQuery) dopiero gdy użytkownik rozwinie wiersz. Kopiuje dane nagłówkowe (numer PO, odbiorca) na każdą pozycję (zachowanie z Blazora). Zagnieżdża DetailDetailGrid."),
+ ("197-337", "export function ScheduleOrdersTreeGrid({ data, pageSize, enableFilterPersistence, filterStorageKey })",
+ "Główna trzypoziomowa tabela: Poziom 1 (główna lista) z funkcjami: stronicowanie, sortowanie, filtrowanie Excel, eksport do XLS, dwuklik → strona szczegółów. Opcjonalne: przyciski 'Zapisz filtry' / 'Usuń filtry' zapisujące stan filtrów w localStorage. getMasterWidget() – dostęp do instancji widgetu EJ2 przez DOM (bo React ref zwraca tylko props, nie stan runtime). readLiveFilterColumns() – serializuje aktywne filtry (z wykluczeniem cyklicznych referencji). handleDataBound() – przywraca zapisane filtry po załadowaniu danych."),
+]),
+
+("src/features/schedule-orders/ScheduleOrderDetailPage.tsx", "Strona szczegółów harmonogramu", [
+ ("27-60", "function DetailDetailGrid(props)",
+ "Tabela szczegółów pozycji: ilość, daty Od/Do, typ SCC, typ ilości, status, data wysyłki."),
+ ("62-139", "export default function ScheduleOrderDetailPage()",
+ "Strona szczegółów harmonogramu DELFOR: odczytuje ID z parametru URL, pobiera dane (getScheduleOrder). Wyświetla: karta informacyjna (kod odbiorcy, odbiorca, nabywca, data aktualizacji) + tabela pozycji z możliwością rozwinięcia do szczegółów-szczegółów."),
+]),
+
+("src/features/products/ProductsPage.tsx", "Strona zarządzania produktami", [
+ ("30-35", "editSettings, toolbar, pageSettings",
+ "Konfiguracja tabeli Syncfusion: tryb edycji wiersz-po-wierszu (Normal), pasek narzędzi z przyciskami Edytuj/Aktualizuj/Anuluj/Szukaj, 10 wierszy na stronę."),
+ ("39", "const DEFAULT_INDEX = 'Uzupelnij'",
+ "Domyślny indeks wyszukiwania: 'Uzupelnij' – pokazuje produkty czekające na przypisanie kodu FA (zachowanie z Blazora)."),
+ ("41-58", "const query = useQuery(...), const mutation = useMutation(...)",
+ "Pobieranie produktów po indeksie (searchTerm). mutation – zapisuje zaktualizowany produkt na serwerze i odświeża cache."),
+ ("60-128", "return (...)",
+ "Formularz wyszukiwania: pole tekstowe + przycisk 'Szukaj'. Tabela z edycją inline: ID (tylko do odczytu), Odbiorca (tylko do odczytu), Indeks odbiorcy (tylko do odczytu), Kod FA (edytowalny). Po kliknięciu 'Aktualizuj' w tabeli – mutation.mutate() wysyła zmianę do API."),
+]),
+
+("src/features/warehouse/WarehousePage.tsx", "Strona modułu magazynowego", [
+ ("51-57", "function clientFlavor(client)",
+ "Funkcja rozpoznająca typ klienta ('meyle' lub 'marelli') na podstawie nazwy. Używana do wyboru odpowiedniego formatu listy pakowej i ścieżki URL."),
+ ("70-131", "Zapytania i dane",
+ "clientsQuery – lista klientów magazynowych (filtrowana do MAGNETI MARELLI i MEYLE). wzDocsQuery – transakcje materiałowe (dokumenty WZ) dla wybranego klienta. headersQuery – istniejące listy pakowe klienta. wzDocuments – zdeduplikowana lista WZ (jedna pozycja na grupę, zachowanie z Blazora GroupBy)."),
+ ("132-221", "const create = useMutation(...)",
+ "Główna logika tworzenia listy pakowej: 1) Sprawdza czy zaznaczono wiersze w tabeli. 2) Sprawdza czy nie istnieje już lista dla tych numerów WZ. 3) Tworzy nagłówek WzHeader z losowym UUID. 4) Dla każdej zaznaczonej transakcji: pobiera zamówienie klienta i dane pozycji. 5) Tworzy wiersze Meyle lub Marelli (zależnie od klienta). 6) Przekierowuje na stronę listy pakowej."),
+ ("228-361", "return (...)",
+ "Interfejs strony: dropdown wyboru klienta → tabela WZ z checkboxami (wielokrotny wybór) → przycisk 'Utwórz Packing List' → tabela istniejących list pakowych z przyciskiem 'Otwórz'. Okna modalne dla błędów: 'Zaznacz przynajmniej jeden rekord' i 'Dla zaznaczonego rekordu istnieje już Packing List'."),
+]),
+
+("src/features/warehouse/packing-lists/PackListShell.tsx", "Wspólny szablon strony listy pakowej", [
+ ("23-102", "export function PackListShell({ title, header, toggleTo, toggleLabel, onGenerateXls, children })",
+ "Szablon używany przez wszystkie cztery strony list pakowych (Meyle pełna/uproszczona, Marelli pełna/uproszczona). Zawiera: nagłówek strony z numerami WZ, link przełączający widok pełny/uproszczony, przycisk 'Generuj XLS' (wywołuje onGenerateXls), pole adresów e-mail z zapisywaniem (addEmailsToWzHeader), slot children (tu renderuje się właściwa tabela). Inicjalizuje pole email z danych nagłówka (render-phase reset pattern)."),
+]),
+
+("src/features/warehouse/packing-lists/MeylePackListPage.tsx", "Lista pakowa Meyle – widok pełny (edytowalny)", [
+ ("40-121", "export default function MeylePackListPage()",
+ "Edytowalna lista pakowa dla klienta Meyle. Pobiera nagłówek WZ (headerQuery) i wiersze (rowsQuery). Inicjalizuje lokalny stan rows z danych serwera. save – zapisuje wszystkie wiersze: PUT jeśli już istniały (aktualizacja), POST jeśli nowe (tworzenie). Tabela z pełną edycją: nr palety, nr zamówienia, nr pozycji, indeks FA, nr części SL, ilość, nr WZ."),
+]),
+
+("src/features/warehouse/packing-lists/MeylePackListSimplePage.tsx", "Lista pakowa Meyle – widok uproszczony (tylko odczyt)", [
+ ("21-63", "export default function MeylePackListSimplePage()",
+ "Uproszczony podgląd listy pakowej Meyle (tylko do odczytu). Pokazuje tylko: nr palety, nr pozycji, indeks FA, ilość. Używa tego samego PackListShell (z przyciskiem 'Generuj XLS' i adresami email)."),
+]),
+
+("src/features/warehouse/packing-lists/MarelliPackListPage.tsx", "Lista pakowa Marelli – widok pełny (edytowalny)", [
+ ("40-116", "export default function MarelliPackListPage()",
+ "Edytowalna lista pakowa dla klienta Marelli. Analogiczna do MeylePackListPage, ale z kolumnami: typ, nr inżyniera zamiast nr części SL. Zapisuje przez PUT (update) lub POST (create)."),
+]),
+
+("src/features/warehouse/packing-lists/MarelliPackListSimplePage.tsx", "Lista pakowa Marelli – widok uproszczony", [
+ ("21-63", "export default function MarelliPackListSimplePage()",
+ "Uproszczony podgląd listy pakowej Marelli (tylko odczyt): nr palety, nr pozycji, indeks FA, ilość."),
+]),
+
+("src/features/customer-orders/CustomerOrdersPage.tsx", "Strona listy zamówień klientów (Syteline)", [
+ ("33-40", "function Field({ label, value })",
+ "Mały pomocniczy komponent: wyświetla etykietę z podkreśleniem i wartość pogrubioną. Odwzorowanie Blazor label: value."),
+ ("43-80", "export function CustomerOrderSummary(order)",
+ "Rozwijany szczegół zamówienia (detail template tabeli): dwa kolumny informacji o zamówieniu: numer CO, numer PO klienta, klient, odbiorca, kontakt, data, warunki, wartość, status, magazyn, kody EDI."),
+ ("82-139", "export default function CustomerOrdersPage()",
+ "Lista zamówień klientów posortowana od najnowszych. Tabela: numer zamówienia, nr klienta, odbiorca, data, status. Dwuklik → strona szczegółów zamówienia. Rozwinięcie wiersza → CustomerOrderSummary."),
+]),
+
+("src/features/customer-orders/CustomerOrderDetailPage.tsx", "Strona szczegółów zamówienia klienta", [
+ ("49-78", "function CustomerOrderLineDetail(line)",
+ "Szczegóły linii zamówienia: numer CO, linia, pozycja, opis, ilość, cena, daty, kody EDI pudełka."),
+ ("81-114", "function CustomerOrderLineItemDetail(item)",
+ "Szczegóły zwolnienia (line item): numer CO, linia, zwolnienie, pozycja, ilość, cena, daty, magazyn, kody EDI."),
+ ("116-295", "export default function CustomerOrderDetailPage()",
+ "Strona szczegółów: podsumowanie zamówienia (CustomerOrderSummary) + przycisk 'Pokaż powiązane DELFOR' (lazy-load harmonogramów powiązanych przez EDI translates) + tabela linii zamówienia (z rozwinięciem do szczegółów) + tabela harmonogramów (po kliknięciu linii, z rozwinięciem do szczegółów harmonogramów). Powiązane harmonogramy filtrowane po ID z ediCustomerOrderTranslates."),
+]),
+
+("src/features/edi-customer-orders/EdiCustomerOrdersPage.tsx", "Strona listy zamówień EDI", [
+ ("52-87", "export function EdiOrderSummary(order)",
+ "Rozwijany panel szczegółów zamówienia EDI: dwie kolumny z numerami, datami, statusem, kodami EDI (gate, recipient, sender, seller, buyer)."),
+ ("89-243", "export default function EdiCustomerOrdersPage()",
+ "Lista zamówień EDI z filtrowaniem (domyślnie tylko niezaksięgowane, poster=0). Checkbox 'Pokaż wszystkie' przełącza widok. Wielokrotny wybór wierszy (checkboxy). Przycisk 'Księguj zaznaczone' – uruchamia handleSend. handleSend: sekwencyjnie wysyła każde zaznaczone zamówienie do Syteline, zbiera wyniki, wyświetla okno modalne z sukcesami (zielone) i błędami (czerwone)."),
+]),
+
+("src/features/edi-customer-orders/EdiCustomerOrderDetailPage.tsx", "Strona szczegółów zamówienia EDI", [
+ ("45-71", "function EdiLineDetail(line)",
+ "Szczegóły linii zamówienia EDI: numer, pozycja, opis, ilość, cena, BoxType, adres, przeznaczenie."),
+ ("73-107", "function EdiLineItemDetail(item)",
+ "Szczegóły zwolnienia EDI: wszystkie pola pozycji + kody EDI (routing, delivery, unloading, destination, pallet)."),
+ ("109-237", "export default function EdiCustomerOrderDetailPage()",
+ "Strona szczegółów zamówienia EDI: podsumowanie (EdiOrderSummary) + tabela linii (z rozwinięciem) + tabela harmonogramów po wybraniu linii."),
+]),
+
+("src/features/translations/TranslationsPage.tsx", "Strona zarządzania powiązaniami zamówień EDI z DELFORami", [
+ ("40-135", "export default function TranslationsPage()",
+ "Tabela powiązań zamówień EDI z Syteline i DELFORami: DELFOR Id, numer EDI, numer SL, nr PO, liczba zamówień, czy znaleziono, data. Każdy wiersz ma przycisk 'Usuń'. Kliknięcie 'Usuń' → modal potwierdzenia → deleteEdiTranslation → odświeżenie listy."),
+]),
+
+("src/features/users/UsersManagerPage.tsx", "Strona zarządzania użytkownikami (zakładki)", [
+ ("57-93", "export default function UsersManagerPage()",
+ "Strona z trzema zakładkami (tab navigation): Użytkownicy, Role, Funkcje. Przełącza między komponentami UsersGrid, RolesGrid, FunctionsGrid."),
+ ("96-210", "function UsersGrid()",
+ "Tabela użytkowników z pełną edycją CRUD: kolumny: ID, Login, Email, Imię, Nazwisko, Aktywny, Data utworzenia + przycisk 'Zresetuj hasło'. Nowy wiersz bez ID → adminCreateUser (generuje tymczasowe hasło). Istniejący wiersz → updateUser. Usunięcie → deleteUser. 'Zresetuj hasło' → adminResetPassword. Po create/reset → modal z tymczasowym hasłem."),
+ ("212-268", "function RolesGrid()",
+ "Tabela ról: ID, Nazwa roli. Pełna edycja: add/edit/delete z rowPointer (UUID)."),
+ ("270-330", "function FunctionsGrid()",
+ "Tabela funkcji systemowych: ID, ID roli, Nazwa funkcji. Pełna edycja z przypisaniem do roli (roleId)."),
+]),
+
+("src/features/scheduler/SchedulerPage.tsx", "Strona harmonogramu zadań automatycznych (Hangfire)", [
+ ("38-59", "function RunLogGrid(props)",
+ "Tabela historii uruchomień zadania: data uruchomienia i treść logu. Renderowana jako detail template."),
+ ("66-149", "export default function SchedulerPage()",
+ "Tabela zaplanowanych zadań automatycznych (Hangfire). Tryb edycji Dialog (okno modalne). Kolumny: ID, Nazwa, Ścieżka, Cron (wyrażenie harmonogramu), Ostatnie uruchomienie, Następne uruchomienie. Rozwinięcie wiersza → historia uruchomień. Add/Edit/Delete wysyłają do API HangfireJobs."),
+]),
+]
+
+# ──────────────────────────────────────────────────────────────────────────────
+# Klasa generatora PDF
+# ──────────────────────────────────────────────────────────────────────────────
+
+class DocPDF(FPDF):
+ def __init__(self):
+ super().__init__()
+ self.add_font("Arial", "", FONT_PATH)
+ self.add_font("Arial", "B", FONT_PATH)
+ self.add_font("Arial", "I", FONT_PATH)
+ self.set_auto_page_break(auto=True, margin=15)
+
+ def header(self):
+ if self.page_no() == 1:
+ return
+ self.set_font("Arial", "I", 8)
+ self.set_text_color(150, 150, 150)
+ self.cell(0, 8, "Dokumentacja projektu FaKrosno Management", align="L")
+ self.cell(0, 8, f"Strona {self.page_no()}", align="R", new_x="LMARGIN", new_y="NEXT")
+ self.set_text_color(0, 0, 0)
+ self.ln(2)
+
+ def footer(self):
+ self.set_y(-13)
+ self.set_font("Arial", "I", 8)
+ self.set_text_color(150, 150, 150)
+ self.cell(0, 8, f"FA Krosno Management | Wygenerowano automatycznie | s. {self.page_no()}", align="C")
+ self.set_text_color(0, 0, 0)
+
+
+def add_cover(pdf: DocPDF):
+ pdf.add_page()
+ pdf.set_fill_color(30, 64, 175)
+ pdf.rect(0, 0, 210, 297, "F")
+ pdf.set_text_color(255, 255, 255)
+ pdf.set_font("Arial", "B", 28)
+ pdf.set_y(80)
+ pdf.multi_cell(0, 12, "Dokumentacja\nTechniczna", align="C")
+ pdf.ln(6)
+ pdf.set_font("Arial", "B", 18)
+ pdf.multi_cell(0, 10, "FA Krosno Management", align="C")
+ pdf.ln(12)
+ pdf.set_font("Arial", "", 13)
+ pdf.multi_cell(0, 8, "Szczegółowy opis kodu źródłowego\naplikacji webowej React + TypeScript", align="C")
+ pdf.ln(20)
+ pdf.set_font("Arial", "", 11)
+ pdf.multi_cell(0, 8, "Projekt: FaKrosnoManagement\nStos: React 19 · TypeScript · Vite · Tailwind CSS\nBackend: .NET Web API\nData generacji: 2026-06-01", align="C")
+ pdf.set_text_color(0, 0, 0)
+
+
+def add_toc(pdf: DocPDF):
+ pdf.add_page()
+ pdf.set_font("Arial", "B", 18)
+ pdf.cell(0, 12, "Spis treści", new_x="LMARGIN", new_y="NEXT")
+ pdf.set_font("Arial", "", 11)
+ pdf.ln(4)
+
+ sections = []
+ for item in DOCS:
+ if item[1] is None:
+ sections.append(("section", item[0]))
+ else:
+ sections.append(("file", item[0]))
+
+ for kind, name in sections:
+ if kind == "section":
+ pdf.ln(4)
+ pdf.set_font("Arial", "B", 11)
+ pdf.set_text_color(30, 64, 175)
+ pdf.cell(0, 7, f" {name}", new_x="LMARGIN", new_y="NEXT")
+ pdf.set_text_color(0, 0, 0)
+ else:
+ pdf.set_font("Arial", "", 10)
+ pdf.set_text_color(60, 60, 60)
+ pdf.cell(8, 6, "", new_x="RIGHT", new_y="TOP")
+ pdf.cell(0, 6, name, new_x="LMARGIN", new_y="NEXT")
+ pdf.set_text_color(0, 0, 0)
+
+
+def add_section_header(pdf: DocPDF, title: str):
+ pdf.add_page()
+ pdf.set_fill_color(30, 64, 175)
+ pdf.set_text_color(255, 255, 255)
+ pdf.set_font("Arial", "B", 16)
+ pdf.rect(0, pdf.get_y() - 2, 210, 20, "F")
+ pdf.cell(0, 16, f" {title}", new_x="LMARGIN", new_y="NEXT")
+ pdf.set_text_color(0, 0, 0)
+ pdf.ln(6)
+
+
+def add_file_section(pdf: DocPDF, filename: str, description: str, lines: list):
+ # File header
+ pdf.set_fill_color(241, 245, 249)
+ pdf.set_draw_color(203, 213, 225)
+ y = pdf.get_y()
+ pdf.set_font("Arial", "B", 12)
+ pdf.set_text_color(30, 64, 175)
+ pdf.set_fill_color(224, 231, 255)
+ pdf.cell(0, 10, f" {filename}", fill=True, new_x="LMARGIN", new_y="NEXT")
+ pdf.set_text_color(0, 0, 0)
+ pdf.set_font("Arial", "I", 10)
+ pdf.set_text_color(80, 80, 80)
+ pdf.multi_cell(0, 6, f" {description}", new_x="LMARGIN", new_y="NEXT")
+ pdf.set_text_color(0, 0, 0)
+ pdf.ln(3)
+
+ # Lines table
+ for (lineno, code, explanation) in lines:
+ if pdf.get_y() > 265:
+ pdf.add_page()
+
+ # Line number / code range
+ pdf.set_fill_color(248, 250, 252)
+ pdf.set_font("Arial", "B", 9)
+ pdf.set_text_color(99, 102, 241)
+ pdf.cell(30, 6, f"Linia {lineno}", fill=True, new_x="RIGHT", new_y="TOP")
+
+ # Code snippet
+ pdf.set_font("Arial", "I", 8.5)
+ pdf.set_text_color(55, 65, 81)
+ code_short = (code[:65] + "...") if len(code) > 68 else code
+ pdf.cell(0, 6, code_short, new_x="LMARGIN", new_y="NEXT")
+
+ # Explanation
+ pdf.set_font("Arial", "", 10)
+ pdf.set_text_color(30, 30, 30)
+ pdf.set_x(10)
+
+ # Handle multi-line explanations
+ for part in explanation.split("\n"):
+ if pdf.get_y() > 268:
+ pdf.add_page()
+ pdf.set_x(12)
+ pdf.multi_cell(185, 5.5, part, new_x="LMARGIN", new_y="NEXT")
+
+ pdf.set_draw_color(226, 232, 240)
+ pdf.line(10, pdf.get_y(), 200, pdf.get_y())
+ pdf.ln(2)
+
+ pdf.ln(6)
+
+
+# ──────────────────────────────────────────────────────────────────────────────
+# Main
+# ──────────────────────────────────────────────────────────────────────────────
+
+def main():
+ print("Generowanie dokumentacji PDF...")
+ pdf = DocPDF()
+
+ add_cover(pdf)
+ add_toc(pdf)
+
+ for item in DOCS:
+ if item[1] is None:
+ # Section header
+ add_section_header(pdf, item[0])
+ else:
+ filename, description, lines = item
+ if pdf.get_y() > 230:
+ pdf.add_page()
+ add_file_section(pdf, filename, description, lines)
+
+ pdf.output(OUTPUT_FILE)
+ print(f"✅ Gotowe! Plik: {OUTPUT_FILE}")
+ print(f" Stron: {pdf.page}")
+
+
+if __name__ == "__main__":
+ main()
+