From 5ba6899c94c2bab79c7eeb5aba90862a8ed462b0 Mon Sep 17 00:00:00 2001 From: Daniel Pupius Date: Thu, 24 Apr 2025 10:35:59 -0700 Subject: [PATCH] Consolidate MeshCard --- web/src/components/dashboard/GatewayList.tsx | 120 ++++---------- web/src/components/dashboard/MeshCard.tsx | 157 +++++++++++++++++++ web/src/components/dashboard/NodeList.tsx | 101 ++---------- web/src/components/dashboard/index.ts | 1 + 4 files changed, 201 insertions(+), 178 deletions(-) create mode 100644 web/src/components/dashboard/MeshCard.tsx diff --git a/web/src/components/dashboard/GatewayList.tsx b/web/src/components/dashboard/GatewayList.tsx index a32f1e1..ade1f14 100644 --- a/web/src/components/dashboard/GatewayList.tsx +++ b/web/src/components/dashboard/GatewayList.tsx @@ -1,7 +1,7 @@ import React from "react"; import { useAppSelector } from "../../hooks"; -import { RefreshCw, Signal, MapPin, Thermometer } from "lucide-react"; -import { Counter } from "../Counter"; +import { RefreshCw } from "lucide-react"; +import { MeshCard } from "./MeshCard"; export const GatewayList: React.FC = () => { const { gateways, nodes } = useAppSelector((state) => state.aggregator); @@ -43,95 +43,39 @@ export const GatewayList: React.FC = () => {
{sortedGateways.map((gateway) => { - // Format last heard time - const lastHeardDate = new Date(gateway.lastHeard * 1000); - const timeString = lastHeardDate.toLocaleTimeString(); - - // Determine if gateway is active (heard in last 5 minutes) - const isActive = Date.now() / 1000 - gateway.lastHeard < 300; + // Extract node ID from gateway ID format if possible + const nodeIdMatch = gateway.gatewayId.match(/^!([0-9a-f]+)/i); + let nodeId = 0; + let matchingNode = null; + + if (nodeIdMatch) { + const nodeIdHex = nodeIdMatch[1]; + nodeId = parseInt(nodeIdHex, 16); + matchingNode = nodes[nodeId]; + } + + // Determine if gateway is active (using stricter timeframe for gateways) + const secondsSinceLastHeard = Date.now() / 1000 - gateway.lastHeard; + const isRecent = secondsSinceLastHeard < 300; // 5 minutes for gateways + const isActive = !isRecent && secondsSinceLastHeard < 900; // 5-15 minutes for gateways return ( -
-
-
- -
-
-
-
- {/* Try to find if gateway ID matches a node we know about by its ID */} - {(() => { - // Extract node ID from gateway ID format if possible - const nodeIdMatch = - gateway.gatewayId.match(/^!([0-9a-f]+)/i); - if (nodeIdMatch) { - const nodeIdHex = nodeIdMatch[1]; - const nodeId = parseInt(nodeIdHex, 16); - const matchingNode = nodes[nodeId]; - - if ( - matchingNode && - (matchingNode.shortName || matchingNode.longName) - ) { - return ( - <> - - {matchingNode.shortName || matchingNode.longName} - - - !{nodeIdHex.slice(-4)} - - {matchingNode.position && ( - - )} - {matchingNode.environmentMetrics && Object.keys(matchingNode.environmentMetrics).length > 0 && ( - - )} - - ); - } - } - - // Default to gateway ID if no match - return ( - - {gateway.gatewayId} - - ); - })()} -
-
- - {timeString} -
-
-
- - - -
-
+ type="gateway" + nodeId={nodeId} + nodeData={matchingNode || { + nodeId, + lastHeard: gateway.lastHeard, + messageCount: gateway.messageCount, + textMessageCount: gateway.textMessageCount + }} + gatewayId={gateway.gatewayId} + observedNodes={gateway.observedNodes} + isRecent={isRecent} + isActive={isActive} + lastHeard={gateway.lastHeard} + /> ); })}
diff --git a/web/src/components/dashboard/MeshCard.tsx b/web/src/components/dashboard/MeshCard.tsx new file mode 100644 index 0000000..520205e --- /dev/null +++ b/web/src/components/dashboard/MeshCard.tsx @@ -0,0 +1,157 @@ +import React from "react"; +import { Radio, Signal, Battery, MapPin, Thermometer } from "lucide-react"; +import { Counter } from "../Counter"; +import { NodeData } from "../../store/slices/aggregatorSlice"; + +export interface MeshCardProps { + type: "node" | "gateway"; + nodeId: number; + nodeData: NodeData; + gatewayId?: string; + observedNodes?: number[]; + onClick?: (nodeId: number) => void; + isActive?: boolean; + isRecent?: boolean; + lastHeard: number; +} + +export const MeshCard: React.FC = ({ + type, + nodeId, + nodeData, + gatewayId, + observedNodes = [], + onClick, + isActive = false, + isRecent = false, + lastHeard, +}) => { + // Format last heard time + const lastHeardDate = new Date(lastHeard * 1000); + const timeString = lastHeardDate.toLocaleTimeString(); + + // Handle click event + const handleClick = () => { + if (onClick) { + onClick(nodeId); + } + }; + + // Get icon based on type + const getIcon = () => { + return type === "gateway" ? ( + + ) : ( + + ); + }; + + // Get card style based on activity + const getCardStyle = () => { + if (isRecent) { + return "bg-neutral-800 hover:bg-neutral-700"; + } else if (isActive) { + return "bg-neutral-800/80 hover:bg-neutral-700/80"; + } else { + return "bg-neutral-800/50 hover:bg-neutral-800"; + } + }; + + // Get icon style based on activity + const getIconStyle = () => { + if (isRecent) { + return "bg-green-900/30 text-green-500"; + } else if (isActive) { + return "bg-green-900/50 text-green-700"; + } else { + return "bg-neutral-700/30 text-neutral-500"; + } + }; + + // Get status dot color + const getStatusDotStyle = () => { + if (isRecent) { + return "bg-green-500"; + } else if (isActive) { + return "bg-green-700"; + } else { + return "bg-neutral-500"; + } + }; + + return ( +
+
+
+ {getIcon()} +
+
+
+
+ {nodeData.shortName || nodeData.longName ? ( + <> + {nodeData.shortName || nodeData.longName} + + !{nodeId.toString(16).slice(-4)} + + + ) : ( + !{nodeId.toString(16)} + )} + {nodeData.position && ( + + )} + {nodeData.environmentMetrics && + Object.keys(nodeData.environmentMetrics).length > 0 && ( + + )} +
+
+ + {timeString} +
+
+
+ {nodeData.batteryLevel !== undefined && ( +
+ + 30 ? "text-green-500" : "text-amber-500" + } + > + {nodeData.batteryLevel}% + +
+ )} + + {/* Only show observed nodes count for gateways */} + {type === "gateway" && observedNodes && observedNodes.length > 0 && ( + + )} + + + +
+
+ ); +}; diff --git a/web/src/components/dashboard/NodeList.tsx b/web/src/components/dashboard/NodeList.tsx index 7514ba2..47c9510 100644 --- a/web/src/components/dashboard/NodeList.tsx +++ b/web/src/components/dashboard/NodeList.tsx @@ -1,8 +1,8 @@ import React from "react"; import { useNavigate } from "@tanstack/react-router"; import { useAppSelector } from "../../hooks"; -import { Radio, Battery, RefreshCw, MapPin, Thermometer } from "lucide-react"; -import { Counter } from "../Counter"; +import { RefreshCw } from "lucide-react"; +import { MeshCard } from "./MeshCard"; export const NodeList: React.FC = () => { const { nodes } = useAppSelector((state) => state.aggregator); @@ -39,11 +39,6 @@ export const NodeList: React.FC = () => {
{sortedNodes.map((node) => { - // Format last heard time - const lastHeardDate = new Date(node.lastHeard * 1000); - const timeString = lastHeardDate.toLocaleTimeString(); - const dateString = lastHeardDate.toLocaleDateString(); - // Calculate time since last heard (in seconds) const secondsSinceLastHeard = Date.now() / 1000 - node.lastHeard; @@ -55,90 +50,16 @@ export const NodeList: React.FC = () => { const isActive = !isRecent && secondsSinceLastHeard < 1800; // 10-30 minutes return ( -
handleNodeClick(node.nodeId)} - className={`flex items-center p-2 rounded-lg cursor-pointer transition-colors ${ - isRecent - ? "bg-neutral-800 hover:bg-neutral-700" - : isActive - ? "bg-neutral-800/80 hover:bg-neutral-700/80" - : "bg-neutral-800/50 hover:bg-neutral-800" - }`} - > -
-
- -
-
-
-
- {node.shortName || node.longName ? ( - <> - {node.shortName || node.longName} - - !{node.nodeId.toString(16).slice(-4)} - - - ) : ( - !{node.nodeId.toString(16)} - )} - {node.position && ( - - )} - {node.environmentMetrics && Object.keys(node.environmentMetrics).length > 0 && ( - - )} -
-
- - {timeString} -
-
-
- {node.batteryLevel !== undefined && ( -
- - 30 - ? "text-green-500" - : "text-amber-500" - } - > - {node.batteryLevel}% - -
- )} - - -
-
+ type="node" + nodeId={node.nodeId} + nodeData={node} + onClick={handleNodeClick} + isRecent={isRecent} + isActive={isActive} + lastHeard={node.lastHeard} + /> ); })}
diff --git a/web/src/components/dashboard/index.ts b/web/src/components/dashboard/index.ts index 0db7f49..fb548c9 100644 --- a/web/src/components/dashboard/index.ts +++ b/web/src/components/dashboard/index.ts @@ -1,3 +1,4 @@ export * from './NodeList'; export * from './GatewayList'; +export * from './MeshCard'; export * from './NodeDetail';