feat: add business identity settings for invoice branding
This commit is contained in:
@@ -26,6 +26,8 @@
|
|||||||
|
|
||||||
<!-- Content pane -->
|
<!-- Content pane -->
|
||||||
<div class="flex-1 p-6 overflow-y-auto">
|
<div class="flex-1 p-6 overflow-y-auto">
|
||||||
|
<Transition name="fade" mode="out-in" :duration="{ enter: 200, leave: 150 }">
|
||||||
|
<div :key="activeTab">
|
||||||
<!-- General -->
|
<!-- General -->
|
||||||
<div v-if="activeTab === 'general'">
|
<div v-if="activeTab === 'general'">
|
||||||
<h2 class="text-[1.25rem] font-semibold font-[family-name:var(--font-heading)] text-text-primary mb-6">General</h2>
|
<h2 class="text-[1.25rem] font-semibold font-[family-name:var(--font-heading)] text-text-primary mb-6">General</h2>
|
||||||
@@ -380,6 +382,85 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Divider -->
|
||||||
|
<div class="border-t border-border-subtle mt-5 pt-5" />
|
||||||
|
|
||||||
|
<!-- Business Identity -->
|
||||||
|
<h3 class="text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] font-medium mb-4">Business Identity</h3>
|
||||||
|
<p class="text-[0.6875rem] text-text-tertiary mb-4">This information appears on your invoices.</p>
|
||||||
|
|
||||||
|
<div class="space-y-4 max-w-md">
|
||||||
|
<div>
|
||||||
|
<label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Business Name</label>
|
||||||
|
<input
|
||||||
|
v-model="businessName"
|
||||||
|
type="text"
|
||||||
|
class="w-full px-3 py-2 bg-bg-inset border border-border-subtle rounded-lg text-[0.8125rem] text-text-primary focus:outline-none focus:border-border-visible"
|
||||||
|
placeholder="Your Company Name"
|
||||||
|
@change="saveBusinessSettings"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Address</label>
|
||||||
|
<textarea
|
||||||
|
v-model="businessAddress"
|
||||||
|
rows="3"
|
||||||
|
class="w-full px-3 py-2 bg-bg-inset border border-border-subtle rounded-lg text-[0.8125rem] text-text-primary focus:outline-none focus:border-border-visible resize-none"
|
||||||
|
placeholder="123 Business St City, State ZIP"
|
||||||
|
@change="saveBusinessSettings"
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Email</label>
|
||||||
|
<input
|
||||||
|
v-model="businessEmail"
|
||||||
|
type="email"
|
||||||
|
class="w-full px-3 py-2 bg-bg-inset border border-border-subtle rounded-lg text-[0.8125rem] text-text-primary focus:outline-none focus:border-border-visible"
|
||||||
|
placeholder="billing@company.com"
|
||||||
|
@change="saveBusinessSettings"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Phone</label>
|
||||||
|
<input
|
||||||
|
v-model="businessPhone"
|
||||||
|
type="tel"
|
||||||
|
class="w-full px-3 py-2 bg-bg-inset border border-border-subtle rounded-lg text-[0.8125rem] text-text-primary focus:outline-none focus:border-border-visible"
|
||||||
|
placeholder="(555) 123-4567"
|
||||||
|
@change="saveBusinessSettings"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Logo</label>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<div
|
||||||
|
v-if="businessLogo"
|
||||||
|
class="w-20 h-12 border border-border-subtle rounded flex items-center justify-center overflow-hidden bg-white"
|
||||||
|
>
|
||||||
|
<img :src="businessLogo" class="max-w-full max-h-full object-contain" />
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<button
|
||||||
|
@click="uploadLogo"
|
||||||
|
class="px-3 py-1.5 text-[0.75rem] border border-border-subtle text-text-secondary rounded-lg hover:bg-bg-elevated transition-colors"
|
||||||
|
>
|
||||||
|
{{ businessLogo ? 'Change' : 'Upload' }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="businessLogo"
|
||||||
|
@click="removeLogo"
|
||||||
|
class="px-3 py-1.5 text-[0.75rem] border border-border-subtle text-status-error rounded-lg hover:bg-status-error/10 transition-colors"
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="text-[0.625rem] text-text-tertiary mt-1">PNG or JPG, max 200x80px. Appears on invoice header.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Data -->
|
<!-- Data -->
|
||||||
@@ -468,6 +549,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Clear Data Confirmation Dialog -->
|
<!-- Clear Data Confirmation Dialog -->
|
||||||
@@ -554,6 +637,13 @@ const roundingEnabled = ref(false)
|
|||||||
const roundingIncrement = ref(15)
|
const roundingIncrement = ref(15)
|
||||||
const roundingMethod = ref('nearest')
|
const roundingMethod = ref('nearest')
|
||||||
|
|
||||||
|
// Business identity settings
|
||||||
|
const businessName = ref('')
|
||||||
|
const businessAddress = ref('')
|
||||||
|
const businessEmail = ref('')
|
||||||
|
const businessPhone = ref('')
|
||||||
|
const businessLogo = ref('')
|
||||||
|
|
||||||
const roundingIncrements = [
|
const roundingIncrements = [
|
||||||
{ value: 1, label: '1 minute' },
|
{ value: 1, label: '1 minute' },
|
||||||
{ value: 5, label: '5 minutes' },
|
{ value: 5, label: '5 minutes' },
|
||||||
@@ -701,6 +791,40 @@ async function saveRoundingSettings() {
|
|||||||
await settingsStore.updateSetting('rounding_method', roundingMethod.value)
|
await settingsStore.updateSetting('rounding_method', roundingMethod.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save business identity settings
|
||||||
|
async function saveBusinessSettings() {
|
||||||
|
await settingsStore.updateSetting('business_name', businessName.value)
|
||||||
|
await settingsStore.updateSetting('business_address', businessAddress.value)
|
||||||
|
await settingsStore.updateSetting('business_email', businessEmail.value)
|
||||||
|
await settingsStore.updateSetting('business_phone', businessPhone.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uploadLogo() {
|
||||||
|
try {
|
||||||
|
const { open } = await import('@tauri-apps/plugin-dialog')
|
||||||
|
const selected = await open({
|
||||||
|
filters: [{ name: 'Images', extensions: ['png', 'jpg', 'jpeg'] }]
|
||||||
|
})
|
||||||
|
if (!selected) return
|
||||||
|
|
||||||
|
const { readFile } = await import('@tauri-apps/plugin-fs')
|
||||||
|
const bytes = await readFile(selected as string)
|
||||||
|
const ext = (selected as string).toLowerCase().endsWith('.png') ? 'png' : 'jpeg'
|
||||||
|
const base64 = btoa(String.fromCharCode(...new Uint8Array(bytes)))
|
||||||
|
const dataUrl = `data:image/${ext};base64,${base64}`
|
||||||
|
businessLogo.value = dataUrl
|
||||||
|
await settingsStore.updateSetting('business_logo', dataUrl)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to upload logo:', e)
|
||||||
|
toastStore.error('Failed to upload logo')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeLogo() {
|
||||||
|
businessLogo.value = ''
|
||||||
|
await settingsStore.updateSetting('business_logo', '')
|
||||||
|
}
|
||||||
|
|
||||||
// Import file handling
|
// Import file handling
|
||||||
async function handleImportFile() {
|
async function handleImportFile() {
|
||||||
try {
|
try {
|
||||||
@@ -816,5 +940,10 @@ onMounted(async () => {
|
|||||||
roundingEnabled.value = settingsStore.settings.rounding_enabled === 'true'
|
roundingEnabled.value = settingsStore.settings.rounding_enabled === 'true'
|
||||||
roundingIncrement.value = parseInt(settingsStore.settings.rounding_increment) || 15
|
roundingIncrement.value = parseInt(settingsStore.settings.rounding_increment) || 15
|
||||||
roundingMethod.value = settingsStore.settings.rounding_method || 'nearest'
|
roundingMethod.value = settingsStore.settings.rounding_method || 'nearest'
|
||||||
|
businessName.value = settingsStore.settings.business_name || ''
|
||||||
|
businessAddress.value = settingsStore.settings.business_address || ''
|
||||||
|
businessEmail.value = settingsStore.settings.business_email || ''
|
||||||
|
businessPhone.value = settingsStore.settings.business_phone || ''
|
||||||
|
businessLogo.value = settingsStore.settings.business_logo || ''
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user