import { useCallback, useEffect, useMemo, useState } from 'react'; import AddIcon from '@mui/icons-material/Add'; import CancelIcon from '@mui/icons-material/Cancel'; import DoneIcon from '@mui/icons-material/Done'; import PlayArrowIcon from '@mui/icons-material/PlayArrow'; import RemoveIcon from '@mui/icons-material/RemoveCircleOutline'; import { Box, Button, Checkbox, Dialog, DialogActions, DialogContent, DialogTitle, Grid, TextField, ToggleButton, ToggleButtonGroup, Typography } from '@mui/material'; import { dialogStyle } from 'CustomTheme'; import type Schema from 'async-validator'; import type { ValidateFieldsError } from 'async-validator'; import { BlockFormControlLabel, ValidatedTextField } from 'components'; import { useI18nContext } from 'i18n/i18n-react'; import { updateValue } from 'utils'; import { validate } from 'validators'; import { ScheduleFlag } from './types'; import type { ScheduleItem } from './types'; // Constants const FLAG_MASK_127 = 127; const SCHEDULE_TYPE_THRESHOLD = 128; const DEFAULT_TIME = '00:00'; const TYPOGRAPHY_FONT_SIZE = 10; // Day of week flag configuration (static, defined outside component) const DAY_FLAGS = [ { value: '2', flag: ScheduleFlag.SCHEDULE_MON }, { value: '4', flag: ScheduleFlag.SCHEDULE_TUE }, { value: '8', flag: ScheduleFlag.SCHEDULE_WED }, { value: '16', flag: ScheduleFlag.SCHEDULE_THU }, { value: '32', flag: ScheduleFlag.SCHEDULE_FRI }, { value: '64', flag: ScheduleFlag.SCHEDULE_SAT }, { value: '1', flag: ScheduleFlag.SCHEDULE_SUN } ] as const; // Day of week flag values array (static) const FLAG_VALUES = [ ScheduleFlag.SCHEDULE_SUN, ScheduleFlag.SCHEDULE_MON, ScheduleFlag.SCHEDULE_TUE, ScheduleFlag.SCHEDULE_WED, ScheduleFlag.SCHEDULE_THU, ScheduleFlag.SCHEDULE_FRI, ScheduleFlag.SCHEDULE_SAT ] as const; interface SchedulerDialogProps { open: boolean; creating: boolean; onClose: () => void; onSave: (ei: ScheduleItem) => void; selectedItem: ScheduleItem; validator: Schema; dow: string[]; } const SchedulerDialog = ({ open, creating, onClose, onSave, selectedItem, validator, dow }: SchedulerDialogProps) => { const { LL } = useI18nContext(); const [editItem, setEditItem] = useState(selectedItem); const [fieldErrors, setFieldErrors] = useState(); const [scheduleType, setScheduleType] = useState(); const updateFormValue = useMemo( () => updateValue( setEditItem as unknown as React.Dispatch< React.SetStateAction> > ), [] ); useEffect(() => { if (open) { setFieldErrors(undefined); setEditItem(selectedItem); // Set the flags based on type when page is loaded: // 0-127 is day schedule // 128 is timer // 129 is on change // 130 is on condition // 132 is immediate setScheduleType( selectedItem.flags < SCHEDULE_TYPE_THRESHOLD ? ScheduleFlag.SCHEDULE_DAY : selectedItem.flags ); } }, [open, selectedItem]); // Helper function to handle save operations const handleSave = useCallback( async (itemToSave: ScheduleItem) => { try { setFieldErrors(undefined); await validate(validator, itemToSave); onSave(itemToSave); } catch (error) { setFieldErrors(error as ValidateFieldsError); } }, [validator, onSave] ); const save = useCallback(async () => { await handleSave(editItem); }, [editItem, handleSave]); const saveandactivate = useCallback(async () => { await handleSave({ ...editItem, active: true }); }, [editItem, handleSave]); const remove = useCallback(() => { onSave({ ...editItem, deleted: true }); }, [editItem, onSave]); // Optimize DOW flag conversion const getFlagDOWnumber = useCallback((flags: string[]) => { return flags.reduce((acc, flag) => acc | Number(flag), 0) & FLAG_MASK_127; }, []); const getFlagDOWstring = useCallback((f: number) => { return FLAG_VALUES.filter((flag) => (f & flag) === flag).map((flag) => String(flag) ); }, []); // Day of week display component const DayOfWeekButton = useCallback( (flag: number) => { const dayIndex = Math.log2(flag); const isSelected = (editItem.flags & flag) === flag; return ( {dow[dayIndex]} ); }, [editItem.flags, dow] ); const handleClose = useCallback( (_event: React.SyntheticEvent, reason: 'backdropClick' | 'escapeKeyDown') => { if (reason !== 'backdropClick') { onClose(); } }, [onClose] ); const handleScheduleTypeChange = useCallback( (_event: React.SyntheticEvent, flag: ScheduleFlag | null) => { if (flag !== null) { setFieldErrors(undefined); // clear any validation errors setScheduleType(flag); // wipe the time field when changing the schedule type // set the flags based on type const newFlags = flag === ScheduleFlag.SCHEDULE_DAY ? 0 : flag; setEditItem((prev) => ({ ...prev, time: '', flags: newFlags })); } }, [] ); const handleDOWChange = useCallback( (_event: React.SyntheticEvent, flags: string[]) => { const newFlags = getFlagDOWnumber(flags); setEditItem((prev) => ({ ...prev, flags: newFlags })); }, [getFlagDOWnumber] ); // Memoize derived values const isDaySchedule = useMemo( () => scheduleType === ScheduleFlag.SCHEDULE_DAY, [scheduleType] ); const isTimerSchedule = useMemo( () => scheduleType === ScheduleFlag.SCHEDULE_TIMER, [scheduleType] ); const isImmediateSchedule = useMemo( () => scheduleType === ScheduleFlag.SCHEDULE_IMMEDIATE, [scheduleType] ); const needsTimeField = useMemo( () => isDaySchedule || isTimerSchedule, [isDaySchedule, isTimerSchedule] ); const dowFlags = useMemo( () => getFlagDOWstring(editItem.flags), [editItem.flags, getFlagDOWstring] ); const timeFieldValue = useMemo(() => { if (needsTimeField) { return editItem.time === '' ? DEFAULT_TIME : editItem.time; } return editItem.time === DEFAULT_TIME ? '' : editItem.time; }, [editItem.time, needsTimeField]); const timeFieldLabel = useMemo(() => { if (scheduleType === ScheduleFlag.SCHEDULE_TIMER) return LL.TIMER(1); if (scheduleType === ScheduleFlag.SCHEDULE_CONDITION) return LL.CONDITION(); if (scheduleType === ScheduleFlag.SCHEDULE_ONCHANGE) return LL.ONCHANGE(); if (scheduleType === ScheduleFlag.SCHEDULE_IMMEDIATE) return LL.IMMEDIATE(); return LL.TIME(1); }, [scheduleType, LL]); return ( {creating ? `${LL.ADD(1)} ${LL.NEW(0)}` : LL.EDIT()}  {LL.SCHEDULE(1)} {LL.SCHEDULE(0)} {LL.TIMER(0)} {LL.ONCHANGE()} {LL.CONDITION()} {LL.IMMEDIATE()} {isDaySchedule && ( {DAY_FLAGS.map(({ value, flag }) => ( {DayOfWeekButton(flag)} ))} )} {!isImmediateSchedule && ( <> } label={LL.ACTIVE()} /> {needsTimeField ? ( <> {isTimerSchedule && ( {LL.SCHEDULER_HELP_2()} )} ) : ( )} )} {!creating && ( )} {isImmediateSchedule && editItem.cmd !== '' && ( )} ); }; export default SchedulerDialog;