16 - Update frontend for cents-based API

This commit is contained in:
myrmidex 2026-03-21 17:48:58 +01:00
parent d6f60ab987
commit cf89ee7cd2
2 changed files with 32 additions and 16 deletions

View file

@ -54,7 +54,7 @@ export default function IncomeDistributionPreview({ scenarioId }: IncomeDistribu
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-CSRF-TOKEN': csrfToken(), 'X-CSRF-TOKEN': csrfToken(),
}, },
body: JSON.stringify({ amount: parsed }), body: JSON.stringify({ amount: Math.round(parsed * 100) }),
}); });
if (!response.ok) { if (!response.ok) {
@ -126,11 +126,11 @@ export default function IncomeDistributionPreview({ scenarioId }: IncomeDistribu
</div> </div>
<div className="text-right"> <div className="text-right">
<span className="font-semibold text-gray-900"> <span className="font-semibold text-gray-900">
${allocation.allocated_amount.toFixed(2)} ${(allocation.allocated_amount / 100).toFixed(2)}
</span> </span>
{allocation.remaining_capacity !== null && ( {allocation.remaining_capacity !== null && (
<span className="ml-2 text-sm text-gray-500"> <span className="ml-2 text-sm text-gray-500">
(${allocation.remaining_capacity.toFixed(2)} remaining) (${(allocation.remaining_capacity / 100).toFixed(2)} remaining)
</span> </span>
)} )}
</div> </div>
@ -143,13 +143,13 @@ export default function IncomeDistributionPreview({ scenarioId }: IncomeDistribu
<div className="flex justify-between rounded-md bg-gray-50 px-4 py-3 text-sm font-medium"> <div className="flex justify-between rounded-md bg-gray-50 px-4 py-3 text-sm font-medium">
<span className="text-gray-700">Total Allocated</span> <span className="text-gray-700">Total Allocated</span>
<span className="text-gray-900">${preview.total_allocated.toFixed(2)}</span> <span className="text-gray-900">${(preview.total_allocated / 100).toFixed(2)}</span>
</div> </div>
{preview.unallocated > 0 && ( {preview.unallocated > 0 && (
<div className="flex justify-between rounded-md bg-amber-50 px-4 py-3 text-sm font-medium"> <div className="flex justify-between rounded-md bg-amber-50 px-4 py-3 text-sm font-medium">
<span className="text-amber-700">Unallocated</span> <span className="text-amber-700">Unallocated</span>
<span className="text-amber-900">${preview.unallocated.toFixed(2)}</span> <span className="text-amber-900">${(preview.unallocated / 100).toFixed(2)}</span>
</div> </div>
)} )}
</div> </div>

View file

@ -24,11 +24,11 @@ interface Bucket {
allocation_value: number | null; allocation_value: number | null;
allocation_type_label: string; allocation_type_label: string;
buffer_multiplier: number; buffer_multiplier: number;
effective_capacity: number; effective_capacity: number | null;
starting_amount: number; starting_amount: number;
current_balance: number; current_balance: number;
has_available_space: boolean; has_available_space: boolean;
available_space: number; available_space: number | null;
} }
interface Stream { interface Stream {
@ -76,11 +76,23 @@ const bucketTypeOptions = [
{ value: 'want', label: 'Want' }, { value: 'want', label: 'Want' },
]; ];
/** Convert cents to dollars for display */
const centsToDollars = (cents: number): number => cents / 100;
/** Convert dollars to cents for storage */
const dollarsToCents = (dollars: number): number => Math.round(dollars * 100);
/** Convert basis points to percent for display */
const basisPointsToPercent = (bp: number): number => bp / 100;
/** Convert percent to basis points for storage */
const percentToBasisPoints = (pct: number): number => Math.round(pct * 100);
const formatAllocationValue = (bucket: Bucket): string => { const formatAllocationValue = (bucket: Bucket): string => {
if (bucket.allocation_type === 'unlimited') return 'All remaining'; if (bucket.allocation_type === 'unlimited') return 'All remaining';
if (bucket.allocation_value === null) return '--'; if (bucket.allocation_value === null) return '--';
if (bucket.allocation_type === 'percentage') return `${Number(bucket.allocation_value).toFixed(2)}%`; if (bucket.allocation_type === 'percentage') return `${basisPointsToPercent(bucket.allocation_value).toFixed(2)}%`;
return `$${Number(bucket.allocation_value).toFixed(2)}`; return `$${centsToDollars(bucket.allocation_value).toFixed(2)}`;
}; };
const csrfToken = () => const csrfToken = () =>
@ -158,7 +170,11 @@ export default function Show({ scenario, buckets, streams = { data: [] }, stream
name: formData.name, name: formData.name,
type: formData.type, type: formData.type,
allocation_type: formData.allocation_type, allocation_type: formData.allocation_type,
allocation_value: formData.allocation_value ? parseFloat(formData.allocation_value) : null, allocation_value: formData.allocation_value
? (formData.allocation_type === 'percentage'
? percentToBasisPoints(parseFloat(formData.allocation_value))
: dollarsToCents(parseFloat(formData.allocation_value)))
: null,
buffer_multiplier: formData.allocation_type === 'fixed_limit' ? parseFloat(formData.buffer_multiplier) || 0 : 0, buffer_multiplier: formData.allocation_type === 'fixed_limit' ? parseFloat(formData.buffer_multiplier) || 0 : 0,
}), }),
}); });
@ -306,11 +322,11 @@ export default function Show({ scenario, buckets, streams = { data: [] }, stream
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-sm text-gray-600">Current Filling</span> <span className="text-sm text-gray-600">Current Filling</span>
<InlineEditInput <InlineEditInput
value={bucket.starting_amount} value={centsToDollars(bucket.starting_amount)}
onSave={(val) => patchBucket(bucket.id, { starting_amount: val })} onSave={(val) => patchBucket(bucket.id, { starting_amount: dollarsToCents(val) })}
formatDisplay={(v) => `$${v.toFixed(2)}`} formatDisplay={(v) => `$${v.toFixed(2)}`}
min={0} min={0}
step="1" step="0.01"
className="text-lg font-semibold text-gray-900" className="text-lg font-semibold text-gray-900"
/> />
</div> </div>
@ -329,8 +345,8 @@ export default function Show({ scenario, buckets, streams = { data: [] }, stream
min={0} min={0}
step="0.01" step="0.01"
/> />
{bucket.buffer_multiplier > 0 && ( {bucket.buffer_multiplier > 0 && bucket.effective_capacity !== null && (
<> = ${bucket.effective_capacity.toFixed(2)} effective</> <> = ${centsToDollars(bucket.effective_capacity).toFixed(2)} effective</>
)} )}
) )
</span> </span>
@ -342,7 +358,7 @@ export default function Show({ scenario, buckets, streams = { data: [] }, stream
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
<span>Progress</span> <span>Progress</span>
<span> <span>
${bucket.current_balance.toFixed(2)} / ${bucket.effective_capacity.toFixed(2)} ${centsToDollars(bucket.current_balance).toFixed(2)} / ${bucket.effective_capacity !== null ? centsToDollars(bucket.effective_capacity).toFixed(2) : '∞'}
</span> </span>
</div> </div>
<div className="mt-1 h-2 bg-gray-200 rounded-full"> <div className="mt-1 h-2 bg-gray-200 rounded-full">