Files
FA_WEB/FaKrosnoManagement/generate_docs.py

842 lines
64 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 <div id='root'> 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={<ProtectedRoute />}",
"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={<AdminRoute />}",
"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<string, string[]>",
"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<T>(...)",
"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 pozycjaklient (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<T>(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<Props, State>",
"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: <Badge tone='blue'>5</Badge> 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<T>",
"Wynik stronicowania: items (lista elementów), totalCount (łączna liczba), pageNumber, pageSize."),
("14-19", "interface ApiResponse<T>",
"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 (dateFromdateTo), 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 pozycjaklient: 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żytkownikrola: 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<typeof loginSchema>",
"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 <u>label:</u> <b>value</b>."),
("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()