BillTracker/client/components/RecentlyDeletedBillsDialog.jsx

86 lines
3.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState } from 'react';
import { RotateCcw, Trash2, Loader2 } from 'lucide-react';
import {
Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { formatUSD } from '@/lib/money';
function daysLeftLabel(days) {
if (days == null) return null;
if (days <= 0) return 'purges today';
if (days === 1) return '1 day left';
return `${days} days left`;
}
/**
* Lists bills that were soft-deleted within the 30-day recovery window and lets
* the user restore them — a durable path beyond the transient "Undo" toast.
*
* Presentational: the parent owns the list (`bills`) and the async `onRestore`,
* so restoring refreshes the page's active bills too.
*/
export default function RecentlyDeletedBillsDialog({ open, onOpenChange, bills = [], onRestore }) {
const [busyId, setBusyId] = useState(null);
async function handleRestore(bill) {
setBusyId(bill.id);
try {
await onRestore(bill);
} finally {
setBusyId(null);
}
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-lg">
<DialogHeader>
<DialogTitle>Recently deleted</DialogTitle>
<DialogDescription>
Deleted bills are kept for 30 days before theyre permanently removed. Restore one to bring it back.
</DialogDescription>
</DialogHeader>
{bills.length === 0 ? (
<div className="flex flex-col items-center gap-2 py-8 text-center">
<Trash2 className="h-8 w-8 text-muted-foreground/40" />
<p className="text-sm text-muted-foreground">Nothing to recover</p>
<p className="text-xs text-muted-foreground/70">Bills you delete will appear here for 30 days.</p>
</div>
) : (
<ul className="-mx-2 max-h-[55vh] divide-y divide-border/60 overflow-y-auto">
{bills.map(bill => {
const left = daysLeftLabel(bill.days_left);
const busy = busyId === bill.id;
return (
<li key={bill.id} className="flex items-center gap-3 px-2 py-2.5">
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-medium">{bill.name}</p>
<p className="truncate text-xs text-muted-foreground">
{formatUSD(bill.expected_amount)}
{bill.category_name ? ` · ${bill.category_name}` : ''}
{left ? <span className="text-muted-foreground/60"> · {left}</span> : null}
</p>
</div>
<Button
type="button"
size="sm"
variant="outline"
className="h-8 shrink-0 gap-1.5"
disabled={busy}
onClick={() => handleRestore(bill)}
>
{busy ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <RotateCcw className="h-3.5 w-3.5" />}
Restore
</Button>
</li>
);
})}
</ul>
)}
</DialogContent>
</Dialog>
);
}