feat: redesign Invoices — amber tabs and totals, rich empty state

This commit is contained in:
Your Name
2026-02-17 21:31:05 +02:00
parent 90bb035b72
commit ed6e10efd3

View File

@@ -1,107 +1,101 @@
<template> <template>
<div class="p-6"> <div class="p-6">
<h1 class="text-2xl font-bold mb-6">Invoices</h1> <h1 class="text-[1.5rem] font-medium text-text-primary mb-6">Invoices</h1>
<!-- Header --> <!-- View tabs -->
<div class="flex items-center justify-between mb-6"> <div class="flex gap-6 mb-6 border-b border-border-subtle">
<div class="flex gap-2">
<button <button
@click="view = 'list'" @click="view = 'list'"
:class="[ class="pb-2 text-[0.8125rem] transition-colors duration-150 -mb-px"
'px-4 py-2 rounded-lg transition-colors', :class="view === 'list'
view === 'list' ? 'text-text-primary border-b-2 border-accent'
? 'bg-amber text-background' : 'text-text-tertiary hover:text-text-secondary'"
: 'border border-border text-text-primary hover:bg-surface-elevated'
]"
> >
List List
</button> </button>
<button <button
@click="view = 'create'" @click="view = 'create'"
:class="[ class="pb-2 text-[0.8125rem] transition-colors duration-150 -mb-px"
'px-4 py-2 rounded-lg transition-colors', :class="view === 'create'
view === 'create' ? 'text-text-primary border-b-2 border-accent'
? 'bg-amber text-background' : 'text-text-tertiary hover:text-text-secondary'"
: 'border border-border text-text-primary hover:bg-surface-elevated'
]"
> >
Create Create
</button> </button>
</div> </div>
</div>
<!-- List View --> <!-- List View -->
<div v-if="view === 'list'" class="bg-surface border border-border rounded-lg overflow-hidden"> <div v-if="view === 'list'">
<div v-if="invoicesStore.invoices.length > 0" class="overflow-x-auto"> <div v-if="invoicesStore.invoices.length > 0" class="overflow-x-auto">
<table class="w-full"> <table class="w-full">
<thead class="bg-surface-elevated"> <thead>
<tr> <tr class="border-b border-border-subtle bg-bg-surface">
<th class="px-4 py-3 text-left text-sm font-medium text-text-secondary">Invoice #</th> <th class="px-4 py-3 text-left text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium">Invoice #</th>
<th class="px-4 py-3 text-left text-sm font-medium text-text-secondary">Client</th> <th class="px-4 py-3 text-left text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium">Client</th>
<th class="px-4 py-3 text-left text-sm font-medium text-text-secondary">Date</th> <th class="px-4 py-3 text-left text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium">Date</th>
<th class="px-4 py-3 text-right text-sm font-medium text-text-secondary">Amount</th> <th class="px-4 py-3 text-right text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium">Amount</th>
<th class="px-4 py-3 text-center text-sm font-medium text-text-secondary">Status</th> <th class="px-4 py-3 text-center text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium">Status</th>
<th class="px-4 py-3 text-center text-sm font-medium text-text-secondary">Actions</th> <th class="px-4 py-3 w-24"></th>
</tr> </tr>
</thead> </thead>
<tbody class="divide-y divide-border"> <tbody>
<tr <tr
v-for="invoice in invoicesStore.invoices" v-for="invoice in invoicesStore.invoices"
:key="invoice.id" :key="invoice.id"
class="hover:bg-surface-elevated transition-colors" class="group border-b border-border-subtle hover:bg-bg-elevated transition-colors duration-150"
> >
<td class="px-4 py-3 text-text-primary font-medium"> <td class="px-4 py-3 text-[0.75rem] font-medium text-text-primary">
{{ invoice.invoice_number }} {{ invoice.invoice_number }}
</td> </td>
<td class="px-4 py-3 text-text-primary"> <td class="px-4 py-3 text-[0.75rem] text-text-primary">
{{ getClientName(invoice.client_id) }} {{ getClientName(invoice.client_id) }}
</td> </td>
<td class="px-4 py-3 text-text-secondary"> <td class="px-4 py-3 text-[0.75rem] text-text-secondary">
{{ formatDate(invoice.date) }} {{ formatDate(invoice.date) }}
</td> </td>
<td class="px-4 py-3 text-right text-text-primary font-mono"> <td class="px-4 py-3 text-right text-[0.75rem] font-mono text-accent-text">
${{ invoice.total.toFixed(2) }} ${{ invoice.total.toFixed(2) }}
</td> </td>
<td class="px-4 py-3 text-center"> <td class="px-4 py-3 text-center">
<span <span
:class="[ class="text-[0.6875rem] font-medium"
'px-2 py-1 text-xs font-medium rounded', :class="{
invoice.status === 'paid' ? 'bg-green-900 text-green-200' : 'text-status-running': invoice.status === 'paid',
invoice.status === 'pending' ? 'bg-yellow-900 text-yellow-200' : 'text-status-warning': invoice.status === 'pending',
'bg-red-900 text-red-200' 'text-status-error': invoice.status === 'overdue' || invoice.status === 'draft'
]" }"
> >
{{ invoice.status }} {{ invoice.status }}
</span> </span>
</td> </td>
<td class="px-4 py-3 text-center"> <td class="px-4 py-3">
<div class="flex items-center justify-center gap-2"> <div class="flex items-center justify-end gap-1 opacity-0 group-hover:opacity-100 transition-opacity duration-100">
<button <button
@click="viewInvoice(invoice)" @click="viewInvoice(invoice)"
class="p-2 text-text-secondary hover:text-amber transition-colors" class="p-1.5 text-text-tertiary hover:text-text-secondary transition-colors duration-150"
title="View" title="View"
> >
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /> <path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" /> <path stroke-linecap="round" stroke-linejoin="round" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg> </svg>
</button> </button>
<button <button
@click="exportPDF(invoice)" @click="exportPDF(invoice)"
class="p-2 text-text-secondary hover:text-amber transition-colors" class="p-1.5 text-text-tertiary hover:text-text-secondary transition-colors duration-150"
title="Export PDF" title="Export PDF"
> >
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /> <path stroke-linecap="round" stroke-linejoin="round" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg> </svg>
</button> </button>
<button <button
@click="confirmDelete(invoice)" @click="confirmDelete(invoice)"
class="p-2 text-text-secondary hover:text-error transition-colors" class="p-1.5 text-text-tertiary hover:text-status-error transition-colors duration-150"
title="Delete" title="Delete"
> >
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /> <path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg> </svg>
</button> </button>
</div> </div>
@@ -111,23 +105,31 @@
</table> </table>
</div> </div>
<div v-else class="text-center text-text-secondary py-12"> <div v-else class="flex flex-col items-center justify-center py-16">
No invoices yet. Create your first invoice! <FileText class="w-12 h-12 text-text-tertiary" :stroke-width="1.5" />
<p class="text-sm text-text-secondary mt-4">No invoices yet</p>
<p class="text-xs text-text-tertiary mt-2 max-w-xs text-center">Create invoices from your tracked time to bill clients.</p>
<button
@click="view = 'create'"
class="mt-4 px-4 py-2 bg-accent text-bg-base text-xs font-medium rounded hover:bg-accent-hover transition-colors"
>
Create Invoice
</button>
</div> </div>
</div> </div>
<!-- Create View --> <!-- Create View -->
<div v-else-if="view === 'create'" class="bg-surface border border-border rounded-lg p-6"> <div v-else-if="view === 'create'" class="max-w-lg">
<h2 class="text-xl font-bold mb-4">Create Invoice</h2> <h2 class="text-[1rem] font-medium text-text-primary mb-4">Create Invoice</h2>
<form @submit.prevent="handleCreate" class="space-y-4"> <form @submit.prevent="handleCreate" class="space-y-4">
<!-- Client --> <!-- Client -->
<div> <div>
<label class="block text-sm text-text-secondary mb-1">Client *</label> <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Client *</label>
<select <select
v-model="createForm.client_id" v-model="createForm.client_id"
required required
class="w-full px-3 py-2 bg-background border border-border rounded-lg text-text-primary focus:outline-none focus:border-amber" class="w-full px-3 py-2 bg-bg-inset border border-border-subtle rounded text-[0.8125rem] text-text-primary focus:outline-none focus:border-border-visible"
> >
<option :value="0">Select a client</option> <option :value="0">Select a client</option>
<option <option
@@ -143,20 +145,20 @@
<!-- Date --> <!-- Date -->
<div class="grid grid-cols-2 gap-4"> <div class="grid grid-cols-2 gap-4">
<div> <div>
<label class="block text-sm text-text-secondary mb-1">Invoice Date *</label> <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Invoice Date *</label>
<input <input
v-model="createForm.date" v-model="createForm.date"
type="date" type="date"
required required
class="w-full px-3 py-2 bg-background border border-border rounded-lg text-text-primary focus:outline-none focus:border-amber" class="w-full px-3 py-2 bg-bg-inset border border-border-subtle rounded text-[0.8125rem] text-text-primary focus:outline-none focus:border-border-visible"
/> />
</div> </div>
<div> <div>
<label class="block text-sm text-text-secondary mb-1">Due Date</label> <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Due Date</label>
<input <input
v-model="createForm.due_date" v-model="createForm.due_date"
type="date" type="date"
class="w-full px-3 py-2 bg-background border border-border rounded-lg text-text-primary focus:outline-none focus:border-amber" class="w-full px-3 py-2 bg-bg-inset border border-border-subtle rounded text-[0.8125rem] text-text-primary focus:outline-none focus:border-border-visible"
/> />
</div> </div>
</div> </div>
@@ -164,56 +166,56 @@
<!-- Tax Rate --> <!-- Tax Rate -->
<div class="grid grid-cols-2 gap-4"> <div class="grid grid-cols-2 gap-4">
<div> <div>
<label class="block text-sm text-text-secondary mb-1">Tax Rate (%)</label> <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Tax Rate (%)</label>
<input <input
v-model.number="createForm.tax_rate" v-model.number="createForm.tax_rate"
type="number" type="number"
min="0" min="0"
max="100" max="100"
step="0.01" step="0.01"
class="w-full px-3 py-2 bg-background border border-border rounded-lg text-text-primary focus:outline-none focus:border-amber" class="w-full px-3 py-2 bg-bg-inset border border-border-subtle rounded text-[0.8125rem] text-text-primary focus:outline-none focus:border-border-visible"
/> />
</div> </div>
<div> <div>
<label class="block text-sm text-text-secondary mb-1">Discount ($)</label> <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Discount ($)</label>
<input <input
v-model.number="createForm.discount" v-model.number="createForm.discount"
type="number" type="number"
min="0" min="0"
step="0.01" step="0.01"
class="w-full px-3 py-2 bg-background border border-border rounded-lg text-text-primary focus:outline-none focus:border-amber" class="w-full px-3 py-2 bg-bg-inset border border-border-subtle rounded text-[0.8125rem] text-text-primary focus:outline-none focus:border-border-visible"
/> />
</div> </div>
</div> </div>
<!-- Notes --> <!-- Notes -->
<div> <div>
<label class="block text-sm text-text-secondary mb-1">Notes</label> <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Notes</label>
<textarea <textarea
v-model="createForm.notes" v-model="createForm.notes"
rows="3" rows="3"
class="w-full px-3 py-2 bg-background border border-border rounded-lg text-text-primary focus:outline-none focus:border-amber" class="w-full px-3 py-2 bg-bg-inset border border-border-subtle rounded text-[0.8125rem] text-text-primary focus:outline-none focus:border-border-visible"
placeholder="Additional notes for the invoice" placeholder="Additional notes"
></textarea> ></textarea>
</div> </div>
<!-- Calculated Total --> <!-- Calculated Total -->
<div class="bg-surface-elevated rounded-lg p-4"> <div class="bg-bg-inset rounded p-4">
<div class="flex justify-between text-text-secondary mb-2"> <div class="flex justify-between text-[0.75rem] text-text-secondary mb-2">
<span>Subtotal:</span> <span>Subtotal:</span>
<span>${{ calculateSubtotal().toFixed(2) }}</span> <span class="font-mono">${{ calculateSubtotal().toFixed(2) }}</span>
</div> </div>
<div class="flex justify-between text-text-secondary mb-2"> <div class="flex justify-between text-[0.75rem] text-text-secondary mb-2">
<span>Tax ({{ createForm.tax_rate }}%):</span> <span>Tax ({{ createForm.tax_rate }}%):</span>
<span>${{ calculateTax().toFixed(2) }}</span> <span class="font-mono">${{ calculateTax().toFixed(2) }}</span>
</div> </div>
<div class="flex justify-between text-text-secondary mb-2"> <div class="flex justify-between text-[0.75rem] text-text-secondary mb-2">
<span>Discount:</span> <span>Discount:</span>
<span>-${{ createForm.discount.toFixed(2) }}</span> <span class="font-mono">-${{ createForm.discount.toFixed(2) }}</span>
</div> </div>
<div class="flex justify-between text-text-primary font-bold text-lg pt-2 border-t border-border"> <div class="flex justify-between text-text-primary font-medium text-[0.8125rem] pt-2 border-t border-border-subtle">
<span>Total:</span> <span>Total:</span>
<span>${{ calculateTotal().toFixed(2) }}</span> <span class="font-mono text-accent-text">${{ calculateTotal().toFixed(2) }}</span>
</div> </div>
</div> </div>
@@ -222,13 +224,13 @@
<button <button
type="button" type="button"
@click="view = 'list'" @click="view = 'list'"
class="px-4 py-2 border border-border text-text-primary rounded-lg hover:bg-surface-elevated transition-colors" class="px-4 py-2 border border-border-subtle text-text-secondary rounded hover:bg-bg-elevated transition-colors duration-150"
> >
Cancel Cancel
</button> </button>
<button <button
type="submit" type="submit"
class="px-4 py-2 bg-amber text-background font-medium rounded-lg hover:bg-amber-hover transition-colors" class="px-4 py-2 bg-accent text-bg-base font-medium rounded hover:bg-accent-hover transition-colors duration-150"
> >
Create Invoice Create Invoice
</button> </button>
@@ -236,24 +238,24 @@
</form> </form>
</div> </div>
<!-- Invoice Detail View --> <!-- Invoice Detail Dialog -->
<div <div
v-if="showDetailDialog" v-if="showDetailDialog"
class="fixed inset-0 bg-black/50 flex items-center justify-center z-50" class="fixed inset-0 bg-black/70 backdrop-blur-[4px] flex items-center justify-center z-50"
@click.self="showDetailDialog = false" @click.self="showDetailDialog = false"
> >
<div class="bg-surface border border-border rounded-lg w-full max-w-2xl mx-4 p-6 max-h-[90vh] overflow-y-auto"> <div class="bg-bg-surface border border-border-subtle rounded shadow-[0_1px_3px_rgba(0,0,0,0.3)] w-full max-w-2xl mx-4 p-6 max-h-[90vh] overflow-y-auto animate-modal-enter">
<div class="flex items-start justify-between mb-6"> <div class="flex items-start justify-between mb-6">
<div> <div>
<h2 class="text-2xl font-bold text-text-primary">Invoice {{ selectedInvoice?.invoice_number }}</h2> <h2 class="text-[1.5rem] font-medium text-text-primary">{{ selectedInvoice?.invoice_number }}</h2>
<p class="text-text-secondary">{{ getClientName(selectedInvoice?.client_id || 0) }}</p> <p class="text-[0.75rem] text-text-secondary">{{ getClientName(selectedInvoice?.client_id || 0) }}</p>
</div> </div>
<button <button
@click="showDetailDialog = false" @click="showDetailDialog = false"
class="p-2 text-text-secondary hover:text-text-primary transition-colors" class="p-2 text-text-tertiary hover:text-text-secondary transition-colors duration-150"
> >
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /> <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg> </svg>
</button> </button>
</div> </div>
@@ -261,44 +263,44 @@
<div class="space-y-4"> <div class="space-y-4">
<div class="grid grid-cols-2 gap-4"> <div class="grid grid-cols-2 gap-4">
<div> <div>
<p class="text-sm text-text-secondary">Invoice Date</p> <p class="text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1">Invoice Date</p>
<p class="text-text-primary">{{ formatDate(selectedInvoice?.date || '') }}</p> <p class="text-[0.8125rem] text-text-primary">{{ formatDate(selectedInvoice?.date || '') }}</p>
</div> </div>
<div> <div>
<p class="text-sm text-text-secondary">Due Date</p> <p class="text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1">Due Date</p>
<p class="text-text-primary">{{ selectedInvoice?.due_date ? formatDate(selectedInvoice.due_date) : '-' }}</p> <p class="text-[0.8125rem] text-text-primary">{{ selectedInvoice?.due_date ? formatDate(selectedInvoice.due_date) : '-' }}</p>
</div> </div>
</div> </div>
<div class="border-t border-border pt-4"> <div class="border-t border-border-subtle pt-4">
<div class="flex justify-between text-text-secondary mb-2"> <div class="flex justify-between text-[0.75rem] text-text-secondary mb-2">
<span>Subtotal:</span> <span>Subtotal:</span>
<span>${{ (selectedInvoice?.subtotal || 0).toFixed(2) }}</span> <span class="font-mono">${{ (selectedInvoice?.subtotal || 0).toFixed(2) }}</span>
</div> </div>
<div class="flex justify-between text-text-secondary mb-2"> <div class="flex justify-between text-[0.75rem] text-text-secondary mb-2">
<span>Tax ({{ selectedInvoice?.tax_rate || 0 }}%):</span> <span>Tax ({{ selectedInvoice?.tax_rate || 0 }}%):</span>
<span>${{ (selectedInvoice?.tax_amount || 0).toFixed(2) }}</span> <span class="font-mono">${{ (selectedInvoice?.tax_amount || 0).toFixed(2) }}</span>
</div> </div>
<div class="flex justify-between text-text-secondary mb-2"> <div class="flex justify-between text-[0.75rem] text-text-secondary mb-2">
<span>Discount:</span> <span>Discount:</span>
<span>-${{ (selectedInvoice?.discount || 0).toFixed(2) }}</span> <span class="font-mono">-${{ (selectedInvoice?.discount || 0).toFixed(2) }}</span>
</div> </div>
<div class="flex justify-between text-text-primary font-bold text-lg pt-2 border-t border-border"> <div class="flex justify-between text-text-primary font-medium text-[0.8125rem] pt-2 border-t border-border-subtle">
<span>Total:</span> <span>Total:</span>
<span>${{ (selectedInvoice?.total || 0).toFixed(2) }}</span> <span class="font-mono text-accent-text">${{ (selectedInvoice?.total || 0).toFixed(2) }}</span>
</div> </div>
</div> </div>
<div v-if="selectedInvoice?.notes" class="border-t border-border pt-4"> <div v-if="selectedInvoice?.notes" class="border-t border-border-subtle pt-4">
<p class="text-sm text-text-secondary mb-1">Notes</p> <p class="text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1">Notes</p>
<p class="text-text-primary">{{ selectedInvoice.notes }}</p> <p class="text-[0.8125rem] text-text-primary">{{ selectedInvoice.notes }}</p>
</div> </div>
</div> </div>
<div class="flex justify-end gap-3 mt-6"> <div class="flex justify-end gap-3 mt-6">
<button <button
@click="exportPDF(selectedInvoice!)" @click="exportPDF(selectedInvoice!)"
class="px-4 py-2 bg-amber text-background font-medium rounded-lg hover:bg-amber-hover transition-colors" class="px-4 py-2 bg-accent text-bg-base font-medium rounded hover:bg-accent-hover transition-colors duration-150"
> >
Export PDF Export PDF
</button> </button>
@@ -309,24 +311,24 @@
<!-- Delete Confirmation Dialog --> <!-- Delete Confirmation Dialog -->
<div <div
v-if="showDeleteDialog" v-if="showDeleteDialog"
class="fixed inset-0 bg-black/50 flex items-center justify-center z-50" class="fixed inset-0 bg-black/70 backdrop-blur-[4px] flex items-center justify-center z-50"
@click.self="showDeleteDialog = false" @click.self="showDeleteDialog = false"
> >
<div class="bg-surface border border-border rounded-lg w-full max-w-sm mx-4 p-6"> <div class="bg-bg-surface border border-border-subtle rounded shadow-[0_1px_3px_rgba(0,0,0,0.3)] w-full max-w-sm mx-4 p-6 animate-modal-enter">
<h2 class="text-xl font-bold mb-2">Delete Invoice</h2> <h2 class="text-[1rem] font-semibold text-text-primary mb-2">Delete Invoice</h2>
<p class="text-text-secondary mb-6"> <p class="text-[0.75rem] text-text-secondary mb-6">
Are you sure you want to delete invoice "{{ invoiceToDelete?.invoice_number }}"? This action cannot be undone. Are you sure you want to delete invoice "{{ invoiceToDelete?.invoice_number }}"? This action cannot be undone.
</p> </p>
<div class="flex justify-end gap-3"> <div class="flex justify-end gap-3">
<button <button
@click="showDeleteDialog = false" @click="showDeleteDialog = false"
class="px-4 py-2 border border-border text-text-primary rounded-lg hover:bg-surface-elevated transition-colors" class="px-4 py-2 border border-border-subtle text-text-secondary rounded hover:bg-bg-elevated transition-colors duration-150"
> >
Cancel Cancel
</button> </button>
<button <button
@click="handleDelete" @click="handleDelete"
class="px-4 py-2 bg-error text-white font-medium rounded-lg hover:opacity-90 transition-opacity" class="px-4 py-2 border border-status-error text-status-error font-medium rounded hover:bg-status-error/10 transition-colors duration-150"
> >
Delete Delete
</button> </button>
@@ -338,12 +340,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, onMounted } from 'vue' import { ref, reactive, onMounted } from 'vue'
import { invoke } from '@tauri-apps/api/core' import { FileText } from 'lucide-vue-next'
import { useInvoicesStore, type Invoice } from '../stores/invoices' import { useInvoicesStore, type Invoice } from '../stores/invoices'
import { useClientsStore } from '../stores/clients' import { useClientsStore } from '../stores/clients'
import { useToastStore } from '../stores/toast'
import { generateInvoicePdf } from '../utils/invoicePdf'
const invoicesStore = useInvoicesStore() const invoicesStore = useInvoicesStore()
const clientsStore = useClientsStore() const clientsStore = useClientsStore()
const toastStore = useToastStore()
// View state // View state
const view = ref<'list' | 'create'>('list') const view = ref<'list' | 'create'>('list')
@@ -383,7 +388,6 @@ function formatDate(dateString: string): string {
// Calculate subtotal (simplified - would need line items in a real app) // Calculate subtotal (simplified - would need line items in a real app)
function calculateSubtotal(): number { function calculateSubtotal(): number {
// For now, just return 0 - would need time entries or line items
return 0 return 0
} }
@@ -421,7 +425,7 @@ async function handleDelete() {
// Handle create // Handle create
async function handleCreate() { async function handleCreate() {
if (!createForm.client_id) { if (!createForm.client_id) {
alert('Please select a client') toastStore.info('Please select a client')
return return
} }
@@ -447,14 +451,16 @@ async function handleCreate() {
view.value = 'list' view.value = 'list'
} }
// Export PDF (placeholder - would need PDF generation) // Export PDF using client-side jsPDF
async function exportPDF(invoice: Invoice) { function exportPDF(invoice: Invoice) {
try { const client = clientsStore.clients.find(c => c.id === invoice.client_id)
await invoke('export_invoice_pdf', { invoiceId: invoice.id }) if (!client) {
} catch (error) { console.error('Client not found for invoice')
console.error('Failed to export PDF:', error) return
alert('Failed to export PDF. This feature requires backend implementation.')
} }
const doc = generateInvoicePdf(invoice, client, [])
doc.save(`${invoice.invoice_number}.pdf`)
} }
// Load data on mount // Load data on mount