# RFCP Iteration 10.6 — Site Modal Dialogs + Batch Power/Tilt **Date:** 2025-01-30 **Status:** Ready for Implementation **Priority:** Medium-High **Estimated Effort:** 60-90 minutes --- ## Overview Two UX improvements: 1. **Part A:** Modal dialogs for Create/Edit site (instead of sidebar form) 2. **Part B:** Additional batch operations (Power, Tilt) --- ## Part A: Site Configuration Modal ### Problem Current flow is confusing: 1. User clicks "Place on Map" → no visual feedback 2. User clicks on map → form appears in sidebar (easy to miss) 3. User doesn't understand they're creating a site Same issue with Edit — form in sidebar is not obvious. ### Solution **Center-screen modal dialog** for: - Creating new site (after clicking on map) - Editing existing site ### Modal Design ``` ┌──────────────────────────────────────────────────────┐ │ ✕ │ │ 📍 New Site Configuration │ │ │ ├──────────────────────────────────────────────────────┤ │ │ │ Site Name │ │ ┌────────────────────────────────────────────────┐ │ │ │ Station-1 │ │ │ └────────────────────────────────────────────────┘ │ │ │ │ Coordinates │ │ ┌───────────────────┐ ┌───────────────────┐ │ │ │ 48.52660975 │ │ 35.68222045 │ │ │ └───────────────────┘ └───────────────────┘ │ │ Latitude Longitude │ │ │ │ ─────────────── RF Parameters ─────────────── │ │ │ │ Transmit Power 43 dBm │ │ ┌────────────────────────────────────────────────┐ │ │ │ ●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━○ │ │ │ └────────────────────────────────────────────────┘ │ │ 10 dBm 50 dBm │ │ LimeSDR 20, BBU 43, RRU 46 │ │ │ │ Antenna Gain 8 dBi │ │ ┌────────────────────────────────────────────────┐ │ │ │ ○━━━━━━━●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ │ │ └────────────────────────────────────────────────┘ │ │ 0 dBi 25 dBi │ │ Omni 2-8, Sector 15-18, Parabolic 20-25 │ │ │ │ Operating Frequency │ │ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │ │ │ 800 │ │1800 │ │1900 │ │2100 │ │2600 │ │ │ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ │ │ ▲ selected │ │ │ │ Custom MHz ┌──────────────┐ [Set] │ │ │ │ │ │ └──────────────┘ │ │ │ │ Current: 1800 MHz │ │ Band 3 (1710-1880 MHz) │ │ λ = 16.7 cm · medium range · good penetration │ │ │ ├──────────────────────────────────────────────────────┤ │ │ │ [Cancel] [Create Site] │ │ │ └──────────────────────────────────────────────────────┘ ``` ### Modal Behavior **Opening:** - "Place on Map" → click on map → Modal opens with coordinates pre-filled - "Edit" button on site → Modal opens with all data pre-filled - "+ Manual" button → Modal opens with empty/default values **Closing:** - Click ✕ → close without saving - Click "Cancel" → close without saving - Press Escape → close without saving - Click outside modal (backdrop) → close without saving - Click "Create Site" / "Save Changes" → validate, save, close **Validation:** - Site name: required, non-empty - Coordinates: valid lat (-90 to 90), lon (-180 to 180) - Power: 10-50 dBm - Gain: 0-25 dBi - Frequency: 100-6000 MHz ### Component Structure ``` src/components/ ├── modals/ │ ├── SiteConfigModal.tsx # Main modal component │ ├── ModalBackdrop.tsx # Reusable backdrop │ └── index.ts # Exports ``` ### Implementation **SiteConfigModal.tsx:** ```typescript interface SiteConfigModalProps { isOpen: boolean; onClose: () => void; onSave: (site: SiteData) => void; initialData?: Partial; // For edit mode mode: 'create' | 'edit'; } function SiteConfigModal({ isOpen, onClose, onSave, initialData, mode }: SiteConfigModalProps) { const [formData, setFormData] = useState({ name: initialData?.name || 'Station-1', lat: initialData?.lat || 0, lon: initialData?.lon || 0, power: initialData?.power || 43, gain: initialData?.gain || 8, frequency: initialData?.frequency || 2100, }); // ... form handling if (!isOpen) return null; return (

