Initial commit: Voiced invoice desktop app

Offline-first invoicing app for Arun P, built by Bongbetic.

- Tauri v2 (Rust) + React/TypeScript + IBM Carbon Design System UI
- SQLite (rusqlite) storage with a persistent invoice-number series
- Guided first-run setup, pre-filled vendor/bank details
- Fixed-amount or rate x quantity line items (per sec/min/hour/session/unit)
- GST as CGST+SGST or IGST, Indian amount-in-words
- Optional signature, with a computer-generated note as fallback
- Print-ready PDF export via @react-pdf/renderer
- Windows cross-compile via cargo-xwin (NSIS installer)

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
This commit is contained in:
2026-10-03 23:07:51 +05:30
co-authored by CommandCodeBot
commit 3af9f3f66f
125 changed files with 13660 additions and 0 deletions
+192
View File
@@ -0,0 +1,192 @@
import { UNIT_LABELS } from "../lib/types";
import { computeLineAmount } from "../lib/invoice";
import { formatAmount, formatDate } from "../lib/format";
import type { InvoicePdfProps } from "../pdf/InvoiceDocument";
const money = (n: number) => `₹${formatAmount(n)}`;
/** On-screen preview that mirrors the exported PDF layout. */
export function InvoicePreview(props: InvoicePdfProps) {
const {
vendor,
bank,
logoDataUri,
signatureDataUri,
number,
invoiceDate,
dueDate,
clientName,
clientAddress,
clientGstin,
poNumber,
placeOfSupplyStateCode,
items,
subtotal,
discount,
taxType,
taxRate,
cgst,
sgst,
igst,
total,
amountInWords,
notes,
} = props;
const isTaxInvoice = taxType !== "none";
const contact = [vendor.vendorEmail, vendor.vendorPhone].filter(Boolean).join(" · ");
return (
<div className="inv-preview-wrap">
<div className="inv-paper">
<div className="inv-top">
<div className="inv-vendor">
{logoDataUri ? <img className="inv-vendor__logo" src={logoDataUri} alt="" /> : null}
<div>
<h2 className="inv-vendor__name">{vendor.vendorName || "Vendor"}</h2>
<p className="inv-vendor__line">{vendor.vendorAddress}</p>
{contact ? <p className="inv-vendor__line">{contact}</p> : null}
{vendor.vendorPan ? <p className="inv-vendor__line">PAN: {vendor.vendorPan}</p> : null}
<p className="inv-vendor__line">GSTIN: {vendor.vendorGstin || "NA"}</p>
</div>
</div>
<div className="inv-meta">
<h3 className="inv-meta__title">{isTaxInvoice ? "Tax Invoice" : "Invoice"}</h3>
<div className="inv-meta__row">
<span className="inv-meta__label">Invoice No.</span>
<span className="inv-meta__value">{number}</span>
</div>
<div className="inv-meta__row">
<span className="inv-meta__label">Date</span>
<span className="inv-meta__value">{formatDate(invoiceDate)}</span>
</div>
{dueDate ? (
<div className="inv-meta__row">
<span className="inv-meta__label">Due Date</span>
<span className="inv-meta__value">{formatDate(dueDate)}</span>
</div>
) : null}
</div>
</div>
<div className="inv-split">
<div className="inv-block" style={{ maxWidth: "60%" }}>
<p className="inv-block__label">Billed To</p>
<div className="inv-block__strong">{clientName || "—"}</div>
{clientAddress ? <div style={{ whiteSpace: "pre-line" }}>{clientAddress}</div> : null}
{clientGstin ? <div>GSTIN: {clientGstin}</div> : null}
{placeOfSupplyStateCode ? <div>Place of Supply: {placeOfSupplyStateCode}</div> : null}
</div>
{poNumber ? (
<div className="inv-block">
<p className="inv-block__label">PO Number</p>
<div className="inv-block__strong">{poNumber}</div>
</div>
) : null}
</div>
<table className="inv-table">
<thead>
<tr>
<th>Description</th>
<th className="num">Unit Price</th>
<th className="num">Qty</th>
<th className="num">Total</th>
</tr>
</thead>
<tbody>
{items.map((item, index) => (
<tr key={item.id ?? index}>
<td>{item.description || "—"}</td>
<td className="num">
{item.mode === "rate" ? `${money(item.rate)} ${UNIT_LABELS[item.unit]}` : "—"}
</td>
<td className="num">{item.mode === "rate" ? item.quantity : 1}</td>
<td className="num">{money(computeLineAmount(item))}</td>
</tr>
))}
</tbody>
</table>
<div className="inv-totals">
<div className="inv-totals__table">
<div className="inv-totals__row">
<span>Subtotal</span>
<span>{money(subtotal)}</span>
</div>
{discount > 0 ? (
<div className="inv-totals__row">
<span>Discount</span>
<span>- {money(discount)}</span>
</div>
) : null}
{taxType === "cgst_sgst" ? (
<>
<div className="inv-totals__row">
<span>CGST ({(taxRate / 2).toFixed(2)}%)</span>
<span>{money(cgst)}</span>
</div>
<div className="inv-totals__row">
<span>SGST ({(taxRate / 2).toFixed(2)}%)</span>
<span>{money(sgst)}</span>
</div>
</>
) : null}
{taxType === "igst" ? (
<div className="inv-totals__row">
<span>IGST ({taxRate.toFixed(2)}%)</span>
<span>{money(igst)}</span>
</div>
) : null}
<div className="inv-totals__row inv-totals__row--grand">
<span>Total</span>
<span>{money(total)}</span>
</div>
</div>
</div>
<p className="inv-words">Amount in words: {amountInWords}</p>
{notes ? (
<div style={{ marginTop: 16 }}>
<p className="inv-block__label">Notes</p>
<div style={{ whiteSpace: "pre-line" }}>{notes}</div>
</div>
) : null}
<div className="inv-bottom">
<div className="inv-block" style={{ maxWidth: "55%" }}>
{bank ? (
<>
<p className="inv-block__label">Pay To</p>
<div className="inv-block__strong">{bank.accountName || vendor.vendorName}</div>
<div>{bank.bankName}</div>
{bank.accountNo ? <div>Account No.: {bank.accountNo}</div> : null}
{bank.branch ? <div>Branch: {bank.branch}</div> : null}
{bank.ifsc ? <div>IFSC: {bank.ifsc}</div> : null}
</>
) : null}
</div>
<div className="inv-signature">
{signatureDataUri ? (
<>
<img className="inv-signature__img" src={signatureDataUri} alt="Signature" />
<div className="inv-signature__name">{vendor.vendorName}</div>
</>
) : (
<>
<div className="inv-signature__name">{vendor.vendorName}</div>
<p className="inv-fineprint">Computer-generated invoice; no signature is required.</p>
</>
)}
</div>
</div>
<div className="inv-footer">
<span>{vendor.vendorName}</span>
<span>Generated with Voiced — a Bongbetic product</span>
</div>
</div>
</div>
);
}