feat(frontend): replace dashboard with realtime device map
All checks were successful
Deploy to Production / deploy (push) Successful in 17m7s
All checks were successful
Deploy to Production / deploy (push) Successful in 17m7s
This commit is contained in:
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user