149 lines
6.6 KiB
TypeScript
149 lines
6.6 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import { toast } from 'sonner';
|
|
import { Loader2 } from 'lucide-react';
|
|
import { api } from '@/api';
|
|
import { errMessage } from '@/lib/utils';
|
|
import { Button } from '@/components/ui/button';
|
|
import {
|
|
AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent,
|
|
AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle,
|
|
AlertDialogTrigger,
|
|
} from '@/components/ui/alert-dialog';
|
|
import { SectionCard, type SectionCardProps } from './dataShared';
|
|
|
|
export default function SeedDemoDataSection({ onSeeded, cardProps = {} }: {
|
|
onSeeded?: () => void;
|
|
cardProps?: Partial<SectionCardProps>;
|
|
}) {
|
|
const [loading, setLoading] = useState(false);
|
|
const [seeded, setSeeded] = useState(false);
|
|
const [counts, setCounts] = useState<{ bills: number; categories: number; payments: number; transactions: number }>(
|
|
{ bills: 0, categories: 0, payments: 0, transactions: 0 }
|
|
);
|
|
const [clearing, setClearing] = useState(false);
|
|
const [showClearConfirm, setShowClearConfirm] = useState(false);
|
|
const [statusLoading, setStatusLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
api.seededStatus()
|
|
.then(raw => {
|
|
const data = raw as { seeded?: boolean; seededBills?: number; seededCategories?: number; seededPayments?: number; seededTransactions?: number };
|
|
setSeeded(!!data.seeded);
|
|
if (data.seeded) setCounts({
|
|
bills: data.seededBills || 0,
|
|
categories: data.seededCategories || 0,
|
|
payments: data.seededPayments || 0,
|
|
transactions: data.seededTransactions || 0,
|
|
});
|
|
})
|
|
.catch(err => console.error('Failed to check seeded status:', err))
|
|
.finally(() => setStatusLoading(false));
|
|
}, []);
|
|
|
|
const handleSeed = async () => {
|
|
setLoading(true);
|
|
try {
|
|
const data = await api.seedDemoData() as { billsCreated?: number; categoriesCreated?: number; paymentsCreated?: number; transactionsCreated?: number } | null;
|
|
if (!data || typeof data !== 'object') throw new Error('Invalid response from server');
|
|
setCounts({
|
|
bills: data.billsCreated || 0,
|
|
categories: data.categoriesCreated || 0,
|
|
payments: data.paymentsCreated || 0,
|
|
transactions: data.transactionsCreated || 0,
|
|
});
|
|
setSeeded(true);
|
|
toast.success(`Seeded ${data.billsCreated || 0} bills with ${data.paymentsCreated || 0} payments and ${data.transactionsCreated || 0} transactions.`);
|
|
setTimeout(() => onSeeded?.(), 100);
|
|
} catch (err) {
|
|
console.error('Seed error:', err);
|
|
toast.error(errMessage(err, 'Failed to seed demo data.'));
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleClearDemoData = async () => {
|
|
setClearing(true);
|
|
try {
|
|
const data = await api.clearDemoData() as { removed?: number };
|
|
setSeeded(false);
|
|
setCounts({ bills: 0, categories: 0, payments: 0, transactions: 0 });
|
|
setShowClearConfirm(false);
|
|
toast.success(`Removed all demo data (${data.removed || 0} records). Your own data is untouched.`);
|
|
onSeeded?.();
|
|
} catch (err) {
|
|
toast.error(errMessage(err, 'Failed to clear demo data.'));
|
|
} finally {
|
|
setClearing(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<SectionCard title="Demo Data" subtitle="Seed your database with demo data for testing" {...cardProps}>
|
|
<div className="rounded-lg border border-border/60 bg-background/50 p-4">
|
|
{statusLoading ? (
|
|
<p className="text-sm text-muted-foreground">Loading…</p>
|
|
) : seeded ? (
|
|
<>
|
|
<p className="text-sm font-medium text-emerald-600 dark:text-emerald-400">Demo data seeded</p>
|
|
<div className="mt-3 grid grid-cols-2 gap-4 text-xs sm:grid-cols-4">
|
|
<div>
|
|
<p className="text-muted-foreground">Bills</p>
|
|
<p className="font-semibold">{counts.bills}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-muted-foreground">Payments</p>
|
|
<p className="font-semibold">{counts.payments}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-muted-foreground">Transactions</p>
|
|
<p className="font-semibold">{counts.transactions}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-muted-foreground">Categories</p>
|
|
<p className="font-semibold">{counts.categories}</p>
|
|
</div>
|
|
</div>
|
|
</>
|
|
) : (
|
|
<p className="text-sm text-muted-foreground">
|
|
Populate every page with a realistic dataset — bills, months of payment history,
|
|
bank transactions, budgets, a debt snowball, and more — so you can explore the whole app.
|
|
It's tied to your account and fully removable; your own data is never touched.
|
|
</p>
|
|
)}
|
|
|
|
<div className="mt-4 flex items-center justify-between gap-3 border-t border-border pt-4">
|
|
<Button size="sm" variant="outline" onClick={handleSeed} disabled={loading || seeded || statusLoading}>
|
|
{loading ? <><Loader2 className="h-3.5 w-3.5 mr-1.5 animate-spin" />Seeding…</> : 'Seed Demo Data'}
|
|
</Button>
|
|
|
|
<AlertDialog open={showClearConfirm} onOpenChange={setShowClearConfirm}>
|
|
<AlertDialogTrigger asChild>
|
|
<Button size="sm" variant="destructive" disabled={!seeded || clearing || statusLoading}>
|
|
{clearing ? <><Loader2 className="h-3.5 w-3.5 mr-1.5 animate-spin" />Clearing…</> : 'Clear Demo Data'}
|
|
</Button>
|
|
</AlertDialogTrigger>
|
|
<AlertDialogContent>
|
|
<AlertDialogHeader>
|
|
<AlertDialogTitle>Clear Demo Data</AlertDialogTitle>
|
|
<AlertDialogDescription>
|
|
This removes all seeded demo data — {counts.bills} bills, {counts.payments} payments,
|
|
{' '}{counts.transactions} transactions and related records. Your own bills and categories
|
|
are kept. This cannot be undone.
|
|
</AlertDialogDescription>
|
|
</AlertDialogHeader>
|
|
<AlertDialogFooter>
|
|
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
<AlertDialogAction onClick={handleClearDemoData} className="bg-destructive text-destructive-foreground hover:bg-destructive/90">
|
|
{clearing ? <><Loader2 className="h-3.5 w-3.5 mr-1.5 animate-spin" />Clearing…</> : 'Clear Data'}
|
|
</AlertDialogAction>
|
|
</AlertDialogFooter>
|
|
</AlertDialogContent>
|
|
</AlertDialog>
|
|
</div>
|
|
</div>
|
|
</SectionCard>
|
|
);
|
|
}
|