@mytec: start iter10.6

This commit is contained in:
2026-01-30 19:59:09 +02:00
parent 8e35329622
commit d8256288b0

View File

@@ -0,0 +1,486 @@
# 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<SiteData>; // For edit mode
mode: 'create' | 'edit';
}
function SiteConfigModal({ isOpen, onClose, onSave, initialData, mode }: SiteConfigModalProps) {
const [formData, setFormData] = useState<SiteFormData>({
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 (
<ModalBackdrop onClose={onClose}>
<div className="modal-content">
<header>
<h2>{mode === 'create' ? '📍 New Site Configuration' : '✏️ Edit Site'}</h2>
<button onClick={onClose}></button>
</header>
{/* Form fields */}
<footer>
<button onClick={onClose}>Cancel</button>
<button onClick={handleSave}>
{mode === 'create' ? 'Create Site' : 'Save Changes'}
</button>
</footer>
</div>
</ModalBackdrop>
);
}
```
**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 (
<div
className="modal-backdrop"
onClick={handleBackdropClick}
>
{children}
</div>
);
}
```
### 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<SiteData>;
}>({ 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
<SiteConfigModal
isOpen={modalState.isOpen}
mode={modalState.mode}
initialData={modalState.initialData}
onClose={() => 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