@@ -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
132183const 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
147195const 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