Add documentation generator script for FaKrosnoManagement
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
841
FaKrosnoManagement/generate_docs.py
Normal file
841
FaKrosnoManagement/generate_docs.py
Normal file
@@ -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 <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 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<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 (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<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()
|
||||
|
||||
Reference in New Issue
Block a user