feat(bi): add interactive sliders to cost configuration modal
Enhanced cost configuration UX with dual-control interface: - Range sliders for quick visual adjustments - Number inputs for precise values - Real-time sync between slider and input - Live value display with formatting ($X,XXX) - Custom purple styling matching Tractatus theme Slider ranges by severity: - CRITICAL: $1k-$250k (step: $1k) - HIGH: $500-$50k (step: $500) - MEDIUM: $100-$10k (step: $100) - LOW: $50-$5k (step: $50) Users can drag sliders OR type exact amounts for maximum flexibility. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
a421c93c51
commit
70f02ec932
1 changed files with 88 additions and 6 deletions
|
|
@ -950,6 +950,14 @@ async function showCostConfigModal() {
|
|||
return;
|
||||
}
|
||||
|
||||
// Slider ranges by severity
|
||||
const sliderConfig = {
|
||||
CRITICAL: { min: 1000, max: 250000, step: 1000 },
|
||||
HIGH: { min: 500, max: 50000, step: 500 },
|
||||
MEDIUM: { min: 100, max: 10000, step: 100 },
|
||||
LOW: { min: 50, max: 5000, step: 50 }
|
||||
};
|
||||
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50';
|
||||
modal.innerHTML = `
|
||||
|
|
@ -960,14 +968,34 @@ async function showCostConfigModal() {
|
|||
</div>
|
||||
|
||||
<div class="p-6 space-y-4">
|
||||
${['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'].map(severity => `
|
||||
${['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'].map(severity => {
|
||||
const config = sliderConfig[severity];
|
||||
const amount = Math.min(Math.max(costFactors[severity].amount, config.min), config.max);
|
||||
return `
|
||||
<div class="border border-gray-200 rounded-lg p-4">
|
||||
<label class="block font-semibold text-gray-900 mb-2">${severity}</label>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
<label class="block font-semibold text-gray-900 mb-3">${severity}</label>
|
||||
|
||||
<div class="mb-4">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<label class="block text-sm text-gray-600">Amount</label>
|
||||
<span class="text-sm font-medium text-gray-900">$<span id="cost-${severity}-display">${amount.toLocaleString()}</span></span>
|
||||
</div>
|
||||
<input type="range" id="cost-${severity}-slider"
|
||||
min="${config.min}" max="${config.max}" step="${config.step}"
|
||||
value="${amount}"
|
||||
class="w-full h-2 bg-purple-200 rounded-lg appearance-none cursor-pointer slider">
|
||||
<div class="flex justify-between text-xs text-gray-500 mt-1">
|
||||
<span>$${(config.min / 1000).toFixed(0)}k</span>
|
||||
<span>$${(config.max / 1000).toFixed(0)}k</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 mb-3">
|
||||
<div>
|
||||
<label class="block text-sm text-gray-600 mb-1">Amount ($)</label>
|
||||
<label class="block text-sm text-gray-600 mb-1">Exact Amount ($)</label>
|
||||
<input type="number" id="cost-${severity}-amount"
|
||||
value="${costFactors[severity].amount}"
|
||||
min="0" step="${config.step}"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-purple-500 focus:border-transparent">
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -977,13 +1005,14 @@ async function showCostConfigModal() {
|
|||
class="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-purple-500 focus:border-transparent">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
|
||||
<div>
|
||||
<label class="block text-sm text-gray-600 mb-1">Rationale</label>
|
||||
<textarea id="cost-${severity}-rationale" rows="2"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-purple-500 focus:border-transparent">${costFactors[severity].rationale}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
`}).join('')}
|
||||
|
||||
<div class="bg-amber-50 border border-amber-200 rounded-lg p-3 text-sm text-amber-800">
|
||||
<strong>Note:</strong> These values are illustrative placeholders for research purposes.
|
||||
|
|
@ -1000,10 +1029,63 @@ async function showCostConfigModal() {
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* Custom slider styling */
|
||||
.slider::-webkit-slider-thumb {
|
||||
appearance: none;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
background: #9333ea;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
.slider::-webkit-slider-thumb:hover {
|
||||
background: #7e22ce;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
.slider::-moz-range-thumb {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
background: #9333ea;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
.slider::-moz-range-thumb:hover {
|
||||
background: #7e22ce;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
|
||||
document.body.appendChild(modal);
|
||||
|
||||
// Sync sliders with number inputs
|
||||
['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'].forEach(severity => {
|
||||
const slider = modal.querySelector(`#cost-${severity}-slider`);
|
||||
const input = modal.querySelector(`#cost-${severity}-amount`);
|
||||
const display = modal.querySelector(`#cost-${severity}-display`);
|
||||
|
||||
// Slider changes update input and display
|
||||
slider.addEventListener('input', (e) => {
|
||||
const value = parseFloat(e.target.value);
|
||||
input.value = value;
|
||||
display.textContent = value.toLocaleString();
|
||||
});
|
||||
|
||||
// Input changes update slider and display
|
||||
input.addEventListener('input', (e) => {
|
||||
const value = parseFloat(e.target.value) || 0;
|
||||
const config = sliderConfig[severity];
|
||||
const clampedValue = Math.min(Math.max(value, config.min), config.max);
|
||||
slider.value = clampedValue;
|
||||
display.textContent = value.toLocaleString();
|
||||
});
|
||||
});
|
||||
|
||||
// Close on cancel
|
||||
modal.querySelector('#cancel-config-btn').addEventListener('click', () => {
|
||||
document.body.removeChild(modal);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue