import { Head, Link, router } from '@inertiajs/react'; import { useState } from 'react'; interface Scenario { id: string; name: string; created_at: string; updated_at: string; } interface Bucket { id: string; name: string; type: 'need' | 'want' | 'overflow'; type_label: string; priority: number; sort_order: number; allocation_type: string; allocation_value: number | null; allocation_type_label: string; formatted_allocation_value: string; current_balance: number; has_available_space: boolean; available_space: number; } interface Stream { id: string; name: string; type: 'income' | 'expense'; type_label: string; amount: number; frequency: string; frequency_label: string; start_date: string; end_date: string | null; bucket_id: string | null; bucket_name: string | null; description: string | null; is_active: boolean; monthly_equivalent: number; } interface StreamStats { total_streams: number; active_streams: number; income_streams: number; expense_streams: number; monthly_income: number; monthly_expenses: number; monthly_net: number; } interface Props { scenario: Scenario; buckets: { data: Bucket[] }; streams: { data: Stream[] }; streamStats?: StreamStats; } const bucketTypeBorderColor = { need: 'border-blue-500', want: 'border-green-500', overflow: 'border-amber-500', } as const; const defaultFormData = { name: '', type: 'need' as Bucket['type'], allocation_type: 'fixed_limit', allocation_value: '' }; export default function Show({ scenario, buckets, streams = { data: [] }, streamStats }: Props) { const [isModalOpen, setIsModalOpen] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); const [editingBucket, setEditingBucket] = useState(null); const [formData, setFormData] = useState({ ...defaultFormData }); const handleEdit = (bucket: Bucket) => { setEditingBucket(bucket); setFormData({ name: bucket.name, type: bucket.type, allocation_type: bucket.allocation_type, allocation_value: bucket.allocation_value ? bucket.allocation_value.toString() : '' }); setIsModalOpen(true); }; const handleDelete = async (bucket: Bucket) => { if (!confirm(`Are you sure you want to delete "${bucket.name}"?`)) { return; } try { const response = await fetch(`/buckets/${bucket.id}`, { method: 'DELETE', headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '', }, }); if (response.ok) { router.reload({ only: ['buckets'] }); } else { console.error('Failed to delete bucket'); } } catch (error) { console.error('Error deleting bucket:', error); } }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setIsSubmitting(true); const url = editingBucket ? `/buckets/${editingBucket.id}` : `/scenarios/${scenario.id}/buckets`; const method = editingBucket ? 'PATCH' : 'POST'; try { const response = await fetch(url, { method: method, headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '', }, body: JSON.stringify({ name: formData.name, type: formData.type, allocation_type: formData.allocation_type, allocation_value: formData.allocation_value ? parseFloat(formData.allocation_value) : null, priority: editingBucket ? editingBucket.priority : undefined }), }); if (response.ok) { setIsModalOpen(false); setFormData({ ...defaultFormData }); setEditingBucket(null); router.reload({ only: ['buckets'] }); } else { const errorData = await response.json(); console.error(`Failed to ${editingBucket ? 'update' : 'create'} bucket:`, errorData); } } catch (error) { console.error(`Error ${editingBucket ? 'updating' : 'creating'} bucket:`, error); } finally { setIsSubmitting(false); } }; const handlePriorityChange = async (bucketId: string, direction: 'up' | 'down') => { const bucket = buckets.data.find(b => b.id === bucketId); if (!bucket) return; const newPriority = direction === 'up' ? bucket.priority - 1 : bucket.priority + 1; // Don't allow moving beyond bounds if (newPriority < 1 || newPriority > buckets.data.length) return; try { const response = await fetch(`/buckets/${bucketId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '', }, body: JSON.stringify({ name: bucket.name, type: bucket.type, allocation_type: bucket.allocation_type, allocation_value: bucket.allocation_value, priority: newPriority }), }); if (response.ok) { router.reload({ only: ['buckets'] }); } else { console.error('Failed to update bucket priority'); } } catch (error) { console.error('Error updating bucket priority:', error); } }; return ( <>
{/* Header */}
Back to Scenarios

{scenario.name}

Water flows through the pipeline into prioritized buckets

{/* Bucket Dashboard */}

Buckets

{buckets.data.map((bucket) => (

{bucket.name}

Priority {bucket.priority} • {bucket.type_label} • {bucket.allocation_type_label}

#{bucket.priority}
Current Balance ${bucket.current_balance.toFixed(2)}
Allocation: {bucket.formatted_allocation_value}
{bucket.allocation_type === 'fixed_limit' && (
Progress ${bucket.current_balance.toFixed(2)} / ${Number(bucket.allocation_value)?.toFixed(2)}
)}
{bucket.type !== 'overflow' && ( )}
))}
{/* Streams Section */}

Income & Expense Streams

{/* Stream Statistics */} {streamStats && (
Monthly Income
${streamStats.monthly_income.toFixed(2)}
Monthly Expenses
${streamStats.monthly_expenses.toFixed(2)}
Net Cash Flow
= 0 ? 'text-green-600' : 'text-red-600'}`}> ${streamStats.monthly_net.toFixed(2)}
Active Streams
{streamStats.active_streams} / {streamStats.total_streams}
)} {streams.data.length === 0 ? (

No streams yet. Add income or expense streams to start tracking cash flow.

) : (
{streams.data.map((stream) => ( ))}
Name Type Amount Frequency Bucket Start Date Status Actions
{stream.name} {stream.type_label} ${stream.amount.toFixed(2)} {stream.frequency_label} {stream.bucket_name || '-'} {new Date(stream.start_date).toLocaleDateString()}
)}
{/* Placeholder for future features */}

Coming Next: Timeline & Projections

Calculate projections to see your money flow through these buckets over time.

{/* Add Bucket Modal */} {isModalOpen && (

{editingBucket ? 'Edit Bucket' : 'Add New Bucket'}

setFormData({ ...formData, name: e.target.value })} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 text-gray-900" placeholder="e.g., Travel Fund" required />
{/* Hide type select for overflow buckets (type is fixed) */} {!(editingBucket && editingBucket.type === 'overflow') && (
)} {/* Hide allocation type select for overflow (always unlimited) */} {formData.type !== 'overflow' && (
)} {formData.allocation_type !== 'unlimited' && (
setFormData({ ...formData, allocation_value: e.target.value })} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 text-gray-900" placeholder={formData.allocation_type === 'percentage' ? '25' : '1000'} step={formData.allocation_type === 'percentage' ? '0.01' : '0.01'} min={formData.allocation_type === 'percentage' ? '0.01' : '0'} max={formData.allocation_type === 'percentage' ? '100' : undefined} required />
)}
)} ); }