| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311 | 
							- import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
 
- import { RiCalendarLine, RiCloseCircleFill } from '@remixicon/react'
 
- import cn from '@/utils/classnames'
 
- import type { DatePickerProps, Period } from '../types'
 
- import { ViewType } from '../types'
 
- import type { Dayjs } from 'dayjs'
 
- import dayjs, {
 
-   clearMonthMapCache,
 
-   cloneTime,
 
-   getDateWithTimezone,
 
-   getDaysInMonth,
 
-   getHourIn12Hour,
 
- } from '../utils/dayjs'
 
- import {
 
-   PortalToFollowElem,
 
-   PortalToFollowElemContent,
 
-   PortalToFollowElemTrigger,
 
- } from '@/app/components/base/portal-to-follow-elem'
 
- import DatePickerHeader from './header'
 
- import Calendar from '../calendar'
 
- import DatePickerFooter from './footer'
 
- import YearAndMonthPickerHeader from '../year-and-month-picker/header'
 
- import YearAndMonthPickerOptions from '../year-and-month-picker/options'
 
- import YearAndMonthPickerFooter from '../year-and-month-picker/footer'
 
- import TimePickerHeader from '../time-picker/header'
 
- import TimePickerOptions from '../time-picker/options'
 
- import { useTranslation } from 'react-i18next'
 
- const DatePicker = ({
 
-   value,
 
-   timezone,
 
-   onChange,
 
-   onClear,
 
-   placeholder,
 
-   needTimePicker = true,
 
-   renderTrigger,
 
-   triggerWrapClassName,
 
-   popupZIndexClassname = 'z-[11]',
 
- }: DatePickerProps) => {
 
-   const { t } = useTranslation()
 
-   const [isOpen, setIsOpen] = useState(false)
 
-   const [view, setView] = useState(ViewType.date)
 
-   const containerRef = useRef<HTMLDivElement>(null)
 
-   const isInitial = useRef(true)
 
-   const inputValue = useRef(value ? value.tz(timezone) : undefined).current
 
-   const defaultValue = useRef(getDateWithTimezone({ timezone })).current
 
-   const [currentDate, setCurrentDate] = useState(inputValue || defaultValue)
 
-   const [selectedDate, setSelectedDate] = useState(inputValue)
 
-   const [selectedMonth, setSelectedMonth] = useState((inputValue || defaultValue).month())
 
-   const [selectedYear, setSelectedYear] = useState((inputValue || defaultValue).year())
 
-   useEffect(() => {
 
-     const handleClickOutside = (event: MouseEvent) => {
 
-       if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
 
-         setIsOpen(false)
 
-         setView(ViewType.date)
 
-       }
 
-     }
 
-     document.addEventListener('mousedown', handleClickOutside)
 
-     return () => document.removeEventListener('mousedown', handleClickOutside)
 
-   }, [])
 
-   useEffect(() => {
 
-     if (isInitial.current) {
 
-       isInitial.current = false
 
-       return
 
-     }
 
-     clearMonthMapCache()
 
-     if (value) {
 
-       const newValue = getDateWithTimezone({ date: value, timezone })
 
-       setCurrentDate(newValue)
 
-       setSelectedDate(newValue)
 
-       onChange(newValue)
 
-     }
 
-     else {
 
-       setCurrentDate(prev => getDateWithTimezone({ date: prev, timezone }))
 
-       setSelectedDate(prev => prev ? getDateWithTimezone({ date: prev, timezone }) : undefined)
 
-     }
 
-   // eslint-disable-next-line react-hooks/exhaustive-deps
 
-   }, [timezone])
 
-   const handleClickTrigger = (e: React.MouseEvent) => {
 
-     e.stopPropagation()
 
-     if (isOpen) {
 
-       setIsOpen(false)
 
-       return
 
-     }
 
-     setView(ViewType.date)
 
-     setIsOpen(true)
 
-     if (value) {
 
-       setCurrentDate(value)
 
-       setSelectedDate(value)
 
-     }
 
-   }
 
-   const handleClear = (e: React.MouseEvent) => {
 
-     e.stopPropagation()
 
-     setSelectedDate(undefined)
 
-     if (!isOpen)
 
-       onClear()
 
-   }
 
-   const days = useMemo(() => {
 
-     return getDaysInMonth(currentDate)
 
-   }, [currentDate])
 
