import { useState, useEffect, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { Calendar, Clock, Hand, Power, Layers, Code, ListOrdered } from 'lucide-react'; import type { ScheduleOptionsProps, ScheduleType } from './types'; import { formatDateInput, formatTimeInput, parseDateInput, parseTimeInput, getDatePlaceholder, getTimePlaceholder, toDateTimeLocalValue, type DateFormat, type TimeFormat, } from '../../utils/date'; /** * Schedule options component for queue items. * Includes schedule type (ASAP/Queue/Schedule), datetime picker, * and options for require previous success and auto power off. */ export function ScheduleOptionsPanel({ options, onChange, dateFormat = 'system', timeFormat = 'system', canControlPrinter = true, showStagger = false, printerCount = 0, hasGcodeSnippets = false, }: ScheduleOptionsProps) { const { t } = useTranslation(); const [dateValue, setDateValue] = useState(''); const [timeValue, setTimeValue] = useState(''); const [isDateValid, setIsDateValid] = useState(true); const [isTimeValid, setIsTimeValid] = useState(true); const hiddenInputRef = useRef(null); const isInitializedRef = useRef(false); // Initialize or sync from options.scheduledTime useEffect(() => { if (options.scheduleType !== 'scheduled') { isInitializedRef.current = false; return; } // Initialize with default time (now + 1 hour) or from existing value if (!isInitializedRef.current) { isInitializedRef.current = true; let date: Date; if (options.scheduledTime) { date = new Date(options.scheduledTime); if (isNaN(date.getTime())) { date = new Date(); date.setHours(date.getHours() + 1, 0, 0, 0); } } else { date = new Date(); date.setHours(date.getHours() + 1, 0, 0, 0); // Set initial value onChange({ ...options, scheduledTime: toDateTimeLocalValue(date) }); } setDateValue(formatDateInput(date, dateFormat as DateFormat)); setTimeValue(formatTimeInput(date, timeFormat as TimeFormat)); setIsDateValid(true); setIsTimeValid(true); } }, [options.scheduleType, options.scheduledTime, dateFormat, timeFormat, onChange, options]); const handleScheduleTypeChange = (scheduleType: ScheduleType) => { onChange({ ...options, scheduleType, requireManualStart: scheduleType === 'queue' ? options.requireManualStart : false, }); }; const updateScheduledTime = (newDateValue: string, newTimeValue: string) => { const parsedDate = parseDateInput(newDateValue, dateFormat as DateFormat); const parsedTime = parseTimeInput(newTimeValue); setIsDateValid(!!parsedDate); setIsTimeValid(!!parsedTime); if (parsedDate && parsedTime) { parsedDate.setHours(parsedTime.hours, parsedTime.minutes, 0, 0); const now = new Date(); if (parsedDate > now) { onChange({ ...options, scheduledTime: toDateTimeLocalValue(parsedDate) }); } } }; const handleDateChange = (value: string) => { setDateValue(value); updateScheduledTime(value, timeValue); }; const handleTimeChange = (value: string) => { setTimeValue(value); updateScheduledTime(dateValue, value); }; // Handle calendar picker selection const handleCalendarChange = (e: React.ChangeEvent) => { const value = e.target.value; if (value) { const date = new Date(value); if (!isNaN(date.getTime())) { setDateValue(formatDateInput(date, dateFormat as DateFormat)); setTimeValue(formatTimeInput(date, timeFormat as TimeFormat)); setIsDateValid(true); setIsTimeValid(true); onChange({ ...options, scheduledTime: value }); } } }; const openCalendar = () => { hiddenInputRef.current?.showPicker(); }; return (
{/* Schedule type */}
{/* Scheduled time input */} {options.scheduleType === 'scheduled' && (
{/* Date input */}
handleDateChange(e.target.value)} placeholder={getDatePlaceholder(dateFormat as DateFormat)} /> {/* Hidden datetime-local anchored here so the native picker opens near the date field */}
{/* Time input */}
handleTimeChange(e.target.value)} placeholder={getTimePlaceholder(timeFormat as TimeFormat)} />
{(!isDateValid || !isTimeValid) && (

{t('printModal.invalidDateTime')}

)}
)} {/* Manual start */} {options.scheduleType === 'queue' && (
onChange({ ...options, requireManualStart: e.target.checked })} className="rounded border-bambu-dark-tertiary bg-bambu-dark text-bambu-green focus:ring-bambu-green" />
)} {/* Require previous success */}
onChange({ ...options, requirePreviousSuccess: e.target.checked })} className="rounded border-bambu-dark-tertiary bg-bambu-dark text-bambu-green focus:ring-bambu-green" />
{/* Auto power off */}
onChange({ ...options, autoOffAfter: e.target.checked })} disabled={!canControlPrinter} className="rounded border-bambu-dark-tertiary bg-bambu-dark text-bambu-green focus:ring-bambu-green disabled:opacity-50" />
{/* G-code injection */} {hasGcodeSnippets && (
onChange({ ...options, gcodeInjection: e.target.checked })} className="rounded border-bambu-dark-tertiary bg-bambu-dark text-bambu-green focus:ring-bambu-green" />
)} {/* Stagger start */} {showStagger && options.scheduleType !== 'queue' && (
onChange({ ...options, staggerEnabled: e.target.checked })} className="rounded border-bambu-dark-tertiary bg-bambu-dark text-bambu-green focus:ring-bambu-green" />
{options.staggerEnabled && (
onChange({ ...options, staggerGroupSize: Math.max(1, parseInt(e.target.value) || 1) })} className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white text-sm focus:outline-none focus:border-bambu-green" />
onChange({ ...options, staggerIntervalMinutes: Math.max(1, parseInt(e.target.value) || 1) })} className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white text-sm focus:outline-none focus:border-bambu-green" />
{printerCount > 0 && (() => { const groupCount = Math.ceil(printerCount / options.staggerGroupSize); const lastGroupSize = printerCount % options.staggerGroupSize; const totalMinutes = (groupCount - 1) * options.staggerIntervalMinutes; return (

{t('printModal.staggerPreview', '{{printers}} printers → {{groups}} groups of {{size}}, starting every {{interval}} min', { printers: printerCount, groups: groupCount, size: options.staggerGroupSize, interval: options.staggerIntervalMinutes, })} {lastGroupSize !== 0 && options.staggerGroupSize < printerCount ? ` (${t('printModal.staggerLastGroup', 'last group: {{count}}', { count: lastGroupSize })})` : ''} {groupCount > 1 ? ` (${t('printModal.staggerTotal', 'total: {{minutes}} min', { minutes: totalMinutes })})` : ''}

); })()}
)}
)} {/* Help text */}

{options.scheduleType === 'asap' ? t('printModal.helpAsap') : options.scheduleType === 'scheduled' ? t('printModal.helpSchedule') : t('printModal.helpQueue')}

); }