Files
zeroclock/src/components/InvoiceTemplatePicker.vue
Your Name ee82abe63e feat: tooltips, two-column timer, font selector, tray behavior, icons, readme
- Custom tooltip directive (WCAG AAA) on every button in the app
- Two-column timer layout with sticky hero and recent entries sidebar
- Timer font selector with 16 monospace Google Fonts and live preview
- UI font selector with 15+ Google Fonts
- Close-to-tray and minimize-to-tray settings
- New app icons (no-glow variants), platform icon set
- Mini timer pop-out window
- Favorites strip with drag-reorder and inline actions
- Comprehensive README with feature documentation
- Remove tracked files that belong in gitignore
2026-02-21 01:15:57 +02:00

140 lines
4.1 KiB
Vue

<script setup lang="ts">
import { computed } from 'vue'
import InvoicePreview from './InvoicePreview.vue'
import {
TEMPLATE_CATEGORIES,
getTemplatesByCategory,
getTemplateById,
} from '../utils/invoiceTemplates'
import type { InvoiceItem } from '../utils/invoicePdf'
import type { BusinessInfo } from '../utils/invoicePdfRenderer'
import type { Invoice } from '../stores/invoices'
import type { Client } from '../stores/clients'
const props = withDefaults(
defineProps<{
modelValue: string
invoice?: Invoice
client?: Client | null
items?: InvoiceItem[]
businessInfo?: BusinessInfo
}>(),
{
invoice: undefined,
client: undefined,
items: undefined,
businessInfo: undefined,
},
)
const emit = defineEmits<{
'update:modelValue': [value: string]
}>()
// ---------------------------------------------------------------------------
// Default sample data
// ---------------------------------------------------------------------------
const defaultInvoice: Invoice = {
client_id: 0,
invoice_number: 'INV-2026-001',
date: '2026-02-18',
due_date: '2026-03-18',
subtotal: 8800,
tax_rate: 10,
tax_amount: 880,
discount: 0,
total: 9680,
notes: 'Payment due within 30 days. Thank you for your business!',
status: 'pending',
}
const defaultClient: Client = {
id: 1,
name: 'Acme Corporation',
email: 'billing@acme.com',
address: '123 Business Ave\nSuite 100\nNew York, NY 10001',
}
const defaultBusinessInfo: BusinessInfo = {
name: 'Your Business Name',
address: '456 Creative St, Design City',
email: 'hello@business.com',
phone: '(555) 123-4567',
logo: '',
}
// ---------------------------------------------------------------------------
// Computed
// ---------------------------------------------------------------------------
const selectedTemplate = computed(() => getTemplateById(props.modelValue))
const previewInvoice = computed(() => props.invoice ?? defaultInvoice)
const previewClient = computed(() =>
props.client !== undefined ? props.client : defaultClient,
)
const previewItems = computed(() =>
props.items && props.items.length > 0 ? props.items : [],
)
const previewBusinessInfo = computed(() => props.businessInfo ?? defaultBusinessInfo)
function selectTemplate(id: string) {
emit('update:modelValue', id)
}
</script>
<template>
<div
class="flex border border-border-subtle rounded-lg overflow-hidden"
style="height: 480px"
>
<!-- Left panel: Template list -->
<div class="w-[30%] border-r border-border-subtle overflow-y-auto bg-bg-surface" role="radiogroup" aria-label="Invoice templates">
<div v-for="cat in TEMPLATE_CATEGORIES" :key="cat.id">
<div
class="text-[0.5625rem] text-text-tertiary uppercase tracking-[0.08em] font-medium px-3 pt-3 pb-1"
>
{{ cat.label }}
</div>
<button
v-for="tmpl in getTemplatesByCategory(cat.id)"
:key="tmpl.id"
role="radio"
:aria-checked="tmpl.id === modelValue"
class="w-full flex items-center gap-2 px-3 py-1.5 text-[0.75rem] transition-colors"
:class="
tmpl.id === modelValue
? 'bg-accent/10 text-accent-text'
: 'text-text-secondary hover:bg-bg-elevated hover:text-text-primary'
"
@click="selectTemplate(tmpl.id)"
>
<span
class="w-2.5 h-2.5 rounded-full shrink-0 border border-black/10"
:style="{ backgroundColor: tmpl.colors.primary }"
aria-hidden="true"
/>
<span class="truncate">{{ tmpl.name }}</span>
</button>
</div>
</div>
<!-- Right panel: Live preview -->
<div class="w-[70%] bg-bg-inset p-4 flex items-start justify-center overflow-y-auto" aria-label="Template preview" aria-live="polite">
<div class="w-full max-w-sm">
<InvoicePreview
:template="selectedTemplate"
:invoice="previewInvoice"
:client="previewClient"
:items="previewItems"
:business-info="previewBusinessInfo"
/>
</div>
</div>
</div>
</template>