|
1 | 1 | "use client"; |
2 | 2 |
|
3 | | -import { useEffect, useState } from "react"; |
4 | | -import { create } from "zustand"; |
5 | | -import { NodeList } from "@/src/components/network/NodeList"; |
6 | | -import { TreeView } from "@/src/components/network/TreeView"; |
7 | | -import { AlertFeed } from "@/src/components/dashboard/AlertFeed"; |
8 | | -import { SkeletonCard } from "@/src/components/skeleton/SkeletonCard"; |
9 | | -import { SkeletonChart } from "@/src/components/skeleton/SkeletonChart"; |
10 | | -import { useSkeletonTiming } from "@/src/hooks/useSkeletonTiming"; |
11 | | -import { |
12 | | - useHierarchicalData, |
13 | | - FlatNetworkNode, |
14 | | -} from "@/src/hooks/useHierarchicalData"; |
15 | | -import type { NodePosition } from "@/src/types/network"; |
| 3 | +import { useEffect, useState } from 'react' |
| 4 | +import { create } from 'zustand' |
| 5 | +import { NodeList } from '@/src/components/network/NodeList' |
| 6 | +import { AlertFeed } from '@/src/components/dashboard/AlertFeed' |
| 7 | +import { ActivityLogList } from '@/src/components/activity/ActivityLogList' |
| 8 | +import { SkeletonCard } from '@/src/components/skeleton/SkeletonCard' |
| 9 | +import { SkeletonChart } from '@/src/components/skeleton/SkeletonChart' |
| 10 | +import { SolarBatteryGauge } from '@/src/components/node/SolarBatteryGauge' |
| 11 | +import { useSkeletonTiming } from '@/src/hooks/useSkeletonTiming' |
| 12 | +import { useActivityLogSubscription } from '@/src/hooks/useActivityLogSubscription' |
| 13 | +import type { NodePosition } from '@/src/types/network' |
| 14 | +import { getNodePowerSource } from '@/src/hooks/useNodeStatus' |
16 | 15 |
|
17 | 16 | interface DashboardStore { |
18 | 17 | nodesReady: boolean; |
@@ -56,6 +55,7 @@ const MOCK_NODES: NodePosition[] = Array.from({ length: 12 }, (_, i) => ({ |
56 | 55 | hardwareModel: ["X1", "P2", "Z3", "Q4"][i % 4], |
57 | 56 | ipAddress: `10.0.${Math.floor(i / 4)}.${(i % 4) * 64 + 1}`, |
58 | 57 | uptime: `${Math.floor(Math.random() * 365)}d ${Math.floor(Math.random() * 24)}h`, |
| 58 | + powerSource: (i % 4 === 0 ? 'grid' : i % 3 === 0 ? 'battery' : 'solar'), |
59 | 59 | }, |
60 | 60 | })); |
61 | 61 |
|
@@ -123,27 +123,12 @@ function AlertSectionSkeleton() { |
123 | 123 | } |
124 | 124 |
|
125 | 125 | export function FacilityDashboard() { |
126 | | - const [nodesData, setNodesData] = useState<NodePosition[] | null>(null); |
127 | | - const [viewMode, setViewMode] = useState<"list" | "tree">(() => { |
128 | | - if (typeof window !== "undefined") { |
129 | | - const saved = localStorage.getItem("dashboard-view-mode"); |
130 | | - return (saved === "tree" ? "tree" : "list") as "list" | "tree"; |
131 | | - } |
132 | | - return "list"; |
133 | | - }); |
134 | | - const setNodesReady = useDashboardStore((s) => s.setNodesReady); |
135 | | - const setAlertsReady = useDashboardStore((s) => s.setAlertsReady); |
136 | | - const setMetricsReady = useDashboardStore((s) => s.setMetricsReady); |
137 | | - |
138 | | - // Persist view mode to localStorage |
139 | | - useEffect(() => { |
140 | | - localStorage.setItem("dashboard-view-mode", viewMode); |
141 | | - }, [viewMode]); |
142 | | - |
143 | | - // Convert nodes to hierarchical data for tree view |
144 | | - const hierarchicalData = useHierarchicalData( |
145 | | - nodesData ? convertToFlatNodes(nodesData) : [], |
146 | | - ); |
| 126 | + const { events: activityEvents } = useActivityLogSubscription(10_000) |
| 127 | + const [nodesData, setNodesData] = useState<NodePosition[] | null>(null) |
| 128 | + const setNodesReady = useDashboardStore((s) => s.setNodesReady) |
| 129 | + const setAlertsReady = useDashboardStore((s) => s.setAlertsReady) |
| 130 | + const setMetricsReady = useDashboardStore((s) => s.setMetricsReady) |
| 131 | + const solarNodes = (nodesData ?? []).filter((node) => getNodePowerSource(node) !== 'grid') |
147 | 132 |
|
148 | 133 | const nodesSkeleton = useSkeletonTiming( |
149 | 134 | () => useDashboardStore.getState().nodesReady, |
@@ -268,6 +253,21 @@ export function FacilityDashboard() { |
268 | 253 | </section> |
269 | 254 | </div> |
270 | 255 |
|
| 256 | + {solarNodes.length > 0 && ( |
| 257 | + <section className="mb-6"> |
| 258 | + <h2 className="text-lg font-semibold text-[#171512] mb-4">Solar Battery Forecasts</h2> |
| 259 | + <div className="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3"> |
| 260 | + {solarNodes.slice(0, 3).map((node) => ( |
| 261 | + <SolarBatteryGauge |
| 262 | + key={node.id} |
| 263 | + facilityId={String(node.metadata?.location ?? 'us-east')} |
| 264 | + nodeLabel={node.label ?? node.id} |
| 265 | + /> |
| 266 | + ))} |
| 267 | + </div> |
| 268 | + </section> |
| 269 | + )} |
| 270 | + |
271 | 271 | <section> |
272 | 272 | <h2 className="text-lg font-semibold text-[#171512] mb-4"> |
273 | 273 | Network Metrics |
@@ -302,6 +302,10 @@ export function FacilityDashboard() { |
302 | 302 | )} |
303 | 303 | </div> |
304 | 304 |
|
| 305 | + <div className="mt-6"> |
| 306 | + <ActivityLogList events={activityEvents} /> |
| 307 | + </div> |
| 308 | + |
305 | 309 | <div className="mt-6"> |
306 | 310 | {metricsSkeleton.showSkeleton ? ( |
307 | 311 | <SkeletonChart bars={16} height={220} /> |
|
0 commit comments