summaryrefslogtreecommitdiff
path: root/templates/invoice.typ
diff options
context:
space:
mode:
Diffstat (limited to 'templates/invoice.typ')
-rw-r--r--templates/invoice.typ148
1 files changed, 148 insertions, 0 deletions
diff --git a/templates/invoice.typ b/templates/invoice.typ
new file mode 100644
index 0000000..7dacc59
--- /dev/null
+++ b/templates/invoice.typ
@@ -0,0 +1,148 @@
+#set page(margin: (top: 0.75in, bottom: 1in, left: 1in, right: 1in))
+#set text(font: ("EB Garamond", "Georgia"), size: 10pt)
+#set par(leading: 0.65em)
+
+// Load invoice data from JSON file
+#let data = json("data.json")
+
+// Helper function to format hours as HH:MM
+#let format-hours(hours) = {
+ let total-minutes = calc.round(hours * 60)
+ let h = calc.floor(total-minutes / 60)
+ let m = calc.rem(total-minutes, 60)
+ str(h) + ":" + if m < 10 { "0" + str(m) } else { str(m) }
+}
+
+// Helper function to format currency with thousands separator and cents
+#let format-currency(amount) = {
+ let dollars = calc.floor(amount)
+ let cents = calc.round((amount - dollars) * 100)
+
+ // Convert to string and add thousands separators
+ let dollar-str = str(dollars)
+ let len = dollar-str.len()
+
+ let formatted = ""
+ for i in range(len) {
+ let digit = dollar-str.at(i)
+ formatted += digit
+ let remaining = len - i - 1
+ if remaining > 0 and calc.rem(remaining, 3) == 0 {
+ formatted += ","
+ }
+ }
+
+ "$" + formatted + "." + if cents < 10 { "0" + str(cents) } else { str(cents) }
+}
+
+// Professional header with company info
+#let professional-header() = {
+ // Company header
+ align(left)[
+ #text(size: 9pt, fill: gray)[
+ #text(weight: "bold")[#data.contractor_name] • #data.contractor_label • #data.contractor_email
+ ]
+ ]
+
+ v(3em)
+
+ // Invoice title and number
+ grid(
+ columns: (1fr, auto),
+ align(left)[
+ #text(size: 28pt, weight: "bold")[Invoice]
+ ],
+ align(right)[
+ #text(size: 11pt)[
+ #text(weight: "bold")[Invoice \##data.invoice_number] \
+ #data.generated_date
+ ]
+ ]
+ )
+
+ v(2.5em)
+}
+
+#let client-info-section() = {
+ grid(
+ columns: (1fr, 1fr),
+ gutter: 3em,
+ // Bill To section
+ [
+ #text(size: 9pt, fill: gray)[BILL TO]
+ #v(0.5em)
+ #text(size: 12pt, weight: "bold")[#data.client_name]
+ #if data.project_name != "" [
+ #v(0.3em)
+ #text(size: 10pt)[Project: #data.project_name]
+ ]
+ ],
+ // Invoice details
+ align(right)[
+ #text(size: 9pt, fill: gray)[INVOICE PERIOD]
+ #v(0.5em)
+ #text(size: 10pt)[#data.date_range_start to #data.date_range_end]
+ ]
+ )
+
+ v(2.5em)
+}
+
+#let professional-table() = {
+ table(
+ columns: (1fr, auto, auto, auto),
+ stroke: (x, y) => if y == 0 or y == 1 { (bottom: 0.8pt + black) } else { none },
+ inset: (x: 8pt, y: 12pt),
+ align: (left, center, center, right),
+ column-gutter: 12pt,
+
+ // Header
+ table.cell(fill: rgb("#f8f9fa"))[#text(weight: "bold", size: 9pt)[DESCRIPTION]],
+ table.cell(fill: rgb("#f8f9fa"))[#text(weight: "bold", size: 9pt)[HOURS]],
+ table.cell(fill: rgb("#f8f9fa"))[#text(weight: "bold", size: 9pt)[RATE]],
+ table.cell(fill: rgb("#f8f9fa"))[#text(weight: "bold", size: 9pt)[AMOUNT]],
+
+ // Line items
+ ..data.line_items.map(item => (
+ text(size: 10pt)[#item.description],
+ text(size: 10pt)[#format-hours(item.hours)],
+ text(size: 10pt)[#format-currency(item.rate)],
+ text(size: 10pt, weight: "medium")[#format-currency(item.amount)]
+ )).flatten()
+ )
+}
+
+#let invoice-summary() = {
+ v(1.5em)
+
+ // Subtotal and total section
+ align(right,
+ table(
+ columns: (auto, auto),
+ stroke: none,
+ inset: (x: 12pt, y: 6pt),
+ align: (right, right),
+
+ [#text(size: 10pt)[Total Hours:]], [#text(size: 10pt)[#format-hours(data.total_hours)]],
+ table.hline(stroke: 0.5pt),
+ [#text(size: 12pt, weight: "bold")[Total Amount:]], [#text(size: 12pt, weight: "bold")[#format-currency(data.total_amount)]]
+ )
+ )
+}
+
+#let payment-terms() = {
+ v(3em)
+
+ [
+ #text(size: 9pt, fill: gray)[
+ *Payment Terms:* Net 30 days. Please remit payment within 30 days of invoice date.
+ ]
+ ]
+}
+
+// Main invoice layout
+#professional-header()
+#client-info-section()
+#professional-table()
+#invoice-summary()
+//#payment-terms()