16 - Update frontend for cents-based API
This commit is contained in:
parent
d6f60ab987
commit
cf89ee7cd2
2 changed files with 32 additions and 16 deletions
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue