From 474d2a7a7159051a3eace3f58c0867f1c1dbaff9 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 8 Jun 2026 16:41:27 +0000 Subject: [PATCH] Add Save / Validate / Download invoice action row at bottom of form MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces single generate button with a 20/20/60 flex row: - Save (20%): calls saveStorage(), shows brief ✓ confirmation - Validate (20%): checks invoice no, sender name, charge-to, line items; shows inline message - Download Invoice (60%): existing PDF generation https://claude.ai/code/session_01MkM7p8Us3L8YAfLKGA13NS --- app/config.yml | 43 +++++++++++++++++++++++++++++--- app/index.html | 67 ++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 96 insertions(+), 14 deletions(-) diff --git a/app/config.yml b/app/config.yml index fc98d12..fe982f7 100644 --- a/app/config.yml +++ b/app/config.yml @@ -457,11 +457,46 @@ translations: de: Zu zahlen fr: À payer "no": Å betale + save: + en: Save + de: Speichern + fr: Enregistrer + "no": Lagre + validate: + en: Validate + de: Prüfen + fr: Valider + "no": Valider + val-ok: + en: All required fields are filled. + de: Alle Pflichtfelder sind ausgefüllt. + fr: Tous les champs obligatoires sont remplis. + "no": Alle obligatoriske felt er fylt ut. + val-invoice-no: + en: Invoice number is required + de: Rechnungsnummer ist erforderlich + fr: Le numéro de facture est requis + "no": Fakturanummer er påkrevd + val-from-name: + en: Sender name is required + de: Absendername ist erforderlich + fr: Le nom de l'expéditeur est requis + "no": Avsendernavn er påkrevd + val-charge-to: + en: Charge-to is required + de: Empfänger ist erforderlich + fr: Le destinataire est requis + "no": Mottaker er påkrevd + val-lines: + en: At least one line item is required + de: Mindestens eine Zeile ist erforderlich + fr: Au moins une ligne est requise + "no": Minst én linje er påkrevd generate-invoice: - en: Generate Invoice - de: Rechnung erstellen - fr: Générer la facture - "no": Generer faktura + en: Download Invoice + de: Rechnung herunterladen + fr: Télécharger la facture + "no": Last ned faktura other: en: Other de: Andere diff --git a/app/index.html b/app/index.html index cb93ba5..9e00cb5 100644 --- a/app/index.html +++ b/app/index.html @@ -934,14 +934,18 @@ function buildForm() { - - + +
+ + + +
`; document.getElementById("pcode").addEventListener("change", function () { @@ -1393,8 +1397,12 @@ function relabel() { const alBtn = document.getElementById("btn-al"); if (alBtn) alBtn.textContent = t("add-line"); - const genBtn = document.getElementById("btn-generate"); - if (genBtn) genBtn.textContent = t("generate-invoice"); + const saveBtn = document.getElementById("btn-save"); + if (saveBtn) saveBtn.textContent = t("save"); + const valBtn = document.getElementById("btn-validate"); + if (valBtn) valBtn.textContent = t("validate"); + const genLbl = document.getElementById("btn-generate-lbl"); + if (genLbl) genLbl.textContent = t("generate-invoice"); Object.keys(tLines).forEach(i => { const ttEl = document.getElementById(`tt-${i}`); @@ -1423,6 +1431,45 @@ function relabel() { } // ── Generate invoice ────────────────────────────────────────────────────────── +function saveInvoice() { + saveStorage(); + const btn = document.getElementById("btn-save"); + if (!btn) return; + const orig = btn.textContent; + btn.textContent = "✓ " + orig; + btn.disabled = true; + setTimeout(() => { btn.textContent = orig; btn.disabled = false; }, 1500); +} + +function validateInvoice() { + const errors = []; + const ino = (document.getElementById("ino")?.value || "").trim(); + if (!ino) errors.push(t("val-invoice-no") || "Invoice number is required"); + const fromName = (document.getElementById("from_name")?.value || "").trim(); + if (!fromName) errors.push(t("val-from-name") || "Sender name is required"); + const ctPick = document.getElementById("ct-pick"); + if (!ctPick || !ctPick.value) errors.push(t("val-charge-to") || "Charge-to is required"); + if (Object.keys(lines).length === 0) errors.push(t("val-lines") || "At least one line item is required"); + + const btn = document.getElementById("btn-validate"); + const existing = document.getElementById("validate-msg"); + if (existing) existing.remove(); + const msg = document.createElement("div"); + msg.id = "validate-msg"; + msg.style.cssText = "margin-top:8px;padding:10px 14px;border-radius:8px;font-size:var(--fs-small)"; + if (errors.length === 0) { + msg.style.background = "color-mix(in srgb, var(--accent) 12%, transparent)"; + msg.style.color = "var(--accent)"; + msg.textContent = t("val-ok") || "All required fields are filled."; + } else { + msg.style.background = "color-mix(in srgb, #e74c3c 12%, transparent)"; + msg.style.color = "#e74c3c"; + msg.innerHTML = errors.map(e => `• ${e}`).join("
"); + } + btn.closest("div").after(msg); + setTimeout(() => msg.remove(), 5000); +} + function generateInvoice() { saveStorage(); localStorage.setItem(LS_GEN, "true");