feat(frontend): replace dashboard with realtime device map
All checks were successful
Deploy to Production / deploy (push) Successful in 17m7s

This commit is contained in:
魏风
2026-03-24 00:18:43 +08:00
parent 20355b0804
commit e195b26b5a

View File

@@ -1,6 +1,8 @@
import { createFileRoute } from "@tanstack/react-router" import { createFileRoute } from "@tanstack/react-router"
import L from "leaflet" import L from "leaflet"
import { import {
ChevronDown,
ChevronUp,
Clock, Clock,
Layers, Layers,
Maximize, Maximize,
@@ -15,13 +17,7 @@ import "leaflet/dist/leaflet.css"
import { Badge } from "@/components/ui/badge" import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
@@ -445,6 +441,7 @@ function Dashboard() {
const { snapshot, status, disconnectedAt } = useTelemetryWS() const { snapshot, status, disconnectedAt } = useTelemetryWS()
const [baseMap, setBaseMap] = useState<BaseMapType>("amap") const [baseMap, setBaseMap] = useState<BaseMapType>("amap")
const [activeDeviceId, setActiveDeviceId] = useState<string>("") const [activeDeviceId, setActiveDeviceId] = useState<string>("")
const [isPanelExpanded, setIsPanelExpanded] = useState(true)
const activeDeviceIdRef = useRef(activeDeviceId) const activeDeviceIdRef = useRef(activeDeviceId)
activeDeviceIdRef.current = activeDeviceId activeDeviceIdRef.current = activeDeviceId
@@ -637,89 +634,113 @@ function Dashboard() {
})} })}
</MapContainer> </MapContainer>
</div> </div>
</div>
{/* Selected Device Panel — placed at bottom, compact */} {/* Selected Device Panel Overlay */}
<Card className="rounded-none border-x-0 border-b-0 shadow-none bg-background/50 backdrop-blur-sm shrink-0"> <div
<CardHeader> className={`absolute bottom-6 right-6 z-9999 w-80 max-w-[calc(100%-3rem)] transition-all duration-300 ${isPanelExpanded ? "opacity-80" : "opacity-100"}`}
<CardTitle className="flex items-center gap-2"> >
Selected Device <Card className="shadow-lg border bg-background/50 backdrop-blur-md overflow-hidden">
{activeDevice?.signal_health && <CardHeader
signalHealthBadge(activeDevice.signal_health)} className="p-3 flex flex-row items-center justify-between space-y-0 cursor-pointer select-none"
</CardTitle> onClick={() => setIsPanelExpanded(!isPanelExpanded)}
<CardDescription>
Click a marker on the map to view detailed device info
</CardDescription>
</CardHeader>
<CardContent className="grid gap-4 text-sm sm:grid-cols-2 lg:grid-cols-4 xl:grid-cols-8">
<div>
<p className="text-muted-foreground mb-1">Device ID</p>
<p className="font-medium break-all">
{activeDevice?.device_id ?? "-"}
</p>
</div>
<div>
<p className="text-muted-foreground mb-1">Latitude</p>
<p className="font-medium font-mono">
{formatCoord(activeDevice?.lat ?? null)}
</p>
</div>
<div>
<p className="text-muted-foreground mb-1">Longitude</p>
<p className="font-medium font-mono">
{formatCoord(activeDevice?.lon ?? null)}
</p>
</div>
<div>
<p className="text-muted-foreground mb-1 flex items-center gap-1">
<Mountain className="size-3" />
Altitude
</p>
<p className="font-medium font-mono">
{activeDevice?.alt_m !== null && activeDevice?.alt_m !== undefined
? `${activeDevice.alt_m.toFixed(1)} m`
: "-"}
</p>
</div>
<div>
<p className="text-muted-foreground mb-1">Fix Status</p>
<p
className={`font-medium ${getFixStatusColor(activeDevice?.fix_status ?? null)}`}
> >
{getFixStatusLabel(activeDevice?.fix_status ?? null)} <CardTitle className="text-sm font-semibold flex items-center gap-2">
</p> Selected Device
</div> {activeDevice?.signal_health &&
<div> !isPanelExpanded &&
<p className="text-muted-foreground mb-1 flex items-center gap-1"> signalHealthBadge(activeDevice.signal_health)}
<Satellite className="size-3" /> </CardTitle>
Satellites <div className="flex items-center gap-1">
</p> {activeDevice?.signal_health &&
<p className="font-medium font-mono"> isPanelExpanded &&
{activeDevice?.satellites_used ?? "-"} signalHealthBadge(activeDevice.signal_health)}
</p> <Button
</div> variant="ghost"
<div> size="icon"
<p className="text-muted-foreground mb-1 flex items-center gap-1"> className="h-6 w-6 hover:bg-transparent"
<Signal className="size-3" /> >
Signal Score {isPanelExpanded ? (
</p> <ChevronDown className="size-4" />
<p className="font-medium font-mono"> ) : (
{activeDevice?.signal_health <ChevronUp className="size-4" />
? `${activeDevice.signal_health.score}%` )}
: "-"} </Button>
</p> </div>
</div> </CardHeader>
<div> {isPanelExpanded && (
<p className="text-muted-foreground mb-1 flex items-center gap-1"> <CardContent className="p-3 pt-0 grid grid-cols-2 gap-x-4 gap-y-3 text-[11px]">
<Clock className="size-3" /> <div className="col-span-2">
Last Seen <p className="text-muted-foreground mb-0.5">Device ID</p>
</p> <p className="font-medium break-all text-xs">
<p className="font-medium"> {activeDevice?.device_id ?? "-"}
{timeAgo(activeDevice?.last_event_time ?? null)} </p>
</p> </div>
</div> <div>
</CardContent> <p className="text-muted-foreground mb-0.5">Latitude</p>
</Card> <p className="font-medium font-mono">
{formatCoord(activeDevice?.lat ?? null)}
</p>
</div>
<div>
<p className="text-muted-foreground mb-0.5">Longitude</p>
<p className="font-medium font-mono">
{formatCoord(activeDevice?.lon ?? null)}
</p>
</div>
<div>
<p className="text-muted-foreground mb-0.5 flex items-center gap-1">
<Mountain className="size-3" />
Altitude
</p>
<p className="font-medium font-mono">
{activeDevice?.alt_m !== null &&
activeDevice?.alt_m !== undefined
? `${activeDevice.alt_m.toFixed(1)} m`
: "-"}
</p>
</div>
<div>
<p className="text-muted-foreground mb-0.5">Fix Status</p>
<p
className={`font-medium ${getFixStatusColor(activeDevice?.fix_status ?? null)}`}
>
{getFixStatusLabel(activeDevice?.fix_status ?? null)}
</p>
</div>
<div>
<p className="text-muted-foreground mb-0.5 flex items-center gap-1">
<Satellite className="size-3" />
Satellites
</p>
<p className="font-medium font-mono">
{activeDevice?.satellites_used ?? "-"}
</p>
</div>
<div>
<p className="text-muted-foreground mb-0.5 flex items-center gap-1">
<Signal className="size-3" />
SNR
</p>
<p className="font-medium font-mono">
{activeDevice?.signal_health
? `${activeDevice.signal_health.score}%`
: "-"}
</p>
</div>
<div className="col-span-2">
<p className="text-muted-foreground mb-0.5 flex items-center gap-1">
<Clock className="size-3" />
Last Seen
</p>
<p className="font-medium">
{timeAgo(activeDevice?.last_event_time ?? null)}
</p>
</div>
</CardContent>
)}
</Card>
</div>
</div>
</div> </div>
) )
} }