1575 lines
90 KiB
Vue
1575 lines
90 KiB
Vue
<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 c = computed(() => props.template.colors)
|
|
const clientName = computed(() => props.client?.name || 'Client Name')
|
|
const clientEmail = computed(() => props.client?.email || '')
|
|
const clientCompany = computed(() => props.client?.company || '')
|
|
const clientAddress = computed(() => props.client?.address || '')
|
|
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') : [])
|
|
|
|
void clientAddress
|
|
</script>
|
|
|
|
<template>
|
|
<!-- ================================================================ -->
|
|
<!-- TEMPLATE 1: CLEAN -->
|
|
<!-- ================================================================ -->
|
|
<div
|
|
v-if="template.id === 'clean'"
|
|
:style="{
|
|
backgroundColor: c.background,
|
|
color: c.bodyText,
|
|
fontFamily: '-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif',
|
|
position: 'relative',
|
|
aspectRatio: '210 / 297',
|
|
width: '100%',
|
|
overflow: 'hidden',
|
|
fontSize: '7.5px',
|
|
lineHeight: '1.5',
|
|
}"
|
|
>
|
|
<div :style="{ padding: '6%' }">
|
|
<!-- Logo + Biz Name -->
|
|
<div :style="{ marginBottom: '10px' }">
|
|
<img
|
|
v-if="biz?.logo"
|
|
:src="biz.logo"
|
|
:style="{ width: '28px', height: '28px', objectFit: 'contain', marginBottom: '4px', display: 'block' }"
|
|
/>
|
|
<div :style="{ fontSize: '13px', fontWeight: '600', color: c.headerText }">
|
|
{{ biz?.name || 'Your Business' }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- INVOICE title + accent line -->
|
|
<div :style="{ marginBottom: '14px' }">
|
|
<div :style="{ fontSize: '20px', fontWeight: '700', color: '#64748b', letterSpacing: '0.02em' }">
|
|
INVOICE
|
|
</div>
|
|
<div :style="{ width: '30%', height: '1px', backgroundColor: c.secondary, marginTop: '4px' }" />
|
|
</div>
|
|
|
|
<!-- From / To -->
|
|
<div :style="{ display: 'flex', gap: '6%', marginBottom: '14px' }">
|
|
<div :style="{ flex: 1 }">
|
|
<div :style="{ fontSize: '8px', fontWeight: '600', textTransform: 'uppercase', letterSpacing: '0.05em', color: c.secondary, marginBottom: '3px' }">From</div>
|
|
<div :style="{ fontSize: '8px', fontWeight: '600' }">{{ biz?.name || 'Your Business' }}</div>
|
|
<div v-for="(line, i) in bizAddressLines" :key="'bf'+i">{{ line }}</div>
|
|
<div v-if="biz?.email">{{ biz.email }}</div>
|
|
<div v-if="biz?.phone">{{ biz.phone }}</div>
|
|
</div>
|
|
<div :style="{ flex: 1 }">
|
|
<div :style="{ fontSize: '8px', fontWeight: '600', textTransform: 'uppercase', letterSpacing: '0.05em', color: c.secondary, marginBottom: '3px' }">To</div>
|
|
<div :style="{ fontSize: '8px', fontWeight: '600' }">{{ clientName }}</div>
|
|
<div v-if="clientCompany">{{ clientCompany }}</div>
|
|
<div v-for="(line, i) in clientAddressLines" :key="'ct'+i">{{ line }}</div>
|
|
<div v-if="clientEmail">{{ clientEmail }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Invoice meta -->
|
|
<div :style="{ display: 'flex', gap: '12px', marginBottom: '14px', fontSize: '7.5px' }">
|
|
<div><span :style="{ fontWeight: '600' }">Invoice #:</span> {{ invoice.invoice_number }}</div>
|
|
<div><span :style="{ fontWeight: '600' }">Date:</span> {{ formatDate(invoice.date) }}</div>
|
|
<div v-if="invoice.due_date"><span :style="{ fontWeight: '600' }">Due:</span> {{ formatDate(invoice.due_date) }}</div>
|
|
</div>
|
|
|
|
<!-- Table -->
|
|
<table :style="{ width: '100%', borderCollapse: 'collapse', marginBottom: '12px' }">
|
|
<thead>
|
|
<tr :style="{ backgroundColor: c.tableHeaderBg }">
|
|
<th :style="{ textAlign: 'left', padding: '4px 4px', fontSize: '7px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText }">Description</th>
|
|
<th :style="{ textAlign: 'right', padding: '4px 4px', fontSize: '7px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText, width: '42px' }">Qty</th>
|
|
<th :style="{ textAlign: 'right', padding: '4px 4px', fontSize: '7px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText, width: '50px' }">Rate</th>
|
|
<th :style="{ textAlign: 'right', padding: '4px 4px', fontSize: '7px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText, width: '58px' }">Amount</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="(item, i) in displayItems" :key="i" :style="{ borderBottom: '1px solid ' + c.tableBorder }">
|
|
<td :style="{ padding: '4px 4px' }">{{ item.description }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '4px 4px' }">{{ item.quantity }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '4px 4px' }">{{ formatCurrency(item.rate) }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '4px 4px' }">{{ formatCurrency(item.amount) }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- Totals -->
|
|
<div :style="{ display: 'flex', justifyContent: 'flex-end' }">
|
|
<div :style="{ width: '45%' }">
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Subtotal</span><span>{{ formatCurrency(invoice.subtotal) }}</span>
|
|
</div>
|
|
<div v-if="invoice.tax_rate > 0" :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Tax ({{ invoice.tax_rate }}%)</span><span>{{ formatCurrency(invoice.tax_amount) }}</span>
|
|
</div>
|
|
<div v-if="invoice.discount > 0" :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Discount</span><span>-{{ formatCurrency(invoice.discount) }}</span>
|
|
</div>
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', padding: '4px 0', borderTop: '1px solid ' + c.tableBorder, marginTop: '4px', fontSize: '12px', fontWeight: '700', color: c.secondary }">
|
|
<span>Total</span><span>{{ formatCurrency(invoice.total) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Notes -->
|
|
<div v-if="invoice.notes" :style="{ marginTop: '14px', fontSize: '6.5px', color: c.bodyText, opacity: 0.7 }">
|
|
<div :style="{ fontWeight: '600', marginBottom: '2px' }">Notes</div>
|
|
{{ invoice.notes }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ================================================================ -->
|
|
<!-- TEMPLATE 2: PROFESSIONAL -->
|
|
<!-- ================================================================ -->
|
|
<div
|
|
v-else-if="template.id === 'professional'"
|
|
:style="{
|
|
backgroundColor: c.background,
|
|
color: c.bodyText,
|
|
fontFamily: '-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif',
|
|
position: 'relative',
|
|
aspectRatio: '210 / 297',
|
|
width: '100%',
|
|
overflow: 'hidden',
|
|
fontSize: '7.5px',
|
|
lineHeight: '1.5',
|
|
}"
|
|
>
|
|
<!-- Navy header band -->
|
|
<div :style="{ backgroundColor: c.headerBg, color: c.headerText, padding: '5% 6% 4%', minHeight: '17%', display: 'flex', flexDirection: 'column', justifyContent: 'center' }">
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }">
|
|
<div>
|
|
<div :style="{ fontSize: '22px', fontWeight: '700', lineHeight: '1.2' }">INVOICE</div>
|
|
<div :style="{ fontSize: '7.5px', marginTop: '4px', opacity: 0.8 }">
|
|
#{{ invoice.invoice_number }} · {{ formatDate(invoice.date) }}
|
|
<span v-if="invoice.due_date"> · Due {{ formatDate(invoice.due_date) }}</span>
|
|
</div>
|
|
</div>
|
|
<div :style="{ textAlign: 'right' }">
|
|
<img v-if="biz?.logo" :src="biz.logo" :style="{ width: '24px', height: '24px', objectFit: 'contain', marginBottom: '2px', marginLeft: 'auto', display: 'block' }" />
|
|
<div :style="{ fontSize: '12px', fontWeight: '600' }">{{ biz?.name || 'Your Business' }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div :style="{ padding: '4% 6%' }">
|
|
<!-- From / To -->
|
|
<div :style="{ display: 'flex', gap: '6%', marginBottom: '14px' }">
|
|
<div :style="{ flex: 1 }">
|
|
<div :style="{ fontSize: '8px', fontWeight: '600', textTransform: 'uppercase', letterSpacing: '0.05em', color: c.headerBg, marginBottom: '3px' }">From</div>
|
|
<div :style="{ fontSize: '8px', fontWeight: '600' }">{{ biz?.name || 'Your Business' }}</div>
|
|
<div v-for="(line, i) in bizAddressLines" :key="'pf'+i">{{ line }}</div>
|
|
<div v-if="biz?.email">{{ biz.email }}</div>
|
|
<div v-if="biz?.phone">{{ biz.phone }}</div>
|
|
</div>
|
|
<div :style="{ flex: 1 }">
|
|
<div :style="{ fontSize: '8px', fontWeight: '600', textTransform: 'uppercase', letterSpacing: '0.05em', color: c.headerBg, marginBottom: '3px' }">Bill To</div>
|
|
<div :style="{ fontSize: '8px', fontWeight: '600' }">{{ clientName }}</div>
|
|
<div v-if="clientCompany">{{ clientCompany }}</div>
|
|
<div v-for="(line, i) in clientAddressLines" :key="'pt'+i">{{ line }}</div>
|
|
<div v-if="clientEmail">{{ clientEmail }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Table -->
|
|
<table :style="{ width: '100%', borderCollapse: 'collapse', marginBottom: '12px' }">
|
|
<thead>
|
|
<tr :style="{ backgroundColor: c.tableHeaderBg }">
|
|
<th :style="{ textAlign: 'left', padding: '5px 4px', fontSize: '7px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText }">Description</th>
|
|
<th :style="{ textAlign: 'right', padding: '5px 4px', fontSize: '7px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText, width: '42px' }">Qty</th>
|
|
<th :style="{ textAlign: 'right', padding: '5px 4px', fontSize: '7px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText, width: '50px' }">Rate</th>
|
|
<th :style="{ textAlign: 'right', padding: '5px 4px', fontSize: '7px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText, width: '58px' }">Amount</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="(item, i) in displayItems" :key="i" :style="{ backgroundColor: i % 2 === 1 ? c.tableRowAlt : 'transparent' }">
|
|
<td :style="{ padding: '4px 4px' }">{{ item.description }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '4px 4px' }">{{ item.quantity }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '4px 4px' }">{{ formatCurrency(item.rate) }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '4px 4px' }">{{ formatCurrency(item.amount) }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- Totals -->
|
|
<div :style="{ display: 'flex', justifyContent: 'flex-end' }">
|
|
<div :style="{ width: '45%' }">
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Subtotal</span><span>{{ formatCurrency(invoice.subtotal) }}</span>
|
|
</div>
|
|
<div v-if="invoice.tax_rate > 0" :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Tax ({{ invoice.tax_rate }}%)</span><span>{{ formatCurrency(invoice.tax_amount) }}</span>
|
|
</div>
|
|
<div v-if="invoice.discount > 0" :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Discount</span><span>-{{ formatCurrency(invoice.discount) }}</span>
|
|
</div>
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', padding: '4px 0', borderTop: '2px solid ' + c.headerBg, marginTop: '4px', fontSize: '13px', fontWeight: '700', color: c.totalHighlight }">
|
|
<span>Total</span><span>{{ formatCurrency(invoice.total) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="invoice.notes" :style="{ marginTop: '14px', fontSize: '6.5px', color: c.bodyText, opacity: 0.7 }">
|
|
<div :style="{ fontWeight: '600', marginBottom: '2px' }">Notes</div>
|
|
{{ invoice.notes }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ================================================================ -->
|
|
<!-- TEMPLATE 3: BOLD -->
|
|
<!-- ================================================================ -->
|
|
<div
|
|
v-else-if="template.id === 'bold'"
|
|
:style="{
|
|
backgroundColor: c.background,
|
|
color: c.bodyText,
|
|
fontFamily: '-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif',
|
|
position: 'relative',
|
|
aspectRatio: '210 / 297',
|
|
width: '100%',
|
|
overflow: 'hidden',
|
|
fontSize: '7.5px',
|
|
lineHeight: '1.5',
|
|
}"
|
|
>
|
|
<!-- Top section: indigo block + biz info -->
|
|
<div :style="{ display: 'flex', minHeight: '17%' }">
|
|
<!-- Large indigo rectangle -->
|
|
<div :style="{ width: '55%', backgroundColor: c.headerBg, color: c.headerText, padding: '5% 5%', display: 'flex', flexDirection: 'column', justifyContent: 'center' }">
|
|
<div :style="{ fontSize: '24px', fontWeight: '800', lineHeight: '1.1' }">INVOICE</div>
|
|
<div :style="{ fontSize: '8px', marginTop: '4px', opacity: 0.85 }">#{{ invoice.invoice_number }}</div>
|
|
</div>
|
|
<!-- Biz info outside the block -->
|
|
<div :style="{ flex: 1, padding: '5% 5%', display: 'flex', flexDirection: 'column', justifyContent: 'center' }">
|
|
<img v-if="biz?.logo" :src="biz.logo" :style="{ width: '22px', height: '22px', objectFit: 'contain', marginBottom: '3px' }" />
|
|
<div :style="{ fontSize: '9px', fontWeight: '600', color: c.headerText }">{{ biz?.name || 'Your Business' }}</div>
|
|
<div v-for="(line, i) in bizAddressLines" :key="'blf'+i" :style="{ fontSize: '7px' }">{{ line }}</div>
|
|
<div v-if="biz?.email" :style="{ fontSize: '7px' }">{{ biz.email }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div :style="{ padding: '4% 5%' }">
|
|
<!-- Dates -->
|
|
<div :style="{ display: 'flex', gap: '12px', marginBottom: '10px', fontSize: '7.5px' }">
|
|
<div><span :style="{ fontWeight: '600' }">Date:</span> {{ formatDate(invoice.date) }}</div>
|
|
<div v-if="invoice.due_date"><span :style="{ fontWeight: '600' }">Due:</span> {{ formatDate(invoice.due_date) }}</div>
|
|
</div>
|
|
|
|
<!-- From / To -->
|
|
<div :style="{ display: 'flex', gap: '6%', marginBottom: '14px' }">
|
|
<div :style="{ flex: 1 }">
|
|
<div :style="{ fontSize: '8px', fontWeight: '600', textTransform: 'uppercase', letterSpacing: '0.05em', color: c.primary, marginBottom: '3px' }">From</div>
|
|
<div :style="{ fontSize: '8px', fontWeight: '600' }">{{ biz?.name || 'Your Business' }}</div>
|
|
<div v-for="(line, i) in bizAddressLines" :key="'blf2'+i">{{ line }}</div>
|
|
<div v-if="biz?.phone">{{ biz.phone }}</div>
|
|
</div>
|
|
<div :style="{ flex: 1 }">
|
|
<div :style="{ fontSize: '8px', fontWeight: '600', textTransform: 'uppercase', letterSpacing: '0.05em', color: c.primary, marginBottom: '3px' }">To</div>
|
|
<div :style="{ fontSize: '8px', fontWeight: '600' }">{{ clientName }}</div>
|
|
<div v-if="clientCompany">{{ clientCompany }}</div>
|
|
<div v-for="(line, i) in clientAddressLines" :key="'blt'+i">{{ line }}</div>
|
|
<div v-if="clientEmail">{{ clientEmail }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Table -->
|
|
<table :style="{ width: '100%', borderCollapse: 'collapse', marginBottom: '12px' }">
|
|
<thead>
|
|
<tr :style="{ backgroundColor: c.tableHeaderBg }">
|
|
<th :style="{ textAlign: 'left', padding: '5px 4px', fontSize: '7px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText }">Description</th>
|
|
<th :style="{ textAlign: 'right', padding: '5px 4px', fontSize: '7px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText, width: '42px' }">Qty</th>
|
|
<th :style="{ textAlign: 'right', padding: '5px 4px', fontSize: '7px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText, width: '50px' }">Rate</th>
|
|
<th :style="{ textAlign: 'right', padding: '5px 4px', fontSize: '7px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText, width: '58px' }">Amount</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="(item, i) in displayItems" :key="i" :style="{ lineHeight: '2' }">
|
|
<td :style="{ padding: '3px 4px' }">{{ item.description }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '3px 4px' }">{{ item.quantity }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '3px 4px' }">{{ formatCurrency(item.rate) }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '3px 4px' }">{{ formatCurrency(item.amount) }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- Totals -->
|
|
<div :style="{ display: 'flex', justifyContent: 'flex-end' }">
|
|
<div :style="{ width: '45%' }">
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Subtotal</span><span>{{ formatCurrency(invoice.subtotal) }}</span>
|
|
</div>
|
|
<div v-if="invoice.tax_rate > 0" :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Tax ({{ invoice.tax_rate }}%)</span><span>{{ formatCurrency(invoice.tax_amount) }}</span>
|
|
</div>
|
|
<div v-if="invoice.discount > 0" :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Discount</span><span>-{{ formatCurrency(invoice.discount) }}</span>
|
|
</div>
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', padding: '4px 0', marginTop: '4px', fontSize: '16px', fontWeight: '700', color: c.totalHighlight }">
|
|
<span>Total</span><span>{{ formatCurrency(invoice.total) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="invoice.notes" :style="{ marginTop: '14px', fontSize: '6.5px', color: c.bodyText, opacity: 0.7 }">
|
|
<div :style="{ fontWeight: '600', marginBottom: '2px' }">Notes</div>
|
|
{{ invoice.notes }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ================================================================ -->
|
|
<!-- TEMPLATE 4: MINIMAL -->
|
|
<!-- ================================================================ -->
|
|
<div
|
|
v-else-if="template.id === 'minimal'"
|
|
:style="{
|
|
backgroundColor: c.background,
|
|
color: c.bodyText,
|
|
fontFamily: '-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif',
|
|
position: 'relative',
|
|
aspectRatio: '210 / 297',
|
|
width: '100%',
|
|
overflow: 'hidden',
|
|
fontSize: '7.5px',
|
|
lineHeight: '1.5',
|
|
}"
|
|
>
|
|
<div :style="{ padding: '6%' }">
|
|
<!-- Centered header -->
|
|
<div :style="{ textAlign: 'center', marginBottom: '10px' }">
|
|
<img v-if="biz?.logo" :src="biz.logo" :style="{ width: '26px', height: '26px', objectFit: 'contain', margin: '0 auto 6px', display: 'block' }" />
|
|
<div :style="{ height: '1px', backgroundColor: '#e4e4e7', marginBottom: '8px' }" />
|
|
<div :style="{ fontSize: '20px', fontWeight: '700', color: '#18181b', letterSpacing: '0.04em' }">INVOICE</div>
|
|
<div :style="{ fontSize: '9px', fontWeight: '600', color: '#18181b', marginTop: '2px' }">{{ biz?.name || 'Your Business' }}</div>
|
|
<div :style="{ height: '1px', backgroundColor: '#e4e4e7', marginTop: '8px' }" />
|
|
</div>
|
|
|
|
<!-- Invoice meta centered -->
|
|
<div :style="{ textAlign: 'center', marginBottom: '12px', fontSize: '7.5px', color: '#3f3f46' }">
|
|
#{{ invoice.invoice_number }} · {{ formatDate(invoice.date) }}
|
|
<span v-if="invoice.due_date"> · Due {{ formatDate(invoice.due_date) }}</span>
|
|
</div>
|
|
|
|
<!-- From / To -->
|
|
<div :style="{ display: 'flex', gap: '6%', marginBottom: '14px' }">
|
|
<div :style="{ flex: 1 }">
|
|
<div :style="{ fontSize: '8px', fontWeight: '600', textTransform: 'uppercase', letterSpacing: '0.05em', color: '#18181b', marginBottom: '3px' }">From</div>
|
|
<div :style="{ fontSize: '8px', fontWeight: '600', color: '#18181b' }">{{ biz?.name || 'Your Business' }}</div>
|
|
<div v-for="(line, i) in bizAddressLines" :key="'mf'+i">{{ line }}</div>
|
|
<div v-if="biz?.email">{{ biz.email }}</div>
|
|
<div v-if="biz?.phone">{{ biz.phone }}</div>
|
|
</div>
|
|
<div :style="{ flex: 1 }">
|
|
<div :style="{ fontSize: '8px', fontWeight: '600', textTransform: 'uppercase', letterSpacing: '0.05em', color: '#18181b', marginBottom: '3px' }">To</div>
|
|
<div :style="{ fontSize: '8px', fontWeight: '600', color: '#18181b' }">{{ clientName }}</div>
|
|
<div v-if="clientCompany">{{ clientCompany }}</div>
|
|
<div v-for="(line, i) in clientAddressLines" :key="'mt'+i">{{ line }}</div>
|
|
<div v-if="clientEmail">{{ clientEmail }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Table: NO backgrounds, whitespace-only separation -->
|
|
<table :style="{ width: '100%', borderCollapse: 'collapse', marginBottom: '12px' }">
|
|
<thead>
|
|
<tr>
|
|
<th :style="{ textAlign: 'left', padding: '6px 4px', fontSize: '7px', fontWeight: '700', textTransform: 'uppercase', color: '#18181b', letterSpacing: '0.03em' }">Description</th>
|
|
<th :style="{ textAlign: 'right', padding: '6px 4px', fontSize: '7px', fontWeight: '700', textTransform: 'uppercase', color: '#18181b', width: '42px' }">Qty</th>
|
|
<th :style="{ textAlign: 'right', padding: '6px 4px', fontSize: '7px', fontWeight: '700', textTransform: 'uppercase', color: '#18181b', width: '50px' }">Rate</th>
|
|
<th :style="{ textAlign: 'right', padding: '6px 4px', fontSize: '7px', fontWeight: '700', textTransform: 'uppercase', color: '#18181b', width: '58px' }">Amount</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="(item, i) in displayItems" :key="i">
|
|
<td :style="{ padding: '6px 4px' }">{{ item.description }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '6px 4px' }">{{ item.quantity }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '6px 4px' }">{{ formatCurrency(item.rate) }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '6px 4px' }">{{ formatCurrency(item.amount) }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- Totals centered -->
|
|
<div :style="{ textAlign: 'center' }">
|
|
<div :style="{ display: 'inline-block', textAlign: 'right' }">
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', gap: '16px', padding: '2px 0' }">
|
|
<span>Subtotal</span><span>{{ formatCurrency(invoice.subtotal) }}</span>
|
|
</div>
|
|
<div v-if="invoice.tax_rate > 0" :style="{ display: 'flex', justifyContent: 'space-between', gap: '16px', padding: '2px 0' }">
|
|
<span>Tax ({{ invoice.tax_rate }}%)</span><span>{{ formatCurrency(invoice.tax_amount) }}</span>
|
|
</div>
|
|
<div v-if="invoice.discount > 0" :style="{ display: 'flex', justifyContent: 'space-between', gap: '16px', padding: '2px 0' }">
|
|
<span>Discount</span><span>-{{ formatCurrency(invoice.discount) }}</span>
|
|
</div>
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', gap: '16px', padding: '4px 0', marginTop: '4px', fontSize: '13px', fontWeight: '700', color: '#18181b' }">
|
|
<span>Total</span><span>{{ formatCurrency(invoice.total) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="invoice.notes" :style="{ marginTop: '14px', fontSize: '6.5px', color: '#3f3f46', opacity: 0.7, textAlign: 'center' }">
|
|
<div :style="{ fontWeight: '600', marginBottom: '2px' }">Notes</div>
|
|
{{ invoice.notes }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ================================================================ -->
|
|
<!-- TEMPLATE 5: CLASSIC -->
|
|
<!-- ================================================================ -->
|
|
<div
|
|
v-else-if="template.id === 'classic'"
|
|
:style="{
|
|
backgroundColor: c.background,
|
|
color: c.bodyText,
|
|
fontFamily: '-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif',
|
|
position: 'relative',
|
|
aspectRatio: '210 / 297',
|
|
width: '100%',
|
|
overflow: 'hidden',
|
|
fontSize: '7.5px',
|
|
lineHeight: '1.5',
|
|
}"
|
|
>
|
|
<div :style="{ padding: '6%' }">
|
|
<!-- Traditional two-column header -->
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '6px' }">
|
|
<div>
|
|
<img v-if="biz?.logo" :src="biz.logo" :style="{ width: '24px', height: '24px', objectFit: 'contain', marginBottom: '3px' }" />
|
|
<div :style="{ fontSize: '11px', fontWeight: '600', color: c.headerText }">{{ biz?.name || 'Your Business' }}</div>
|
|
<div v-for="(line, i) in bizAddressLines" :key="'clf'+i">{{ line }}</div>
|
|
<div v-if="biz?.email">{{ biz.email }}</div>
|
|
<div v-if="biz?.phone">{{ biz.phone }}</div>
|
|
</div>
|
|
<div :style="{ textAlign: 'right' }">
|
|
<div :style="{ fontSize: '20px', fontWeight: '700', color: c.primary }">INVOICE</div>
|
|
<div :style="{ fontSize: '8px', marginTop: '2px' }"><span :style="{ fontWeight: '600' }">#</span>{{ invoice.invoice_number }}</div>
|
|
<div :style="{ fontSize: '7.5px', marginTop: '1px' }">{{ formatDate(invoice.date) }}</div>
|
|
<div v-if="invoice.due_date" :style="{ fontSize: '7.5px' }">Due: {{ formatDate(invoice.due_date) }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Burgundy rule -->
|
|
<div :style="{ height: '2px', backgroundColor: c.primary, marginBottom: '12px' }" />
|
|
|
|
<!-- Bill To -->
|
|
<div :style="{ marginBottom: '14px' }">
|
|
<div :style="{ fontSize: '9px', fontWeight: '600', textTransform: 'uppercase', letterSpacing: '0.05em', color: c.primary, marginBottom: '3px' }">Bill To</div>
|
|
<div :style="{ fontSize: '8px', fontWeight: '600' }">{{ clientName }}</div>
|
|
<div v-if="clientCompany">{{ clientCompany }}</div>
|
|
<div v-for="(line, i) in clientAddressLines" :key="'clt'+i">{{ line }}</div>
|
|
<div v-if="clientEmail">{{ clientEmail }}</div>
|
|
</div>
|
|
|
|
<!-- Table: bordered grid -->
|
|
<table :style="{ width: '100%', borderCollapse: 'collapse', marginBottom: '12px', border: '1px solid ' + c.tableBorder }">
|
|
<thead>
|
|
<tr :style="{ backgroundColor: c.tableHeaderBg }">
|
|
<th :style="{ textAlign: 'left', padding: '4px 4px', fontSize: '7px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText, border: '1px solid ' + c.tableBorder }">Description</th>
|
|
<th :style="{ textAlign: 'right', padding: '4px 4px', fontSize: '7px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText, width: '42px', border: '1px solid ' + c.tableBorder }">Qty</th>
|
|
<th :style="{ textAlign: 'right', padding: '4px 4px', fontSize: '7px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText, width: '50px', border: '1px solid ' + c.tableBorder }">Rate</th>
|
|
<th :style="{ textAlign: 'right', padding: '4px 4px', fontSize: '7px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText, width: '58px', border: '1px solid ' + c.tableBorder }">Amount</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="(item, i) in displayItems" :key="i" :style="{ backgroundColor: i % 2 === 1 ? c.tableRowAlt : 'transparent' }">
|
|
<td :style="{ padding: '4px 4px', border: '1px solid ' + c.tableBorder }">{{ item.description }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '4px 4px', border: '1px solid ' + c.tableBorder }">{{ item.quantity }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '4px 4px', border: '1px solid ' + c.tableBorder }">{{ formatCurrency(item.rate) }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '4px 4px', border: '1px solid ' + c.tableBorder }">{{ formatCurrency(item.amount) }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- Totals -->
|
|
<div :style="{ display: 'flex', justifyContent: 'flex-end' }">
|
|
<div :style="{ width: '45%' }">
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Subtotal</span><span>{{ formatCurrency(invoice.subtotal) }}</span>
|
|
</div>
|
|
<div v-if="invoice.tax_rate > 0" :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Tax ({{ invoice.tax_rate }}%)</span><span>{{ formatCurrency(invoice.tax_amount) }}</span>
|
|
</div>
|
|
<div v-if="invoice.discount > 0" :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Discount</span><span>-{{ formatCurrency(invoice.discount) }}</span>
|
|
</div>
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', padding: '4px 0', borderTop: '2px solid ' + c.primary, marginTop: '4px', fontSize: '13px', fontWeight: '700', color: c.totalHighlight }">
|
|
<span>Total</span><span>{{ formatCurrency(invoice.total) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="invoice.notes" :style="{ marginTop: '14px', fontSize: '6.5px', color: c.bodyText, opacity: 0.7 }">
|
|
<div :style="{ fontWeight: '600', marginBottom: '2px' }">Notes</div>
|
|
{{ invoice.notes }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ================================================================ -->
|
|
<!-- TEMPLATE 6: MODERN -->
|
|
<!-- ================================================================ -->
|
|
<div
|
|
v-else-if="template.id === 'modern'"
|
|
:style="{
|
|
backgroundColor: c.background,
|
|
color: c.bodyText,
|
|
fontFamily: '-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif',
|
|
position: 'relative',
|
|
aspectRatio: '210 / 297',
|
|
width: '100%',
|
|
overflow: 'hidden',
|
|
fontSize: '7.5px',
|
|
lineHeight: '1.5',
|
|
}"
|
|
>
|
|
<div :style="{ padding: '6%' }">
|
|
<!-- Asymmetric header -->
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '6px' }">
|
|
<div>
|
|
<div :style="{ fontSize: '22px', fontWeight: '700', color: c.headerText, lineHeight: '1.1' }">INVOICE</div>
|
|
<div :style="{ fontSize: '7.5px', marginTop: '3px', color: c.bodyText }">
|
|
#{{ invoice.invoice_number }} · {{ formatDate(invoice.date) }}
|
|
</div>
|
|
<div v-if="invoice.due_date" :style="{ fontSize: '7.5px', color: c.bodyText }">Due {{ formatDate(invoice.due_date) }}</div>
|
|
</div>
|
|
<div :style="{ textAlign: 'right' }">
|
|
<img v-if="biz?.logo" :src="biz.logo" :style="{ width: '22px', height: '22px', objectFit: 'contain', marginBottom: '2px', marginLeft: 'auto', display: 'block' }" />
|
|
<div :style="{ fontSize: '10px', fontWeight: '600', color: c.headerText }">{{ biz?.name || 'Your Business' }}</div>
|
|
<div v-for="(line, i) in bizAddressLines" :key="'modf'+i" :style="{ fontSize: '7px' }">{{ line }}</div>
|
|
<div v-if="biz?.email" :style="{ fontSize: '7px' }">{{ biz.email }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Thin teal line -->
|
|
<div :style="{ height: '1px', backgroundColor: c.primary, marginBottom: '12px' }" />
|
|
|
|
<!-- From / To -->
|
|
<div :style="{ display: 'flex', gap: '6%', marginBottom: '14px' }">
|
|
<div :style="{ flex: 1 }">
|
|
<div :style="{ fontSize: '8px', fontWeight: '600', textTransform: 'uppercase', letterSpacing: '0.05em', color: c.primary, marginBottom: '3px' }">From</div>
|
|
<div :style="{ fontSize: '8px', fontWeight: '600' }">{{ biz?.name || 'Your Business' }}</div>
|
|
<div v-for="(line, i) in bizAddressLines" :key="'modf2'+i">{{ line }}</div>
|
|
<div v-if="biz?.phone">{{ biz.phone }}</div>
|
|
</div>
|
|
<div :style="{ flex: 1 }">
|
|
<div :style="{ fontSize: '8px', fontWeight: '600', textTransform: 'uppercase', letterSpacing: '0.05em', color: c.primary, marginBottom: '3px' }">To</div>
|
|
<div :style="{ fontSize: '8px', fontWeight: '600' }">{{ clientName }}</div>
|
|
<div v-if="clientCompany">{{ clientCompany }}</div>
|
|
<div v-for="(line, i) in clientAddressLines" :key="'modt'+i">{{ line }}</div>
|
|
<div v-if="clientEmail">{{ clientEmail }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Table: borderless, teal bottom borders, teal header TEXT -->
|
|
<table :style="{ width: '100%', borderCollapse: 'collapse', marginBottom: '12px' }">
|
|
<thead>
|
|
<tr>
|
|
<th :style="{ textAlign: 'left', padding: '4px 4px', fontSize: '7px', fontWeight: '600', textTransform: 'uppercase', color: c.tableHeaderText, borderBottom: '1px solid ' + c.primary }">Description</th>
|
|
<th :style="{ textAlign: 'right', padding: '4px 4px', fontSize: '7px', fontWeight: '600', textTransform: 'uppercase', color: c.tableHeaderText, width: '42px', borderBottom: '1px solid ' + c.primary }">Qty</th>
|
|
<th :style="{ textAlign: 'right', padding: '4px 4px', fontSize: '7px', fontWeight: '600', textTransform: 'uppercase', color: c.tableHeaderText, width: '50px', borderBottom: '1px solid ' + c.primary }">Rate</th>
|
|
<th :style="{ textAlign: 'right', padding: '4px 4px', fontSize: '7px', fontWeight: '600', textTransform: 'uppercase', color: c.tableHeaderText, width: '58px', borderBottom: '1px solid ' + c.primary }">Amount</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="(item, i) in displayItems" :key="i" :style="{ borderBottom: '1px solid ' + c.tableBorder }">
|
|
<td :style="{ padding: '4px 4px' }">{{ item.description }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '4px 4px' }">{{ item.quantity }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '4px 4px' }">{{ formatCurrency(item.rate) }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '4px 4px' }">{{ formatCurrency(item.amount) }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- Totals with teal bg strip on total row -->
|
|
<div :style="{ display: 'flex', justifyContent: 'flex-end' }">
|
|
<div :style="{ width: '45%' }">
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Subtotal</span><span>{{ formatCurrency(invoice.subtotal) }}</span>
|
|
</div>
|
|
<div v-if="invoice.tax_rate > 0" :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Tax ({{ invoice.tax_rate }}%)</span><span>{{ formatCurrency(invoice.tax_amount) }}</span>
|
|
</div>
|
|
<div v-if="invoice.discount > 0" :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Discount</span><span>-{{ formatCurrency(invoice.discount) }}</span>
|
|
</div>
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', padding: '5px 4px', marginTop: '4px', fontSize: '13px', fontWeight: '700', color: c.totalHighlight, backgroundColor: c.tableRowAlt, borderRadius: '2px' }">
|
|
<span>Total</span><span>{{ formatCurrency(invoice.total) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="invoice.notes" :style="{ marginTop: '14px', fontSize: '6.5px', color: c.bodyText, opacity: 0.7 }">
|
|
<div :style="{ fontWeight: '600', marginBottom: '2px' }">Notes</div>
|
|
{{ invoice.notes }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ================================================================ -->
|
|
<!-- TEMPLATE 7: ELEGANT -->
|
|
<!-- ================================================================ -->
|
|
<div
|
|
v-else-if="template.id === 'elegant'"
|
|
:style="{
|
|
backgroundColor: c.background,
|
|
color: c.bodyText,
|
|
fontFamily: '-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif',
|
|
position: 'relative',
|
|
aspectRatio: '210 / 297',
|
|
width: '100%',
|
|
overflow: 'hidden',
|
|
fontSize: '7.5px',
|
|
lineHeight: '1.5',
|
|
}"
|
|
>
|
|
<div :style="{ padding: '6%' }">
|
|
<!-- Gold double-rule at top -->
|
|
<div :style="{ marginBottom: '10px' }">
|
|
<div :style="{ height: '1px', backgroundColor: c.primary }" />
|
|
<div :style="{ height: '1.5px' }" />
|
|
<div :style="{ height: '1px', backgroundColor: c.primary }" />
|
|
</div>
|
|
|
|
<!-- Centered header -->
|
|
<div :style="{ textAlign: 'center', marginBottom: '8px' }">
|
|
<img v-if="biz?.logo" :src="biz.logo" :style="{ width: '24px', height: '24px', objectFit: 'contain', margin: '0 auto 4px', display: 'block' }" />
|
|
<div :style="{ fontSize: '20px', fontWeight: '700', color: c.headerText, letterSpacing: '0.06em' }">INVOICE</div>
|
|
<div :style="{ fontSize: '10px', fontWeight: '600', color: c.headerText, marginTop: '2px' }">{{ biz?.name || 'Your Business' }}</div>
|
|
<div :style="{ fontSize: '7.5px', marginTop: '2px' }">
|
|
#{{ invoice.invoice_number }} · {{ formatDate(invoice.date) }}
|
|
<span v-if="invoice.due_date"> · Due {{ formatDate(invoice.due_date) }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Gold double-rule -->
|
|
<div :style="{ marginBottom: '12px' }">
|
|
<div :style="{ height: '1px', backgroundColor: c.primary }" />
|
|
<div :style="{ height: '1.5px' }" />
|
|
<div :style="{ height: '1px', backgroundColor: c.primary }" />
|
|
</div>
|
|
|
|
<!-- From / To -->
|
|
<div :style="{ display: 'flex', gap: '6%', marginBottom: '14px' }">
|
|
<div :style="{ flex: 1 }">
|
|
<div :style="{ fontSize: '8px', fontWeight: '600', textTransform: 'uppercase', letterSpacing: '0.05em', color: c.primary, marginBottom: '3px' }">From</div>
|
|
<div :style="{ fontSize: '8px', fontWeight: '600' }">{{ biz?.name || 'Your Business' }}</div>
|
|
<div v-for="(line, i) in bizAddressLines" :key="'ef'+i">{{ line }}</div>
|
|
<div v-if="biz?.email">{{ biz.email }}</div>
|
|
<div v-if="biz?.phone">{{ biz.phone }}</div>
|
|
</div>
|
|
<div :style="{ flex: 1 }">
|
|
<div :style="{ fontSize: '8px', fontWeight: '600', textTransform: 'uppercase', letterSpacing: '0.05em', color: c.primary, marginBottom: '3px' }">To</div>
|
|
<div :style="{ fontSize: '8px', fontWeight: '600' }">{{ clientName }}</div>
|
|
<div v-if="clientCompany">{{ clientCompany }}</div>
|
|
<div v-for="(line, i) in clientAddressLines" :key="'et'+i">{{ line }}</div>
|
|
<div v-if="clientEmail">{{ clientEmail }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Table: gold double-rule above/below header, single rule between rows -->
|
|
<table :style="{ width: '100%', borderCollapse: 'collapse', marginBottom: '12px' }">
|
|
<thead>
|
|
<tr>
|
|
<th :colspan="4" :style="{ padding: 0, height: '0' }">
|
|
<div :style="{ height: '1px', backgroundColor: c.primary }" />
|
|
<div :style="{ height: '1.5px' }" />
|
|
<div :style="{ height: '1px', backgroundColor: c.primary }" />
|
|
</th>
|
|
</tr>
|
|
<tr>
|
|
<th :style="{ textAlign: 'left', padding: '5px 4px', fontSize: '7px', fontWeight: '600', textTransform: 'uppercase', color: c.tableHeaderText }">Description</th>
|
|
<th :style="{ textAlign: 'right', padding: '5px 4px', fontSize: '7px', fontWeight: '600', textTransform: 'uppercase', color: c.tableHeaderText, width: '42px' }">Qty</th>
|
|
<th :style="{ textAlign: 'right', padding: '5px 4px', fontSize: '7px', fontWeight: '600', textTransform: 'uppercase', color: c.tableHeaderText, width: '50px' }">Rate</th>
|
|
<th :style="{ textAlign: 'right', padding: '5px 4px', fontSize: '7px', fontWeight: '600', textTransform: 'uppercase', color: c.tableHeaderText, width: '58px' }">Amount</th>
|
|
</tr>
|
|
<tr>
|
|
<th :colspan="4" :style="{ padding: 0, height: '0' }">
|
|
<div :style="{ height: '1px', backgroundColor: c.primary }" />
|
|
<div :style="{ height: '1.5px' }" />
|
|
<div :style="{ height: '1px', backgroundColor: c.primary }" />
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="(item, i) in displayItems" :key="i" :style="{ borderBottom: '1px solid ' + c.tableBorder }">
|
|
<td :style="{ padding: '4px 4px' }">{{ item.description }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '4px 4px' }">{{ item.quantity }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '4px 4px' }">{{ formatCurrency(item.rate) }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '4px 4px' }">{{ formatCurrency(item.amount) }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- Totals -->
|
|
<div :style="{ display: 'flex', justifyContent: 'flex-end' }">
|
|
<div :style="{ width: '45%' }">
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Subtotal</span><span>{{ formatCurrency(invoice.subtotal) }}</span>
|
|
</div>
|
|
<div v-if="invoice.tax_rate > 0" :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Tax ({{ invoice.tax_rate }}%)</span><span>{{ formatCurrency(invoice.tax_amount) }}</span>
|
|
</div>
|
|
<div v-if="invoice.discount > 0" :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Discount</span><span>-{{ formatCurrency(invoice.discount) }}</span>
|
|
</div>
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', padding: '4px 0', borderTop: '1px solid ' + c.primary, marginTop: '4px', fontSize: '13px', fontWeight: '700', color: c.totalHighlight }">
|
|
<span>Total</span><span>{{ formatCurrency(invoice.total) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="invoice.notes" :style="{ marginTop: '14px', fontSize: '6.5px', color: c.bodyText, opacity: 0.7 }">
|
|
<div :style="{ fontWeight: '600', marginBottom: '2px' }">Notes</div>
|
|
{{ invoice.notes }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ================================================================ -->
|
|
<!-- TEMPLATE 8: CREATIVE -->
|
|
<!-- ================================================================ -->
|
|
<div
|
|
v-else-if="template.id === 'creative'"
|
|
:style="{
|
|
backgroundColor: c.background,
|
|
color: c.bodyText,
|
|
fontFamily: '-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif',
|
|
position: 'relative',
|
|
aspectRatio: '210 / 297',
|
|
width: '100%',
|
|
overflow: 'hidden',
|
|
fontSize: '7.5px',
|
|
lineHeight: '1.5',
|
|
}"
|
|
>
|
|
<!-- Narrow purple sidebar -->
|
|
<div :style="{ position: 'absolute', left: '0', top: '0', bottom: '0', width: '3%', backgroundColor: c.primary }" />
|
|
|
|
<!-- Content offset past sidebar -->
|
|
<div :style="{ paddingLeft: '7%', paddingRight: '5%', paddingTop: '5%', paddingBottom: '5%' }">
|
|
<!-- Logo + Title -->
|
|
<div :style="{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '10px' }">
|
|
<img v-if="biz?.logo" :src="biz.logo" :style="{ width: '24px', height: '24px', objectFit: 'contain' }" />
|
|
<div>
|
|
<div :style="{ fontSize: '20px', fontWeight: '700', color: c.primary }">INVOICE</div>
|
|
<div :style="{ fontSize: '7.5px' }">#{{ invoice.invoice_number }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Biz name -->
|
|
<div :style="{ fontSize: '10px', fontWeight: '600', color: c.headerText, marginBottom: '3px' }">{{ biz?.name || 'Your Business' }}</div>
|
|
<div :style="{ fontSize: '7.5px', marginBottom: '10px' }">
|
|
{{ formatDate(invoice.date) }}
|
|
<span v-if="invoice.due_date"> · Due {{ formatDate(invoice.due_date) }}</span>
|
|
</div>
|
|
|
|
<!-- From / To -->
|
|
<div :style="{ display: 'flex', gap: '6%', marginBottom: '14px' }">
|
|
<div :style="{ flex: 1 }">
|
|
<div :style="{ fontSize: '8px', fontWeight: '600', textTransform: 'uppercase', letterSpacing: '0.05em', color: c.primary, marginBottom: '3px' }">From</div>
|
|
<div :style="{ fontSize: '8px', fontWeight: '600' }">{{ biz?.name || 'Your Business' }}</div>
|
|
<div v-for="(line, i) in bizAddressLines" :key="'crf'+i">{{ line }}</div>
|
|
<div v-if="biz?.email">{{ biz.email }}</div>
|
|
<div v-if="biz?.phone">{{ biz.phone }}</div>
|
|
</div>
|
|
<div :style="{ flex: 1 }">
|
|
<div :style="{ fontSize: '8px', fontWeight: '600', textTransform: 'uppercase', letterSpacing: '0.05em', color: c.primary, marginBottom: '3px' }">To</div>
|
|
<div :style="{ fontSize: '8px', fontWeight: '600' }">{{ clientName }}</div>
|
|
<div v-if="clientCompany">{{ clientCompany }}</div>
|
|
<div v-for="(line, i) in clientAddressLines" :key="'crt'+i">{{ line }}</div>
|
|
<div v-if="clientEmail">{{ clientEmail }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Table: card-style rows -->
|
|
<div :style="{ marginBottom: '12px' }">
|
|
<!-- Header -->
|
|
<div :style="{ display: 'flex', padding: '4px 6px', fontSize: '7px', fontWeight: '600', textTransform: 'uppercase', color: c.tableHeaderText }">
|
|
<div :style="{ flex: 1 }">Description</div>
|
|
<div :style="{ width: '42px', textAlign: 'right' }">Qty</div>
|
|
<div :style="{ width: '50px', textAlign: 'right' }">Rate</div>
|
|
<div :style="{ width: '58px', textAlign: 'right' }">Amount</div>
|
|
</div>
|
|
<!-- Rows as subtle cards -->
|
|
<div
|
|
v-for="(item, i) in displayItems"
|
|
:key="i"
|
|
:style="{ display: 'flex', padding: '5px 6px', backgroundColor: c.tableRowAlt, borderRadius: '2px', marginTop: '2px' }"
|
|
>
|
|
<div :style="{ flex: 1 }">{{ item.description }}</div>
|
|
<div :style="{ width: '42px', textAlign: 'right' }">{{ item.quantity }}</div>
|
|
<div :style="{ width: '50px', textAlign: 'right' }">{{ formatCurrency(item.rate) }}</div>
|
|
<div :style="{ width: '58px', textAlign: 'right' }">{{ formatCurrency(item.amount) }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Totals -->
|
|
<div :style="{ display: 'flex', justifyContent: 'flex-end' }">
|
|
<div :style="{ width: '45%' }">
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Subtotal</span><span>{{ formatCurrency(invoice.subtotal) }}</span>
|
|
</div>
|
|
<div v-if="invoice.tax_rate > 0" :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Tax ({{ invoice.tax_rate }}%)</span><span>{{ formatCurrency(invoice.tax_amount) }}</span>
|
|
</div>
|
|
<div v-if="invoice.discount > 0" :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Discount</span><span>-{{ formatCurrency(invoice.discount) }}</span>
|
|
</div>
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', padding: '4px 0', marginTop: '4px', fontSize: '14px', fontWeight: '700', color: c.totalHighlight }">
|
|
<span>Total</span><span>{{ formatCurrency(invoice.total) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="invoice.notes" :style="{ marginTop: '14px', fontSize: '6.5px', color: c.bodyText, opacity: 0.7 }">
|
|
<div :style="{ fontWeight: '600', marginBottom: '2px' }">Notes</div>
|
|
{{ invoice.notes }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ================================================================ -->
|
|
<!-- TEMPLATE 9: COMPACT -->
|
|
<!-- ================================================================ -->
|
|
<div
|
|
v-else-if="template.id === 'compact'"
|
|
:style="{
|
|
backgroundColor: c.background,
|
|
color: c.bodyText,
|
|
fontFamily: '-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif',
|
|
position: 'relative',
|
|
aspectRatio: '210 / 297',
|
|
width: '100%',
|
|
overflow: 'hidden',
|
|
fontSize: '7.5px',
|
|
lineHeight: '1.5',
|
|
}"
|
|
>
|
|
<div :style="{ padding: '4%' }">
|
|
<!-- Single-line header: biz name left, INVOICE # right -->
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: '2px' }">
|
|
<div :style="{ fontSize: '11px', fontWeight: '600', color: c.headerText }">{{ biz?.name || 'Your Business' }}</div>
|
|
<div :style="{ fontSize: '9px', fontWeight: '700', color: c.primary }">INVOICE #{{ invoice.invoice_number }}</div>
|
|
</div>
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', fontSize: '7px', marginBottom: '10px', color: c.bodyText }">
|
|
<div>
|
|
<span v-if="biz?.email">{{ biz.email }}</span>
|
|
<span v-if="biz?.phone"> · {{ biz.phone }}</span>
|
|
</div>
|
|
<div>
|
|
{{ formatDate(invoice.date) }}
|
|
<span v-if="invoice.due_date"> · Due {{ formatDate(invoice.due_date) }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- From / To: tight spacing -->
|
|
<div :style="{ display: 'flex', gap: '4%', marginBottom: '10px' }">
|
|
<div :style="{ flex: 1 }">
|
|
<div :style="{ fontSize: '7px', fontWeight: '600', textTransform: 'uppercase', letterSpacing: '0.05em', color: c.primary, marginBottom: '2px' }">From</div>
|
|
<div :style="{ fontSize: '7.5px', fontWeight: '600' }">{{ biz?.name || 'Your Business' }}</div>
|
|
<div v-for="(line, i) in bizAddressLines" :key="'cpf'+i" :style="{ fontSize: '7px' }">{{ line }}</div>
|
|
</div>
|
|
<div :style="{ flex: 1 }">
|
|
<div :style="{ fontSize: '7px', fontWeight: '600', textTransform: 'uppercase', letterSpacing: '0.05em', color: c.primary, marginBottom: '2px' }">To</div>
|
|
<div :style="{ fontSize: '7.5px', fontWeight: '600' }">{{ clientName }}</div>
|
|
<div v-if="clientCompany" :style="{ fontSize: '7px' }">{{ clientCompany }}</div>
|
|
<div v-for="(line, i) in clientAddressLines" :key="'cpt'+i" :style="{ fontSize: '7px' }">{{ line }}</div>
|
|
<div v-if="clientEmail" :style="{ fontSize: '7px' }">{{ clientEmail }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Table: tight zebra stripes, smaller row height -->
|
|
<table :style="{ width: '100%', borderCollapse: 'collapse', marginBottom: '10px' }">
|
|
<thead>
|
|
<tr :style="{ backgroundColor: c.tableHeaderBg }">
|
|
<th :style="{ textAlign: 'left', padding: '3px 3px', fontSize: '6.5px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText }">Description</th>
|
|
<th :style="{ textAlign: 'right', padding: '3px 3px', fontSize: '6.5px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText, width: '38px' }">Qty</th>
|
|
<th :style="{ textAlign: 'right', padding: '3px 3px', fontSize: '6.5px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText, width: '46px' }">Rate</th>
|
|
<th :style="{ textAlign: 'right', padding: '3px 3px', fontSize: '6.5px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText, width: '52px' }">Amount</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="(item, i) in displayItems" :key="i" :style="{ backgroundColor: i % 2 === 1 ? c.tableRowAlt : 'transparent' }">
|
|
<td :style="{ padding: '3px 3px', fontSize: '7px' }">{{ item.description }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '3px 3px', fontSize: '7px' }">{{ item.quantity }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '3px 3px', fontSize: '7px' }">{{ formatCurrency(item.rate) }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '3px 3px', fontSize: '7px' }">{{ formatCurrency(item.amount) }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- Totals: subtle, not oversized -->
|
|
<div :style="{ display: 'flex', justifyContent: 'flex-end' }">
|
|
<div :style="{ width: '40%' }">
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', padding: '1px 0', fontSize: '7px' }">
|
|
<span>Subtotal</span><span>{{ formatCurrency(invoice.subtotal) }}</span>
|
|
</div>
|
|
<div v-if="invoice.tax_rate > 0" :style="{ display: 'flex', justifyContent: 'space-between', padding: '1px 0', fontSize: '7px' }">
|
|
<span>Tax ({{ invoice.tax_rate }}%)</span><span>{{ formatCurrency(invoice.tax_amount) }}</span>
|
|
</div>
|
|
<div v-if="invoice.discount > 0" :style="{ display: 'flex', justifyContent: 'space-between', padding: '1px 0', fontSize: '7px' }">
|
|
<span>Discount</span><span>-{{ formatCurrency(invoice.discount) }}</span>
|
|
</div>
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', padding: '3px 0', borderTop: '1px solid ' + c.tableBorder, marginTop: '3px', fontSize: '10px', fontWeight: '700', color: c.totalHighlight }">
|
|
<span>Total</span><span>{{ formatCurrency(invoice.total) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="invoice.notes" :style="{ marginTop: '10px', fontSize: '6.5px', color: c.bodyText, opacity: 0.7 }">
|
|
<div :style="{ fontWeight: '600', marginBottom: '2px' }">Notes</div>
|
|
{{ invoice.notes }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ================================================================ -->
|
|
<!-- TEMPLATE 10: DARK -->
|
|
<!-- ================================================================ -->
|
|
<div
|
|
v-else-if="template.id === 'dark'"
|
|
:style="{
|
|
backgroundColor: c.background,
|
|
color: c.bodyText,
|
|
fontFamily: '-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif',
|
|
position: 'relative',
|
|
aspectRatio: '210 / 297',
|
|
width: '100%',
|
|
overflow: 'hidden',
|
|
fontSize: '7.5px',
|
|
lineHeight: '1.5',
|
|
}"
|
|
>
|
|
<div :style="{ padding: '6%' }">
|
|
<!-- Header -->
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '6px' }">
|
|
<div>
|
|
<div :style="{ fontSize: '22px', fontWeight: '700', color: c.primary, lineHeight: '1.1' }">INVOICE</div>
|
|
<div :style="{ width: '40px', height: '1px', backgroundColor: c.primary, marginTop: '4px' }" />
|
|
</div>
|
|
<div :style="{ textAlign: 'right' }">
|
|
<img v-if="biz?.logo" :src="biz.logo" :style="{ width: '22px', height: '22px', objectFit: 'contain', marginBottom: '2px', marginLeft: 'auto', display: 'block' }" />
|
|
<div :style="{ fontSize: '10px', fontWeight: '600', color: c.headerText }">{{ biz?.name || 'Your Business' }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Meta -->
|
|
<div :style="{ fontSize: '7.5px', marginBottom: '12px', color: c.bodyText }">
|
|
#{{ invoice.invoice_number }} · {{ formatDate(invoice.date) }}
|
|
<span v-if="invoice.due_date"> · Due {{ formatDate(invoice.due_date) }}</span>
|
|
</div>
|
|
|
|
<!-- From / To -->
|
|
<div :style="{ display: 'flex', gap: '6%', marginBottom: '14px' }">
|
|
<div :style="{ flex: 1 }">
|
|
<div :style="{ fontSize: '8px', fontWeight: '600', textTransform: 'uppercase', letterSpacing: '0.05em', color: c.primary, marginBottom: '3px' }">From</div>
|
|
<div :style="{ fontSize: '8px', fontWeight: '600', color: c.headerText }">{{ biz?.name || 'Your Business' }}</div>
|
|
<div v-for="(line, i) in bizAddressLines" :key="'df'+i">{{ line }}</div>
|
|
<div v-if="biz?.email">{{ biz.email }}</div>
|
|
<div v-if="biz?.phone">{{ biz.phone }}</div>
|
|
</div>
|
|
<div :style="{ flex: 1 }">
|
|
<div :style="{ fontSize: '8px', fontWeight: '600', textTransform: 'uppercase', letterSpacing: '0.05em', color: c.primary, marginBottom: '3px' }">To</div>
|
|
<div :style="{ fontSize: '8px', fontWeight: '600', color: c.headerText }">{{ clientName }}</div>
|
|
<div v-if="clientCompany">{{ clientCompany }}</div>
|
|
<div v-for="(line, i) in clientAddressLines" :key="'dt'+i">{{ line }}</div>
|
|
<div v-if="clientEmail">{{ clientEmail }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Table: dark header, alternating dark rows -->
|
|
<table :style="{ width: '100%', borderCollapse: 'collapse', marginBottom: '12px' }">
|
|
<thead>
|
|
<tr :style="{ backgroundColor: c.tableHeaderBg }">
|
|
<th :style="{ textAlign: 'left', padding: '5px 4px', fontSize: '7px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText }">Description</th>
|
|
<th :style="{ textAlign: 'right', padding: '5px 4px', fontSize: '7px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText, width: '42px' }">Qty</th>
|
|
<th :style="{ textAlign: 'right', padding: '5px 4px', fontSize: '7px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText, width: '50px' }">Rate</th>
|
|
<th :style="{ textAlign: 'right', padding: '5px 4px', fontSize: '7px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText, width: '58px' }">Amount</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="(item, i) in displayItems" :key="i" :style="{ backgroundColor: i % 2 === 1 ? c.tableRowAlt : c.background }">
|
|
<td :style="{ padding: '4px 4px' }">{{ item.description }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '4px 4px' }">{{ item.quantity }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '4px 4px' }">{{ formatCurrency(item.rate) }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '4px 4px' }">{{ formatCurrency(item.amount) }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- Totals -->
|
|
<div :style="{ display: 'flex', justifyContent: 'flex-end' }">
|
|
<div :style="{ width: '45%' }">
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Subtotal</span><span>{{ formatCurrency(invoice.subtotal) }}</span>
|
|
</div>
|
|
<div v-if="invoice.tax_rate > 0" :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Tax ({{ invoice.tax_rate }}%)</span><span>{{ formatCurrency(invoice.tax_amount) }}</span>
|
|
</div>
|
|
<div v-if="invoice.discount > 0" :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Discount</span><span>-{{ formatCurrency(invoice.discount) }}</span>
|
|
</div>
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', padding: '4px 0', borderTop: '1px solid ' + c.tableBorder, marginTop: '4px', fontSize: '14px', fontWeight: '700', color: c.totalHighlight }">
|
|
<span>Total</span><span>{{ formatCurrency(invoice.total) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="invoice.notes" :style="{ marginTop: '14px', fontSize: '6.5px', color: c.bodyText, opacity: 0.7 }">
|
|
<div :style="{ fontWeight: '600', marginBottom: '2px' }">Notes</div>
|
|
{{ invoice.notes }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ================================================================ -->
|
|
<!-- TEMPLATE 11: VIBRANT -->
|
|
<!-- ================================================================ -->
|
|
<div
|
|
v-else-if="template.id === 'vibrant'"
|
|
:style="{
|
|
backgroundColor: c.background,
|
|
color: c.bodyText,
|
|
fontFamily: '-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif',
|
|
position: 'relative',
|
|
aspectRatio: '210 / 297',
|
|
width: '100%',
|
|
overflow: 'hidden',
|
|
fontSize: '7.5px',
|
|
lineHeight: '1.5',
|
|
}"
|
|
>
|
|
<!-- Gradient band -->
|
|
<div :style="{ background: 'linear-gradient(135deg, #ea580c 0%, #f97316 100%)', color: '#ffffff', padding: '5% 6% 4%', minHeight: '15%', display: 'flex', flexDirection: 'column', justifyContent: 'center' }">
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }">
|
|
<div>
|
|
<div :style="{ fontSize: '22px', fontWeight: '700', lineHeight: '1.1' }">INVOICE</div>
|
|
<div :style="{ fontSize: '7.5px', marginTop: '3px', opacity: 0.9 }">
|
|
#{{ invoice.invoice_number }} · {{ formatDate(invoice.date) }}
|
|
<span v-if="invoice.due_date"> · Due {{ formatDate(invoice.due_date) }}</span>
|
|
</div>
|
|
</div>
|
|
<div :style="{ textAlign: 'right' }">
|
|
<img v-if="biz?.logo" :src="biz.logo" :style="{ width: '22px', height: '22px', objectFit: 'contain', marginBottom: '2px', marginLeft: 'auto', display: 'block' }" />
|
|
<div :style="{ fontSize: '11px', fontWeight: '600' }">{{ biz?.name || 'Your Business' }}</div>
|
|
<div v-if="biz?.email" :style="{ fontSize: '7px', opacity: 0.9 }">{{ biz.email }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div :style="{ padding: '4% 6%' }">
|
|
<!-- From / To -->
|
|
<div :style="{ display: 'flex', gap: '6%', marginBottom: '14px' }">
|
|
<div :style="{ flex: 1 }">
|
|
<div :style="{ fontSize: '8px', fontWeight: '600', textTransform: 'uppercase', letterSpacing: '0.05em', color: c.primary, marginBottom: '3px' }">From</div>
|
|
<div :style="{ fontSize: '8px', fontWeight: '600' }">{{ biz?.name || 'Your Business' }}</div>
|
|
<div v-for="(line, i) in bizAddressLines" :key="'vf'+i">{{ line }}</div>
|
|
<div v-if="biz?.phone">{{ biz.phone }}</div>
|
|
</div>
|
|
<div :style="{ flex: 1 }">
|
|
<div :style="{ fontSize: '8px', fontWeight: '600', textTransform: 'uppercase', letterSpacing: '0.05em', color: c.primary, marginBottom: '3px' }">To</div>
|
|
<div :style="{ fontSize: '8px', fontWeight: '600' }">{{ clientName }}</div>
|
|
<div v-if="clientCompany">{{ clientCompany }}</div>
|
|
<div v-for="(line, i) in clientAddressLines" :key="'vt'+i">{{ line }}</div>
|
|
<div v-if="clientEmail">{{ clientEmail }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Table: warm-tinted header, borderless -->
|
|
<table :style="{ width: '100%', borderCollapse: 'collapse', marginBottom: '12px' }">
|
|
<thead>
|
|
<tr :style="{ backgroundColor: c.tableHeaderBg }">
|
|
<th :style="{ textAlign: 'left', padding: '4px 4px', fontSize: '7px', fontWeight: '600', textTransform: 'uppercase', color: c.tableHeaderText }">Description</th>
|
|
<th :style="{ textAlign: 'right', padding: '4px 4px', fontSize: '7px', fontWeight: '600', textTransform: 'uppercase', color: c.tableHeaderText, width: '42px' }">Qty</th>
|
|
<th :style="{ textAlign: 'right', padding: '4px 4px', fontSize: '7px', fontWeight: '600', textTransform: 'uppercase', color: c.tableHeaderText, width: '50px' }">Rate</th>
|
|
<th :style="{ textAlign: 'right', padding: '4px 4px', fontSize: '7px', fontWeight: '600', textTransform: 'uppercase', color: c.tableHeaderText, width: '58px' }">Amount</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="(item, i) in displayItems" :key="i" :style="{ backgroundColor: i % 2 === 1 ? c.tableRowAlt : 'transparent' }">
|
|
<td :style="{ padding: '4px 4px' }">{{ item.description }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '4px 4px' }">{{ item.quantity }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '4px 4px' }">{{ formatCurrency(item.rate) }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '4px 4px' }">{{ formatCurrency(item.amount) }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- Totals -->
|
|
<div :style="{ display: 'flex', justifyContent: 'flex-end' }">
|
|
<div :style="{ width: '45%' }">
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Subtotal</span><span>{{ formatCurrency(invoice.subtotal) }}</span>
|
|
</div>
|
|
<div v-if="invoice.tax_rate > 0" :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Tax ({{ invoice.tax_rate }}%)</span><span>{{ formatCurrency(invoice.tax_amount) }}</span>
|
|
</div>
|
|
<div v-if="invoice.discount > 0" :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Discount</span><span>-{{ formatCurrency(invoice.discount) }}</span>
|
|
</div>
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', padding: '4px 0', borderTop: '2px solid ' + c.primary, marginTop: '4px', fontSize: '14px', fontWeight: '700', color: c.totalHighlight }">
|
|
<span>Total</span><span>{{ formatCurrency(invoice.total) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="invoice.notes" :style="{ marginTop: '14px', fontSize: '6.5px', color: c.bodyText, opacity: 0.7 }">
|
|
<div :style="{ fontWeight: '600', marginBottom: '2px' }">Notes</div>
|
|
{{ invoice.notes }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ================================================================ -->
|
|
<!-- TEMPLATE 12: CORPORATE -->
|
|
<!-- ================================================================ -->
|
|
<div
|
|
v-else-if="template.id === 'corporate'"
|
|
:style="{
|
|
backgroundColor: c.background,
|
|
color: c.bodyText,
|
|
fontFamily: '-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif',
|
|
position: 'relative',
|
|
aspectRatio: '210 / 297',
|
|
width: '100%',
|
|
overflow: 'hidden',
|
|
fontSize: '7.5px',
|
|
lineHeight: '1.5',
|
|
}"
|
|
>
|
|
<!-- Deep blue band -->
|
|
<div :style="{ backgroundColor: c.headerBg, color: c.headerText, padding: '4% 6% 3%', minHeight: '12%', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }">
|
|
<div>
|
|
<img v-if="biz?.logo" :src="biz.logo" :style="{ width: '22px', height: '22px', objectFit: 'contain', marginBottom: '2px' }" />
|
|
<div :style="{ fontSize: '12px', fontWeight: '600' }">{{ biz?.name || 'Your Business' }}</div>
|
|
</div>
|
|
<div :style="{ fontSize: '22px', fontWeight: '700' }">INVOICE</div>
|
|
</div>
|
|
|
|
<!-- Lighter blue info bar -->
|
|
<div :style="{ backgroundColor: c.secondary, color: '#ffffff', padding: '1.5% 6%', fontSize: '7px', display: 'flex', justifyContent: 'space-between' }">
|
|
<div>#{{ invoice.invoice_number }}</div>
|
|
<div>{{ formatDate(invoice.date) }}</div>
|
|
<div v-if="invoice.due_date">Due: {{ formatDate(invoice.due_date) }}</div>
|
|
<div :style="{ textTransform: 'uppercase', fontWeight: '600' }">{{ invoice.status }}</div>
|
|
</div>
|
|
|
|
<div :style="{ padding: '4% 6%' }">
|
|
<!-- From / To -->
|
|
<div :style="{ display: 'flex', gap: '6%', marginBottom: '14px' }">
|
|
<div :style="{ flex: 1 }">
|
|
<div :style="{ fontSize: '8px', fontWeight: '600', textTransform: 'uppercase', letterSpacing: '0.05em', color: c.primary, marginBottom: '3px' }">From</div>
|
|
<div :style="{ fontSize: '8px', fontWeight: '600' }">{{ biz?.name || 'Your Business' }}</div>
|
|
<div v-for="(line, i) in bizAddressLines" :key="'cof'+i">{{ line }}</div>
|
|
<div v-if="biz?.email">{{ biz.email }}</div>
|
|
<div v-if="biz?.phone">{{ biz.phone }}</div>
|
|
</div>
|
|
<div :style="{ flex: 1 }">
|
|
<div :style="{ fontSize: '8px', fontWeight: '600', textTransform: 'uppercase', letterSpacing: '0.05em', color: c.primary, marginBottom: '3px' }">Bill To</div>
|
|
<div :style="{ fontSize: '8px', fontWeight: '600' }">{{ clientName }}</div>
|
|
<div v-if="clientCompany">{{ clientCompany }}</div>
|
|
<div v-for="(line, i) in clientAddressLines" :key="'cot'+i">{{ line }}</div>
|
|
<div v-if="clientEmail">{{ clientEmail }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Table: bordered, blue header -->
|
|
<table :style="{ width: '100%', borderCollapse: 'collapse', marginBottom: '12px', border: '1px solid ' + c.tableBorder }">
|
|
<thead>
|
|
<tr :style="{ backgroundColor: c.tableHeaderBg }">
|
|
<th :style="{ textAlign: 'left', padding: '4px 4px', fontSize: '7px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText, border: '1px solid ' + c.tableBorder }">Description</th>
|
|
<th :style="{ textAlign: 'right', padding: '4px 4px', fontSize: '7px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText, width: '42px', border: '1px solid ' + c.tableBorder }">Qty</th>
|
|
<th :style="{ textAlign: 'right', padding: '4px 4px', fontSize: '7px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText, width: '50px', border: '1px solid ' + c.tableBorder }">Rate</th>
|
|
<th :style="{ textAlign: 'right', padding: '4px 4px', fontSize: '7px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText, width: '58px', border: '1px solid ' + c.tableBorder }">Amount</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="(item, i) in displayItems" :key="i" :style="{ backgroundColor: i % 2 === 1 ? c.tableRowAlt : 'transparent' }">
|
|
<td :style="{ padding: '4px 4px', border: '1px solid ' + c.tableBorder }">{{ item.description }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '4px 4px', border: '1px solid ' + c.tableBorder }">{{ item.quantity }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '4px 4px', border: '1px solid ' + c.tableBorder }">{{ formatCurrency(item.rate) }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '4px 4px', border: '1px solid ' + c.tableBorder }">{{ formatCurrency(item.amount) }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- Totals -->
|
|
<div :style="{ display: 'flex', justifyContent: 'flex-end' }">
|
|
<div :style="{ width: '45%' }">
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Subtotal</span><span>{{ formatCurrency(invoice.subtotal) }}</span>
|
|
</div>
|
|
<div v-if="invoice.tax_rate > 0" :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Tax ({{ invoice.tax_rate }}%)</span><span>{{ formatCurrency(invoice.tax_amount) }}</span>
|
|
</div>
|
|
<div v-if="invoice.discount > 0" :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Discount</span><span>-{{ formatCurrency(invoice.discount) }}</span>
|
|
</div>
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', padding: '4px 0', borderTop: '2px solid ' + c.primary, marginTop: '4px', fontSize: '14px', fontWeight: '700', color: c.totalHighlight }">
|
|
<span>Total</span><span>{{ formatCurrency(invoice.total) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="invoice.notes" :style="{ marginTop: '14px', fontSize: '6.5px', color: c.bodyText, opacity: 0.7 }">
|
|
<div :style="{ fontWeight: '600', marginBottom: '2px' }">Notes</div>
|
|
{{ invoice.notes }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ================================================================ -->
|
|
<!-- TEMPLATE 13: FRESH -->
|
|
<!-- ================================================================ -->
|
|
<div
|
|
v-else-if="template.id === 'fresh'"
|
|
:style="{
|
|
backgroundColor: c.background,
|
|
color: c.bodyText,
|
|
fontFamily: '-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif',
|
|
position: 'relative',
|
|
aspectRatio: '210 / 297',
|
|
width: '100%',
|
|
overflow: 'hidden',
|
|
fontSize: '7.5px',
|
|
lineHeight: '1.5',
|
|
}"
|
|
>
|
|
<div :style="{ padding: '6%' }">
|
|
<!-- Header with logo left, watermark invoice number right -->
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '12px', position: 'relative' }">
|
|
<div>
|
|
<img v-if="biz?.logo" :src="biz.logo" :style="{ width: '26px', height: '26px', objectFit: 'contain', marginBottom: '3px' }" />
|
|
<div :style="{ fontSize: '11px', fontWeight: '600', color: c.headerText }">{{ biz?.name || 'Your Business' }}</div>
|
|
<div v-for="(line, i) in bizAddressLines" :key="'frf'+i" :style="{ fontSize: '7px' }">{{ line }}</div>
|
|
<div v-if="biz?.email" :style="{ fontSize: '7px' }">{{ biz.email }}</div>
|
|
<div v-if="biz?.phone" :style="{ fontSize: '7px' }">{{ biz.phone }}</div>
|
|
</div>
|
|
<div :style="{ textAlign: 'right', position: 'relative' }">
|
|
<div :style="{ fontSize: '14px', fontWeight: '600', color: c.primary, letterSpacing: '0.02em' }">INVOICE</div>
|
|
<!-- Oversized watermark number -->
|
|
<div :style="{ fontSize: '40px', fontWeight: '800', color: '#bae6fd', opacity: 0.3, lineHeight: '1', marginTop: '-4px' }">
|
|
{{ invoice.invoice_number }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Dates -->
|
|
<div :style="{ display: 'flex', gap: '12px', marginBottom: '10px', fontSize: '7.5px' }">
|
|
<div><span :style="{ fontWeight: '600' }">Date:</span> {{ formatDate(invoice.date) }}</div>
|
|
<div v-if="invoice.due_date"><span :style="{ fontWeight: '600' }">Due:</span> {{ formatDate(invoice.due_date) }}</div>
|
|
</div>
|
|
|
|
<!-- From / To -->
|
|
<div :style="{ display: 'flex', gap: '6%', marginBottom: '14px' }">
|
|
<div :style="{ flex: 1 }">
|
|
<div :style="{ fontSize: '8px', fontWeight: '600', textTransform: 'uppercase', letterSpacing: '0.05em', color: c.primary, marginBottom: '3px' }">From</div>
|
|
<div :style="{ fontSize: '8px', fontWeight: '600' }">{{ biz?.name || 'Your Business' }}</div>
|
|
<div v-for="(line, i) in bizAddressLines" :key="'frf2'+i">{{ line }}</div>
|
|
</div>
|
|
<div :style="{ flex: 1 }">
|
|
<div :style="{ fontSize: '8px', fontWeight: '600', textTransform: 'uppercase', letterSpacing: '0.05em', color: c.primary, marginBottom: '3px' }">To</div>
|
|
<div :style="{ fontSize: '8px', fontWeight: '600' }">{{ clientName }}</div>
|
|
<div v-if="clientCompany">{{ clientCompany }}</div>
|
|
<div v-for="(line, i) in clientAddressLines" :key="'frt'+i">{{ line }}</div>
|
|
<div v-if="clientEmail">{{ clientEmail }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Table: sky blue header, light blue zebra -->
|
|
<table :style="{ width: '100%', borderCollapse: 'collapse', marginBottom: '12px' }">
|
|
<thead>
|
|
<tr :style="{ backgroundColor: c.tableHeaderBg }">
|
|
<th :style="{ textAlign: 'left', padding: '4px 4px', fontSize: '7px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText }">Description</th>
|
|
<th :style="{ textAlign: 'right', padding: '4px 4px', fontSize: '7px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText, width: '42px' }">Qty</th>
|
|
<th :style="{ textAlign: 'right', padding: '4px 4px', fontSize: '7px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText, width: '50px' }">Rate</th>
|
|
<th :style="{ textAlign: 'right', padding: '4px 4px', fontSize: '7px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText, width: '58px' }">Amount</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="(item, i) in displayItems" :key="i" :style="{ backgroundColor: i % 2 === 1 ? c.tableRowAlt : 'transparent' }">
|
|
<td :style="{ padding: '4px 4px' }">{{ item.description }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '4px 4px' }">{{ item.quantity }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '4px 4px' }">{{ formatCurrency(item.rate) }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '4px 4px' }">{{ formatCurrency(item.amount) }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- Totals -->
|
|
<div :style="{ display: 'flex', justifyContent: 'flex-end' }">
|
|
<div :style="{ width: '45%' }">
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Subtotal</span><span>{{ formatCurrency(invoice.subtotal) }}</span>
|
|
</div>
|
|
<div v-if="invoice.tax_rate > 0" :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Tax ({{ invoice.tax_rate }}%)</span><span>{{ formatCurrency(invoice.tax_amount) }}</span>
|
|
</div>
|
|
<div v-if="invoice.discount > 0" :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Discount</span><span>-{{ formatCurrency(invoice.discount) }}</span>
|
|
</div>
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', padding: '4px 0', borderTop: '2px solid ' + c.primary, marginTop: '4px', fontSize: '14px', fontWeight: '700', color: c.totalHighlight }">
|
|
<span>Total</span><span>{{ formatCurrency(invoice.total) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="invoice.notes" :style="{ marginTop: '14px', fontSize: '6.5px', color: c.bodyText, opacity: 0.7 }">
|
|
<div :style="{ fontWeight: '600', marginBottom: '2px' }">Notes</div>
|
|
{{ invoice.notes }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ================================================================ -->
|
|
<!-- TEMPLATE 14: NATURAL -->
|
|
<!-- ================================================================ -->
|
|
<div
|
|
v-else-if="template.id === 'natural'"
|
|
:style="{
|
|
backgroundColor: c.background,
|
|
color: c.bodyText,
|
|
fontFamily: '-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif',
|
|
position: 'relative',
|
|
aspectRatio: '210 / 297',
|
|
width: '100%',
|
|
overflow: 'hidden',
|
|
fontSize: '7.5px',
|
|
lineHeight: '1.5',
|
|
}"
|
|
>
|
|
<div :style="{ padding: '6%' }">
|
|
<!-- Header -->
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '12px' }">
|
|
<div>
|
|
<img v-if="biz?.logo" :src="biz.logo" :style="{ width: '24px', height: '24px', objectFit: 'contain', marginBottom: '3px' }" />
|
|
<div :style="{ fontSize: '20px', fontWeight: '700', color: c.primary }">INVOICE</div>
|
|
</div>
|
|
<div :style="{ textAlign: 'right' }">
|
|
<div :style="{ fontSize: '11px', fontWeight: '600', color: c.headerText }">{{ biz?.name || 'Your Business' }}</div>
|
|
<div v-for="(line, i) in bizAddressLines" :key="'nf'+i" :style="{ fontSize: '7px', color: c.headerText }">{{ line }}</div>
|
|
<div v-if="biz?.email" :style="{ fontSize: '7px', color: c.headerText }">{{ biz.email }}</div>
|
|
<div v-if="biz?.phone" :style="{ fontSize: '7px', color: c.headerText }">{{ biz.phone }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Meta -->
|
|
<div :style="{ display: 'flex', gap: '12px', marginBottom: '12px', fontSize: '7.5px', color: c.headerText }">
|
|
<div><span :style="{ fontWeight: '600' }">#</span>{{ invoice.invoice_number }}</div>
|
|
<div>{{ formatDate(invoice.date) }}</div>
|
|
<div v-if="invoice.due_date">Due: {{ formatDate(invoice.due_date) }}</div>
|
|
</div>
|
|
|
|
<!-- From / To -->
|
|
<div :style="{ display: 'flex', gap: '6%', marginBottom: '14px' }">
|
|
<div :style="{ flex: 1 }">
|
|
<div :style="{ fontSize: '8px', fontWeight: '600', textTransform: 'uppercase', letterSpacing: '0.05em', color: c.primary, marginBottom: '3px' }">From</div>
|
|
<div :style="{ fontSize: '8px', fontWeight: '600', color: c.headerText }">{{ biz?.name || 'Your Business' }}</div>
|
|
<div v-for="(line, i) in bizAddressLines" :key="'nf2'+i">{{ line }}</div>
|
|
</div>
|
|
<div :style="{ flex: 1 }">
|
|
<div :style="{ fontSize: '8px', fontWeight: '600', textTransform: 'uppercase', letterSpacing: '0.05em', color: c.primary, marginBottom: '3px' }">To</div>
|
|
<div :style="{ fontSize: '8px', fontWeight: '600', color: c.headerText }">{{ clientName }}</div>
|
|
<div v-if="clientCompany">{{ clientCompany }}</div>
|
|
<div v-for="(line, i) in clientAddressLines" :key="'nt'+i">{{ line }}</div>
|
|
<div v-if="clientEmail">{{ clientEmail }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Table: terracotta header, warm cream alternating rows -->
|
|
<table :style="{ width: '100%', borderCollapse: 'collapse', marginBottom: '12px' }">
|
|
<thead>
|
|
<tr :style="{ backgroundColor: c.tableHeaderBg }">
|
|
<th :style="{ textAlign: 'left', padding: '4px 4px', fontSize: '7px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText }">Description</th>
|
|
<th :style="{ textAlign: 'right', padding: '4px 4px', fontSize: '7px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText, width: '42px' }">Qty</th>
|
|
<th :style="{ textAlign: 'right', padding: '4px 4px', fontSize: '7px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText, width: '50px' }">Rate</th>
|
|
<th :style="{ textAlign: 'right', padding: '4px 4px', fontSize: '7px', fontWeight: '500', textTransform: 'uppercase', color: c.tableHeaderText, width: '58px' }">Amount</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="(item, i) in displayItems" :key="i" :style="{ backgroundColor: i % 2 === 1 ? c.tableRowAlt : c.background }">
|
|
<td :style="{ padding: '4px 4px' }">{{ item.description }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '4px 4px' }">{{ item.quantity }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '4px 4px' }">{{ formatCurrency(item.rate) }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '4px 4px' }">{{ formatCurrency(item.amount) }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- Totals -->
|
|
<div :style="{ display: 'flex', justifyContent: 'flex-end' }">
|
|
<div :style="{ width: '45%' }">
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Subtotal</span><span>{{ formatCurrency(invoice.subtotal) }}</span>
|
|
</div>
|
|
<div v-if="invoice.tax_rate > 0" :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Tax ({{ invoice.tax_rate }}%)</span><span>{{ formatCurrency(invoice.tax_amount) }}</span>
|
|
</div>
|
|
<div v-if="invoice.discount > 0" :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Discount</span><span>-{{ formatCurrency(invoice.discount) }}</span>
|
|
</div>
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', padding: '4px 0', borderTop: '2px solid ' + c.primary, marginTop: '4px', fontSize: '14px', fontWeight: '700', color: c.totalHighlight }">
|
|
<span>Total</span><span>{{ formatCurrency(invoice.total) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="invoice.notes" :style="{ marginTop: '14px', fontSize: '6.5px', color: c.bodyText, opacity: 0.7 }">
|
|
<div :style="{ fontWeight: '600', marginBottom: '2px' }">Notes</div>
|
|
{{ invoice.notes }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ================================================================ -->
|
|
<!-- TEMPLATE 15: STATEMENT -->
|
|
<!-- ================================================================ -->
|
|
<div
|
|
v-else-if="template.id === 'statement'"
|
|
:style="{
|
|
backgroundColor: c.background,
|
|
color: c.bodyText,
|
|
fontFamily: '-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif',
|
|
position: 'relative',
|
|
aspectRatio: '210 / 297',
|
|
width: '100%',
|
|
overflow: 'hidden',
|
|
fontSize: '7.5px',
|
|
lineHeight: '1.5',
|
|
}"
|
|
>
|
|
<div :style="{ padding: '6%' }">
|
|
<!-- Hero header: INVOICE small top-left, TOTAL MASSIVE top-right -->
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '14px' }">
|
|
<div>
|
|
<div :style="{ fontSize: '14px', fontWeight: '700', color: c.headerText }">INVOICE</div>
|
|
<div :style="{ fontSize: '7.5px', marginTop: '2px' }">#{{ invoice.invoice_number }}</div>
|
|
<div :style="{ fontSize: '7.5px' }">{{ formatDate(invoice.date) }}</div>
|
|
<div v-if="invoice.due_date" :style="{ fontSize: '7.5px' }">Due: {{ formatDate(invoice.due_date) }}</div>
|
|
</div>
|
|
<div :style="{ textAlign: 'right' }">
|
|
<div :style="{ fontSize: '7px', textTransform: 'uppercase', letterSpacing: '0.08em', color: c.bodyText, fontWeight: '500' }">Total Due</div>
|
|
<div :style="{ fontSize: '24px', fontWeight: '800', color: c.secondary, lineHeight: '1.1' }">{{ formatCurrency(invoice.total) }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Biz info -->
|
|
<div :style="{ marginBottom: '10px' }">
|
|
<img v-if="biz?.logo" :src="biz.logo" :style="{ width: '22px', height: '22px', objectFit: 'contain', marginBottom: '3px' }" />
|
|
<div :style="{ fontSize: '9px', fontWeight: '600', color: c.headerText }">{{ biz?.name || 'Your Business' }}</div>
|
|
<div v-for="(line, i) in bizAddressLines" :key="'sf'+i" :style="{ fontSize: '7px' }">{{ line }}</div>
|
|
<div v-if="biz?.email" :style="{ fontSize: '7px' }">{{ biz.email }}</div>
|
|
<div v-if="biz?.phone" :style="{ fontSize: '7px' }">{{ biz.phone }}</div>
|
|
</div>
|
|
|
|
<!-- From / To -->
|
|
<div :style="{ display: 'flex', gap: '6%', marginBottom: '14px' }">
|
|
<div :style="{ flex: 1 }">
|
|
<div :style="{ fontSize: '8px', fontWeight: '600', textTransform: 'uppercase', letterSpacing: '0.05em', color: c.headerText, marginBottom: '3px' }">From</div>
|
|
<div :style="{ fontSize: '8px', fontWeight: '600' }">{{ biz?.name || 'Your Business' }}</div>
|
|
<div v-for="(line, i) in bizAddressLines" :key="'sf2'+i">{{ line }}</div>
|
|
</div>
|
|
<div :style="{ flex: 1 }">
|
|
<div :style="{ fontSize: '8px', fontWeight: '600', textTransform: 'uppercase', letterSpacing: '0.05em', color: c.headerText, marginBottom: '3px' }">To</div>
|
|
<div :style="{ fontSize: '8px', fontWeight: '600' }">{{ clientName }}</div>
|
|
<div v-if="clientCompany">{{ clientCompany }}</div>
|
|
<div v-for="(line, i) in clientAddressLines" :key="'st'+i">{{ line }}</div>
|
|
<div v-if="clientEmail">{{ clientEmail }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Table: clean, whitespace-only. NO borders, NO stripes -->
|
|
<table :style="{ width: '100%', borderCollapse: 'collapse', marginBottom: '12px' }">
|
|
<thead>
|
|
<tr>
|
|
<th :style="{ textAlign: 'left', padding: '5px 4px', fontSize: '7px', fontWeight: '600', textTransform: 'uppercase', color: c.tableHeaderText }">Description</th>
|
|
<th :style="{ textAlign: 'right', padding: '5px 4px', fontSize: '7px', fontWeight: '600', textTransform: 'uppercase', color: c.tableHeaderText, width: '42px' }">Qty</th>
|
|
<th :style="{ textAlign: 'right', padding: '5px 4px', fontSize: '7px', fontWeight: '600', textTransform: 'uppercase', color: c.tableHeaderText, width: '50px' }">Rate</th>
|
|
<th :style="{ textAlign: 'right', padding: '5px 4px', fontSize: '7px', fontWeight: '600', textTransform: 'uppercase', color: c.tableHeaderText, width: '58px' }">Amount</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="(item, i) in displayItems" :key="i">
|
|
<td :style="{ padding: '5px 4px' }">{{ item.description }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '5px 4px' }">{{ item.quantity }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '5px 4px' }">{{ formatCurrency(item.rate) }}</td>
|
|
<td :style="{ textAlign: 'right', padding: '5px 4px' }">{{ formatCurrency(item.amount) }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- Summary totals (total already shown at top as hero) -->
|
|
<div :style="{ display: 'flex', justifyContent: 'flex-end' }">
|
|
<div :style="{ width: '45%' }">
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Subtotal</span><span>{{ formatCurrency(invoice.subtotal) }}</span>
|
|
</div>
|
|
<div v-if="invoice.tax_rate > 0" :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Tax ({{ invoice.tax_rate }}%)</span><span>{{ formatCurrency(invoice.tax_amount) }}</span>
|
|
</div>
|
|
<div v-if="invoice.discount > 0" :style="{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }">
|
|
<span>Discount</span><span>-{{ formatCurrency(invoice.discount) }}</span>
|
|
</div>
|
|
<div :style="{ display: 'flex', justifyContent: 'space-between', padding: '3px 0', borderTop: '1px solid ' + c.tableBorder, marginTop: '3px', fontSize: '9px', fontWeight: '600', color: c.headerText }">
|
|
<span>Total</span><span>{{ formatCurrency(invoice.total) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="invoice.notes" :style="{ marginTop: '14px', fontSize: '6.5px', color: c.bodyText, opacity: 0.7 }">
|
|
<div :style="{ fontWeight: '600', marginBottom: '2px' }">Notes</div>
|
|
{{ invoice.notes }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ================================================================ -->
|
|
<!-- FALLBACK -->
|
|
<!-- ================================================================ -->
|
|
<div
|
|
v-else
|
|
:style="{
|
|
backgroundColor: c.background,
|
|
color: c.bodyText,
|
|
fontFamily: '-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif',
|
|
position: 'relative',
|
|
aspectRatio: '210 / 297',
|
|
width: '100%',
|
|
overflow: 'hidden',
|
|
fontSize: '7.5px',
|
|
lineHeight: '1.5',
|
|
padding: '6%',
|
|
}"
|
|
>
|
|
<div :style="{ fontSize: '14px', fontWeight: '700', color: c.headerText }">INVOICE</div>
|
|
<div :style="{ marginTop: '4px' }">Template "{{ template.id }}" not found.</div>
|
|
</div>
|
|
</template>
|