# RFCP - Iteration 1: Fixes & Features ## Comprehensive Task for Claude Code Read RFCP-Fixes-Iteration1.md and RFCP-TechSpec-v3.0.md for context. --- ## CRITICAL FIXES (Priority: HIGH) ### 1. Heatmap Color Gradient - More Obvious **Current:** Green → Yellow → Red (not very distinct) **New:** Blue → Cyan → Green → Yellow → Orange → Red **File:** `frontend/src/components/map/Heatmap.tsx` ```typescript // Update gradient in HeatmapLayer component: gradient={{ 0.0: '#0d47a1', // Dark Blue (very weak, -120 dBm) 0.2: '#00bcd4', // Cyan (weak, -110 dBm) 0.4: '#4caf50', // Green (fair, -100 dBm) 0.6: '#ffeb3b', // Yellow (good, -85 dBm) 0.8: '#ff9800', // Orange (strong, -70 dBm) 1.0: '#f44336', // Red (excellent, > -70 dBm) }} ``` **Reasoning:** - Cold colors (blue/cyan) = weak signal - Warm colors (yellow/orange/red) = strong signal - More intuitive for RF planning - Better contrast and visibility --- ### 2. Coverage Radius - Increase to 100km **Current:** Max 20km **New:** Max 100km with larger step **File:** `frontend/src/components/panels/SiteForm.tsx` or wherever Coverage Settings panel is ```typescript // Find the radius slider and update: updateCoverageSettings({ radius: value })} suffix="km" help="Calculation area around each site" /> ``` **Also update default value:** **File:** `frontend/src/store/coverage.ts` (or wherever initial settings are) ```typescript const defaultSettings: CoverageSettings = { radius: 10, // Changed from 20 to 10 (better default) resolution: 200, // 200m rsrpThreshold: -120 }; ``` --- ### 3. "Save & Calculate" Button - Primary Action **Current:** Separate "Save" and "Calculate" buttons **New:** "Save & Calculate" as primary action + "Save Only" as secondary **File:** `frontend/src/components/panels/SiteForm.tsx` ```typescript // Replace button section:
// Add handler function: const handleSaveAndCalculate = async () => { try { // Save the site first await handleSave(); // Get all sites from store const sites = useSitesStore.getState().sites; if (sites.length === 0) { toast.info('Add at least one site to calculate coverage'); return; } // Trigger coverage calculation const coverageStore = useCoverageStore.getState(); await coverageStore.calculateCoverage(sites); // Success feedback toast.success(`Coverage calculated for ${sites.length} site(s)!`); } catch (error) { toast.error(`Failed: ${error.message}`); } }; ``` --- ### 4. Legend Colors - Match New Gradient **File:** `frontend/src/components/map/Legend.tsx` ```typescript const signalRanges = [ { label: 'Excellent', range: '> -70 dBm', color: '#f44336', // Red description: 'Very strong signal' }, { label: 'Good', range: '-70 to -85 dBm', color: '#ff9800', // Orange description: 'Strong signal' }, { label: 'Fair', range: '-85 to -100 dBm', color: '#ffeb3b', // Yellow description: 'Acceptable signal' }, { label: 'Poor', range: '-100 to -110 dBm', color: '#4caf50', // Green description: 'Weak signal' }, { label: 'Weak', range: '-110 to -120 dBm', color: '#00bcd4', // Cyan description: 'Very weak signal' }, { label: 'No Service', range: '< -120 dBm', color: '#0d47a1', // Dark Blue description: 'No coverage' }, ]; ``` **Also update constants:** **File:** `frontend/src/constants/rsrp-thresholds.ts` ```typescript export const SIGNAL_COLORS = { excellent: '#f44336', // Red good: '#ff9800', // Orange fair: '#ffeb3b', // Yellow poor: '#4caf50', // Green weak: '#00bcd4', // Cyan 'no-service': '#0d47a1' // Dark Blue } as const; ``` --- ## UI IMPROVEMENTS (Priority: HIGH) ### 5. Dark Theme Support 🌙 **Add complete dark mode with toggle** **Files to create/update:** - `frontend/src/store/settings.ts` - theme state - `frontend/src/components/ui/ThemeToggle.tsx` - toggle button - `frontend/tailwind.config.js` - dark mode config - `frontend/src/index.css` - dark theme styles **Implementation:** **A) Tailwind Config:** ```javascript // tailwind.config.js export default { darkMode: 'class', // Enable class-based dark mode content: [ "./index.html", "./src/**/*.{js,ts,jsx,tsx}", ], theme: { extend: { colors: { // Custom colors for dark mode dark: { bg: '#1a1a1a', surface: '#2d2d2d', border: '#404040', text: '#e0e0e0', } } }, }, plugins: [], } ``` **B) Settings Store:** ```typescript // src/store/settings.ts (create if doesn't exist) import { create } from 'zustand'; import { persist } from 'zustand/middleware'; interface SettingsState { theme: 'light' | 'dark' | 'system'; setTheme: (theme: 'light' | 'dark' | 'system') => void; } export const useSettingsStore = create()( persist( (set) => ({ theme: 'system', setTheme: (theme) => { set({ theme }); applyTheme(theme); }, }), { name: 'rfcp-settings', } ) ); function applyTheme(theme: 'light' | 'dark' | 'system') { const root = window.document.documentElement; if (theme === 'system') { const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; root.classList.toggle('dark', systemTheme === 'dark'); } else { root.classList.toggle('dark', theme === 'dark'); } } // Apply theme on page load if (typeof window !== 'undefined') { const stored = localStorage.getItem('rfcp-settings'); if (stored) { const { theme } = JSON.parse(stored); applyTheme(theme); } } ``` **C) Theme Toggle Component:** ```typescript // src/components/ui/ThemeToggle.tsx import { useSettingsStore } from '@/store/settings'; export function ThemeToggle() { const { theme, setTheme } = useSettingsStore(); const icons = { light: '☀️', dark: '🌙', system: '💻' }; return (
{(['light', 'dark', 'system'] as const).map((t) => ( ))}
); } ``` **D) Update App.tsx:** ```typescript // Add ThemeToggle to header import { ThemeToggle } from '@/components/ui/ThemeToggle'; function App() { return (

