- improved UI for sessions page
This commit is contained in:
@@ -14,11 +14,7 @@ const FrontCameraOverviewCard = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card className={clsx("relative min-h-[40vh] md:min-h-[60vh] max-h-[80vh] lg:w-[70%] overflow-y-hidden")}>
|
||||||
className={clsx(
|
|
||||||
"relative min-h-[40vh] md:min-h-[60vh] max-h-[80vh] lg:w-[70%] overflow-y-hidden"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="w-full" {...handlers}>
|
<div className="w-full" {...handlers}>
|
||||||
<SightingOverview />
|
<SightingOverview />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -30,11 +30,7 @@ const OverviewVideoContainer = ({
|
|||||||
trackMouse: true,
|
trackMouse: true,
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card className={clsx("relative min-h-[40vh] md:min-h-[60vh] max-h-[80vh] lg:w-[70%] overflow-y-hidden")}>
|
||||||
className={clsx(
|
|
||||||
"relative min-h-[40vh] md:min-h-[60vh] max-h-[80vh] lg:w-[70%] overflow-y-hidden"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="w-full" {...handlers}>
|
<div className="w-full" {...handlers}>
|
||||||
<SnapshotContainer
|
<SnapshotContainer
|
||||||
side={side}
|
side={side}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import NPED_CAT_A from "/NPED_Cat_A.svg";
|
|||||||
import NPED_CAT_B from "/NPED_Cat_B.svg";
|
import NPED_CAT_B from "/NPED_Cat_B.svg";
|
||||||
import NPED_CAT_C from "/NPED_Cat_C.svg";
|
import NPED_CAT_C from "/NPED_Cat_C.svg";
|
||||||
import { checkIsHotListHit, getNPEDCategory } from "../../utils/utils";
|
import { checkIsHotListHit, getNPEDCategory } from "../../utils/utils";
|
||||||
|
import { faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
type AlertItemProps = {
|
type AlertItemProps = {
|
||||||
item: SightingType;
|
item: SightingType;
|
||||||
@@ -53,9 +55,12 @@ const AlertItem = ({ item }: AlertItemProps) => {
|
|||||||
dispatch({ type: "REMOVE", payload: item });
|
dispatch({ type: "REMOVE", payload: item });
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full relative">
|
||||||
<div className="border border-gray-600 rounded-lg items-center py-1">
|
<div className="border border-gray-600 rounded-lg items-center py-1">
|
||||||
<InfoBar obj={item} />
|
<InfoBar obj={item} />
|
||||||
|
<button onClick={() => handleDelete(item)} className="absolute right-2 top-1">
|
||||||
|
<FontAwesomeIcon icon={faTrash} size="xl" />
|
||||||
|
</button>
|
||||||
<div className="flex flex-row p-4 w-full mx-auto justify-between" onClick={handleClick}>
|
<div className="flex flex-row p-4 w-full mx-auto justify-between" onClick={handleClick}>
|
||||||
{isHotListHit && <img src={HotListImg} alt="hotlistHit" className="h-20 object-contain rounded-md" />}
|
{isHotListHit && <img src={HotListImg} alt="hotlistHit" className="h-20 object-contain rounded-md" />}
|
||||||
{isNPEDHitA && <img src={NPED_CAT_A} alt="NPEDHITicon" className="h-20 object-contain rounded-md" />}
|
{isNPEDHitA && <img src={NPED_CAT_A} alt="NPEDHITicon" className="h-20 object-contain rounded-md" />}
|
||||||
@@ -64,7 +69,9 @@ const AlertItem = ({ item }: AlertItemProps) => {
|
|||||||
<div className={`border p-1 hidden md:block`}>
|
<div className={`border p-1 hidden md:block`}>
|
||||||
<img src={item?.plateUrlColour} height={48} width={200} alt="colour patch" />
|
<img src={item?.plateUrlColour} height={48} width={200} alt="colour patch" />
|
||||||
</div>
|
</div>
|
||||||
<NumberPlate vrm={item.vrm} motion={motionAway} />
|
<div className="h-20">
|
||||||
|
<NumberPlate vrm={item.vrm} motion={motionAway} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<SightingModal
|
<SightingModal
|
||||||
isSightingModalOpen={isModalOpen}
|
isSightingModalOpen={isModalOpen}
|
||||||
|
|||||||
@@ -1,34 +1,14 @@
|
|||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
import { useAlertHitContext } from "../../context/AlertHitContext";
|
import { useAlertHitContext } from "../../context/AlertHitContext";
|
||||||
import { useCameraBlackboard } from "../../hooks/useCameraBlackboard";
|
import { useCameraBlackboard } from "../../hooks/useCameraBlackboard";
|
||||||
import type { CameraBlackBoardOptions, SightingType } from "../../types/types";
|
import type { CameraBlackBoardOptions } from "../../types/types";
|
||||||
import Card from "../UI/Card";
|
import Card from "../UI/Card";
|
||||||
import CardHeader from "../UI/CardHeader";
|
import CardHeader from "../UI/CardHeader";
|
||||||
import AlertItem from "./AlertItem";
|
import AlertItem from "./AlertItem";
|
||||||
import { faTrash } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
|
|
||||||
const HistoryList = () => {
|
const HistoryList = () => {
|
||||||
const { state, dispatch, isLoading, error } = useAlertHitContext();
|
const { state, dispatch, isLoading, error } = useAlertHitContext();
|
||||||
const { mutation } = useCameraBlackboard();
|
const { mutation } = useCameraBlackboard();
|
||||||
|
|
||||||
const handleDeleteClick = async (deletedItem: SightingType) => {
|
|
||||||
const res = await mutation.mutateAsync({
|
|
||||||
operation: "VIEW",
|
|
||||||
path: "alertHistory",
|
|
||||||
});
|
|
||||||
const oldArray = res?.result;
|
|
||||||
const updatedArray = oldArray?.filter(
|
|
||||||
(item: SightingType) => item?.ref !== deletedItem?.ref
|
|
||||||
);
|
|
||||||
|
|
||||||
mutation.mutate({
|
|
||||||
operation: "INSERT",
|
|
||||||
path: "alertHistory",
|
|
||||||
value: updatedArray,
|
|
||||||
});
|
|
||||||
dispatch({ type: "REMOVE", payload: deletedItem });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClearListClick = (listName: CameraBlackBoardOptions) => {
|
const handleClearListClick = (listName: CameraBlackBoardOptions) => {
|
||||||
dispatch({ type: "DELETE", payload: [] });
|
dispatch({ type: "DELETE", payload: [] });
|
||||||
mutation.mutate({
|
mutation.mutate({
|
||||||
@@ -38,7 +18,7 @@ const HistoryList = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="h-100 p-4">
|
<Card className="h-100 p-4 col-span-3">
|
||||||
<CardHeader title="Alert History" />
|
<CardHeader title="Alert History" />
|
||||||
<button
|
<button
|
||||||
className="bg-red-600 text-white px-4 py-2 rounded hover:bg-red-700 transition md:w-[10%] mb-2"
|
className="bg-red-600 text-white px-4 py-2 rounded hover:bg-red-700 transition md:w-[10%] mb-2"
|
||||||
@@ -50,18 +30,23 @@ const HistoryList = () => {
|
|||||||
{error && <p className="text-red-500 px-2">Error: {error.message}</p>}
|
{error && <p className="text-red-500 px-2">Error: {error.message}</p>}
|
||||||
<div className="flex flex-col gap-1 px-2">
|
<div className="flex flex-col gap-1 px-2">
|
||||||
{state?.alertList?.length > 0 ? (
|
{state?.alertList?.length > 0 ? (
|
||||||
state?.alertList?.map((alertItem, index) => (
|
<div className="mt-3 grid grid-cols-1 gap-3">
|
||||||
<div key={index} className="flex flex-row space-x-2">
|
{state?.alertList?.map((alertItem) => (
|
||||||
<AlertItem item={alertItem} />
|
<AlertItem item={alertItem} key={alertItem.vrm} />
|
||||||
<button onClick={() => handleDeleteClick(alertItem)}>
|
))}
|
||||||
<div className="p-4">
|
</div>
|
||||||
<FontAwesomeIcon icon={faTrash} size="2x" />
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
) : (
|
) : (
|
||||||
<p>No Alert results</p>
|
<div className="mt-4 flex flex-col items-center justify-center rounded-2xl border border-slate-800 bg-slate-900/40 p-10 text-center">
|
||||||
|
<div className="mb-3 rounded-xl bg-slate-800 px-3 py-1 text-xs uppercase tracking-wider text-slate-400">
|
||||||
|
No Alert Results
|
||||||
|
</div>
|
||||||
|
<p className="max-w-md text-slate-300">
|
||||||
|
Alerts will appear here in real-time once there are <span className="text-emerald-400">Hotlist</span> or{" "}
|
||||||
|
<span className="text-amber-600">NPED</span> hits. Use{" "}
|
||||||
|
<span className="text-emerald-400">Start Session</span> to begin capturing results, or add a{" "}
|
||||||
|
<span className="text-emerald-400">Sighting</span> from the sighting list.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -18,18 +18,9 @@ const RearCameraOverviewCard = ({ className }: CardProps) => {
|
|||||||
});
|
});
|
||||||
const { mostRecent } = useSightingFeedContext();
|
const { mostRecent } = useSightingFeedContext();
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card className={clsx("relative min-h-[40vh] md:min-h-[60vh] h-auto", className)}>
|
||||||
className={clsx(
|
|
||||||
"relative min-h-[40vh] md:min-h-[60vh] h-auto",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="flex flex-col space-y-3 h-full" {...handlers}>
|
<div className="flex flex-col space-y-3 h-full" {...handlers}>
|
||||||
<CardHeader
|
<CardHeader title="Rear Overview" icon={faCamera} sighting={mostRecent} />
|
||||||
title="Rear Overview"
|
|
||||||
icon={faCamera}
|
|
||||||
sighting={mostRecent}
|
|
||||||
/>
|
|
||||||
<SightingOverview />
|
<SightingOverview />
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -9,38 +9,36 @@ const SessionCard = () => {
|
|||||||
const { dispatch } = useAlertHitContext();
|
const { dispatch } = useAlertHitContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="p-4">
|
<Card className="p-4 col-span-5">
|
||||||
<CardHeader title={"Hit Search"} />
|
<CardHeader title={"Hit Search"} />
|
||||||
<div className="flex flex-col gap-4 px-2">
|
<div className="flex flex-col gap-4 px-2">
|
||||||
|
<label htmlFor="VRM" className="font-medium whitespace-nowrap md:w-1/2 text-left">
|
||||||
|
VRM (Min 2 letters)
|
||||||
|
</label>
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<label
|
<div className="flex flex-row justify-between md:w-full space-x-3">
|
||||||
htmlFor="VRM"
|
|
||||||
className="font-medium whitespace-nowrap md:w-1/2 text-left"
|
|
||||||
>
|
|
||||||
VRM (Min 2 letters)
|
|
||||||
</label>
|
|
||||||
<div className="flex-1 flex justify-end md:w-2/3">
|
|
||||||
<input
|
<input
|
||||||
id="VRMSelect"
|
id="VRMSelect"
|
||||||
name="VRMSelect"
|
name="VRMSelect"
|
||||||
type="text"
|
type="text"
|
||||||
className="p-2 border border-gray-400 rounded-lg w-full max-w-xs"
|
className="p-2 border border-gray-400 rounded-lg w-full max-w-[70%] focus:border-emerald-400 focus:outline-none focus:ring-2 focus:ring-emerald-400/30"
|
||||||
placeholder="Enter VRM"
|
placeholder="Enter VRM"
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="bg-[#26B170] text-white px-4 py-2 rounded hover:bg-green-700 transition w-[30%] mx-3"
|
||||||
|
onClick={() => dispatch({ type: "SEARCH", payload: searchTerm })}
|
||||||
|
disabled={searchTerm.trim().length < 2}
|
||||||
|
>
|
||||||
|
Search Hit list
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<button
|
|
||||||
className="bg-[#26B170] text-white px-4 py-2 rounded hover:bg-green-700 transition w-full mx-auto"
|
|
||||||
onClick={() => dispatch({ type: "SEARCH", payload: searchTerm })}
|
|
||||||
disabled={searchTerm.trim().length < 2}
|
|
||||||
>
|
|
||||||
Search Hit list
|
|
||||||
</button>
|
|
||||||
{searchTerm && (
|
{searchTerm && (
|
||||||
<button
|
<button
|
||||||
className="bg-gray-300 text-gray-900 px-4 py-2 rounded hover:bg-gray-700 transition w-full mx-auto"
|
className="bg-gray-300 text-gray-900 px-4 py-2 rounded hover:bg-gray-700 transition w-[30%] "
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSearchTerm("");
|
setSearchTerm("");
|
||||||
dispatch({ type: "SEARCH", payload: "" });
|
dispatch({ type: "SEARCH", payload: "" });
|
||||||
|
|||||||
@@ -9,37 +9,21 @@ const SessionCard = () => {
|
|||||||
|
|
||||||
const handleStartClick = () => {
|
const handleStartClick = () => {
|
||||||
setSessionStarted(!sessionStarted);
|
setSessionStarted(!sessionStarted);
|
||||||
toast(
|
toast(`${sessionStarted ? "Vehicle tracking session Ended" : "Vehicle tracking session Started"}`);
|
||||||
`${
|
|
||||||
sessionStarted
|
|
||||||
? "Vehicle tracking session Ended"
|
|
||||||
: "Vehicle tracking session Started"
|
|
||||||
}`
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const sightings = [
|
const sightings = [...new Map(sessionList.map((vehicle) => [vehicle.vrm, vehicle]))];
|
||||||
...new Map(sessionList.map((vehicle) => [vehicle.vrm, vehicle])),
|
|
||||||
];
|
|
||||||
|
|
||||||
const dedupedSightings = sightings.map((sighting) => sighting[1]);
|
const dedupedSightings = sightings.map((sighting) => sighting[1]);
|
||||||
|
|
||||||
const vehicles = dedupedSightings.reduce<
|
const vehicles = dedupedSightings.reduce<Record<string, ReducedSightingType[]>>(
|
||||||
Record<string, ReducedSightingType[]>
|
|
||||||
>(
|
|
||||||
(acc, item) => {
|
(acc, item) => {
|
||||||
if (item.metadata?.npedJSON["NPED CATEGORY"] === "A")
|
if (item.metadata?.npedJSON["NPED CATEGORY"] === "A") acc.npedCatA.push(item);
|
||||||
acc.npedCatA.push(item);
|
if (item.metadata?.npedJSON["NPED CATEGORY"] === "B") acc.npedCatB.push(item);
|
||||||
if (item.metadata?.npedJSON["NPED CATEGORY"] === "B")
|
if (item.metadata?.npedJSON["NPED CATEGORY"] === "C") acc.npedCatC.push(item);
|
||||||
acc.npedCatB.push(item);
|
if (item.metadata?.npedJSON["NPED CATEGORY"] === "D") acc.npedCatD.push(item);
|
||||||
if (item.metadata?.npedJSON["NPED CATEGORY"] === "C")
|
if (item.metadata?.npedJSON["TAX STATUS"] === false) acc.notTaxed.push(item);
|
||||||
acc.npedCatC.push(item);
|
if (item.metadata?.npedJSON["MOT STATUS"] === false) acc.notMOT.push(item);
|
||||||
if (item.metadata?.npedJSON["NPED CATEGORY"] === "D")
|
|
||||||
acc.npedCatD.push(item);
|
|
||||||
if (item.metadata?.npedJSON["TAX STATUS"] === false)
|
|
||||||
acc.notTaxed.push(item);
|
|
||||||
if (item.metadata?.npedJSON["MOT STATUS"] === false)
|
|
||||||
acc.notMOT.push(item);
|
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
@@ -54,13 +38,11 @@ const SessionCard = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="p-4">
|
<Card className="p-4 col-span-3">
|
||||||
<CardHeader title="Session" />
|
<CardHeader title="Session" />
|
||||||
<div className="flex flex-col gap-4 px-2">
|
<div className="flex flex-col gap-4 px-3">
|
||||||
<button
|
<button
|
||||||
className={`${
|
className={`${sessionStarted ? "bg-red-600" : "bg-[#26B170]"} text-white px-4 py-2 rounded ${
|
||||||
sessionStarted ? "bg-red-600" : "bg-[#26B170]"
|
|
||||||
} text-white px-4 py-2 rounded ${
|
|
||||||
sessionStarted ? "hover:bg-red-700" : "hover:bg-green-700"
|
sessionStarted ? "hover:bg-red-700" : "hover:bg-green-700"
|
||||||
} transition w-full`}
|
} transition w-full`}
|
||||||
onClick={handleStartClick}
|
onClick={handleStartClick}
|
||||||
@@ -69,12 +51,30 @@ const SessionCard = () => {
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<ul className="text-white space-y-2">
|
<ul className="text-white space-y-2">
|
||||||
<li>Number of Vehicles: {dedupedSightings.length} </li>
|
<li className="rounded-xl border border-slate-800 bg-slate-800/60 p-3 shadow-sm flex flex-row justify-between">
|
||||||
<li>Vehicles without Tax: {vehicles.notTaxed.length}</li>
|
<p>Number of Vehicles:</p>
|
||||||
<li>Vehicles without MOT: {vehicles.notMOT.length}</li>
|
<span className="font-bold text-green-600 text-xl">{dedupedSightings.length}</span>
|
||||||
<li>Vehicles with NPED Cat A: {vehicles.npedCatA.length}</li>
|
</li>
|
||||||
<li>Vehicles with NPED Cat B: {vehicles.npedCatB.length}</li>
|
<li className="rounded-xl border border-slate-800 bg-slate-800/60 p-3 shadow-sm flex flex-row justify-between">
|
||||||
<li>Vehicles with NPED Cat C: {vehicles.npedCatC.length}</li>
|
<p>Vehicles without Tax:</p>
|
||||||
|
<span className="font-bold text-amber-600 text-xl">{vehicles.notTaxed.length}</span>
|
||||||
|
</li>
|
||||||
|
<li className="rounded-xl border border-slate-800 bg-slate-800/60 p-3 shadow-sm flex flex-row justify-between">
|
||||||
|
<p>Vehicles without MOT:</p>{" "}
|
||||||
|
<span className="font-bold text-red-500 text-xl">{vehicles.notMOT.length}</span>
|
||||||
|
</li>
|
||||||
|
<li className="rounded-xl border border-slate-800 bg-slate-800/60 p-3 shadow-sm flex flex-row justify-between">
|
||||||
|
<p>Vehicles with NPED Cat A:</p>
|
||||||
|
<span className="font-bold text-gray-300 text-xl">{vehicles.npedCatA.length}</span>
|
||||||
|
</li>
|
||||||
|
<li className="rounded-xl border border-slate-800 bg-slate-800/60 p-3 shadow-sm flex flex-row justify-between">
|
||||||
|
<p>Vehicles with NPED Cat B:</p>{" "}
|
||||||
|
<span className="font-bold text-gray-300text-xl">{vehicles.npedCatB.length}</span>
|
||||||
|
</li>
|
||||||
|
<li className="rounded-xl border border-slate-800 bg-slate-800/60 p-3 shadow-sm flex flex-row justify-between">
|
||||||
|
Vehicles with NPED Cat C:{" "}
|
||||||
|
<span className="font-bold text-gray-300 text-xl">{vehicles.npedCatC.length}</span>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const Card = ({ children, className }: CardProps) => {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"bg-[#253445] rounded-lg mt-4 mx-2 shadow-2xl overflow-x-hidden md:row-span-1 px-2",
|
"bg-[#253445] rounded-lg mt-4 mx-2 shadow-2xl overflow-x-hidden md:row-span-1 px-2 border border-gray-600 ",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ const Session = () => {
|
|||||||
return (
|
return (
|
||||||
<SightingFeedProvider>
|
<SightingFeedProvider>
|
||||||
<div className="mx-auto grid grid-cols-1 sm:grid-cols-1 lg:grid-cols-2 gap-2 px-1 sm:px-2 lg:px-0 w-full">
|
<div className="mx-auto grid grid-cols-1 sm:grid-cols-1 lg:grid-cols-2 gap-2 px-1 sm:px-2 lg:px-0 w-full">
|
||||||
<HitSearchCard />
|
<div className="grid grid-cols-1 lg:grid-cols-8 col-span-2 w-full">
|
||||||
<SessionCard />
|
<HitSearchCard />
|
||||||
|
<SessionCard />
|
||||||
|
</div>
|
||||||
<div className="col-span-2">
|
<div className="col-span-2">
|
||||||
<HistoryList />
|
<HistoryList />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user