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