RFCP - RF Coverage Planner

{/* rest of app */}
); } ``` **E) Dark Mode Styles for Components:** Update all major components with dark mode classes: ```typescript // Example pattern:
{/* content */}
// Borders: className="border border-gray-200 dark:border-dark-border" // Inputs: className="bg-white dark:bg-dark-bg border-gray-300 dark:border-dark-border" // Buttons: className="bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700" ``` **Apply dark mode to:** - Map panels (SiteForm, SiteList, Coverage Settings) - Legend component - Toast notifications - Modal overlays - Input fields and sliders --- ### 6. Sites List - Show More Info **Current:** Just site name **New:** Show key parameters inline **File:** `frontend/src/components/panels/SiteList.tsx` ```typescript // Update site item rendering:
{/* Color indicator */}
{/* Site info */}
{site.name}
📻 {site.frequency} MHz • ⚡ {site.power} dBm • 📡 {site.antennaType === 'omni' ? 'Omni' : `Sector ${site.azimuth}°`}
{/* Actions */}
``` --- ### 7. Keyboard Shortcuts **Add useful shortcuts for power users** **File:** `frontend/src/App.tsx` or create `src/hooks/useKeyboardShortcuts.ts` ```typescript // Create hook: // src/hooks/useKeyboardShortcuts.ts import { useEffect } from 'react'; import { useSitesStore } from '@/store/sites'; import { useCoverageStore } from '@/store/coverage'; import { toast } from '@/components/ui/Toast'; export function useKeyboardShortcuts() { useEffect(() => { const handleKeyPress = (e: KeyboardEvent) => { // Ignore if typing in input if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) { return; } const isMac = navigator.platform.toLowerCase().includes('mac'); const modKey = isMac ? e.metaKey : e.ctrlKey; if (modKey) { switch(e.key.toLowerCase()) { case 's': // Ctrl/Cmd+S: Save current site e.preventDefault(); const selectedSite = useSitesStore.getState().selectedSite; if (selectedSite) { useSitesStore.getState().updateSite(selectedSite.id, selectedSite); toast.success('Site saved'); } break; case 'enter': // Ctrl/Cmd+Enter: Calculate coverage e.preventDefault(); const sites = useSitesStore.getState().sites; if (sites.length > 0) { useCoverageStore.getState().calculateCoverage(sites); } else { toast.info('Add sites first'); } break; case 'n': // Ctrl/Cmd+N: New site (enter placement mode) e.preventDefault(); useSitesStore.getState().setPlacementMode(true); toast.info('Click on map to place new site'); break; } } // Non-modifier shortcuts switch(e.key) { case 'Escape': // Escape: Cancel/close useSitesStore.getState().setSelectedSite(null); useSitesStore.getState().setPlacementMode(false); break; case 'h': // H: Toggle heatmap useCoverageStore.getState().toggleHeatmap(); break; } }; window.addEventListener('keydown', handleKeyPress); return () => window.removeEventListener('keydown', handleKeyPress); }, []); } // Use in App.tsx: function App() { useKeyboardShortcuts(); // ... } ``` **Add keyboard shortcuts help:** ```typescript // Add to header or settings: {showShortcuts && (

