import { Button } from '@/components/ui/button' import { Combobox, type ComboboxOption } from '@/components/ui/combobox' import { Dialog, DialogContent, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import { Textarea } from '@/components/ui/textarea' import { type CreateScheduledTaskRequest, type RecurrenceType, type TaskType, getRecurrenceTypeLabel, getTaskTypeLabel, } from '@/lib/api/services/schedulers' import { soundsService } from '@/lib/api/services/sounds' import { playlistsService } from '@/lib/api/services/playlists' import { getSupportedTimezones } from '@/utils/locale' import { useLocale } from '@/hooks/use-locale' import { CalendarPlus, Loader2, Music, PlayCircle } from 'lucide-react' import { useState, useEffect } from 'react' interface CreateTaskDialogProps { open: boolean onOpenChange: (open: boolean) => void loading: boolean onSubmit: (data: CreateScheduledTaskRequest) => void onCancel: () => void } const TASK_TYPES: TaskType[] = ['play_sound', 'play_playlist'] const RECURRENCE_TYPES: RecurrenceType[] = ['none', 'minutely', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'cron'] export function CreateTaskDialog({ open, onOpenChange, loading, onSubmit, onCancel, }: CreateTaskDialogProps) { const { timezone } = useLocale() const getDefaultScheduledTime = () => { const now = new Date() now.setMinutes(now.getMinutes() + 10) // Default to 10 minutes from now // Format the time in the user's timezone const formatter = new Intl.DateTimeFormat('en-US', { timeZone: timezone, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: false, }) const parts = formatter.formatToParts(now) const partsObj = parts.reduce((acc, part) => { acc[part.type] = part.value return acc }, {} as Record) // Return in datetime-local format (YYYY-MM-DDTHH:MM) return `${partsObj.year}-${partsObj.month}-${partsObj.day}T${partsObj.hour}:${partsObj.minute}` } const [formData, setFormData] = useState({ name: '', task_type: 'play_sound', scheduled_at: getDefaultScheduledTime(), timezone: timezone, parameters: {}, recurrence_type: 'none', cron_expression: null, recurrence_count: null, expires_at: null, }) const [parametersJson, setParametersJson] = useState('{}') const [parametersError, setParametersError] = useState(null) // Task-specific parameters const [selectedSoundId, setSelectedSoundId] = useState('') const [selectedPlaylistId, setSelectedPlaylistId] = useState('') const [playMode, setPlayMode] = useState('continuous') const [shuffle, setShuffle] = useState(false) // Data loading const [sounds, setSounds] = useState>([]) const [playlists, setPlaylists] = useState>([]) const [loadingSounds, setLoadingSounds] = useState(false) const [loadingPlaylists, setLoadingPlaylists] = useState(false) // Load sounds and playlists when dialog opens useEffect(() => { if (open) { loadSounds() loadPlaylists() } }, [open]) const loadSounds = async () => { setLoadingSounds(true) try { const soundsData = await soundsService.getSounds({ types: ['SDB', 'TTS'] }) setSounds(soundsData || []) } catch (error) { console.error('Failed to load sounds:', error) } finally { setLoadingSounds(false) } } const loadPlaylists = async () => { setLoadingPlaylists(true) try { const playlistsData = await playlistsService.getPlaylists({}) setPlaylists(playlistsData.playlists || []) } catch (error) { console.error('Failed to load playlists:', error) } finally { setLoadingPlaylists(false) } } // Prepare options for comboboxes const soundOptions: ComboboxOption[] = sounds.map((sound) => ({ value: sound.id.toString(), label: sound.name || sound.filename, icon: , searchValue: `${sound.id}-${sound.name || sound.filename}` })) const playlistOptions: ComboboxOption[] = playlists.map((playlist) => ({ value: playlist.id.toString(), label: playlist.name, icon: , searchValue: `${playlist.id}-${playlist.name}` })) const timezoneOptions: ComboboxOption[] = [ { value: 'UTC', label: 'UTC' }, ...getSupportedTimezones().map((tz) => ({ value: tz, label: tz.replace('_', ' '), searchValue: tz.replace('_', ' ') })) ] const handleSubmit = (e: React.FormEvent) => { e.preventDefault() // Build parameters based on task type let parameters: Record = {} if (formData.task_type === 'play_sound') { if (!selectedSoundId) { setParametersError('Please select a sound') return } parameters = { sound_id: parseInt(selectedSoundId) } } else if (formData.task_type === 'play_playlist') { if (!selectedPlaylistId) { setParametersError('Please select a playlist') return } parameters = { playlist_id: parseInt(selectedPlaylistId), play_mode: playMode, shuffle: shuffle, } } else if (formData.task_type === 'credit_recharge') { // For credit recharge, use the JSON textarea for custom parameters try { parameters = JSON.parse(parametersJson) } catch { setParametersError('Invalid JSON format') return } } // Send the datetime as UTC to prevent backend timezone conversion let scheduledAt = formData.scheduled_at if (scheduledAt.length === 16) { scheduledAt += ':00' // Add seconds if missing } // Add Z to indicate this is already UTC time, preventing backend conversion const scheduledAtUTC = scheduledAt + 'Z' onSubmit({ ...formData, parameters, scheduled_at: scheduledAtUTC, }) } const handleParametersChange = (value: string) => { setParametersJson(value) setParametersError(null) // Try to parse JSON to validate try { JSON.parse(value) } catch { if (value.trim()) { setParametersError('Invalid JSON format') } } } const handleCancel = () => { // Reset form setFormData({ name: '', task_type: 'play_sound', scheduled_at: getDefaultScheduledTime(), timezone: timezone, parameters: {}, recurrence_type: 'none', cron_expression: null, recurrence_count: null, expires_at: null, }) setParametersJson('{}') setParametersError(null) setSelectedSoundId('') setSelectedPlaylistId('') setPlayMode('continuous') setShuffle(false) onCancel() } return ( Create Scheduled Task
setFormData(prev => ({ ...prev, name: e.target.value }))} placeholder="Enter task name" required />
setFormData(prev => ({ ...prev, scheduled_at: e.target.value }))} required />
setFormData(prev => ({ ...prev, timezone: value }))} options={timezoneOptions} placeholder="Select timezone..." searchPlaceholder="Search timezone..." emptyMessage="No timezone found." />
{formData.recurrence_type === 'cron' && (
setFormData(prev => ({ ...prev, cron_expression: e.target.value }))} placeholder="0 0 * * *" />
)} {formData.recurrence_type !== 'none' && formData.recurrence_type !== 'cron' && (
setFormData(prev => ({ ...prev, recurrence_count: e.target.value ? parseInt(e.target.value) : null }))} placeholder="Leave empty for infinite" />
)}
setFormData(prev => ({ ...prev, expires_at: e.target.value || null }))} />
{/* Task-specific parameters based on task type */} {formData.task_type === 'play_sound' && (
{parametersError && (

{parametersError}

)}
)} {formData.task_type === 'play_playlist' && (
{parametersError && (

{parametersError}

)}
)} {formData.task_type === 'credit_recharge' && (