285 lines
No EOL
8.9 KiB
Vue
285 lines
No EOL
8.9 KiB
Vue
<template>
|
|
<div class="min-h-screen bg-gray-50">
|
|
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
<div class="mb-8">
|
|
<h1 class="text-3xl font-bold text-gray-900">Data Migration</h1>
|
|
<p class="text-gray-600 mt-2">Migrate your data from localStorage to MongoDB</p>
|
|
</div>
|
|
|
|
<div class="bg-white rounded-lg shadow p-6">
|
|
<!-- Status -->
|
|
<div v-if="status" class="mb-6 p-4 rounded" :class="statusClass">
|
|
{{ status }}
|
|
</div>
|
|
|
|
<!-- Migration Section -->
|
|
<div class="mb-8">
|
|
<h3 class="text-lg font-semibold mb-4">1. Auto-Migration from localStorage</h3>
|
|
<button
|
|
@click="migrateFromLocalStorage"
|
|
:disabled="loading"
|
|
class="bg-blue-500 hover:bg-blue-600 disabled:bg-gray-400 text-white font-bold py-2 px-4 rounded">
|
|
{{ loading ? 'Migrating...' : 'Migrate from localStorage' }}
|
|
</button>
|
|
<p class="text-sm text-gray-600 mt-2">
|
|
This will automatically detect and migrate any existing localStorage data.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Manual Import Section -->
|
|
<div class="mb-8">
|
|
<h3 class="text-lg font-semibold mb-4">2. Manual Data Import</h3>
|
|
|
|
<!-- Current Balance -->
|
|
<div class="mb-4">
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">
|
|
Current Balance (CAD $)
|
|
</label>
|
|
<input
|
|
v-model.number="manualData.balance"
|
|
type="number"
|
|
step="0.01"
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
placeholder="0.00" />
|
|
</div>
|
|
|
|
<!-- Transactions JSON -->
|
|
<div class="mb-4">
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">
|
|
Transactions JSON
|
|
</label>
|
|
<textarea
|
|
v-model="manualData.transactionsJson"
|
|
rows="10"
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
placeholder="Paste your transactions JSON array here"></textarea>
|
|
<p class="text-sm text-gray-600 mt-1">
|
|
Paste the JSON array of your transactions (the data you provided earlier)
|
|
</p>
|
|
</div>
|
|
|
|
<button
|
|
@click="migrateManualData"
|
|
:disabled="loading"
|
|
class="bg-green-500 hover:bg-green-600 disabled:bg-gray-400 text-white font-bold py-2 px-4 rounded">
|
|
{{ loading ? 'Importing...' : 'Import Manual Data' }}
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Current Data Display -->
|
|
<div class="mb-8">
|
|
<h3 class="text-lg font-semibold mb-4">3. Current Data Status</h3>
|
|
<button
|
|
@click="loadCurrentData"
|
|
:disabled="loading"
|
|
class="bg-gray-500 hover:bg-gray-600 disabled:bg-gray-400 text-white font-bold py-2 px-4 rounded mb-4">
|
|
{{ loading ? 'Loading...' : 'Refresh Data Status' }}
|
|
</button>
|
|
|
|
<div class="bg-gray-50 p-4 rounded">
|
|
<p><strong>Current Balance:</strong> {{ formatCurrency(currentData.balance) }}</p>
|
|
<p><strong>Transactions:</strong> {{ currentData.transactions.length }} items</p>
|
|
<div v-if="currentData.transactions.length > 0" class="mt-2">
|
|
<p class="text-sm font-medium">Sample transactions:</p>
|
|
<ul class="text-sm text-gray-600 mt-1">
|
|
<li v-for="transaction in currentData.transactions.slice(0, 3)" :key="transaction.id">
|
|
{{ transaction.description }}: {{ formatCurrency(transaction.amount) }}
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Navigation -->
|
|
<div class="pt-4 border-t">
|
|
<NuxtLink
|
|
to="/"
|
|
class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded">
|
|
Back to Dashboard
|
|
</NuxtLink>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
// Page metadata
|
|
useSeoMeta({
|
|
title: 'Data Migration - Faber Finances',
|
|
description: 'Migrate your financial data'
|
|
});
|
|
|
|
// State
|
|
const loading = ref(false);
|
|
const status = ref('');
|
|
const statusClass = ref('');
|
|
|
|
const manualData = ref({
|
|
balance: 0,
|
|
transactionsJson: ''
|
|
});
|
|
|
|
const currentData = ref({
|
|
balance: 0,
|
|
transactions: []
|
|
});
|
|
|
|
// Load current data status
|
|
const loadCurrentData = async () => {
|
|
loading.value = true;
|
|
try {
|
|
// Load balance
|
|
const balanceResponse = await fetch('/api/balances');
|
|
if (balanceResponse.ok) {
|
|
const balanceData = await balanceResponse.json();
|
|
currentData.value.balance = balanceData.currentBalance || 0;
|
|
}
|
|
|
|
// Load transactions
|
|
const transactionsResponse = await fetch('/api/transactions');
|
|
if (transactionsResponse.ok) {
|
|
const transactionsData = await transactionsResponse.json();
|
|
currentData.value.transactions = transactionsData;
|
|
}
|
|
|
|
setStatus('Data loaded successfully', 'success');
|
|
} catch (error) {
|
|
console.error('Failed to load current data:', error);
|
|
setStatus('Failed to load current data: ' + error.message, 'error');
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
};
|
|
|
|
// Migrate from localStorage
|
|
const migrateFromLocalStorage = async () => {
|
|
loading.value = true;
|
|
try {
|
|
if (typeof window === 'undefined') {
|
|
throw new Error('Window not available');
|
|
}
|
|
|
|
const savedBalance = localStorage.getItem('cashflow_balance');
|
|
const savedTransactions = localStorage.getItem('cashflow_transactions');
|
|
|
|
if (!savedBalance && !savedTransactions) {
|
|
setStatus('No localStorage data found to migrate', 'warning');
|
|
return;
|
|
}
|
|
|
|
const migrationData = {
|
|
balance: savedBalance ? parseFloat(savedBalance) : 0,
|
|
transactions: savedTransactions ? JSON.parse(savedTransactions) : []
|
|
};
|
|
|
|
const response = await fetch('/api/migrate/localStorage', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(migrationData)
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Migration failed: ${response.statusText}`);
|
|
}
|
|
|
|
const result = await response.json();
|
|
setStatus(
|
|
`Migration successful! Migrated ${result.transactionsCount} transactions and balance of ${formatCurrency(result.balance)}`,
|
|
'success'
|
|
);
|
|
|
|
// Clear localStorage after successful migration
|
|
localStorage.removeItem('cashflow_balance');
|
|
localStorage.removeItem('cashflow_transactions');
|
|
localStorage.removeItem('account_balances');
|
|
|
|
// Refresh current data
|
|
await loadCurrentData();
|
|
|
|
} catch (error) {
|
|
console.error('Migration error:', error);
|
|
setStatus('Migration failed: ' + error.message, 'error');
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
};
|
|
|
|
// Import manual data
|
|
const migrateManualData = async () => {
|
|
loading.value = true;
|
|
try {
|
|
let transactions = [];
|
|
|
|
if (manualData.value.transactionsJson.trim()) {
|
|
try {
|
|
transactions = JSON.parse(manualData.value.transactionsJson);
|
|
if (!Array.isArray(transactions)) {
|
|
throw new Error('Transactions must be an array');
|
|
}
|
|
} catch (error) {
|
|
throw new Error('Invalid JSON format: ' + error.message);
|
|
}
|
|
}
|
|
|
|
const migrationData = {
|
|
balance: manualData.value.balance || 0,
|
|
transactions: transactions
|
|
};
|
|
|
|
const response = await fetch('/api/migrate/localStorage', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(migrationData)
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Import failed: ${response.statusText}`);
|
|
}
|
|
|
|
const result = await response.json();
|
|
setStatus(
|
|
`Import successful! Imported ${result.transactionsCount} transactions and balance of ${formatCurrency(result.balance)}`,
|
|
'success'
|
|
);
|
|
|
|
// Clear form
|
|
manualData.value = { balance: 0, transactionsJson: '' };
|
|
|
|
// Refresh current data
|
|
await loadCurrentData();
|
|
|
|
} catch (error) {
|
|
console.error('Import error:', error);
|
|
setStatus('Import failed: ' + error.message, 'error');
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
};
|
|
|
|
// Utility functions
|
|
const setStatus = (message, type) => {
|
|
status.value = message;
|
|
statusClass.value = type === 'success' ? 'bg-green-100 text-green-800' :
|
|
type === 'warning' ? 'bg-yellow-100 text-yellow-800' :
|
|
type === 'error' ? 'bg-red-100 text-red-800' :
|
|
'bg-blue-100 text-blue-800';
|
|
|
|
// Clear status after 5 seconds
|
|
setTimeout(() => {
|
|
status.value = '';
|
|
}, 5000);
|
|
};
|
|
|
|
const formatCurrency = (amount) => {
|
|
return new Intl.NumberFormat('en-CA', {
|
|
style: 'currency',
|
|
currency: 'CAD'
|
|
}).format(amount || 0);
|
|
};
|
|
|
|
// Load current data on mount
|
|
onMounted(() => {
|
|
loadCurrentData();
|
|
});
|
|
</script> |