Keyboard Shortcuts

  • Ctrl/Cmd + S - Save site
  • Ctrl/Cmd + Enter - Calculate coverage
  • Ctrl/Cmd + N - New site
  • Esc - Cancel/Close
  • H - Toggle heatmap
)} ``` --- ### 8. Map Controls - Fit to Sites & Reset View **Add helpful map navigation buttons** **File:** `frontend/src/components/map/Map.tsx` ```typescript // Add control buttons:
{/* Fit to sites */} {/* Reset to Ukraine */}
// Handlers: const handleFitToSites = () => { if (sites.length === 0) return; const bounds = sites.map(site => [site.lat, site.lon] as [number, number]); mapRef.current?.fitBounds(bounds, { padding: [50, 50] }); }; const handleResetView = () => { mapRef.current?.setView([48.4, 35.0], 6); // Ukraine center }; ``` --- ### 9. Better Error Handling **Add comprehensive error handling with helpful messages** **File:** `frontend/src/store/coverage.ts` ```typescript calculateCoverage: async (sites: Site[]) => { if (sites.length === 0) { toast.error('No sites to calculate'); return; } set({ isCalculating: true, error: null }); try { const settings = get().settings; // Validation if (settings.radius > 100) { throw new Error('Radius too large (max 100km)'); } if (settings.resolution < 50) { throw new Error('Resolution too fine (min 50m)'); } // Calculate const result = await calculator.calculateCoverage(sites, bounds, settings); if (result.points.length === 0) { toast.warning('No coverage points found. Try increasing radius or lowering threshold.'); } else { toast.success(`Calculated ${result.points.length.toLocaleString()} points in ${(result.calculationTime / 1000).toFixed(1)}s`); } set({ coveragePoints: result.points, isCalculating: false }); } catch (error) { console.error('Coverage calculation error:', error); // User-friendly error messages let message = 'Calculation failed'; if (error.message.includes('timeout')) { message = 'Calculation timeout. Try reducing radius or increasing resolution.'; } else if (error.message.includes('worker')) { message = 'Web Worker error. Please refresh the page.'; } else if (error.message.includes('memory')) { message = 'Out of memory. Try smaller radius or coarser resolution.'; } toast.error(message); set({ error: error.message, isCalculating: false }); } } ``` --- ### 10. Mobile Responsiveness Improvements **Ensure panels don't block map on mobile** **Files:** `SiteForm.tsx`, `SiteList.tsx`, `Legend.tsx` ```typescript // Pattern for mobile-friendly panels:
{/* Panel content */}
// Add drag handle for mobile:
``` **Touch-friendly buttons:** ```typescript // Minimum 44x44px touch targets ``` --- ## OPTIONAL ENHANCEMENTS (if time permits) ### 11. Calculation Stats Display **Show useful statistics after calculation** **File:** `frontend/src/components/map/Legend.tsx` ```typescript {coveragePoints.length > 0 && (

📊 Coverage Statistics

