Add frontend/src/components/PortCard.tsx

This commit is contained in:
Zac Gaetano 2026-04-14 09:21:16 -04:00
parent d8e4386426
commit 606004a443

View file

@ -0,0 +1,117 @@
import React from 'react';
import { PortStatus } from '../types';
interface PortCardProps {
port: PortStatus;
isSelected: boolean;
onSelect: (portIndex: number) => void;
onStartRecording: (portIndex: number) => void;
onStopRecording: (portIndex: number) => void;
onOpenConfig: (portIndex: number) => void;
}
function formatUptime(seconds: number): string {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = Math.floor(seconds % 60);
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
}
function truncateFilePath(path: string, maxLength: number = 50): string {
if (path.length <= maxLength) {
return path;
}
return '...' + path.slice(-(maxLength - 3));
}
export function PortCard({
port,
isSelected,
onSelect,
onStartRecording,
onStopRecording,
onOpenConfig,
}: PortCardProps) {
const recordingBadgeClass = port.is_recording
? 'bg-red-500 text-white text-xs px-2 py-1 rounded'
: 'bg-gray-500 text-white text-xs px-2 py-1 rounded';
const recordingStatus = port.is_recording ? 'RECORDING' : 'IDLE';
const cardClass = `bg-gray-800 border border-gray-700 rounded-lg p-4 cursor-pointer hover:border-blue-500 transition-colors ${
isSelected ? 'border-blue-500 ring-2 ring-blue-500' : ''
}`;
return (
<div className={cardClass} onClick={() => onSelect(port.port_index)}>
{/* Header: Port number and recording status badge */}
<div className="flex items-center justify-between mb-3">
<h3 className="text-lg font-semibold text-white">Port {port.port_index}</h3>
<span className={recordingBadgeClass}>{recordingStatus}</span>
</div>
{/* Metrics row */}
<div className="grid grid-cols-2 gap-2 mb-3 text-sm text-gray-300">
<div>
<span className="text-gray-400">Frames:</span> {port.frame_count}
</div>
<div>
<span className="text-gray-400">FPS:</span> {port.fps.toFixed(2)}
</div>
<div>
<span className="text-gray-400">Bitrate:</span> {port.bitrate_mbps.toFixed(2)} Mbps
</div>
<div>
<span className="text-gray-400">Uptime:</span> {formatUptime(port.uptime_seconds)}
</div>
</div>
{/* Current file path */}
<div className="mb-3 text-sm">
<span className="text-gray-400">File:</span>
<div className="text-gray-300 truncate font-mono text-xs mt-1">
{truncateFilePath(port.current_file)}
</div>
</div>
{/* Codec info */}
<div className="mb-4 text-sm text-gray-300">
<span className="text-gray-400">Codec:</span> {port.codec}
</div>
{/* Control buttons */}
<div className="flex gap-2">
{!port.is_recording ? (
<button
onClick={(e) => {
e.stopPropagation();
onStartRecording(port.port_index);
}}
className="bg-green-600 hover:bg-green-700 text-white px-3 py-1 rounded text-sm transition-colors"
>
Start
</button>
) : (
<button
onClick={(e) => {
e.stopPropagation();
onStopRecording(port.port_index);
}}
className="bg-red-600 hover:bg-red-700 text-white px-3 py-1 rounded text-sm transition-colors"
>
Stop
</button>
)}
<button
onClick={(e) => {
e.stopPropagation();
onOpenConfig(port.port_index);
}}
className="bg-gray-600 hover:bg-gray-700 text-white px-3 py-1 rounded text-sm transition-colors"
>
Config
</button>
</div>
</div>
);
}