mirror of
https://github.com/langgenius/dify.git
synced 2026-05-05 17:18:40 +08:00
feat: range picker
This commit is contained in:
parent
92ab453a5a
commit
56dd17cdaa
@ -4,13 +4,17 @@ import type { FC } from 'react'
|
|||||||
import React, { useCallback } from 'react'
|
import React, { useCallback } from 'react'
|
||||||
import { SimpleSelect } from '@/app/components/base/select'
|
import { SimpleSelect } from '@/app/components/base/select'
|
||||||
import type { Item } from '@/app/components/base/select'
|
import type { Item } from '@/app/components/base/select'
|
||||||
|
import type { Dayjs } from 'dayjs'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { HourglassShape } from '@/app/components/base/icons/src/vender/other'
|
import { HourglassShape } from '@/app/components/base/icons/src/vender/other'
|
||||||
import TimePicker from '@/app/components/base/date-and-time-picker/time-picker'
|
|
||||||
import { noop } from 'lodash-es'
|
import { noop } from 'lodash-es'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import { RiArrowDownSLine, RiCheckLine } from '@remixicon/react'
|
import { RiArrowDownSLine, RiCalendarLine, RiCheckLine } from '@remixicon/react'
|
||||||
|
import DatePicker from '@/app/components/base/date-and-time-picker/date-picker'
|
||||||
|
import 'dayjs/locale/zh-cn'
|
||||||
|
import { formatToLocalTime } from '@/utils/format'
|
||||||
|
import { useI18N } from '@/context/i18n'
|
||||||
|
|
||||||
const today = dayjs()
|
const today = dayjs()
|
||||||
|
|
||||||
@ -68,6 +72,17 @@ const TimeRangePicker: FC<Props> = ({
|
|||||||
}
|
}
|
||||||
onSelect({ query: period!, name })
|
onSelect({ query: period!, name })
|
||||||
}, [onSelect])
|
}, [onSelect])
|
||||||
|
|
||||||
|
const { locale } = useI18N()
|
||||||
|
|
||||||
|
const renderDate = useCallback(({ value, handleClickTrigger, isOpen }: { value?: Dayjs, handleClickTrigger: () => void, isOpen: boolean }) => {
|
||||||
|
return (
|
||||||
|
<div className={cn('system-sm-regular flex h-7 cursor-pointer items-center rounded-lg px-1 text-components-input-text-filled hover:bg-state-base-hover', isOpen && 'bg-state-base-hover')} onClick={handleClickTrigger}>
|
||||||
|
{value ? formatToLocalTime(value, locale, 'MMM D') : ''}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}, [locale])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex items-center'>
|
<div className='flex items-center'>
|
||||||
<SimpleSelect
|
<SimpleSelect
|
||||||
@ -83,10 +98,23 @@ const TimeRangePicker: FC<Props> = ({
|
|||||||
renderOption={renderOption}
|
renderOption={renderOption}
|
||||||
/>
|
/>
|
||||||
<HourglassShape className='h-3.5 w-2 text-components-input-bg-normal' />
|
<HourglassShape className='h-3.5 w-2 text-components-input-bg-normal' />
|
||||||
<TimePicker
|
<div className='flex h-8 items-center space-x-0.5 rounded-lg bg-components-input-bg-normal px-2'>
|
||||||
value={today}
|
<RiCalendarLine className='size-3.5 text-text-tertiary' />
|
||||||
onChange={noop}
|
<DatePicker
|
||||||
/>
|
value={today}
|
||||||
|
onChange={noop}
|
||||||
|
renderTrigger={renderDate}
|
||||||
|
needTimePicker={false}
|
||||||
|
/>
|
||||||
|
<span className='system-sm-regular text-text-tertiary'>-</span>
|
||||||
|
<DatePicker
|
||||||
|
value={today}
|
||||||
|
onChange={noop}
|
||||||
|
renderTrigger={renderDate}
|
||||||
|
needTimePicker={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,9 +8,10 @@ const Calendar: FC<CalendarProps> = ({
|
|||||||
selectedDate,
|
selectedDate,
|
||||||
onDateClick,
|
onDateClick,
|
||||||
wrapperClassName,
|
wrapperClassName,
|
||||||
|
getIsDateDisabled,
|
||||||
}) => {
|
}) => {
|
||||||
return <div className={wrapperClassName}>
|
return <div className={wrapperClassName}>
|
||||||
<DaysOfWeek/>
|
<DaysOfWeek />
|
||||||
<div className='grid grid-cols-7 gap-0.5 p-2'>
|
<div className='grid grid-cols-7 gap-0.5 p-2'>
|
||||||
{
|
{
|
||||||
days.map(day => <CalendarItem
|
days.map(day => <CalendarItem
|
||||||
@ -18,6 +19,7 @@ const Calendar: FC<CalendarProps> = ({
|
|||||||
day={day}
|
day={day}
|
||||||
selectedDate={selectedDate}
|
selectedDate={selectedDate}
|
||||||
onClick={onDateClick}
|
onClick={onDateClick}
|
||||||
|
isDisabled={getIsDateDisabled ? getIsDateDisabled(day.date) : false}
|
||||||
/>)
|
/>)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -7,6 +7,7 @@ const Item: FC<CalendarItemProps> = ({
|
|||||||
day,
|
day,
|
||||||
selectedDate,
|
selectedDate,
|
||||||
onClick,
|
onClick,
|
||||||
|
isDisabled,
|
||||||
}) => {
|
}) => {
|
||||||
const { date, isCurrentMonth } = day
|
const { date, isCurrentMonth } = day
|
||||||
const isSelected = selectedDate?.isSame(date, 'date')
|
const isSelected = selectedDate?.isSame(date, 'date')
|
||||||
@ -14,11 +15,12 @@ const Item: FC<CalendarItemProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<button type="button"
|
<button type="button"
|
||||||
onClick={() => onClick(date)}
|
onClick={() => !isDisabled && onClick(date)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'system-sm-medium relative flex items-center justify-center rounded-lg px-1 py-2',
|
'system-sm-medium relative flex items-center justify-center rounded-lg px-1 py-2',
|
||||||
isCurrentMonth ? 'text-text-secondary' : 'text-text-quaternary hover:text-text-secondary',
|
isCurrentMonth ? 'text-text-secondary' : 'text-text-quaternary hover:text-text-secondary',
|
||||||
isSelected ? 'system-sm-medium bg-components-button-primary-bg text-components-button-primary-text' : 'hover:bg-state-base-hover',
|
isSelected ? 'system-sm-medium bg-components-button-primary-bg text-components-button-primary-text' : 'hover:bg-state-base-hover',
|
||||||
|
isDisabled && 'cursor-not-allowed text-text-quaternary hover:bg-transparent',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{date.date()}
|
{date.date()}
|
||||||
|
|||||||
@ -36,6 +36,7 @@ const DatePicker = ({
|
|||||||
renderTrigger,
|
renderTrigger,
|
||||||
triggerWrapClassName,
|
triggerWrapClassName,
|
||||||
popupZIndexClassname = 'z-[11]',
|
popupZIndexClassname = 'z-[11]',
|
||||||
|
getIsDateDisabled,
|
||||||
}: DatePickerProps) => {
|
}: DatePickerProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
@ -270,6 +271,7 @@ const DatePicker = ({
|
|||||||
days={days}
|
days={days}
|
||||||
selectedDate={selectedDate}
|
selectedDate={selectedDate}
|
||||||
onDateClick={handleDateSelect}
|
onDateClick={handleDateSelect}
|
||||||
|
getIsDateDisabled={getIsDateDisabled}
|
||||||
/>
|
/>
|
||||||
) : view === ViewType.yearMonth ? (
|
) : view === ViewType.yearMonth ? (
|
||||||
<YearAndMonthPickerOptions
|
<YearAndMonthPickerOptions
|
||||||
|
|||||||
@ -30,6 +30,7 @@ export type DatePickerProps = {
|
|||||||
renderTrigger?: (props: TriggerProps) => React.ReactNode
|
renderTrigger?: (props: TriggerProps) => React.ReactNode
|
||||||
minuteFilter?: (minutes: string[]) => string[]
|
minuteFilter?: (minutes: string[]) => string[]
|
||||||
popupZIndexClassname?: string
|
popupZIndexClassname?: string
|
||||||
|
getIsDateDisabled?: (date: Dayjs) => boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DatePickerHeaderProps = {
|
export type DatePickerHeaderProps = {
|
||||||
@ -80,12 +81,14 @@ export type CalendarProps = {
|
|||||||
selectedDate: Dayjs | undefined
|
selectedDate: Dayjs | undefined
|
||||||
onDateClick: (date: Dayjs) => void
|
onDateClick: (date: Dayjs) => void
|
||||||
wrapperClassName?: string
|
wrapperClassName?: string
|
||||||
|
getIsDateDisabled?: (date: Dayjs) => boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CalendarItemProps = {
|
export type CalendarItemProps = {
|
||||||
day: Day
|
day: Day
|
||||||
selectedDate: Dayjs | undefined
|
selectedDate: Dayjs | undefined
|
||||||
onClick: (date: Dayjs) => void
|
onClick: (date: Dayjs) => void
|
||||||
|
isDisabled: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TimeOptionsProps = {
|
export type TimeOptionsProps = {
|
||||||
|
|||||||
@ -67,6 +67,7 @@ const translation = {
|
|||||||
quarterToDate: '四半期初から今日まで',
|
quarterToDate: '四半期初から今日まで',
|
||||||
yearToDate: '年初から今日まで',
|
yearToDate: '年初から今日まで',
|
||||||
allTime: 'すべての期間',
|
allTime: 'すべての期間',
|
||||||
|
custom: 'カスタム',
|
||||||
},
|
},
|
||||||
annotation: {
|
annotation: {
|
||||||
all: 'すべて',
|
all: 'すべて',
|
||||||
|
|||||||
@ -1,3 +1,50 @@
|
|||||||
|
import type { Locale } from '@/i18n-config'
|
||||||
|
import type { Dayjs } from 'dayjs'
|
||||||
|
import 'dayjs/locale/de'
|
||||||
|
import 'dayjs/locale/es'
|
||||||
|
import 'dayjs/locale/fa'
|
||||||
|
import 'dayjs/locale/fr'
|
||||||
|
import 'dayjs/locale/hi'
|
||||||
|
import 'dayjs/locale/id'
|
||||||
|
import 'dayjs/locale/it'
|
||||||
|
import 'dayjs/locale/ja'
|
||||||
|
import 'dayjs/locale/ko'
|
||||||
|
import 'dayjs/locale/pl'
|
||||||
|
import 'dayjs/locale/pt-br'
|
||||||
|
import 'dayjs/locale/ro'
|
||||||
|
import 'dayjs/locale/ru'
|
||||||
|
import 'dayjs/locale/sl'
|
||||||
|
import 'dayjs/locale/th'
|
||||||
|
import 'dayjs/locale/tr'
|
||||||
|
import 'dayjs/locale/uk'
|
||||||
|
import 'dayjs/locale/vi'
|
||||||
|
import 'dayjs/locale/zh-cn'
|
||||||
|
import 'dayjs/locale/zh-tw'
|
||||||
|
|
||||||
|
const localeMap: Record<Locale, string> = {
|
||||||
|
'en-US': 'en',
|
||||||
|
'zh-Hans': 'zh-cn',
|
||||||
|
'zh-Hant': 'zh-tw',
|
||||||
|
'pt-BR': 'pt-br',
|
||||||
|
'es-ES': 'es',
|
||||||
|
'fr-FR': 'fr',
|
||||||
|
'de-DE': 'de',
|
||||||
|
'ja-JP': 'ja',
|
||||||
|
'ko-KR': 'ko',
|
||||||
|
'ru-RU': 'ru',
|
||||||
|
'it-IT': 'it',
|
||||||
|
'th-TH': 'th',
|
||||||
|
'id-ID': 'id',
|
||||||
|
'uk-UA': 'uk',
|
||||||
|
'vi-VN': 'vi',
|
||||||
|
'ro-RO': 'ro',
|
||||||
|
'pl-PL': 'pl',
|
||||||
|
'hi-IN': 'hi',
|
||||||
|
'tr-TR': 'tr',
|
||||||
|
'fa-IR': 'fa',
|
||||||
|
'sl-SI': 'sl',
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formats a number with comma separators.
|
* Formats a number with comma separators.
|
||||||
* @example formatNumber(1234567) will return '1,234,567'
|
* @example formatNumber(1234567) will return '1,234,567'
|
||||||
@ -90,3 +137,7 @@ export const formatNumberAbbreviated = (num: number) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const formatToLocalTime = (time: Dayjs, local: string, format: string) => {
|
||||||
|
return time.locale(localeMap[local] ?? 'en').format(format)
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user