From 47d596af0864268a5077f34c0de1eef2dcf58593 Mon Sep 17 00:00:00 2001 From: Mo Tarbin Date: Fri, 3 Jul 2026 01:58:31 -0400 Subject: [PATCH] feat: enhance DueDatePickerField with quick date and time selection options --- src/views/components/DueDatePickerField.jsx | 754 ++++++++++++++------ 1 file changed, 539 insertions(+), 215 deletions(-) diff --git a/src/views/components/DueDatePickerField.jsx b/src/views/components/DueDatePickerField.jsx index 6410f8e..bb0685a 100644 --- a/src/views/components/DueDatePickerField.jsx +++ b/src/views/components/DueDatePickerField.jsx @@ -1,16 +1,31 @@ import { + Bedtime, CalendarMonth, Close, + EventNote, + LightMode, NextWeek, + NightsStay, Today, WbSunny, + WbTwilight, Weekend, } from '@mui/icons-material' -import { Box, Button, IconButton, Input, Sheet, Typography } from '@mui/joy' -import { ClickAwayListener, Popper } from '@mui/material' +import { + Box, + Button, + Checkbox, + IconButton, + Input, + List, + ListItem, + Typography, +} from '@mui/joy' import moment from 'moment' -import { useEffect, useMemo, useRef, useState } from 'react' -import { Z_INDEX } from '../../constants/zIndex' +import { useEffect, useMemo, useState } from 'react' +import Calendar from 'react-calendar' +import { useLocalization } from '../../contexts/LocalizationContext' +import { useResponsiveModal } from '../../hooks/useResponsiveModal' const DueDatePickerField = ({ dueDateOnly, @@ -24,7 +39,34 @@ const DueDatePickerField = ({ size = 'sm', }) => { const [isOpen, setIsOpen] = useState(false) - const buttonRef = useRef(null) + 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 (isOpen) { + setLocalDueDateOnly(dueDateOnly) + setLocalDueTime(dueTime) + setLocalUseCustomTime(useCustomTime) + } + }, [isOpen, dueDateOnly, dueTime, useCustomTime]) + + const calendarType = + firstDayOfWeek === 1 + ? 'iso8601' + : firstDayOfWeek === 6 + ? 'islamic' + : 'gregory' + + const pillListSx = { + '--List-gap': '8px', + '--ListItem-radius': '20px', + } const getQuickScheduleDate = option => { const now = new Date() @@ -50,6 +92,11 @@ const DueDatePickerField = ({ 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 } @@ -57,25 +104,43 @@ const DueDatePickerField = ({ const handleQuickSchedule = option => { const date = getQuickScheduleDate(option) - const dateStr = date.toISOString().split('T')[0] - onDueDateChange?.({ target: { value: dateStr } }) - setIsOpen(false) + setLocalDueDateOnly(date.toISOString().split('T')[0]) } - useEffect(() => { - if (!isOpen) return - - const handleEscape = event => { - if (event.key === 'Escape') { - setIsOpen(false) - } + const handleQuickTime = timeStr => { + // Tap the active chip again to deselect it + if (localUseCustomTime && localDueTime === timeStr) { + setLocalUseCustomTime(false) + setLocalDueTime(null) + return } - - document.addEventListener('keydown', handleEscape) - return () => { - document.removeEventListener('keydown', handleEscape) + if (!localDueDateOnly) { + setLocalDueDateOnly(new Date().toISOString().split('T')[0]) } - }, [isOpen]) + 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 = () => { + onDueDateChange?.({ target: { value: localDueDateOnly || '' } }) + onUseCustomTimeChange?.(localUseCustomTime) + if (localUseCustomTime && localDueTime) { + onDueTimeChange?.({ target: { value: localDueTime } }) + } else { + onDueTimeChange?.({ target: { value: '' } }) + } + setIsOpen(false) + } const hasDueDate = Boolean(dueDateOnly) const shouldShowLabel = hasDueDate || emptyDisplay === 'icon-text' @@ -94,210 +159,469 @@ const DueDatePickerField = ({ }, [dueDateOnly, dueTime, useCustomTime]) return ( - - - {hasDueDate && onClear && ( - { - e.stopPropagation() - onClear?.() - }} - sx={{ - position: 'absolute', - top: -12, - right: -16, - zIndex: 10, - maxHeight: 18, - maxWidth: 18, - borderRadius: '50%', - '&:hover': { - bgcolor: 'danger.softBg', - }, - }} - > - - - )} + + + {dueDateLabel} + + + {hasDueDate && onClear && ( + { + e.stopPropagation() + onClear?.() + }} + sx={{ + position: 'absolute', + top: -12, + right: -16, + zIndex: 10, + maxHeight: 18, + maxWidth: 18, + borderRadius: '50%', + '&:hover': { + bgcolor: 'danger.softBg', + }, + }} + > + + + )} + - {isOpen && ( - - setIsOpen(false)}> - - - Due Date - - - - - - - - - - Due time (optional) - - { - if (!useCustomTime) { - onUseCustomTimeChange?.(true) - } - onDueTimeChange?.(e) + setIsOpen(false)} + title='Due Date' + footer={ + + {hasDueDate && ( + - - - {hasDueDate && ( - - )} - - - - )} - + sx={{ mr: 'auto' }} + > + Remove + + )} + + + + } + > + + {/* Date shortcuts */} + + Quick date + + + {[ + { + key: 'today', + label: 'Today', + icon: , + }, + { + key: 'tomorrow', + label: 'Tomorrow', + icon: , + }, + { + key: 'weekend', + label: 'Weekend', + icon: , + }, + { + key: 'next-week', + label: 'Next week', + icon: , + }, + { + key: 'next-month', + label: 'Next month', + icon: , + }, + ].map(opt => { + const dateStr = getQuickScheduleDate(opt.key) + .toISOString() + .split('T')[0] + return ( + + handleQuickSchedule(opt.key)} + overlay + disableIcon + variant='soft' + label={ + + {opt.icon} + {opt.label} + + } + /> + + ) + })} + + + {/* Time shortcuts */} + + Quick time + + + {[ + { + time: '09:00', + label: 'Morning', + icon: , + }, + { + time: '12:00', + label: 'Noon', + icon: , + }, + { + time: '15:00', + label: 'Afternoon', + icon: , + }, + { + time: '18:00', + label: 'Evening', + icon: , + }, + { + time: '22:00', + label: '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()] + } + /> + + + Custom time + + + + + + + + + ) }