152 lines
7.3 KiB
TypeScript
152 lines
7.3 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
|
|
const API_BASE = '/seismic/api';
|
|
|
|
interface CoverageStats {
|
|
total_positions: number;
|
|
with_data: number;
|
|
with_aux: number;
|
|
total_files: number;
|
|
coverage_pct: number;
|
|
missing: number;
|
|
}
|
|
|
|
interface Gap {
|
|
start: number;
|
|
end: number;
|
|
length: number;
|
|
}
|
|
|
|
function H5Coverage({ onClose }: { onClose: () => void }) {
|
|
const [stats, setStats] = useState<CoverageStats | null>(null);
|
|
const [gaps, setGaps] = useState<Gap[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
Promise.all([
|
|
fetch(`${API_BASE}/h5/coverage`).then(r => r.json()),
|
|
fetch(`${API_BASE}/h5/gaps`).then(r => r.json())
|
|
]).then(([statsData, gapsData]) => {
|
|
setStats(statsData);
|
|
setGaps(gapsData.gaps.sort((a: Gap, b: Gap) => b.length - a.length));
|
|
setLoading(false);
|
|
});
|
|
}, []);
|
|
|
|
if (loading) {
|
|
return (
|
|
<div style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, background: '#0f172a', zIndex: 3000, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff' }}>
|
|
<div>Chargement des statistiques H5…</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, background: '#0f172a', zIndex: 3000, display: 'flex', flexDirection: 'column', overflowY: 'auto' }}>
|
|
<div style={{ padding: '20px', background: '#1e293b', borderBottom: '1px solid #334155', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
<h2 style={{ margin: 0, color: '#f8fafc' }}>📊 H5 Data Coverage</h2>
|
|
<button onClick={onClose} style={{ background: '#ef4444', color: '#fff', border: 'none', padding: '8px 20px', borderRadius: '4px', cursor: 'pointer' }}>Fermer</button>
|
|
</div>
|
|
|
|
<div style={{ flex: 1, padding: '20px', maxWidth: '1200px', margin: '0 auto', width: '100%' }}>
|
|
{/* Stats globales */}
|
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: '15px', marginBottom: '30px' }}>
|
|
<div style={{ background: '#1e293b', padding: '20px', borderRadius: '8px', border: '1px solid #334155' }}>
|
|
<div style={{ fontSize: '0.8rem', color: '#94a3b8', marginBottom: '5px' }}>Positions totales</div>
|
|
<div style={{ fontSize: '2rem', fontWeight: 'bold', color: '#f8fafc' }}>{stats?.total_positions}</div>
|
|
</div>
|
|
|
|
<div style={{ background: '#1e293b', padding: '20px', borderRadius: '8px', border: '1px solid #334155' }}>
|
|
<div style={{ fontSize: '0.8rem', color: '#94a3b8', marginBottom: '5px' }}>Avec données</div>
|
|
<div style={{ fontSize: '2rem', fontWeight: 'bold', color: '#4ade80' }}>{stats?.with_data}</div>
|
|
</div>
|
|
|
|
<div style={{ background: '#1e293b', padding: '20px', borderRadius: '8px', border: '1px solid #334155' }}>
|
|
<div style={{ fontSize: '0.8rem', color: '#94a3b8', marginBottom: '5px' }}>Manquantes</div>
|
|
<div style={{ fontSize: '2rem', fontWeight: 'bold', color: '#ef4444' }}>{stats?.missing}</div>
|
|
</div>
|
|
|
|
<div style={{ background: '#1e293b', padding: '20px', borderRadius: '8px', border: '1px solid #334155' }}>
|
|
<div style={{ fontSize: '0.8rem', color: '#94a3b8', marginBottom: '5px' }}>Coverage</div>
|
|
<div style={{ fontSize: '2rem', fontWeight: 'bold', color: stats && stats.coverage_pct > 50 ? '#4ade80' : '#fbbf24' }}>{stats?.coverage_pct}%</div>
|
|
</div>
|
|
|
|
<div style={{ background: '#1e293b', padding: '20px', borderRadius: '8px', border: '1px solid #334155' }}>
|
|
<div style={{ fontSize: '0.8rem', color: '#94a3b8', marginBottom: '5px' }}>Fichiers totaux</div>
|
|
<div style={{ fontSize: '2rem', fontWeight: 'bold', color: '#38bdf8' }}>{stats?.total_files}</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Progress bar */}
|
|
<div style={{ background: '#1e293b', padding: '20px', borderRadius: '8px', border: '1px solid #334155', marginBottom: '30px' }}>
|
|
<div style={{ fontSize: '0.9rem', color: '#94a3b8', marginBottom: '10px' }}>Progression du déploiement</div>
|
|
<div style={{ background: '#0f172a', height: '30px', borderRadius: '15px', overflow: 'hidden', position: 'relative' }}>
|
|
<div style={{
|
|
background: 'linear-gradient(90deg, #4ade80, #22c55e)',
|
|
height: '100%',
|
|
width: `${stats?.coverage_pct}%`,
|
|
transition: 'width 1s ease'
|
|
}}></div>
|
|
<div style={{ position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', color: '#fff', fontWeight: 'bold', fontSize: '0.9rem' }}>
|
|
{stats?.with_data} / {stats?.total_positions} positions
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Top 10 gaps */}
|
|
<div style={{ background: '#1e293b', padding: '20px', borderRadius: '8px', border: '1px solid #334155' }}>
|
|
<h3 style={{ margin: '0 0 15px 0', color: '#f8fafc' }}>🔍 Top 10 Gaps (plages manquantes)</h3>
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
|
|
{gaps.slice(0, 10).map((gap, i) => (
|
|
<div key={i} style={{ background: '#0f172a', padding: '12px', borderRadius: '6px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
<div>
|
|
<span style={{ color: '#f8fafc', fontWeight: 'bold' }}>b{gap.start}</span>
|
|
<span style={{ color: '#94a3b8', margin: '0 8px' }}>→</span>
|
|
<span style={{ color: '#f8fafc', fontWeight: 'bold' }}>b{gap.end}</span>
|
|
</div>
|
|
<div style={{
|
|
background: gap.length > 50 ? '#dc2626' : gap.length > 20 ? '#f59e0b' : '#3b82f6',
|
|
color: '#fff',
|
|
padding: '4px 12px',
|
|
borderRadius: '12px',
|
|
fontSize: '0.85rem',
|
|
fontWeight: 'bold'
|
|
}}>
|
|
{gap.length} positions
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{gaps.length > 10 && (
|
|
<div style={{ marginTop: '15px', textAlign: 'center', color: '#94a3b8', fontSize: '0.85rem' }}>
|
|
… et {gaps.length - 10} autres gaps
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Verdict */}
|
|
<div style={{
|
|
background: stats && stats.coverage_pct < 50 ? '#7f1d1d' : '#065f46',
|
|
padding: '20px',
|
|
borderRadius: '8px',
|
|
marginTop: '30px',
|
|
border: stats && stats.coverage_pct < 50 ? '1px solid #dc2626' : '1px solid #10b981'
|
|
}}>
|
|
<div style={{ fontSize: '1.1rem', fontWeight: 'bold', color: '#fff', marginBottom: '10px' }}>
|
|
{stats && stats.coverage_pct < 50 ? '⚠️ Déploiement partiel détecté' : '✅ Coverage acceptable'}
|
|
</div>
|
|
<div style={{ color: '#e5e7eb', fontSize: '0.9rem' }}>
|
|
{stats && stats.coverage_pct < 50
|
|
? `Seulement ${stats.coverage_pct}% des positions planifiées ont des données. Cela suggère un test de déploiement plutôt qu'une collecte complète.`
|
|
: `${stats?.coverage_pct}% des positions déployées avec succès.`
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default H5Coverage;
|