Files
rfcp/docs/devlog/front/RFCP-Iteration10.6-Site-Modal-Batch-Power-Tilt.md
2026-01-30 20:39:13 +02:00

17 KiB

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:

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:

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

.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:

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

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