Commit graph

36 commits

Author SHA1 Message Date
Claude
ad2aa079a9
Fix doctitle to mirror reimburse: correct SVG sizing, lowercase app name
Replace fragile viewBox string-replace with proper width/height injection
so the 24px icon renders at the right size. Use non-breaking space in
.meta when no invoice number is set (preserves row height).

https://claude.ai/code/session_01MkM7p8Us3L8YAfLKGA13NS
2026-06-08 16:30:10 +00:00
Claude
f47f524a84
Zoom: 50%–150% in 10% steps, default 100%
https://claude.ai/code/session_01MkM7p8Us3L8YAfLKGA13NS
2026-06-08 16:26:03 +00:00
Claude
24909e6f45
Align kb-header with timesheet/reimburse design
- Always use kb-brand structure (remove app-wordmark fallback path)
- Default brand: kbenestad.invoice / Invoice Generator when org-name
  and org-subheading are absent from config
- Remove .app-wordmark CSS class
- kb-footer max-width 980px to match kb-wrap

https://claude.ai/code/session_01MkM7p8Us3L8YAfLKGA13NS
2026-06-08 16:20:07 +00:00
Claude
a9115d0355
Set kb-wrap max-width to 980px
https://claude.ai/code/session_01MkM7p8Us3L8YAfLKGA13NS
2026-06-08 16:04:18 +00:00
Claude
206ed6184a
Dynamic header: org brand, app wordmark, invoice number meta, ⓘ button
Left brand:
- org-name set in config → kb-brand with 46px logo, org name, optional
  subheading (org-subheading)
- org-name absent/empty → plain app-wordmark (icon + "invoice")

Right doctitle:
- h1: 24px icon inline + lowercase "invoice" (built by buildHeader())
- .meta: invoice number, live-updates as user types in the ino field

Toolbar:
- Theme toggle updated to moon-phase icon (matches timesheet)
- ⓘ button added; shown only when cfg.about is configured

config.yml: org-name / org-subheading keys documented (commented out)
About modal comment updated to mention ⓘ button

https://claude.ai/code/session_01MkM7p8Us3L8YAfLKGA13NS
2026-06-08 15:59:46 +00:00
Claude
406ac77073
Add favicon.ico and icon-192.png; wire both into HTML and manifest
favicon.ico: 16+32px embedded ICO for legacy browsers
icon-192.png: scaled from 512px for Android PWA install prompts

https://claude.ai/code/session_01MkM7p8Us3L8YAfLKGA13NS
2026-06-08 15:43:37 +00:00
Claude
36318022fa
Move all static assets into app/assets/
Favicons, icons, and site.webmanifest moved from app/ root to app/assets/.
Updated all href references in index.html accordingly.

https://claude.ai/code/session_01MkM7p8Us3L8YAfLKGA13NS
2026-06-08 13:23:37 +00:00
Claude
3ee874ccee
Replace kb-brand with app-wordmark: mark left of invoice text
Some checks are pending
/ mirror (push) Waiting to run
https://claude.ai/code/session_01MkM7p8Us3L8YAfLKGA13NS
2026-06-08 05:11:32 +00:00
Claude
2dde3450d9
Fix line amount top-alignment and PDF contact line wrapping
Remove align-self:center from line total span so it sits at the top of
the row. Use sp()+forEach in buildPDF to wrap sender and charge-to
contact lines within their column instead of overflowing.

https://claude.ai/code/session_01MkM7p8Us3L8YAfLKGA13NS
2026-06-08 04:46:10 +00:00
Claude
69e4ad9624
Port all development-branch features into kBenestad redesign
Some checks failed
/ mirror (push) Has been cancelled
- Bidirectional FX rate entry: rcur dropdown (e.g. "35 THB per USD" or
  "0.028 USD per THB") with rother label updating dynamically
- FX labels from config translations; currency code appended at runtime
- ct-pick persisted to localStorage; restoreStorage rebuilds client-
  specific project code options before re-applying saved pcode value
