import React, { FC, memo, useState, useEffect, useMemo, useCallback, } from '../../lib/teact/teact'; import buildClassName from '../../util/buildClassName'; import { formatMonthAndYear, formatHumanDate, formatTime, } from '../../util/dateFormat'; import { IS_MOBILE_SCREEN } from '../../util/environment'; import useLang, { LangFn } from '../../hooks/useLang'; import Modal from '../ui/Modal'; import Button from '../ui/Button'; import './CalendarModal.scss'; export type OwnProps = { selectedAt?: number; maxAt?: number; isFutureMode?: boolean; isPastMode?: boolean; isOpen: boolean; withTimePicker?: boolean; submitButtonLabel?: string; secondButtonLabel?: string; onClose: () => void; onSubmit: (date: Date) => void; onSecondButtonClick?: NoneToVoidFunction; }; const WEEKDAY_LETTERS = ['M', 'T', 'W', 'T', 'F', 'S', 'S']; const CalendarModal: FC = ({ selectedAt, maxAt, isFutureMode, isPastMode, isOpen, withTimePicker, submitButtonLabel, secondButtonLabel, onClose, onSubmit, onSecondButtonClick, }) => { const lang = useLang(); const now = new Date(); const defaultSelectedDate = useMemo(() => (selectedAt ? new Date(selectedAt) : new Date()), [selectedAt]); const maxDate = maxAt ? new Date(maxAt) : undefined; const [selectedDate, setSelectedDate] = useState(defaultSelectedDate); const [selectedHours, setSelectedHours] = useState( formatInputTime(defaultSelectedDate.getHours()), ); const [selectedMinutes, setSelectedMinutes] = useState( formatInputTime(defaultSelectedDate.getMinutes()), ); const currentYear = selectedDate.getFullYear(); const currentMonth = selectedDate.getMonth(); const currentDate = selectedDate.getDate(); useEffect(() => { if (isOpen) { setSelectedDate(defaultSelectedDate); } }, [isOpen, defaultSelectedDate]); const shouldDisableNextMonth = (isPastMode && currentYear >= now.getFullYear() && currentMonth >= now.getMonth()) || (maxDate && currentYear >= maxDate.getFullYear() && currentMonth >= maxDate.getMonth()); const shouldDisablePrevMonth = isFutureMode && currentYear <= now.getFullYear() && currentMonth <= now.getMonth(); const calendarGrid = useMemo(() => ( buildCalendarGrid(currentYear, currentMonth) ), [currentMonth, currentYear]); function handlePrevMonth() { setSelectedDate((d) => { const dateCopy = new Date(d); dateCopy.setMonth(dateCopy.getMonth() - 1); return dateCopy; }); } function handleNextMonth() { setSelectedDate((d) => { const dateCopy = new Date(d); dateCopy.setMonth(dateCopy.getMonth() + 1); return dateCopy; }); } function handleDateSelect(date: number) { setSelectedDate((d) => { const dateCopy = new Date(d); dateCopy.setDate(date); return dateCopy; }); } function handleSubmit() { onSubmit(selectedDate); } const handleChangeHours = useCallback((e: React.ChangeEvent) => { const value = e.target.value.replace(/[^\d]+/g, ''); if (!value.length) { setSelectedHours(''); e.target.value = ''; return; } const hours = Math.max(0, Math.min(Number(value), 23)); const date = new Date(selectedDate.getTime()); date.setHours(hours); setSelectedDate(date); const hoursStr = formatInputTime(hours); setSelectedHours(hoursStr); e.target.value = hoursStr; }, [selectedDate]); const handleChangeMinutes = useCallback((e: React.ChangeEvent) => { const value = e.target.value.replace(/[^\d]+/g, ''); if (!value.length) { setSelectedMinutes(''); e.target.value = ''; return; } const minutes = Math.max(0, Math.min(Number(value), 59)); const date = new Date(selectedDate.getTime()); date.setMinutes(minutes); setSelectedDate(date); const minutesStr = formatInputTime(minutes); setSelectedMinutes(minutesStr); e.target.value = minutesStr; }, [selectedDate]); function renderTimePicker() { return (
:
); } return (

{formatMonthAndYear(lang, selectedDate, IS_MOBILE_SCREEN)}

{WEEKDAY_LETTERS.map((letter) => (
{letter}
))} {calendarGrid.map((gridDate) => (
handleDateSelect(gridDate)} className={buildClassName( 'day-button', isDisabledDay( currentYear, currentMonth, gridDate, isFutureMode ? now : undefined, isPastMode ? now : maxDate, ) ? 'disabled' : `${gridDate ? 'clickable' : ''}`, gridDate === currentDate && 'selected', )} > {!!gridDate && ( {gridDate} )}
))}
{withTimePicker && renderTimePicker()}
{secondButtonLabel && ( )}
); }; function buildCalendarGrid(year: number, month: number) { const grid: number[] = []; const date = new Date(); date.setFullYear(year); date.setMonth(month); date.setDate(1); const monthStartDay = date.getDay(); // Fill empty cells for (let i = 1; i < monthStartDay; i++) { grid.push(0); } while (date.getMonth() === month) { const gridDate = date.getDate(); grid.push(gridDate); date.setDate(gridDate + 1); } return grid; } function isDisabledDay(year: number, month: number, day: number, minDate?: Date, maxDate?: Date) { const selectedDay = new Date(year, month, day, 0, 0, 0, 0); const fixedMinDate = minDate && new Date(minDate.getFullYear(), minDate.getMonth(), minDate.getDate(), 0, 0, 0, 0); const fixedMaxDate = maxDate && new Date(maxDate.getFullYear(), maxDate.getMonth(), maxDate.getDate(), 0, 0, 0, 0); if (fixedMaxDate && selectedDay > fixedMaxDate) { return true; } else if (fixedMinDate && selectedDay < fixedMinDate) { return true; } return false; } function formatInputTime(value: string | number) { return String(value).padStart(2, '0'); } function formatSubmitLabel(lang: LangFn, date: Date) { const day = formatHumanDate(lang, date, true); if (day === 'Today') { return lang('Conversation.ScheduleMessage.SendToday', formatTime(date)); } return lang('Conversation.ScheduleMessage.SendOn', day).replace('%@', formatTime(date)); } export default memo(CalendarModal);