@@ -2,28 +2,26 @@ import Dropdown from "./Dropdown";
22import React , { useState , useMemo , useEffect } from "react" ;
33
44interface InputProps {
5- msgID : string ;
6- messageName : string ;
7- category ?: string ;
8- data ?: Record < string , string > [ ] ;
9- lastUpdated ?: number ;
10- rawData ?: string ;
5+ msgID : string ;
6+ name : string ;
7+ category ?: string ;
8+ data ?: Record < string , string > [ ] ;
9+ rawData : string ;
10+ lastUpdated ?: number ;
1111}
1212
1313// Defining the structure of the data, can be changed later
1414type DataPair = Record < string , string > ;
15-
1615const DataTextBox = ( {
1716 children,
1817 align = "center" ,
1918} : {
2019 children : React . ReactNode ;
2120 align ?: "left" | "center" | "right" ;
2221} ) => (
23-
2422 < div
2523 className = { [
26- "w-full rounded-full bg-data-textbox-bg text-white text-sm font-semibold py-2 px-1" ,
24+ "w-full rounded-full bg-data-textbox-bg text-white text-xs font-semibold py-2 px-1" ,
2725 align === "left" && "text-left" ,
2826 align === "center" && "text-center" ,
2927 align === "right" && "text-right" ,
@@ -33,43 +31,49 @@ const DataTextBox = ({
3331 </ div >
3432) ;
3533
36- function DataCard ( { msgID, messageName, category, data, lastUpdated, rawData } : Readonly < InputProps > ) {
37-
38- const [ currentTime , setCurrentTime ] = useState ( Date . now ( ) ) ;
39-
40- useEffect ( ( ) => {
41- const interval = setInterval ( ( ) => setCurrentTime ( Date . now ( ) ) , 100 ) ;
42- return ( ) => clearInterval ( interval ) ;
43- } , [ ] ) ;
44-
45- const timeDiff = lastUpdated ? currentTime - lastUpdated : 0 ;
46-
47- const computedCategory = useMemo ( ( ) => {
48- if ( category ) return category ;
49- if ( ! data || data . length === 0 ) return "NO CAT" ;
50- const signalNames = data . flatMap ( obj => Object . keys ( obj ) ) ;
51- const hasINV = signalNames . some ( name => name . includes ( "INV" ) ) ;
52- const hasBMS = signalNames . some ( name => name . includes ( "BMS" ) || name . includes ( "TORCH" ) ) || messageName . includes ( "TORCH" ) ;
53- const hasVCU = signalNames . some ( name => name . includes ( "VCU" ) ) ;
54- if ( hasVCU ) return "VCU" ;
55- else if ( hasBMS ) return "BMS/TORCH" ;
56- else if ( hasINV ) return "INV" ;
57- else return "NO CAT" ;
58- } , [ category , data ] ) ;
59-
60- // Event handlers for dropdown menu options specific to the data cards
61- const handleMenuSelect = ( selection : string ) => {
62- if ( selection === "Add to Favourites" ) {
63- // TODO: Favourite card section
64- console . log ( `${ msgID } added to favourites.` ) ;
65- } else if ( selection === "Collapse" ) {
66- console . log ( `${ msgID } has been collapsed.` ) ;
67- }
68- } ;
69-
70- // Data population functions
71- const [ dataPairs , setDataPairs ] = useState < DataPair [ ] > ( [ ] ) ;
72- const populateDataColumns = ( incoming : DataPair [ ] | string ) => {
34+ function DataCard ( { msgID, name, category, data, lastUpdated, rawData } : Readonly < InputProps > ) {
35+
36+ const [ currentTime , setCurrentTime ] = useState ( Date . now ( ) ) ;
37+
38+ useEffect ( ( ) => {
39+ const interval = setInterval ( ( ) => setCurrentTime ( Date . now ( ) ) , 100 ) ;
40+ return ( ) => clearInterval ( interval ) ;
41+ } , [ ] ) ;
42+
43+ const timeDiff = lastUpdated ? currentTime - lastUpdated : 0 ;
44+
45+ const computedCategory = useMemo ( ( ) => {
46+ if ( category ) return category ;
47+ if ( ! data || data . length === 0 ) return "NO CAT" ;
48+ const signalNames = data . flatMap ( obj => Object . keys ( obj ) ) ;
49+ const hasINV = signalNames . some ( name => name . includes ( "INV" ) ) ;
50+ const hasBMS = signalNames . some ( name => name . includes ( "BMS" ) || name . includes ( "TORCH" ) ) || name . includes ( "TORCH" ) ;
51+ const hasVCU = signalNames . some ( name => name . includes ( "VCU" ) ) ;
52+ if ( hasVCU ) return "VCU" ;
53+ else if ( hasBMS ) return "BMS/TORCH" ;
54+ else if ( hasINV ) return "INV" ;
55+ else return "NO CAT" ;
56+ } , [ category , data ] ) ;
57+
58+ const [ collapsed , setCollapsed ] = useState ( false ) ;
59+ const menuItems = collapsed ? [ "Add to Favourites" , "br" , "Expand" ] : [ "Add to Favourites" , "br" , "Collapse" ] ;
60+ const toggleCollapsed = ( ) => setCollapsed ( prev => ! prev ) ;
61+
62+ // Event handlers for dropdown menu options specific to the data cards
63+ const handleMenuSelect = ( selection : string ) => {
64+ if ( selection == "Add to Favourites" ) {
65+ // TODO: Favourite card section
66+ console . log ( `${ msgID } added to favourites.` ) ;
67+ } else if ( selection == "Collapse" ) {
68+ setCollapsed ( true ) ;
69+ } else if ( selection == "Expand" ) {
70+ setCollapsed ( false ) ;
71+ }
72+ } ;
73+
74+ // Data population
75+ const [ dataPairs , setDataPairs ] = useState < DataPair [ ] > ( [ ] ) ;
76+ const populateDataColumns = ( incoming : DataPair [ ] | string ) => {
7377 try {
7478 const parsed = typeof incoming === "string" ? ( JSON . parse ( incoming ) as DataPair [ ] ) : incoming ;
7579
@@ -78,6 +82,7 @@ function DataCard({ msgID, messageName, category, data, lastUpdated, rawData }:
7882 return ;
7983 }
8084
85+ // Cleaning up data
8186 const cleaned = parsed
8287 . map ( ( obj ) => {
8388 const entries = Object . entries ( obj ) ;
@@ -107,23 +112,11 @@ function DataCard({ msgID, messageName, category, data, lastUpdated, rawData }:
107112 }
108113 } ;
109114
110- // If the parent passes data, auto-load it.
115+ // If the parent passes data, auto-load it
111116 useEffect ( ( ) => {
112117 if ( data && data . length ) populateDataColumns ( data ) ;
113118 } , [ data ] ) ;
114119
115- // Testing data for displaying, will need to feed
116- useEffect ( ( ) => {
117- if ( ! data ) {
118- populateDataColumns ( [
119- { "Voltage 1" : "3.57 V" } ,
120- { "Voltage 2" : "3.57 V" } ,
121- { "Voltage 3" : "3.57 V" } ,
122- { "Voltage 4" : "3.57 V" } ,
123- ] ) ;
124- }
125- } , [ data ] ) ;
126-
127120 const rows = useMemo (
128121 ( ) =>
129122 dataPairs . map ( ( obj ) => {
@@ -133,90 +126,91 @@ function DataCard({ msgID, messageName, category, data, lastUpdated, rawData }:
133126 [ dataPairs ]
134127 ) ;
135128
136- return (
137- // Data Card
138- < div className = "w-[400px] m-[10px] " >
139-
140- { /* DM Header */ }
141- < div className = " grid grid-cols-6 box-border gap-1.5 mx-[3px]" >
142- { /* Message ID Button */ }
143- < Dropdown
144- items = { [ "Add to Favourites" , "br" , "Collapse" ] }
145- onSelect = { handleMenuSelect }
146- widthClass = "w-[150px]"
147- // TODO: Handle menu button events
148- >
149- < div className = "col-span-1 h-[40px] m-[5px] mx-[0px] w-100 rounded-t-md box-border bg-data-module-bg flex justify-center items-center" >
150- < p className = "text-white font-semibold " > { msgID } </ p >
151- </ div >
152- </ Dropdown >
153-
154- { /* Message Name */ }
155- < div className = "col-span-3 h-[40px] m-[5px] mx-[0px] rounded-t-md box-border bg-data-module-bg flex justify-center items-center" >
156- < p className = "text-white text-[15px] font-semibold " > { messageName } </ p >
157- </ div >
158-
129+ return (
130+ // Data Card
131+ < div className = "min- w-[400px] max-w-[440px] w-100 " >
132+
133+ { /* DM Header */ }
134+ < div className = { ` ${ collapsed ? "gap-0.5" : "gap-1.5" } grid grid-cols-6 box-border mx-[3px]` } >
135+ { /* Message ID Button */ }
136+ < Dropdown
137+ items = { menuItems }
138+ onSelect = { handleMenuSelect }
139+ widthClass = "w-[150px]"
140+ >
141+ < div className = { ` ${ collapsed ? "rounded-l-lg bg-data-textbox-bg" : "rounded-t-md bg-data-module-bg" } col-span-1 h-[40px] mx-[0px] w-100 box-border flex justify-center items-center cursor-pointer` } >
142+ < p className = "text-white font-semibold " > { msgID } </ p >
143+ </ div >
144+ </ Dropdown >
145+
146+ { /* Message Name */ }
147+ < div className = { ` ${ collapsed ? "" : "rounded-t-md" } col-span-3 h-[40px] mx-[0px] box-border bg-data-module-bg flex justify-center items-center hover:brightness-110 transition` } >
148+ < button type = "button" onClick = { toggleCollapsed } className = { ` h-[40px] mx-[0px] box-border bg-data-module-bg flex justify-center items-center` } >
149+ < p className = "text-white text-xs font-semibold " > { name } </ p >
150+ </ button >
151+ </ div >
159152
160- { /* Category Name */ }
161- { /* div background colour will change based on which category is assigned to it */ }
162- < div
163- className = { `col-span-2 h-[40px] m-[5px] mx-[0px] rounded-t-md box-border flex justify-center items-center
164- ${ computedCategory === "TEST" ? "bg-sky-400" :
165- computedCategory === "CAT1" ? "bg-green-400" :
166- computedCategory === "CAT2" ? "bg-sky-500" :
167- computedCategory === "BMS/TORCH" ? "bg-orange-400" :
168- computedCategory === "CAT4" ? "bg-red-500" :
169- "bg-blue-500" } `} // Default
170- // TODO: Assign data categories to colours
171- >
172- < p className = "text-white font-semibold " > { computedCategory } </ p >
173153
174- </ div >
175154
176- </ div >
177-
178- { /* DM Content */ }
179- < div id = { msgID } className = "w-100 h-fit min-h-[100px] rounded-md bg-data-module-bg flex flex-column box-border p-[10px]" >
180-
181- { /* Data Display */ }
182- < div className = "w-full flex flex-col gap-2 p-[10px]" >
183- { rows . map ( ( [ label , value ] , idx ) => (
184- < Dropdown
185- items = { [ "Graph 1" , "Graph 2" , "br" , "New Graph" ] }
186- onSelect = { handleMenuSelect }
187- widthClass = "w-[120px]"
188- // TODO: Handle menu button events
189- >
190- < div key = { idx } className = "grid grid-cols-5 w-full" >
191- { /* Left column (label) */ }
192- < div className = "col-span-3 p-[5px]" >
193- < DataTextBox align = "center" > { label } </ DataTextBox >
194- </ div >
195- { /* Right column (value) */ }
196- < div className = "col-span-2 p-[5px]" >
197- < DataTextBox align = "center" > { value } </ DataTextBox >
198- </ div >
199- </ div >
200- </ Dropdown >
201- ) ) }
155+ { /* Category Name */ }
156+ { /* div background colour will change based on which category is assigned to it */ }
157+ < div
158+ className = { `${ collapsed ? "rounded-r-lg" : "rounded-t-md" } col-span-2 h-[40px] mx-[0px] box-border flex justify-center items-center
159+ ${ computedCategory === "VCU" ? "bg-sky-400" :
160+ computedCategory === "INV" ? "bg-green-400" :
161+ computedCategory === "CAT2" ? "bg-sky-500" :
162+ computedCategory === "BMS/TORCH" ? "bg-orange-400" :
163+ computedCategory === "CAT4" ? "bg-red-500" :
164+ "bg-blue-500" } `} // Default
165+ // TODO: Assign data categories to colours
166+ >
167+ < p className = "text-white text-xs font-semibold" > { computedCategory } </ p >
168+ </ div >
169+ </ div >
170+
171+ { /* DM Content (collapsible) */ }
172+ < div
173+ id = { msgID }
174+ className = { [
175+ "w-100 rounded-md bg-data-module-bg flex flex-column box-border mt-1" ,
176+ "overflow-hidden transition-[max-height,opacity] duration-300 ease-in-out" ,
177+ collapsed ? "max-h-0 opacity-0 pointer-events-none" : "p-[10px] max-h-[1000px] opacity-100" ,
178+ ] . join ( " " ) }
179+ aria-expanded = { ! collapsed }
180+ >
181+ { /* Data Display */ }
182+ < div className = "w-full flex flex-col gap-2 p-[10px]" >
183+ { rows . map ( ( [ label , value ] , idx ) => (
184+ < Dropdown
185+ items = { [ "Graph 1" , "Graph 2" , "br" , "New Graph" ] }
186+ onSelect = { handleMenuSelect }
187+ widthClass = "w-[120px]"
188+ // TODO: Handle menu button events
189+ >
190+ < div key = { `${ label } -${ idx } ` } className = "grid grid-cols-5 w-full" >
191+ { /* Left column (label) */ }
192+ < div className = "col-span-3 p-[5px]" >
193+ < DataTextBox align = "center" > { label } </ DataTextBox >
202194 </ div >
203-
204- < div className = "w-90 h-[2px] bg-white self-center rounded-xs" > </ div >
205-
206- { /* Raw Data Display */ }
207- < div className = "h-[50px] flex text-white text-xs items-center justify-start relative" >
208-
209- < p id = "raw-data" className = "font-semibold font-mono" > { rawData || "00 01 02 03 04 05 06 07" } </ p >
210- < p id = "raw-data-received" className = "absolute left-[55%] font-semibold font-mono" > Last Update: { timeDiff } ms</ p >
211-
195+ { /* Right column (value) */ }
196+ < div className = "col-span-2 p-[5px]" >
197+ < DataTextBox align = "center" > { value } </ DataTextBox >
212198 </ div >
213- </ div >
214-
199+ </ div >
200+ </ Dropdown >
201+ ) ) }
202+ </ div >
215203
204+ < div className = "w-90 h-[2px] bg-white self-center rounded-xs" > </ div >
216205
206+ { /* Raw Data Display */ }
207+ < div className = "h-[50px] grid grid-cols-6 text-white text-xs items-center justify-start" >
208+ < p id = "raw-data" className = "col-span-4 font-semibold" > { rawData || "00 01 02 03 04 05 06 07" } </ p >
209+ < p id = "raw-data-received" className = "col-span-2 text-end font-semibold" > Last Update: { timeDiff } ms</ p >
217210 </ div >
218-
219- ) ;
211+ </ div >
212+ </ div >
213+ ) ;
220214}
221215
222216export default DataCard ;
0 commit comments