Skip to content

Commit dda097b

Browse files
committed
refactor(dashboard): update date handling to use system timezone for charts and modals
1 parent a41c902 commit dda097b

File tree

10 files changed

+108
-57
lines changed

10 files changed

+108
-57
lines changed

dashboard/src/components/charts/all-nodes-stacked-bar-chart.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,10 @@ function CustomTooltip({ active, payload, chartConfig, dir, period }: TooltipPro
6262
if (!active || !payload || !payload.length) return null
6363

6464
const data = payload[0].payload
65-
const d = dateUtils.toDayjs(data._period_start)
65+
const d = dateUtils.toSystemTimezoneDayjs(data._period_start)
6666

6767
// Check if this is today's data
68-
const today = dateUtils.toDayjs(new Date())
68+
const today = dateUtils.toSystemTimezoneDayjs(new Date())
6969
const isToday = d.isSame(today, 'day')
7070

7171
let formattedDate
@@ -343,13 +343,17 @@ export function AllNodesStackedBarChart() {
343343
const startDate = dateRange.from
344344
const endDate = dateRange.to
345345
const period = getPeriodFromDateRange(dateRange)
346+
const startTime = period === Period.day ? dateUtils.toUTCDayStartISO(startDate) : dateUtils.toSystemTimezoneISO(startDate)
346347

347348
// Always use end of day for daily period to avoid extra bars
348-
const endTime = period === Period.day ? dateUtils.toDayjs(endDate).endOf('day').toISOString() : new Date().toISOString()
349+
const endTime =
350+
period === Period.day
351+
? dateUtils.toSystemTimezoneISO(dateUtils.toSystemTimezoneDayjs(endDate).endOf('day').toDate())
352+
: dateUtils.toSystemTimezoneISO(new Date())
349353

350354
const params = {
351355
period: period,
352-
start: startDate.toISOString(),
356+
start: startTime,
353357
end: endTime,
354358
group_by_node: true,
355359
}
@@ -395,7 +399,7 @@ export function AllNodesStackedBarChart() {
395399

396400
if (aggregatedStats.length > 0) {
397401
const data = aggregatedStats.map(point => {
398-
const d = dateUtils.toDayjs(point.period_start)
402+
const d = dateUtils.toSystemTimezoneDayjs(point.period_start)
399403
let timeFormat
400404
if (period === Period.hour) {
401405
timeFormat = d.format('HH:mm')
@@ -449,7 +453,7 @@ export function AllNodesStackedBarChart() {
449453
if (sortedPeriods.length > 0) {
450454
// Build chart data: [{ time, [nodeName]: usage, ... }]
451455
const data = sortedPeriods.map(periodStart => {
452-
const d = dateUtils.toDayjs(periodStart)
456+
const d = dateUtils.toSystemTimezoneDayjs(periodStart)
453457
let timeFormat
454458
if (period === Period.hour) {
455459
timeFormat = d.format('HH:mm')

dashboard/src/components/charts/area-costume-chart.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ const CustomTooltip = ({ active, payload, period, viewMode }: any) => {
2929
let formattedDate = data.time
3030

3131
if (data._period_start) {
32-
const d = dateUtils.toDayjs(data._period_start)
33-
const today = dateUtils.toDayjs(new Date())
32+
const d = dateUtils.toSystemTimezoneDayjs(data._period_start)
33+
const today = dateUtils.toSystemTimezoneDayjs(new Date())
3434
const isToday = d.isSame(today, 'day')
3535

3636
try {
@@ -281,7 +281,7 @@ export function AreaCostumeChart({ nodeId, currentStats, realtimeStats }: AreaCo
281281
time: timeStr,
282282
cpu: cpuUsage,
283283
ram: ramUsage,
284-
_period_start: now.toISOString(),
284+
_period_start: dateUtils.toSystemTimezoneISO(now),
285285
},
286286
]
287287
const MAX_HISTORY = 120
@@ -320,16 +320,16 @@ export function AreaCostumeChart({ nodeId, currentStats, realtimeStats }: AreaCo
320320
try {
321321
const period = getPeriodFromDateRange(dateRange)
322322
const data = await getNodeStatsPeriodic(nodeId, {
323-
start: dateRange.from!.toISOString(),
324-
end: dateRange.to!.toISOString(),
323+
start: dateUtils.toSystemTimezoneISO(dateRange.from!),
324+
end: dateUtils.toSystemTimezoneISO(dateRange.to!),
325325
period: period,
326326
})
327327

328328
const statsArray = data?.stats
329329

330330
if (Array.isArray(statsArray)) {
331331
const formattedData = statsArray.map((point: NodeStats) => {
332-
const d = dateUtils.toDayjs(point.period_start)
332+
const d = dateUtils.toSystemTimezoneDayjs(point.period_start)
333333
let timeFormat
334334
if (period === Period.hour) {
335335
timeFormat = d.format('HH:mm')

dashboard/src/components/charts/costume-bar-chart.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ function CustomBarTooltip({ active, payload, period }: TooltipProps<any, any> &
6464
const { t, i18n } = useTranslation()
6565
if (!active || !payload || !payload.length) return null
6666
const data = payload[0].payload
67-
const d = dateUtils.toDayjs(data._period_start)
68-
const today = dateUtils.toDayjs(new Date())
67+
const d = dateUtils.toSystemTimezoneDayjs(data._period_start)
68+
const today = dateUtils.toSystemTimezoneDayjs(new Date())
6969
const isToday = d.isSame(today, 'day')
7070

7171
let formattedDate
@@ -317,12 +317,14 @@ export function CostumeBarChart({ nodeId }: CostumeBarChartProps) {
317317
const endDate = dateRange.to
318318
// Determine period based on range
319319
const period = getPeriodFromDateRange(dateRange)
320+
const startTime = period === Period.day ? dateUtils.toUTCDayStartISO(startDate) : dateUtils.toSystemTimezoneISO(startDate)
321+
const endTime = dateUtils.toSystemTimezoneISO(dateUtils.toSystemTimezoneDayjs(endDate).endOf('day').toDate())
320322

321323
// Prepare API parameters
322324
const params = {
323325
period: period,
324-
start: startDate.toISOString(),
325-
end: dateUtils.toDayjs(endDate).endOf('day').toISOString(),
326+
start: startTime,
327+
end: endTime,
326328
...(nodeId !== undefined && { node_id: nodeId }),
327329
}
328330

@@ -350,7 +352,7 @@ export function CostumeBarChart({ nodeId }: CostumeBarChartProps) {
350352

351353
if (statsArr.length > 0) {
352354
const formattedData = statsArr.map(point => {
353-
const d = dateUtils.toDayjs(point.period_start)
355+
const d = dateUtils.toSystemTimezoneDayjs(point.period_start)
354356
let timeFormat
355357
if (period === Period.hour) {
356358
timeFormat = d.format('HH:mm')

dashboard/src/components/charts/time-selector.tsx

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Button } from '@/components/ui/button'
33
import { DropdownMenu, DropdownMenuContent, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
44
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'
55
import { cn } from '@/lib/utils'
6+
import { useTranslation } from 'react-i18next'
67

78
export type TimePeriod = string
89
export type TimeSelectorShortcut = {
@@ -31,6 +32,7 @@ export const TRAFFIC_TIME_SELECTOR_SHORTCUTS: TimeSelectorShortcut[] = [
3132
{ value: '1w', label: '1w', quick: true },
3233
{ value: '2w', label: '2w' },
3334
{ value: '1m', label: '1m' },
35+
{ value: 'all', label: 'all' },
3436
]
3537

3638
interface TimeSelectorProps {
@@ -42,9 +44,17 @@ interface TimeSelectorProps {
4244
}
4345

4446
export default function TimeSelector({ selectedTime, setSelectedTime, shortcuts = DEFAULT_TIME_SELECTOR_SHORTCUTS, maxVisible = shortcuts.length, className }: TimeSelectorProps) {
47+
const { t } = useTranslation()
4548
const [isMobileMoreOpen, setIsMobileMoreOpen] = useState(false)
4649
const [isDesktopMoreOpen, setIsDesktopMoreOpen] = useState(false)
4750

51+
const getShortcutLabel = (shortcut: TimeSelectorShortcut) => {
52+
if (shortcut.value === 'all') {
53+
return t('alltime', { defaultValue: 'All Time' })
54+
}
55+
return shortcut.label
56+
}
57+
4858
const quickShortcuts = useMemo(() => {
4959
const explicitQuick = shortcuts.filter(shortcut => shortcut.quick)
5060
if (explicitQuick.length > 0) return explicitQuick
@@ -67,7 +77,7 @@ export default function TimeSelector({ selectedTime, setSelectedTime, shortcuts
6777
const isDesktopOverflowSelected = desktopOverflowShortcuts.some(shortcut => shortcut.value === selectedTime)
6878

6979
return (
70-
<div className={cn('w-full min-w-0 max-w-full overflow-hidden rounded-md border border-border/60 bg-muted/20 p-1', className)}>
80+
<div dir="ltr" className={cn('w-full min-w-0 max-w-full overflow-hidden rounded-md border border-border/60 bg-muted/20 p-1', className)}>
7181
<div className="flex w-full min-w-0 items-center gap-1 lg:hidden">
7282
<ToggleGroup
7383
type="single"
@@ -83,7 +93,7 @@ export default function TimeSelector({ selectedTime, setSelectedTime, shortcuts
8393
variant="default"
8494
className="h-7 min-w-[2.25rem] shrink-0 border-0 bg-transparent px-2.5 py-1 text-xs font-medium text-muted-foreground data-[state=on]:bg-background data-[state=on]:text-foreground data-[state=on]:shadow-sm"
8595
>
86-
{shortcut.label}
96+
{getShortcutLabel(shortcut)}
8797
</ToggleGroupItem>
8898
))}
8999
</ToggleGroup>
@@ -98,10 +108,10 @@ export default function TimeSelector({ selectedTime, setSelectedTime, shortcuts
98108
isMobileOverflowSelected && 'bg-background text-foreground shadow-sm',
99109
)}
100110
>
101-
More
111+
{t('more', { defaultValue: 'More' })}
102112
</Button>
103113
</DropdownMenuTrigger>
104-
<DropdownMenuContent align="end" className="min-w-[7rem]" onInteractOutside={() => setIsMobileMoreOpen(false)}>
114+
<DropdownMenuContent dir="ltr" align="end" className="min-w-[7rem]" onInteractOutside={() => setIsMobileMoreOpen(false)}>
105115
<DropdownMenuRadioGroup
106116
value={selectedTime}
107117
onValueChange={value => {
@@ -111,7 +121,7 @@ export default function TimeSelector({ selectedTime, setSelectedTime, shortcuts
111121
>
112122
{mobileOverflowShortcuts.map(shortcut => (
113123
<DropdownMenuRadioItem key={shortcut.value} value={shortcut.value} className="text-xs">
114-
{shortcut.label}
124+
{getShortcutLabel(shortcut)}
115125
</DropdownMenuRadioItem>
116126
))}
117127
</DropdownMenuRadioGroup>
@@ -134,7 +144,7 @@ export default function TimeSelector({ selectedTime, setSelectedTime, shortcuts
134144
variant="default"
135145
className="h-7 min-w-[2.25rem] shrink-0 border-0 bg-transparent px-2.5 py-1 text-xs font-medium text-muted-foreground data-[state=on]:bg-background data-[state=on]:text-foreground data-[state=on]:shadow-sm"
136146
>
137-
{shortcut.label}
147+
{getShortcutLabel(shortcut)}
138148
</ToggleGroupItem>
139149
))}
140150
</ToggleGroup>
@@ -149,10 +159,10 @@ export default function TimeSelector({ selectedTime, setSelectedTime, shortcuts
149159
isDesktopOverflowSelected && 'bg-background text-foreground shadow-sm',
150160
)}
151161
>
152-
More
162+
{t('more', { defaultValue: 'More' })}
153163
</Button>
154164
</DropdownMenuTrigger>
155-
<DropdownMenuContent align="end" className="min-w-[7rem]" onInteractOutside={() => setIsDesktopMoreOpen(false)}>
165+
<DropdownMenuContent dir="ltr" align="end" className="min-w-[7rem]" onInteractOutside={() => setIsDesktopMoreOpen(false)}>
156166
<DropdownMenuRadioGroup
157167
value={selectedTime}
158168
onValueChange={value => {
@@ -162,7 +172,7 @@ export default function TimeSelector({ selectedTime, setSelectedTime, shortcuts
162172
>
163173
{desktopOverflowShortcuts.map(shortcut => (
164174
<DropdownMenuRadioItem key={shortcut.value} value={shortcut.value} className="text-xs">
165-
{shortcut.label}
175+
{getShortcutLabel(shortcut)}
166176
</DropdownMenuRadioItem>
167177
))}
168178
</DropdownMenuRadioGroup>

dashboard/src/components/dashboard/data-usage-chart.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ const transformUsageData = (apiData: { stats: (UserUsageStat | NodeUsageStat)[]
3636
if (!apiData?.stats || !Array.isArray(apiData.stats)) {
3737
return []
3838
}
39-
const today = dateUtils.toDayjs(new Date())
39+
const today = dateUtils.toSystemTimezoneDayjs(new Date())
4040

4141
return apiData.stats.map((stat: UserUsageStat | NodeUsageStat) => {
42-
const d = dateUtils.toDayjs(stat.period_start)
42+
const d = dateUtils.toSystemTimezoneDayjs(stat.period_start)
4343
const isToday = d.isSame(today, 'day')
4444

4545
let displayLabel = ''
@@ -118,8 +118,8 @@ function CustomBarTooltip({ active, payload, period }: TooltipProps<number, stri
118118
if (!active || !payload || !payload.length) return null
119119
const data = payload[0].payload
120120
// Use period_start if available (from transformUsageData), otherwise parse the display label
121-
const d = data.period_start ? dateUtils.toDayjs(data.period_start) : dateUtils.toDayjs(data.date)
122-
const today = dateUtils.toDayjs(new Date())
121+
const d = data.period_start ? dateUtils.toSystemTimezoneDayjs(data.period_start) : dateUtils.toDayjs(data.date)
122+
const today = dateUtils.toSystemTimezoneDayjs(new Date())
123123
const isToday = d.isSame(today, 'day')
124124

125125
let formattedDate
@@ -271,7 +271,7 @@ const DataUsageChart = ({ admin_username }: { admin_username?: string }) => {
271271
} else {
272272
start = now
273273
}
274-
return { startDate: start.toISOString(), endDate: now.toISOString() }
274+
return { startDate: dateUtils.toSystemTimezoneISO(start.toDate()), endDate: dateUtils.toSystemTimezoneISO(now.toDate()) }
275275
}, [periodOption])
276276

277277
const shouldUseNodeUsage = is_sudo && !admin_username
@@ -280,7 +280,7 @@ const DataUsageChart = ({ admin_username }: { admin_username?: string }) => {
280280
() => ({
281281
period: periodOption.period,
282282
start: startDate,
283-
end: dateUtils.toDayjs(endDate).endOf('day').toISOString(),
283+
end: dateUtils.toSystemTimezoneISO(dateUtils.toSystemTimezoneDayjs(endDate).endOf('day').toDate()),
284284
}),
285285
[periodOption.period, startDate, endDate],
286286
)
@@ -290,7 +290,7 @@ const DataUsageChart = ({ admin_username }: { admin_username?: string }) => {
290290
...(admin_username ? { admin: [admin_username] } : {}),
291291
period: periodOption.period,
292292
start: startDate,
293-
end: dateUtils.toDayjs(endDate).endOf('day').toISOString(),
293+
end: dateUtils.toSystemTimezoneISO(dateUtils.toSystemTimezoneDayjs(endDate).endOf('day').toDate()),
294294
}),
295295
[admin_username, periodOption.period, startDate, endDate],
296296
)
@@ -451,11 +451,11 @@ const DataUsageChart = ({ admin_username }: { admin_username?: string }) => {
451451
const lastDataPoint = chartData[chartData.length - 1] as { date: string; period_start?: string }
452452
// Check if this tick value matches the last data point's date
453453
if (lastDataPoint && value === lastDataPoint.date) {
454-
const today = dateUtils.toDayjs(new Date())
454+
const today = dateUtils.toSystemTimezoneDayjs(new Date())
455455
// Try to get period_start from the data point
456456
const dataPoint = chartData.find(d => typeof d === 'object' && d !== null && 'date' in d && (d as { date: string }).date === value) as { period_start?: string } | undefined
457457
if (dataPoint?.period_start) {
458-
const pointDate = dateUtils.toDayjs(dataPoint.period_start)
458+
const pointDate = dateUtils.toSystemTimezoneDayjs(dataPoint.period_start)
459459
if (pointDate.isSame(today, 'day')) {
460460
return t('today', { defaultValue: 'Today' })
461461
}

dashboard/src/components/dialogs/node-stats-modal.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,10 @@ const NodeStatsModal = ({ open, onClose, data, chartConfig, period, allChartData
7171

7272
if (!data) return null
7373

74-
const d = dateUtils.toDayjs(data._period_start)
74+
const d = dateUtils.toSystemTimezoneDayjs(data._period_start)
7575

7676
// Check if this is today's data
77-
const today = dateUtils.toDayjs(new Date())
77+
const today = dateUtils.toSystemTimezoneDayjs(new Date())
7878
const isToday = d.isSame(today, 'day')
7979

8080
let formattedDate

0 commit comments

Comments
 (0)