5 - Add buffer multiplier UI with preset and custom options
This commit is contained in:
parent
ed6be6249e
commit
8f6de4aace
1 changed files with 70 additions and 8 deletions
|
|
@ -18,7 +18,8 @@ interface Bucket {
|
||||||
allocation_type: string;
|
allocation_type: string;
|
||||||
allocation_value: number | null;
|
allocation_value: number | null;
|
||||||
allocation_type_label: string;
|
allocation_type_label: string;
|
||||||
formatted_allocation_value: string;
|
buffer_multiplier: number;
|
||||||
|
effective_capacity: number;
|
||||||
current_balance: number;
|
current_balance: number;
|
||||||
has_available_space: boolean;
|
has_available_space: boolean;
|
||||||
available_space: number;
|
available_space: number;
|
||||||
|
|
@ -64,11 +65,22 @@ const bucketTypeBorderColor = {
|
||||||
overflow: 'border-amber-500',
|
overflow: 'border-amber-500',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
const BUFFER_PRESETS = ['0', '0.5', '1', '1.5', '2'] as const;
|
||||||
|
|
||||||
|
const formatAllocationValue = (bucket: Bucket): string => {
|
||||||
|
if (bucket.allocation_type === 'unlimited') return 'All remaining';
|
||||||
|
if (bucket.allocation_value === null) return '--';
|
||||||
|
if (bucket.allocation_type === 'percentage') return `${Number(bucket.allocation_value).toFixed(2)}%`;
|
||||||
|
return `$${Number(bucket.allocation_value).toFixed(2)}`;
|
||||||
|
};
|
||||||
|
|
||||||
const defaultFormData = {
|
const defaultFormData = {
|
||||||
name: '',
|
name: '',
|
||||||
type: 'need' as Bucket['type'],
|
type: 'need' as Bucket['type'],
|
||||||
allocation_type: 'fixed_limit',
|
allocation_type: 'fixed_limit',
|
||||||
allocation_value: ''
|
allocation_value: '',
|
||||||
|
buffer_multiplier: '0',
|
||||||
|
buffer_mode: 'preset' as 'preset' | 'custom',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Show({ scenario, buckets, streams = { data: [] }, streamStats }: Props) {
|
export default function Show({ scenario, buckets, streams = { data: [] }, streamStats }: Props) {
|
||||||
|
|
@ -79,11 +91,15 @@ export default function Show({ scenario, buckets, streams = { data: [] }, stream
|
||||||
|
|
||||||
const handleEdit = (bucket: Bucket) => {
|
const handleEdit = (bucket: Bucket) => {
|
||||||
setEditingBucket(bucket);
|
setEditingBucket(bucket);
|
||||||
|
const bufferStr = bucket.buffer_multiplier.toString();
|
||||||
|
const isPreset = (BUFFER_PRESETS as readonly string[]).includes(bufferStr);
|
||||||
setFormData({
|
setFormData({
|
||||||
name: bucket.name,
|
name: bucket.name,
|
||||||
type: bucket.type,
|
type: bucket.type,
|
||||||
allocation_type: bucket.allocation_type,
|
allocation_type: bucket.allocation_type,
|
||||||
allocation_value: bucket.allocation_value ? bucket.allocation_value.toString() : ''
|
allocation_value: bucket.allocation_value ? bucket.allocation_value.toString() : '',
|
||||||
|
buffer_multiplier: bufferStr,
|
||||||
|
buffer_mode: isPreset ? 'preset' : 'custom',
|
||||||
});
|
});
|
||||||
setIsModalOpen(true);
|
setIsModalOpen(true);
|
||||||
};
|
};
|
||||||
|
|
@ -133,6 +149,7 @@ export default function Show({ scenario, buckets, streams = { data: [] }, stream
|
||||||
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 ? parseFloat(formData.allocation_value) : null,
|
||||||
|
buffer_multiplier: formData.allocation_type === 'fixed_limit' ? parseFloat(formData.buffer_multiplier) || 0 : 0,
|
||||||
priority: editingBucket ? editingBucket.priority : undefined
|
priority: editingBucket ? editingBucket.priority : undefined
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
@ -174,6 +191,7 @@ export default function Show({ scenario, buckets, streams = { data: [] }, stream
|
||||||
type: bucket.type,
|
type: bucket.type,
|
||||||
allocation_type: bucket.allocation_type,
|
allocation_type: bucket.allocation_type,
|
||||||
allocation_value: bucket.allocation_value,
|
allocation_value: bucket.allocation_value,
|
||||||
|
buffer_multiplier: bucket.buffer_multiplier,
|
||||||
priority: newPriority
|
priority: newPriority
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
@ -283,8 +301,13 @@ export default function Show({ scenario, buckets, streams = { data: [] }, stream
|
||||||
|
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<span className="text-sm text-gray-600">
|
<span className="text-sm text-gray-600">
|
||||||
Allocation: {bucket.formatted_allocation_value}
|
Allocation: {formatAllocationValue(bucket)}
|
||||||
</span>
|
</span>
|
||||||
|
{bucket.allocation_type === 'fixed_limit' && bucket.buffer_multiplier > 0 && (
|
||||||
|
<span className="text-sm text-gray-500 ml-2">
|
||||||
|
({bucket.buffer_multiplier}x buffer = ${bucket.effective_capacity.toFixed(2)} effective)
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{bucket.allocation_type === 'fixed_limit' && (
|
{bucket.allocation_type === 'fixed_limit' && (
|
||||||
|
|
@ -292,14 +315,14 @@ 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)} / ${Number(bucket.allocation_value)?.toFixed(2)}
|
${bucket.current_balance.toFixed(2)} / ${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">
|
||||||
<div
|
<div
|
||||||
className="h-2 bg-blue-600 rounded-full transition-all"
|
className="h-2 bg-blue-600 rounded-full transition-all"
|
||||||
style={{
|
style={{
|
||||||
width: `${bucket.allocation_value ? Math.min((bucket.current_balance / Number(bucket.allocation_value)) * 100, 100) : 0}%`
|
width: `${bucket.effective_capacity ? Math.min((bucket.current_balance / bucket.effective_capacity) * 100, 100) : 0}%`
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -564,6 +587,45 @@ export default function Show({ scenario, buckets, streams = { data: [] }, stream
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{formData.allocation_type === 'fixed_limit' && (
|
||||||
|
<div>
|
||||||
|
<label htmlFor="buffer_multiplier" className="block text-sm font-medium text-gray-700">
|
||||||
|
Buffer
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="buffer_preset"
|
||||||
|
value={formData.buffer_mode === 'custom' ? 'custom' : formData.buffer_multiplier}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (e.target.value === 'custom') {
|
||||||
|
setFormData({ ...formData, buffer_mode: 'custom' });
|
||||||
|
} else {
|
||||||
|
setFormData({ ...formData, buffer_multiplier: e.target.value, buffer_mode: 'preset' });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 text-gray-900"
|
||||||
|
>
|
||||||
|
<option value="0">None</option>
|
||||||
|
<option value="0.5">0.5x (50% extra)</option>
|
||||||
|
<option value="1">1x (100% extra)</option>
|
||||||
|
<option value="1.5">1.5x (150% extra)</option>
|
||||||
|
<option value="2">2x (200% extra)</option>
|
||||||
|
<option value="custom">Custom</option>
|
||||||
|
</select>
|
||||||
|
{formData.buffer_mode === 'custom' && (
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="buffer_multiplier"
|
||||||
|
value={formData.buffer_multiplier}
|
||||||
|
onChange={(e) => setFormData({ ...formData, buffer_multiplier: e.target.value })}
|
||||||
|
className="mt-2 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 text-gray-900"
|
||||||
|
placeholder="e.g., 0.75"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="flex gap-3 pt-4">
|
<div className="flex gap-3 pt-4">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue