import { Bedtime, EventNote, LightMode, NextWeek, NightsStay, Today, WbSunny, WbTwilight, Weekend, } from '@mui/icons-material' import { Box, Button, Checkbox, Input, List, ListItem, Typography, } from '@mui/joy' import moment from 'moment' import { useEffect, useState } from 'react' import Calendar from 'react-calendar' import { useTranslation } from 'react-i18next' import ModalActions from '../../components/common/ModalActions' import { useLocalization } from '../../contexts/LocalizationContext' import { useResponsiveModal } from '../../hooks/useResponsiveModal' // Split a date-ish value (ISO string / Date) into the parts this picker edits. export const splitDueDate = value => { if (!value) { return { dueDateOnly: null, dueTime: null, useCustomTime: false } } const m = moment(value) if (!m.isValid()) { return { dueDateOnly: null, dueTime: null, useCustomTime: false } } const time = m.format('HH:mm') return { dueDateOnly: m.format('YYYY-MM-DD'), dueTime: time, // Midnight is how a date-only value round-trips, so treat it as "anytime" useCustomTime: time !== '00:00', } } // Inverse of splitDueDate — returns a Date, or null when there is no due date. export const combineDueDate = ({ dueDateOnly, dueTime, useCustomTime }) => { if (!dueDateOnly) return null const time = useCustomTime && dueTime ? dueTime : '00:00' return moment(`${dueDateOnly} ${time}`, 'YYYY-MM-DD HH:mm').toDate() } export const getQuickScheduleDate = option => { const now = new Date() const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()) switch (option) { case 'today': return today case 'tomorrow': { const tomorrow = new Date(today) tomorrow.setDate(today.getDate() + 1) return tomorrow } case 'weekend': { const weekend = new Date(today) const daysUntilSaturday = (6 - today.getDay() + 7) % 7 || 7 weekend.setDate(today.getDate() + daysUntilSaturday) return weekend } case 'next-week': { const nextWeek = new Date(today) const daysUntilMonday = (1 - today.getDay() + 7) % 7 || 7 nextWeek.setDate(today.getDate() + daysUntilMonday) return nextWeek } case 'next-month': { const nextMonth = new Date(today) nextMonth.setMonth(today.getMonth() + 1) return nextMonth } default: return today } } const toDateKey = date => moment(date).format('YYYY-MM-DD') /** * The shared due-date picker UI (quick dates, quick times, calendar, custom * time). Used both by DueDatePickerField and by anything that needs to * reschedule a task — task cards, swipe actions, action menus. */ const DueDatePickerModal = ({ applyLabel, dueDateOnly, dueTime, onApply, onClose, onRemove, open, title, useCustomTime, }) => { const { t } = useTranslation('chores') const { ResponsiveModal } = useResponsiveModal() const { firstDayOfWeek } = useLocalization() // Local buffered state — only committed on Apply const [localDueDateOnly, setLocalDueDateOnly] = useState(dueDateOnly) const [localDueTime, setLocalDueTime] = useState(dueTime) const [localUseCustomTime, setLocalUseCustomTime] = useState(useCustomTime) // Sync local state from props whenever the modal opens useEffect(() => { if (open) { setLocalDueDateOnly(dueDateOnly) setLocalDueTime(dueTime) setLocalUseCustomTime(useCustomTime) } }, [open, dueDateOnly, dueTime, useCustomTime]) const calendarType = firstDayOfWeek === 1 ? 'iso8601' : firstDayOfWeek === 6 ? 'islamic' : 'gregory' const pillListSx = { '--List-gap': '8px', '--ListItem-radius': '20px', } const handleQuickSchedule = option => { setLocalDueDateOnly(toDateKey(getQuickScheduleDate(option))) } const handleQuickTime = timeStr => { // Tap the active chip again to deselect it if (localUseCustomTime && localDueTime === timeStr) { setLocalUseCustomTime(false) setLocalDueTime(null) return } if (!localDueDateOnly) { setLocalDueDateOnly(toDateKey(new Date())) } setLocalUseCustomTime(true) setLocalDueTime(timeStr) } const handleCalendarChange = selected => { if (!selected || Array.isArray(selected)) return setLocalDueDateOnly(moment(selected).format('YYYY-MM-DD')) } const handleLocalTimeInputChange = e => { setLocalUseCustomTime(true) setLocalDueTime(e.target.value) } const handleSave = () => { onApply?.({ dueDateOnly: localDueDateOnly || null, dueTime: localUseCustomTime ? localDueTime || null : null, useCustomTime: Boolean(localUseCustomTime && localDueTime), }) } return ( } > {/* Date shortcuts */} {t('duePicker.quickDate')} {[ { key: 'today', label: t('duePicker.today'), icon: , }, { key: 'tomorrow', label: t('duePicker.tomorrow'), icon: , }, { key: 'weekend', label: t('duePicker.weekend'), icon: , }, { key: 'next-week', label: t('duePicker.nextWeek'), icon: , }, { key: 'next-month', label: t('duePicker.nextMonth'), icon: , }, ].map(opt => { const dateStr = toDateKey(getQuickScheduleDate(opt.key)) return ( handleQuickSchedule(opt.key)} overlay disableIcon variant='soft' label={ {opt.icon} {opt.label} } /> ) })} {/* Time shortcuts */} {t('duePicker.quickTime')} {[ { time: '09:00', label: t('duePicker.morning'), icon: , }, { time: '12:00', label: t('duePicker.noon'), icon: , }, { time: '15:00', label: t('duePicker.afternoon'), icon: , }, { time: '18:00', label: t('duePicker.evening'), icon: , }, { time: '22:00', label: t('duePicker.night'), icon: , }, ].map(opt => ( handleQuickTime(opt.time)} overlay disableIcon variant='soft' label={ {opt.icon} {opt.label} } /> ))} ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'][date.getDay()] } formatMonth={(locale, date) => [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', ][date.getMonth()] } /> {t('duePicker.customTime')} ) } export default DueDatePickerModal