feat: add InvoicePreview.vue with all 7 header styles and 5 table styles

This commit is contained in:
Your Name
2026-02-18 13:30:27 +02:00
parent 673da2aab8
commit 3cadb42f8b

View File

@@ -0,0 +1,723 @@
<script setup lang="ts">
import { computed } from 'vue'
import type { InvoiceTemplateConfig } from '../utils/invoiceTemplates'
import type { Invoice } from '../stores/invoices'
import type { Client } from '../stores/clients'
import type { InvoiceItem } from '../utils/invoicePdf'
import type { BusinessInfo } from '../utils/invoicePdfRenderer'
import { formatCurrency, formatDate } from '../utils/locale'
const props = defineProps<{
template: InvoiceTemplateConfig
invoice: Invoice
client: Client | null
items: InvoiceItem[]
businessInfo?: BusinessInfo
}>()
const sampleItems: InvoiceItem[] = [
{ description: 'Web Design & Development', quantity: 40, rate: 150, amount: 6000 },
{ description: 'UI/UX Consultation', quantity: 8, rate: 200, amount: 1600 },
{ description: 'Project Management', quantity: 12, rate: 100, amount: 1200 },
]
const displayItems = computed(() =>
props.items.length > 0 ? props.items : sampleItems,
)
const config = computed(() => props.template)
const clientName = computed(() => props.client?.name || 'N/A')
const clientEmail = computed(() => props.client?.email || '')
const clientAddressLines = computed(() =>
props.client?.address ? props.client.address.split('\n') : [],
)
const biz = computed(() => props.businessInfo)
const bizAddressLines = computed(() =>
biz.value?.address ? biz.value.address.split('\n') : [],
)
const numberFont = computed(() =>
config.value.typography.numberStyle === 'monospace-feel'
? "'Courier New', Courier, monospace"
: 'inherit',
)
const sideOffset = computed(() =>
config.value.layout.headerStyle === 'sidebar'
? `${config.value.decorative.sidebarWidth + 6}%`
: '0px',
)
// Divider style helper
const dividerBorder = computed(() => {
if (!config.value.layout.showDividers || config.value.layout.dividerStyle === 'none') return 'none'
switch (config.value.layout.dividerStyle) {
case 'thin': return `1px solid ${config.value.colors.tableBorder}`
case 'double': return `3px double ${config.value.colors.tableBorder}`
case 'thick': return `2px solid ${config.value.colors.primary}`
default: return 'none'
}
})
const showDivider = computed(() =>
config.value.layout.showDividers && config.value.layout.dividerStyle !== 'none',
)
// Logo position style
const logoContainerStyle = computed(() => {
const pos = config.value.layout.logoPosition
const base: Record<string, string> = { marginBottom: '2%' }
if (pos === 'top-center') base.textAlign = 'center'
else if (pos === 'top-right') base.textAlign = 'right'
else base.textAlign = 'left'
return base
})
</script>
<template>
<!-- Outer A4 container -->
<div
:style="{
backgroundColor: config.colors.background,
color: config.colors.bodyText,
fontFamily: '\'Helvetica Neue\', Helvetica, Arial, sans-serif',
position: 'relative',
aspectRatio: '8.5 / 11',
width: '100%',
overflow: 'hidden',
borderRadius: '4px',
boxShadow: '0 4px 6px -1px rgba(0,0,0,0.1)',
}"
>
<!-- Background tint -->
<div
v-if="config.decorative.backgroundTint && config.decorative.backgroundTintColor"
:style="{
position: 'absolute',
inset: '0',
backgroundColor: config.decorative.backgroundTintColor,
pointerEvents: 'none',
}"
/>
<!-- Decorative: Sidebar -->
<div
v-if="config.decorative.sidebarWidth > 0"
:style="{
position: 'absolute',
top: '0',
left: '0',
bottom: '0',
width: config.decorative.sidebarWidth + '%',
backgroundColor: config.decorative.sidebarColor,
pointerEvents: 'none',
}"
/>
<!-- Decorative: Corner block -->
<div
v-if="config.decorative.cornerShape === 'colored-block'"
:style="{
position: 'absolute',
top: '0',
left: '0',
width: '28%',
height: '20%',
backgroundColor: config.colors.primary,
pointerEvents: 'none',
}"
/>
<!-- Decorative: Triangle (SVG in top-right) -->
<svg
v-if="config.decorative.cornerShape === 'triangle'"
:style="{
position: 'absolute',
top: '0',
right: '0',
width: '28%',
height: '28%',
pointerEvents: 'none',
}"
viewBox="0 0 100 100"
preserveAspectRatio="none"
>
<polygon points="100,0 100,100 0,0" :fill="config.colors.primary" />
</svg>
<!-- Decorative: Diagonal -->
<svg
v-if="config.decorative.cornerShape === 'diagonal'"
:style="{
position: 'absolute',
top: '0',
left: '0',
width: '100%',
height: '20%',
pointerEvents: 'none',
}"
viewBox="0 0 210 60"
preserveAspectRatio="none"
>
<polygon points="0,0 210,0 0,60" :fill="config.colors.primary" />
</svg>
<!-- Content area with padding -->
<div
:style="{
position: 'relative',
zIndex: 1,
padding: '5%',
fontSize: config.typography.bodySize * 0.85 + 'px',
lineHeight: '1.4',
height: '100%',
boxSizing: 'border-box',
overflow: 'hidden',
}"
>
<!-- ===== HEADER: MINIMAL ===== -->
<template v-if="config.layout.headerStyle === 'minimal'">
<!-- Logo -->
<div v-if="biz?.logo" :style="logoContainerStyle">
<img :src="biz.logo" :style="{ maxHeight: '5%', maxWidth: '20%', objectFit: 'contain' }" />
</div>
<!-- Business info -->
<div v-if="biz?.name" :style="{ marginBottom: '2%' }">
<div :style="{ fontWeight: 'bold', fontSize: config.typography.headerSize * 0.85 + 'px', color: config.colors.headerText }">
{{ biz.name }}
</div>
<div v-for="(line, i) in bizAddressLines" :key="'ba' + i" :style="{ fontSize: config.typography.bodySize * 0.8 + 'px' }">{{ line }}</div>
<div v-if="biz.email" :style="{ fontSize: config.typography.bodySize * 0.8 + 'px' }">{{ biz.email }}</div>
<div v-if="biz.phone" :style="{ fontSize: config.typography.bodySize * 0.8 + 'px' }">{{ biz.phone }}</div>
</div>
<!-- Title -->
<div :style="{
fontWeight: config.typography.titleWeight,
fontSize: config.typography.titleSize * 0.85 + 'px',
color: config.colors.primary,
marginBottom: '1%',
}">
INVOICE
</div>
<!-- Accent line -->
<div :style="{ width: '15%', height: '2px', backgroundColor: config.colors.primary, marginBottom: '3%' }" />
<!-- Details + Bill To -->
<div :style="{ display: 'flex', justifyContent: 'space-between', marginBottom: '3%' }">
<div>
<div>Invoice #: {{ invoice.invoice_number }}</div>
<div>Date: {{ formatDate(invoice.date) }}</div>
<div v-if="invoice.due_date">Due Date: {{ formatDate(invoice.due_date) }}</div>
<div>Status: {{ invoice.status }}</div>
</div>
<div :style="{ textAlign: 'right' }">
<div :style="{ fontWeight: 'bold', marginBottom: '2px' }">Bill To:</div>
<div>{{ clientName }}</div>
<div v-if="clientEmail">{{ clientEmail }}</div>
<div v-for="(line, i) in clientAddressLines" :key="'ca' + i">{{ line }}</div>
</div>
</div>
<!-- Divider -->
<div v-if="showDivider" :style="{ borderBottom: dividerBorder, marginBottom: '3%' }" />
</template>
<!-- ===== HEADER: FULL-WIDTH ===== -->
<template v-if="config.layout.headerStyle === 'full-width'">
<!-- Full-width banner (negative margins to fill) -->
<div :style="{
margin: '-5% -5% 0 -5%',
padding: '4% 5%',
backgroundColor: config.colors.headerBg,
color: config.colors.headerText,
marginBottom: '3%',
}">
<!-- Logo inside banner -->
<div v-if="biz?.logo" :style="logoContainerStyle">
<img :src="biz.logo" :style="{ maxHeight: '5%', maxWidth: '20%', objectFit: 'contain' }" />
</div>
<div :style="{ display: 'flex', justifyContent: 'space-between' }">
<div>
<div :style="{
fontWeight: config.typography.titleWeight,
fontSize: config.typography.titleSize * 0.85 + 'px',
marginBottom: '2%',
}">
INVOICE
</div>
<div :style="{ fontSize: config.typography.bodySize * 0.8 + 'px' }">
<div>#{{ invoice.invoice_number }}</div>
<div>Date: {{ formatDate(invoice.date) }}</div>
<div v-if="invoice.due_date">Due: {{ formatDate(invoice.due_date) }}</div>
<div>Status: {{ invoice.status }}</div>
</div>
</div>
<div v-if="biz?.name && config.layout.logoPosition !== 'top-right'" :style="{ textAlign: 'right', fontSize: config.typography.bodySize * 0.8 + 'px' }">
<div :style="{ fontWeight: 'bold', fontSize: config.typography.bodySize * 0.9 + 'px' }">{{ biz.name }}</div>
<div v-if="biz.email">{{ biz.email }}</div>
<div v-if="biz.phone">{{ biz.phone }}</div>
<div v-for="(line, i) in bizAddressLines" :key="'fba' + i">{{ line }}</div>
</div>
</div>
</div>
<!-- Bill To below banner -->
<div :style="{ marginBottom: '3%' }">
<div :style="{ fontWeight: 'bold', marginBottom: '2px', color: config.colors.bodyText }">Bill To:</div>
<div :style="{ color: config.colors.bodyText }">
<div>{{ clientName }}</div>
<div v-if="clientEmail">{{ clientEmail }}</div>
<div v-for="(line, i) in clientAddressLines" :key="'fca' + i">{{ line }}</div>
</div>
</div>
</template>
<!-- ===== HEADER: SPLIT ===== -->
<template v-if="config.layout.headerStyle === 'split'">
<div :style="{
display: 'flex',
margin: '-5% -5% 0 -5%',
marginBottom: '3%',
}">
<!-- Left half: colored -->
<div :style="{
width: '50%',
backgroundColor: config.colors.headerBg,
color: config.colors.headerText,
padding: '4% 5%',
}">
<div :style="{
fontWeight: config.typography.titleWeight,
fontSize: config.typography.titleSize * 0.85 + 'px',
marginBottom: '3%',
}">
INVOICE
</div>
<div :style="{ fontSize: config.typography.bodySize * 0.8 + 'px' }">
<div v-if="biz?.name" :style="{ fontWeight: 'bold' }">{{ biz.name }}</div>
<div v-if="biz?.email">{{ biz.email }}</div>
<div v-if="biz?.phone">{{ biz.phone }}</div>
<div v-for="(line, i) in bizAddressLines" :key="'sba' + i">{{ line }}</div>
</div>
<!-- Logo in left half -->
<div v-if="biz?.logo" :style="{ marginTop: '3%' }">
<img :src="biz.logo" :style="{ maxHeight: '14%', maxWidth: '40%', objectFit: 'contain' }" />
</div>
</div>
<!-- Right half: light -->
<div :style="{
width: '50%',
padding: '4% 5%',
color: config.colors.bodyText,
}">
<div :style="{ fontSize: config.typography.bodySize * 0.8 + 'px' }">
<div>Invoice #: {{ invoice.invoice_number }}</div>
<div>Date: {{ formatDate(invoice.date) }}</div>
<div v-if="invoice.due_date">Due Date: {{ formatDate(invoice.due_date) }}</div>
<div>Status: {{ invoice.status }}</div>
</div>
</div>
</div>
<!-- Bill To below split -->
<div :style="{ marginBottom: '3%' }">
<div :style="{ fontWeight: 'bold', marginBottom: '2px' }">Bill To:</div>
<div>{{ clientName }}</div>
<div v-if="clientEmail">{{ clientEmail }}</div>
<div v-for="(line, i) in clientAddressLines" :key="'sca' + i">{{ line }}</div>
</div>
</template>
<!-- ===== HEADER: SIDEBAR ===== -->
<template v-if="config.layout.headerStyle === 'sidebar'">
<div :style="{ paddingLeft: sideOffset }">
<!-- Logo -->
<div v-if="biz?.logo" :style="logoContainerStyle">
<img :src="biz.logo" :style="{ maxHeight: '5%', maxWidth: '20%', objectFit: 'contain' }" />
</div>
<!-- Business info -->
<div v-if="biz?.name" :style="{ marginBottom: '2%' }">
<div :style="{ fontWeight: 'bold', fontSize: config.typography.headerSize * 0.85 + 'px', color: config.colors.headerText }">
{{ biz.name }}
</div>
<div v-for="(line, i) in bizAddressLines" :key="'sbba' + i" :style="{ fontSize: config.typography.bodySize * 0.8 + 'px' }">{{ line }}</div>
<div v-if="biz?.email" :style="{ fontSize: config.typography.bodySize * 0.8 + 'px' }">{{ biz.email }}</div>
<div v-if="biz?.phone" :style="{ fontSize: config.typography.bodySize * 0.8 + 'px' }">{{ biz.phone }}</div>
</div>
<!-- Title -->
<div :style="{
fontWeight: config.typography.titleWeight,
fontSize: config.typography.titleSize * 0.85 + 'px',
color: config.colors.primary,
marginBottom: '1%',
}">
INVOICE
</div>
<!-- Details + Bill To -->
<div :style="{ display: 'flex', justifyContent: 'space-between', marginBottom: '3%' }">
<div>
<div>Invoice #: {{ invoice.invoice_number }}</div>
<div>Date: {{ formatDate(invoice.date) }}</div>
<div v-if="invoice.due_date">Due Date: {{ formatDate(invoice.due_date) }}</div>
<div>Status: {{ invoice.status }}</div>
</div>
<div :style="{ textAlign: 'right' }">
<div :style="{ fontWeight: 'bold', marginBottom: '2px' }">Bill To:</div>
<div>{{ clientName }}</div>
<div v-if="clientEmail">{{ clientEmail }}</div>
<div v-for="(line, i) in clientAddressLines" :key="'sbca' + i">{{ line }}</div>
</div>
</div>
<!-- Divider -->
<div v-if="showDivider" :style="{ borderBottom: dividerBorder, marginBottom: '3%' }" />
</div>
</template>
<!-- ===== HEADER: GRADIENT ===== -->
<template v-if="config.layout.headerStyle === 'gradient'">
<div :style="{
margin: '-5% -5% 0 -5%',
padding: '4% 5%',
background: `linear-gradient(to right, ${config.decorative.gradientFrom || config.colors.headerBg}, ${config.decorative.gradientTo || config.colors.primary})`,
color: config.colors.headerText,
marginBottom: '3%',
}">
<!-- Logo inside gradient -->
<div v-if="biz?.logo" :style="logoContainerStyle">
<img :src="biz.logo" :style="{ maxHeight: '5%', maxWidth: '20%', objectFit: 'contain' }" />
</div>
<div :style="{ display: 'flex', justifyContent: 'space-between' }">
<div>
<div :style="{
fontWeight: config.typography.titleWeight,
fontSize: config.typography.titleSize * 0.85 + 'px',
marginBottom: '2%',
}">
INVOICE
</div>
<div :style="{ fontSize: config.typography.bodySize * 0.8 + 'px' }">
<div>#{{ invoice.invoice_number }}</div>
<div>Date: {{ formatDate(invoice.date) }}</div>
<div v-if="invoice.due_date">Due: {{ formatDate(invoice.due_date) }}</div>
<div>Status: {{ invoice.status }}</div>
</div>
</div>
<div v-if="biz?.name && config.layout.logoPosition !== 'top-right'" :style="{ textAlign: 'right', fontSize: config.typography.bodySize * 0.8 + 'px' }">
<div :style="{ fontWeight: 'bold', fontSize: config.typography.bodySize * 0.9 + 'px' }">{{ biz.name }}</div>
<div v-if="biz.email">{{ biz.email }}</div>
<div v-if="biz.phone">{{ biz.phone }}</div>
</div>
</div>
</div>
<!-- Bill To below gradient -->
<div :style="{ marginBottom: '3%', color: config.colors.bodyText }">
<div :style="{ fontWeight: 'bold', marginBottom: '2px' }">Bill To:</div>
<div>{{ clientName }}</div>
<div v-if="clientEmail">{{ clientEmail }}</div>
<div v-for="(line, i) in clientAddressLines" :key="'gca' + i">{{ line }}</div>
</div>
</template>
<!-- ===== HEADER: GEOMETRIC ===== -->
<template v-if="config.layout.headerStyle === 'geometric'">
<!-- Logo (top-left to avoid triangle) -->
<div v-if="biz?.logo" :style="logoContainerStyle">
<img :src="biz.logo" :style="{ maxHeight: '5%', maxWidth: '20%', objectFit: 'contain' }" />
</div>
<!-- Business info -->
<div v-if="biz?.name" :style="{ marginBottom: '2%', maxWidth: '60%' }">
<div :style="{ fontWeight: 'bold', fontSize: config.typography.headerSize * 0.85 + 'px', color: config.colors.headerText }">
{{ biz.name }}
</div>
<div v-if="biz.email" :style="{ fontSize: config.typography.bodySize * 0.8 + 'px' }">{{ biz.email }}</div>
<div v-if="biz.phone" :style="{ fontSize: config.typography.bodySize * 0.8 + 'px' }">{{ biz.phone }}</div>
</div>
<!-- Title -->
<div :style="{
fontWeight: config.typography.titleWeight,
fontSize: config.typography.titleSize * 0.85 + 'px',
color: config.colors.primary,
marginBottom: '1%',
}">
INVOICE
</div>
<!-- Details + Bill To -->
<div :style="{ display: 'flex', justifyContent: 'space-between', marginBottom: '3%' }">
<div>
<div>Invoice #: {{ invoice.invoice_number }}</div>
<div>Date: {{ formatDate(invoice.date) }}</div>
<div v-if="invoice.due_date">Due Date: {{ formatDate(invoice.due_date) }}</div>
<div>Status: {{ invoice.status }}</div>
</div>
<div :style="{ textAlign: 'right' }">
<div :style="{ fontWeight: 'bold', marginBottom: '2px' }">Bill To:</div>
<div>{{ clientName }}</div>
<div v-if="clientEmail">{{ clientEmail }}</div>
<div v-for="(line, i) in clientAddressLines" :key="'geca' + i">{{ line }}</div>
</div>
</div>
<!-- Divider -->
<div v-if="showDivider" :style="{ borderBottom: dividerBorder, marginBottom: '3%' }" />
</template>
<!-- ===== HEADER: CENTERED ===== -->
<template v-if="config.layout.headerStyle === 'centered'">
<!-- Logo centered -->
<div v-if="biz?.logo" :style="{ textAlign: 'center', marginBottom: '2%' }">
<img :src="biz.logo" :style="{ maxHeight: '5%', maxWidth: '20%', objectFit: 'contain' }" />
</div>
<!-- Top divider line -->
<div :style="{ borderBottom: '1px solid ' + config.colors.tableBorder, marginBottom: '2%' }" />
<!-- Title centered -->
<div :style="{
textAlign: 'center',
fontWeight: config.typography.titleWeight,
fontSize: config.typography.titleSize * 0.85 + 'px',
color: config.colors.primary,
marginBottom: '1%',
}">
INVOICE
</div>
<!-- Business name centered -->
<div v-if="biz?.name" :style="{ textAlign: 'center', marginBottom: '2%' }">
<div :style="{ fontSize: config.typography.headerSize * 0.85 + 'px', color: config.colors.headerText }">
{{ biz.name }}
</div>
<div v-for="(line, i) in bizAddressLines" :key="'cba' + i" :style="{ fontSize: config.typography.bodySize * 0.8 + 'px' }">{{ line }}</div>
<div v-if="biz.email" :style="{ fontSize: config.typography.bodySize * 0.8 + 'px' }">{{ biz.email }}</div>
<div v-if="biz.phone" :style="{ fontSize: config.typography.bodySize * 0.8 + 'px' }">{{ biz.phone }}</div>
</div>
<!-- Bottom divider line -->
<div :style="{ borderBottom: '1px solid ' + config.colors.tableBorder, marginBottom: '2%' }" />
<!-- Details + Bill To side by side -->
<div :style="{ display: 'flex', justifyContent: 'space-between', marginBottom: '3%' }">
<div>
<div>Invoice #: {{ invoice.invoice_number }}</div>
<div>Date: {{ formatDate(invoice.date) }}</div>
<div v-if="invoice.due_date">Due Date: {{ formatDate(invoice.due_date) }}</div>
<div>Status: {{ invoice.status }}</div>
</div>
<div :style="{ textAlign: 'right' }">
<div :style="{ fontWeight: 'bold', marginBottom: '2px' }">Bill To:</div>
<div>{{ clientName }}</div>
<div v-if="clientEmail">{{ clientEmail }}</div>
<div v-for="(line, i) in clientAddressLines" :key="'cca' + i">{{ line }}</div>
</div>
</div>
<!-- Divider -->
<div v-if="showDivider" :style="{ borderBottom: dividerBorder, marginBottom: '3%' }" />
</template>
<!-- ===== TABLE ===== -->
<div :style="{ paddingLeft: config.layout.headerStyle === 'sidebar' ? sideOffset : '0' }">
<table
:style="{
width: '100%',
borderCollapse: 'collapse',
fontSize: config.typography.bodySize * 0.85 + 'px',
marginBottom: '4%',
}"
>
<thead>
<tr
:style="{
backgroundColor: config.colors.tableHeaderBg,
color: config.colors.tableHeaderText,
}"
>
<th
:style="{
textAlign: 'left',
padding: '1.5% 2%',
fontWeight: 'bold',
width: '50%',
...(config.layout.tableStyle === 'bordered'
? { border: '1px solid ' + config.colors.tableBorder }
: {}),
}"
>
Description
</th>
<th
:style="{
textAlign: 'left',
padding: '1.5% 2%',
fontWeight: 'bold',
width: '15%',
fontFamily: numberFont,
...(config.layout.tableStyle === 'bordered'
? { border: '1px solid ' + config.colors.tableBorder }
: {}),
}"
>
Qty
</th>
<th
:style="{
textAlign: 'left',
padding: '1.5% 2%',
fontWeight: 'bold',
width: '17.5%',
fontFamily: numberFont,
...(config.layout.tableStyle === 'bordered'
? { border: '1px solid ' + config.colors.tableBorder }
: {}),
}"
>
Rate
</th>
<th
:style="{
textAlign: 'left',
padding: '1.5% 2%',
fontWeight: 'bold',
width: '17.5%',
fontFamily: numberFont,
...(config.layout.tableStyle === 'bordered'
? { border: '1px solid ' + config.colors.tableBorder }
: {}),
}"
>
Amount
</th>
</tr>
</thead>
<tbody>
<tr
v-for="(item, idx) in displayItems"
:key="idx"
:style="{
backgroundColor:
config.layout.tableStyle === 'striped' && idx % 2 === 1
? config.colors.tableRowAlt
: config.layout.tableStyle === 'colored-sections'
? idx % 2 === 1 ? config.colors.tableRowAlt : config.colors.background
: 'transparent',
borderBottom:
config.layout.tableStyle === 'minimal-lines'
? '1px solid ' + config.colors.tableBorder
: 'none',
}"
>
<td
:style="{
padding: '1.5% 2%',
...(config.layout.tableStyle === 'bordered'
? { border: '1px solid ' + config.colors.tableBorder }
: {}),
}"
>
{{ item.description }}
</td>
<td
:style="{
padding: '1.5% 2%',
fontFamily: numberFont,
...(config.layout.tableStyle === 'bordered'
? { border: '1px solid ' + config.colors.tableBorder }
: {}),
}"
>
{{ item.quantity }}
</td>
<td
:style="{
padding: '1.5% 2%',
fontFamily: numberFont,
...(config.layout.tableStyle === 'bordered'
? { border: '1px solid ' + config.colors.tableBorder }
: {}),
}"
>
{{ formatCurrency(item.rate) }}
</td>
<td
:style="{
padding: '1.5% 2%',
fontFamily: numberFont,
...(config.layout.tableStyle === 'bordered'
? { border: '1px solid ' + config.colors.tableBorder }
: {}),
}"
>
{{ formatCurrency(item.amount) }}
</td>
</tr>
</tbody>
</table>
<!-- ===== TOTALS ===== -->
<div
:style="{
display: 'flex',
justifyContent: config.layout.totalsPosition === 'center' ? 'center' : 'flex-end',
marginBottom: '4%',
}"
>
<div :style="{ width: '40%', fontSize: config.typography.bodySize * 0.9 + 'px' }">
<!-- Subtotal -->
<div :style="{ display: 'flex', justifyContent: 'space-between', marginBottom: '1%' }">
<span>Subtotal:</span>
<span :style="{ fontFamily: numberFont }">{{ formatCurrency(invoice.subtotal) }}</span>
</div>
<!-- Tax -->
<div v-if="invoice.tax_rate > 0" :style="{ display: 'flex', justifyContent: 'space-between', marginBottom: '1%' }">
<span>Tax ({{ invoice.tax_rate }}%):</span>
<span :style="{ fontFamily: numberFont }">{{ formatCurrency(invoice.tax_amount) }}</span>
</div>
<!-- Discount -->
<div v-if="invoice.discount > 0" :style="{ display: 'flex', justifyContent: 'space-between', marginBottom: '1%' }">
<span>Discount:</span>
<span :style="{ fontFamily: numberFont }">-{{ formatCurrency(invoice.discount) }}</span>
</div>
<!-- Separator -->
<div :style="{ borderBottom: '1px solid ' + config.colors.tableBorder, margin: '2% 0' }" />
<!-- Total -->
<div :style="{
display: 'flex',
justifyContent: 'space-between',
fontWeight: 'bold',
fontSize: config.typography.bodySize * 1.1 + 'px',
color: config.colors.totalHighlight,
}">
<span>Total:</span>
<span :style="{ fontFamily: numberFont }">{{ formatCurrency(invoice.total) }}</span>
</div>
</div>
</div>
<!-- ===== NOTES ===== -->
<div v-if="invoice.notes" :style="{ borderTop: '1px solid ' + config.colors.tableBorder, paddingTop: '2%' }">
<div :style="{ fontWeight: 'bold', marginBottom: '1%' }">Notes:</div>
<div :style="{ fontSize: config.typography.bodySize * 0.8 + 'px', whiteSpace: 'pre-wrap' }">{{ invoice.notes }}</div>
</div>
</div>
</div>
</div>
</template>