complete export/import cycle and remove sample data

Export now includes invoice_payments and recurring_invoices tables.
Import restored to use ID-based lookups and all fields for clients,
projects, tasks, and time entries. Added missing import support for
timeline_events, calendar_sources, calendar_events, invoice_payments,
and recurring_invoices. Export uses native save dialog instead of blob
download. Removed sample data seeding (seed.rs, UI, command).
This commit is contained in:
Your Name
2026-02-21 01:34:26 +02:00
parent d4dba6cfd8
commit 55941a4d41
5 changed files with 177 additions and 796 deletions

View File

@@ -26,11 +26,20 @@ const entityLabels: Record<string, string> = {
tasks: 'Tasks',
time_entries: 'Time Entries',
tags: 'Tags',
entry_tags: 'Entry Tags',
invoices: 'Invoices',
invoice_items: 'Invoice Items',
invoice_payments: 'Invoice Payments',
recurring_invoices: 'Recurring Invoices',
expenses: 'Expenses',
favorites: 'Favorites',
recurring_entries: 'Recurring Entries',
tracked_apps: 'Tracked Apps',
timeline_events: 'Timeline Events',
calendar_sources: 'Calendar Sources',
calendar_events: 'Calendar Events',
timesheet_locks: 'Timesheet Locks',
timesheet_rows: 'Timesheet Rows',
entry_templates: 'Entry Templates',
settings: 'Settings',
}

View File

@@ -1402,7 +1402,7 @@
<div>
<div class="flex items-center justify-between mb-4">
<h3 class="text-[0.8125rem] font-medium text-text-primary">Import Data</h3>
<button @click="showJsonImportWizard = true" class="px-3 py-1.5 text-[0.6875rem] border border-border-subtle text-text-secondary rounded-lg hover:bg-bg-elevated transition-colors">
<button @click="showJsonImportWizard = true" class="px-3 py-1.5 text-[0.8125rem] border border-border-subtle text-text-secondary rounded-lg hover:bg-bg-elevated transition-colors">
Restore from Backup
</button>
</div>
@@ -1463,19 +1463,6 @@
<div class="rounded-xl border border-status-error/20 p-5">
<h3 class="text-xs text-status-error-text uppercase tracking-[0.08em] font-medium mb-4">Danger Zone</h3>
<div class="space-y-4">
<div class="flex items-center justify-between">
<div>
<p class="text-[0.8125rem] text-text-primary">Load Sample Data</p>
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">Populate with demo data for screenshots (clears existing data first)</p>
</div>
<button
@click="showSeedDialog = true"
:disabled="isSeedingData"
class="px-4 py-1.5 border border-status-warning text-status-warning text-[0.8125rem] font-medium rounded-lg hover:bg-status-warning/10 transition-colors duration-150 disabled:opacity-40"
>
{{ isSeedingData ? 'Loading...' : 'Load Demo' }}
</button>
</div>
<div class="flex items-center justify-between">
<div>
<p class="text-[0.8125rem] text-text-primary">Clear All Data</p>
@@ -1530,38 +1517,6 @@
</div>
</Transition>
<!-- Seed Data Confirmation Dialog -->
<Transition name="modal">
<div
v-if="showSeedDialog"
class="fixed inset-0 bg-black/70 backdrop-blur-[4px] flex items-center justify-center p-4 z-50"
@click.self="showSeedDialog = false"
>
<div role="alertdialog" aria-modal="true" aria-labelledby="seed-data-title" aria-describedby="seed-data-desc" class="bg-bg-surface border border-border-subtle rounded-lg shadow-[0_1px_3px_rgba(0,0,0,0.3)] w-full max-w-sm p-6">
<h2 id="seed-data-title" class="text-[1.125rem] font-semibold font-[family-name:var(--font-heading)] text-text-primary mb-2">Load Sample Data</h2>
<p id="seed-data-desc" class="text-[0.75rem] text-text-secondary mb-4">
This will clear all existing data and replace it with demo content.
</p>
<p class="text-[0.6875rem] text-status-warning mb-6">
All current entries, projects, clients, and invoices will be replaced.
</p>
<div class="flex justify-end gap-3">
<button
@click="showSeedDialog = false"
class="px-4 py-2 border border-border-subtle text-text-secondary rounded-lg hover:bg-bg-elevated transition-colors duration-150"
>
Cancel
</button>
<button
@click="seedSampleData"
class="px-4 py-2 bg-status-warning text-white font-medium rounded-lg hover:bg-status-warning/80 transition-colors duration-150"
>
Load Demo Data
</button>
</div>
</div>
</div>
</Transition>
<JsonImportWizard
:show="showJsonImportWizard"
@@ -1965,9 +1920,6 @@ const showClearDataDialog = ref(false)
const clearDialogRef = ref<HTMLElement | null>(null)
const { activate: activateClearTrap, deactivate: deactivateClearTrap } = useFocusTrap()
const showSeedDialog = ref(false)
const isSeedingData = ref(false)
watch(showClearDataDialog, (val) => {
if (val) {
setTimeout(() => {
@@ -2450,20 +2402,23 @@ async function executeImport() {
// Export all data
async function exportData() {
try {
const data = await invoke('export_data')
const { save } = await import('@tauri-apps/plugin-dialog')
const { writeTextFile } = await import('@tauri-apps/plugin-fs')
const filePath = await save({
defaultPath: `zeroclock-export-${new Date().toISOString().split('T')[0]}.json`,
filters: [{ name: 'JSON', extensions: ['json'] }],
})
if (!filePath) return
const data = await invoke('export_data')
const json = JSON.stringify(data, null, 2)
const blob = new Blob([json], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `zeroclock-export-${new Date().toISOString().split('T')[0]}.json`
a.click()
URL.revokeObjectURL(url)
await writeTextFile(filePath, json)
const now = new Date().toISOString()
await settingsStore.updateSetting('last_exported', now)
lastExported.value = now
toastStore.success('Data exported successfully')
} catch (error) {
console.error('Failed to export data:', error)
toastStore.error('Failed to export data')
@@ -2482,21 +2437,6 @@ async function clearAllData() {
}
}
async function seedSampleData() {
isSeedingData.value = true
showSeedDialog.value = false
try {
await invoke('seed_sample_data')
toastStore.success('Sample data loaded successfully')
setTimeout(() => { window.location.reload() }, 500)
} catch (error) {
console.error('Failed to load sample data:', error)
toastStore.error('Failed to load sample data')
} finally {
isSeedingData.value = false
}
}
// Load settings on mount
onMounted(async () => {
await settingsStore.fetchSettings()