Browse Source

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
maziggy 3 months ago
parent
commit
f96b27c5ee

+ 4 - 0
CHANGELOG.md

@@ -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
   - 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
   - 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
   - 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
 ### Added
 - **Windows Portable Launcher** (contributed by nmori):
 - **Windows Portable Launcher** (contributed by nmori):

+ 1 - 0
frontend/src/i18n/locales/de.ts

@@ -687,6 +687,7 @@ export default {
       pending: 'Ausstehend',
       pending: 'Ausstehend',
       waiting: 'Wartend',
       waiting: 'Wartend',
       printing: 'Druckt',
       printing: 'Druckt',
+      paused: 'Pausiert',
       completed: 'Abgeschlossen',
       completed: 'Abgeschlossen',
       failed: 'Fehlgeschlagen',
       failed: 'Fehlgeschlagen',
       skipped: 'Übersprungen',
       skipped: 'Übersprungen',

+ 1 - 0
frontend/src/i18n/locales/en.ts

@@ -687,6 +687,7 @@ export default {
       pending: 'Pending',
       pending: 'Pending',
       waiting: 'Waiting',
       waiting: 'Waiting',
       printing: 'Printing',
       printing: 'Printing',
+      paused: 'Paused',
       completed: 'Completed',
       completed: 'Completed',
       failed: 'Failed',
       failed: 'Failed',
       skipped: 'Skipped',
       skipped: 'Skipped',

+ 1 - 0
frontend/src/i18n/locales/ja.ts

@@ -549,6 +549,7 @@ export default {
     status: {
     status: {
       pending: '待機中',
       pending: '待機中',
       printing: '印刷中',
       printing: '印刷中',
+      paused: '一時停止',
       completed: '完了',
       completed: '完了',
       failed: '失敗',
       failed: '失敗',
       skipped: 'スキップ',
       skipped: 'スキップ',

+ 47 - 3
frontend/src/pages/QueuePage.tsx

@@ -1,6 +1,6 @@
 import { useState, useMemo, useEffect, useCallback } from 'react';
 import { useState, useMemo, useEffect, useCallback } from 'react';
 import { useTranslation } from 'react-i18next';
 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 { Link } from 'react-router-dom';
 import {
 import {
   DndContext,
   DndContext,
@@ -46,6 +46,7 @@ import {
   CheckSquare,
   CheckSquare,
   Square,
   Square,
   User,
   User,
+  Pause,
 } from 'lucide-react';
 } from 'lucide-react';
 import { api } from '../api/client';
 import { api } from '../api/client';
 import { parseUTCDate, formatDateTime, type TimeFormat } from '../utils/date';
 import { parseUTCDate, formatDateTime, type TimeFormat } from '../utils/date';
@@ -80,7 +81,7 @@ function formatRelativeTime(dateString: string | null, timeFormat: TimeFormat =
   return formatDateTime(dateString, 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"
   // Special case: pending with waiting_reason shows as "Waiting"
   if (status === 'pending' && waitingReason) {
   if (status === 'pending' && waitingReason) {
     return (
     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 = {
   const config = {
     pending: { icon: Clock, color: 'text-status-warning bg-status-warning/10 border-status-warning/20', label: t('queue.status.pending') },
     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') },
     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,
   onToggleSelect,
   hasPermission,
   hasPermission,
   canModify,
   canModify,
+  printerState,
   t,
   t,
 }: {
 }: {
   item: PrintQueueItem;
   item: PrintQueueItem;
@@ -301,6 +313,7 @@ function SortableQueueItem({
   onToggleSelect?: () => void;
   onToggleSelect?: () => void;
   hasPermission: (permission: Permission) => boolean;
   hasPermission: (permission: Permission) => boolean;
   canModify: (resource: 'queue' | 'archives' | 'library', action: 'update' | 'delete' | 'reprint', createdById: number | null | undefined) => 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;
   t: (key: string, options?: Record<string, unknown>) => string;
 }) {
 }) {
   const canReorder = hasPermission('queue:reorder');
   const canReorder = hasPermission('queue:reorder');
@@ -500,7 +513,7 @@ function SortableQueueItem({
         </div>
         </div>
 
 
         {/* Status badge */}
         {/* 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 */}
         {/* Actions */}
         <div className="flex items-center gap-1">
         <div className="flex items-center gap-1">
@@ -825,6 +838,36 @@ export function QueuePage() {
     return items;
     return items;
   }, [queue, filterLocation, matchesLocationFilter]);
   }, [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(() => {
   const historyItems = useMemo(() => {
     let items = queue?.filter(i => ['completed', 'failed', 'skipped', 'cancelled'].includes(i.status)) || [];
     let items = queue?.filter(i => ['completed', 'failed', 'skipped', 'cancelled'].includes(i.status)) || [];
     if (filterLocation) {
     if (filterLocation) {
@@ -1035,6 +1078,7 @@ export function QueuePage() {
                     timeFormat={timeFormat}
                     timeFormat={timeFormat}
                     hasPermission={hasPermission}
                     hasPermission={hasPermission}
                     canModify={canModify}
                     canModify={canModify}
+                    printerState={item.printer_id ? printerStateMap[item.printer_id] : null}
                     t={t}
                     t={t}
                   />
                   />
                 ))}
                 ))}

File diff suppressed because it is too large
+ 0 - 0
static/assets/index-C32ywEzV.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/index-fdAEMOwp.css


File diff suppressed because it is too large
+ 0 - 0
static/assets/index-hIPoB0LS.css


+ 2 - 2
static/index.html

@@ -23,8 +23,8 @@
 
 
     <!-- Splash screens for iOS -->
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
     <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>
   </head>
   <body>
   <body>
     <div id="root"></div>
     <div id="root"></div>

Some files were not shown because too many files changed in this diff