-   const handleClickNextMonth = useCallback(() => {
 
-     setCurrentDate(currentDate.clone().add(1, 'month'))
 
-   }, [currentDate])
 
-   const handleClickPrevMonth = useCallback(() => {
 
-     setCurrentDate(currentDate.clone().subtract(1, 'month'))
 
-   }, [currentDate])
 
-   const handleDateSelect = useCallback((day: Dayjs) => {
 
-     const newDate = cloneTime(day, selectedDate || getDateWithTimezone({ timezone }))
 
-     setCurrentDate(newDate)
 
-     setSelectedDate(newDate)
 
-   }, [selectedDate, timezone])
 
-   const handleSelectCurrentDate = () => {
 
-     const newDate = getDateWithTimezone({ timezone })
 
-     setCurrentDate(newDate)
 
-     setSelectedDate(newDate)
 
-     onChange(newDate)
 
-     setIsOpen(false)
 
-   }
 
-   const handleConfirmDate = () => {
 
-     // debugger
 
-     console.log(selectedDate, selectedDate?.tz(timezone))
 
-     onChange(selectedDate ? selectedDate.tz(timezone) : undefined)
 
-     setIsOpen(false)
 
-   }
 
-   const handleClickTimePicker = () => {
 
-     if (view === ViewType.date) {
 
-       setView(ViewType.time)
 
-       return
 
-     }
 
-     if (view === ViewType.time)
 
-       setView(ViewType.date)
 
-   }
 
-   const handleTimeSelect = (hour: string, minute: string, period: Period) => {
 
-     const newTime = cloneTime(dayjs(), dayjs(`1/1/2000 ${hour}:${minute} ${period}`))
 
-     setSelectedDate((prev) => {
 
-       return prev ? cloneTime(prev, newTime) : newTime
 
-     })
 
-   }
 
-   const handleSelectHour = useCallback((hour: string) => {
 
-     const selectedTime = selectedDate || getDateWithTimezone({ timezone })
 
-     handleTimeSelect(hour, selectedTime.minute().toString().padStart(2, '0'), selectedTime.format('A') as Period)
 
-   }, [selectedDate, timezone])
 
-   const handleSelectMinute = useCallback((minute: string) => {
 
-     const selectedTime = selectedDate || getDateWithTimezone({ timezone })
 
-     handleTimeSelect(getHourIn12Hour(selectedTime).toString().padStart(2, '0'), minute, selectedTime.format('A') as Period)
 
-   }, [selectedDate, timezone])
 
-   const handleSelectPeriod = useCallback((period: Period) => {
 
-     const selectedTime = selectedDate || getDateWithTimezone({ timezone })
 
-     handleTimeSelect(getHourIn12Hour(selectedTime).toString().padStart(2, '0'), selectedTime.minute().toString().padStart(2, '0'), period)
 
-   }, [selectedDate, timezone])
 
-   const handleOpenYearMonthPicker = () => {
 
-     setSelectedMonth(currentDate.month())
 
-     setSelectedYear(currentDate.year())
 
-     setView(ViewType.yearMonth)
 
-   }
 
-   const handleCloseYearMonthPicker = useCallback(() => {
 
-     setView(ViewType.date)
 
-   }, [])
 
-   const handleMonthSelect = useCallback((month: number) => {
 
-     setSelectedMonth(month)
 
-   }, [])
 
-   const handleYearSelect = useCallback((year: number) => {
 
-     setSelectedYear(year)
 
-   }, [])
 
-   const handleYearMonthCancel = useCallback(() => {
 
-     setView(ViewType.date)
 
-   }, [])
 
-   const handleYearMonthConfirm = () => {
 
-     setCurrentDate(prev => prev.clone().month(selectedMonth).year(selectedYear))
 
-     setView(ViewType.date)
 
-   }
 
-   const timeFormat = needTimePicker ? 'MMMM D, YYYY hh:mm A' : 'MMMM D, YYYY'
 
-   const displayValue = value?.format(timeFormat) || ''
 
-   const displayTime = selectedDate?.format('hh:mm A') || '--:-- --'
 
-   const placeholderDate = isOpen && selectedDate ? selectedDate.format(timeFormat) : (placeholder || t('time.defaultPlaceholder'))
 
