Show paused status in print queue when printer is paused

The queue was showing "Printing" even when the printer was paused.
Now fetches real-time printer state for active print jobs and displays
"Paused" with a yellow badge when the printer state is PAUSE/PAUSED.

Changes:
- Add printerState prop to StatusBadge and SortableQueueItem components
- Fetch printer statuses for printers with active jobs using useQueries
- Show pause icon and yellow badge when printing but printer is paused
- Add "paused" translation key for queue status (en, de, ja)

Closes #249
This commit is contained in:
maziggy
2026-02-04 07:28:46 +01:00
parent d4ca646581
commit f96b27c5ee
9 changed files with 157 additions and 106 deletions
+4
View File
@@ -11,6 +11,10 @@ All notable changes to Bambuddy will be documented in this file.
- The `filament_used_grams` field already contains the total for the entire print job
- Removed incorrect `* quantity` multiplication from archive stats, Prometheus metrics, and FilamentTrends chart
- Example: A print with 26 objects using 126g was incorrectly shown as 3,276g
- **Print Queue Status Does Not Match Printer Status** (Issue #249):
- Queue now shows "Paused" when the printer is paused instead of "Printing"
- Fetches real-time printer state for actively printing queue items
- Added translations for paused status in English, German, and Japanese
### Added
- **Windows Portable Launcher** (contributed by nmori):
+1
View File
@@ -687,6 +687,7 @@ export default {
pending: 'Ausstehend',
waiting: 'Wartend',
printing: 'Druckt',
paused: 'Pausiert',
completed: 'Abgeschlossen',
failed: 'Fehlgeschlagen',
skipped: 'Übersprungen',
+1
View File
@@ -687,6 +687,7 @@ export default {
pending: 'Pending',
waiting: 'Waiting',
printing: 'Printing',
paused: 'Paused',
completed: 'Completed',
failed: 'Failed',
skipped: 'Skipped',
+1
View File
@@ -549,6 +549,7 @@ export default {
status: {
pending: '待機中',
printing: '印刷中',
paused: '一時停止',
completed: '完了',
failed: '失敗',
skipped: 'スキップ',
+47 -3
View File
@@ -1,6 +1,6 @@
import { useState, useMemo, useEffect, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useQuery, useQueries, useMutation, useQueryClient } from '@tanstack/react-query';
import { Link } from 'react-router-dom';
import {
DndContext,
@@ -46,6 +46,7 @@ import {
CheckSquare,
Square,
User,
Pause,
} from 'lucide-react';
import { api } from '../api/client';
import { parseUTCDate, formatDateTime, type TimeFormat } from '../utils/date';
@@ -80,7 +81,7 @@ function formatRelativeTime(dateString: string | null, timeFormat: TimeFormat =
return formatDateTime(dateString, timeFormat);
}
function StatusBadge({ status, waitingReason, t }: { status: PrintQueueItem['status']; waitingReason?: string | null; t: (key: string) => string }) {
function StatusBadge({ status, waitingReason, printerState, t }: { status: PrintQueueItem['status']; waitingReason?: string | null; printerState?: string | null; t: (key: string) => string }) {
// Special case: pending with waiting_reason shows as "Waiting"
if (status === 'pending' && waitingReason) {
return (
@@ -91,6 +92,16 @@ function StatusBadge({ status, waitingReason, t }: { status: PrintQueueItem['sta
);
}
// Special case: printing but printer is paused
if (status === 'printing' && (printerState === 'PAUSE' || printerState === 'PAUSED')) {
return (
<span className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium border text-yellow-400 bg-yellow-400/10 border-yellow-400/20">
<Pause className="w-3.5 h-3.5" />
{t('queue.status.paused')}
</span>
);
}
const config = {
pending: { icon: Clock, color: 'text-status-warning bg-status-warning/10 border-status-warning/20', label: t('queue.status.pending') },
printing: { icon: Play, color: 'text-blue-400 bg-blue-400/10 border-blue-400/20', label: t('queue.status.printing') },
@@ -286,6 +297,7 @@ function SortableQueueItem({
onToggleSelect,
hasPermission,
canModify,
printerState,
t,
}: {
item: PrintQueueItem;
@@ -301,6 +313,7 @@ function SortableQueueItem({
onToggleSelect?: () => void;
hasPermission: (permission: Permission) => boolean;
canModify: (resource: 'queue' | 'archives' | 'library', action: 'update' | 'delete' | 'reprint', createdById: number | null | undefined) => boolean;
printerState?: string | null;
t: (key: string, options?: Record<string, unknown>) => string;
}) {
const canReorder = hasPermission('queue:reorder');
@@ -500,7 +513,7 @@ function SortableQueueItem({
</div>
{/* Status badge */}
<StatusBadge status={item.status} waitingReason={item.waiting_reason} t={t} />
<StatusBadge status={item.status} waitingReason={item.waiting_reason} printerState={printerState} t={t} />
{/* Actions */}
<div className="flex items-center gap-1">
@@ -825,6 +838,36 @@ export function QueuePage() {
return items;
}, [queue, filterLocation, matchesLocationFilter]);
// Get unique printer IDs from active items to fetch their statuses
const activePrinterIds = useMemo(() => {
const ids = new Set<number>();
activeItems.forEach(item => {
if (item.printer_id) ids.add(item.printer_id);
});
return Array.from(ids);
}, [activeItems]);
// Fetch printer statuses for printers with active jobs
const printerStatusQueries = useQueries({
queries: activePrinterIds.map(printerId => ({
queryKey: ['printerStatus', printerId],
queryFn: () => api.getPrinterStatus(printerId),
refetchInterval: 5000,
})),
});
// Build a map of printer_id -> state for quick lookup
const printerStateMap = useMemo(() => {
const map: Record<number, string | null> = {};
activePrinterIds.forEach((printerId, index) => {
const result = printerStatusQueries[index];
if (result?.data?.state) {
map[printerId] = result.data.state;
}
});
return map;
}, [activePrinterIds, printerStatusQueries]);
const historyItems = useMemo(() => {
let items = queue?.filter(i => ['completed', 'failed', 'skipped', 'cancelled'].includes(i.status)) || [];
if (filterLocation) {
@@ -1035,6 +1078,7 @@ export function QueuePage() {
timeFormat={timeFormat}
hasPermission={hasPermission}
canModify={canModify}
printerState={item.printer_id ? printerStateMap[item.printer_id] : null}
t={t}
/>
))}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -23,8 +23,8 @@
<!-- Splash screens for iOS -->
<link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
<script type="module" crossorigin src="/assets/index-2WXQ5Rri.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-hIPoB0LS.css">
<script type="module" crossorigin src="/assets/index-C32ywEzV.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-fdAEMOwp.css">
</head>
<body>
<div id="root"></div>