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:
@@ -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',
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user