- Footer rebuilt by buildFooter() with dynamic About link
- About modal with markdown-rendered content from config.yml (marked.js)
- about section added to config.yml with EN/DE/FR/NO content
- rcur saved/restored in inv_lines_v1; gatherData includes rcur in fxNote
- relabel() calls updateFxLabels() for each active FX line on lang switch

https://claude.ai/code/session_01MkM7p8Us3L8YAfLKGA13NS
2026-06-08 04:35:08 +00:00
Claude
3ef2f9206c
Fix line item row vertical alignment: top-align cells, nudge remove button
https://claude.ai/code/session_01MkM7p8Us3L8YAfLKGA13NS
2026-06-08 04:27:06 +00:00
Claude
b06500512a
Restructure toolbar and header: lang dropdown left, app name in brand
Move language select to the left of the toolbar. Add lowercase "invoice"
app name next to the squircle logo in the brand section.

https://claude.ai/code/session_01MkM7p8Us3L8YAfLKGA13NS
2026-06-08 04:24:39 +00:00
Claude
195e61794d
Add favicons and web manifest
Copies the design-system favicon set into app/ and wires up SVG, PNG
(16/32/48), apple-touch-icon, web manifest, and theme-color meta tag.

https://claude.ai/code/session_01MkM7p8Us3L8YAfLKGA13NS
2026-06-08 04:19:40 +00:00
Claude
46b17cd154
Remove brand text from header — icon only
https://claude.ai/code/session_01MkM7p8Us3L8YAfLKGA13NS
2026-06-08 04:05:29 +00:00
Claude
fcd2f047e7
Add subtle invoice logo to header
Replaces the generic file-icon placeholder with the real invoice.svg squircle
(28px, 82% opacity, accent fill with white glyph). Strips the brand text to
12px muted "kBenestad" at 70% opacity. Tightens header vertical padding so
the whole bar recedes after a few uses — noticeable on first visit, invisible
by the third.

