2 - Add distribute button and wire up preview/apply endpoints

This commit is contained in:
myrmidex 2026-03-22 15:15:56 +01:00
parent 7938677950
commit 5ac8d48727
2 changed files with 99 additions and 2 deletions

View file

@ -6,7 +6,7 @@ import InlineEditInput from '@/components/InlineEditInput';
import InlineEditSelect from '@/components/InlineEditSelect';
import SettingsPanel from '@/components/SettingsPanel';
import { csrfToken } from '@/lib/utils';
import { type Bucket, type Scenario } from '@/types';
import { type Bucket, type DistributionPreview, type Scenario } from '@/types';
interface Props {
scenario: Scenario;
@ -60,10 +60,75 @@ export default function Show({ scenario, buckets }: Props) {
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [formData, setFormData] = useState({ ...defaultFormData });
const [incomeAmount, setIncomeAmount] = useState('');
const [distribution, setDistribution] = useState<DistributionPreview | null>(null);
const [isDistributing, setIsDistributing] = useState(false);
const [isSaving, setIsSaving] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);
const incomeRef = useRef<HTMLDivElement>(null);
const bucketRefs = useRef<Map<string, HTMLElement>>(new Map());
const handleDistribute = async () => {
const dollars = parseFloat(incomeAmount);
if (!dollars || dollars <= 0) return;
setIsDistributing(true);
try {
const response = await fetch(`/scenarios/${scenario.id}/projections/preview`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': csrfToken(),
},
body: JSON.stringify({ amount: dollarsToCents(dollars) }),
});
if (response.ok) {
setDistribution(await response.json());
} else {
console.error('Distribution preview failed:', response.status, await response.text());
}
} catch (error) {
console.error('Error fetching distribution preview:', error);
} finally {
setIsDistributing(false);
}
};
const handleSaveDistribution = async () => {
const dollars = parseFloat(incomeAmount);
if (!dollars || dollars <= 0 || !distribution) return;
setIsSaving(true);
try {
const response = await fetch(`/scenarios/${scenario.id}/projections/apply`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': csrfToken(),
},
body: JSON.stringify({ amount: dollarsToCents(dollars) }),
});
if (response.ok) {
setDistribution(null);
setIncomeAmount('');
router.reload({ only: ['buckets'] });
} else {
console.error('Apply distribution failed:', response.status, await response.text());
}
} catch (error) {
console.error('Error applying distribution:', error);
} finally {
setIsSaving(false);
}
};
const handleIncomeChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setIncomeAmount(e.target.value);
setDistribution(null);
};
const openCreateModal = () => {
setFormData({ ...defaultFormData });
setIsCreateModalOpen(true);
@ -228,14 +293,32 @@ export default function Show({ scenario, buckets }: Props) {
INCOME
</h2>
</div>
<div className="text-center">
<div className="text-center space-y-4">
<input
type="number"
value={incomeAmount}
onChange={handleIncomeChange}
className="w-48 bg-black border-2 border-red-500/50 px-4 py-2 text-center font-digital text-2xl text-red-500 focus:border-red-500 focus:outline-none"
placeholder="0"
min="0"
step="0.01"
/>
<button
onClick={handleDistribute}
disabled={!incomeAmount || parseFloat(incomeAmount) <= 0 || isDistributing}
className="block mx-auto border-2 border-red-500 bg-black px-6 py-2 text-xs font-mono font-bold uppercase tracking-wider text-red-500 hover:bg-red-500 hover:text-black transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
>
{isDistributing ? 'CALCULATING...' : 'DISTRIBUTE'}
</button>
{distribution && (
<button
onClick={handleSaveDistribution}
disabled={isSaving}
className="block mx-auto border-2 border-red-500 bg-red-500 px-6 py-2 text-xs font-mono font-bold uppercase tracking-wider text-black hover:bg-red-400 transition-colors disabled:opacity-50"
>
{isSaving ? 'SAVING...' : 'SAVE'}
</button>
)}
</div>
</div>
</div>

View file

@ -50,6 +50,20 @@ export interface Scenario {
updated_at: string;
}
export interface AllocationPreview {
bucket_id: string;
bucket_name: string;
bucket_type: string;
allocated_amount: number;
remaining_capacity: number | null;
}
export interface DistributionPreview {
allocations: AllocationPreview[];
total_allocated: number;
unallocated: number;
}
export interface SharedData {
name: string;
quote: { message: string; author: string };