Skip to content

Commit fbb7900

Browse files
authored
Merge pull request #9 from Western-Formula-Racing/dev-base-frontend
Replaced the fetch('/assets/dbc.dbc') call with a raw import using Vite’s ?raw suffix to resolve the issue where the DBC file was served as HTML instead of text. Updated createCanProcessor() to use the imported file. Verified the DataStore and collapse behavior using both the test message processor and websocket_sender.py. Everything functions as expected after the DBC fix.
2 parents d533ba1 + 452287f commit fbb7900

11 files changed

Lines changed: 444 additions & 1509 deletions

File tree

pecan/Frontend/pecan-live-dashboard/public/assets/dbc.dbc

Lines changed: 0 additions & 1322 deletions
This file was deleted.

pecan/Frontend/pecan-live-dashboard/src/App.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ function App() {
2929
/>
3030
</div>
3131

32-
<main id="main-content" className=" w-100 h-full p-4">
32+
{/* Main content area, Outlet element is needed to display the rendered child pages received from the routes */}
33+
<main id="main-content" className=" w-100 h-full">
3334
<Outlet />
3435
</main>
3536

4.67 KB
Loading
4.54 KB
Loading

pecan/Frontend/pecan-live-dashboard/src/components/DataCard.tsx

Lines changed: 129 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,26 @@ import Dropdown from "./Dropdown";
22
import React, { useState, useMemo, useEffect } from "react";
33

44
interface 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
1414
type DataPair = Record<string, string>;
15-
1615
const 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">&nbsp;&nbsp;&nbsp;{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:&nbsp;&nbsp;&nbsp;{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">&nbsp;&nbsp;&nbsp;{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:&nbsp;&nbsp;&nbsp;{timeDiff}ms</p>
217210
</div>
218-
219-
);
211+
</div>
212+
</div>
213+
);
220214
}
221215

222216
export default DataCard;

0 commit comments

Comments
 (0)