SpoolBuddyTopBar.tsx 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. import { useState, useEffect, useMemo } from 'react';
  2. import { useQuery, useQueries } from '@tanstack/react-query';
  3. import { useTranslation } from 'react-i18next';
  4. import { WifiOff } from 'lucide-react';
  5. import { api, type Printer } from '../../api/client';
  6. interface SpoolBuddyTopBarProps {
  7. selectedPrinterId: number | null;
  8. onPrinterChange: (id: number) => void;
  9. deviceOnline: boolean;
  10. }
  11. export function SpoolBuddyTopBar({ selectedPrinterId, onPrinterChange, deviceOnline }: SpoolBuddyTopBarProps) {
  12. const { t } = useTranslation();
  13. const [currentTime, setCurrentTime] = useState(new Date());
  14. const { data: printers = [] } = useQuery({
  15. queryKey: ['printers'],
  16. queryFn: () => api.getPrinters(),
  17. });
  18. // Fetch status for each printer to determine which are online
  19. const statusQueries = useQueries({
  20. queries: printers.map((printer: Printer) => ({
  21. queryKey: ['printerStatus', printer.id],
  22. queryFn: () => api.getPrinterStatus(printer.id),
  23. refetchInterval: 10000,
  24. })),
  25. });
  26. const onlinePrinters = useMemo(() => {
  27. return printers.filter((_: Printer, i: number) => statusQueries[i]?.data?.connected);
  28. }, [printers, statusQueries]);
  29. // Auto-select first online printer
  30. useEffect(() => {
  31. const currentStillOnline = onlinePrinters.some((p: Printer) => p.id === selectedPrinterId);
  32. if ((!selectedPrinterId || !currentStillOnline) && onlinePrinters.length > 0) {
  33. onPrinterChange(onlinePrinters[0].id);
  34. }
  35. }, [onlinePrinters, selectedPrinterId, onPrinterChange]);
  36. // Clock - update every second for kiosk display
  37. useEffect(() => {
  38. const timer = setInterval(() => setCurrentTime(new Date()), 1000);
  39. return () => clearInterval(timer);
  40. }, []);
  41. const formatTime = (date: Date) =>
  42. date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
  43. return (
  44. <div className="h-11 bg-bambu-dark-secondary border-b border-bambu-dark-tertiary flex items-center px-3 gap-4 shrink-0">
  45. {/* Logo */}
  46. <div className="flex items-center shrink-0">
  47. <img src="/img/spoolbuddy_logo_dark.png" alt="SpoolBuddy" className="h-7" />
  48. </div>
  49. {/* Printer selector - centered */}
  50. <div className="flex-1 flex justify-center">
  51. <select
  52. value={selectedPrinterId ?? ''}
  53. onChange={(e) => onPrinterChange(Number(e.target.value))}
  54. className="bg-bambu-dark text-white text-sm px-3 py-1.5 rounded border border-bambu-dark-tertiary focus:outline-none focus:border-bambu-green min-w-[150px]"
  55. >
  56. {onlinePrinters.length === 0 ? (
  57. <option value="">{t('spoolbuddy.status.noPrinters', 'No printers online')}</option>
  58. ) : (
  59. onlinePrinters.map((printer: Printer) => (
  60. <option key={printer.id} value={printer.id}>
  61. {printer.name}
  62. </option>
  63. ))
  64. )}
  65. </select>
  66. </div>
  67. {/* Right side indicators */}
  68. <div className="flex items-center gap-3 shrink-0">
  69. {/* WiFi signal bars */}
  70. <div className="flex items-center" title={deviceOnline ? t('spoolbuddy.status.online', 'Online') : t('spoolbuddy.status.offline', 'Offline')}>
  71. {deviceOnline ? (
  72. <div className="flex items-end gap-0.5 h-4">
  73. {[1, 2, 3, 4].map((level) => (
  74. <div
  75. key={level}
  76. className={`w-1 rounded-sm ${level <= 4 ? 'bg-white' : 'bg-bambu-dark-tertiary'}`}
  77. style={{ height: `${level * 4}px` }}
  78. />
  79. ))}
  80. </div>
  81. ) : (
  82. <WifiOff className="w-5 h-5 text-red-400" />
  83. )}
  84. </div>
  85. {/* Device LED */}
  86. <div className="flex items-center gap-1.5">
  87. <div className={`w-2.5 h-2.5 rounded-full ${deviceOnline ? 'bg-bambu-green shadow-[0_0_6px_rgba(34,197,94,0.5)]' : 'bg-bambu-gray'}`} />
  88. <span className="text-xs text-white/50">{deviceOnline ? t('spoolbuddy.status.online', 'Online') : t('spoolbuddy.status.offline', 'Offline')}</span>
  89. </div>
  90. {/* Clock */}
  91. <span className="text-white/50 text-sm font-mono min-w-[50px] text-right">
  92. {formatTime(currentTime)}
  93. </span>
  94. </div>
  95. </div>
  96. );
  97. }