const VAT_OPTIONS = [ { value: "23", label: "23%" }, { value: "8", label: "8%" }, { value: "5", label: "5%" }, { value: "0", label: "0% (ZW)" }, { value: "ZW", label: "ZW - zwolnione" }, { value: "NP", label: "NP - poza zakresem" }, ]; const VAT_RATE_VALUES = { "23": 0.23, "8": 0.08, "5": 0.05, "0": 0, ZW: 0, NP: 0, }; const setupSection = document.getElementById("setup-section"); const loginSection = document.getElementById("login-section"); const appSection = document.getElementById("app-section"); const setupForm = document.getElementById("setup-form"); const loginForm = document.getElementById("login-form"); const invoiceForm = document.getElementById("invoice-form"); const businessForm = document.getElementById("business-form"); const setupFeedback = document.getElementById("setup-feedback"); const loginFeedback = document.getElementById("login-feedback"); const businessFeedback = document.getElementById("business-feedback"); const businessDisplay = document.getElementById("business-display"); const toggleBusinessFormButton = document.getElementById("toggle-business-form"); const cancelBusinessUpdateButton = document.getElementById("cancel-business-update"); const itemsBody = document.getElementById("items-body"); const addItemButton = document.getElementById("add-item-button"); const totalNetLabel = document.getElementById("total-net"); const totalVatLabel = document.getElementById("total-vat"); const totalGrossLabel = document.getElementById("total-gross"); const rateSummaryContainer = document.getElementById("rate-summary"); const exemptionNoteWrapper = document.getElementById("exemption-note-wrapper"); const exemptionNoteInput = document.getElementById("exemption-note"); const invoiceResult = document.getElementById("invoice-result"); const invoiceOutput = document.getElementById("invoice-output"); const downloadButton = document.getElementById("download-button"); const logoutButton = document.getElementById("logout-button"); let authToken = sessionStorage.getItem("invoiceAuthToken") || null; let currentBusiness = null; let lastInvoice = null; let pdfFontPromise = null; let pdfFontBase64 = null; function setState(state) { setupSection.classList.add("hidden"); loginSection.classList.add("hidden"); appSection.classList.add("hidden"); if (state === "setup") { setupSection.classList.remove("hidden"); } else if (state === "login") { loginSection.classList.remove("hidden"); } else if (state === "app") { appSection.classList.remove("hidden"); } } function clearFeedback(element) { element.textContent = ""; element.classList.remove("error", "success"); } function showFeedback(element, message, type = "error") { element.textContent = message; element.classList.remove("error", "success"); if (type) { element.classList.add(type); } } function parseNumber(value) { if (typeof value === "number") { return Number.isFinite(value) ? value : 0; } if (!value) { return 0; } const normalized = value.toString().replace(",", "."); const parsed = Number.parseFloat(normalized); return Number.isFinite(parsed) ? parsed : 0; } function formatCurrency(value) { const number = parseNumber(value); return `${number.toFixed(2)} PLN`; } function vatLabelFromCode(code) { if (code === "ZW" || code === "0") { return "ZW"; } if (code === "NP") { return "NP"; } return `${code}%`; } function requiresExemption(code) { return code === "ZW" || code === "0"; } async function apiRequest(path, { method = "GET", body, headers = {} } = {}, requireAuth = false) { const options = { method, headers: { "Content-Type": "application/json", ...headers, }, }; if (body !== undefined) { options.body = JSON.stringify(body); } if (requireAuth) { if (!authToken) { throw new Error("Brak tokenu autoryzacyjnego."); } options.headers.Authorization = `Bearer ${authToken}`; } const response = await fetch(path, options); const isJson = response.headers.get("content-type")?.includes("application/json"); const data = isJson ? await response.json() : {}; if (response.status === 401) { authToken = null; sessionStorage.removeItem("invoiceAuthToken"); setState("login"); throw new Error(data.error || "Sesja wygasla. Zaloguj sie ponownie."); } if (!response.ok) { throw new Error(data.error || "Wystapil blad podczas komunikacji z serwerem."); } return data; } function renderBusinessDisplay(business) { if (!business) { businessDisplay.textContent = "Brak zapisanych danych firmy."; return; } businessDisplay.innerHTML = `

${business.company_name}

${business.owner_name}

${business.address_line}

${business.postal_code} ${business.city}

NIP: ${business.tax_id}

Konto: ${business.bank_account}

`; } function fillBusinessForm(business) { if (!business) { return; } businessForm.elements.company_name.value = business.company_name || ""; businessForm.elements.owner_name.value = business.owner_name || ""; businessForm.elements.address_line.value = business.address_line || ""; businessForm.elements.postal_code.value = business.postal_code || ""; businessForm.elements.city.value = business.city || ""; businessForm.elements.tax_id.value = business.tax_id || ""; businessForm.elements.bank_account.value = business.bank_account || ""; } function vatSelectElement(initialValue = "23") { const select = document.createElement("select"); select.className = "item-vat"; VAT_OPTIONS.forEach((option) => { const element = document.createElement("option"); element.value = option.value; element.textContent = option.label; select.appendChild(element); }); select.value = VAT_OPTIONS.some((option) => option.value === initialValue) ? initialValue : "23"; return select; } function createItemRow(initialValues = {}) { const row = document.createElement("tr"); const nameCell = document.createElement("td"); const nameInput = document.createElement("input"); nameInput.type = "text"; nameInput.className = "item-name"; nameInput.placeholder = "Nazwa towaru lub uslugi"; if (initialValues.name) { nameInput.value = initialValues.name; } nameCell.appendChild(nameInput); const quantityCell = document.createElement("td"); const quantityInput = document.createElement("input"); quantityInput.type = "number"; quantityInput.className = "item-quantity"; quantityInput.min = "0.01"; quantityInput.step = "0.01"; quantityInput.value = initialValues.quantity ?? "1"; quantityCell.appendChild(quantityInput); const unitGrossCell = document.createElement("td"); const unitGrossInput = document.createElement("input"); unitGrossInput.type = "number"; unitGrossInput.className = "item-gross"; unitGrossInput.min = "0.01"; unitGrossInput.step = "0.01"; unitGrossInput.placeholder = "Brutto"; if (initialValues.unit_price_gross) { unitGrossInput.value = initialValues.unit_price_gross; } unitGrossCell.appendChild(unitGrossInput); const vatCell = document.createElement("td"); const vatSelect = vatSelectElement(initialValues.vat_code); vatCell.appendChild(vatSelect); const totalCell = document.createElement("td"); totalCell.className = "item-total"; totalCell.textContent = "0.00 PLN"; const actionsCell = document.createElement("td"); const removeButton = document.createElement("button"); removeButton.type = "button"; removeButton.className = "remove-item"; removeButton.textContent = "Usun"; actionsCell.appendChild(removeButton); row.appendChild(nameCell); row.appendChild(quantityCell); row.appendChild(unitGrossCell); row.appendChild(vatCell); row.appendChild(totalCell); row.appendChild(actionsCell); const handleChange = () => updateTotals(); nameInput.addEventListener("input", handleChange); quantityInput.addEventListener("input", handleChange); unitGrossInput.addEventListener("input", handleChange); vatSelect.addEventListener("change", handleChange); removeButton.addEventListener("click", () => { if (itemsBody.children.length === 1) { nameInput.value = ""; quantityInput.value = "1"; unitGrossInput.value = ""; vatSelect.value = "23"; updateTotals(); return; } row.remove(); updateTotals(); }); itemsBody.appendChild(row); updateTotals(); } function calculateRowTotals(row) { const name = row.querySelector(".item-name")?.value.trim() ?? ""; const quantity = parseNumber(row.querySelector(".item-quantity")?.value); const unitGross = parseNumber(row.querySelector(".item-gross")?.value); const vatCode = row.querySelector(".item-vat")?.value ?? "23"; const rate = VAT_RATE_VALUES[vatCode] ?? 0; const hasValues = name || quantity > 0 || unitGross > 0; if (!hasValues) { return { valid: false, vatCode, vatLabel: vatLabelFromCode(vatCode), requiresExemption: requiresExemption(vatCode), quantity, unitGross, unitNet: 0, netTotal: 0, vatAmount: 0, grossTotal: 0, }; } if (quantity <= 0 || unitGross <= 0) { return { valid: false, vatCode, vatLabel: vatLabelFromCode(vatCode), requiresExemption: requiresExemption(vatCode), quantity, unitGross, unitNet: 0, netTotal: 0, vatAmount: 0, grossTotal: quantity * unitGross, }; } const grossTotal = quantity * unitGross; const netTotal = rate > 0 ? grossTotal / (1 + rate) : grossTotal; const vatAmount = grossTotal - netTotal; const unitNet = netTotal / quantity; return { valid: true, vatCode, vatLabel: vatLabelFromCode(vatCode), requiresExemption: requiresExemption(vatCode), quantity, unitGross, unitNet, netTotal, vatAmount, grossTotal, }; } function updateTotals() { let totalNet = 0; let totalVat = 0; let totalGross = 0; const summary = new Map(); let exemptionNeeded = false; const rows = Array.from(itemsBody.querySelectorAll("tr")); rows.forEach((row) => { const totals = calculateRowTotals(row); if (totals.requiresExemption) { exemptionNeeded = true; } const totalCell = row.querySelector(".item-total"); totalCell.textContent = formatCurrency(totals.grossTotal); if (!totals.valid) { return; } totalNet += totals.netTotal; totalVat += totals.vatAmount; totalGross += totals.grossTotal; const existing = summary.get(totals.vatLabel) || { net: 0, vat: 0, gross: 0 }; existing.net += totals.netTotal; existing.vat += totals.vatAmount; existing.gross += totals.grossTotal; summary.set(totals.vatLabel, existing); }); totalNetLabel.textContent = `Suma netto: ${totalNet.toFixed(2)} PLN`; totalVatLabel.textContent = `Kwota VAT: ${totalVat.toFixed(2)} PLN`; totalGrossLabel.textContent = `Suma brutto: ${totalGross.toFixed(2)} PLN`; renderRateSummary(summary); if (exemptionNeeded) { exemptionNoteWrapper.classList.remove("hidden"); } else { exemptionNoteWrapper.classList.add("hidden"); exemptionNoteInput.value = ""; } } function renderRateSummary(summary) { if (!summary || summary.size === 0) { rateSummaryContainer.innerHTML = ""; return; } const entries = Array.from(summary.entries()).sort(([a], [b]) => a.localeCompare(b)); const markup = entries .map( ([label, totals]) => `
${label} Netto: ${totals.net.toFixed(2)} PLN VAT: ${totals.vat.toFixed(2)} PLN Brutto: ${totals.gross.toFixed(2)} PLN
` ) .join(""); rateSummaryContainer.innerHTML = `

Podsumowanie stawek

${markup}`; } function collectInvoicePayload() { const items = []; const rows = Array.from(itemsBody.querySelectorAll("tr")); rows.forEach((row) => { const name = row.querySelector(".item-name")?.value.trim() ?? ""; const quantity = parseNumber(row.querySelector(".item-quantity")?.value); const unitGross = parseNumber(row.querySelector(".item-gross")?.value); const vatCode = row.querySelector(".item-vat")?.value ?? "23"; const hasValues = name || quantity > 0 || unitGross > 0; if (!hasValues) { return; } if (!name) { throw new Error("Kazda pozycja musi miec nazwe."); } if (quantity <= 0) { throw new Error("Ilosc musi byc wieksza od zera."); } if (unitGross <= 0) { throw new Error("Cena brutto musi byc wieksza od zera."); } items.push({ name, quantity: quantity.toFixed(2), unit_price_gross: unitGross.toFixed(2), vat_code: vatCode, }); }); if (items.length === 0) { throw new Error("Dodaj przynajmniej jedna pozycje."); } const saleDate = invoiceForm.elements.saleDate.value || null; const exemptionNote = exemptionNoteInput.value.trim(); const requiresExemptionNote = items.some((item) => item.vat_code === "ZW" || item.vat_code === "0"); if (requiresExemptionNote && !exemptionNote) { throw new Error("Podaj podstawe prawna zwolnienia dla pozycji rozliczanych jako ZW."); } const client = { name: (invoiceForm.elements.clientName.value || "").trim(), tax_id: (invoiceForm.elements.clientTaxId.value || "").trim(), address_line: (invoiceForm.elements.clientAddress.value || "").trim(), postal_code: (invoiceForm.elements.clientPostalCode.value || "").trim(), city: (invoiceForm.elements.clientCity.value || "").trim(), }; return { sale_date: saleDate, client, items, exemption_note: exemptionNote, }; } function renderInvoicePreview(invoice) { if (!invoice || !currentBusiness) { invoiceOutput.innerHTML = "

Brak danych faktury.

"; return; } const client = invoice.client || {}; const hasClientData = client.name || client.address_line || client.postal_code || client.city || client.tax_id; const itemsRows = invoice.items .map( (item) => ` ${item.name} ${parseNumber(item.quantity).toFixed(2)} ${formatCurrency(item.unit_price_net)} ${formatCurrency(item.net_total)} ${item.vat_label} ${formatCurrency(item.vat_amount)} ${formatCurrency(item.gross_total)} ` ) .join(""); const summaryRows = (invoice.summary || []) .map( (entry) => `
${entry.vat_label} Netto: ${formatCurrency(entry.net_total)} VAT: ${formatCurrency(entry.vat_total)} Brutto: ${formatCurrency(entry.gross_total)}
` ) .join(""); invoiceOutput.innerHTML = `
Numer: ${invoice.invoice_id} Data wystawienia: ${invoice.issued_at} Data sprzedazy: ${invoice.sale_date}

Nabywca

${ hasClientData ? `

${client.name || "---"}

${client.address_line || "---"}

${client.postal_code || ""} ${client.city || ""}

${client.tax_id ? `NIP: ${client.tax_id}` : ""}

` : "

Brak danych nabywcy.

" }

Sprzedawca

${currentBusiness.company_name}

${currentBusiness.owner_name}

${currentBusiness.address_line}

${currentBusiness.postal_code} ${currentBusiness.city}

NIP: ${currentBusiness.tax_id}

Konto: ${currentBusiness.bank_account}

${itemsRows}
Nazwa Ilość Cena jedn. netto Wartość netto (pozycja) Stawka VAT Kwota VAT (pozycja) Wartość brutto

Podsumowanie stawek

${summaryRows}
Netto: ${formatCurrency(invoice.totals.net)} VAT: ${formatCurrency(invoice.totals.vat)} Brutto: ${formatCurrency(invoice.totals.gross)}
${ invoice.exemption_note ? `
Podstawa prawna zwolnienia: ${invoice.exemption_note}
` : "" } `; } function drawPartyBox(doc, title, lines, x, y, width) { const lineHeight = 5; const wrappedLines = lines.flatMap((line) => doc.splitTextToSize(line, width)); const boxHeight = wrappedLines.length * lineHeight + 14; doc.roundedRect(x - 4, y - 8, width + 8, boxHeight, 2, 2); doc.setFontSize(11); doc.text(title, x, y); doc.setFontSize(10); let cursor = y + 5; wrappedLines.forEach((line) => { doc.text(line, x, cursor); cursor += lineHeight; }); return y - 8 + boxHeight; } function arrayBufferToBase64(buffer) { const bytes = new Uint8Array(buffer); const chunkSize = 0x8000; let binary = ""; for (let offset = 0; offset < bytes.length; offset += chunkSize) { const chunk = bytes.subarray(offset, Math.min(offset + chunkSize, bytes.length)); binary += String.fromCharCode.apply(null, chunk); } return btoa(binary); } const PDF_FONT_FILE = "Roboto-VariableFont_wdth,wght.ttf"; const PDF_FONT_NAME = "RobotoPolish"; async function ensurePdfFont() { if (pdfFontPromise) { return pdfFontPromise; } if (!window.jspdf || !window.jspdf.jsPDF) { throw new Error("Biblioteka jsPDF nie zostala zaladowana."); } const { jsPDF } = window.jspdf; const loadBase64 = async () => { if (typeof window !== "undefined" && window.PDF_FONT_BASE64) { return window.PDF_FONT_BASE64; } const response = await fetch(`/${encodeURIComponent(PDF_FONT_FILE)}`); if (!response.ok) { throw new Error(`Nie udalo sie pobrac czcionki Roboto (status ${response.status}).`); } const buffer = await response.arrayBuffer(); return arrayBufferToBase64(buffer); }; pdfFontPromise = loadBase64().then((data) => { pdfFontBase64 = data; return data; }); return pdfFontPromise; } async function generatePdf(business, invoice) { if (!window.jspdf || !window.jspdf.jsPDF) { alert("Biblioteka jsPDF nie zostala zaladowana. Sprawdz polaczenie z internetem."); return; } let fontBase64; try { fontBase64 = await ensurePdfFont(); } catch (error) { alert(error.message || "Nie udalo sie przygotowac czcionki do PDF."); return; } const { jsPDF } = window.jspdf; const doc = new jsPDF({ orientation: "portrait", unit: "mm", format: "a4" }); const marginX = 18; let cursorY = 20; if (!doc.getFontList()[PDF_FONT_NAME]) { const embeddedFont = pdfFontBase64 || fontBase64; doc.addFileToVFS(PDF_FONT_FILE, embeddedFont); doc.addFont(PDF_FONT_FILE, PDF_FONT_NAME, "normal"); } doc.setFont(PDF_FONT_NAME, "normal"); doc.setFontSize(16); doc.text(`Faktura ${invoice.invoice_id}`, marginX, cursorY); doc.setFontSize(10); doc.text(`Data wystawienia: ${invoice.issued_at}`, marginX, cursorY + 6); doc.text(`Data sprzedaży: ${invoice.sale_date}`, marginX, cursorY + 12); cursorY += 22; const columnWidth = 85; const sellerX = marginX + columnWidth + 12; const clientLines = invoice.client && (invoice.client.name || invoice.client.address_line || invoice.client.city || invoice.client.tax_id) ? [ invoice.client.name || "---", invoice.client.address_line || "", `${(invoice.client.postal_code || "").trim()} ${(invoice.client.city || "").trim()}`.trim(), invoice.client.tax_id ? `NIP: ${invoice.client.tax_id}` : "", ].filter((line) => line && line.trim()) : ["Brak danych nabywcy"]; const sellerLines = [ business.company_name, business.owner_name, business.address_line, `${business.postal_code} ${business.city}`.trim(), `NIP: ${business.tax_id}`, `Konto: ${business.bank_account}`, ]; const buyerBottom = drawPartyBox(doc, "NABYWCA", clientLines, marginX, cursorY, columnWidth); const sellerBottom = drawPartyBox(doc, "SPRZEDAWCA", sellerLines, sellerX, cursorY, columnWidth); cursorY = Math.max(buyerBottom, sellerBottom) + 12; const tableColumns = [ { key: "name", label: "Nazwa", width: 52 }, { key: "quantity", label: "Ilość", width: 16 }, { key: "unitNet", label: "Cena jedn. netto", width: 24 }, { key: "netTotal", label: "Wartość netto", width: 24 }, { key: "vatLabel", label: "Stawka VAT", width: 15 }, { key: "vatAmount", label: "Kwota VAT", width: 22 }, { key: "grossTotal", label: "Wartość brutto", width: 21 }, ]; const tableWidth = tableColumns.reduce((sum, col) => sum + col.width, 0); const lineHeight = 5; const headerLineHeight = 4.2; tableColumns.forEach((column) => { column.headerLines = doc.splitTextToSize(column.label, column.width - 4); }); const headerHeight = Math.max(...tableColumns.map((column) => column.headerLines.length * headerLineHeight + 3)); doc.setFillColor(241, 243, 247); doc.rect(marginX, cursorY, tableWidth, headerHeight, "F"); doc.rect(marginX, cursorY, tableWidth, headerHeight); let offsetX = marginX; doc.setFontSize(10); tableColumns.forEach((column) => { doc.rect(offsetX, cursorY, column.width, headerHeight); column.headerLines.forEach((line, index) => { const textY = cursorY + 4 + index * headerLineHeight; doc.text((line || "").trim(), offsetX + 2, textY); }); offsetX += column.width; }); cursorY += headerHeight; invoice.items.forEach((item) => { const quantity = parseNumber(item.quantity).toFixed(2); const unitNet = formatCurrency(item.unit_price_net); const netTotal = formatCurrency(item.net_total); const vatAmount = formatCurrency(item.vat_amount); const grossTotal = formatCurrency(item.gross_total); const wrapText = (text, width) => doc .splitTextToSize(text ?? "", width) .map((line) => line.trim()); const columnData = tableColumns.map((column) => { switch (column.key) { case "name": return wrapText(item.name, column.width - 4); case "quantity": return [quantity]; case "unitNet": return [unitNet]; case "netTotal": return [netTotal]; case "vatLabel": return [item.vat_label]; case "vatAmount": return [vatAmount]; case "grossTotal": return [grossTotal]; default: return [""]; } }); const rowHeight = Math.max(...columnData.map((data) => data.length)) * lineHeight + 2; offsetX = marginX; tableColumns.forEach((column, index) => { doc.rect(offsetX, cursorY, column.width, rowHeight); const lines = columnData[index]; lines.forEach((line, lineIndex) => { const textY = cursorY + (lineIndex + 1) * lineHeight; const content = (line || "").trim(); doc.text(content, offsetX + 2, textY); }); offsetX += column.width; }); cursorY += rowHeight; }); cursorY += 10; doc.setFontSize(11); doc.text("Podsumowanie stawek:", marginX, cursorY); cursorY += 6; const summaryEntries = Array.isArray(invoice.summary) ? invoice.summary : []; summaryEntries.forEach((entry) => { const summaryLine = `${entry.vat_label} – Netto: ${formatCurrency(entry.net_total)} / VAT: ${formatCurrency(entry.vat_total)} / Brutto: ${formatCurrency(entry.gross_total)}`; const wrapped = doc.splitTextToSize(summaryLine, 170); wrapped.forEach((line) => { doc.text((line || "").trim(), marginX, cursorY); cursorY += lineHeight; }); }); cursorY += 6; doc.setFontSize(12); doc.text(`Suma netto: ${formatCurrency(invoice.totals.net)}`, marginX, cursorY); doc.text(`Suma VAT: ${formatCurrency(invoice.totals.vat)}`, marginX, cursorY + 6); doc.text(`Suma brutto: ${formatCurrency(invoice.totals.gross)}`, marginX, cursorY + 12); cursorY += 20; if (invoice.exemption_note) { doc.setFontSize(10); const noteLines = doc.splitTextToSize(`Podstawa prawna zwolnienia: ${invoice.exemption_note}`, 170); doc.text(noteLines, marginX, cursorY); } doc.save(`${invoice.invoice_id}.pdf`); } async function loadBusinessData() { const data = await apiRequest("/api/business", {}, true); currentBusiness = data.business; renderBusinessDisplay(currentBusiness); fillBusinessForm(currentBusiness); } function resetInvoiceForm() { invoiceForm.reset(); exemptionNoteInput.value = ""; exemptionNoteWrapper.classList.add("hidden"); itemsBody.innerHTML = ""; createItemRow(); const today = new Date().toISOString().slice(0, 10); invoiceForm.elements.saleDate.value = today; } async function bootstrapApp() { try { await loadBusinessData(); setState("app"); } catch (error) { console.error(error); authToken = null; sessionStorage.removeItem("invoiceAuthToken"); showFeedback(loginFeedback, error.message || "Nie udalo sie pobrac danych firmy."); setState("login"); } } async function initialize() { resetInvoiceForm(); try { const status = await apiRequest("/api/status"); if (!status.configured) { setState("setup"); return; } if (authToken) { await bootstrapApp(); } else { setState("login"); } } catch (error) { console.error(error); setState("setup"); showFeedback(setupFeedback, "Nie udalo sie nawiazac polaczenia z serwerem."); } } setupForm.addEventListener("submit", async (event) => { event.preventDefault(); clearFeedback(setupFeedback); const formData = new FormData(setupForm); const password = formData.get("password")?.toString() ?? ""; const confirmPassword = formData.get("confirm_password")?.toString() ?? ""; if (password !== confirmPassword) { showFeedback(setupFeedback, "Hasla musza byc identyczne."); return; } if (password.trim().length < 4) { showFeedback(setupFeedback, "Haslo musi miec co najmniej 4 znaki."); return; } const payload = { company_name: formData.get("company_name")?.toString().trim(), owner_name: formData.get("owner_name")?.toString().trim(), address_line: formData.get("address_line")?.toString().trim(), postal_code: formData.get("postal_code")?.toString().trim(), city: formData.get("city")?.toString().trim(), tax_id: formData.get("tax_id")?.toString().trim(), bank_account: formData.get("bank_account")?.toString().trim(), password, }; try { await apiRequest("/api/setup", { method: "POST", body: payload }); showFeedback(setupFeedback, "Dane zapisane. Mozesz sie zalogowac.", "success"); setTimeout(() => { setState("login"); clearFeedback(setupFeedback); setupForm.reset(); }, 1500); } catch (error) { showFeedback(setupFeedback, error.message || "Nie udalo sie zapisac danych."); } }); loginForm.addEventListener("submit", async (event) => { event.preventDefault(); clearFeedback(loginFeedback); const password = loginForm.elements.password.value; if (!password) { showFeedback(loginFeedback, "Podaj haslo."); return; } try { const response = await apiRequest("/api/login", { method: "POST", body: { password } }); authToken = response.token; sessionStorage.setItem("invoiceAuthToken", authToken); loginForm.reset(); await bootstrapApp(); } catch (error) { showFeedback(loginFeedback, error.message || "Logowanie nie powiodlo sie."); } }); toggleBusinessFormButton.addEventListener("click", () => { const isHidden = businessForm.classList.contains("hidden"); if (isHidden) { fillBusinessForm(currentBusiness); businessForm.classList.remove("hidden"); toggleBusinessFormButton.textContent = "Ukryj formularz"; } else { businessForm.classList.add("hidden"); toggleBusinessFormButton.textContent = "Edytuj dane"; clearFeedback(businessFeedback); } }); cancelBusinessUpdateButton.addEventListener("click", () => { businessForm.classList.add("hidden"); toggleBusinessFormButton.textContent = "Edytuj dane"; clearFeedback(businessFeedback); }); businessForm.addEventListener("submit", async (event) => { event.preventDefault(); clearFeedback(businessFeedback); const formData = new FormData(businessForm); const payload = { company_name: formData.get("company_name")?.toString().trim(), owner_name: formData.get("owner_name")?.toString().trim(), address_line: formData.get("address_line")?.toString().trim(), postal_code: formData.get("postal_code")?.toString().trim(), city: formData.get("city")?.toString().trim(), tax_id: formData.get("tax_id")?.toString().trim(), bank_account: formData.get("bank_account")?.toString().trim(), }; try { const data = await apiRequest("/api/business", { method: "PUT", body: payload }, true); currentBusiness = data.business; renderBusinessDisplay(currentBusiness); showFeedback(businessFeedback, "Dane sprzedawcy zaktualizowane.", "success"); setTimeout(() => clearFeedback(businessFeedback), 2000); } catch (error) { showFeedback(businessFeedback, error.message || "Nie udalo sie zaktualizowac danych."); } }); invoiceForm.addEventListener("submit", async (event) => { event.preventDefault(); try { const payload = collectInvoicePayload(); const response = await apiRequest("/api/invoices", { method: "POST", body: payload }, true); lastInvoice = response.invoice; renderInvoicePreview(lastInvoice); invoiceResult.classList.remove("hidden"); resetInvoiceForm(); } catch (error) { alert(error.message || "Nie udalo sie wygenerowac faktury."); } }); addItemButton.addEventListener("click", () => { createItemRow(); }); downloadButton.addEventListener("click", async () => { if (!lastInvoice || !currentBusiness) { alert("Brak faktury do pobrania. Wygeneruj ja najpierw."); return; } await generatePdf(currentBusiness, lastInvoice); }); logoutButton.addEventListener("click", () => { authToken = null; sessionStorage.removeItem("invoiceAuthToken"); lastInvoice = null; currentBusiness = null; invoiceResult.classList.add("hidden"); setState("login"); }); initialize().catch((error) => { console.error(error); showFeedback(setupFeedback, "Nie udalo sie uruchomic aplikacji."); });