Add clear plate confirmation between queued prints

The print scheduler previously treated FINISH/FAILED as idle states
and would auto-start the next queued print before the user cleared
the build plate. Now requires explicit user confirmation via a
"Clear Plate & Start Next" button on the printer card. Uses an
in-memory plate-cleared flag — no MQTT command needed since the
scheduler's start_print overrides the printer state.
This commit is contained in:
maziggy
2026-02-12 08:45:05 +01:00
parent 1b5683b3a3
commit aa87e5598b
18 changed files with 525 additions and 98 deletions
+1 -1
View File
@@ -375,7 +375,7 @@ export function Layout() {
break;
}
}
}, [navigate, orderedSidebarIds, navItemsMap]);
}, [navigate, orderedSidebarIds, navItemsMap, extLinksMap]);
useEffect(() => {
document.addEventListener('keydown', handleKeyDown);
+66 -4
View File
@@ -1,11 +1,15 @@
import { useQuery } from '@tanstack/react-query';
import { Clock, Calendar, ChevronRight } from 'lucide-react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { Clock, Calendar, ChevronRight, Loader2, CircleCheck } from 'lucide-react';
import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { api } from '../api/client';
import { useAuth } from '../contexts/AuthContext';
import { useToast } from '../contexts/ToastContext';
import { parseUTCDate } from '../utils/date';
interface PrinterQueueWidgetProps {
printerId: number;
printerState?: string | null;
}
function formatRelativeTime(dateString: string | null): string {
@@ -22,13 +26,29 @@ function formatRelativeTime(dateString: string | null): string {
return date.toLocaleDateString();
}
export function PrinterQueueWidget({ printerId }: PrinterQueueWidgetProps) {
export function PrinterQueueWidget({ printerId, printerState }: PrinterQueueWidgetProps) {
const { t } = useTranslation();
const queryClient = useQueryClient();
const { showToast } = useToast();
const { hasPermission } = useAuth();
const { data: queue } = useQuery({
queryKey: ['queue', printerId, 'pending'],
queryFn: () => api.getQueue(printerId, 'pending'),
refetchInterval: 30000,
});
const clearPlateMutation = useMutation({
mutationFn: () => api.clearPlate(printerId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['queue', printerId] });
queryClient.invalidateQueries({ queryKey: ['printerStatus', printerId] });
showToast(t('queue.clearPlateSuccess'), 'success');
},
onError: (err: Error) => {
showToast(err.message, 'error');
},
});
const nextItem = queue?.[0];
const totalPending = queue?.length || 0;
@@ -36,6 +56,48 @@ export function PrinterQueueWidget({ printerId }: PrinterQueueWidgetProps) {
return null;
}
const needsClearPlate = printerState === 'FINISH' || printerState === 'FAILED';
if (needsClearPlate) {
return (
<div className="mb-3 p-3 bg-bambu-dark rounded-lg border border-yellow-400/30">
<div className="flex items-center gap-3 mb-2">
<Calendar className="w-5 h-5 text-yellow-400 flex-shrink-0" />
<div className="min-w-0 flex-1">
<p className="text-xs text-bambu-gray">{t('queue.nextInQueue')}</p>
<p className="text-sm text-white truncate">
{nextItem?.archive_name || `Archive #${nextItem?.archive_id}`}
</p>
</div>
{totalPending > 1 && (
<span className="text-xs px-1.5 py-0.5 bg-yellow-400/20 text-yellow-400 rounded flex-shrink-0">
+{totalPending - 1}
</span>
)}
</div>
{clearPlateMutation.isSuccess ? (
<div className="w-full py-2 px-3 rounded-lg bg-bambu-green/10 border border-bambu-green/20 text-bambu-green text-sm flex items-center justify-center gap-2">
<CircleCheck className="w-4 h-4" />
{t('queue.plateReady')}
</div>
) : (
<button
onClick={() => clearPlateMutation.mutate()}
disabled={clearPlateMutation.isPending || !hasPermission('printers:control')}
className="w-full py-2 px-3 rounded-lg bg-bambu-green/20 border border-bambu-green/40 text-bambu-green hover:bg-bambu-green/30 transition-colors text-sm font-medium flex items-center justify-center gap-2 disabled:opacity-50"
>
{clearPlateMutation.isPending ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<CircleCheck className="w-4 h-4" />
)}
{t('queue.clearPlate')}
</button>
)}
</div>
);
}
return (
<Link
to="/queue"
@@ -45,7 +107,7 @@ export function PrinterQueueWidget({ printerId }: PrinterQueueWidgetProps) {
<div className="flex items-center gap-3 min-w-0 flex-1">
<Calendar className="w-5 h-5 text-yellow-400 flex-shrink-0" />
<div className="min-w-0 flex-1">
<p className="text-xs text-bambu-gray">Next in queue</p>
<p className="text-xs text-bambu-gray">{t('queue.nextInQueue')}</p>
<p className="text-sm text-white truncate">
{nextItem?.archive_name || `Archive #${nextItem?.archive_id}`}
</p>