-   return (
 
-     <PortalToFollowElem
 
-       open={isOpen}
 
-       onOpenChange={setIsOpen}
 
-       placement='bottom-end'
 
-     >
 
-       <PortalToFollowElemTrigger className={triggerWrapClassName}>
 
-         {renderTrigger ? (renderTrigger({
 
-           value,
 
-           selectedDate,
 
-           isOpen,
 
-           handleClear,
 
-           handleClickTrigger,
 
-         })) : (
 
-           <div
 
-             className='group flex w-[252px] cursor-pointer items-center gap-x-0.5 rounded-lg bg-components-input-bg-normal px-2 py-1 hover:bg-state-base-hover-alt'
 
-             onClick={handleClickTrigger}
 
-           >
 
-             <input
 
-               className='system-xs-regular flex-1 cursor-pointer appearance-none truncate bg-transparent p-1
 
-             text-components-input-text-filled outline-none placeholder:text-components-input-text-placeholder'
 
-               readOnly
 
-               value={isOpen ? '' : displayValue}
 
-               placeholder={placeholderDate}
 
-             />
 
-             <RiCalendarLine className={cn(
 
-               'h-4 w-4 shrink-0 text-text-quaternary',
 
-               isOpen ? 'text-text-secondary' : 'group-hover:text-text-secondary',
 
-               (displayValue || (isOpen && selectedDate)) && 'group-hover:hidden',
 
-             )} />
 
-             <RiCloseCircleFill
 
-               className={cn(
 
-                 'hidden h-4 w-4 shrink-0 text-text-quaternary',
 
-                 (displayValue || (isOpen && selectedDate)) && 'hover:text-text-secondary group-hover:inline-block',
 
-               )}
 
-               onClick={handleClear}
 
-             />
 
-           </div>
 
-         )}
 
-       </PortalToFollowElemTrigger>
 
-       <PortalToFollowElemContent className={popupZIndexClassname}>
 
-         <div className='mt-1 w-[252px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg shadow-shadow-shadow-5'>
 
-           {/* Header */}
 
-           {view === ViewType.date ? (
 
-             <DatePickerHeader
 
-               handleOpenYearMonthPicker={handleOpenYearMonthPicker}
 
-               currentDate={currentDate}
 
-               onClickNextMonth={handleClickNextMonth}
 
-               onClickPrevMonth={handleClickPrevMonth}
 
-             />
 
-           ) : view === ViewType.yearMonth ? (
 
-             <YearAndMonthPickerHeader
 
-               selectedYear={selectedYear}
 
-               selectedMonth={selectedMonth}
 
-               onClick={handleCloseYearMonthPicker}
 
-             />
 
-           ) : (
 
-             <TimePickerHeader />
 
-           )}
 
-           {/* Content */}
 
-           {
 
-             view === ViewType.date ? (
 
-               <Calendar
 
-                 days={days}
 
-                 selectedDate={selectedDate}
 
-                 onDateClick={handleDateSelect}
 
-               />
 
-             ) : view === ViewType.yearMonth ? (
 
-               <YearAndMonthPickerOptions
 
-                 selectedMonth={selectedMonth}
 
-                 selectedYear={selectedYear}
 
-                 handleMonthSelect={handleMonthSelect}
 
-                 handleYearSelect={handleYearSelect}
 
-               />
 
-             ) : (
 
-               <TimePickerOptions
 
-                 selectedTime={selectedDate}
 
-                 handleSelectHour={handleSelectHour}
 
-                 handleSelectMinute={handleSelectMinute}
 
-                 handleSelectPeriod={handleSelectPeriod}
 
-               />
 
-             )
 
-           }
 
-           {/* Footer */}
 
-           {
 
-             [ViewType.date, ViewType.time].includes(view) ? (
 
-               <DatePickerFooter
 
-                 needTimePicker={needTimePicker}
 
-                 displayTime={displayTime}
 
-                 view={view}
 
-                 handleClickTimePicker={handleClickTimePicker}
 
-                 handleSelectCurrentDate={handleSelectCurrentDate}
 
-                 handleConfirmDate={handleConfirmDate}
 
-               />
 
-             ) : (
 
-               <YearAndMonthPickerFooter
 
-                 handleYearMonthCancel={handleYearMonthCancel}
 
-                 handleYearMonthConfirm={handleYearMonthConfirm}
 
-               />
 
-             )
 
-           }
 
-         </div>
 
-       </PortalToFollowElemContent>
 
-     </PortalToFollowElem>
 
-   )
 
- }
 
- export default DatePicker
 
 
  |