|
|
@@ -1236,14 +1236,34 @@ function StatusSummaryBar({ printers }: { printers: Printer[] | undefined }) {
|
|
|
const { t } = useTranslation();
|
|
|
const queryClient = useQueryClient();
|
|
|
|
|
|
- const counts = useMemo(() => {
|
|
|
+ // Subscribe to query cache changes to re-render when status updates
|
|
|
+ // Throttled to prevent rapid re-renders from causing tab crashes
|
|
|
+ const [cacheTick, setCacheTick] = useState(0);
|
|
|
+ useEffect(() => {
|
|
|
+ let pending = false;
|
|
|
+ const unsubscribe = queryClient.getQueryCache().subscribe(() => {
|
|
|
+ if (!pending) {
|
|
|
+ pending = true;
|
|
|
+ requestAnimationFrame(() => {
|
|
|
+ setCacheTick(t => t + 1);
|
|
|
+ pending = false;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return () => unsubscribe();
|
|
|
+ }, [queryClient]);
|
|
|
+
|
|
|
+ const { counts, nextFinish } = useMemo(() => {
|
|
|
let printing = 0;
|
|
|
let idle = 0;
|
|
|
let offline = 0;
|
|
|
let loading = 0;
|
|
|
+ let nextPrinterName: string | null = null;
|
|
|
+ let nextRemainingMin: number | null = null;
|
|
|
+ let nextProgress: number = 0;
|
|
|
|
|
|
printers?.forEach((printer) => {
|
|
|
- const status = queryClient.getQueryData<{ connected: boolean; state: string | null }>(['printerStatus', printer.id]);
|
|
|
+ const status = queryClient.getQueryData<{ connected: boolean; state: string | null; remaining_time: number | null; progress: number | null }>(['printerStatus', printer.id]);
|
|
|
if (status === undefined) {
|
|
|
// Status not yet loaded - don't count as offline yet
|
|
|
loading++;
|
|
|
@@ -1251,35 +1271,35 @@ function StatusSummaryBar({ printers }: { printers: Printer[] | undefined }) {
|
|
|
offline++;
|
|
|
} else if (status.state === 'RUNNING') {
|
|
|
printing++;
|
|
|
+ if (status.remaining_time != null && status.remaining_time > 0) {
|
|
|
+ if (nextRemainingMin === null || status.remaining_time < nextRemainingMin) {
|
|
|
+ nextRemainingMin = status.remaining_time;
|
|
|
+ nextPrinterName = printer.name;
|
|
|
+ nextProgress = status.progress || 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
} else {
|
|
|
idle++;
|
|
|
}
|
|
|
});
|
|
|
|
|
|
- return { printing, idle, offline, loading, total: (printers?.length || 0) };
|
|
|
- }, [printers, queryClient]);
|
|
|
-
|
|
|
- // Subscribe to query cache changes to re-render when status updates
|
|
|
- // Throttled to prevent rapid re-renders from causing tab crashes
|
|
|
- const [, setTick] = useState(0);
|
|
|
- useEffect(() => {
|
|
|
- let pending = false;
|
|
|
- const unsubscribe = queryClient.getQueryCache().subscribe(() => {
|
|
|
- if (!pending) {
|
|
|
- pending = true;
|
|
|
- requestAnimationFrame(() => {
|
|
|
- setTick(t => t + 1);
|
|
|
- pending = false;
|
|
|
- });
|
|
|
- }
|
|
|
- });
|
|
|
- return () => unsubscribe();
|
|
|
- }, [queryClient]);
|
|
|
+ return {
|
|
|
+ counts: { printing, idle, offline, loading, total: (printers?.length || 0) },
|
|
|
+ nextFinish: nextPrinterName && nextRemainingMin ? { name: nextPrinterName, remainingMin: nextRemainingMin, progress: nextProgress } : null,
|
|
|
+ };
|
|
|
+ // eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
+ }, [printers, queryClient, cacheTick]);
|
|
|
|
|
|
if (!printers?.length) return null;
|
|
|
|
|
|
return (
|
|
|
<div className="flex items-center gap-4 text-sm">
|
|
|
+ <div className="flex items-center gap-1.5">
|
|
|
+ <div className={`w-2 h-2 rounded-full ${counts.idle > 0 ? 'bg-bambu-green' : 'bg-gray-500'}`} />
|
|
|
+ <span className="text-bambu-gray">
|
|
|
+ <span className="text-white font-medium">{counts.idle}</span> {t('printers.status.available').toLowerCase()}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
{counts.printing > 0 && (
|
|
|
<div className="flex items-center gap-1.5">
|
|
|
<div className="w-2 h-2 rounded-full bg-bambu-green animate-pulse" />
|
|
|
@@ -1288,14 +1308,6 @@ function StatusSummaryBar({ printers }: { printers: Printer[] | undefined }) {
|
|
|
</span>
|
|
|
</div>
|
|
|
)}
|
|
|
- {counts.idle > 0 && (
|
|
|
- <div className="flex items-center gap-1.5">
|
|
|
- <div className="w-2 h-2 rounded-full bg-blue-400" />
|
|
|
- <span className="text-bambu-gray">
|
|
|
- <span className="text-white font-medium">{counts.idle}</span> {t('printers.status.idle').toLowerCase()}
|
|
|
- </span>
|
|
|
- </div>
|
|
|
- )}
|
|
|
{counts.offline > 0 && (
|
|
|
<div className="flex items-center gap-1.5">
|
|
|
<div className="w-2 h-2 rounded-full bg-gray-400" />
|
|
|
@@ -1304,6 +1316,23 @@ function StatusSummaryBar({ printers }: { printers: Printer[] | undefined }) {
|
|
|
</span>
|
|
|
</div>
|
|
|
)}
|
|
|
+ {nextFinish && (
|
|
|
+ <>
|
|
|
+ <div className="w-px h-4 bg-bambu-dark-tertiary" />
|
|
|
+ <div className="flex items-center gap-2">
|
|
|
+ <span className="text-bambu-green font-medium">{t('printers.nextAvailable')}:</span>
|
|
|
+ <span className="text-white font-medium">{nextFinish.name}</span>
|
|
|
+ <div className="w-16 bg-bambu-dark-tertiary rounded-full h-1.5">
|
|
|
+ <div
|
|
|
+ className="bg-bambu-green h-1.5 rounded-full transition-all"
|
|
|
+ style={{ width: `${nextFinish.progress}%` }}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <span className="text-white font-medium">{Math.round(nextFinish.progress)}%</span>
|
|
|
+ <span className="text-bambu-gray">({formatTime(nextFinish.remainingMin * 60)})</span>
|
|
|
+ </div>
|
|
|
+ </>
|
|
|
+ )}
|
|
|
</div>
|
|
|
);
|
|
|
}
|