diff --git a/RFCP-Fixes-Iteration1.md b/RFCP-Fixes-Iteration1.md deleted file mode 100644 index b3a7f7c..0000000 --- a/RFCP-Fixes-Iteration1.md +++ /dev/null @@ -1,251 +0,0 @@ -# RFCP Fixes - Iteration 1 - -## Issues to Fix: - -### 1. Heatmap Colors - More Obvious Gradient - -**Current problem:** Green→Yellow→Red gradient not obvious enough - -**Fix:** Update `src/components/map/Heatmap.tsx` - -Change the gradient to use more distinct colors with better visibility: - -```typescript -// In Heatmap.tsx, update the gradient prop: - - p[1]} - latitudeExtractor={(p: any) => p[0]} - intensityExtractor={(p: any) => p[2]} - gradient={{ - 0.0: '#0000ff', // Blue (very weak, -120 dBm) - 0.2: '#00ffff', // Cyan (weak, -110 dBm) - 0.4: '#00ff00', // Green (fair, -100 dBm) - 0.6: '#ffff00', // Yellow (good, -85 dBm) - 0.8: '#ff7f00', // Orange (strong, -70 dBm) - 1.0: '#ff0000', // Red (excellent, > -70 dBm) - }} - radius={25} - blur={15} - max={1.0} -/> -``` - -**Reasoning:** -- Blue/Cyan for weak signals (more intuitive - "cold" = weak) -- Green for acceptable -- Yellow/Orange for good -- Red for excellent (hot = strong) - -This is actually inverted from typical "green=good", but in RF planning, RED = STRONG SIGNAL = GOOD! - ---- - -### 2. Coverage Radius - Increase to 100km - -**Current problem:** Max radius only 20km, not enough for tactical planning - -**Fix:** Update `src/components/panels/SiteForm.tsx` (or wherever Coverage Settings are) - -Find the radius slider and change: - -```typescript -// Old: - updateCoverageSettings({ radius: value })} -/> - -// New: - updateCoverageSettings({ radius: value })} - help="Calculation area around each site" -/> -``` - -**Also update default value in store:** - -```typescript -// In src/store/coverage.ts or wherever coverage settings are initialized: -const initialSettings = { - radius: 10, // Default 10km (reasonable starting point) - resolution: 200, // 200m resolution - rsrpThreshold: -120 -}; -``` - ---- - -### 3. Save & Calculate Button - -**Current problem:** Two separate buttons, extra click needed - -**Fix:** Update `src/components/panels/SiteForm.tsx` - -Replace the button section: - -```typescript -// Old: -
- - -
- -// New: -
- - - -
- -// Add the handler: -const handleSaveAndCalculate = async () => { - // Save the site first - await handleSave(); - - // Then trigger coverage calculation - const sites = useSitesStore.getState().sites; - const coverage = useCoverageStore.getState(); - - // Calculate coverage for all sites - await coverage.calculateCoverage(sites); - - // Show success toast - toast.success('Site saved and coverage calculated!'); -}; -``` - -**Alternative (simpler):** Just make the main button do both: - -```typescript -
- - -
-``` - ---- - -### 4. Legend Colors Update - -**Fix:** Update `src/components/map/Legend.tsx` to match new gradient: - -```typescript -const signalRanges = [ - { label: 'Excellent', range: '> -70 dBm', color: '#ff0000' }, // Red - { label: 'Good', range: '-70 to -85 dBm', color: '#ff7f00' }, // Orange - { label: 'Fair', range: '-85 to -100 dBm', color: '#ffff00' }, // Yellow - { label: 'Poor', range: '-100 to -110 dBm', color: '#00ff00' }, // Green - { label: 'Weak', range: '-110 to -120 dBm', color: '#00ffff' }, // Cyan - { label: 'Very Weak', range: '< -120 dBm', color: '#0000ff' }, // Blue -]; -``` - ---- - -### 5. Resolution Adjustment for Large Radius - -**Problem:** 200m resolution + 100km radius = MANY points = slow calculation - -**Fix:** Auto-adjust resolution based on radius: - -```typescript -// In src/store/coverage.ts or coverage calculator: - -const getOptimalResolution = (radius: number): number => { - if (radius <= 10) return 100; // 100m for small areas - if (radius <= 30) return 200; // 200m for medium areas - if (radius <= 60) return 300; // 300m for large areas - return 500; // 500m for very large areas (100km) -}; - -// Use in calculation: -const resolution = settings.resolution || getOptimalResolution(settings.radius); -``` - -Or add a notice in UI: - -```typescript -

- 💡 Larger radius = longer calculation time. - Consider increasing resolution (200m → 500m) for faster results. -

-``` - ---- - -## Summary of Changes: - -1. ✅ **Heatmap gradient:** Blue → Cyan → Green → Yellow → Orange → Red -2. ✅ **Max radius:** 20km → 100km (step: 5km) -3. ✅ **Button:** "Save & Calculate" as primary action -4. ✅ **Legend:** Updated colors to match new gradient -5. ✅ **Performance:** Auto-adjust resolution or show warning - ---- - -## Implementation Order: - -1. **Start with colors** (easiest, biggest visual impact) -2. **Then radius** (simple slider change) -3. **Then button** (requires store integration) -4. **Test everything** - ---- - -## Files to Edit: - -``` -frontend/src/components/map/Heatmap.tsx - gradient colors -frontend/src/components/map/Legend.tsx - legend colors -frontend/src/components/panels/SiteForm.tsx - radius slider + button -frontend/src/store/coverage.ts - default settings -``` - ---- - -Would you like me to create the exact code patches for Claude Code to apply? -Or should I create complete replacement files? diff --git a/RFCP-Iteration1-Full-Task.md b/RFCP-Iteration1-Full-Task.md new file mode 100644 index 0000000..2ca5882 --- /dev/null +++ b/RFCP-Iteration1-Full-Task.md @@ -0,0 +1,847 @@ +# 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! 🚀