Merged in enhancement/sessionpage (pull request #22)
Enhancement/sessionpage
This commit is contained in:
@@ -14,11 +14,7 @@ const FrontCameraOverviewCard = () => {
|
||||
});
|
||||
|
||||
return (
|
||||
<Card
|
||||
className={clsx(
|
||||
"relative min-h-[40vh] md:min-h-[60vh] max-h-[80vh] lg:w-[70%] overflow-y-hidden"
|
||||
)}
|
||||
>
|
||||
<Card className={clsx("relative min-h-[40vh] md:min-h-[60vh] max-h-[80vh] lg:w-[70%] overflow-y-hidden")}>
|
||||
<div className="w-full" {...handlers}>
|
||||
<SightingOverview />
|
||||
</div>
|
||||
|
||||
@@ -30,11 +30,7 @@ const OverviewVideoContainer = ({
|
||||
trackMouse: true,
|
||||
});
|
||||
return (
|
||||
<Card
|
||||
className={clsx(
|
||||
"relative min-h-[40vh] md:min-h-[60vh] max-h-[80vh] lg:w-[70%] overflow-y-hidden"
|
||||
)}
|
||||
>
|
||||
<Card className={clsx("relative min-h-[40vh] md:min-h-[60vh] max-h-[80vh] lg:w-[70%] overflow-y-hidden")}>
|
||||
<div className="w-full" {...handlers}>
|
||||
<SnapshotContainer
|
||||
side={side}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { SightingType } from "../../types/types";
|
||||
import NumberPlate from "../PlateStack/NumberPlate";
|
||||
import SightingModal from "../SightingModal/SightingModal";
|
||||
import InfoBar from "../SightingsWidget/InfoBar";
|
||||
import { useState } from "react";
|
||||
import HotListImg from "/Hotlist_Hit.svg";
|
||||
import { useAlertHitContext } from "../../context/AlertHitContext";
|
||||
@@ -9,7 +8,11 @@ import { useCameraBlackboard } from "../../hooks/useCameraBlackboard";
|
||||
import NPED_CAT_A from "/NPED_Cat_A.svg";
|
||||
import NPED_CAT_B from "/NPED_Cat_B.svg";
|
||||
import NPED_CAT_C from "/NPED_Cat_C.svg";
|
||||
import { checkIsHotListHit, getNPEDCategory } from "../../utils/utils";
|
||||
import { checkIsHotListHit, formatAge, getNPEDCategory } from "../../utils/utils";
|
||||
import { faX } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faClock } from "@fortawesome/free-regular-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import Badge from "../UI/Badge";
|
||||
|
||||
type AlertItemProps = {
|
||||
item: SightingType;
|
||||
@@ -20,7 +23,6 @@ const AlertItem = ({ item }: AlertItemProps) => {
|
||||
const { dispatch } = useAlertHitContext();
|
||||
const { mutation } = useCameraBlackboard();
|
||||
|
||||
// const {d} = useCameraBlackboard();
|
||||
const motionAway = (item?.motion ?? "").toUpperCase() === "AWAY";
|
||||
|
||||
const isHotListHit = checkIsHotListHit(item);
|
||||
@@ -53,9 +55,15 @@ const AlertItem = ({ item }: AlertItemProps) => {
|
||||
dispatch({ type: "REMOVE", payload: item });
|
||||
};
|
||||
return (
|
||||
<div className="flex flex-col w-full">
|
||||
<div className="border border-gray-600 rounded-lg items-center py-1">
|
||||
<InfoBar obj={item} />
|
||||
<div className="flex flex-col w-full relative">
|
||||
<div className="border border-gray-600 rounded-lg items-center p-4">
|
||||
<div className="flex flex-row space-x-3 ml-4">
|
||||
<Badge text={`Seen: ${formatAge(item.timeStampMillis)}`} icon={faClock} />
|
||||
</div>
|
||||
<button onClick={() => handleDelete(item)} className="absolute right-2 top-3">
|
||||
<FontAwesomeIcon icon={faX} size="xl" />
|
||||
</button>
|
||||
|
||||
<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" />}
|
||||
{isNPEDHitA && <img src={NPED_CAT_A} alt="NPEDHITicon" className="h-20 object-contain rounded-md" />}
|
||||
@@ -64,8 +72,10 @@ const AlertItem = ({ item }: AlertItemProps) => {
|
||||
<div className={`border p-1 hidden md:block`}>
|
||||
<img src={item?.plateUrlColour} height={48} width={200} alt="colour patch" />
|
||||
</div>
|
||||
<div className="h-20">
|
||||
<NumberPlate vrm={item.vrm} motion={motionAway} />
|
||||
</div>
|
||||
</div>
|
||||
<SightingModal
|
||||
isSightingModalOpen={isModalOpen}
|
||||
handleClose={closeModal}
|
||||
|
||||
@@ -1,34 +1,14 @@
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useAlertHitContext } from "../../context/AlertHitContext";
|
||||
import { useCameraBlackboard } from "../../hooks/useCameraBlackboard";
|
||||
import type { CameraBlackBoardOptions, SightingType } from "../../types/types";
|
||||
import type { CameraBlackBoardOptions } from "../../types/types";
|
||||
import Card from "../UI/Card";
|
||||
import CardHeader from "../UI/CardHeader";
|
||||
import AlertItem from "./AlertItem";
|
||||
import { faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
const HistoryList = () => {
|
||||
const { state, dispatch, isLoading, error } = useAlertHitContext();
|
||||
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) => {
|
||||
dispatch({ type: "DELETE", payload: [] });
|
||||
mutation.mutate({
|
||||
@@ -38,7 +18,7 @@ const HistoryList = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="h-100 p-4">
|
||||
<Card className="h-100 p-4 col-span-3">
|
||||
<CardHeader title="Alert History" />
|
||||
<button
|
||||
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>}
|
||||
<div className="flex flex-col gap-1 px-2">
|
||||
{state?.alertList?.length > 0 ? (
|
||||
state?.alertList?.map((alertItem, index) => (
|
||||
<div key={index} className="flex flex-row space-x-2">
|
||||
<AlertItem item={alertItem} />
|
||||
<button onClick={() => handleDeleteClick(alertItem)}>
|
||||
<div className="p-4">
|
||||
<FontAwesomeIcon icon={faTrash} size="2x" />
|
||||
<div className="mt-3 grid grid-cols-1 gap-3">
|
||||
{state?.alertList?.map((alertItem) => (
|
||||
<AlertItem item={alertItem} key={alertItem.vrm} />
|
||||
))}
|
||||
</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>
|
||||
</Card>
|
||||
|
||||
@@ -18,18 +18,9 @@ const RearCameraOverviewCard = ({ className }: CardProps) => {
|
||||
});
|
||||
const { mostRecent } = useSightingFeedContext();
|
||||
return (
|
||||
<Card
|
||||
className={clsx(
|
||||
"relative min-h-[40vh] md:min-h-[60vh] h-auto",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<Card className={clsx("relative min-h-[40vh] md:min-h-[60vh] h-auto", className)}>
|
||||
<div className="flex flex-col space-y-3 h-full" {...handlers}>
|
||||
<CardHeader
|
||||
title="Rear Overview"
|
||||
icon={faCamera}
|
||||
sighting={mostRecent}
|
||||
/>
|
||||
<CardHeader title="Rear Overview" icon={faCamera} sighting={mostRecent} />
|
||||
<SightingOverview />
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -9,38 +9,36 @@ const SessionCard = () => {
|
||||
const { dispatch } = useAlertHitContext();
|
||||
|
||||
return (
|
||||
<Card className="p-4">
|
||||
<Card className="p-4 col-span-5">
|
||||
<CardHeader title={"Hit Search"} />
|
||||
<div className="flex flex-col gap-4 px-2">
|
||||
<FormGroup>
|
||||
<label
|
||||
htmlFor="VRM"
|
||||
className="font-medium whitespace-nowrap md:w-1/2 text-left"
|
||||
>
|
||||
<label 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">
|
||||
<FormGroup>
|
||||
<div className="flex flex-row justify-between md:w-full space-x-3">
|
||||
<input
|
||||
id="VRMSelect"
|
||||
name="VRMSelect"
|
||||
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"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</FormGroup>
|
||||
|
||||
<button
|
||||
className="bg-[#26B170] text-white px-4 py-2 rounded hover:bg-green-700 transition w-full mx-auto"
|
||||
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>
|
||||
</FormGroup>
|
||||
{searchTerm && (
|
||||
<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={() => {
|
||||
setSearchTerm("");
|
||||
dispatch({ type: "SEARCH", payload: "" });
|
||||
|
||||
@@ -9,37 +9,21 @@ const SessionCard = () => {
|
||||
|
||||
const handleStartClick = () => {
|
||||
setSessionStarted(!sessionStarted);
|
||||
toast(
|
||||
`${
|
||||
sessionStarted
|
||||
? "Vehicle tracking session Ended"
|
||||
: "Vehicle tracking session Started"
|
||||
}`
|
||||
);
|
||||
toast(`${sessionStarted ? "Vehicle tracking session Ended" : "Vehicle tracking session Started"}`);
|
||||
};
|
||||
|
||||
const sightings = [
|
||||
...new Map(sessionList.map((vehicle) => [vehicle.vrm, vehicle])),
|
||||
];
|
||||
const sightings = [...new Map(sessionList.map((vehicle) => [vehicle.vrm, vehicle]))];
|
||||
|
||||
const dedupedSightings = sightings.map((sighting) => sighting[1]);
|
||||
|
||||
const vehicles = dedupedSightings.reduce<
|
||||
Record<string, ReducedSightingType[]>
|
||||
>(
|
||||
const vehicles = dedupedSightings.reduce<Record<string, ReducedSightingType[]>>(
|
||||
(acc, item) => {
|
||||
if (item.metadata?.npedJSON["NPED CATEGORY"] === "A")
|
||||
acc.npedCatA.push(item);
|
||||
if (item.metadata?.npedJSON["NPED CATEGORY"] === "B")
|
||||
acc.npedCatB.push(item);
|
||||
if (item.metadata?.npedJSON["NPED CATEGORY"] === "C")
|
||||
acc.npedCatC.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);
|
||||
if (item.metadata?.npedJSON["NPED CATEGORY"] === "A") acc.npedCatA.push(item);
|
||||
if (item.metadata?.npedJSON["NPED CATEGORY"] === "B") acc.npedCatB.push(item);
|
||||
if (item.metadata?.npedJSON["NPED CATEGORY"] === "C") acc.npedCatC.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;
|
||||
},
|
||||
@@ -54,13 +38,11 @@ const SessionCard = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<Card className="p-4">
|
||||
<Card className="p-4 col-span-3">
|
||||
<CardHeader title="Session" />
|
||||
<div className="flex flex-col gap-4 px-2">
|
||||
<div className="flex flex-col gap-4 px-3">
|
||||
<button
|
||||
className={`${
|
||||
sessionStarted ? "bg-red-600" : "bg-[#26B170]"
|
||||
} text-white px-4 py-2 rounded ${
|
||||
className={`${sessionStarted ? "bg-red-600" : "bg-[#26B170]"} text-white px-4 py-2 rounded ${
|
||||
sessionStarted ? "hover:bg-red-700" : "hover:bg-green-700"
|
||||
} transition w-full`}
|
||||
onClick={handleStartClick}
|
||||
@@ -69,12 +51,30 @@ const SessionCard = () => {
|
||||
</button>
|
||||
|
||||
<ul className="text-white space-y-2">
|
||||
<li>Number of Vehicles: {dedupedSightings.length} </li>
|
||||
<li>Vehicles without Tax: {vehicles.notTaxed.length}</li>
|
||||
<li>Vehicles without MOT: {vehicles.notMOT.length}</li>
|
||||
<li>Vehicles with NPED Cat A: {vehicles.npedCatA.length}</li>
|
||||
<li>Vehicles with NPED Cat B: {vehicles.npedCatB.length}</li>
|
||||
<li>Vehicles with NPED Cat C: {vehicles.npedCatC.length}</li>
|
||||
<li className="rounded-xl border border-slate-800 bg-slate-800/60 p-3 shadow-sm flex flex-row justify-between">
|
||||
<p>Number of Vehicles:</p>
|
||||
<span className="font-bold text-green-600 text-xl">{dedupedSightings.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 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>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -120,7 +120,11 @@ const SightingModal = ({ isSightingModalOpen, handleClose, sighting, onDelete }:
|
||||
{hotlistName && (
|
||||
<div>
|
||||
<p className="text-gray-300">Hotlist</p>
|
||||
<p className="font-medium text-2xl break-all">{hotlistName ? hotlistName[0] : "-"}</p>
|
||||
<div className="items-center px-2.5 py-0.5 rounded-sm me-2 bg-amber-500">
|
||||
<p className="font-medium text-2xl break-all text-amber-800">
|
||||
{hotlistName ? hotlistName[0] : "-"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
18
src/components/UI/Badge.tsx
Normal file
18
src/components/UI/Badge.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { Icon, IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
type BadgeProps = {
|
||||
icon?: Icon | IconDefinition;
|
||||
text: string;
|
||||
};
|
||||
|
||||
const Badge = ({ icon, text }: BadgeProps) => {
|
||||
return (
|
||||
<span className="text-md font-medium inline-flex items-center px-2.5 py-0.5 rounded-sm me-2 bg-blue-900 text-blue-200 border border-blue-500 space-x-2">
|
||||
{icon && <FontAwesomeIcon icon={icon} />}
|
||||
<span>{text}</span>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export default Badge;
|
||||
@@ -10,7 +10,7 @@ const Card = ({ children, className }: CardProps) => {
|
||||
return (
|
||||
<div
|
||||
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
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -11,8 +11,10 @@ const Session = () => {
|
||||
return (
|
||||
<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="grid grid-cols-1 lg:grid-cols-8 col-span-2 w-full">
|
||||
<HitSearchCard />
|
||||
<SessionCard />
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<HistoryList />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user