Fix coverage: add /api/coverage route, remove stray gather code from loadCoverage
This commit is contained in:
241
frontend_src/components/CampaignDocs.tsx
Normal file
241
frontend_src/components/CampaignDocs.tsx
Normal file
@@ -0,0 +1,241 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
interface TimelineEvent {
|
||||
date: string;
|
||||
event: string;
|
||||
type: 'start' | 'data' | 'milestone' | 'operation' | 'end';
|
||||
}
|
||||
|
||||
interface Document {
|
||||
name: string;
|
||||
file: string;
|
||||
category: string;
|
||||
}
|
||||
|
||||
interface CampaignManifest {
|
||||
timeline: TimelineEvent[];
|
||||
documents: Document[];
|
||||
}
|
||||
|
||||
export default function CampaignDocs() {
|
||||
const [manifest, setManifest] = useState<CampaignManifest | null>(null);
|
||||
const [activeTab, setActiveTab] = useState<'timeline' | 'docs'>('timeline');
|
||||
|
||||
useEffect(() => {
|
||||
fetch('/seismic/api/docs/manifest')
|
||||
.then(r => r.json())
|
||||
.then(setManifest)
|
||||
.catch(console.error);
|
||||
}, []);
|
||||
|
||||
if (!manifest) return <div style={{ padding: '20px', color: '#64748b' }}>Chargement...</div>;
|
||||
|
||||
const typeColors: Record<TimelineEvent['type'], string> = {
|
||||
start: '#10b981',
|
||||
data: '#3b82f6',
|
||||
milestone: '#f59e0b',
|
||||
operation: '#8b5cf6',
|
||||
end: '#ef4444'
|
||||
};
|
||||
|
||||
const typeIcons: Record<TimelineEvent['type'], string> = {
|
||||
start: '🚀',
|
||||
data: '📊',
|
||||
milestone: '🎯',
|
||||
operation: '⚙️',
|
||||
end: '🏁'
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ padding: '20px', maxWidth: '1200px', margin: '0 auto' }}>
|
||||
{/* Header */}
|
||||
<div style={{ marginBottom: '30px' }}>
|
||||
<h1 style={{ fontSize: '2rem', fontWeight: 'bold', color: '#1e293b', marginBottom: '10px' }}>
|
||||
📚 Campagne SeaKESP - Sète 2020
|
||||
</h1>
|
||||
<p style={{ color: '#64748b', fontSize: '1.1rem' }}>
|
||||
Documentation, chronologie et ressources de la campagne OBN
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div style={{ display: 'flex', gap: '10px', marginBottom: '20px', borderBottom: '2px solid #e2e8f0' }}>
|
||||
<button
|
||||
onClick={() => setActiveTab('timeline')}
|
||||
style={{
|
||||
padding: '10px 20px',
|
||||
background: activeTab === 'timeline' ? '#3b82f6' : 'transparent',
|
||||
color: activeTab === 'timeline' ? 'white' : '#64748b',
|
||||
border: 'none',
|
||||
borderRadius: '8px 8px 0 0',
|
||||
cursor: 'pointer',
|
||||
fontWeight: 'bold',
|
||||
fontSize: '1rem'
|
||||
}}
|
||||
>
|
||||
📅 Chronologie
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('docs')}
|
||||
style={{
|
||||
padding: '10px 20px',
|
||||
background: activeTab === 'docs' ? '#3b82f6' : 'transparent',
|
||||
color: activeTab === 'docs' ? 'white' : '#64748b',
|
||||
border: 'none',
|
||||
borderRadius: '8px 8px 0 0',
|
||||
cursor: 'pointer',
|
||||
fontWeight: 'bold',
|
||||
fontSize: '1rem'
|
||||
}}
|
||||
>
|
||||
📄 Documents
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Timeline Tab */}
|
||||
{activeTab === 'timeline' && (
|
||||
<div style={{ background: 'white', padding: '30px', borderRadius: '12px', boxShadow: '0 2px 8px rgba(0,0,0,0.1)' }}>
|
||||
<h2 style={{ fontSize: '1.5rem', fontWeight: 'bold', color: '#1e293b', marginBottom: '20px' }}>
|
||||
Chronologie de la campagne
|
||||
</h2>
|
||||
|
||||
<div style={{ position: 'relative', paddingLeft: '40px' }}>
|
||||
{/* Timeline line */}
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
left: '15px',
|
||||
top: '10px',
|
||||
bottom: '10px',
|
||||
width: '3px',
|
||||
background: 'linear-gradient(to bottom, #10b981, #ef4444)'
|
||||
}} />
|
||||
|
||||
{manifest.timeline.map((event, idx) => (
|
||||
<div key={idx} style={{ position: 'relative', marginBottom: '25px' }}>
|
||||
{/* Timeline dot */}
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
left: '-30px',
|
||||
width: '15px',
|
||||
height: '15px',
|
||||
borderRadius: '50%',
|
||||
background: typeColors[event.type],
|
||||
border: '3px solid white',
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.2)'
|
||||
}} />
|
||||
|
||||
{/* Event card */}
|
||||
<div style={{
|
||||
background: '#f8fafc',
|
||||
padding: '15px',
|
||||
borderRadius: '8px',
|
||||
border: `2px solid ${typeColors[event.type]}`,
|
||||
marginLeft: '10px'
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '10px', marginBottom: '5px' }}>
|
||||
<span style={{ fontSize: '1.5rem' }}>{typeIcons[event.type]}</span>
|
||||
<span style={{ fontSize: '0.9rem', fontWeight: 'bold', color: typeColors[event.type] }}>
|
||||
{event.date}
|
||||
</span>
|
||||
</div>
|
||||
<p style={{ fontSize: '1rem', color: '#334155', margin: 0 }}>
|
||||
{event.event}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Stats */}
|
||||
<div style={{ marginTop: '30px', display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: '15px' }}>
|
||||
<div style={{ background: '#f0f9ff', padding: '15px', borderRadius: '8px', textAlign: 'center' }}>
|
||||
<div style={{ fontSize: '2rem', fontWeight: 'bold', color: '#3b82f6' }}>46 jours</div>
|
||||
<div style={{ color: '#64748b', fontSize: '0.9rem' }}>Durée totale</div>
|
||||
</div>
|
||||
<div style={{ background: '#f0fdf4', padding: '15px', borderRadius: '8px', textAlign: 'center' }}>
|
||||
<div style={{ fontSize: '2rem', fontWeight: 'bold', color: '#10b981' }}>345 fichiers</div>
|
||||
<div style={{ color: '#64748b', fontSize: '0.9rem' }}>RAW collectés</div>
|
||||
</div>
|
||||
<div style={{ background: '#fef3c7', padding: '15px', borderRadius: '8px', textAlign: 'center' }}>
|
||||
<div style={{ fontSize: '2rem', fontWeight: 'bold', color: '#f59e0b' }}>~800h</div>
|
||||
<div style={{ color: '#64748b', fontSize: '0.9rem' }}>Enregistrements</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Documents Tab */}
|
||||
{activeTab === 'docs' && (
|
||||
<div style={{ background: 'white', padding: '30px', borderRadius: '12px', boxShadow: '0 2px 8px rgba(0,0,0,0.1)' }}>
|
||||
<h2 style={{ fontSize: '1.5rem', fontWeight: 'bold', color: '#1e293b', marginBottom: '20px' }}>
|
||||
Documents de référence
|
||||
</h2>
|
||||
|
||||
{['Sources', 'Acquisition', 'Specifications', 'Geometry', 'Maps'].map(category => {
|
||||
const categoryDocs = manifest.documents.filter(d => d.category === category);
|
||||
if (categoryDocs.length === 0) return null;
|
||||
|
||||
const categoryIcons: Record<string, string> = {
|
||||
Sources: '🎺',
|
||||
Acquisition: '📋',
|
||||
Specifications: '📖',
|
||||
Geometry: '🗺️',
|
||||
Maps: '🗺️'
|
||||
};
|
||||
|
||||
return (
|
||||
<div key={category} style={{ marginBottom: '25px' }}>
|
||||
<h3 style={{ fontSize: '1.2rem', fontWeight: 'bold', color: '#475569', marginBottom: '10px', display: 'flex', alignItems: 'center', gap: '10px' }}>
|
||||
<span>{categoryIcons[category]}</span>
|
||||
{category}
|
||||
</h3>
|
||||
<div style={{ display: 'grid', gap: '10px' }}>
|
||||
{categoryDocs.map((doc, idx) => (
|
||||
<a
|
||||
key={idx}
|
||||
href={`/seismic/api/docs/${doc.file}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '15px',
|
||||
padding: '15px',
|
||||
background: '#f8fafc',
|
||||
border: '1px solid #e2e8f0',
|
||||
borderRadius: '8px',
|
||||
textDecoration: 'none',
|
||||
color: '#334155',
|
||||
transition: 'all 0.2s'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.background = '#f1f5f9';
|
||||
e.currentTarget.style.borderColor = '#3b82f6';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.background = '#f8fafc';
|
||||
e.currentTarget.style.borderColor = '#e2e8f0';
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: '1.5rem' }}>
|
||||
{doc.file.endsWith('.pdf') ? '📄' :
|
||||
doc.file.endsWith('.xlsx') || doc.file.endsWith('.xlsm') ? '📊' :
|
||||
doc.file.endsWith('.docx') ? '📝' :
|
||||
doc.file.endsWith('.png') ? '🖼️' : '📁'}
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ fontWeight: 'bold', marginBottom: '3px' }}>{doc.name}</div>
|
||||
<div style={{ fontSize: '0.85rem', color: '#64748b' }}>{doc.file}</div>
|
||||
</div>
|
||||
<div style={{ color: '#3b82f6', fontSize: '1.2rem' }}>→</div>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user