'use client'; import { useEffect, useState } from 'react'; interface Account { id: string; name: string; type: 'checking' | 'savings' | 'credit_card' | 'mortgage' | 'investment' | 'loan'; balance: number; institution?: string; accountNumber?: string; color: string; isActive: boolean; defaultPaycheckAmount?: number; } interface Envelope { id: string; name: string; allocated: number; spent: number; color: string; accountId: string; } interface Transaction { id: string; envelopeId?: string; amount: number; description: string; date: Date; accountId: string; } type DistributionMethod = 'proportional' | 'equal' | 'custom'; type AllocationMap = Record; interface PaycheckTemplate { amount: number; selectedAccountId: string; description: string; distributionMethod: DistributionMethod; customAllocations: AllocationMap; } interface GetPaidProps { accounts: Account[]; envelopes: Envelope[]; onIncomeAdded: (transactions: Transaction[]) => void; onAccountUpdate?: (account: Account) => void; autoOpen?: boolean; } export default function GetPaid({ accounts, envelopes, onIncomeAdded, onAccountUpdate, autoOpen = false, }: GetPaidProps) { const [isOpen, setIsOpen] = useState(autoOpen); const [showPreview, setShowPreview] = useState(false); const [amount, setAmount] = useState(''); const [selectedAccountId, setSelectedAccountId] = useState(''); const [description, setDescription] = useState('Paycheck'); const [distributionMethod, setDistributionMethod] = useState('proportional'); const [customAllocations, setCustomAllocations] = useState({}); const [previewAllocations, setPreviewAllocations] = useState({}); const [showDefaultAmountField, setShowDefaultAmountField] = useState(false); const [defaultAmountInput, setDefaultAmountInput] = useState(''); const [saveTemplate, setSaveTemplate] = useState(false); const [savedTemplate, setSavedTemplate] = useState(null); useEffect(() => { if (typeof window === 'undefined') return; const template = localStorage.getItem('paycheckTemplate'); if (!template) return; try { const parsed = JSON.parse(template) as PaycheckTemplate; setSavedTemplate(parsed); } catch (error) { console.error('Error loading saved template', error); } }, []); const accountEnvelopes = selectedAccountId ? envelopes.filter((env) => env.accountId === selectedAccountId) : []; const totalAllocated = accountEnvelopes.reduce((sum, env) => sum + env.allocated, 0); const calculateDistribution = (): AllocationMap => { const paycheckAmount = parseFloat(amount); if (!paycheckAmount || accountEnvelopes.length === 0) return {}; if (distributionMethod === 'custom') { const sanitized: AllocationMap = {}; accountEnvelopes.forEach((env) => { sanitized[env.id] = Math.max(0, customAllocations[env.id] || 0); }); return sanitized; } const distribution: AllocationMap = {}; if (distributionMethod === 'equal') { const equalAmount = paycheckAmount / accountEnvelopes.length; accountEnvelopes.forEach((env) => { distribution[env.id] = equalAmount; }); return distribution; } accountEnvelopes.forEach((env) => { const proportion = totalAllocated > 0 ? env.allocated / totalAllocated : 0; distribution[env.id] = paycheckAmount * proportion; }); return distribution; }; const applySavedTemplate = () => { if (!savedTemplate) return; setAmount(savedTemplate.amount?.toString() || ''); setSelectedAccountId(savedTemplate.selectedAccountId || ''); setDescription(savedTemplate.description || 'Paycheck'); setDistributionMethod(savedTemplate.distributionMethod || 'proportional'); setCustomAllocations(savedTemplate.customAllocations || {}); }; const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); const paycheckAmount = parseFloat(amount); if (!paycheckAmount || !selectedAccountId) { alert('Please enter a valid amount and select an account'); return; } if (paycheckAmount <= 0 || paycheckAmount > 999999999) { alert('Please enter a valid paycheck amount ($0 - $999,999,999)'); return; } const distribution = calculateDistribution(); setPreviewAllocations(distribution); setShowPreview(true); }; const handleConfirmPaycheck = () => { const paycheckAmount = parseFloat(amount); if (!paycheckAmount || !selectedAccountId) return; const transactions: Transaction[] = []; transactions.push({ id: crypto.randomUUID(), amount: paycheckAmount, description: description || 'Paycheck', date: new Date(), accountId: selectedAccountId, }); Object.entries(previewAllocations).forEach(([envelopeId, allocatedAmount]) => { if (allocatedAmount > 0) { transactions.push({ id: crypto.randomUUID(), envelopeId, amount: allocatedAmount, description: `${description} → ${envelopes.find((env) => env.id === envelopeId)?.name || 'Envelope'}`, date: new Date(), accountId: selectedAccountId, }); } }); if (saveTemplate && typeof window !== 'undefined') { const template: PaycheckTemplate = { amount: paycheckAmount, selectedAccountId, description, distributionMethod, customAllocations: previewAllocations, }; localStorage.setItem('paycheckTemplate', JSON.stringify(template)); setSavedTemplate(template); } onIncomeAdded(transactions); setIsOpen(false); setShowPreview(false); setAmount(''); setSelectedAccountId(''); setDescription('Paycheck'); setCustomAllocations({}); setPreviewAllocations({}); setShowDefaultAmountField(false); setDefaultAmountInput(''); setSaveTemplate(false); }; const handleEditAllocation = (envelopeId: string, newAmount: string) => { const numAmount = parseFloat(newAmount); if (Number.isNaN(numAmount) || numAmount < 0) return; setPreviewAllocations((prev) => ({ ...prev, [envelopeId]: numAmount, })); }; const handleSetDefaultAmount = () => { const defaultAmount = parseFloat(defaultAmountInput); if (!defaultAmount || defaultAmount <= 0) { alert('Please enter a valid default amount'); return; } const selectedAccount = accounts.find((acc) => acc.id === selectedAccountId); if (!selectedAccount) return; const updatedAccount = { ...selectedAccount, defaultPaycheckAmount: defaultAmount }; if (onAccountUpdate) { onAccountUpdate(updatedAccount); alert(`Default paycheck amount set to $${defaultAmount.toFixed(2)} for this account`); setShowDefaultAmountField(false); setDefaultAmountInput(''); } else { alert('Unable to save default amount - account update not available'); } }; const handleUseDefaultAmount = () => { const selectedAccount = accounts.find((acc) => acc.id === selectedAccountId); if (selectedAccount?.defaultPaycheckAmount) { setAmount(selectedAccount.defaultPaycheckAmount.toString()); } }; const distribution = calculateDistribution(); const numericAmount = parseFloat(amount || '0') || 0; const totalDistributed = Object.values(distribution).reduce((sum, value) => sum + value, 0); const remaining = numericAmount - totalDistributed; return ( <> {!autoOpen && ( )} {isOpen && (

Record Paycheck

{savedTemplate && (

Saved Template Available

Amount: ${savedTemplate.amount?.toFixed(2)} • {savedTemplate.description}

)}
$ setAmount(event.target.value)} className="w-full pl-8 pr-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white text-gray-900 placeholder-gray-400" placeholder="0.00" required />
{selectedAccountId && accounts.find((acc) => acc.id === selectedAccountId)?.defaultPaycheckAmount && ( )} {selectedAccountId && ( )} {showDefaultAmountField && (
$ setDefaultAmountInput(event.target.value)} className="w-full pl-8 pr-3 py-1 text-sm border border-gray-300 rounded bg-white text-gray-900 placeholder-gray-400" placeholder="0.00" />
)}
setDescription(event.target.value)} className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white text-gray-900 placeholder-gray-400" placeholder="Paycheck description" />
setSaveTemplate(event.target.checked)} className="mr-2" />
{selectedAccountId && accountEnvelopes.length > 0 && ( <>

Income Distribution Preview

{accountEnvelopes.map((envelope) => { const allocatedAmount = distribution[envelope.id] || 0; const percentage = numericAmount > 0 ? (allocatedAmount / numericAmount) * 100 : 0; return (
{envelope.name}
{distributionMethod === 'custom' ? ( setCustomAllocations((prev) => ({ ...prev, [envelope.id]: parseFloat(event.target.value) || 0, })) } className="w-20 px-2 py-1 text-sm border border-gray-300 rounded bg-white text-gray-900 placeholder-gray-400" placeholder="0.00" /> ) : ( ${allocatedAmount.toFixed(2)} )} ({percentage.toFixed(1)}%)
); })}
Total Distributed: ${totalDistributed.toFixed(2)}
{Math.abs(remaining) >= 0.01 && (
Remaining: = 0 ? 'text-orange-600' : 'text-red-600'}`}> ${remaining.toFixed(2)}
)}
)} {selectedAccountId && accountEnvelopes.length === 0 && (
No envelopes are linked to this account. Create envelopes for this account to distribute income here.
)}
)} {showPreview && (

Confirm Paycheck Allocation

Paycheck Amount: ${numericAmount.toFixed(2)}
Deposit to: {accounts.find((acc) => acc.id === selectedAccountId)?.name}
{description && (
Description: {description}
)}

Envelope Allocations

Review and adjust how this paycheck will be distributed:

{accountEnvelopes.map((envelope) => { const allocatedAmount = previewAllocations[envelope.id] || 0; const percentage = numericAmount > 0 ? (allocatedAmount / numericAmount) * 100 : 0; return (
{envelope.name}
{percentage.toFixed(1)}% of paycheck
Amount:
$ handleEditAllocation(envelope.id, event.target.value)} className="w-full pl-8 pr-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white text-gray-900" />
); })}
Total Allocated: ${Object.values(previewAllocations).reduce((sum, value) => sum + value, 0).toFixed(2)}
{(() => { const totalAllocatedPreview = Object.values(previewAllocations).reduce( (sum, value) => sum + value, 0, ); const remainingAmount = numericAmount - totalAllocatedPreview; if (Math.abs(remainingAmount) >= 0.01) { return (
Unallocated: = 0 ? 'text-orange-600' : 'text-red-600'}`} > ${remainingAmount.toFixed(2)} {remainingAmount < 0 && '(over allocated!)'}
); } return null; })()}
)} ); }