import { memo } from 'react';
import type { CoveragePoint } from '@/types/index.ts';
interface CoverageStatsProps {
points: CoveragePoint[];
resolution: number; // meters
}
/**
* Estimate total coverage area from grid points.
* Each point represents a resolution × resolution cell.
*/
function estimateAreaKm2(pointCount: number, resolutionM: number): number {
const cellAreaM2 = resolutionM * resolutionM;
return (pointCount * cellAreaM2) / 1_000_000;
}
const LEVELS = [
{ label: 'Excellent', threshold: -70, color: 'bg-green-500' },
{ label: 'Good', threshold: -85, color: 'bg-lime-500' },
{ label: 'Fair', threshold: -100, color: 'bg-yellow-500' },
{ label: 'Weak', threshold: -Infinity, color: 'bg-red-500' },
] as const;
function classifyPoints(points: CoveragePoint[]) {
const counts = { excellent: 0, good: 0, fair: 0, weak: 0 };
for (const p of points) {
if (p.rsrp > -70) counts.excellent++;
else if (p.rsrp > -85) counts.good++;
else if (p.rsrp > -100) counts.fair++;
else counts.weak++;
}
return counts;
}
export default memo(function CoverageStats({ points, resolution }: CoverageStatsProps) {
if (points.length === 0) {
return (
Coverage Analysis
📊
No coverage data yet.
Press Ctrl+Enter to calculate.
);
}
const counts = classifyPoints(points);
const totalArea = estimateAreaKm2(points.length, resolution);
const total = points.length;
// Use reduce instead of Math.min/max spread — spread crashes on 65k+ elements
let minRSRP = Infinity;
let maxRSRP = -Infinity;
let sumRSRP = 0;
for (const p of points) {
if (p.rsrp < minRSRP) minRSRP = p.rsrp;
if (p.rsrp > maxRSRP) maxRSRP = p.rsrp;
sumRSRP += p.rsrp;
}
const avgRSRP = sumRSRP / total;
// Unique sites contributing to coverage
const uniqueSites = new Set(points.map((p) => p.siteId)).size;
const levels = [
{ ...LEVELS[0], count: counts.excellent },
{ ...LEVELS[1], count: counts.good },
{ ...LEVELS[2], count: counts.fair },
{ ...LEVELS[3], count: counts.weak },
];
return (
Coverage Analysis
{/* Summary stats */}
Total Area
{totalArea.toFixed(1)} km²
Grid Points
{total.toLocaleString()}
Avg RSRP
{avgRSRP.toFixed(1)} dBm
{/* RSRP range */}
Range: {minRSRP.toFixed(1)} to {maxRSRP.toFixed(1)} dBm
{/* Signal quality breakdown */}
{levels.map((level) => {
const pct = total > 0 ? (level.count / total) * 100 : 0;
return (
{level.label}
({level.threshold === -Infinity
? '< -100'
: `> ${level.threshold}`} dBm)
{pct.toFixed(1)}%
);
})}
);
});