{mode === 'create' ? '📍 New Site Configuration' : '✏️ Edit Site'}

{/* Form fields */}
); } ``` **ModalBackdrop.tsx:** ```typescript interface ModalBackdropProps { children: React.ReactNode; onClose: () => void; } function ModalBackdrop({ children, onClose }: ModalBackdropProps) { const handleBackdropClick = (e: React.MouseEvent) => { if (e.target === e.currentTarget) { onClose(); } }; useEffect(() => { const handleEscape = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); }; document.addEventListener('keydown', handleEscape); return () => document.removeEventListener('keydown', handleEscape); }, [onClose]); return (
{children}
); } ``` ### CSS Styling ```css .modal-backdrop { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.7); display: flex; align-items: center; justify-content: center; z-index: 1000; backdrop-filter: blur(4px); } .modal-content { background: #1e293b; /* slate-800 */ border-radius: 12px; width: 90%; max-width: 480px; max-height: 90vh; overflow-y: auto; box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5); border: 1px solid rgba(255, 255, 255, 0.1); } .modal-content header { display: flex; justify-content: space-between; align-items: center; padding: 16px 20px; border-bottom: 1px solid rgba(255, 255, 255, 0.1); } .modal-content header h2 { font-size: 1.25rem; font-weight: 600; color: white; } .modal-content footer { display: flex; justify-content: flex-end; gap: 12px; padding: 16px 20px; border-top: 1px solid rgba(255, 255, 255, 0.1); } ``` ### Integration with App **In App.tsx or SiteList.tsx:** ```typescript const [modalState, setModalState] = useState<{ isOpen: boolean; mode: 'create' | 'edit'; initialData?: Partial; }>({ isOpen: false, mode: 'create' }); // When user clicks on map after "Place on Map" const handleMapClick = (lat: number, lon: number) => { if (isPlacingMode) { setModalState({ isOpen: true, mode: 'create', initialData: { lat, lon }, }); setIsPlacingMode(false); } }; // When user clicks "Edit" on a site const handleEditSite = (site: Site) => { setModalState({ isOpen: true, mode: 'edit', initialData: site, }); }; // Render modal setModalState({ ...modalState, isOpen: false })} onSave={handleSaveSite} /> ``` --- ## Part B: Batch Power/Tilt Operations ### Current Batch Operations Already implemented: - ✅ Adjust Height: +10m, +5m, -5m, -10m - ✅ Set Height: exact value - ✅ Adjust Azimuth: -90°, -45°, -10°, +10°, +45°, +90° - ✅ Set Azimuth: exact value ### New Batch Operations to Add **1. Power Adjustment:** ``` Adjust Power: [+6dB] [+3dB] [+1dB] [-1dB] [-3dB] [-6dB] Set Power: ┌─────────────────┐ [Apply] │ dBm │ └─────────────────┘ ``` **2. Tilt Adjustment (for directional antennas):** ``` Adjust Tilt: [+10°] [+5°] [+2°] [-2°] [-5°] [-10°] Set Tilt: ┌─────────────────┐ [Apply] │ degrees │ └─────────────────┘ ``` **3. Frequency (Set only, no adjust):** ``` Set Frequency: [800] [1800] [1900] [2100] [2600] ← quick buttons Custom: ┌─────────────────┐ [Apply] │ MHz │ └─────────────────┘ ``` ### Implementation Location In the existing batch edit panel (visible when multiple sectors selected). **File to modify:** - `src/components/panels/SiteList.tsx` or wherever BatchEditPanel is ### Batch Operation Handler ```typescript const handleBatchAdjustPower = (delta: number) => { selectedSectors.forEach(sector => { const newPower = Math.max(10, Math.min(50, sector.power + delta)); updateSector(sector.id, { power: newPower }); }); }; const handleBatchSetPower = (value: number) => { const clamped = Math.max(10, Math.min(50, value)); selectedSectors.forEach(sector => { updateSector(sector.id, { power: clamped }); }); }; const handleBatchAdjustTilt = (delta: number) => { selectedSectors.forEach(sector => { const newTilt = Math.max(-90, Math.min(90, sector.tilt + delta)); updateSector(sector.id, { tilt: newTilt }); }); }; const handleBatchSetFrequency = (frequency: number) => { selectedSectors.forEach(sector => { updateSector(sector.id, { frequency }); }); }; ``` ### UI Layout for Batch Panel ``` ┌─────────────────────────────────────────┐ │ Batch Edit (3 selected) Clear │ ├─────────────────────────────────────────┤ │ │ │ Adjust Height: │ │ [+10m] [+5m] [-5m] [-10m] │ │ │ │ Set Height: │ │ [________] meters [Apply] │ │ │ │ ─────────────────────────────────────── │ │ │ │ Adjust Azimuth: │ │ [-90°] [-45°] [-10°] [+10°] [+45°] [+90°]│ │ │ │ Set Azimuth: │ │ [________] 0-359° [N 0°] [Apply] │ │ │ │ ─────────────────────────────────────── │ │ │ │ Adjust Power: [NEW] │ │ [+6dB] [+3dB] [+1dB] [-1dB] [-3dB] [-6dB]│ │ │ │ Set Power: │ │ [________] dBm [Apply] │ │ │ │ ─────────────────────────────────────── │ │ │ │ Adjust Tilt: [NEW] │ │ [+10°] [+5°] [+2°] [-2°] [-5°] [-10°] │ │ │ │ Set Tilt: │ │ [________] degrees [Apply] │ │ │ │ ─────────────────────────────────────── │ │ │ │ Set Frequency: [NEW] │ │ [800] [1800] [1900] [2100] [2600] MHz │ │ │ │ Custom: [________] MHz [Apply] │ │ │ └─────────────────────────────────────────┘ ``` --- ## Testing Checklist ### Part A: Site Modal - [ ] "Place on Map" → click map → modal opens - [ ] Modal shows correct coordinates from click - [ ] Can edit all fields in modal - [ ] "Create Site" saves and closes modal - [ ] Site appears on map after creation - [ ] "Cancel" closes without saving - [ ] Clicking backdrop closes without saving - [ ] Escape key closes without saving - [ ] "Edit" on existing site opens modal with data - [ ] "Save Changes" updates site correctly - [ ] Validation prevents invalid data - [ ] Modal scrolls if content too tall - [ ] Modal looks good on mobile ### Part B: Batch Operations - [ ] Select multiple sectors shows batch panel - [ ] Adjust Power buttons work (+6, +3, +1, -1, -3, -6) - [ ] Set Power applies exact value to all selected - [ ] Adjust Tilt buttons work - [ ] Set Tilt applies exact value - [ ] Frequency quick buttons work (800, 1800, etc.) - [ ] Custom frequency input works - [ ] Values stay within valid ranges (clamped) - [ ] Coverage updates after batch changes --- ## Files to Modify/Create **New Files:** - `src/components/modals/SiteConfigModal.tsx` - `src/components/modals/ModalBackdrop.tsx` - `src/components/modals/index.ts` **Modify:** - `src/App.tsx` — integrate modal state - `src/components/panels/SiteList.tsx` — trigger modal instead of sidebar form - `src/components/panels/BatchEditPanel.tsx` (or equivalent) — add Power/Tilt/Frequency --- ## Notes - Modal should use same form components as current sidebar (reuse) - Consider extracting form fields to shared component - Batch operations should trigger coverage recalculation - Test with both single-sector and multi-sector sites --- ## Reference - Previous iteration: RFCP-Iteration10.5-Input-Validation-Hierarchy-UI.md - Current SiteForm: check existing implementation for field structure