https://claude.ai/code/session_01MkM7p8Us3L8YAfLKGA13NS
2026-06-08 04:04:08 +00:00
Claude
f717a62e11
Redesign app surface to kBenestad design system
Replaces all custom CSS with the kBenestad token set (Schibsted Grotesk +
JetBrains Mono, Nordic-minimal palette, single #2F6FED accent). Converts
the navy-banner layout to kb-header/kb-card/kb-grid/kb-row structure from
kbenestad-forms.css. Adds auto + manual dark mode via data-theme. Switches
line-item and tax tables from <table> to CSS-grid .kb-row divs. Adds a
light/dark toggle button to the toolbar. All JS logic, IDs, localStorage
keys, and PDF output are unchanged.

https://claude.ai/code/session_01MkM7p8Us3L8YAfLKGA13NS
2026-06-08 03:59:15 +00:00
Claude
dd8bc3ac16
Rename total-local key to total-foreign with corrected label text
https://claude.ai/code/session_0127ywsQA7qWcX3tGX4itqN5
2026-05-29 15:31:26 +00:00
Claude
40d4bae612
Support optional preset qty on predefined products
Products in config.yml can now include a qty field. When a predefined
line is selected the quantity input is pre-filled with that value (still
editable). Demonstrated on the REPORT product (qty: 1).

https://claude.ai/code/session_0127ywsQA7qWcX3tGX4itqN5
2026-05-29 15:20:44 +00:00
Claude
bacce29312
Widen quantity input to fit NNN.XX format
Increases col-qty from 72px to 96px and adds placeholder "0.00" with
step="0.01" to communicate the three-digit, two-decimal format.

https://claude.ai/code/session_0127ywsQA7qWcX3tGX4itqN5
2026-05-29 15:18:42 +00:00
Claude
30fe67fd6e
Replace PDF preview overlay with direct download on Generate
Clicking Generate now immediately triggers a PDF download instead of
opening a full-screen overlay preview that was inaccessible on smaller
screens. Removes the overlay HTML, its CSS, buildPreviewHTML(), and
closeOverlay().

https://claude.ai/code/session_0127ywsQA7qWcX3tGX4itqN5
2026-05-29 15:13:38 +00:00
Claude
0da4956a5a
Rename sender name label to "Issuer" in all languages
https://claude.ai/code/session_0127ywsQA7qWcX3tGX4itqN5
2026-05-29 14:57:31 +00:00
557badaf3a Update index.html 2026-05-27 11:59:52 +07:00
Claude
f07b1e9ae0
Per-recipient project codes
Each charge-to entry in config.yml can now have its own project-codes
list. Selecting a recipient swaps the project code dropdown to that
recipient's codes; switching back to Select or Other restores the
global list. Falls back to the global list if a recipient has no
project-codes defined.
2026-05-24 16:25:32 +00:00
Claude
3baf821fbd
Save project code selection to localStorage
Added data-ls attributes to pcode select and pcode-other input so they
are persisted and restored with the rest of the form. Restoring an
"Other" selection also re-shows the free-text input field.
2026-05-24 16:17:23 +00:00
Claude
ff5f4bdb19
Apply UX feedback: field locking, dynamic FX labels, line persistence
Charge to:
- All fields locked when "Select"; fill+lock on predefined entity;
  unlock all fields only when "Other" is chosen

Invoice lines:
- QTY, UOM, Price start disabled; Description dropdown is always active
- Predefined item: fills UOM (locked) and rate (editable) from config
- "Other": unlocks all three fields

Foreign currency:
- Unit Price locked and calculated-only when FX is enabled
- Re-enables Unit Price when FX is toggled back to No
- Exchange rate label dynamically shows "Exchange rate (X USD = 1 EUR)"
  using the selected foreign currency and invoice currency
- Per-item label shows "Price per item in USD" (selected FCY)
- Label updates when either currency changes

Invoice output:
- FX note shows "Price per item (foreign currency): USD …" —
  the "Converted from …" prefix is removed from preview and PDF

Invoice lines persistence:
- Lines saved to localStorage on every change (desc, qty, UOM, price,
  FX toggle, FCY, rate, per-item)
- Lines fully restored on page reload including FX state
- "Reset invoice lines" button added to the INVOICE LINES card header
2026-05-24 16:09:12 +00:00
Claude
89a310a7e5
Flip FX rate direction to X-foreign=1-local; add A-/A+ zoom buttons
Exchange rate semantics changed from "1 foreign = X local" to
"X foreign = 1 local" (divide instead of multiply). Updates the
calculation, preview note, both PDF fxStr strings, and config labels.

Adds accessible font-size A−/A+ buttons to the lang-bar (always
visible). Zooms only #form-root; index persisted in localStorage.
Language selector shown only when multiple languages are configured.

https://claude.ai/code/session_015iyCBgoTXNNqaErR287U1u
2026-05-19 09:55:31 +00:00
Claude
7222152664
Add sender Tax ID field; unify contact info rendering
- New VAT / Tax ID field on the sender form (saved to localStorage)
- Preview: sender phone/email/tax ID now rendered as .bt-meta spans matching
  charge-to style (bold label, muted text, flex wrap with gap)
- PDF: sender contact line includes tax ID alongside phone and email
- .bt-meta CSS selector generalised (removed .d-bill scope) so it works for
  both sender and charge-to panels without duplication

https://claude.ai/code/session_015iyCBgoTXNNqaErR287U1u
2026-05-19 09:43:05 +00:00
Claude
a63fcc8a42
Swap Invoice details / Payment positions; bank fields only for Other
- New layout: Sender | Invoice details (top row), Charge to | Payment (bottom row)
- Bank account fields (#bank-section) are hidden by default and only revealed
  when "Other" is selected for charge-to; hidden again for predefined recipients
- gatherData() only collects bank fields when bank-section is visible
- Preview and PDF panels updated to match the new quadrant order

https://claude.ai/code/session_015iyCBgoTXNNqaErR287U1u
2026-05-19 09:34:43 +00:00
Claude
275d3a3b71
Restructure layout to 2×2 grid: Sender|Payment / Charge-to|Invoice details
Form and invoice output now follow the same four-panel layout:
  Sender          | Payment details
  Charge to       | Invoice details (INVOICE title + meta)
  Invoice lines (full width)

- Payment card is standalone (top-right), bank fields always visible
- Bank fields saved to localStorage so sender enters them once
- hide-payment-info suppresses bank details from invoice output only
- fillChargeTo() no longer touches payment/bank fields
- gatherData() always collects bank fields; hidePaymentOut passed to renderers
- buildPreviewHTML() uses .d-4hdr grid with four .d-tl/.d-tr/.d-bl/.d-br cells
- buildPDF() rewritten: row-1 (sender|payment), light divider, row-2 (charge-to|INVOICE+meta), navy rule, then lines
- Removed bank config fields from Example NGO (those belong to the sender)

https://claude.ai/code/session_015iyCBgoTXNNqaErR287U1u
2026-05-19 09:25:09 +00:00
Claude
e4cff52909
Fix payment info on PDF; always show terms/pay-by; bank details conditional
- Payment terms (default 7 days) and pay-by date are now always visible in the
  form and always rendered in the invoice preview and PDF when pTerm > 0
- Bank account details moved into a separate #bank-section that is shown only
  when "Other" is selected or when the predefined charge-to has bank info in config
- hide-payment-info: yes now only suppresses bank details, not terms/pay-by
- Example NGO in config.yml now includes UK bank info (NatWest IBAN + BIC) which
  auto-fills and locks the bank section when that recipient is selected
- calcPayBy() is called on form init so the default 7-day pay-by date shows immediately

https://claude.ai/code/session_015iyCBgoTXNNqaErR287U1u
2026-05-19 09:07:24 +00:00
Claude
72c2c9e637
Add Payment panel to charge-to section
- New right-hand Payment column appears only when "Other" is selected for charge-to
- config.yml: hide-payment-info key (true/yes) suppresses the panel entirely
- Fields: payment terms (days), computed pay-by date, account holder, account no.
  (BBAN/IBAN), bank/BIC, bank address (2 lines), please use reference
- calcPayBy() derives the pay-by date from invoice date + terms days using fmtDate()
- relabel() keeps all payment labels in sync on language switch
- gatherData() captures payment fields only when panel is visible
- buildPreviewHTML() and buildPDF() render a payment section after totals when present

https://claude.ai/code/session_015iyCBgoTXNNqaErR287U1u
2026-05-19 08:54:41 +00:00
Claude
bdb2cb28a3
Add Registration no. field to charge-to section
- Add reg-no key to charge-to entries in config.yml
- Add registration-no translation key (en/de/fr/no)
- Add Registration no. input below VAT ID in the charge-to form
- fillChargeTo() populates and clears creg; field locks when predefined recipient is selected
- relabel() maps lbl-creg to registration-no translation
- gatherData() collects ctReg; buildPreviewHTML() and buildPDF() display it in the bill-to block

https://claude.ai/code/session_015iyCBgoTXNNqaErR287U1u
2026-05-19 08:42:39 +00:00
Claude
15addbeae8
Configurable date/paper format; multi-line dynamic tax system
Date format
- Replace hardcoded toLocaleDateString with a config-driven formatter
- date-format key in config (default "d MMMM YYYY") supports tokens:
  d, dd, M, MM, MMM, MMMM, YY, YYYY (regex replace, longest match first)

Paper format
- paper-format key in config: "a4" (default) or "letter"
- Passed directly to jsPDF; page dimensions adjusted accordingly

Tax system redesign
- Remove single-select tax-rates from config; add tax-types list, each
  with a key and per-language labels (VAT, GST, Sales Tax, Withholding,
  Other)
- Totals section now has a dynamic tax-tbody: each row has a number
  field, a % / Amount selector, and a tax-type label selector; rows
  are added with "+ Add new tax" and removed with ×
- calcTotals iterates tLines: % rows compute sub × (val/100), Amount
  rows use the fixed value directly; each row shows its calculated
  amount live
- gatherData collects all tax lines into a taxes[] array with lineLabel
  (e.g. "VAT 7%" or "GST (50.00)") and amt; both preview and PDF
  render one row per tax entry
- relabel() rebuilds tax-line dropdowns on language switch

https://claude.ai/code/session_015iyCBgoTXNNqaErR287U1u
2026-05-19 08:26:52 +00:00
Claude
f90718ba34
Five fixes to invoice form and PDF
- Date picker: change invoice date from month picker to full date picker
  (type=date, formatted as "19 May 2026" in output)

- Invoice currency: add currency selector under invoice date, populated
  from config.currencies list, saved to localStorage; shown in invoice
  meta block on both preview and PDF

- Recipient currency: add currency field to each charge-to entry in
  config.yml; selecting a predefined recipient auto-sets invoice currency

- Lock predefined recipients: selecting a predefined charge-to entry
  locks all its fields (pointer-events off + muted style via #ct-fields
  .locked); switching to Other or clearing unlocks them

- Fix foreign-currency exchange rate calculation: the formula was
  inverted (per / rate instead of per * rate). If 1 USD = 32 local and
  per-item is USD 100, local price is now correctly 100 × 32 = 3200,
  not 100 / 32 = 3.125. Fix applied in calcFxFromPer, calcLine display,
  and gatherData (foreignTot = per × qty, the foreign-currency total).
  Updated fx note text to the specified format:
  "Converted from USD: 1 USD = 32.00000 THB. Per item: USD 100.00,
  line total: USD 500.00"

https://claude.ai/code/session_015iyCBgoTXNNqaErR287U1u
2026-05-19 08:09:39 +00:00
Claude
f39eed979a
Replace browser print with jsPDF Helvetica PDF generation
The browser print-to-PDF approach requires the user to manually toggle
"Background graphics" on and "Headers and footers" off — impossible to
control programmatically. Replace with direct jsPDF generation:

- Loads jsPDF 2.5.1 UMD from CDN
- buildPDF() draws the invoice programmatically using Helvetica (PDF
  built-in font, no embedding needed)
- Layout: sender/title header, navy rule, charge-to block with navy
  accent bar, line-items table (navy header, alternating rows, multi-
  line description wrapping, foreign-currency sub-row), totals with
  navy To-Pay bar — all in mm on A4
- PDF downloads directly as invoice-{no}.pdf with no dialog
- HTML preview overlay retained for review before download
- Replace "print-invoice" config key with "download-pdf"

https://claude.ai/code/session_015iyCBgoTXNNqaErR287U1u
2026-05-19 07:52:40 +00:00
Claude
8bac41fe77
Build invoicing app: app/index.html and app/config.yml
Static single-page invoice app per spec. Features:
- Multilingual UI (EN/DE/FR/NO) loaded from config.yml; form always
  prints in the default language
- Sender fields + invoice details (date, project code, invoice no.)
  persisted in localStorage; invoice number auto-increments after
  each generation
- Predefined charge-to recipients selectable from config, or manual entry
- Dynamic invoice line items: predefined products with UOM/price
  pre-fill, free-text fallback, foreign-currency sub-row with
  exchange-rate and per-item price that calculates back to local currency
- Subtotal / tax (configurable rates) / paid / to-pay calculated live
- "Generate Invoice" renders a clean A4-formatted preview overlay;
  "Print / Save as PDF" triggers browser print-to-PDF

https://claude.ai/code/session_015iyCBgoTXNNqaErR287U1u
2026-05-18 18:03:48 +00:00