Batch Region Comparison UI for Interactive Map
Overview
Extend the interactive Leaflet.js map with a dedicated UI component that allows users to select multiple regions (by clicking/checkboxes) and display a detailed side-by-side comparison table. This closes the gap between the backend /api/v1/regions/compare endpoint and the frontend user experience.
Scope
Included:
- Toggle-able comparison panel (off by default, activated by user action)
- Select multiple regions via map markers or comparison list
- Display side-by-side service availability table
- Highlight common services across selected regions
- Export comparison as CSV
- Clear/reset selection easily
- Responsive design for mobile and desktop
Explicitly Excluded:
- Sharing comparison results via URL (phase 2)
- Saving comparisons to browser storage (phase 2)
- Advanced analytics or scoring of regions
- Custom comparison formulas or weighting
Technical Requirements
UI/UX Flow
- Activation: User clicks "Compare Regions" button in info panel or clicks marker with Ctrl/Cmd key held
- Selection: Regions are added to comparison list (max 5-10 regions to keep table readable)
- Display: Comparison panel slides in from right side showing:
- Selected region names (with remove button for each)
- Service availability matrix (rows = services, columns = regions)
- Color-coded cells: green (available), gray (unavailable), yellow (partial/tiered)
- Service type indicator (boolean vs. tiered)
- Actions:
- Add/remove regions
- Export to CSV
- Clear all selections
- Sort by service or region
Frontend Component Structure
<!-- layouts/index.html updates -->
<!-- Comparison Panel (hidden by default) -->
<div id="comparisonPanel" class="fixed right-0 top-0 h-full w-96 bg-white shadow-lg
transform translate-x-full transition-transform duration-300 z-40 overflow-y-auto">
<!-- Header -->
<div class="p-4 border-b flex justify-between items-center sticky top-0 bg-white">
<h2 class="text-lg font-bold">Region Comparison</h2>
<button id="closeComparisonBtn" class="text-gray-500 hover:text-gray-700">×</button>
</div>
<!-- Selected Regions List -->
<div id="selectedRegionsList" class="p-4 border-b">
<div class="space-y-2" id="regionTags"></div>
<button id="clearSelectionBtn" class="mt-2 w-full py-2 text-sm text-red-600
hover:bg-red-50 rounded">Clear All</button>
</div>
<!-- Comparison Table -->
<div id="comparisonTable" class="p-4">
<div class="overflow-x-auto">
<table class="w-full text-sm border-collapse" id="serviceMatrix"></table>
</div>
</div>
<!-- Export Button -->
<div class="p-4 border-t sticky bottom-0 bg-white">
<button id="exportCsvBtn" class="w-full py-2 bg-blue-600 text-white rounded
hover:bg-blue-700">Export as CSV</button>
</div>
</div>
<!-- "Compare Regions" button added to info panel -->
Implementation Plan
Step 1: Add Comparison State Management
// In layouts/index.html <script> section
const comparisonState = {
selectedRegions: [],
maxRegions: 6,
addRegion(region) {
if (this.selectedRegions.length >= this.maxRegions) {
alert(`Maximum ${this.maxRegions} regions allowed for comparison`)
return
}
if (!this.selectedRegions.find(r => r.id === region.id)) {
this.selectedRegions.push(region)
this.render()
}
},
removeRegion(regionId) {
this.selectedRegions = this.selectedRegions.filter(r => r.id !== regionId)
this.render()
},
clear() {
this.selectedRegions = []
this.render()
}
}
Step 2: API Integration Function
// Fetch comparison data from backend
async function fetchComparison(regionIds) {
const ids = regionIds.join(',')
const res = await fetch(`/api/v1/regions/compare?ids=${ids}`)
if (!res.ok) throw new Error(`API error: ${res.statusText}`)
return res.json()
}
Step 3: Comparison Panel Rendering
async function renderComparisonPanel() {
const panel = document.getElementById('comparisonPanel')
if (comparisonState.selectedRegions.length === 0) {
panel.classList.add('translate-x-full')
return
}
// Fetch comparison data
const regionIds = comparisonState.selectedRegions.map(r => r.id)
const comparisonData = await fetchComparison(regionIds)
// Render selected regions tags
const tagContainer = document.getElementById('regionTags')
tagContainer.innerHTML = comparisonState.selectedRegions.map(r => `
<div class="flex justify-between items-center bg-gray-100 p-2 rounded">
<span class="font-medium">${r.name}</span>
<button class="text-red-500 hover:text-red-700"
onclick="comparisonState.removeRegion('${r.id}')">✕</button>
</div>
`).join('')
// Render comparison table
const table = document.getElementById('serviceMatrix')
table.innerHTML = buildComparisonMatrix(comparisonData)
// Show panel
panel.classList.remove('translate-x-full')
}
function buildComparisonMatrix(comparisonData) {
// Build HTML table with service rows and region columns
// Color-code cells based on availability
let html = '<thead><tr><th>Service</th>'
comparisonData.regions.forEach(region => {
html += `<th>${region.name}</th>`
})
html += '</tr></thead><tbody>'
comparisonData.services.forEach(service => {
html += `<tr><td class="font-medium">${service.name}</td>`
service.regionStatus.forEach(status => {
const bgColor = status.available ? 'bg-green-100' : 'bg-gray-100'
const text = status.available ? '✓' : '—'
html += `<td class="${bgColor} text-center p-2">${text}</td>`
})
html += '</tr>'
})
html += '</tbody>'
return html
}
Step 4: CSV Export Function
function exportComparisonAsCSV() {
const rows = []
const regionNames = comparisonState.selectedRegions.map(r => r.name)
// Header row
rows.push(['Service', ...regionNames].join(','))
// Service rows (simplified; use actual table data)
const table = document.getElementById('serviceMatrix')
const tableRows = table.querySelectorAll('tbody tr')
tableRows.forEach(row => {
const cells = row.querySelectorAll('td')
const rowData = Array.from(cells).map(c => c.textContent.trim())
rows.push(rowData.join(','))
})
// Download CSV
const csv = rows.join('\n')
const blob = new Blob([csv], { type: 'text/csv' })
const url = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `vdc-comparison-${Date.now()}.csv`
a.click()
}
Step 5: Event Handlers
// Toggle comparison mode on marker click
function setupMarkerClickHandler(marker, region) {
marker.on('click', function(e) {
if (e.originalEvent.ctrlKey || e.originalEvent.metaKey) {
// Ctrl/Cmd + click = add to comparison
comparisonState.addRegion(region)
} else {
// Regular click = show info
marker.openPopup()
}
})
}
// Button event handlers
document.getElementById('closeComparisonBtn')?.addEventListener('click', () => {
document.getElementById('comparisonPanel').classList.add('translate-x-full')
})
document.getElementById('clearSelectionBtn')?.addEventListener('click', () => {
comparisonState.clear()
})
document.getElementById('exportCsvBtn')?.addEventListener('click', () => {
exportComparisonAsCSV()
})
// "Compare Regions" button in info panel
document.getElementById('compareRegionsBtn')?.addEventListener('click', () => {
// Activate comparison mode; user then selects regions
document.getElementById('comparisonPanel').classList.remove('translate-x-full')
})
Acceptance Criteria
Priority
User Impact: 4/5 (extends popular comparison API to frontend)
Strategic Alignment: 4/5 (improves UX for infrastructure planning)
Feasibility: 4/5 (leverages existing backend)
Overall Score: 8.0 (second highest priority)
Justification: The comparison API already exists on the backend but isn't exposed in the UI. This is a quick win that surfaces an existing feature to users and provides significant value for multi-region deployment planning.
Dependencies
- Blocks: None
- Blocked by: None (depends on existing
/api/v1/regions/compare endpoint)
Implementation Size
- Estimated effort: Medium (4-5 days)
- Complexity: Medium (state management + API integration + responsive design)
- Testing effort: Medium (integration tests, responsive design testing)
Implementation Order
- Add comparison state management
- Create comparison panel HTML/CSS (Tailwind)
- Implement API fetch and data processing
- Build comparison matrix rendering
- Add event handlers and user interactions
- CSV export functionality
- Mobile responsiveness testing
Additional Notes
- Consider debouncing comparison panel renders if many regions selected
- Could add filtering to comparison table (e.g., "show only vdc_vault services")
- Phase 2: Add URL sharing so users can send comparisons to colleagues
- Phase 2: Persist selected regions to localStorage for resume on return
Batch Region Comparison UI for Interactive Map
Overview
Extend the interactive Leaflet.js map with a dedicated UI component that allows users to select multiple regions (by clicking/checkboxes) and display a detailed side-by-side comparison table. This closes the gap between the backend
/api/v1/regions/compareendpoint and the frontend user experience.Scope
Included:
Explicitly Excluded:
Technical Requirements
UI/UX Flow
Frontend Component Structure
Implementation Plan
Step 1: Add Comparison State Management
Step 2: API Integration Function
Step 3: Comparison Panel Rendering
Step 4: CSV Export Function
Step 5: Event Handlers
Acceptance Criteria
Priority
User Impact: 4/5 (extends popular comparison API to frontend)
Strategic Alignment: 4/5 (improves UX for infrastructure planning)
Feasibility: 4/5 (leverages existing backend)
Overall Score: 8.0 (second highest priority)
Justification: The comparison API already exists on the backend but isn't exposed in the UI. This is a quick win that surfaces an existing feature to users and provides significant value for multi-region deployment planning.
Dependencies
/api/v1/regions/compareendpoint)Implementation Size
Implementation Order
Additional Notes