From 0a7399f0157633b22a045840185688619bebdd9e Mon Sep 17 00:00:00 2001 From: Piotr Kus Date: Mon, 27 Jul 2026 19:18:04 +0200 Subject: [PATCH] Add documentation generator script for FaKrosnoManagement Co-authored-by: Cursor --- FaKrosnoManagement/generate_docs.py | 841 ++++++++++++++++++++++++++++ 1 file changed, 841 insertions(+) create mode 100644 FaKrosnoManagement/generate_docs.py 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() +