@mytec: iter7 ready for test

This commit is contained in:
2026-01-30 13:06:31 +02:00
parent baebd29e1a
commit 3e1061e369
12 changed files with 592 additions and 39 deletions

View File

@@ -0,0 +1,138 @@
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 function CoverageStats({ points, resolution }: CoverageStatsProps) {
if (points.length === 0) {
return (
<div className="bg-white dark:bg-dark-surface border border-gray-200 dark:border-dark-border rounded-lg shadow-sm p-4">
<h3 className="text-sm font-semibold text-gray-800 dark:text-dark-text mb-2">
Coverage Analysis
</h3>
<p className="text-xs text-gray-400 dark:text-dark-muted">
No coverage data. Calculate coverage first.
</p>
</div>
);
}
const counts = classifyPoints(points);
const totalArea = estimateAreaKm2(points.length, resolution);
const total = points.length;
const rsrpValues = points.map((p) => p.rsrp);
const minRSRP = Math.min(...rsrpValues);
const maxRSRP = Math.max(...rsrpValues);
const avgRSRP = rsrpValues.reduce((a, b) => a + b, 0) / 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 (
<div className="bg-white dark:bg-dark-surface border border-gray-200 dark:border-dark-border rounded-lg shadow-sm p-4 space-y-3">
<h3 className="text-sm font-semibold text-gray-800 dark:text-dark-text">
Coverage Analysis
</h3>
{/* Summary stats */}
<div className="grid grid-cols-2 gap-2 text-xs">
<div className="bg-gray-50 dark:bg-dark-bg rounded p-2">
<div className="text-gray-500 dark:text-dark-muted">Total Area</div>
<div className="font-semibold text-gray-800 dark:text-dark-text">
{totalArea.toFixed(1)} km²
</div>
</div>
<div className="bg-gray-50 dark:bg-dark-bg rounded p-2">
<div className="text-gray-500 dark:text-dark-muted">Grid Points</div>
<div className="font-semibold text-gray-800 dark:text-dark-text">
{total.toLocaleString()}
</div>
</div>
<div className="bg-gray-50 dark:bg-dark-bg rounded p-2">
<div className="text-gray-500 dark:text-dark-muted">Avg RSRP</div>
<div className="font-semibold text-gray-800 dark:text-dark-text">
{avgRSRP.toFixed(1)} dBm
</div>
</div>
<div className="bg-gray-50 dark:bg-dark-bg rounded p-2">
<div className="text-gray-500 dark:text-dark-muted">Sites</div>
<div className="font-semibold text-gray-800 dark:text-dark-text">
{uniqueSites}
</div>
</div>
</div>
{/* RSRP range */}
<div className="text-xs text-gray-500 dark:text-dark-muted">
Range: {minRSRP.toFixed(1)} to {maxRSRP.toFixed(1)} dBm
</div>
{/* Signal quality breakdown */}
<div className="space-y-1.5">
{levels.map((level) => {
const pct = total > 0 ? (level.count / total) * 100 : 0;
return (
<div key={level.label} className="space-y-0.5">
<div className="flex items-center justify-between text-xs">
<span className="text-gray-700 dark:text-dark-text">
{level.label}
<span className="text-gray-400 dark:text-dark-muted ml-1">
({level.threshold === -Infinity
? '< -100'
: `> ${level.threshold}`} dBm)
</span>
</span>
<span className="font-medium text-gray-800 dark:text-dark-text">
{pct.toFixed(1)}%
</span>
</div>
<div className="w-full h-1.5 bg-gray-200 dark:bg-dark-border rounded-full overflow-hidden">
<div
className={`h-full ${level.color} rounded-full transition-all`}
style={{ width: `${pct}%` }}
/>
</div>
</div>
);
})}
</div>
</div>
);
}