Quellcode durchsuchen

Add per-job ETA to print queue

maziggy vor 1 Monat
Ursprung
Commit
6b2c888a8b

+ 27 - 0
frontend/src/__tests__/utils/queueEta.test.ts

@@ -0,0 +1,27 @@
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
+import { formatETA } from '../../utils/date';
+import { formatQueueItemETA } from '../../utils/queueEta';
+
+describe('formatQueueItemETA', () => {
+  beforeEach(() => {
+    vi.useFakeTimers();
+    vi.setSystemTime(new Date('2026-07-31T18:00:00Z'));
+  });
+
+  afterEach(() => {
+    vi.useRealTimers();
+  });
+
+  it('calculates the ETA from the current time and job duration', () => {
+    expect(formatQueueItemETA(90 * 60, '24h')).toBe(
+      formatETA(90, '24h'),
+    );
+  });
+
+  it('returns null without a usable print duration', () => {
+    expect(formatQueueItemETA(null)).toBeNull();
+    expect(formatQueueItemETA(undefined)).toBeNull();
+    expect(formatQueueItemETA(0)).toBeNull();
+    expect(formatQueueItemETA(-60)).toBeNull();
+  });
+});

+ 15 - 4
frontend/src/pages/QueuePage.tsx

@@ -66,6 +66,7 @@ import { api, ApiError } from '../api/client';
 import { PipelineRunsView } from './PipelineRunsPage';
 import { type TimeFormat, formatETA, formatDuration, formatRelativeTime, parseUTCDate } from '../utils/date';
 import { getBedTypeInfo } from '../utils/bedType';
+import { formatQueueItemETA } from '../utils/queueEta';
 import type { PrintQueueItem, PrintQueueBulkUpdate, Permission, CalibrationMode } from '../api/client';
 import { Card } from '../components/Card';
 import { Button } from '../components/Button';
@@ -598,10 +599,20 @@ function SortableQueueItem({
               </span>
             </span>
             {item.print_time_seconds && (
-              <span className="flex items-center gap-1 sm:gap-1.5">
-                <Timer className="w-3 h-3 sm:w-3.5 sm:h-3.5" />
-                {formatDuration(item.print_time_seconds)}
-              </span>
+              <>
+                <span className="flex items-center gap-1 sm:gap-1.5">
+                  <Timer className="w-3 h-3 sm:w-3.5 sm:h-3.5" />
+                  {formatDuration(item.print_time_seconds)}
+                </span>
+                {!item.waiting_reason && (
+                  <span
+                    className="text-bambu-green font-medium"
+                    title={t('printers.estimatedCompletion')}
+                  >
+                    ETA {formatQueueItemETA(item.print_time_seconds, timeFormat, t)}
+                  </span>
+                )}
+              </>
             )}
             {item.filament_used_grams && (
               <span className="flex items-center gap-1 sm:gap-1.5">

+ 18 - 0
frontend/src/utils/queueEta.ts

@@ -0,0 +1,18 @@
+import { formatETA, type TimeFormat } from './date';
+
+/**
+ * Formats the estimated completion time for a queue item if it were
+ * started at the current time.
+ *
+ * This is deliberately a per-job estimate rather than a cumulative
+ * queue forecast.
+ */
+export function formatQueueItemETA(
+  printTimeSeconds: number | null | undefined,
+  timeFormat: TimeFormat = 'system',
+  t?: Parameters<typeof formatETA>[2],
+): string | null {
+  if (printTimeSeconds == null || printTimeSeconds <= 0) return null;
+
+  return formatETA(printTimeSeconds / 60, timeFormat, t);
+}