Files
seisee/frontend_src/components/H5Dashboard.tsx

247 lines
10 KiB
TypeScript

import React, { useState, useEffect } from 'react';
interface H5File {
filename: string;
nodeId: string;
date: string;
}
interface WaveformData {
samples: number[];
sample_rate: number;
duration_sec: number;
channel: number;
channel_name: string;
unit: string;
stats: {
min: number;
max: number;
mean: number;
std: number;
rms: number;
};
}
export default function H5Dashboard() {
const [files, setFiles] = useState<H5File[]>([]);
const [selectedFile, setSelectedFile] = useState<string | null>(null);
const [waveformData, setWaveformData] = useState<WaveformData | null>(null);
const [channel, setChannel] = useState(1);
const [loading, setLoading] = useState(false);
const [startTime, setStartTime] = useState(0);
const [duration, setDuration] = useState(10);
const [globalView, setGlobalView] = useState(false);
useEffect(() => {
fetch('/seismic/api/h5/files')
.then(r => r.json())
.then(data => setFiles(data.files || []))
.catch(console.error);
}, []);
const loadWaveform = async (file: string, ch: number, start: number, dur: number, global: boolean = false) => {
setLoading(true);
try {
const params = new URLSearchParams({
file,
channel: String(ch),
start: String(start),
duration: global ? '0' : String(dur)
});
const response = await fetch(`/seismic/api/h5/data?${params}`);
const data = await response.json();
setWaveformData(data);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};
const handleFileSelect = (file: string) => {
setSelectedFile(file);
setGlobalView(false);
loadWaveform(file, channel, startTime, duration, false);
};
const handleChannelChange = (ch: number) => {
setChannel(ch);
if (selectedFile) loadWaveform(selectedFile, ch, startTime, duration, globalView);
};
const handleGlobalView = () => {
if (selectedFile) {
setGlobalView(true);
loadWaveform(selectedFile, channel, 0, 0, true);
}
};
return (
<div style={{ padding: '20px', maxWidth: '1400px', margin: '0 auto' }}>
{/* Header */}
<div style={{ marginBottom: '30px' }}>
<h1 style={{ fontSize: '2rem', fontWeight: 'bold', color: '#1e293b', marginBottom: '10px' }}>
🎯 Dashboard H5 - Visualisation Waveforms
</h1>
<p style={{ color: '#64748b', fontSize: '1.1rem' }}>
{files.length} fichiers H5 calibrés disponibles
</p>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '300px 1fr', gap: '20px' }}>
{/* Sidebar - Liste des fichiers */}
<div style={{ background: 'white', padding: '20px', borderRadius: '12px', boxShadow: '0 2px 8px rgba(0,0,0,0.1)', maxHeight: '80vh', overflowY: 'auto' }}>
<h2 style={{ fontSize: '1.2rem', fontWeight: 'bold', color: '#1e293b', marginBottom: '15px' }}>
📁 Fichiers H5
</h2>
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
{files.map((file, idx) => (
<button
key={idx}
onClick={() => handleFileSelect(file.filename)}
style={{
textAlign: 'left',
padding: '12px',
background: selectedFile === file.filename ? '#3b82f6' : '#f8fafc',
color: selectedFile === file.filename ? 'white' : '#334155',
border: selectedFile === file.filename ? '2px solid #2563eb' : '1px solid #e2e8f0',
borderRadius: '8px',
cursor: 'pointer',
fontSize: '0.85rem',
transition: 'all 0.2s'
}}
onMouseEnter={(e) => {
if (selectedFile !== file.filename) {
e.currentTarget.style.background = '#f1f5f9';
}
}}
onMouseLeave={(e) => {
if (selectedFile !== file.filename) {
e.currentTarget.style.background = '#f8fafc';
}
}}
>
<div style={{ fontWeight: 'bold', marginBottom: '4px' }}>Node {file.nodeId}</div>
<div style={{ fontSize: '0.75rem', opacity: 0.8 }}>{file.date}</div>
</button>
))}
</div>
</div>
{/* Main content - Waveform */}
<div style={{ background: 'white', padding: '20px', borderRadius: '12px', boxShadow: '0 2px 8px rgba(0,0,0,0.1)' }}>
{!selectedFile && (
<div style={{ textAlign: 'center', padding: '60px', color: '#64748b' }}>
<div style={{ fontSize: '3rem', marginBottom: '20px' }}>📊</div>
<p style={{ fontSize: '1.2rem' }}>Sélectionnez un fichier H5 pour visualiser les waveforms</p>
</div>
)}
{selectedFile && (
<>
{/* Controls */}
<div style={{ marginBottom: '20px', display: 'flex', gap: '15px', flexWrap: 'wrap', alignItems: 'center' }}>
<div>
<label style={{ fontSize: '0.9rem', fontWeight: 'bold', color: '#475569', display: 'block', marginBottom: '5px' }}>
Canal :
</label>
<div style={{ display: 'flex', gap: '5px' }}>
{[1, 2, 3, 4].map(ch => (
<button
key={ch}
onClick={() => handleChannelChange(ch)}
style={{
padding: '8px 12px',
background: channel === ch ? '#3b82f6' : '#f1f5f9',
color: channel === ch ? 'white' : '#334155',
border: 'none',
borderRadius: '6px',
cursor: 'pointer',
fontWeight: 'bold'
}}
>
{ch <= 3 ? `Geo ${ch}` : 'Hydro'}
</button>
))}
</div>
</div>
<button
onClick={handleGlobalView}
disabled={loading}
style={{
padding: '8px 16px',
background: globalView ? '#10b981' : '#8b5cf6',
color: 'white',
border: 'none',
borderRadius: '6px',
cursor: loading ? 'wait' : 'pointer',
fontWeight: 'bold'
}}
>
🌍 Vue Globale
</button>
</div>
{/* Waveform Display */}
{loading && (
<div style={{ textAlign: 'center', padding: '40px', color: '#64748b' }}>
<div style={{ fontSize: '2rem', marginBottom: '10px' }}></div>
<p>Chargement des données...</p>
</div>
)}
{waveformData && !loading && (
<div>
{/* Info bar */}
<div style={{ background: '#f8fafc', padding: '15px', borderRadius: '8px', marginBottom: '20px', display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))', gap: '10px' }}>
<div>
<div style={{ fontSize: '0.75rem', color: '#64748b', marginBottom: '3px' }}>Canal</div>
<div style={{ fontWeight: 'bold', color: '#1e293b' }}>{waveformData.channel_name}</div>
</div>
<div>
<div style={{ fontSize: '0.75rem', color: '#64748b', marginBottom: '3px' }}>Unité</div>
<div style={{ fontWeight: 'bold', color: '#1e293b' }}>{waveformData.unit}</div>
</div>
<div>
<div style={{ fontSize: '0.75rem', color: '#64748b', marginBottom: '3px' }}>Durée</div>
<div style={{ fontWeight: 'bold', color: '#1e293b' }}>{waveformData.duration_sec.toFixed(1)} s</div>
</div>
<div>
<div style={{ fontSize: '0.75rem', color: '#64748b', marginBottom: '3px' }}>Échantillons</div>
<div style={{ fontWeight: 'bold', color: '#1e293b' }}>{waveformData.samples.length}</div>
</div>
</div>
{/* Stats */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(100px, 1fr))', gap: '10px', marginBottom: '20px' }}>
{Object.entries(waveformData.stats).map(([key, value]) => (
<div key={key} style={{ background: '#f0f9ff', padding: '10px', borderRadius: '6px', textAlign: 'center' }}>
<div style={{ fontSize: '0.75rem', color: '#0369a1', marginBottom: '3px', textTransform: 'uppercase' }}>{key}</div>
<div style={{ fontWeight: 'bold', color: '#0c4a6e', fontSize: '0.9rem' }}>{value.toExponential(3)}</div>
</div>
))}
</div>
{/* Waveform visualization (simple text representation for now) */}
<div style={{ background: '#0f172a', padding: '20px', borderRadius: '8px', color: '#94a3b8', fontFamily: 'monospace', fontSize: '0.75rem', overflowX: 'auto' }}>
<div style={{ marginBottom: '10px', color: '#f1f5f9', fontWeight: 'bold' }}>
Waveform Preview (premiers échantillons) :
</div>
<pre style={{ margin: 0 }}>
{waveformData.samples.slice(0, 50).map((v, i) =>
`[${i.toString().padStart(4, '0')}] ${v.toExponential(4)} ${waveformData.unit}`
).join('\n')}
{waveformData.samples.length > 50 && `\n... (${waveformData.samples.length - 50} échantillons restants)`}
</pre>
</div>
</div>
)}
</>
)}
</div>
</div>
</div>
);
}