feat: add time zone

This commit is contained in:
Joel 2025-07-10 11:34:05 +08:00
parent 784a236280
commit 95ce7b6f47
6 changed files with 93 additions and 20 deletions

View File

@ -111,6 +111,15 @@ const TimePicker = ({
const displayValue = value?.format(timeFormat) || ''
const placeholderDate = isOpen && selectedTime ? selectedTime.format(timeFormat) : (placeholder || t('time.defaultPlaceholder'))
const inputElem = (
<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}
/>
)
return (
<PortalToFollowElem
open={isOpen}
@ -118,18 +127,16 @@ const TimePicker = ({
placement='bottom-end'
>
<PortalToFollowElemTrigger>
{renderTrigger ? (renderTrigger()) : (
{renderTrigger ? (renderTrigger({
inputElem,
onClick: handleClickTrigger,
isOpen,
})) : (
<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}
/>
{inputElem}
<RiTimeLine className={cn(
'h-4 w-4 shrink-0 text-text-quaternary',
isOpen ? 'text-text-secondary' : 'group-hover:text-text-secondary',

View File

@ -48,13 +48,18 @@ export type DatePickerFooterProps = {
handleConfirmDate: () => void
}
export type TriggerParams = {
isOpen: boolean
inputElem: React.ReactNode
onClick: (e: React.MouseEvent) => void
}
export type TimePickerProps = {
value: Dayjs | undefined
timezone?: string
placeholder?: string
onChange: (date: Dayjs | undefined) => void
onClear: () => void
renderTrigger?: () => React.ReactNode
renderTrigger?: (props: TriggerParams) => React.ReactNode
title?: string
minuteFilter?: (minutes: string[]) => string[]
popupClassName?: string

View File

@ -2,6 +2,7 @@ import dayjs, { type Dayjs } from 'dayjs'
import type { Day } from '../types'
import utc from 'dayjs/plugin/utc'
import timezone from 'dayjs/plugin/timezone'
import tz from '@/utils/timezone.json'
dayjs.extend(utc)
dayjs.extend(timezone)
@ -78,3 +79,14 @@ export const getHourIn12Hour = (date: Dayjs) => {
export const getDateWithTimezone = (props: { date?: Dayjs, timezone?: string }) => {
return props.date ? dayjs.tz(props.date, props.timezone) : dayjs().tz(props.timezone)
}
// Asia/Shanghai -> UTC+8
const DEFAULT_OFFSET_STR = 'UTC+0'
export const convertTimezoneToOffsetStr = (timezone?: string) => {
if(!timezone)
return DEFAULT_OFFSET_STR
const tzItem = tz.find(item => item.value === timezone)
if(!tzItem)
return DEFAULT_OFFSET_STR
return `UTC${tzItem.name.charAt(0)}${tzItem.name.charAt(2)}`
}

View File

@ -4,12 +4,17 @@ import React, { useCallback, useMemo } from 'react'
import { AUTO_UPDATE_MODE, AUTO_UPDATE_STRATEGY, type AutoUpdateConfig } from './types'
import Label from '../label'
import StrategyPicker from './strategy-picker'
import { useTranslation } from 'react-i18next'
import { Trans, useTranslation } from 'react-i18next'
import TimePicker from '@/app/components/base/date-and-time-picker/time-picker'
import OptionCard from '@/app/components/workflow/nodes/_base/components/option-card'
import PluginsPicker from './plugins-picker'
import { convertLocalSecondsToUTCDaySeconds, convertUTCDaySecondsToLocalSeconds, dayjsToTimeOfDay, timeOfDayToDayjs } from './utils'
import { useAppContext } from '@/context/app-context'
import type { TriggerParams } from '@/app/components/base/date-and-time-picker/types'
import { RiTimeLine } from '@remixicon/react'
import cn from '@/utils/classnames'
import { convertTimezoneToOffsetStr } from '@/app/components/base/date-and-time-picker/utils/dayjs'
import { useModalContextSelector } from '@/context/modal-context'
const i18nPrefix = 'plugin.autoUpdate'
@ -18,6 +23,16 @@ type Props = {
onChange: (payload: AutoUpdateConfig) => void
}
const SettingTimeZone: FC<{
children?: React.ReactNode
}> = ({
children,
}) => {
const setShowAccountSettingModal = useModalContextSelector(s => s.setShowAccountSettingModal)
return (
<span className='body-xs-regular cursor-pointer text-text-accent' onClick={() => setShowAccountSettingModal({ payload: 'language' })} >{children}</span>
)
}
const AutoUpdateSetting: FC<Props> = ({
payload,
onChange,
@ -83,10 +98,29 @@ const AutoUpdateSetting: FC<Props> = ({
})
}
}, [payload, onChange])
const renderTimePickerTrigger = useCallback(({ inputElem, onClick, isOpen }: TriggerParams) => {
return (
<div
className='group float-right flex h-8 w-[160px] cursor-pointer items-center justify-between rounded-lg bg-components-input-bg-normal px-2 hover:bg-state-base-hover-alt'
onClick={onClick}
>
<div className='flex w-0 grow items-center gap-x-1'>
<RiTimeLine className={cn(
'h-4 w-4 shrink-0 text-text-tertiary',
isOpen ? 'text-text-secondary' : 'group-hover:text-text-secondary',
)} />
{inputElem}
</div>
<div className='system-sm-regular text-text-tertiary'>{convertTimezoneToOffsetStr(timezone)}</div>
</div>
)
}, [timezone])
return (
<div className='self-stretch px-6'>
<div className='my-3 flex items-center'>
<div className='system-xs-medium-uppercase text-text-tertiary'>Updates Settings</div>
<div className='system-xs-medium-uppercase text-text-tertiary'>{t(`${i18nPrefix}.updateSettings`)}</div>
<div className='ml-2 h-px grow bg-divider-subtle'></div>
</div>
@ -99,15 +133,26 @@ const AutoUpdateSetting: FC<Props> = ({
<>
<div className='flex items-center justify-between'>
<Label label={t(`${i18nPrefix}.updateTime`)} />
<TimePicker
value={timeOfDayToDayjs(convertUTCDaySecondsToLocalSeconds(upgrade_time_of_day, timezone!))}
timezone={timezone}
onChange={v => handleChange('upgrade_time_of_day')(convertLocalSecondsToUTCDaySeconds(dayjsToTimeOfDay(v), timezone!))}
onClear={() => handleChange('upgrade_time_of_day')(convertLocalSecondsToUTCDaySeconds(0, timezone!))}
popupClassName='z-[99]'
title={t(`${i18nPrefix}.updateTime`)}
minuteFilter={minuteFilter}
/>
<div className='flex flex-col justify-start'>
<TimePicker
value={timeOfDayToDayjs(convertUTCDaySecondsToLocalSeconds(upgrade_time_of_day, timezone!))}
timezone={timezone}
onChange={v => handleChange('upgrade_time_of_day')(convertLocalSecondsToUTCDaySeconds(dayjsToTimeOfDay(v), timezone!))}
onClear={() => handleChange('upgrade_time_of_day')(convertLocalSecondsToUTCDaySeconds(0, timezone!))}
popupClassName='z-[99]'
title={t(`${i18nPrefix}.updateTime`)}
minuteFilter={minuteFilter}
renderTrigger={renderTimePickerTrigger}
/>
<div className='body-xs-regular mt-1 text-right text-text-tertiary'>
<Trans
i18nKey={`${i18nPrefix}.changeTimezone`}
components={{
setTimezone: <SettingTimeZone />,
}}
/>
</div>
</div>
</div>
<div>
<Label label={t(`${i18nPrefix}.specifyPluginsToUpdate`)} />

View File

@ -160,6 +160,8 @@ const translation = {
noFound: 'No plugins were found',
noInstalled: 'No plugins installed',
},
updateSettings: 'Update Settings',
changeTimezone: 'To change time zone, go to <setTimezone>Settings</setTimezone>',
},
pluginInfoModal: {
title: 'Plugin info',

View File

@ -160,6 +160,8 @@ const translation = {
noFound: '未找到插件',
noInstalled: '未安装插件',
},
updateSettings: '更新设置',
changeTimezone: '要更改时区,请前往<setTimezone>设置</setTimezone>',
},
pluginInfoModal: {
title: '插件信息',