Skip to content

Commit 934a36b

Browse files
committed
fix(charts): fix tooltip time formatting in AreaCostumeChart
1 parent 5ec668c commit 934a36b

File tree

1 file changed

+108
-81
lines changed

1 file changed

+108
-81
lines changed

dashboard/src/components/charts/AreaCostumeChart.tsx

Lines changed: 108 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -17,39 +17,110 @@ type DataPoint = {
1717
time: string
1818
cpu: number
1919
ram: number
20+
_period_start?: string
2021
}
2122

22-
// Chart configuration will be created dynamically with theme colors
23-
24-
// Custom tooltip component with proper time formatting
25-
const CustomTooltip = ({ active, payload, label }: any) => {
23+
const CustomTooltip = ({ active, payload, period, viewMode }: any) => {
2624
const { i18n } = useTranslation()
2725

2826
if (active && payload && payload.length) {
29-
let formattedDate = label
27+
const data = payload[0].payload
28+
let formattedDate = data.time
3029

31-
try {
32-
const today = new Date()
30+
if (data._period_start) {
31+
const d = dateUtils.toDayjs(data._period_start)
32+
const today = dateUtils.toDayjs(new Date())
33+
const isToday = d.isSame(today, 'day')
3334

34-
if (i18n.language === 'fa') {
35-
// Use Persian (Jalali) calendar and Persian locale
36-
if (/\d{2}\/\d{2}/.test(label)) {
37-
// MM/DD format, treat as past day
38-
const [month, day] = label.split('/')
39-
const localDate = new Date(today.getFullYear(), parseInt(month) - 1, parseInt(day), 0, 0, 0)
40-
formattedDate = localDate
41-
.toLocaleString('fa-IR', {
42-
year: 'numeric',
43-
month: '2-digit',
44-
day: '2-digit',
45-
hour: '2-digit',
46-
minute: '2-digit',
47-
hour12: false,
48-
})
49-
.replace(',', '')
50-
} else if (/\d{2}:\d{2}/.test(label)) {
51-
// HH:mm format, treat as today
52-
const now = new Date()
35+
try {
36+
if (i18n.language === 'fa') {
37+
if (period === 'day' && isToday) {
38+
formattedDate = new Date()
39+
.toLocaleString('fa-IR', {
40+
year: 'numeric',
41+
month: '2-digit',
42+
day: '2-digit',
43+
hour: '2-digit',
44+
minute: '2-digit',
45+
hour12: false,
46+
})
47+
.replace(',', '')
48+
} else if (period === 'day') {
49+
const localDate = new Date(d.year(), d.month(), d.date(), 0, 0, 0)
50+
formattedDate = localDate
51+
.toLocaleString('fa-IR', {
52+
year: 'numeric',
53+
month: '2-digit',
54+
day: '2-digit',
55+
hour: '2-digit',
56+
minute: '2-digit',
57+
hour12: false,
58+
})
59+
.replace(',', '')
60+
} else {
61+
formattedDate = d
62+
.toDate()
63+
.toLocaleString('fa-IR', {
64+
year: 'numeric',
65+
month: '2-digit',
66+
day: '2-digit',
67+
hour: '2-digit',
68+
minute: '2-digit',
69+
hour12: false,
70+
})
71+
.replace(',', '')
72+
}
73+
} else {
74+
if (period === 'day' && isToday) {
75+
const now = new Date()
76+
formattedDate = now
77+
.toLocaleString('en-US', {
78+
year: 'numeric',
79+
month: '2-digit',
80+
day: '2-digit',
81+
hour: '2-digit',
82+
minute: '2-digit',
83+
hour12: false,
84+
})
85+
.replace(',', '')
86+
} else if (period === 'day') {
87+
const localDate = new Date(d.year(), d.month(), d.date(), 0, 0, 0)
88+
formattedDate = localDate
89+
.toLocaleString('en-US', {
90+
year: 'numeric',
91+
month: '2-digit',
92+
day: '2-digit',
93+
hour: '2-digit',
94+
minute: '2-digit',
95+
hour12: false,
96+
})
97+
.replace(',', '')
98+
} else {
99+
formattedDate = d
100+
.toDate()
101+
.toLocaleString('en-US', {
102+
year: 'numeric',
103+
month: '2-digit',
104+
day: '2-digit',
105+
hour: '2-digit',
106+
minute: '2-digit',
107+
hour12: false,
108+
})
109+
.replace(',', '')
110+
}
111+
}
112+
} catch {
113+
formattedDate = d.format('YYYY/MM/DD HH:mm')
114+
}
115+
} else if (viewMode === 'realtime') {
116+
try {
117+
const now = new Date()
118+
const timeParts = data.time.split(':')
119+
if (timeParts.length >= 2) {
120+
now.setHours(parseInt(timeParts[0]), parseInt(timeParts[1]), 0)
121+
}
122+
123+
if (i18n.language === 'fa') {
53124
formattedDate = now
54125
.toLocaleString('fa-IR', {
55126
year: 'numeric',
@@ -60,26 +131,7 @@ const CustomTooltip = ({ active, payload, label }: any) => {
60131
hour12: false,
61132
})
62133
.replace(',', '')
63-
}
64-
} else {
65-
// English formatting
66-
if (/\d{2}\/\d{2}/.test(label)) {
67-
// MM/DD format, treat as past day
68-
const [month, day] = label.split('/')
69-
const localDate = new Date(today.getFullYear(), parseInt(month) - 1, parseInt(day), 0, 0, 0)
70-
formattedDate = localDate
71-
.toLocaleString('en-US', {
72-
year: 'numeric',
73-
month: '2-digit',
74-
day: '2-digit',
75-
hour: '2-digit',
76-
minute: '2-digit',
77-
hour12: false,
78-
})
79-
.replace(',', '')
80-
} else if (/\d{2}:\d{2}/.test(label)) {
81-
// HH:mm format, treat as today
82-
const now = new Date()
134+
} else {
83135
formattedDate = now
84136
.toLocaleString('en-US', {
85137
year: 'numeric',
@@ -91,9 +143,9 @@ const CustomTooltip = ({ active, payload, label }: any) => {
91143
})
92144
.replace(',', '')
93145
}
146+
} catch {
147+
formattedDate = data.time
94148
}
95-
} catch {
96-
formattedDate = label
97149
}
98150

99151
return (
@@ -128,22 +180,18 @@ interface AreaCostumeChartProps {
128180
realtimeStats?: SystemStats | NodeRealtimeStats
129181
}
130182

131-
// Helper function to determine period
132183
const getPeriodFromDateRange = (range?: DateRange): Period => {
133184
if (!range?.from || !range?.to) {
134-
return Period.hour // Default to hour if no range
185+
return Period.hour
135186
}
136187
const diffTime = Math.abs(range.to.getTime() - range.from.getTime())
137188
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
138189

139190
if (diffDays <= 2) {
140-
// Up to 2 days, use hourly data
141191
return Period.hour
142192
}
143-
return Period.day // More than 2 days, use daily data
193+
return Period.day
144194
}
145-
146-
// Type guard functions
147195
const isSystemStats = (stats: SystemStats | NodeRealtimeStats): stats is SystemStats => {
148196
return 'total_user' in stats
149197
}
@@ -161,22 +209,18 @@ export function AreaCostumeChart({ nodeId, currentStats, realtimeStats }: AreaCo
161209
const [dateRange, setDateRange] = useState<DateRange | undefined>(undefined)
162210
const [viewMode, setViewMode] = useState<'realtime' | 'historical'>('realtime')
163211

164-
// Add refs for chart container
165212
const chartContainerRef = useRef<HTMLDivElement>(null)
166213

167-
// Dynamic chart configuration with theme colors
168214
const chartConfig = useMemo<ChartConfig>(() => ({
169215
cpu: {
170216
label: t('statistics.cpuUsage'),
171-
color: 'hsl(var(--chart-1))', // Use theme chart color 1
217+
color: 'hsl(var(--chart-1))',
172218
},
173219
ram: {
174220
label: t('statistics.ramUsage'),
175-
color: 'hsl(var(--chart-2))', // Use theme chart color 2
221+
color: 'hsl(var(--chart-2))',
176222
},
177223
}), [t])
178-
179-
// Dynamic gradient definitions with theme colors
180224
const gradientDefs = useMemo(() => {
181225
const isDark = resolvedTheme === 'dark'
182226
return {
@@ -197,14 +241,12 @@ export function AreaCostumeChart({ nodeId, currentStats, realtimeStats }: AreaCo
197241
}
198242
}, [resolvedTheme])
199243

200-
// Clear stats when node changes
201244
useEffect(() => {
202245
setStatsHistory([])
203246
setDateRange(undefined)
204247
setViewMode('realtime')
205248
}, [nodeId])
206249

207-
// Toggle between real-time and historical view
208250
const toggleViewMode = () => {
209251
if (viewMode === 'realtime') {
210252
setViewMode('historical')
@@ -215,25 +257,22 @@ export function AreaCostumeChart({ nodeId, currentStats, realtimeStats }: AreaCo
215257
}
216258
}
217259

218-
// Effect for Real-time Stats
219260
useEffect(() => {
220261
if (!realtimeStats || viewMode !== 'realtime') return
221262

222263
try {
223264
const now = new Date()
224-
const timeStr = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`
265+
const timeStr = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`
225266

226267
let cpuUsage = 0
227268
let ramUsage = 0
228269

229270
if (isSystemStats(realtimeStats)) {
230-
// Master server stats
231271
cpuUsage = Number(realtimeStats.cpu_usage ?? 0)
232272
const memUsed = Number(realtimeStats.mem_used ?? 0)
233273
const memTotal = Number(realtimeStats.mem_total ?? 1)
234274
ramUsage = parseFloat(((memUsed / memTotal) * 100).toFixed(1))
235275
} else if (isNodeRealtimeStats(realtimeStats)) {
236-
// Node stats
237276
cpuUsage = Number(realtimeStats.cpu_usage ?? 0)
238277
const memUsed = Number(realtimeStats.mem_used ?? 0)
239278
const memTotal = Number(realtimeStats.mem_total ?? 1)
@@ -247,10 +286,9 @@ export function AreaCostumeChart({ nodeId, currentStats, realtimeStats }: AreaCo
247286
time: timeStr,
248287
cpu: cpuUsage,
249288
ram: ramUsage,
289+
_period_start: now.toISOString(),
250290
},
251291
]
252-
253-
// Improved cleanup logic for real-time data
254292
const MAX_HISTORY = 120
255293
const CLEANUP_THRESHOLD = 150
256294

@@ -278,7 +316,6 @@ export function AreaCostumeChart({ nodeId, currentStats, realtimeStats }: AreaCo
278316
}
279317
}, [realtimeStats, viewMode])
280318

281-
// Effect for Historical Stats
282319
useEffect(() => {
283320
if (nodeId === undefined || viewMode !== 'historical' || !dateRange?.from || !dateRange?.to) return
284321

@@ -308,6 +345,7 @@ export function AreaCostumeChart({ nodeId, currentStats, realtimeStats }: AreaCo
308345
time: timeFormat,
309346
cpu: point.cpu_usage_percentage,
310347
ram: point.mem_usage_percentage,
348+
_period_start: point.period_start,
311349
}
312350
})
313351
setStatsHistory(formattedData)
@@ -328,13 +366,11 @@ export function AreaCostumeChart({ nodeId, currentStats, realtimeStats }: AreaCo
328366
fetchNodeHistoricalStats()
329367
}, [nodeId, dateRange, viewMode])
330368

331-
// --- Header Display Logic ---
332369
let displayCpuUsage: string | JSX.Element = <Skeleton className="h-5 w-16" />
333370
let displayRamUsage: string | JSX.Element = <Skeleton className="h-5 w-16" />
334371

335372
if (currentStats) {
336373
if (isSystemStats(currentStats)) {
337-
// Master server stats
338374
const cpuUsage = Number(currentStats.cpu_usage ?? 0)
339375
const memUsed = Number(currentStats.mem_used ?? 0)
340376
const memTotal = Number(currentStats.mem_total ?? 1)
@@ -343,7 +379,6 @@ export function AreaCostumeChart({ nodeId, currentStats, realtimeStats }: AreaCo
343379
displayCpuUsage = `${cpuUsage.toFixed(1)}%`
344380
displayRamUsage = `${ramPercentage.toFixed(1)}%`
345381
} else if (isNodeRealtimeStats(currentStats)) {
346-
// Node stats
347382
const cpuUsage = Number(currentStats.cpu_usage ?? 0)
348383
const memUsed = Number(currentStats.mem_used ?? 0)
349384
const memTotal = Number(currentStats.mem_total ?? 1)
@@ -359,17 +394,14 @@ export function AreaCostumeChart({ nodeId, currentStats, realtimeStats }: AreaCo
359394

360395
return (
361396
<Card className="flex flex-1 flex-col">
362-
{/* Header - Mobile First Design */}
363397
<CardHeader className="flex flex-col space-y-4 p-4 md:p-6">
364-
{/* Title and Button Row */}
365398
<div className="flex flex-col space-y-3 sm:flex-row sm:items-center sm:justify-between sm:space-y-0">
366399
<div className="flex items-center space-x-3">
367400
<div className="flex items-center gap-x-2">
368401
<CardTitle className="text-lg md:text-xl">{viewMode === 'realtime' ? t('statistics.realTimeData') : t('statistics.historicalData')}</CardTitle>
369402
</div>
370403
</div>
371404

372-
{/* Toggle Button - Always visible on mobile */}
373405
{nodeId !== undefined && (
374406
<Button variant={viewMode === 'realtime' ? 'default' : 'outline'} size="sm" onClick={toggleViewMode} className="h-9 w-full px-4 font-medium sm:w-auto">
375407
{viewMode === 'realtime' ? (
@@ -387,10 +419,7 @@ export function AreaCostumeChart({ nodeId, currentStats, realtimeStats }: AreaCo
387419
)}
388420
</div>
389421

390-
{/* Description */}
391422
<CardDescription className="text-sm text-muted-foreground sm:!mt-0">{viewMode === 'realtime' ? t('statistics.realtimeDescription') : t('statistics.historicalDescription')}</CardDescription>
392-
393-
{/* Stats Display - Responsive Grid */}
394423
<div className="grid grid-cols-2 gap-4 pt-2 sm:gap-6">
395424
<div className="flex flex-col items-center space-y-2 rounded-lg bg-muted/50 p-3">
396425
<div className="flex items-center gap-2">
@@ -411,7 +440,6 @@ export function AreaCostumeChart({ nodeId, currentStats, realtimeStats }: AreaCo
411440
</div>
412441
</CardHeader>
413442

414-
{/* Time Range Selector - Only show in historical mode */}
415443
{viewMode === 'historical' && nodeId !== undefined && (
416444
<div className="border-t bg-muted/30 p-4 md:p-6">
417445
<div className="flex flex-col space-y-4 lg:flex-row lg:items-center lg:justify-between lg:space-y-0">
@@ -426,7 +454,6 @@ export function AreaCostumeChart({ nodeId, currentStats, realtimeStats }: AreaCo
426454
</div>
427455
)}
428456

429-
{/* Chart Content */}
430457
<CardContent className="flex-1 p-4 pt-0 md:p-6">
431458
{isLoading ? (
432459
<div className="flex h-[280px] w-full items-center justify-center sm:h-[320px] lg:h-[360px]">
@@ -497,7 +524,7 @@ export function AreaCostumeChart({ nodeId, currentStats, realtimeStats }: AreaCo
497524
/>
498525

499526
<Tooltip
500-
content={<CustomTooltip />}
527+
content={<CustomTooltip period={viewMode === 'historical' ? getPeriodFromDateRange(dateRange) : Period.hour} viewMode={viewMode} />}
501528
cursor={{
502529
stroke: 'hsl(var(--border))',
503530
strokeWidth: 1,

0 commit comments

Comments
 (0)