Spaces:
Running
Running
import React, { useEffect, useRef } from 'react'; | |
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'; | |
import { Camera, MicOff } from 'lucide-react'; | |
interface MetricsPanelProps { | |
activeTab: 'SENSORS' | 'MOTORS'; | |
setActiveTab: (tab: 'SENSORS' | 'MOTORS') => void; | |
sensorData: any[]; | |
motorData: any[]; | |
hasPermissions: boolean; | |
streamRef: React.RefObject<MediaStream | null>; | |
isVoiceActive: boolean; | |
micLevel: number; | |
} | |
const MetricsPanel: React.FC<MetricsPanelProps> = ({ | |
activeTab, | |
setActiveTab, | |
sensorData, | |
motorData, | |
hasPermissions, | |
streamRef, | |
isVoiceActive, | |
micLevel, | |
}) => { | |
const sensorVideoRef = useRef<HTMLVideoElement>(null); | |
useEffect(() => { | |
if (activeTab === 'SENSORS' && hasPermissions && sensorVideoRef.current && streamRef.current) { | |
if (sensorVideoRef.current.srcObject !== streamRef.current) { | |
sensorVideoRef.current.srcObject = streamRef.current; | |
} | |
} | |
}, [activeTab, hasPermissions, streamRef]); | |
return ( | |
<div className="w-full lg:w-1/2 p-2 sm:p-4"> | |
<div className="bg-gray-900 rounded-lg p-4 h-full flex flex-col"> | |
{/* Tab Headers */} | |
<div className="flex mb-4"> | |
<button | |
onClick={() => setActiveTab('MOTORS')} | |
className={`px-6 py-2 rounded-t-lg text-sm sm:text-base ${ | |
activeTab === 'MOTORS' | |
? 'bg-orange-500 text-white' | |
: 'bg-gray-700 text-gray-300 hover:bg-gray-600' | |
}`} | |
> | |
MOTORS | |
</button> | |
<button | |
onClick={() => setActiveTab('SENSORS')} | |
className={`px-6 py-2 rounded-t-lg ml-2 text-sm sm:text-base ${ | |
activeTab === 'SENSORS' | |
? 'bg-orange-500 text-white' | |
: 'bg-gray-700 text-gray-300 hover:bg-gray-600' | |
}`} | |
> | |
SENSORS | |
</button> | |
</div> | |
{/* Chart Content */} | |
<div className="flex-1 overflow-y-auto"> | |
{activeTab === 'SENSORS' && ( | |
<div className="space-y-4"> | |
{/* Webcam Feed */} | |
<div className="border border-gray-800 rounded p-2 flex flex-col h-64"> | |
<h3 className="text-sm text-white font-medium mb-2">Live Camera Feed</h3> | |
{hasPermissions ? ( | |
<div className="flex-1 bg-black rounded overflow-hidden"> | |
<video | |
ref={sensorVideoRef} | |
autoPlay | |
muted | |
playsInline | |
className="w-full h-full object-contain" | |
/> | |
</div> | |
) : ( | |
<div className="flex-1 flex items-center justify-center bg-black rounded"> | |
<div className="text-center"> | |
<Camera className="w-12 h-12 mx-auto text-gray-500 mb-2" /> | |
<p className="text-gray-400">Camera permission not granted.</p> | |
</div> | |
</div> | |
)} | |
</div> | |
{/* Mic Detection & Other Sensors */} | |
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> | |
<div className="border border-gray-800 rounded p-2 flex flex-col justify-center min-h-[120px]"> | |
<h3 className="text-sm text-center text-white font-medium mb-2">Voice Activity</h3> | |
{hasPermissions ? ( | |
<div className="flex-1 flex flex-col items-center justify-center gap-2 text-center"> | |
<div className="flex items-end h-10 gap-px w-full justify-center"> | |
{[...Array(15)].map((_, i) => { | |
const barIsActive = isVoiceActive && i < (micLevel / 120 * 15); | |
return ( | |
<div | |
key={i} | |
className={`w-1.5 rounded-full transition-colors duration-75 ${barIsActive ? 'bg-orange-500' : 'bg-gray-700'}`} | |
style={{ height: `${(i / 15 * 60) + 20}%` }} | |
/> | |
); | |
})} | |
</div> | |
<p className="text-xs text-gray-300"> | |
{isVoiceActive ? "Voice commands active" : "Voice commands muted"} | |
</p> | |
</div> | |
) : ( | |
<div className="flex-1 flex items-center justify-center bg-black rounded"> | |
<div className="text-center"> | |
<MicOff className="w-8 h-8 mx-auto text-gray-500 mb-2" /> | |
<p className="text-gray-400">Microphone permission not granted.</p> | |
</div> | |
</div> | |
)} | |
</div> | |
{/* Sensor Charts */} | |
{['sensor3', 'sensor4'].map((sensor, index) => ( | |
<div key={sensor} className="border border-gray-800 rounded p-2 flex flex-col h-auto min-h-[120px]"> | |
<h3 className="text-sm text-white font-medium mb-2">Sensor {index + 3}</h3> | |
<ResponsiveContainer width="100%" height="90%"> | |
<LineChart data={sensorData}> | |
<CartesianGrid strokeDasharray="3 3" stroke="#374151" /> | |
<XAxis hide /> | |
<YAxis fontSize={12} stroke="#9CA3AF" /> | |
<Tooltip | |
contentStyle={{ | |
backgroundColor: '#1F2937', | |
border: '1px solid #374151', | |
color: '#fff' | |
}} | |
/> | |
<Line | |
type="monotone" | |
dataKey={sensor} | |
stroke={index % 2 === 1 ? '#ff6b35' : '#ffdd44'} | |
strokeWidth={2} | |
dot={false} | |
/> | |
</LineChart> | |
</ResponsiveContainer> | |
</div> | |
))} | |
</div> | |
</div> | |
)} | |
{activeTab === 'MOTORS' && ( | |
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> | |
{['motor1', 'motor2', 'motor3', 'motor4', 'motor5', 'motor6'].map((motor, index) => ( | |
<div key={motor} className="border border-gray-800 rounded p-2 h-40"> | |
<h3 className="text-sm text-white font-medium mb-2">Motor {index + 1}</h3> | |
<ResponsiveContainer width="100%" height="80%"> | |
<LineChart data={motorData}> | |
<CartesianGrid strokeDasharray="3 3" stroke="#374151" /> | |
<XAxis hide /> | |
<YAxis fontSize={12} stroke="#9CA3AF" /> | |
<Tooltip | |
contentStyle={{ | |
backgroundColor: '#1F2937', | |
border: '1px solid #374151', | |
color: '#fff' | |
}} | |
/> | |
<Line | |
type="monotone" | |
dataKey={motor} | |
stroke={index % 2 === 0 ? '#ff6b35' : '#ffdd44'} | |
strokeWidth={2} | |
dot={false} | |
/> | |
</LineChart> | |
</ResponsiveContainer> | |
</div> | |
))} | |
</div> | |
)} | |
</div> | |
</div> | |
</div> | |
); | |
}; | |
export default MetricsPanel; | |