Points: {coveragePoints.length.toLocaleString()}
Calculation: {(calculationTime / 1000).toFixed(2)}s
Coverage area: ~{calculateArea()} km²
Sites: {sites.length}
)} ``` --- ### 12. Templates Verification **Ensure quick templates work correctly** **File:** `frontend/src/components/panels/SiteForm.tsx` ```typescript const QUICK_TEMPLATES = { limesdr: { name: 'LimeSDR Mini', power: 20, gain: 2, frequency: 1800, height: 10, antennaType: 'omni' as const }, 'low-bbu': { name: 'Low Power BBU', power: 40, gain: 8, frequency: 1800, height: 20, antennaType: 'omni' as const }, 'high-bbu': { name: 'High Power BBU', power: 43, gain: 15, frequency: 1800, height: 30, antennaType: 'sector' as const, azimuth: 0, beamwidth: 65 } }; const applyTemplate = (templateId: keyof typeof QUICK_TEMPLATES) => { const template = QUICK_TEMPLATES[templateId]; // Apply ALL fields setFormData({ ...formData, power: template.power, gain: template.gain, frequency: template.frequency, height: template.height, antennaType: template.antennaType, azimuth: template.azimuth || 0, beamwidth: template.beamwidth || 65 }); toast.success(`Applied: ${template.name}`); }; ``` --- ## TESTING CHECKLIST After implementation, verify: ### Visual Tests: - [ ] Dark theme looks good on all components - [ ] New heatmap gradient is clearly visible (blue=weak, red=strong) - [ ] Legend matches heatmap colors - [ ] Sites list shows frequency, power, antenna type - [ ] Mobile panels don't block map completely ### Functional Tests: - [ ] "Save & Calculate" button works (saves + calculates) - [ ] "Save Only" button still works - [ ] Coverage radius slider goes to 100km - [ ] Large radius (50-100km) calculates without errors - [ ] Dark/Light/System theme toggle works - [ ] Theme persists after page refresh ### Keyboard Shortcuts: - [ ] Ctrl/Cmd+S saves current site - [ ] Ctrl/Cmd+Enter calculates coverage - [ ] Ctrl/Cmd+N enters placement mode - [ ] Esc cancels/closes - [ ] H toggles heatmap visibility ### Mobile Tests (if possible): - [ ] Panels slide up from bottom on mobile - [ ] Touch targets are large enough (44x44px) - [ ] Sliders work with touch - [ ] Map can be panned/zoomed on mobile ### Error Handling: - [ ] Helpful error messages for calculation failures - [ ] Toast notifications for all actions - [ ] No console errors --- ## IMPLEMENTATION NOTES 1. **Start with visual changes** (colors, legend) - quick wins 2. **Then functional changes** (button, radius, dark theme) 3. **Then UX improvements** (shortcuts, mobile, errors) 4. **Test thoroughly** - especially dark theme across all components **Performance Note:** Current calculation is instant because it's only FSPL without terrain data. When Phase 4 (30m terrain) is implemented, large radius will take longer. Performance warnings can be added then. --- ## FILES TO CREATE/MODIFY ### New Files: - `frontend/src/store/settings.ts` - theme management - `frontend/src/components/ui/ThemeToggle.tsx` - theme switcher - `frontend/src/hooks/useKeyboardShortcuts.ts` - keyboard shortcuts ### Modified Files: - `frontend/tailwind.config.js` - dark mode config - `frontend/src/index.css` - dark theme CSS variables - `frontend/src/App.tsx` - add ThemeToggle, use shortcuts hook - `frontend/src/components/map/Heatmap.tsx` - new gradient - `frontend/src/components/map/Legend.tsx` - new colors + stats - `frontend/src/components/map/Map.tsx` - Fit/Reset buttons - `frontend/src/components/panels/SiteForm.tsx` - Save&Calculate, radius - `frontend/src/components/panels/SiteList.tsx` - more info display - `frontend/src/store/coverage.ts` - better error handling, defaults - `frontend/src/constants/rsrp-thresholds.ts` - new color constants - All UI components - add dark mode classes --- ## SUCCESS CRITERIA ✅ New heatmap gradient is clearly visible ✅ Dark theme works flawlessly ✅ Coverage radius goes to 100km ✅ "Save & Calculate" is primary action ✅ Keyboard shortcuts work ✅ Mobile responsive ✅ Better error messages ✅ No TypeScript errors ✅ All existing features still work --- **Estimated Time:** 20-30 minutes for full implementation **Complexity:** Medium (mostly UI updates + new theme system) **Risk:** Low (additive changes, not breaking existing functionality) Good luck! 🚀