Fix date picker format issues

Issue #233: Respect user date/time format in queue scheduler
- Replace native datetime-local input with custom text inputs
- Add date parsing/formatting utilities for US, EU, ISO, and system formats
- Add calendar button that opens native picker for convenience
- Pass dateFormat and timeFormat settings to ScheduleOptionsPanel
- Show format-appropriate placeholders (MM/DD/YYYY, DD/MM/YYYY, etc.)

Closes #233
This commit is contained in:
maziggy
2026-02-03 09:13:00 +01:00
parent 6431a86e2f
commit 49daba0579
5 changed files with 376 additions and 7 deletions
@@ -1,17 +1,117 @@
import { useState, useEffect, useRef } from 'react';
import { Calendar, Clock, Hand, Power } from 'lucide-react';
import { getMinDateTime } from '../../utils/amsHelpers';
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/Scheduled/Queue Only), datetime picker,
* and options for require previous success and auto power off.
*/
export function ScheduleOptionsPanel({ options, onChange }: ScheduleOptionsProps) {
export function ScheduleOptionsPanel({
options,
onChange,
dateFormat = 'system',
timeFormat = 'system',
}: ScheduleOptionsProps) {
const [dateValue, setDateValue] = useState('');
const [timeValue, setTimeValue] = useState('');
const [isDateValid, setIsDateValid] = useState(true);
const [isTimeValid, setIsTimeValid] = useState(true);
const hiddenInputRef = useRef<HTMLInputElement>(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 });
};
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<HTMLInputElement>) => {
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 (
<div className="space-y-4">
{/* Schedule type */}
@@ -61,14 +161,58 @@ export function ScheduleOptionsPanel({ options, onChange }: ScheduleOptionsProps
{options.scheduleType === 'scheduled' && (
<div>
<label className="block text-sm text-bambu-gray mb-1">Date & Time</label>
<div className="flex gap-2">
{/* Date input */}
<div className="flex-1 relative">
<input
type="text"
className={`w-full px-3 py-2 pr-10 bg-bambu-dark border rounded-lg text-white focus:outline-none ${
isDateValid
? 'border-bambu-dark-tertiary focus:border-bambu-green'
: 'border-red-500'
}`}
value={dateValue}
onChange={(e) => handleDateChange(e.target.value)}
placeholder={getDatePlaceholder(dateFormat as DateFormat)}
/>
<button
type="button"
onClick={openCalendar}
className="absolute right-2 top-1/2 -translate-y-1/2 text-bambu-gray hover:text-white"
title="Open calendar"
>
<Calendar className="w-4 h-4" />
</button>
</div>
{/* Time input */}
<div className="w-32">
<input
type="text"
className={`w-full px-3 py-2 bg-bambu-dark border rounded-lg text-white focus:outline-none ${
isTimeValid
? 'border-bambu-dark-tertiary focus:border-bambu-green'
: 'border-red-500'
}`}
value={timeValue}
onChange={(e) => handleTimeChange(e.target.value)}
placeholder={getTimePlaceholder(timeFormat as TimeFormat)}
/>
</div>
</div>
{/* Hidden datetime-local for calendar picker */}
<input
ref={hiddenInputRef}
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"
className="sr-only"
value={options.scheduledTime}
onChange={(e) => onChange({ ...options, scheduledTime: e.target.value })}
min={getMinDateTime()}
required
onChange={handleCalendarChange}
tabIndex={-1}
/>
{(!isDateValid || !isTimeValid) && (
<p className="mt-1 text-xs text-red-400">
Please enter a valid date and time
</p>
)}
</div>
)}
+6 -1
View File
@@ -688,7 +688,12 @@ export function PrintModal({
{/* Schedule options - only for queue modes */}
{mode !== 'reprint' && (
<ScheduleOptionsPanel options={scheduleOptions} onChange={setScheduleOptions} />
<ScheduleOptionsPanel
options={scheduleOptions}
onChange={setScheduleOptions}
dateFormat={settings?.date_format || 'system'}
timeFormat={settings?.time_format || 'system'}
/>
)}
{/* Error message */}
@@ -187,4 +187,8 @@ export interface PrintOptionsProps {
export interface ScheduleOptionsProps {
options: ScheduleOptions;
onChange: (options: ScheduleOptions) => void;
/** Date format setting from user preferences */
dateFormat?: 'system' | 'us' | 'eu' | 'iso';
/** Time format setting from user preferences */
timeFormat?: 'system' | '12h' | '24h';
}