diff --git a/frontend/src/components/PortCard.tsx b/frontend/src/components/PortCard.tsx index 744a68b..f945f17 100644 --- a/frontend/src/components/PortCard.tsx +++ b/frontend/src/components/PortCard.tsx @@ -11,107 +11,159 @@ interface PortCardProps { } 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')}`; + const h = Math.floor(seconds / 3600); + const m = Math.floor((seconds % 3600) / 60); + const s = Math.floor(seconds % 60); + return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`; } -function truncateFilePath(path: string, maxLength: number = 50): string { - if (path.length <= maxLength) { - return path; - } - return '...' + path.slice(-(maxLength - 3)); +function truncateFilePath(path: string, maxLength = 38): string { + if (!path) return '—'; + return path.length <= maxLength ? path : '...' + path.slice(-(maxLength - 3)); } export function PortCard({ - port, - isSelected, - onSelect, - onStartRecording, - onStopRecording, - onOpenConfig, + 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 isRec = port.is_recording; - 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' : '' - }`; + const cardStyle: React.CSSProperties = { + background: 'var(--bg-card)', + border: `1px solid ${isRec ? 'var(--rec-red)' : isSelected ? 'var(--dragon-blue)' : 'var(--border)'}`, + borderRadius: 4, + overflow: 'hidden', + cursor: 'pointer', + transition: 'border-color 0.15s, box-shadow 0.15s', + boxShadow: isRec + ? '0 0 0 1px rgba(255,32,32,0.4), 0 4px 20px rgba(255,32,32,0.2)' + : isSelected + ? '0 0 0 1px var(--dragon-blue), 0 6px 30px rgba(26,58,255,0.25)' + : 'none', + }; return ( -
onSelect(port.port_index)}> - {/* Header: Port number and recording status badge */} -
-

Port {port.port_index}

- {recordingStatus} -
+
onSelect(port.port_index)}> - {/* Metrics row */} -
-
- Frames: {port.frame_count} -
-
- FPS: {port.fps.toFixed(2)} -
-
- Bitrate: {port.bitrate_mbps.toFixed(2)} Mbps -
-
- Uptime: {formatUptime(port.uptime_seconds)} -
-
+ {/* Recording bar */} +
- {/* Current file path */} -
- File: -
+
+ + {/* Header row */} +
+
+ PORT {port.port_index} + SDI +
+ + {isRec ? 'REC' : 'IDLE'} + +
+ + {/* Metrics */} +
+ {[ + { label: 'FPS', value: port.fps.toFixed(2), highlight: true }, + { label: 'Bitrate', value: `${port.bitrate_mbps.toFixed(1)} Mbps`, highlight: false }, + { label: 'Frames', value: port.frame_count.toLocaleString(), highlight: false }, + { label: 'Uptime', value: formatUptime(port.uptime_seconds), highlight: false }, + ].map(({ label, value, highlight }) => ( +
+ + {label} + + + {value} + +
+ ))} +
+ + {/* File path */} +
{truncateFilePath(port.current_file)}
+ + {/* Codec */} +
+ Codec + + {port.codec} + +
+ + {/* Actions */} +
+ {isRec ? ( + + ) : ( + + )} + +
+
- {/* Codec info */} -
- Codec: {port.codec} -
- - {/* Control buttons */} -
- {!port.is_recording ? ( - - ) : ( - - )} - -
+
); }