SpoolBuddyTopBar.tsx 4.3 KB

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