whisper-money/resources/js/components/accounts/update-balance-dialog.tsx

146 lines
4.8 KiB
TypeScript

import { store } from '@/actions/App/Http/Controllers/AccountBalanceController';
import { AmountInput } from '@/components/ui/amount-input';
import { Button } from '@/components/ui/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import type { Account } from '@/types/account';
import { useState } from 'react';
interface UpdateBalanceDialogProps {
account: Account;
open: boolean;
onOpenChange: (open: boolean) => void;
onSuccess?: () => void;
}
function getTodayDate(): string {
const today = new Date();
return today.toISOString().split('T')[0];
}
export function UpdateBalanceDialog({
account,
open,
onOpenChange,
onSuccess,
}: UpdateBalanceDialogProps) {
const [date, setDate] = useState(getTodayDate());
const [balance, setBalance] = useState(0);
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
function handleOpenChange(newOpen: boolean) {
if (!newOpen) {
setDate(getTodayDate());
setBalance(0);
setError(null);
}
onOpenChange(newOpen);
}
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setIsSubmitting(true);
setError(null);
try {
const response = await fetch(store.url(account.id), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-XSRF-TOKEN': decodeURIComponent(
document.cookie
.split('; ')
.find((row) => row.startsWith('XSRF-TOKEN='))
?.split('=')[1] || '',
),
Accept: 'application/json',
},
body: JSON.stringify({
balance_date: date,
balance: balance,
}),
});
if (!response.ok) {
const data = await response.json();
throw new Error(data.message || 'Failed to update balance');
}
handleOpenChange(false);
onSuccess?.();
} catch (err) {
setError(
err instanceof Error ? err.message : 'Failed to update balance',
);
} finally {
setIsSubmitting(false);
}
}
return (
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogContent hasKeyboard className="sm:max-w-[400px]">
<DialogHeader>
<DialogTitle>Update balance</DialogTitle>
<DialogDescription>
Set the balance for this account on a specific date.
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="balance-amount">Balance</Label>
<AmountInput
id="balance-amount"
className="mt-1"
value={balance}
onChange={setBalance}
currencyCode={account.currency_code}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="balance-date">Date</Label>
<Input
id="balance-date"
type="date"
className="mt-1"
value={date}
onChange={(e) => setDate(e.target.value)}
required
/>
</div>
{error && (
<p className="text-sm text-destructive">{error}</p>
)}
<DialogFooter>
<Button
type="button"
variant="outline"
onClick={() => handleOpenChange(false)}
disabled={isSubmitting}
>
Cancel
</Button>
<Button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Saving...' : 'Save'}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
);
}