Generator ofert PDF w 30 minut z Claude AI - tutorial krok po kroku
Proposify kosztuje 140 zł miesięcznie. PandaDoc - około 80 zł za użytkownika. Fakturownia w pakietach wyższych - 39-79 zł. Każde z tych narzędzi sprzedaje to samo: szablon oferty z polami do wypełnienia i przycisk „Pobierz PDF”.
Zbudujesz dokładnie to samo. W 30 minut. Z Claude AI. Zero subskrypcji.
Za pół godziny będziesz mieć profesjonalny generator ofert handlowych. Z polskimi znakami, tabelą pozycji z dynamiczną kalkulacją VAT, kwotą słownie po polsku, logo Twojej firmy, historią ofert i eksportem do PDF. Jeden plik HTML. Otwierasz w przeglądarce. Działa.

→ Zobacz gotowe narzędzie - efekt finalny jest już online. Przeklikaj, wygeneruj testową ofertę PDF, zobacz jak to działa. Potem wróć i zbuduj własną wersję.
To nie jest artykuł o tym, jak zaoszczędzić 1 680 zł rocznie na Proposify (chociaż to efekt uboczny). To jest artykuł o tym, jak z Claude AI zbudować własne narzędzia biznesowe zamiast płacić miesięcznie za szablony.
TL;DR: 4 rzeczy, które wyniesiesz z tego artykułu:
- Działający generator ofert PDF - dane firmy, klient, pozycje z VAT, logo, historia
- 6 gotowych promptów CRISP - kopiujesz, wklejasz do Claude, dostajesz kod
- Polskie znaki w PDF bez tricków - podejście html2canvas robotnicze, nie hakerskie
- Wzorzec pracy z AI nad realnym narzędziem biznesowym - powtórzysz przy fakturach, umowach, raportach
Poziom 2 (średniozaawansowany) | Seria „Zbuduj to z AI” | 6 kroków | 6 promptów CRISP | 1 plik HTML | ~30 minut
Co zbudujemy
| Funkcja | Krok |
|---|---|
| Layout oferty w proporcjach A4 z live preview | #1 |
| Dane sprzedawcy, nabywcy, numeracja oferty (walidacja NIP) | #2 |
| Tabela pozycji z automatyczną kalkulacją VAT + kwota słownie | #3 |
| Generator PDF z polskimi znakami (html2canvas + jsPDF) | #4 |
| Upload logo firmy z auto-resize | #5 |
| Historia ofert + eksport/import JSON | #6 |
Stack: Jeden plik HTML. Dwie biblioteki z CDN (html2canvas 1.4.1, jsPDF 2.5.1). Reszta to czyste vanilla JavaScript + localStorage.
Zobacz gotowy generator ofert w akcji - to jest finalny efekt, który zbudujesz przechodząc przez 6 kroków tego tutoriala. Otwórz w nowej karcie, przeklikaj - zobacz jak wygląda pełna wersja. Potem wróć i buduj własny.
Krok 0: Przygotowanie (1 minuta)
Nie potrzebujesz żadnego klucza API. Nie potrzebujesz rejestracji. Jedyne biblioteki zewnętrzne (html2canvas i jsPDF) ładują się z CDN po pierwszym otwarciu - potem aplikacja może działać offline.
Czego potrzebujesz
- Przeglądarka - Chrome, Firefox, Edge, Safari (cokolwiek z ostatnich 3 lat)
- Edytor tekstu - Notatnik, VS Code, cokolwiek z podświetlaniem składni
- Claude - darmowe konto na claude.ai. Dla tego tutoriala wystarczy model Sonnet.
Przygotuj plik
Utwórz nowy plik tekstowy. Nazwij go oferta.html. To będzie jedyny plik, nad którym pracujemy.
Krok 1: Layout oferty A4 (Prompt #1)
Stan aplikacji: Pusty plik oferta.html. Zaczynamy od zera.
Czas na ten krok: ~5 minut
Pierwszy krok to fundament wizualny. Oferta handlowa w Polsce ma konkretną strukturę: header z numerem, dwie kolumny z danymi stron, tytuł, tabela pozycji, podsumowanie, warunki, stopka. Każdy z tych elementów ma swoje miejsce.
Budujemy layout w proporcjach A4 - to będzie nasz live preview. Dokładnie to, co ostatecznie trafi do PDF.
Dlaczego ten prompt działa
Prompt używa formatu CRISP - tego samego, co w tutorialu Kanban i Pomodoro:
| Element | Co robi | W tym prompcie |
|---|---|---|
| Context | Kontekst projektu | Generator ofert w jednym pliku HTML |
| Role | Rola AI | Designer dokumentów B2B, znający polskie standardy |
| Intent | Cel | Layout dwukolumnowy, preview w proporcjach A4 |
| Scope | Ograniczenia | Kolorystyka granat, Inter, zero bibliotek |
| Precision | Format odpowiedzi | Pełny plik HTML |
Pełne wyjaśnienie metody: Inżynieria promptów - Framework CRISP
Prompt do skopiowania
Otwórz Claude i wklej:
<context>
Buduję generator ofert handlowych PDF w jednym pliku HTML (z CSS
i JavaScript w środku), w ramach serii "Zbuduj to z AI". Aplikacja
ma działać po otwarciu pliku .html w przeglądarce. Użytkownik widzi
po lewej panel konfiguracji, po prawej live preview oferty w proporcjach
A4. W kolejnych krokach dodamy PDF, logo i historię. Teraz skupiamy się
na czystym wyglądzie.
</context>
<role>
Jesteś UI/UX designerem specjalizującym się w dokumentach B2B
i profesjonalnych szablonach handlowych. Znasz standardy ofert
handlowych stosowane w Polsce.
</role>
<intent>
Stwórz szkielet generatora ofert:
1. Layout dwukolumnowy (flex): 30% panel ustawień po lewej,
70% live preview po prawej. Na mobile - stacked.
2. Preview w proporcjach A4 portrait (210mm/297mm, skalowane),
cień pod dokumentem, białe tło.
3. Sekcje w preview: header (placeholder logo + "OFERTA NR 2026/04/001"),
meta-grid 2 kolumny (Sprzedawca / Nabywca), tytuł oferty,
placeholder tabeli pozycji, blok podsumowania netto/VAT/brutto
(wyrównany do prawej), warunki handlowe, stopka z podpisem.
4. Panel ustawień ma puste kontenery na kolejne kroki.
</intent>
<scope>
- Jeden plik HTML, lang="pl", meta charset UTF-8
- Typografia: font Inter z Google Fonts, weight 400/600/700
- Kolory B2B: akcent granat #1E3A5F, tekst grafit #2C3E50,
tło #F5F7FA, bordery #E0E6ED
- Użyj realistycznych danych-zaślepek (np. "Moja Firma Sp. z o.o."
jako sprzedawca, "ABC Consulting" jako klient) zamiast "Lorem ipsum"
- Zero bibliotek - tylko czyste HTML/CSS w tym kroku
- Responsywny, komentarze w kodzie po polsku
</scope>
<precision>
Zwróć KOMPLETNY plik HTML gotowy do zapisania jako oferta.html
i otwarcia w przeglądarce. Kod czytelny, z komentarzami po polsku
przy każdej sekcji CSS.
</precision>
Co się zmieni
Claude wygeneruje plik HTML z profesjonalnym layoutem. Po lewej szary panel z pustymi kontenerami. Po prawej biały dokument w proporcjach A4 z cieniem - wygląda jak wydrukowana kartka na biurku. Skopiuj kod, wklej do oferta.html, zapisz i otwórz w przeglądarce.
Weryfikacja
- Widzisz dwukolumnowy layout: panel po lewej, dokument po prawej
- Dokument ma proporcje pionowej kartki A4 z widocznym cieniem
- Header dokumentu zawiera miejsce na logo i numer „OFERTA NR 2026/04/001”
- Sekcje Sprzedawca i Nabywca są ułożone w dwie kolumny
- Na telefonie (zmniejsz okno) wszystko układa się pionowo, pozostaje czytelne
Co się tu uczysz
- CSS aspect-ratio - proporcje A4 (210/297) to jedna linijka CSS, a nie kalkulacje w JS
- CSS Grid dla metadanych - grid-template-columns: 1fr 1fr układa dwa bloki bez żadnego floatowania czy flexboxa
- Hierarchia typograficzna - Inter w trzech wagach (400 body, 600 labels, 700 nagłówki) daje profesjonalny efekt bez użycia wielu fontów
Krok 2: Dane firmy, klienta i localStorage profilu (Prompt #2)
Stan aplikacji: Piękny, ale statyczny layout. Panel po lewej ma puste kontenery. Dane w preview są zaślepkami - nic nie wpływa na nic.
Czas na ten krok: ~5 minut
Pora podłączyć formularz do live preview. Dane sprzedawcy (czyli Twoje) wpisujesz raz - zapisują się w localStorage i pojawiają w każdej kolejnej ofercie. Dane nabywcy wpisujesz per oferta. Numer oferty generuje się automatycznie. NIP ma walidację - nie tylko długości, ale pełną sumę kontrolną.
Prompt do skopiowania
Wklej do Claude (w tej samej rozmowie - Claude pamięta kontekst):
<context>
Mam gotowy layout generatora ofert z panelem po lewej i preview A4
po prawej. Sekcje preview mają zaślepki. Pora podłączyć formularz
do live preview - dane sprzedawcy, dane klienta, numer oferty, daty.
Profil firmy ma się zapisywać w localStorage, żeby nie wpisywać go
za każdym razem.
</context>
<role>
Jesteś frontend developerem specjalizującym się w formularzach B2B
i walidacji polskich danych firmowych (NIP, adresy).
</role>
<intent>
Podłącz formularz do live preview:
1. Panel "Sprzedawca" (zwijany, domyślnie schowany jeśli profil w localStorage):
nazwa firmy, adres, NIP, email, telefon, konto bankowe.
Dane zapisuj w localStorage pod kluczem "oferta_profil_firmy"
przy każdej zmianie (debounce 400ms). Przy starcie - załaduj.
2. Panel "Nabywca": nazwa firmy, NIP, osoba kontaktowa, email.
3. Panel "Oferta": numer (auto-generowany YYYY/MM/NNN z licznikiem
w localStorage "oferta_licznik_YYYY_MM"), data wystawienia (dziś),
termin ważności (dziś + 14 dni, edytowalny), tytuł oferty.
4. Walidacja NIP: dokładnie 10 cyfr, checksum algorytmem
(wagi 6,5,7,2,3,4,5,6,7, modulo 11). Czerwona ramka gdy nieprawidłowy.
5. Live preview aktualizuje się na każdy input (event listener 'input').
Pusty input = delikatny placeholder "[Uzupełnij dane]" w preview.
</intent>
<scope>
- Zaktualizuj istniejący plik HTML
- Formatuj daty w preview po polsku: "21 kwietnia 2026"
- NIP w preview wyświetlany z myślnikami: 123-456-78-90
- Zachowaj wszystkie style z kroku 1
- Przycisk "Edytuj profil firmy" rozwija panel Sprzedawcy
</scope>
<precision>
Zwróć KOMPLETNY, zaktualizowany plik HTML. Zachowaj istniejący
layout i kolorystykę. Dodaj komentarze po polsku przy nowej logice.
</precision>
Co się zmieni
Wpisujesz nazwę firmy - pojawia się w preview. Zmieniasz NIP - widzisz go sformatowanego z myślnikami. Wpisujesz błędny NIP (np. 1234567890) - czerwona ramka, algorytm wyłapał, że suma kontrolna się nie zgadza. Po odświeżeniu strony - Twoje dane firmy już są wczytane. Nabywca resetowany, bo to zmienna rzecz.
Weryfikacja
- Wpisujesz dane w panelu - preview aktualizuje się na żywo
- Nieprawidłowy NIP (np. 1111111111) - pole dostaje czerwoną ramkę
- Odświeżasz stronę - dane Twojej firmy zostają, dane klienta nie
- Numer oferty auto-generuje się w formacie 2026/04/001
- Data ważności = data dzisiejsza + 14 dni
Jeśli checksum NIP nie działa - Claude mógł użyć uproszczonej wersji. Wklej:
„Walidacja NIP przepuszcza błędne numery. Sprawdź, czy używasz algorytmu z wagami [6,5,7,2,3,4,5,6,7] i modulo 11, a ostatnia cyfra NIP to cyfra kontrolna.”
Co się tu uczysz
- Algorytm sumy kontrolnej NIP - to nie jest tajemna magia, tylko mnożenie cyfr przez wagi i dzielenie modulo 11. Ten sam wzorzec działa dla PESEL, REGON, IBAN
- Debounce - zapisy do localStorage co 400ms zamiast co keystroke. Chroni przed zapisywaniem 50 razy na sekundę przy szybkim pisaniu
- localStorage jako profil - oddzielenie „danych trwałych” (profil firmy) od „danych sesji” (bieżąca oferta). Podstawowy wzorzec każdej aplikacji z persystencją
Krok 3: Tabela pozycji z VAT i kwotą słownie (Prompt #3)
Stan aplikacji: Dane sprzedawcy i nabywcy wpisane, numer oferty się generuje. Jednak środek oferty - tabela pozycji - wciąż jest pusty.
Czas na ten krok: ~5 minut
To jest sedno oferty handlowej. Tabela z pozycjami, cenami, VAT. Automatyczne przeliczenia netto → VAT → brutto. Podsumowanie per stawka VAT. I to, co zawsze robi wrażenie na czytelnikach ofert: kwota słownie po polsku.
„Dwa tysiące trzysta czterdzieści pięć 50/100 PLN” to wymóg formalny w wielu polskich dokumentach handlowych. Zbudujemy algorytm, który to zrobi poprawnie.
Prompt do skopiowania
<context>
Generator ofert ma wypełnione dane sprzedawcy i klienta, numer oferty
i daty. Teraz serce oferty - tabela pozycji z automatycznymi
przeliczeniami VAT. Musi obsługiwać polskie stawki VAT i wyliczać
kwotę słownie po polsku (wymaganie formalne ofert handlowych).
</context>
<role>
Jesteś frontend developerem z doświadczeniem w narzędziach
rozliczeniowych i algorytmach zamiany liczb na tekst słowny
w języku polskim.
</role>
<intent>
Zbuduj dynamiczną tabelę pozycji oferty:
1. Kolumny tabeli w preview: Lp., Nazwa pozycji, Ilość, Cena netto,
Stawka VAT, Wartość netto, Wartość VAT, Wartość brutto.
2. W panelu ustawień: mini-formularz dodawania pozycji
(nazwa, ilość, cena netto, stawka VAT jako dropdown:
23%, 8%, 5%, 0%, "zw."), przycisk "Dodaj pozycję".
3. Każda pozycja w panelu ma przyciski: edytuj (inline),
usuń (X z confirm), strzałki góra/dół do zmiany kolejności.
4. Automatyczne przeliczenia: netto = ilość × cena_netto,
vat = netto × stawka (dla "zw." = 0), brutto = netto + vat.
Wszystko zaokrąglone do 2 miejsc po przecinku (grosze).
5. Podsumowanie pod tabelą w preview:
- Suma netto, suma VAT, suma brutto (bold, granat)
- Rozbicie VAT per stawka ("VAT 23%: X zł, VAT 8%: Y zł")
- Kwota słownie po polsku
(przykład: "dwa tysiące trzysta czterdzieści pięć 50/100 PLN")
6. Pozycje zapisuj w zmiennej state (tablica obiektów), renderuj
tabelę z tej tablicy. Po każdej zmianie - re-render.
</intent>
<scope>
- Zaktualizuj istniejący plik HTML
- Funkcja zamiany liczby na słowa: obsłuż zakres 0 do 999 999 999
(jedności, dziesiątki, setki, tysiące, miliony)
- Format groszy w kwocie słownie: ZAWSZE "XX/100 PLN" (nie "XX groszy").
Przykład: 2345.50 → "dwa tysiące trzysta czterdzieści pięć 50/100 PLN"
Przykład: 2345.00 → "dwa tysiące trzysta czterdzieści pięć 00/100 PLN"
- Format liczby w preview: "1 234,56 zł" (spacja jako separator
tysięcy, przecinek jako separator dziesiętny, "zł" na końcu)
- Dodaj 2 przykładowe pozycje jako dane startowe
- Zachowaj istniejącą funkcjonalność formularza firmy/klienta
</scope>
<precision>
Zwróć KOMPLETNY, zaktualizowany plik HTML z pełną logiką tabeli,
przeliczeń i funkcją zamiany liczby na słowa. Funkcja kwotaSlownie()
w osobnym bloku z komentarzem wyjaśniającym algorytm.
</precision>
Co się zmieni
Dodajesz pozycję „Usługa konsultingowa”, ilość 10, cena netto 500, VAT 23%. W tabeli pojawia się wiersz z wszystkimi przeliczeniami. Dodajesz drugą pozycję. Dodajesz trzecią. Pod tabelą rośnie podsumowanie z rozbiciem VAT per stawka. I słownie: „sześć tysięcy sto pięćdziesiąt 00/100 PLN”.
Weryfikacja
- Dodanie pozycji pojawia się w tabeli z automatycznymi przeliczeniami
- Zmiana ilości lub ceny aktualizuje wiersz i podsumowanie
- Usunięcie pozycji (X) wymaga potwierdzenia
- Podsumowanie rozbija VAT per stawkę (osobny wiersz dla 23%, 8% itd.)
- Kwota słownie jest poprawna gramatycznie (np. „dwa tysiące” nie „dwie tysiące”)
Algorytm kwoty słownie to klasyczny przypadek „proste z wyglądu, diabelnie trudne w szczegółach”. Algorytm kwoty słownie wygląda prosto z wyglądu, ale ma dużo szczegółów gramatycznych. Polski ma 7 przypadków i 3 rodzaje - jedyny sposób, żeby to zrobić dobrze, to tablica fleksji. Claude zrobi to poprawnie dla typowych kwot.
Co się tu uczysz
- State-driven rendering - pozycje żyją w tablicy JS, tabela jest odbiciem tej tablicy. Dokładnie ten sam wzorzec co w tablicy Kanban, tylko dla innej struktury danych
- Zaokrąglenia finansowe -
Math.round(x * 100) / 100daje grosze, aletoFixed(2)daje string. Obie techniki są przydatne w różnych kontekstach - Algorytm polskiej ortografii liczebnikowej - miliony to „miliony” (2-4) lub „milionów” (5+), tysiące to „tysiące” lub „tysięcy”. Te reguły dotyczą też list produktów, faktur i umów
Utknąłeś na tym etapie? Jeśli Claude zwraca błędny VAT albo niepoprawną kwotę słownie, wklej mu cały kod i opisz dokładnie, co nie działa. W 90% przypadków poprawia za pierwszym razem. Jeśli chcesz systemowo nauczyć się pracy z Claude - darmowy Kurs Claude AI ma 44 lekcje od zera do zaawansowanego.
Krok 4: Generator PDF z polskimi znakami (Prompt #4)
Stan aplikacji: Kompletna oferta w live preview. Dane, pozycje, VAT, kwota słownie. Działa jednak tylko w przeglądarce - nie da się wysłać klientowi.
Czas na ten krok: ~6 minut
To jest najważniejszy krok technicznie. Polskie znaki w jsPDF to klasyczny ból głowy - domyślne fonty (Helvetica, Times) nie mają glifów dla ą, ę, ś, ć. Standardowe rozwiązanie: dołączyć font TTF jako base64 (to 180 KB). Inne rozwiązanie: renderować HTML do obrazu i wkleić ten obraz do PDF.
Wybieramy drugie. Jest robotnicze, nie hakerskie, i działa zawsze.
Jak działa html2canvas + jsPDF?
Trzy fazy:
- html2canvas bierze element DOM (nasze preview) i renderuje go jako canvas HTML5 (w praktyce: screenshot zachowujący CSS)
- canvas.toDataURL() zamienia canvas na obraz PNG w base64
- jsPDF.addImage() wstawia ten obraz do PDF w proporcjach A4
Polskie znaki są częścią renderowanego CSS z fontem Inter - pojawiają się w PDF jako piksele, nie jako tekst. Tradeoff: PDF jest większy (2-3 MB zamiast 100 KB), nie jest „selektowalny”. Za to zawsze wygląda identycznie jak w przeglądarce.
Prompt do skopiowania
<context>
Mam kompletną ofertę w live preview - dane, pozycje, przeliczenia,
kwota słownie. Teraz najważniejszy krok: wygenerowanie pliku PDF
z tej oferty. Chcę zachować polskie znaki (ą, ę, ś, ć, ł, ń, ó, ź, ż)
w pełnej czytelności, dlatego wybieram podejście html2canvas + jsPDF
(PDF jako obraz) zamiast natywnych fontów jsPDF.
</context>
<role>
Jesteś frontend developerem z doświadczeniem w generowaniu dokumentów
PDF z HTML, znasz ograniczenia jsPDF z polskimi znakami i typowe
problemy z html2canvas (fonty, wysokość, paginacja).
</role>
<intent>
Dodaj generowanie PDF:
1. Wczytaj biblioteki z CDN w tagu head:
- https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js
- https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js
2. Przycisk "Pobierz PDF" w panelu ustawień (duży, granatowy).
3. Logika generowania (async function):
- Pokaż loader na przycisku ("Generuję..." + spinner CSS)
- Zrób html2canvas elementu preview z opcjami:
scale: 2 (retina quality), useCORS: true, backgroundColor: "#ffffff"
- Utwórz jsPDF: format 'a4', unit 'mm', orientation 'portrait'
- Wymiary A4: 210mm × 297mm, marginesy 10mm wszystkie strony
- Przeskaluj canvas do szerokości 190mm (A4 minus marginesy)
- Paginacja: oblicz liczbę stron = Math.ceil(canvasHeight_mm / pageHeight_mm).
Dla każdej strony: addImage z offsetem Y = -i * pageHeight (ujemna pozycja
przesuwa obraz w górę). Między stronami: pdf.addPage().
NIE tnij canvasu na fragmenty - używaj pełnego obrazu z ujemnym
position Y dla kolejnych stron.
- Zapisz jako obraz PNG w PDF z kompresją 'FAST'
4. Nazwa pliku: "Oferta_NUMER_NAZWA-KLIENTA.pdf" gdzie:
- NUMER: numer oferty z myślnikami zamiast ukośników
- NAZWA-KLIENTA: sanitized (polskie znaki → ASCII, spacje → _,
usuń znaki specjalne, max 40 znaków)
5. Obsługa błędów: try/catch, w razie problemu alert
"Nie udało się wygenerować PDF: [szczegóły]" i ukryj loader.
</intent>
<scope>
- Zaktualizuj istniejący plik HTML
- Loader: CSS spinner (@keyframes rotate), wyłącz przycisk podczas
generowania, żeby nie wywołać podwójnie
- Przed wywołaniem html2canvas: upewnij się, że wszystkie fonty Inter
są załadowane (document.fonts.ready)
- Funkcja sanitize dla nazwy pliku - podmień polskie litery
(ą→a, ę→e, ś→s, ć→c, ł→l, ń→n, ó→o, ź→z, ż→z, + wielkie)
- Zachowaj całą dotychczasową funkcjonalność preview
</scope>
<precision>
Zwróć KOMPLETNY, zaktualizowany plik HTML z działającym generatorem
PDF. Funkcja generujPDF() z komentarzami wyjaśniającymi paginację
i dlaczego stosujemy podejście html2canvas zamiast natywnych fontów
jsPDF. Testowałem - po wygenerowaniu plik powinien być poprawnym PDF
z zachowanymi polskimi znakami.
</precision>
Co się zmieni
Klikasz „Pobierz PDF”. Przycisk zmienia się na „Generuję...” ze spinnerem. Dwie sekundy później - plik Oferta_2026-04-001_ABC-Consulting.pdf ląduje w pobranych. Otwierasz - wygląda dokładnie jak preview. Polskie znaki bez problemu. Logo na swoim miejscu. Klient może to od razu wydrukować lub podpisać elektronicznie.
Weryfikacja
- Przycisk „Pobierz PDF” pokazuje loader podczas generowania
- Plik PDF pobiera się z poprawną nazwą (numer oferty + nazwa klienta bez polskich znaków w nazwie pliku)
- Po otwarciu PDF - polskie znaki są prawidłowe
- Układ PDF zachowuje proporcje oferty z preview
- Długa oferta (20+ pozycji) dzieli się na dwie strony
Jeśli PDF wychodzi przycięty na dole:
„Po wygenerowaniu PDF ostatnie pozycje tabeli są przycięte. Sprawdź paginację - kiedy canvas jest wyższy niż 277mm, powinien być dzielony na kolejne strony PDF przez wywołanie addImage z ujemnym offsetem Y dla każdej kolejnej strony.”
Jeśli polskie znaki są krzakami w PDF:
„Polskie znaki wychodzą jako krzaki. Sprawdź: (1) Czy przed html2canvas wywołujesz document.fonts.ready. (2) Czy canvas ma scale: 2. (3) Czy w CSS font Inter jest poprawnie załadowany z Google Fonts.”
Co się tu uczysz
- html2canvas vs natywny jsPDF - dwa podejścia do generowania PDF. html2canvas daje WYSIWYG kosztem rozmiaru pliku. Natywne fonty jsPDF dają mały plik kosztem komplikacji z polskimi znakami. Dla ofert handlowych wybór jest oczywisty
- Paginacja canvas → PDF - canvas jest jednym obrazem, PDF ma wiele stron. Trzeba ciąć wysokość canvas na fragmenty odpowiadające stronom A4
- Fontfaces i document.fonts.ready - jeśli renderujesz do canvas zanim font się załaduje, dostaniesz fallback font (Arial). Czekanie na fonts.ready to 3 linijki, które ratują wygląd dokumentu
Krok 5: Logo firmy (Prompt #5)
Stan aplikacji: PDF działa, polskie znaki są, pozycje się dzielą na strony. W headerze wciąż jednak widnieje napis „LOGO” zamiast faktycznego logo.
Czas na ten krok: ~4 minuty
Logo firmy to szczegół, który odróżnia „szablon z internetu” od „dokumentu firmowego”. Będziesz go wrzucał raz - potem automatycznie pojawi się w każdej ofercie. Automatyczny resize do 400×200 pikseli, żeby wielkie firmowe logo 4K nie generowało PDF-ów po 15 MB.
Prompt do skopiowania
<context>
Generator ofert działa, PDF się pobiera. Brakuje mi możliwości
dodania logo firmy - bez tego oferta wygląda generycznie.
Chcę upload przez input file, automatyczny resize (żeby duże
pliki nie spowalniały PDF) i zapis w localStorage.
</context>
<role>
Jesteś frontend developerem z doświadczeniem w obsłudze plików
graficznych w przeglądarce (FileReader, Canvas API, base64).
</role>
<intent>
Dodaj upload i obsługę logo:
1. W panelu "Sprzedawca" (zwijanym): sekcja "Logo firmy"
- Input type="file" z accept="image/png, image/jpeg, image/svg+xml"
- Preview aktualnego logo (thumbnail 100px)
- Przycisk "Usuń logo" (jeśli jakieś jest)
2. Po wyborze pliku:
- FileReader.readAsDataURL → base64 string
- Narysuj obraz na ukrytym canvas, przeskaluj do max 400×200px
zachowując proporcje (aspect ratio)
- Wyeksportuj zresizowany canvas do base64 PNG
- Zapisz w localStorage pod kluczem "oferta_logo_base64"
3. W preview oferty: header zastępuje placeholder logo tagiem
<img src="[base64]"> - wyrównany do lewej, max-height: 60px.
4. Walidacja: max rozmiar pliku 2 MB przed resize
(alert jeśli większy), obsługa błędu FileReader.
5. Logo musi poprawnie trafiać do wygenerowanego PDF
(jest częścią preview, html2canvas je zrenderuje).
</intent>
<scope>
- Zaktualizuj istniejący plik HTML
- Jeśli logo jest SVG: wyświetl jako base64 data:image/svg+xml
(bez resize przez canvas - SVG skaluje się wektorowo)
- Jeśli logo jest PNG/JPG: zawsze przepuść przez canvas-resize
- Po usunięciu logo: localStorage.removeItem i powrót do placeholder
"LOGO" w headerze
- Zachowaj całą dotychczasową funkcjonalność
</scope>
<precision>
Zwróć KOMPLETNY, zaktualizowany plik HTML z obsługą logo.
Funkcja resizeLogoObraz() z komentarzem o zachowaniu proporcji.
Testowałem - logo musi widnieć w preview i w wygenerowanym PDF.
</precision>
Co się zmieni
Wrzucasz swoje logo firmowe (PNG, JPG albo SVG). Preview aktualizuje się natychmiast. Pobierasz PDF - logo się pojawia. Odświeżasz stronę - logo nie znika. Dodajesz nową ofertę - logo już tam widać. Raz ustawione, służy na zawsze.
Weryfikacja
- Wrzucenie pliku logo aktualizuje preview natychmiast
- Logo pojawia się w wygenerowanym PDF
- Odświeżenie strony zachowuje logo
- Przycisk „Usuń logo” wraca do placeholder „LOGO”
- Wrzucenie pliku > 2 MB wyświetla alert o za dużym rozmiarze
Co się tu uczysz
- FileReader.readAsDataURL - standardowy sposób wczytywania pliku z inputa do stringa base64. Działa dla obrazów, ale też dla dowolnych plików
- Canvas jako narzędzie resize - rysujesz obraz na canvas z nowymi wymiarami, eksportujesz canvas z powrotem do base64. Czysta JavaScript, bez bibliotek
- Zachowanie proporcji przy resize -
Math.min(400/width, 200/height)daje współczynnik skalowania, który zmieści obraz w docelowych wymiarach bez deformacji
Krok 6: Historia ofert + eksport JSON (Prompt #6)
Stan aplikacji: Generator działa w pełni. Po wysłaniu oferty klientowi - znika. Nie ma żadnej historii, żadnej bazy, żadnego sposobu, żeby do niej wrócić za tydzień.
Czas na ten krok: ~5 minut
Ostatni krok to transformacja narzędzia w system. Każda oferta trafia do localStorage z datą, numerem, nazwą klienta, kwotą brutto. Panel „Moje oferty” pokazuje całą historię. Duplikowanie pozwala użyć starej oferty jako szablonu do nowej. Eksport JSON daje backup i możliwość przenoszenia między komputerami.
Prompt do skopiowania
<context>
Generator jest w pełni funkcjonalny - layout, dane, pozycje, PDF, logo.
Ostatni element: zapisywanie ofert do późniejszego wykorzystania
(duplikowanie, edycja, przeglądanie historii). Chcę też eksport
i import wszystkich ofert do backupu.
</context>
<role>
Jesteś frontend developerem specjalizującym się w client-side
persistence i projektowaniu prostych mini-CRM.
</role>
<intent>
Dodaj historię ofert:
1. Przycisk "Zapisz ofertę" w panelu ustawień. Po kliknięciu:
- Zbierz cały state (sprzedawca, nabywca, meta, pozycje, logo-key)
- Dodaj timestamp zapisu i unikalne id (Date.now() + losowy string)
- Wrzuć do tablicy pod kluczem localStorage "oferta_historia"
(JSON.stringify całej tablicy)
- Toast/alert "Oferta zapisana"
2. Panel "Moje oferty" (zwijany, pod przyciskiem zapisu):
- Lista zapisanych ofert: numer oferty, data, nazwa klienta,
kwota brutto (z formatowaniem polskim)
- Sortowanie: najnowsze na górze
- Przy każdej pozycji 3 akcje: "Wczytaj", "Duplikuj", "Usuń"
3. Wczytaj: załaduj state do formularza i preview (nadpisz bieżący).
Poprzedź confirm'em "Zastąpić bieżącą ofertę?" jeśli są zmiany.
4. Duplikuj: skopiuj ofertę, wygeneruj nowy numer, wczytaj do formularza.
5. Usuń: confirm, usuń z tablicy, przepisz localStorage.
6. Pod listą - dwa przyciski tekstowe:
- "Eksportuj wszystkie oferty (.json)" - pobierz plik JSON
z całą zawartością "oferta_historia"
- "Importuj oferty (.json)" - input file, dopisuje do istniejących
(nie nadpisuje) po walidacji struktury.
</intent>
<scope>
- Zaktualizuj istniejący plik HTML
- Limit localStorage (5-10 MB) - ostrzeż użytkownika gdy zapisanych
ofert jest > 50 (toast: "Rozważ eksport do backupu")
- Import JSON: try/catch, walidacja że to tablica obiektów
z wymaganymi polami (id, sprzedawca, nabywca, pozycje)
- Nazwa pliku eksportu: "oferty_backup_YYYY-MM-DD.json"
- Toast'y: proste, CSS, znikają po 2 sekundach
</scope>
<precision>
Zwróć KOMPLETNY, FINALNY plik HTML generatora ofert. To jest ostateczna
wersja - zawiera layout, dane firmy/klienta, tabelę pozycji z VAT,
generator PDF, logo i historię z eksportem/importem. Pełny, gotowy
do produkcyjnego użycia kod.
</precision>
Co się zmieni
Zapisujesz ofertę - pojawia się w panelu „Moje oferty”. Zapisujesz drugą. Piątą. Setną. Zawsze widzisz historię. Klient z poprzedniego kwartału dzwoni z pytaniem - wczytujesz jego ofertę w 2 kliknięcia. Potrzebujesz copy-paste dla nowego klienta - duplikujesz i zmieniasz tylko to, co trzeba. Przenosisz się na nowy laptop - eksportujesz JSON, importujesz na drugim komputerze.
Weryfikacja
- Zapisanie oferty pojawia się w panelu „Moje oferty” z datą i kwotą
- Wczytanie oferty wypełnia wszystkie pola i preview
- Duplikowanie tworzy nową ofertę z nowym numerem; reszta zostaje identyczna
- Usunięcie wymaga potwierdzenia i natychmiast znika z listy
- Eksport pobiera plik JSON z datą w nazwie
- Import dopisuje (nie nadpisuje) istniejące oferty
Co się tu uczysz
- Mini-CRM w localStorage - tablica obiektów JSON + CRUD (create, read, update, delete). To samo podejście co w tablicy Kanban, tylko zamiast kart oferty
- Eksport pliku z przeglądarki -
Blob + URL.createObjectURL + ukryty link + click + removeChildto wzorzec pobierania dowolnego pliku generowanego po stronie klienta - Deduplikacja przy imporcie - sprawdzasz, czy id już istnieje w tablicy; jeśli tak, pomijasz. Chroni przed dublowaniem przy wielokrotnym imporcie tego samego backupu
Dlaczego to jest ważniejsze niż kolejny SaaS
Każdy miesiąc subskrypcji Proposify to 140 zł. Rok to 1 680 zł. 5 lat to 8 400 zł. A to jest JEDEN SaaS z 15-20, które subskrybuje typowy MŚP.
Policz swoje subskrypcje. Jeśli 3 z nich to „generator PDF / szablony / formularze” - zbudowałeś właśnie alternatywę dla jednej z nich. W 30 minut. Za 0 zł.
Nie chodzi o to, żeby wszystko zbudować samemu. Chodzi o to, żeby wiedzieć, KIEDY SaaS ma sens, a kiedy jest haraczem za problem, który rozwiązuje Claude za godzinę.
Efekt końcowy - co masz po 30 minutach
Otwórz działający generator ofert - to gotowa wersja, z którą możesz porównać swój wynik. Wgrałem ją na serwer, żeby każdy mógł przetestować przed budowaniem. Działa w 100% po stronie przeglądarki - wpisz swoje dane, dodaj pozycje, pobierz PDF.
Po przejściu 6 kroków masz plik oferta.html z:
| Funkcja | Status |
|---|---|
| Layout oferty w proporcjach A4 z live preview | Działa |
| Dane sprzedawcy w localStorage (wpisujesz raz) | Działa |
| Dane nabywcy, walidacja NIP z sumą kontrolną | Działa |
| Auto-numeracja ofert (YYYY/MM/NNN) | Działa |
| Dynamiczna tabela pozycji z kalkulacją VAT | Działa |
| Podsumowanie netto/VAT/brutto z rozbiciem stawek | Działa |
| Kwota słownie po polsku | Działa |
| Generator PDF z pełną obsługą polskich znaków | Działa |
| Paginacja PDF przy długich ofertach | Działa |
| Upload logo firmy z auto-resize | Działa |
| Historia ofert (zapis, wczytanie, duplikowanie) | Działa |
| Eksport/import JSON (backup i przenoszenie) | Działa |
| Działa offline (po pierwszym załadowaniu bibliotek) | Działa |
Jeden plik. Zero subskrypcji. Zero opłat. Otwierasz w przeglądarce i masz pełnoprawny generator ofert handlowych.
Czego się właśnie nauczyłeś
Cztery kompetencje, które pojawią się przy każdym kolejnym narzędziu biznesowym z tej serii:
1. Dokumenty PDF z polskimi znakami
Podejście html2canvas + jsPDF. Tradeoff: większy plik za pełną wierność wizualną. To samo rozwiązanie zadziała dla faktur, umów, raportów, certyfikatów. Natywne fonty jsPDF (jeśli kiedyś Ci się spodobają) to osobna ścieżka - dla CV, notatek, prostych list.
2. Formularze biznesowe z walidacją
Walidacja NIP z sumą kontrolną to ten sam wzorzec co PESEL, REGON, numer konta IBAN. Każdy polski identyfikator ma formalny algorytm sprawdzający. Claude zna je wszystkie - wystarczy o nie poprosić.
3. Mini-system persystencji w localStorage
Profil firmy (zapisany raz), oferta bieżąca (w zmiennej), historia (tablica w localStorage). Trzy warstwy stanu, każda z innym cyklem życia. Wzorzec przenośny na każdą aplikację, która potrzebuje więcej niż prosty „zapisz wszystko naraz”.
4. Biznesowa logika w czystym JS
Zaokrąglenia groszy, rozbicie VAT per stawka, kwota słownie. To nie są funkcje „zabawkowe”. Są wykorzystywane w produkcyjnych systemach B2B. Ty też możesz.
5 kierunków rozbudowy
Masz fundament. Każdy kierunek to 1-2 prompty do Claude:
1. Podpis elektroniczny (upload + umieszczenie w PDF)
„Dodaj sekcję 'Mój podpis'. Upload obrazu PNG podpisu z przezroczystym tłem, zapis w localStorage. W footerze PDF: tytuł 'Podpis sprzedawcy' + obrazek podpisu + imię i nazwisko pod spodem.”
2. Tryb oferty projektowej (wielostronicowej)
„Dodaj tryb 'Oferta projektowa' - zamiast tabeli pozycji, sekcje rozdziałów: Zrozumienie problemu, Rozwiązanie, Harmonogram, Cena, O nas. Każdy rozdział edytowalny inline. PDF paginowany rozdziałami.”
3. Szablony branżowe (gotowe presety)
„Dodaj dropdown 'Szablon branżowy' z 5 presetami: IT/software, marketing, consulting, szkolenia, hotelarstwo. Każdy preset wczytuje przykładowe pozycje i warunki dopasowane do branży.”
4. Email z ofertą (mailto: z załącznikiem)
„Dodaj przycisk 'Wyślij email' - otwiera klient pocztowy z wstępnie wypełnionym tematem ('Oferta nr X dla [nazwa klienta]') i treścią ('Dzień dobry, w załączeniu przesyłam ofertę...'). Adresat: email z pola Nabywca.”
5. Wersjonowanie ofert (historia zmian)
„Dodaj historię wersji oferty. Każdy zapis tej samej oferty nadpisuje poprzednią wersję, ale poprzednie wersje są dostępne do porównania. Różnice podświetlone w preview (co się zmieniło między v1 a v2).”
Najczęstsze problemy i rozwiązania
| Problem | Przyczyna | Rozwiązanie |
|---|---|---|
| Polskie znaki w PDF są krzakami | Font Inter się nie załadował przed html2canvas | „Przed wywołaniem html2canvas dodaj await document.fonts.ready„ |
| PDF jest przycięty na dole | Brak paginacji dla długich ofert | „Dodaj podział canvas na strony A4 jeśli wysokość > 277mm” |
| Nazwa pliku PDF ma krzaki zamiast polskich liter | Brak sanitizacji | „W nazwie pliku zamień polskie znaki na ASCII (ą→a, ę→e itd.)” |
| Dane firmy znikają po odświeżeniu | Brak zapisu do localStorage | „Po każdej zmianie inputu firmy wywołaj localStorage.setItem” |
| Walidacja NIP przepuszcza błędne numery | Uproszczony algorytm | „Sprawdź wagi [6,5,7,2,3,4,5,6,7], modulo 11, obsługa przypadku gdy reszta = 10” |
| Logo jest rozciągnięte w PDF | Brak zachowania proporcji | „Użyj min(400/szerokość, 200/wysokość) jako współczynnika skalowania” |
| Tabela pozycji ma rozjechane kolumny | Brak table-layout: fixed | „Dodaj table-layout: fixed do CSS tabeli pozycji„ |
Inny problem? Wklej treść błędu z konsoli (F12 → Console) do Claude i opisz, co się dzieje.
Dlaczego to nie jest tylko kolejny tutorial
Każda gotowa apka do ofert kosztuje pieniądze - 40 zł, 140 zł, 300 zł miesięcznie. Żadna z nich nie oferuje czegoś, czego nie da się zbudować. Wszystkie robią to samo: szablon z polami do wypełnienia i przycisk „Pobierz PDF”.
Zbudowałeś właśnie to samo narzędzie. Za 30 minut pracy i 0 zł miesięcznie. Przy okazji nauczyłeś się wzorca, który powtórzysz przy każdym kolejnym narzędziu biznesowym - fakturach uproszczonych, umowach szablonowych, zaproszeniach, certyfikatach, voucherach.
W tutorialu tablicy Kanban zbudowałeś narzędzie dla siebie. Dzisiaj zbudowałeś coś, czego używasz w pracy z klientami. To jest Poziom 2 serii - nie trudność kodu, tylko kontekst zastosowania.
Co wyniesie Twój księgowy
Jeśli masz księgowego, pokaż mu PDF z tego generatora. Powie Ci, że 90% oferty jest zgodne z wymogami formalnymi polskich dokumentów handlowych. Pozostałe 10% to specyficzne wymogi branżowe (np. oświadczenie o pochodzeniu towaru dla eksportu, klauzule dla usług objętych odwrotnym obciążeniem) - dodasz je promptem w 5 minut.
Twoi konkurenci używają Proposify, bo tak się nauczyli. Za 3 lata używaj narzędzi, które sam zbudowałeś - a wtedy SaaS będzie robił to, czego TY NIE CHCESZ budować, a nie to, co mógłbyś zbudować w pół godziny.
Co dalej
| Następny krok | Co zyskasz |
|---|---|
| Zbuduj kalkulator B2B vs UoP 2026 | Zobacz, ile tracisz na złej formie zatrudnienia - różnica roczna bywa większa niż koszt samochodu |
| Framework CRISP - inżynieria promptów | Nauczysz się pisać prompty, które dają działający kod za pierwszym razem |
| Vibe coding z Claude Code | Przejdziesz od jednego pliku HTML do realnych aplikacji z bazą danych |
| Kurs Claude AI - 44 lekcje, darmowy | 7 modułów od zera do zaawansowanego - CEO, marketing, sprzedaż, programowanie |
Jeśli jesteś nowy na blogu - Zacznij Tutaj. 3 ścieżki nauki dopasowane do Twojej roli.
Aktualność: Artykuł opublikowany 20 kwietnia 2026. Ceny konkurencji (Proposify, PandaDoc, Fakturownia) sprawdzone 18 kwietnia 2026 - zweryfikuj przed cytowaniem u klienta. Kod używa bibliotek html2canvas 1.4.1 i jsPDF 2.5.1 (LTS, brak planowanych breaking changes do 2027).
Ten artykuł to część serii „Zbuduj to z AI”. Zobacz też: Aplikacja pogodowa | Timer Pomodoro | Tablica Kanban | Tracker nawyków
Autor
Mateusz Czech // Czechu
AI Senior Digital Product Specialist w grupie Górskie Resorty. Projektuję i wdrażam aplikacje oparte na AI dla sprzedaży i obsługi klienta w hotelach. Wcześniej w SentiOne współtworzyłem voicebota InfoNina dla Alior Banku (Celent Model Bank Awards 2022). Piszę o wdrożeniach AI na czechu.blog. Prywatnie gram w tenisa i słucham polskiego rapu.
Tagi
Powiązane artykuły
Co jeszcze warto przeczytać
Kalkulator B2B vs UoP 2026 - zbuduj własny z Claude AI w 30 minut
Zbuduj kalkulator porównujący UoP, B2B ryczałt, liniowy i skalę. Stawki kwiecień 2026, Chart.js, scenariusze what-if, eksport PDF. Jeden plik HTML, zero rejestracji.
Jak zbudować tablicę Kanban z AI - własne mini Trello w 30 minut
Budujemy tablicę Kanban (mini Trello) z Claude AI krok po kroku. 6 promptów CRISP, drag & drop, edycja kart, localStorage. Jeden plik HTML.
Jak zbudować timer Pomodoro z AI - od zera do narzędzia produktywności w 20 minut
Budujemy timer Pomodoro z Claude AI krok po kroku. 5 promptów CRISP, alarm dźwiękowy, cykle 25/5, wykres produktywności. Jeden plik HTML, zero kodu.
Newsletter Strategic AI Implementation
Co tydzień jeden framework, jedno case study, zero spamu
Dołącz do listy. Dostajesz to, czego nie wrzucam na bloga: kulisy moich wdrożeń, sprawdzone prompty, błędy do uniknięcia. Wypisujesz się jednym kliknięciem.