191 lines
5.8 KiB
TypeScript
191 lines
5.8 KiB
TypeScript
"use client"
|
|
|
|
import { ArrowLeftIcon, ChevronLeft } from "lucide-react"
|
|
import Link from "next/link"
|
|
import { Drawer, DrawerContent, DrawerTrigger } from "@/components/ui/drawer"
|
|
import { Button } from "@/components/ui/button"
|
|
import Image from "next/image"
|
|
import { useEffect, useState } from "react"
|
|
import { socket } from "@/components/socket"
|
|
import { usePlateStore } from "@/components/zustand/plate-history"
|
|
import { Spinner } from "@/components/ui/spinner"
|
|
|
|
const stream_dummy = [
|
|
{ id: "asssd", url: "/app/static/uploads/ds1.mp4" },
|
|
{ id: "asaggssd", url: "/app/static/uploads/ds2.mp4" },
|
|
{ id: "asss112d", url: "/app/static/uploads/ds3.mp4" },
|
|
{ id: "asgsasssd", url: "/app/static/uploads/ds4.mp4" },
|
|
]
|
|
|
|
export default function Etle() {
|
|
const [sessionId, setSessionId] = useState<string | null>(null)
|
|
const [imageSrc, setImageSrc] = useState<string>("")
|
|
const [plate, setPlate] = useState<string>("")
|
|
const [confidence, setConfidence] = useState<number | null>(null)
|
|
const activeStream = usePlateStore((s) => s.activeStreamId)
|
|
const setActiveStream = usePlateStore((s) => s.setActiveStream)
|
|
const history = usePlateStore((s) => s.history)
|
|
const addPlate = usePlateStore((s) => s.addPlate)
|
|
const [loading, setLoading] = useState(true)
|
|
|
|
const currentStream = usePlateStore.getState().activeStreamId
|
|
|
|
const startDetection = (index: number) => {
|
|
setLoading(true)
|
|
if (history[activeStream]) setPlate("")
|
|
setConfidence(null)
|
|
const stream = stream_dummy[index].url
|
|
|
|
socket.emit("stop_detection")
|
|
socket.emit("start_detection", {
|
|
stream_url: stream,
|
|
})
|
|
setTimeout(() => {
|
|
setLoading(false)
|
|
}, 2000)
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (!socket.connected) {
|
|
socket.connect()
|
|
}
|
|
|
|
socket.on("connected", (data) => {
|
|
setSessionId(data.session_id)
|
|
const stream = stream_dummy.find((a) => a.id == currentStream)
|
|
setActiveStream(currentStream)
|
|
socket.emit("start_detection", {
|
|
stream_url: stream?.url ?? stream_dummy[0].url,
|
|
})
|
|
setTimeout(() => {
|
|
setLoading(false)
|
|
}, 2000)
|
|
})
|
|
|
|
socket.on("video_frame", (data) => {
|
|
const thumb = `data:image/jpeg;base64,${data.frame}`
|
|
setImageSrc(thumb)
|
|
})
|
|
|
|
socket.on("plate_detected", (data) => {
|
|
setPlate(data.plate_text)
|
|
setConfidence(data.confidence ?? null)
|
|
|
|
const thumb = `data:image/jpeg;base64,${data.frame_thumb}`
|
|
|
|
if (!currentStream) return
|
|
|
|
addPlate(currentStream, {
|
|
imgThumb: thumb,
|
|
plate: data.plate_text,
|
|
confidence: data.confidence,
|
|
})
|
|
})
|
|
|
|
socket.on("error", (data) => {
|
|
console.error(data.message)
|
|
})
|
|
|
|
socket.on("detection_stopped", (data) => {
|
|
console.log("Selesai. Total:", data.total)
|
|
})
|
|
|
|
return () => {
|
|
socket.emit("stop_detection")
|
|
socket.removeAllListeners()
|
|
socket.disconnect()
|
|
}
|
|
}, [])
|
|
|
|
const [hasMounted, setHasMounted] = useState(false)
|
|
|
|
useEffect(() => {
|
|
setHasMounted(true)
|
|
}, [])
|
|
|
|
if (!hasMounted) {
|
|
return null
|
|
}
|
|
|
|
return (
|
|
<div className="flex flex-col">
|
|
<div className="flex h-[8vh] flex-row items-center gap-10 bg-[#0057B3] px-8 py-5">
|
|
<Link href="/dashboard">
|
|
<ArrowLeftIcon />
|
|
</Link>
|
|
<p className="text-2xl font-semibold">ETLE Toll</p>
|
|
</div>
|
|
<div className="flex h-[92vh] flex-col items-start gap-5 bg-gray-200 p-8 text-black">
|
|
<p className="text-2xl font-semibold">TRAFFIC VIOLATIONS</p>
|
|
<div className="flex gap-2">
|
|
<p>now {currentStream}</p>
|
|
{stream_dummy.map((stream, index) => (
|
|
<Button
|
|
key={stream.id}
|
|
onClick={() => {
|
|
setActiveStream(stream.id)
|
|
startDetection(index)
|
|
}}
|
|
className={`cursor-pointer ${currentStream == stream.id ? "border-2 bg-gray-50 p-2" : ""}`}
|
|
>
|
|
DS{index + 1}
|
|
</Button>
|
|
))}
|
|
</div>
|
|
{loading ? (
|
|
<div className="w-screen rounded-lg md:w-full xl:w-180">
|
|
<Spinner className="h-50 w-50" />
|
|
</div>
|
|
) : (
|
|
imageSrc && (
|
|
<img
|
|
src={imageSrc}
|
|
alt="Realtime"
|
|
className="w-screen rounded-lg md:w-full xl:w-180"
|
|
/>
|
|
)
|
|
)}
|
|
<Button
|
|
variant="ghost"
|
|
className="h-12 bg-white px-10 text-xl shadow-sm"
|
|
>
|
|
{plate || "Belum ada plat"}
|
|
</Button>
|
|
<p className="text-2xl font-semibold">VIOLATIONS</p>
|
|
<p>
|
|
{plate
|
|
? `Plat ${plate} terdeteksi dengan confidence ${confidence}`
|
|
: "Menunggu deteksi..."}
|
|
</p>
|
|
</div>
|
|
<Drawer direction="right">
|
|
<DrawerTrigger asChild title="">
|
|
<Button className="fixed top-1/2 right-0 h-20 w-6 -translate-y-1/2 cursor-pointer rounded-none rounded-l-md bg-white">
|
|
<ChevronLeft />
|
|
</Button>
|
|
</DrawerTrigger>
|
|
<DrawerContent>
|
|
<div className="no-scrollbar space-y-3 overflow-y-auto bg-[#CBCBCBCC] px-4 py-4 text-black">
|
|
<div className="rounded-lg bg-white p-3 text-lg">
|
|
Total Image: {history[activeStream]?.length}
|
|
</div>{" "}
|
|
{history[currentStream]?.map((item, index) => (
|
|
<div key={index} className="rounded-lg bg-white p-3 text-lg">
|
|
<Image
|
|
src={item.imgThumb}
|
|
width={1280}
|
|
height={960}
|
|
alt={"image" + index}
|
|
className="rounded-lg"
|
|
/>
|
|
<p>Plate : {item.plate}</p>
|
|
<p>Confidence: {item.confidence}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</DrawerContent>
|
|
</Drawer>
|
|
</div>
|
|
)
|
|
}
|