SpoolBuddyTopBar.tsx 4.3 KB

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