| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239 |
- import { useState, useEffect } from 'react';
- import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
- import { Calendar, Clock, X, AlertCircle, Power } from 'lucide-react';
- import { api } from '../api/client';
- import type { PrintQueueItemCreate } from '../api/client';
- import { Card, CardContent } from './Card';
- import { Button } from './Button';
- import { useToast } from '../contexts/ToastContext';
- interface AddToQueueModalProps {
- archiveId: number;
- archiveName: string;
- onClose: () => void;
- }
- export function AddToQueueModal({ archiveId, archiveName, onClose }: AddToQueueModalProps) {
- const queryClient = useQueryClient();
- const { showToast } = useToast();
- const [printerId, setPrinterId] = useState<number | null>(null);
- const [scheduleType, setScheduleType] = useState<'asap' | 'scheduled'>('asap');
- const [scheduledTime, setScheduledTime] = useState('');
- const [requirePreviousSuccess, setRequirePreviousSuccess] = useState(false);
- const [autoOffAfter, setAutoOffAfter] = useState(false);
- const { data: printers } = useQuery({
- queryKey: ['printers'],
- queryFn: () => api.getPrinters(),
- });
- // Set default printer if only one available
- useEffect(() => {
- if (printers?.length === 1 && !printerId) {
- setPrinterId(printers[0].id);
- }
- }, [printers, printerId]);
- // Close on Escape key
- useEffect(() => {
- const handleKeyDown = (e: KeyboardEvent) => {
- if (e.key === 'Escape') onClose();
- };
- window.addEventListener('keydown', handleKeyDown);
- return () => window.removeEventListener('keydown', handleKeyDown);
- }, [onClose]);
- const addMutation = useMutation({
- mutationFn: (data: PrintQueueItemCreate) => api.addToQueue(data),
- onSuccess: () => {
- queryClient.invalidateQueries({ queryKey: ['queue'] });
- showToast('Added to print queue');
- onClose();
- },
- onError: (error: Error) => {
- showToast(error.message || 'Failed to add to queue', 'error');
- },
- });
- const handleSubmit = (e: React.FormEvent) => {
- e.preventDefault();
- if (!printerId) {
- showToast('Please select a printer', 'error');
- return;
- }
- const data: PrintQueueItemCreate = {
- printer_id: printerId,
- archive_id: archiveId,
- require_previous_success: requirePreviousSuccess,
- auto_off_after: autoOffAfter,
- };
- if (scheduleType === 'scheduled' && scheduledTime) {
- data.scheduled_time = new Date(scheduledTime).toISOString();
- }
- addMutation.mutate(data);
- };
- // Get minimum datetime (now + 1 minute)
- const getMinDateTime = () => {
- const now = new Date();
- now.setMinutes(now.getMinutes() + 1);
- return now.toISOString().slice(0, 16);
- };
- return (
- <div
- className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4"
- onClick={onClose}
- >
- <Card className="w-full max-w-md" onClick={(e) => e.stopPropagation()}>
- <CardContent className="p-0">
- {/* Header */}
- <div className="flex items-center justify-between p-4 border-b border-bambu-dark-tertiary">
- <div className="flex items-center gap-2">
- <Calendar className="w-5 h-5 text-bambu-green" />
- <h2 className="text-xl font-semibold text-white">Schedule Print</h2>
- </div>
- <button
- onClick={onClose}
- className="text-bambu-gray hover:text-white transition-colors"
- >
- <X className="w-5 h-5" />
- </button>
- </div>
- {/* Form */}
- <form onSubmit={handleSubmit} className="p-4 space-y-4">
- {/* Archive name */}
- <div>
- <label className="block text-sm text-bambu-gray mb-1">Print Job</label>
- <p className="text-white font-medium truncate">{archiveName}</p>
- </div>
- {/* Printer selection */}
- <div>
- <label className="block text-sm text-bambu-gray mb-1">Printer</label>
- {printers?.length === 0 ? (
- <div className="flex items-center gap-2 text-red-400 text-sm">
- <AlertCircle className="w-4 h-4" />
- No printers configured
- </div>
- ) : (
- <select
- className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none"
- value={printerId || ''}
- onChange={(e) => setPrinterId(e.target.value ? Number(e.target.value) : null)}
- required
- >
- <option value="">Select printer...</option>
- {printers?.map((p) => (
- <option key={p.id} value={p.id}>{p.name}</option>
- ))}
- </select>
- )}
- </div>
- {/* Schedule type */}
- <div>
- <label className="block text-sm text-bambu-gray mb-2">When to print</label>
- <div className="flex gap-2">
- <button
- type="button"
- className={`flex-1 px-3 py-2 rounded-lg border text-sm flex items-center justify-center gap-2 transition-colors ${
- scheduleType === 'asap'
- ? 'bg-bambu-green border-bambu-green text-white'
- : 'bg-bambu-dark border-bambu-dark-tertiary text-bambu-gray hover:text-white'
- }`}
- onClick={() => setScheduleType('asap')}
- >
- <Clock className="w-4 h-4" />
- ASAP (when idle)
- </button>
- <button
- type="button"
- className={`flex-1 px-3 py-2 rounded-lg border text-sm flex items-center justify-center gap-2 transition-colors ${
- scheduleType === 'scheduled'
- ? 'bg-bambu-green border-bambu-green text-white'
- : 'bg-bambu-dark border-bambu-dark-tertiary text-bambu-gray hover:text-white'
- }`}
- onClick={() => setScheduleType('scheduled')}
- >
- <Calendar className="w-4 h-4" />
- Scheduled
- </button>
- </div>
- </div>
- {/* Scheduled time input */}
- {scheduleType === 'scheduled' && (
- <div>
- <label className="block text-sm text-bambu-gray mb-1">Date & Time</label>
- <input
- type="datetime-local"
- className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none"
- value={scheduledTime}
- onChange={(e) => setScheduledTime(e.target.value)}
- min={getMinDateTime()}
- required
- />
- </div>
- )}
- {/* Require previous success */}
- <div className="flex items-center gap-2">
- <input
- type="checkbox"
- id="requirePrevious"
- checked={requirePreviousSuccess}
- onChange={(e) => setRequirePreviousSuccess(e.target.checked)}
- className="rounded border-bambu-dark-tertiary bg-bambu-dark text-bambu-green focus:ring-bambu-green"
- />
- <label htmlFor="requirePrevious" className="text-sm text-bambu-gray">
- Only start if previous print succeeded
- </label>
- </div>
- {/* Auto power off */}
- <div className="flex items-center gap-2">
- <input
- type="checkbox"
- id="autoOffAfter"
- checked={autoOffAfter}
- onChange={(e) => setAutoOffAfter(e.target.checked)}
- className="rounded border-bambu-dark-tertiary bg-bambu-dark text-bambu-green focus:ring-bambu-green"
- />
- <label htmlFor="autoOffAfter" className="text-sm text-bambu-gray flex items-center gap-1">
- <Power className="w-3.5 h-3.5" />
- Power off printer when done
- </label>
- </div>
- {/* Help text */}
- <p className="text-xs text-bambu-gray">
- {scheduleType === 'asap'
- ? 'Print will start as soon as the printer is idle.'
- : 'Print will start at the scheduled time if the printer is idle. If busy, it will wait until the printer becomes available.'}
- </p>
- {/* Actions */}
- <div className="flex gap-3 pt-2">
- <Button type="button" variant="secondary" onClick={onClose} className="flex-1">
- Cancel
- </Button>
- <Button
- type="submit"
- className="flex-1"
- disabled={addMutation.isPending || !printerId || printers?.length === 0}
- >
- {addMutation.isPending ? 'Adding...' : 'Add to Queue'}
- </Button>
- </div>
- </form>
- </CardContent>
- </Card>
- </div>
- );
- }
|