feat: load invoice templates from JSON files via backend
Templates are now loaded dynamically from data/templates/*.json via the get_invoice_templates Tauri command instead of being hardcoded in TypeScript. Preview and PDF renderer switch on template.layout instead of template.id, allowing custom templates to reuse built-in layouts with different colors.
This commit is contained in:
@@ -1825,7 +1825,7 @@ export function renderInvoicePdf(
|
||||
items: InvoiceItem[],
|
||||
businessInfo: BusinessInfo,
|
||||
): jsPDF {
|
||||
switch (config.id) {
|
||||
switch (config.layout) {
|
||||
case 'clean': return renderClean(config, invoice, client, items, businessInfo)
|
||||
case 'professional': return renderProfessional(config, invoice, client, items, businessInfo)
|
||||
case 'bold': return renderBold(config, invoice, client, items, businessInfo)
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { ref } from 'vue'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
|
||||
export interface InvoiceTemplateColors {
|
||||
primary: string
|
||||
secondary: string
|
||||
@@ -15,318 +18,12 @@ export interface InvoiceTemplateColors {
|
||||
export interface InvoiceTemplateConfig {
|
||||
id: string
|
||||
name: string
|
||||
category: 'essential' | 'creative' | 'warm' | 'premium'
|
||||
layout: string
|
||||
category: string
|
||||
description: string
|
||||
colors: InvoiceTemplateColors
|
||||
}
|
||||
|
||||
const clean: InvoiceTemplateConfig = {
|
||||
id: 'clean',
|
||||
name: 'Clean',
|
||||
category: 'essential',
|
||||
description: 'Swiss minimalism with a single blue accent',
|
||||
colors: {
|
||||
primary: '#1e293b',
|
||||
secondary: '#3b82f6',
|
||||
background: '#ffffff',
|
||||
headerBg: '#ffffff',
|
||||
headerText: '#1e293b',
|
||||
bodyText: '#374151',
|
||||
tableHeaderBg: '#f8fafc',
|
||||
tableHeaderText: '#374151',
|
||||
tableRowAlt: '#f8fafc',
|
||||
tableBorder: '#e5e7eb',
|
||||
totalHighlight: '#3b82f6',
|
||||
},
|
||||
}
|
||||
|
||||
const professional: InvoiceTemplateConfig = {
|
||||
id: 'professional',
|
||||
name: 'Professional',
|
||||
category: 'essential',
|
||||
description: 'Navy header band with corporate polish',
|
||||
colors: {
|
||||
primary: '#1e3a5f',
|
||||
secondary: '#2563eb',
|
||||
background: '#ffffff',
|
||||
headerBg: '#1e3a5f',
|
||||
headerText: '#ffffff',
|
||||
bodyText: '#374151',
|
||||
tableHeaderBg: '#1e3a5f',
|
||||
tableHeaderText: '#ffffff',
|
||||
tableRowAlt: '#f3f4f6',
|
||||
tableBorder: '#d1d5db',
|
||||
totalHighlight: '#1e3a5f',
|
||||
},
|
||||
}
|
||||
|
||||
const bold: InvoiceTemplateConfig = {
|
||||
id: 'bold',
|
||||
name: 'Bold',
|
||||
category: 'essential',
|
||||
description: 'Large indigo block with oversized typography',
|
||||
colors: {
|
||||
primary: '#4f46e5',
|
||||
secondary: '#a5b4fc',
|
||||
background: '#ffffff',
|
||||
headerBg: '#4f46e5',
|
||||
headerText: '#ffffff',
|
||||
bodyText: '#1f2937',
|
||||
tableHeaderBg: '#4f46e5',
|
||||
tableHeaderText: '#ffffff',
|
||||
tableRowAlt: '#f5f3ff',
|
||||
tableBorder: '#e0e7ff',
|
||||
totalHighlight: '#4f46e5',
|
||||
},
|
||||
}
|
||||
|
||||
const minimal: InvoiceTemplateConfig = {
|
||||
id: 'minimal',
|
||||
name: 'Minimal',
|
||||
category: 'essential',
|
||||
description: 'Pure monochrome centered layout',
|
||||
colors: {
|
||||
primary: '#18181b',
|
||||
secondary: '#18181b',
|
||||
background: '#ffffff',
|
||||
headerBg: '#ffffff',
|
||||
headerText: '#18181b',
|
||||
bodyText: '#3f3f46',
|
||||
tableHeaderBg: '#ffffff',
|
||||
tableHeaderText: '#18181b',
|
||||
tableRowAlt: '#ffffff',
|
||||
tableBorder: '#e4e4e7',
|
||||
totalHighlight: '#18181b',
|
||||
},
|
||||
}
|
||||
|
||||
const classic: InvoiceTemplateConfig = {
|
||||
id: 'classic',
|
||||
name: 'Classic',
|
||||
category: 'essential',
|
||||
description: 'Traditional layout with burgundy accents',
|
||||
colors: {
|
||||
primary: '#7f1d1d',
|
||||
secondary: '#991b1b',
|
||||
background: '#ffffff',
|
||||
headerBg: '#7f1d1d',
|
||||
headerText: '#ffffff',
|
||||
bodyText: '#374151',
|
||||
tableHeaderBg: '#7f1d1d',
|
||||
tableHeaderText: '#ffffff',
|
||||
tableRowAlt: '#f5f5f4',
|
||||
tableBorder: '#d6d3d1',
|
||||
totalHighlight: '#7f1d1d',
|
||||
},
|
||||
}
|
||||
|
||||
const modern: InvoiceTemplateConfig = {
|
||||
id: 'modern',
|
||||
name: 'Modern',
|
||||
category: 'creative',
|
||||
description: 'Asymmetric header with teal accents',
|
||||
colors: {
|
||||
primary: '#0d9488',
|
||||
secondary: '#14b8a6',
|
||||
background: '#ffffff',
|
||||
headerBg: '#ffffff',
|
||||
headerText: '#0f172a',
|
||||
bodyText: '#334155',
|
||||
tableHeaderBg: '#ffffff',
|
||||
tableHeaderText: '#0d9488',
|
||||
tableRowAlt: '#f0fdfa',
|
||||
tableBorder: '#99f6e4',
|
||||
totalHighlight: '#0d9488',
|
||||
},
|
||||
}
|
||||
|
||||
const elegant: InvoiceTemplateConfig = {
|
||||
id: 'elegant',
|
||||
name: 'Elegant',
|
||||
category: 'creative',
|
||||
description: 'Gold double-rule accents on centered layout',
|
||||
colors: {
|
||||
primary: '#a16207',
|
||||
secondary: '#ca8a04',
|
||||
background: '#ffffff',
|
||||
headerBg: '#ffffff',
|
||||
headerText: '#422006',
|
||||
bodyText: '#57534e',
|
||||
tableHeaderBg: '#ffffff',
|
||||
tableHeaderText: '#422006',
|
||||
tableRowAlt: '#fefce8',
|
||||
tableBorder: '#a16207',
|
||||
totalHighlight: '#a16207',
|
||||
},
|
||||
}
|
||||
|
||||
const creative: InvoiceTemplateConfig = {
|
||||
id: 'creative',
|
||||
name: 'Creative',
|
||||
category: 'creative',
|
||||
description: 'Purple sidebar with card-style rows',
|
||||
colors: {
|
||||
primary: '#7c3aed',
|
||||
secondary: '#a78bfa',
|
||||
background: '#ffffff',
|
||||
headerBg: '#ffffff',
|
||||
headerText: '#1f2937',
|
||||
bodyText: '#374151',
|
||||
tableHeaderBg: '#faf5ff',
|
||||
tableHeaderText: '#7c3aed',
|
||||
tableRowAlt: '#faf5ff',
|
||||
tableBorder: '#e9d5ff',
|
||||
totalHighlight: '#7c3aed',
|
||||
},
|
||||
}
|
||||
|
||||
const compact: InvoiceTemplateConfig = {
|
||||
id: 'compact',
|
||||
name: 'Compact',
|
||||
category: 'creative',
|
||||
description: 'Data-dense layout with tight spacing',
|
||||
colors: {
|
||||
primary: '#475569',
|
||||
secondary: '#64748b',
|
||||
background: '#ffffff',
|
||||
headerBg: '#ffffff',
|
||||
headerText: '#0f172a',
|
||||
bodyText: '#334155',
|
||||
tableHeaderBg: '#f1f5f9',
|
||||
tableHeaderText: '#334155',
|
||||
tableRowAlt: '#f8fafc',
|
||||
tableBorder: '#e2e8f0',
|
||||
totalHighlight: '#475569',
|
||||
},
|
||||
}
|
||||
|
||||
const dark: InvoiceTemplateConfig = {
|
||||
id: 'dark',
|
||||
name: 'Dark',
|
||||
category: 'warm',
|
||||
description: 'Near-black background with cyan highlights',
|
||||
colors: {
|
||||
primary: '#06b6d4',
|
||||
secondary: '#22d3ee',
|
||||
background: '#0f172a',
|
||||
headerBg: '#020617',
|
||||
headerText: '#e2e8f0',
|
||||
bodyText: '#cbd5e1',
|
||||
tableHeaderBg: '#020617',
|
||||
tableHeaderText: '#06b6d4',
|
||||
tableRowAlt: '#1e293b',
|
||||
tableBorder: '#334155',
|
||||
totalHighlight: '#06b6d4',
|
||||
},
|
||||
}
|
||||
|
||||
const vibrant: InvoiceTemplateConfig = {
|
||||
id: 'vibrant',
|
||||
name: 'Vibrant',
|
||||
category: 'warm',
|
||||
description: 'Coral-to-orange gradient header band',
|
||||
colors: {
|
||||
primary: '#ea580c',
|
||||
secondary: '#f97316',
|
||||
background: '#ffffff',
|
||||
headerBg: '#ea580c',
|
||||
headerText: '#ffffff',
|
||||
bodyText: '#1f2937',
|
||||
tableHeaderBg: '#fff7ed',
|
||||
tableHeaderText: '#9a3412',
|
||||
tableRowAlt: '#fff7ed',
|
||||
tableBorder: '#fed7aa',
|
||||
totalHighlight: '#ea580c',
|
||||
},
|
||||
}
|
||||
|
||||
const corporate: InvoiceTemplateConfig = {
|
||||
id: 'corporate',
|
||||
name: 'Corporate',
|
||||
category: 'warm',
|
||||
description: 'Deep blue header with info bar below',
|
||||
colors: {
|
||||
primary: '#1e40af',
|
||||
secondary: '#3b82f6',
|
||||
background: '#ffffff',
|
||||
headerBg: '#1e40af',
|
||||
headerText: '#ffffff',
|
||||
bodyText: '#1f2937',
|
||||
tableHeaderBg: '#1e40af',
|
||||
tableHeaderText: '#ffffff',
|
||||
tableRowAlt: '#eff6ff',
|
||||
tableBorder: '#bfdbfe',
|
||||
totalHighlight: '#1e40af',
|
||||
},
|
||||
}
|
||||
|
||||
const fresh: InvoiceTemplateConfig = {
|
||||
id: 'fresh',
|
||||
name: 'Fresh',
|
||||
category: 'premium',
|
||||
description: 'Oversized watermark invoice number',
|
||||
colors: {
|
||||
primary: '#0284c7',
|
||||
secondary: '#38bdf8',
|
||||
background: '#ffffff',
|
||||
headerBg: '#ffffff',
|
||||
headerText: '#0c4a6e',
|
||||
bodyText: '#334155',
|
||||
tableHeaderBg: '#0284c7',
|
||||
tableHeaderText: '#ffffff',
|
||||
tableRowAlt: '#f0f9ff',
|
||||
tableBorder: '#bae6fd',
|
||||
totalHighlight: '#0284c7',
|
||||
},
|
||||
}
|
||||
|
||||
const natural: InvoiceTemplateConfig = {
|
||||
id: 'natural',
|
||||
name: 'Natural',
|
||||
category: 'premium',
|
||||
description: 'Warm beige background with terracotta accents',
|
||||
colors: {
|
||||
primary: '#c2703e',
|
||||
secondary: '#d97706',
|
||||
background: '#fdf6ec',
|
||||
headerBg: '#fdf6ec',
|
||||
headerText: '#78350f',
|
||||
bodyText: '#57534e',
|
||||
tableHeaderBg: '#c2703e',
|
||||
tableHeaderText: '#ffffff',
|
||||
tableRowAlt: '#fef3c7',
|
||||
tableBorder: '#d6d3d1',
|
||||
totalHighlight: '#c2703e',
|
||||
},
|
||||
}
|
||||
|
||||
const statement: InvoiceTemplateConfig = {
|
||||
id: 'statement',
|
||||
name: 'Statement',
|
||||
category: 'premium',
|
||||
description: 'Total-forward design with hero amount',
|
||||
colors: {
|
||||
primary: '#18181b',
|
||||
secondary: '#be123c',
|
||||
background: '#ffffff',
|
||||
headerBg: '#ffffff',
|
||||
headerText: '#18181b',
|
||||
bodyText: '#3f3f46',
|
||||
tableHeaderBg: '#ffffff',
|
||||
tableHeaderText: '#18181b',
|
||||
tableRowAlt: '#ffffff',
|
||||
tableBorder: '#e4e4e7',
|
||||
totalHighlight: '#be123c',
|
||||
},
|
||||
}
|
||||
|
||||
export const INVOICE_TEMPLATES: InvoiceTemplateConfig[] = [
|
||||
clean, professional, bold, minimal, classic,
|
||||
modern, elegant, creative, compact,
|
||||
dark, vibrant, corporate,
|
||||
fresh, natural, statement,
|
||||
]
|
||||
|
||||
export const TEMPLATE_CATEGORIES = [
|
||||
{ id: 'essential', label: 'Professional Essentials' },
|
||||
{ id: 'creative', label: 'Creative & Modern' },
|
||||
@@ -334,10 +31,36 @@ export const TEMPLATE_CATEGORIES = [
|
||||
{ id: 'premium', label: 'Premium & Specialized' },
|
||||
] as const
|
||||
|
||||
const templates = ref<InvoiceTemplateConfig[]>([])
|
||||
let loaded = false
|
||||
|
||||
export async function loadTemplates(): Promise<InvoiceTemplateConfig[]> {
|
||||
if (loaded && templates.value.length > 0) return templates.value
|
||||
try {
|
||||
templates.value = await invoke<InvoiceTemplateConfig[]>('get_invoice_templates')
|
||||
loaded = true
|
||||
} catch (e) {
|
||||
console.error('Failed to load invoice templates:', e)
|
||||
}
|
||||
return templates.value
|
||||
}
|
||||
|
||||
export function getLoadedTemplates(): InvoiceTemplateConfig[] {
|
||||
return templates.value
|
||||
}
|
||||
|
||||
export function getTemplateById(id: string): InvoiceTemplateConfig {
|
||||
return INVOICE_TEMPLATES.find(t => t.id === id) || INVOICE_TEMPLATES[0]
|
||||
return templates.value.find(t => t.id === id) || templates.value[0] || {
|
||||
id: 'clean', name: 'Clean', layout: 'clean', category: 'essential',
|
||||
description: 'Default', colors: {
|
||||
primary: '#1e293b', secondary: '#3b82f6', background: '#ffffff',
|
||||
headerBg: '#ffffff', headerText: '#1e293b', bodyText: '#374151',
|
||||
tableHeaderBg: '#f8fafc', tableHeaderText: '#374151',
|
||||
tableRowAlt: '#f8fafc', tableBorder: '#e5e7eb', totalHighlight: '#3b82f6',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function getTemplatesByCategory(category: string): InvoiceTemplateConfig[] {
|
||||
return INVOICE_TEMPLATES.filter(t => t.category === category)
|
||||
return templates.value.filter(t => t.category === category)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user