11'use client' ;
22
3- import React , { useEffect , useState } from 'react' ;
3+ import React , { useCallback , useEffect , useState } from 'react' ;
44import { Card } from '@/components/ui/card' ;
55import { Badge } from '@/components/ui/badge' ;
66import { Button } from '@/components/ui/button' ;
77import { Skeleton } from '@/components/ui/skeleton' ;
88import { LineChart , Line , XAxis , YAxis , CartesianGrid , Tooltip , Legend , ResponsiveContainer , AreaChart , Area , BarChart , Bar } from 'recharts' ;
9- import { Activity , TrendingUp , Calendar , Clock , AlertTriangle , Play , Thermometer } from 'lucide-react' ;
9+ import { Activity , Calendar , Clock , AlertTriangle , Play , Thermometer } from 'lucide-react' ;
10+
11+ interface TwinStateData {
12+ temperature ?: number ;
13+ humidity ?: number ;
14+ }
15+
16+ interface TwinMetrics {
17+ health_score ?: number ;
18+ decay_rate ?: number ;
19+ temperature_stress ?: number ;
20+ humidity_stress ?: number ;
21+ }
22+
23+ interface HealthHistoryEntry {
24+ timestamp : string ;
25+ score : number ;
26+ }
1027
1128interface TwinState {
1229 id : string ;
13- state_data : any ;
14- metrics : any ;
30+ state_data : TwinStateData ;
31+ metrics : TwinMetrics ;
1532 timestamp : string ;
1633 source : string ;
1734}
1835
1936interface DigitalTwin {
2037 id : string ;
2138 name : string ;
22- current_state : any ;
39+ current_state : TwinStateData ;
2340 current_health_score ?: number ;
2441 predicted_expiry_date ?: string ;
25- health_history ?: any [ ] ;
42+ health_history ?: HealthHistoryEntry [ ] ;
2643}
2744
2845interface TwinViewProps {
@@ -37,13 +54,7 @@ export function TwinView({ twinId, className = '' }: TwinViewProps) {
3754 const [ error , setError ] = useState < string | null > ( null ) ;
3855 const [ viewMode , setViewMode ] = useState < 'health' | 'temperature' | 'decay' | 'metrics' > ( 'health' ) ;
3956
40- useEffect ( ( ) => {
41- fetchTwinData ( ) ;
42- const interval = setInterval ( fetchTwinData , 15000 ) ; // Refresh every 15s
43- return ( ) => clearInterval ( interval ) ;
44- } , [ twinId ] ) ;
45-
46- const fetchTwinData = async ( ) => {
57+ const fetchTwinData = useCallback ( async ( ) => {
4758 try {
4859 const [ twinRes , historyRes ] = await Promise . all ( [
4960 fetch ( `/api/v1/digital-twins/${ twinId } ` ) ,
@@ -62,12 +73,21 @@ export function TwinView({ twinId, className = '' }: TwinViewProps) {
6273 setError ( err instanceof Error ? err . message : 'Unknown error' ) ;
6374 setLoading ( false ) ;
6475 }
65- } ;
76+ } , [ twinId ] ) ;
77+
78+ useEffect ( ( ) => {
79+ // setState happens only after an await inside fetchTwinData, so this is an
80+ // async data fetch, not a synchronous cascading render.
81+ // eslint-disable-next-line react-hooks/set-state-in-effect
82+ fetchTwinData ( ) ;
83+ const interval = setInterval ( fetchTwinData , 15000 ) ; // Refresh every 15s
84+ return ( ) => clearInterval ( interval ) ;
85+ } , [ fetchTwinData ] ) ;
6686
6787 const getHealthChartData = ( ) => {
6888 if ( ! twin ?. health_history ) return [ ] ;
69-
70- return twin . health_history . map ( ( entry : any ) => ( {
89+
90+ return twin . health_history . map ( ( entry ) => ( {
7191 timestamp : new Date ( entry . timestamp ) . toLocaleTimeString ( ) ,
7292 score : Math . round ( entry . score * 100 ) ,
7393 } ) ) ;
0 commit comments