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