import { useState } from 'react'; import { router } from '@inertiajs/react'; import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; interface Allocation { bucket_id: string; bucket_name: string; bucket_type: 'need' | 'want' | 'overflow'; allocated_amount: number; remaining_capacity: number | null; } interface PreviewResult { allocations: Allocation[]; total_allocated: number; unallocated: number; } interface IncomeDistributionPreviewProps { scenarioId: string; } const bucketTypeColor = { need: 'bg-blue-100 text-blue-800', want: 'bg-green-100 text-green-800', overflow: 'bg-amber-100 text-amber-800', } as const; const csrfToken = () => document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || ''; export default function IncomeDistributionPreview({ scenarioId }: IncomeDistributionPreviewProps) { const [amount, setAmount] = useState(''); const [preview, setPreview] = useState(null); const [isLoading, setIsLoading] = useState(false); const [isApplying, setIsApplying] = useState(false); const [error, setError] = useState(null); const [applied, setApplied] = useState(false); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setIsLoading(true); setError(null); setApplied(false); const parsed = parseFloat(amount); if (isNaN(parsed) || parsed <= 0) { setError('Please enter a valid amount'); setIsLoading(false); return; } try { const response = await fetch(`/scenarios/${scenarioId}/projections/preview`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken(), }, body: JSON.stringify({ amount: Math.round(parsed * 100) }), }); if (!response.ok) { let message = 'Failed to preview allocation'; try { const data = await response.json(); message = data.message || message; } catch { /* non-JSON error body */ } setError(message); setPreview(null); return; } const data: PreviewResult = await response.json(); setPreview(data); } catch { setError('Failed to connect to server'); setPreview(null); } finally { setIsLoading(false); } }; const handleApply = async () => { const parsed = parseFloat(amount); if (isNaN(parsed) || parsed <= 0) return; setIsApplying(true); setError(null); try { const response = await fetch(`/scenarios/${scenarioId}/projections/apply`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken(), }, body: JSON.stringify({ amount: Math.round(parsed * 100) }), }); if (!response.ok) { let message = 'Failed to save distribution'; try { const data = await response.json(); message = data.message || message; } catch { /* non-JSON error body */ } setError(message); return; } setPreview(null); setAmount(''); setApplied(true); router.reload({ only: ['buckets'] }); } catch { setError('Failed to connect to server'); } finally { setIsApplying(false); } }; return ( Income Distribution Preview
setAmount(e.target.value)} className="block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 text-gray-900" placeholder="e.g., 3000" step="0.01" min="0.01" required />
{error && (
{error}
)} {applied && (
Distribution saved successfully. Bucket balances have been updated.
)} {preview && (
{preview.allocations.length > 0 ? (
{preview.allocations.map((allocation) => (
{allocation.bucket_type} {allocation.bucket_name}
${(allocation.allocated_amount / 100).toFixed(2)} {allocation.remaining_capacity !== null && ( (${(allocation.remaining_capacity / 100).toFixed(2)} remaining) )}
))}
) : (

No buckets to allocate to.

)}
Total Allocated ${(preview.total_allocated / 100).toFixed(2)}
{preview.unallocated > 0 && (
Unallocated ${(preview.unallocated / 100).toFixed(2)}
)} {preview.allocations.length > 0 && ( )}
)}
); }