import "../module-globals.js";
import React from "react";
const header = defineComponent({
name: "header",
schema: z.object({
strings: z
.object({
invoice: z.string().default(""),
invoiceNumber: z.string().default(""),
date: z.string().default(""),
dueDate: z.string().default(""),
})
.default({}),
fromAddress: z.string().array().nullish(),
invoice: z.object({
invoiceNumber: z.string().or(z.number()),
date: z.string(),
dueDate: z.string(),
}),
}),
component: ({ spec, styles, getComponent }) => {
const Markdown = getComponent(defaultComponents.markdown);
return (
<>
{spec.strings?.invoice}
{spec.fromAddress?.join("\n")}
{spec.strings?.invoiceNumber}
{spec.invoice.invoiceNumber}
{spec.strings?.date}
{spec.invoice.date}
{spec.strings?.dueDate}
{spec.invoice.dueDate}
>
);
},
defaultStyles: {
container: {
marginBottom: "20pt",
},
headerRow: {
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
},
fromSection: {
flex: 1,
},
title: {
fontSize: "28pt",
fontWeight: "bold",
marginBottom: "10pt",
},
fromAddress: {
fontSize: "10pt",
lineHeight: "1.2",
},
invoiceDetails: {
width: "200pt",
},
detailRow: {
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
marginBottom: "4pt",
},
label: {
fontSize: "10pt",
fontWeight: "bold",
},
value: {
fontSize: "10pt",
},
} as const,
});
const toAddress = defineComponent({
name: "toAddress",
schema: z.object({
strings: z
.object({
billTo: z.string().default(""),
shipTo: z.string().default(""),
})
.default({}),
toAddress: z.string().array().nullish(),
shipAddress: z.string().array().nullish(),
}),
component: ({ spec, styles, getComponent }) => {
const Markdown = getComponent(defaultComponents.markdown);
const billTo = spec.toAddress;
const shipTo = spec.shipAddress;
return (
<>
{spec.strings?.billTo}
{billTo?.join("\n")}
{shipTo && (
{spec.strings?.shipTo}
{shipTo?.join("\n")}
)}
>
);
},
defaultStyles: {
container: {
marginBottom: "20pt",
},
addressRow: {
display: "flex",
flexDirection: "row",
gap: "40pt",
},
addressSection: {
flex: 1,
},
addressLabel: {
fontSize: "12pt",
fontWeight: "bold",
marginBottom: "6pt",
},
address: {
fontSize: "10pt",
lineHeight: "1.2",
},
} as const,
});
const invoiceTableRow = defineComponent({
name: "invoiceTableRow",
schema: z.object({}),
additionalProps: z.object({
item: z.object({
definition: z.object({
description: z.string(),
unitPrice: z.number(),
}),
quantity: z.number().nullish(),
}),
currency: z.string().nullish(),
}),
component: ({ item, currency = "USD", styles }) => {
const lineTotal = item.definition.unitPrice * (item.quantity || 1);
return (
<>
{item.definition.description}
{item.quantity || 1}
{currency} {item.definition.unitPrice.toFixed(2)}
{currency} {lineTotal.toFixed(2)}
>
);
},
defaultStyles: {
tableRow: {
display: "flex",
flexDirection: "row",
borderBottom: "0.5pt solid #ccc",
},
cell: {
fontSize: "9pt",
padding: "6pt",
borderRight: "0.5pt solid #ccc",
},
descriptionCol: {
flex: 3,
},
qtyCol: {
flex: 1,
},
priceCol: {
flex: 2,
textAlign: "right",
},
totalCol: {
flex: 2,
textAlign: "right",
borderRight: "none",
},
} as const,
});
const invoiceSummaryModifier = defineComponent({
name: "invoiceSummaryModifier",
schema: z.object({}),
additionalProps: z.object({
modifier: z.object({
description: z.string(),
rate: z.number(),
}),
subtotal: z.number(),
currency: z.string().nullish(),
}),
component: ({ modifier, subtotal, currency = "USD", styles }) => {
const modifierAmount = subtotal * modifier.rate;
return (
<>
{modifier.description} ({(modifier.rate * 100).toFixed(0)}%):
{currency} {modifierAmount.toFixed(2)}
>
);
},
defaultStyles: {
summaryRow: {
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
marginBottom: "4pt",
},
summaryLabel: {
fontSize: "10pt",
},
summaryValue: {
fontSize: "10pt",
textAlign: "right",
},
} as const,
});
const invoiceSummary = defineComponent({
name: "invoiceSummary",
schema: z.object({}),
additionalProps: z.object({
subtotal: z.number(),
total: z.number(),
modifiers: z
.array(
z.object({
description: z.string(),
rate: z.number(),
}),
)
.nullish(),
paymentMade: z.number().nullish(),
currency: z.string().nullish(),
targetCurrency: z.string().nullish(),
conversionRate: z.number().nullish(),
strings: z
.object({
subtotal: z.string().default(""),
totalLabel: z.string().default(""),
paymentMade: z.string().default(""),
balanceDue: z.string().default(""),
exchangeRate: z.string().default(""),
convertedTotal: z.string().default(""),
})
.nullish(),
}),
component: ({
subtotal,
total,
modifiers,
paymentMade = 0,
currency = "USD",
targetCurrency,
conversionRate,
strings,
styles,
getComponent,
}) => {
const balance = total - (paymentMade || 0);
const InvoiceSummaryModifier = getComponent({
name: "invoiceSummaryModifier",
});
const convertedTotal =
targetCurrency && conversionRate ? total * conversionRate : null;
// Payment made is assumed to already be in target currency if target currency is defined
const convertedBalance =
targetCurrency && conversionRate && convertedTotal !== null
? convertedTotal - (paymentMade || 0)
: null;
return (
<>
{strings?.subtotal}:
{currency} {subtotal.toFixed(2)}
{/* Modifiers */}
{(modifiers || []).map((modifier, index) => (
))}
{strings?.totalLabel}:
{currency} {total.toFixed(2)}
{/* Currency conversion section */}
{targetCurrency && conversionRate && convertedTotal !== null && (
<>
{strings?.exchangeRate}:
{currency} 1 = {targetCurrency} {conversionRate.toFixed(4)}
{strings?.convertedTotal}:
{targetCurrency} {convertedTotal.toFixed(2)}
>
)}
{(paymentMade || 0) > 0 && (
<>
{strings?.paymentMade}:
{targetCurrency && conversionRate ? targetCurrency : currency}{" "}
{(paymentMade || 0).toFixed(2)}
{strings?.balanceDue}:
{targetCurrency && conversionRate ? targetCurrency : currency}{" "}
{targetCurrency && conversionRate
? convertedBalance?.toFixed(2)
: balance.toFixed(2)}
>
)}
>
);
},
defaultStyles: {
summaryRow: {
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
marginBottom: "4pt",
},
summaryLabel: {
fontSize: "10pt",
},
summaryValue: {
fontSize: "10pt",
textAlign: "right",
},
totalRow: {
borderTop: "1pt solid #000",
paddingTop: "6pt",
marginTop: "6pt",
},
totalLabel: {
fontWeight: "bold",
fontSize: "12pt",
},
totalValue: {
fontWeight: "bold",
fontSize: "12pt",
},
convertedTotalRow: {
borderTop: "0.5pt solid #999",
paddingTop: "4pt",
marginTop: "4pt",
},
convertedTotalLabel: {
fontWeight: "bold",
fontSize: "11pt",
fontStyle: "italic",
},
convertedTotalValue: {
fontWeight: "bold",
fontSize: "11pt",
fontStyle: "italic",
},
balanceRow: {
borderTop: "1pt solid #000",
paddingTop: "6pt",
marginTop: "6pt",
},
balanceLabel: {
fontWeight: "bold",
fontSize: "12pt",
},
balanceValue: {
fontWeight: "bold",
fontSize: "12pt",
},
convertedBalanceText: {
fontSize: "10pt",
fontStyle: "italic",
color: "#666",
},
} as const,
});
const invoiceTable = defineComponent({
name: "invoiceTable",
schema: z.object({
strings: z
.object({
description: z.string().default(""),
qty: z.string().default(""),
unitPrice: z.string().default(""),
total: z.string().default(""),
subtotal: z.string().default(""),
totalLabel: z.string().default(""),
paymentMade: z.string().default(""),
balanceDue: z.string().default(""),
})
.default({}),
items: z.array(
z.object({
definition: z.object({
description: z.string(),
unitPrice: z.number(),
}),
quantity: z.number().nullish(),
}),
),
modifiers: z
.array(
z.object({
description: z.string(),
rate: z.number(),
}),
)
.nullish(),
invoice: z.object({
currency: z.string().nullish(),
targetCurrency: z.string().nullish(),
conversionRate: z.number().nullish(),
paymentMade: z.number().nullish(),
}),
}),
component: ({ spec, styles, getComponent }) => {
const InvoiceTableRow = getComponent({ name: "invoiceTableRow" });
const InvoiceSummary = getComponent({ name: "invoiceSummary" });
const currency = spec.invoice.currency || "USD";
const paymentMade = spec.invoice.paymentMade || 0;
const subtotal = spec.items.reduce(
(sum, item) => sum + item.definition.unitPrice * (item.quantity || 1),
0,
);
const modifierTotal = (spec.modifiers || []).reduce(
(sum, modifier) => sum + subtotal * modifier.rate,
0,
);
const total = subtotal + modifierTotal;
return (
<>
{/* Table Header */}
{spec.strings?.description}
{spec.strings?.qty}
{spec.strings?.unitPrice}
{spec.strings?.total}
{/* Table Rows */}
{spec.items.map((item, index) => (
))}
{/* Summary Section */}
>
);
},
defaultStyles: {
container: {
marginTop: "20pt",
marginBottom: "20pt",
},
tableHeader: {
display: "flex",
flexDirection: "row",
backgroundColor: "#f0f0f0",
borderTop: "1pt solid #000",
borderBottom: "1pt solid #000",
},
headerCell: {
fontSize: "10pt",
fontWeight: "bold",
padding: "6pt",
borderRight: "0.5pt solid #ccc",
},
descriptionCol: {
flex: 3,
},
qtyCol: {
flex: 1,
},
priceCol: {
flex: 2,
textAlign: "right",
},
totalCol: {
flex: 2,
textAlign: "right",
borderRight: "none",
},
summaryContainer: {
marginTop: "10pt",
width: "50%",
marginLeft: "auto",
},
} as const,
});
const body = defineComponent({
name: "body",
schema: z.object({
body: z.string().or(z.string().array()).nullish(),
}),
component: ({ spec, styles, getComponent }) => {
const Markdown = getComponent(defaultComponents.markdown);
if (!spec.body) return null;
return (
<>
{spec.body}
>
);
},
defaultStyles: {
container: {
marginTop: "20pt",
},
text: { fontSize: "10pt" },
} as const,
});
export default {
config: {
components: {
header,
toAddress,
invoiceTable,
invoiceTableRow,
invoiceSummaryModifier,
invoiceSummary,
body,
},
},
order: ["header", "toAddress", "invoiceTable", "body"],
strings: {
invoice: "INVOICE",
invoiceNumber: "Invoice #:",
date: "Date:",
dueDate: "Due Date:",
billTo: "Bill To:",
shipTo: "Ship To:",
description: "Description",
qty: "Qty",
unitPrice: "Unit Price",
total: "Total",
subtotal: "Subtotal",
totalLabel: "Total",
paymentMade: "Payment Made",
balanceDue: "Balance Due",
exchangeRate: "Exchange Rate",
convertedTotal: "Total (Converted)",
},
styles: {
page: {
container: {
paddingHorizontal: "48pt",
paddingVertical: "40pt",
},
},
},
};