GreenPlusbyGXS / web /src /pages /BiodiversityEar.js
gaialive's picture
Upload BiodiversityEar.js
05e6791 verified
import React, { useState, useRef, useEffect } from 'react';
// The 'analyzeAudioForSpecies' utility is no longer used as analysis is now handled by the backend.
import { biodiversityDB } from '../utils/biodiversityDatabase';
// --- Child Component for displaying overall statistics ---
function BiodiversityStats({ recordings }) {
if (!recordings || recordings.length === 0) {
return (
<div style={{ textAlign: 'center', padding: '20px', color: '#666' }}>
No recordings available for statistics
</div>
);
}
const totalSpecies = new Set(
recordings.flatMap(r => r.detectedSpecies?.map(s => s.name) || [])
).size;
const avgBiodiversity = recordings.reduce((sum, r) =>
sum + (r.biodiversityMetrics?.biodiversityScore || 0), 0
) / recordings.length;
const ecosystemHealthCounts = recordings.reduce((acc, r) => {
const health = r.biodiversityMetrics?.ecosystemHealth || 'Unknown';
acc[health] = (acc[health] || 0) + 1;
return acc;
}, {});
return (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: '20px' }}>
<div style={{ textAlign: 'center', padding: '20px', background: '#e8f5e8', borderRadius: '8px' }}>
<div style={{ fontSize: '2rem', color: '#2E7D32', marginBottom: '10px' }}>{totalSpecies}</div>
<div style={{ fontWeight: 'bold' }}>Unique Species</div>
</div>
<div style={{ textAlign: 'center', padding: '20px', background: '#e3f2fd', borderRadius: '8px' }}>
<div style={{ fontSize: '2rem', color: '#1976d2', marginBottom: '10px' }}>{Math.round(avgBiodiversity)}</div>
<div style={{ fontWeight: 'bold' }}>Avg Biodiversity Score</div>
</div>
<div style={{ textAlign: 'center', padding: '20px', background: '#fff3e0', borderRadius: '8px' }}>
<div style={{ fontSize: '2rem', color: '#F57C00', marginBottom: '10px' }}>{recordings.length}</div>
<div style={{ fontWeight: 'bold' }}>Total Recordings</div>
</div>
<div style={{ textAlign: 'center', padding: '20px', background: '#fce4ec', borderRadius: '8px' }}>
<div style={{ fontSize: '1.2rem', color: '#C2185B', marginBottom: '10px' }}>
{Object.entries(ecosystemHealthCounts).map(([health, count]) => (
<div key={health}>{health}: {count}</div>
))}
</div>
<div style={{ fontWeight: 'bold' }}>Ecosystem Health</div>
</div>
</div>
);
}
// --- Main BiodiversityEar Component ---
function BiodiversityEar({ onActivityComplete }) {
// State hooks for managing component data and UI status
const [isRecording, setIsRecording] = useState(false);
const [audioData, setAudioData] = useState(null); // URL for the audio player
const [analysis, setAnalysis] = useState(null); // Stores results from the backend
const [isAnalyzing, setIsAnalyzing] = useState(false); // Controls loading indicators
const [recordings, setRecordings] = useState([]); // List of past recordings
const [selectedHabitat, setSelectedHabitat] = useState('');
const [selectedRegion, setSelectedRegion] = useState('North America');
const [uploadedFile, setUploadedFile] = useState(null); // The actual audio file object
const [userLocation, setUserLocation] = useState(null);
const [dragActive, setDragActive] = useState(false); // For drag-and-drop UI
// Refs for DOM elements and media recorder instance
const mediaRecorderRef = useRef(null);
const audioChunksRef = useRef([]);
const fileInputRef = useRef(null);
const habitats = [
"Urban Park", "Forest Edge", "Wetland/Marsh", "Suburban Garden",
"Agricultural Field", "Coastal Area", "Mountain Trail", "Desert Scrub",
"Backyard", "Nature Reserve", "Riverside", "Prairie Grassland"
];
const regions = ["North America", "Europe", "Asia", "South America", "Africa", "Australia"];
// Effect hook to run on component mount
useEffect(() => {
// Fetches initial data like past recordings and user location
const initializeApp = async () => {
try {
await biodiversityDB.init();
const savedRecordings = await biodiversityDB.getAllRecordings(50);
setRecordings(savedRecordings);
} catch (error) {
console.warn('Database initialization failed:', error);
}
};
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
(position) => setUserLocation({
latitude: position.coords.latitude,
longitude: position.coords.longitude
}),
(error) => console.log('Location access denied:', error)
);
}
initializeApp();
}, []);
// --- Core Component Methods ---
const startRecording = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
mediaRecorderRef.current = new MediaRecorder(stream, { mimeType: 'audio/webm' });
audioChunksRef.current = [];
mediaRecorderRef.current.ondataavailable = (event) => {
audioChunksRef.current.push(event.data);
};
mediaRecorderRef.current.onstop = () => {
const audioBlob = new Blob(audioChunksRef.current, { type: 'audio/webm' });
const audioUrl = URL.createObjectURL(audioBlob);
setAudioData(audioUrl);
setUploadedFile(audioBlob); // Set the file object for upload
stream.getTracks().forEach(track => track.stop());
};
mediaRecorderRef.current.start();
setIsRecording(true);
} catch (error) {
console.error('Error accessing microphone:', error);
alert('Unable to access microphone. Please check permissions.');
}
};
const stopRecording = () => {
if (mediaRecorderRef.current) {
mediaRecorderRef.current.stop();
setIsRecording(false);
}
};
const handleFileUpload = (event) => {
const file = event.target.files[0];
if (file && file.type.startsWith('audio/')) {
const audioUrl = URL.createObjectURL(file);
setAudioData(audioUrl);
setUploadedFile(file); // Set the file object for upload
}
};
// Drag and drop event handlers
const handleDrop = (e) => {
e.preventDefault();
e.stopPropagation();
setDragActive(false);
if (e.dataTransfer.files && e.dataTransfer.files[0]) {
handleFileUpload({ target: { files: e.dataTransfer.files } });
}
};
const handleDragOver = (e) => { e.preventDefault(); e.stopPropagation(); setDragActive(true); };
const handleDragLeave = (e) => { e.preventDefault(); e.stopPropagation(); setDragActive(false); };
const analyzeAudio = async () => {
// Ensure a file has been uploaded or recorded before proceeding.
if (!uploadedFile) {
alert('No valid audio file is ready for analysis.');
return;
}
setIsAnalyzing(true);
setAnalysis(null); // Clear previous results to prepare for new analysis.
try {
// Create a FormData object to package the file for HTTP transport.
const formData = new FormData();
formData.append('audioFile', uploadedFile); // 'audioFile' must match the key expected by the server.
formData.append('region', selectedRegion);
formData.append('habitat', selectedHabitat);
// Send the audio file to the backend server for processing.
// The server is expected to be running on localhost:5000.
const response = await fetch('http://localhost:5000/api/analyze-audio', {
method: 'POST',
body: formData,
});
// Handle non-successful HTTP responses.
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || 'The analysis server returned an error.');
}
// Parse the JSON results returned from the backend.
const analysisResults = await response.json();
// Assemble the final analysis object for state and UI rendering.
const finalAnalysis = {
id: biodiversityDB.generateId(),
timestamp: new Date().toISOString(),
location: userLocation,
habitat: selectedHabitat || 'Unknown',
region: selectedRegion,
...analysisResults // Spread the results from the backend (species, metrics, etc.)
};
setAnalysis(finalAnalysis);
// Log activity for environmental impact tracking
try {
const { authManager } = await import('../utils/auth');
await authManager.logActivity('Biodiversity scan completed', {
type: 'biodiversity_scan',
region: selectedRegion,
speciesCount: finalAnalysis.speciesDetected?.length || 0,
ecosystemHealth: finalAnalysis.biodiversityMetrics?.ecosystemHealth || 'Unknown',
points: 25,
amount: 1 // For biodiversityScans counter
});
console.log('✅ Biodiversity scan activity logged successfully');
} catch (error) {
console.warn('Failed to log biodiversity activity:', error);
}
// Persist the results to the local client-side database.
try {
await biodiversityDB.saveRecording(finalAnalysis);
// If an onActivityComplete callback is provided, call it to update parent state.
if (onActivityComplete) {
onActivityComplete({ type: 'bio_scan' });
}
} catch (dbError) {
console.warn('Failed to save recording to local DB:', dbError);
}
// Refresh the list of recent recordings from the database.
const updatedRecordings = await biodiversityDB.getAllRecordings(50);
setRecordings(updatedRecordings);
} catch (error) {
console.error('An error occurred during the analysis process:', error);
alert(`Analysis Failed: ${error.message}. Please check the connection to the server and try again.`);
} finally {
// Ensure the loading state is turned off, regardless of success or failure.
setIsAnalyzing(false);
}
};
// Helper functions for styling the results dynamically
const getQualityColor = (ecosystemHealth) => {
switch (ecosystemHealth) {
case 'Excellent': return '#4CAF50';
case 'Good': return '#8BC34A';
case 'Fair': return '#FF9800';
case 'Poor': return '#f44336';
default: return '#666';
}
};
const getConservationColor = (status) => {
switch (status) {
case 'Least Concern': return '#4CAF50';
case 'Threatened': return '#f44336';
default: return '#666';
}
};
// --- JSX Rendering ---
return (
<div className="container">
{/* The entire UI structure from your original file is preserved below */}
<div style={{ textAlign: 'center', marginBottom: '40px' }}>
<h2 style={{ fontSize: '3.5rem', color: '#2E7D32', marginBottom: '10px' }}>
🎧 BiodiversityEar: AI Ecosystem Monitoring
</h2>
<p style={{ fontSize: '1.3rem', color: '#666', marginBottom: '15px' }}>
Mapping ecosystem health through the acoustic fingerprint of nature
</p>
<div style={{
background: 'linear-gradient(135deg, #2E7D32 0%, #4CAF50 100%)',
color: 'white',
padding: '15px 30px',
borderRadius: '25px',
display: 'inline-block',
fontSize: '1rem',
fontWeight: 'bold'
}}>
🌍 Real-Time Ecosystem Monitoring • 🤖 AI Species Detection • 📊 Live Biodiversity Mapping
</div>
</div>
<div className="card" style={{
marginBottom: '30px',
background: 'linear-gradient(135deg, #ffebee 0%, #ffcdd2 100%)',
border: '2px solid #f44336'
}}>
<h3 style={{ color: '#d32f2f', marginBottom: '15px' }}>🚨 The Biodiversity Crisis</h3>
<p style={{ fontSize: '1.1rem', lineHeight: '1.6', marginBottom: '15px' }}>
<strong>Traditional biodiversity tracking is slow, expensive, and requires expert biologists on-site.</strong>
We have no real-time, large-scale way to know if an ecosystem is recovering or collapsing.
</p>
<div style={{
background: 'rgba(244, 67, 54, 0.1)',
padding: '15px',
borderRadius: '8px',
fontSize: '0.95rem'
}}>
• Ecosystem monitoring takes months or years to show results<br />
• Requires expensive equipment and trained specialists<br />
• No global, real-time view of biodiversity health<br />
• Climate change is accelerating faster than our ability to track it
</div>
</div>
<div className="card" style={{
marginBottom: '30px',
background: 'linear-gradient(135deg, #e8f5e8 0%, #c8e6c9 100%)',
border: '2px solid #4CAF50'
}}>
<h3 style={{ color: '#2E7D32', marginBottom: '20px' }}>🎯 The BiodiversityEar Revolution</h3>
<div style={{
background: 'white',
padding: '20px',
borderRadius: '12px',
marginBottom: '20px',
border: '1px solid #4CAF50'
}}>
<h4 style={{ color: '#2E7D32', marginBottom: '10px' }}>💡 The "One-of-a-Kind" Insight:</h4>
<p style={{ fontSize: '1.1rem', lineHeight: '1.6' }}>
<strong>Every ecosystem has a unique "soundscape"</strong>—a combination of birdsong, insect calls,
frog croaks, and animal movements. BiodiversityEar uses machine learning to listen to this symphony
and identify its players, creating a real-time acoustic map of planetary health.
</p>
</div>
<div className="grid grid-4" style={{ marginTop: '20px' }}>
<div style={{ textAlign: 'center', padding: '20px' }}>
<div style={{ fontSize: '3rem', marginBottom: '15px' }}>🎤</div>
<h4 style={{ color: '#2E7D32', marginBottom: '10px' }}>1. Record</h4>
<p style={{ color: '#666' }}>Capture 30-second audio recordings anywhere</p>
</div>
<div style={{ textAlign: 'center', padding: '20px' }}>
<div style={{ fontSize: '3rem', marginBottom: '15px' }}>🤖</div>
<h4 style={{ color: '#2E7D32', marginBottom: '10px' }}>2. Analyze</h4>
<p style={{ color: '#666' }}>AI identifies species by acoustic signatures</p>
</div>
<div style={{ textAlign: 'center', padding: '20px' }}>
<div style={{ fontSize: '3rem', marginBottom: '15px' }}>📊</div>
<h4 style={{ color: '#2E7D32', marginBottom: '10px' }}>3. Assess</h4>
<p style={{ color: '#666' }}>Calculate biodiversity and ecosystem health</p>
</div>
<div style={{ textAlign: 'center', padding: '20px' }}>
<div style={{ fontSize: '3rem', marginBottom: '15px' }}>🗺️</div>
<h4 style={{ color: '#2E7D32', marginBottom: '10px' }}>4. Map</h4>
<p style={{ color: '#666' }}>Create real-time biodiversity hotspot maps</p>
</div>
</div>
</div>
<div className="card" style={{ marginBottom: '30px' }}>
<h3 style={{ color: '#2E7D32', marginBottom: '20px' }}>🌍 Recording Location</h3>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px' }}>
<div>
<label style={{ display: 'block', marginBottom: '10px', fontWeight: 'bold' }}>Region:</label>
<select
value={selectedRegion}
onChange={(e) => setSelectedRegion(e.target.value)}
style={{
width: '100%',
padding: '12px',
fontSize: '1rem',
borderRadius: '8px',
border: '2px solid #4CAF50'
}}
>
{regions.map(region => (
<option key={region} value={region}>{region}</option>
))}
</select>
</div>
<div>
<label style={{ display: 'block', marginBottom: '10px', fontWeight: 'bold' }}>Habitat:</label>
<select
value={selectedHabitat}
onChange={(e) => setSelectedHabitat(e.target.value)}
style={{
width: '100%',
padding: '12px',
fontSize: '1rem',
borderRadius: '8px',
border: '2px solid #4CAF50'
}}
>
<option value="">Choose habitat...</option>
{habitats.map(habitat => (
<option key={habitat} value={habitat}>{habitat}</option>
))}
</select>
</div>
</div>
{userLocation && (
<div style={{ marginTop: '15px', padding: '10px', background: '#e8f5e8', borderRadius: '8px', fontSize: '0.9rem' }}>
📍 Current Location: {userLocation.latitude.toFixed(4)}, {userLocation.longitude.toFixed(4)}
</div>
)}
</div>
<div className="card" style={{
marginBottom: '30px',
background: 'linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%)',
border: '2px solid #2196F3'
}}>
<h3 style={{ color: '#1976d2', marginBottom: '20px' }}>💡 Pro Tips for Better Bird Detection</h3>
<div className="grid grid-2" style={{ gap: '20px' }}>
<div>
<h4 style={{ color: '#1976d2', marginBottom: '10px' }}>🕐 Best Recording Times</h4>
<ul style={{ paddingLeft: '20px', fontSize: '0.9rem', lineHeight: '1.6' }}>
<li><strong>Dawn Chorus (5-7 AM):</strong> Peak bird activity</li>
<li><strong>Evening (6-8 PM):</strong> Second peak activity</li>
<li><strong>Spring/Summer:</strong> Breeding season = more songs</li>
<li><strong>Calm Weather:</strong> Avoid windy or rainy days</li>
</ul>
</div>
<div>
<h4 style={{ color: '#1976d2', marginBottom: '10px' }}>🎯 Recording Quality Tips</h4>
<ul style={{ paddingLeft: '20px', fontSize: '0.9rem', lineHeight: '1.6' }}>
<li><strong>Duration:</strong> Record for 30-60 seconds minimum</li>
<li><strong>Distance:</strong> Get within 50 feet of bird sounds</li>
<li><strong>Background:</strong> Minimize traffic, wind, human noise</li>
<li><strong>Format:</strong> Use high-quality audio settings</li>
</ul>
</div>
</div>
</div>
<div className="card" style={{ marginBottom: '30px' }}>
<h3 style={{ color: '#2E7D32', marginBottom: '20px' }}>🎤 Audio Recording</h3>
{!isRecording ? (
<>
<div style={{ textAlign: 'center', marginBottom: '20px' }}>
<div style={{ display: 'flex', gap: '15px', justifyContent: 'center', flexWrap: 'wrap' }}>
<button
onClick={startRecording}
style={{
fontSize: '1.2rem',
padding: '15px 30px',
background: 'linear-gradient(135deg, #2E7D32 0%, #4CAF50 100%)',
border: 'none',
borderRadius: '25px',
color: 'white',
fontWeight: 'bold',
cursor: 'pointer'
}}
>
🎤 Start Recording
</button>
<button
onClick={() => fileInputRef.current?.click()}
style={{
fontSize: '1.1rem',
padding: '15px 30px',
borderRadius: '25px',
background: '#FF9800',
color: 'white',
border: 'none',
fontWeight: 'bold',
cursor: 'pointer'
}}
>
📁 Upload Audio
</button>
</div>
</div>
<div
onDrop={handleDrop}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
style={{
border: `2px dashed ${dragActive ? '#4CAF50' : '#ccc'}`,
borderRadius: '12px',
padding: '40px 20px',
textAlign: 'center',
background: dragActive ? '#e8f5e8' : '#f9f9f9',
transition: 'all 0.3s ease',
cursor: 'pointer'
}}
onClick={() => fileInputRef.current?.click()}
>
<div style={{ fontSize: '3rem', marginBottom: '15px' }}>
{dragActive ? '📤' : '🎵'}
</div>
<p style={{ fontSize: '1.1rem', color: '#666', marginBottom: '10px' }}>
{dragActive ? 'Drop your audio file here!' : 'Drag & drop audio files here'}
</p>
<p style={{ fontSize: '0.9rem', color: '#999' }}>
Supports: MP3, WAV, M4A, OGG • Best results: 30+ seconds, minimal background noise
</p>
</div>
<input
ref={fileInputRef}
type="file"
accept="audio/*"
onChange={handleFileUpload}
style={{ display: 'none' }}
/>
</>
) : (
<div style={{ textAlign: 'center' }}>
<div style={{
fontSize: '4rem',
color: '#f44336',
marginBottom: '20px',
animation: 'pulse 1s infinite'
}}>
🔴
</div>
<button
onClick={stopRecording}
style={{
fontSize: '1.2rem',
padding: '15px 30px',
background: '#f44336',
color: 'white',
border: 'none',
borderRadius: '25px',
fontWeight: 'bold',
cursor: 'pointer'
}}
>
⏹️ Stop Recording
</button>
<p style={{ marginTop: '15px', color: '#666' }}>
Recording in progress... Capture at least 30 seconds for best results
</p>
</div>
)}
</div>
{audioData && (
<div className="card" style={{ marginBottom: '30px' }}>
<h3 style={{ color: '#2E7D32', marginBottom: '20px' }}>🎵 Audio Analysis</h3>
<div style={{ textAlign: 'center', marginBottom: '20px' }}>
<div style={{
background: '#f8f9fa',
padding: '20px',
borderRadius: '12px',
marginBottom: '20px'
}}>
<h4 style={{ marginBottom: '15px', color: '#2E7D32' }}>
🎵 {uploadedFile && uploadedFile.name ? 'Uploaded Audio' : 'Recorded Audio'}
</h4>
<audio
controls
src={audioData}
style={{
width: '100%',
maxWidth: '500px',
marginBottom: '15px'
}}
/>
{uploadedFile && uploadedFile.size && (
<div style={{ fontSize: '0.9rem', color: '#666' }}>
File Size: {(uploadedFile.size / 1024 / 1024).toFixed(2)} MB
</div>
)}
</div>
<button
onClick={analyzeAudio}
disabled={isAnalyzing}
style={{
fontSize: '1.2rem',
padding: '15px 40px',
background: isAnalyzing ? '#ccc' : 'linear-gradient(135deg, #FF9800 0%, #F57C00 100%)',
border: 'none',
borderRadius: '25px',
color: 'white',
fontWeight: 'bold',
cursor: isAnalyzing ? 'not-allowed' : 'pointer'
}}
>
{isAnalyzing ? '🤖 Analyzing Audio...' : '🔍 Analyze with AI'}
</button>
</div>
</div>
)}
{analysis && (
<div className="card" style={{ marginBottom: '30px' }}>
<h3 style={{ color: '#2E7D32', marginBottom: '20px' }}>📊 Biodiversity Analysis Results</h3>
<div style={{
background: `linear-gradient(135deg, ${getQualityColor(analysis.biodiversityMetrics.ecosystemHealth)}20 0%, ${getQualityColor(analysis.biodiversityMetrics.ecosystemHealth)}40 100%)`,
padding: '20px',
borderRadius: '12px',
marginBottom: '20px',
border: `2px solid ${getQualityColor(analysis.biodiversityMetrics.ecosystemHealth)}`
}}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '15px' }}>
<h4 style={{ color: getQualityColor(analysis.biodiversityMetrics.ecosystemHealth), margin: 0 }}>
Ecosystem Health: {analysis.biodiversityMetrics.ecosystemHealth}
</h4>
<div style={{
background: '#2E7D32',
color: 'white',
padding: '8px 16px',
borderRadius: '20px',
fontSize: '0.9rem',
fontWeight: 'bold'
}}>
Confidence: {Math.round(analysis.confidence)}%
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))', gap: '15px', fontSize: '0.9rem' }}>
<div><strong>Species Detected:</strong> {analysis.detectedSpecies.length}</div>
<div><strong>Biodiversity Score:</strong> {Math.round(analysis.biodiversityMetrics.biodiversityScore || 0)}</div>
<div><strong>Shannon Index:</strong> {analysis.biodiversityMetrics.shannonIndex}</div>
<div><strong>Audio Quality:</strong> {analysis.audioQuality}</div>
</div>
</div>
{analysis.detectedSpecies.length > 0 && (
<div style={{ marginBottom: '20px' }}>
<h4 style={{ color: '#2E7D32', marginBottom: '15px' }}>🐦 Detected Species</h4>
<div style={{ display: 'grid', gap: '15px' }}>
{analysis.detectedSpecies.map((species, index) => (
<div key={index} style={{
background: '#f9f9f9',
padding: '20px',
borderRadius: '12px',
border: '1px solid #ddd'
}}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '10px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<span style={{ fontSize: '2rem' }}>{species.icon}</span>
<div>
<h5 style={{ margin: 0, color: '#2E7D32' }}>{species.name}</h5>
<p style={{ margin: 0, fontSize: '0.9rem', fontStyle: 'italic', color: '#666' }}>
{species.scientificName}
</p>
</div>
</div>
<div style={{ textAlign: 'right' }}>
<div style={{
background: '#4CAF50',
color: 'white',
padding: '4px 12px',
borderRadius: '12px',
fontSize: '0.8rem',
marginBottom: '5px'
}}>
{species.confidence}% confidence
</div>
<div style={{
background: getConservationColor(species.conservationStatus),
color: 'white',
padding: '4px 12px',
borderRadius: '12px',
fontSize: '0.8rem'
}}>
{species.conservationStatus}
</div>
</div>
</div>
<div style={{ marginBottom: '10px' }}>
<p style={{ margin: 0, fontSize: '0.9rem', lineHeight: '1.4' }}>
{species.description}
</p>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: '10px', fontSize: '0.8rem', color: '#666' }}>
<div><strong>Habitat:</strong> {species.habitat}</div>
<div><strong>Frequency:</strong> {species.frequency}</div>
<div><strong>Call Type:</strong> {species.callType}</div>
<div><strong>Sound:</strong> {species.sound}</div>
</div>
</div>
))}
</div>
</div>
)}
{analysis.recommendations && analysis.recommendations.length > 0 && (
<div style={{
background: '#e3f2fd',
border: '2px solid #2196F3',
borderRadius: '8px',
padding: '15px',
marginBottom: '20px'
}}>
<h5 style={{ color: '#1976d2', marginBottom: '10px' }}>💡 Recommendations</h5>
{analysis.recommendations.map((recommendation, index) => (
<div key={index} style={{ color: '#1976d2', marginBottom: '5px' }}>
• {recommendation}
</div>
))}
</div>
)}
<div style={{
background: '#f5f5f5',
padding: '15px',
borderRadius: '8px',
fontSize: '0.9rem',
color: '#666'
}}>
<strong>Analysis Details:</strong><br />
Region: {analysis.region} • Habitat: {analysis.habitat}<br />
Timestamp: {new Date(analysis.timestamp).toLocaleString()}<br />
{analysis.location && (
<>Location: {analysis.location.latitude.toFixed(4)}, {analysis.location.longitude.toFixed(4)}</>
)}
</div>
</div>
)}
{recordings.length > 0 && (
<div className="card" style={{ marginBottom: '30px' }}>
<h3 style={{ color: '#2E7D32', marginBottom: '20px' }}>📈 Biodiversity Statistics</h3>
<BiodiversityStats recordings={recordings} />
</div>
)}
{recordings.length > 0 && (
<div className="card">
<h3 style={{ color: '#2E7D32', marginBottom: '20px' }}>🎵 Recent Recordings</h3>
<div style={{ maxHeight: '400px', overflowY: 'auto' }}>
{recordings.slice(0, 10).map(recording => (
<div key={recording.id} style={{
background: '#f9f9f9',
padding: '15px',
borderRadius: '8px',
marginBottom: '10px',
border: '1px solid #ddd'
}}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '10px' }}>
<div>
<strong>{recording.habitat || 'Unknown Habitat'}</strong>
<span style={{
marginLeft: '10px',
padding: '4px 8px',
borderRadius: '12px',
fontSize: '0.8rem',
background: getQualityColor(recording.biodiversityMetrics?.ecosystemHealth),
color: 'white'
}}>
{recording.biodiversityMetrics?.ecosystemHealth || 'Unknown'}
</span>
</div>
<div style={{ fontSize: '0.8rem', color: '#666' }}>
{new Date(recording.timestamp).toLocaleDateString()} {new Date(recording.timestamp).toLocaleTimeString()}
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(120px, 1fr))', gap: '10px', fontSize: '0.9rem' }}>
<div>Species: <strong>{recording.detectedSpecies?.length || 0}</strong></div>
<div>Score: <strong>{Math.round(recording.biodiversityMetrics?.biodiversityScore || 0)}</strong></div>
<div>Duration: <strong>{recording.duration?.toFixed(1) || 0}s</strong></div>
<div>Confidence: <strong>{recording.confidence || 0}%</strong></div>
</div>
{recording.detectedSpecies && recording.detectedSpecies.length > 0 && (
<div style={{ marginTop: '10px', fontSize: '0.8rem' }}>
<strong>Species:</strong> {recording.detectedSpecies.map(s => s.name).join(', ')}
</div>
)}
</div>
))}
</div>
</div>
)}
</div>
);
}
export default BiodiversityEar;