SpoolBuddyTopBar.tsx 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. import { useState, useEffect } from 'react';
  2. import { useQuery } from '@tanstack/react-query';
  3. import { useTranslation } from 'react-i18next';
  4. import { api, type Printer } from '../../api/client';
  5. interface SpoolBuddyTopBarProps {
  6. selectedPrinterId: number | null;
  7. onPrinterChange: (id: number) => void;
  8. deviceOnline: boolean;
  9. }
  10. export function SpoolBuddyTopBar({ selectedPrinterId, onPrinterChange, deviceOnline }: SpoolBuddyTopBarProps) {
  11. const { t } = useTranslation();
  12. const [currentTime, setCurrentTime] = useState(new Date());
  13. const { data: printers = [] } = useQuery({
  14. queryKey: ['printers'],
  15. queryFn: () => api.getPrinters(),
  16. });
  17. // Auto-select first printer
  18. useEffect(() => {
  19. if (!selectedPrinterId && printers.length > 0) {
  20. onPrinterChange(printers[0].id);
  21. }
  22. }, [printers, selectedPrinterId, onPrinterChange]);
  23. // Clock
  24. useEffect(() => {
  25. const timer = setInterval(() => setCurrentTime(new Date()), 30000);
  26. return () => clearInterval(timer);
  27. }, []);
  28. const formatTime = (date: Date) =>
  29. date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
  30. return (
  31. <div className="h-11 bg-zinc-950 border-b border-zinc-800 flex items-center px-3 gap-4 shrink-0">
  32. {/* Logo */}
  33. <div className="flex items-center gap-2 shrink-0">
  34. <div className="w-6 h-6 rounded bg-green-600 flex items-center justify-center">
  35. <svg className="w-4 h-4 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  36. <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
  37. </svg>
  38. </div>
  39. <span className="text-white font-semibold text-sm hidden sm:inline">SpoolBuddy</span>
  40. </div>
  41. {/* Printer selector - centered */}
  42. <div className="flex-1 flex justify-center">
  43. <select
  44. value={selectedPrinterId ?? ''}
  45. onChange={(e) => onPrinterChange(Number(e.target.value))}
  46. className="bg-zinc-800 text-white text-sm px-3 py-1.5 rounded border border-zinc-700 focus:outline-none focus:border-green-500 min-w-[150px]"
  47. >
  48. {printers.length === 0 ? (
  49. <option value="">{t('spoolbuddy.status.noPrinters', 'No printers')}</option>
  50. ) : (
  51. printers.map((printer: Printer) => (
  52. <option key={printer.id} value={printer.id}>
  53. {printer.name}
  54. </option>
  55. ))
  56. )}
  57. </select>
  58. </div>
  59. {/* Right side indicators */}
  60. <div className="flex items-center gap-3 shrink-0">
  61. {/* Device LED */}
  62. <div className="flex items-center gap-1.5" title={deviceOnline ? t('spoolbuddy.status.online', 'Online') : t('spoolbuddy.status.offline', 'Offline')}>
  63. <div className={`w-2.5 h-2.5 rounded-full ${deviceOnline ? 'bg-green-500 shadow-[0_0_6px_rgba(34,197,94,0.5)]' : 'bg-zinc-600'}`} />
  64. <span className="text-xs text-zinc-400">{deviceOnline ? t('spoolbuddy.status.online', 'Online') : t('spoolbuddy.status.offline', 'Offline')}</span>
  65. </div>
  66. {/* Clock */}
  67. <span className="text-zinc-400 text-sm font-mono min-w-[50px] text-right">
  68. {formatTime(currentTime)}
  69. </span>
  70. </div>
  71. </div>
  72. );
  73. }