From 07a4d7a1821f3051fb1544c5e399059fb2fa9024 Mon Sep 17 00:00:00 2001 From: Joel Date: Thu, 6 Nov 2025 10:42:31 +0800 Subject: [PATCH] feat: refactor the components --- .../[appId]/overview/time-range-picker.tsx | 121 ------------------ .../time-range-picker/date-picker.tsx | 57 +++++++++ .../overview/time-range-picker/index.tsx | 72 +++++++++++ .../time-range-picker/range-selector.tsx | 81 ++++++++++++ web/app/components/app/overview/app-chart.tsx | 15 ++- 5 files changed, 222 insertions(+), 124 deletions(-) delete mode 100644 web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker.tsx create mode 100644 web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/date-picker.tsx create mode 100644 web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/index.tsx create mode 100644 web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/range-selector.tsx diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker.tsx deleted file mode 100644 index a51d377d6f..0000000000 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker.tsx +++ /dev/null @@ -1,121 +0,0 @@ -'use client' -import type { PeriodParams } from '@/app/components/app/overview/app-chart' -import type { FC } from 'react' -import React, { useCallback } from 'react' -import { SimpleSelect } from '@/app/components/base/select' -import type { Item } from '@/app/components/base/select' -import type { Dayjs } from 'dayjs' -import dayjs from 'dayjs' -import { HourglassShape } from '@/app/components/base/icons/src/vender/other' -import { noop } from 'lodash-es' -import { useTranslation } from 'react-i18next' -import cn from '@/utils/classnames' -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() - -type Props = { - ranges: { value: number; name: string }[] - onSelect: (payload: PeriodParams) => void - queryDateFormat: string -} - -const TimeRangePicker: FC = ({ - ranges, - onSelect, - queryDateFormat, -}) => { - const { t } = useTranslation() - - const isCustom = false - - const renderRangeTrigger = useCallback((item: Item | null, isOpen: boolean) => { - return ( -
-
{isCustom ? t('appLog.filter.period.custom') : item?.name}
- -
- ) - }, [isCustom]) - - const renderOption = useCallback(({ item, selected }: { item: Item; selected: boolean }) => { - return ( - <> - {selected && ( - - - )} - {item.name} - - ) - }, []) - - const handleSelectRange = useCallback((item: Item) => { - const { name, value } = item - let period: PeriodParams['query'] | null = null - if (value === 0) { - const startOfToday = today.startOf('day').format(queryDateFormat) - const endOfToday = today.endOf('day').format(queryDateFormat) - period = { start: startOfToday, end: endOfToday } - } - else { - period = { start: today.subtract(item.value as number, 'day').startOf('day').format(queryDateFormat), end: today.endOf('day').format(queryDateFormat) } - } - onSelect({ query: period!, name }) - }, [onSelect]) - - const { locale } = useI18N() - - const renderDate = useCallback(({ value, handleClickTrigger, isOpen }: { value?: Dayjs, handleClickTrigger: () => void, isOpen: boolean }) => { - return ( -
- {value ? formatToLocalTime(value, locale, 'MMM D') : ''} -
- ) - }, [locale]) - - return ( -
- ({ ...v, name: t(`appLog.filter.period.${v.name}`) }))} - className='mt-0 !w-40' - notClearable={true} - onSelect={handleSelectRange} - defaultValue={0} - wrapperClassName='h-8' - optionWrapClassName='w-[200px] translate-x-[-24px]' - renderTrigger={renderRangeTrigger} - optionClassName='flex items-center py-0 pl-7 pr-2 h-8' - renderOption={renderOption} - /> - -
- - - - - -
- -
- ) -} -export default React.memo(TimeRangePicker) diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/date-picker.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/date-picker.tsx new file mode 100644 index 0000000000..66a302e7fd --- /dev/null +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/date-picker.tsx @@ -0,0 +1,57 @@ +'use client' +import { RiCalendarLine } from '@remixicon/react' +import type { Dayjs } from 'dayjs' +import type { FC } from 'react' +import React, { useCallback } from 'react' +import cn from '@/utils/classnames' +import { formatToLocalTime } from '@/utils/format' +import { useI18N } from '@/context/i18n' +import Picker from '@/app/components/base/date-and-time-picker/date-picker' +import type { TriggerProps } from '@/app/components/base/date-and-time-picker/types' +import { noop } from 'lodash-es' + +type Props = { + start: Dayjs + end: Dayjs + onStartChange: (date?: Dayjs) => void + onEndChange: (date?: Dayjs) => void +} + +const DatePicker: FC = ({ + start, + end, + onStartChange, + onEndChange, +}) => { + const { locale } = useI18N() + + const renderDate = useCallback(({ value, handleClickTrigger, isOpen }: TriggerProps) => { + return ( +
+ {value ? formatToLocalTime(value, locale, 'MMM D') : ''} +
+ ) + }, [locale]) + return ( +
+ + + - + +
+ + ) +} +export default React.memo(DatePicker) diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/index.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/index.tsx new file mode 100644 index 0000000000..45c0292d72 --- /dev/null +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/index.tsx @@ -0,0 +1,72 @@ +'use client' +import type { PeriodParams, PeriodParamsWithTimeRange } from '@/app/components/app/overview/app-chart' +import type { FC } from 'react' +import React, { useCallback, useState } from 'react' +import type { Dayjs } from 'dayjs' +import { HourglassShape } from '@/app/components/base/icons/src/vender/other' +import RangeSelector from './range-selector' +import DatePicker from './date-picker' +import dayjs from 'dayjs' + +const today = dayjs() + +type Props = { + ranges: { value: number; name: string }[] + onSelect: (payload: PeriodParams) => void + queryDateFormat: string +} + +const TimeRangePicker: FC = ({ + ranges, + onSelect, + queryDateFormat, +}) => { + const [isCustomRange, setIsCustomRange] = useState(false) + const [start, setStart] = useState(today) + const [end, setEnd] = useState(today) + + const handleRangeChange = useCallback((payload: PeriodParamsWithTimeRange) => { + setIsCustomRange(false) + setStart(payload.query!.start) + setEnd(payload.query!.end) + onSelect({ + name: payload.name, + query: { + start: payload.query!.start.format(queryDateFormat), + end: payload.query!.end.format(queryDateFormat), + }, + }) + }, [onSelect]) + + const handleStartChange = useCallback((date?: Dayjs) => { + if (!date) return + setStart(date) + setIsCustomRange(true) + onSelect({ name: 'custom', query: { start: date.format(queryDateFormat), end: end.format(queryDateFormat) } }) + }, [end, onSelect, queryDateFormat]) + + const handleEndChange = useCallback((date?: Dayjs) => { + if (!date) return + setEnd(date) + setIsCustomRange(true) + onSelect({ name: 'custom', query: { start: start.format(queryDateFormat), end: date.format(queryDateFormat) } }) + }, [start, onSelect, queryDateFormat]) + + return ( +
+ + + +
+ ) +} +export default React.memo(TimeRangePicker) diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/range-selector.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/range-selector.tsx new file mode 100644 index 0000000000..f99ea52492 --- /dev/null +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/range-selector.tsx @@ -0,0 +1,81 @@ +'use client' +import type { PeriodParamsWithTimeRange, TimeRange } from '@/app/components/app/overview/app-chart' +import type { FC } from 'react' +import React, { useCallback } from 'react' +import { SimpleSelect } from '@/app/components/base/select' +import type { Item } from '@/app/components/base/select' +import dayjs from 'dayjs' +import { RiArrowDownSLine, RiCheckLine } from '@remixicon/react' +import cn from '@/utils/classnames' +import { useTranslation } from 'react-i18next' + +const today = dayjs() + +type Props = { + isCustomRange: boolean + ranges: { value: number; name: string }[] + onSelect: (payload: PeriodParamsWithTimeRange) => void +} + +const RangeSelector: FC = ({ + isCustomRange, + ranges, + onSelect, +}) => { + const { t } = useTranslation() + + const handleSelectRange = useCallback((item: Item) => { + const { name, value } = item + let period: TimeRange | null = null + if (value === 0) { + const startOfToday = today.startOf('day') + const endOfToday = today.endOf('day') + period = { start: startOfToday, end: endOfToday } + } + else { + period = { start: today.subtract(item.value as number, 'day').startOf('day'), end: today.endOf('day') } + } + onSelect({ query: period!, name }) + }, [onSelect]) + + const renderTrigger = useCallback((item: Item | null, isOpen: boolean) => { + return ( +
+
{isCustomRange ? t('appLog.filter.period.custom') : item?.name}
+ +
+ ) + }, [isCustomRange]) + + const renderOption = useCallback(({ item, selected }: { item: Item; selected: boolean }) => { + return ( + <> + {selected && ( + + + )} + {item.name} + + ) + }, []) + return ( + ({ ...v, name: t(`appLog.filter.period.${v.name}`) }))} + className='mt-0 !w-40' + notClearable={true} + onSelect={handleSelectRange} + defaultValue={0} + wrapperClassName='h-8' + optionWrapClassName='w-[200px] translate-x-[-24px]' + renderTrigger={renderTrigger} + optionClassName='flex items-center py-0 pl-7 pr-2 h-8' + renderOption={renderOption} + /> + ) +} +export default React.memo(RangeSelector) diff --git a/web/app/components/app/overview/app-chart.tsx b/web/app/components/app/overview/app-chart.tsx index c550f0b23f..8f28e16402 100644 --- a/web/app/components/app/overview/app-chart.tsx +++ b/web/app/components/app/overview/app-chart.tsx @@ -4,6 +4,7 @@ import React from 'react' import ReactECharts from 'echarts-for-react' import type { EChartsOption } from 'echarts' import useSWR from 'swr' +import type { Dayjs } from 'dayjs' import dayjs from 'dayjs' import { get } from 'lodash-es' import Decimal from 'decimal.js' @@ -78,6 +79,16 @@ export type PeriodParams = { } } +export type TimeRange = { + start: Dayjs + end: Dayjs +} + +export type PeriodParamsWithTimeRange = { + name: string + query?: TimeRange +} + export type IBizChartProps = { period: PeriodParams id: string @@ -215,9 +226,7 @@ const Chart: React.FC = ({ formatter(params) { return `
${params.name}
${valueFormatter((params.data as any)[yField])} - ${!CHART_TYPE_CONFIG[chartType].showTokens - ? '' - : ` + ${!CHART_TYPE_CONFIG[chartType].showTokens ? '' : ` ( ~$${get(params.data, 'total_price', 0)} )