added NPED cat sorting

This commit is contained in:
2025-09-22 09:26:45 +01:00
parent 50cedaf2c4
commit 69eb5cc7be
15 changed files with 212 additions and 46 deletions

5
.env
View File

@@ -1,5 +1,5 @@
VITE_BASEURL=http://192.168.75.11/ VITE_BASEURL=http://192.168.75.11/
VITE_CAM_BASE=http://100.72.72.70:8080 VITE_CAM_BASE=http://100.113.222.39
VITE_FOLKESTONE_BASE=http://100.116.253.81 VITE_FOLKESTONE_BASE=http://100.116.253.81
VITE_TESTURL=http://100.82.205.44/SightingListRear/sightingSummary?mostRecentRef=-1 VITE_TESTURL=http://100.82.205.44/SightingListRear/sightingSummary?mostRecentRef=-1
VITE_OUTSIDE_BASEURL=http://100.82.205.44/api VITE_OUTSIDE_BASEURL=http://100.82.205.44/api
@@ -17,4 +17,5 @@ VITE_AGX_FRONT_BASE=http://100.72.72.70:8080/
VITE_LOCAL=http://10.42.0.1:8080/SightingListRear/sightingSummary?mostRecentRef= VITE_LOCAL=http://10.42.0.1:8080/SightingListRear/sightingSummary?mostRecentRef=
VITE_LOCAL_FRONT=http://10.42.0.1:8080/SightingListFront/sightingSummary?mostRecentRef= VITE_LOCAL_FRONT=http://10.42.0.1:8080/SightingListFront/sightingSummary?mostRecentRef=
VITE_LOCAL_BASE=http://10.42.0.1:8080/ VITE_LOCAL_BASE=http://10.42.0.1:8080/
VITE_LOCAL_BASE_NEW=http://100.113.222.39

30
public/NPED_Cat_A.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 25 KiB

31
public/NPED_Cat_B.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 25 KiB

29
public/NPED_Cat_C.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -1,6 +1,6 @@
import Container from "./components/UI/Container"; import Container from "./components/UI/Container";
import Dashboard from "./pages/Dashboard"; import Dashboard from "./pages/Dashboard";
import { Route, Routes } from "react-router"; import { Navigate, Route, Routes } from "react-router";
import FrontCamera from "./pages/FrontCamera"; import FrontCamera from "./pages/FrontCamera";
import RearCamera from "./pages/RearCamera"; import RearCamera from "./pages/RearCamera";
import SystemSettings from "./pages/SystemSettings"; import SystemSettings from "./pages/SystemSettings";
@@ -19,6 +19,7 @@ function App() {
<Route path="rear-camera-settings" element={<RearCamera />} /> <Route path="rear-camera-settings" element={<RearCamera />} />
<Route path="system-settings" element={<SystemSettings />} /> <Route path="system-settings" element={<SystemSettings />} />
<Route path="session-settings" element={<Session />} /> <Route path="session-settings" element={<Session />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Route> </Route>
</Routes> </Routes>
</AlertHitProvider> </AlertHitProvider>

View File

@@ -13,7 +13,6 @@ const CameraSettings = ({ title, side }: { title: string; side: string }) => {
updateCameraConfigError, updateCameraConfigError,
} = useFetchCameraConfig(side); } = useFetchCameraConfig(side);
console.log(updateCameraConfigError); console.log(updateCameraConfigError);
console.log(isPending);
return ( return (
<Card> <Card>

View File

@@ -1,5 +1,4 @@
import { Field, useFormikContext } from "formik"; import { Field, useFormikContext } from "formik";
import FormToggle from "../components/FormToggle"; import FormToggle from "../components/FormToggle";
export const ValuesComponent = () => { export const ValuesComponent = () => {
@@ -11,13 +10,13 @@ const BearerTypeFields = () => {
return ( return (
<div className="flex flex-col space-y-4"> <div className="flex flex-col space-y-4">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3 justify-between">
<label htmlFor="format">Format</label> <label htmlFor="format">Format</label>
<Field <Field
as="select" as="select"
name="format" name="format"
id="format" id="format"
className="p-2 border border-gray-400 rounded-lg text-white bg-[#253445]" className="p-2 border border-gray-400 rounded-lg text-white bg-[#253445] w-full md:w-60"
> >
<option value="JSON">JSON</option> <option value="JSON">JSON</option>
<option value="BOF2">BOF2</option> <option value="BOF2">BOF2</option>

View File

@@ -18,7 +18,7 @@ const ChannelFields = () => {
type="text" type="text"
id="backoffice" id="backoffice"
placeholder="https://www.backoffice.com" placeholder="https://www.backoffice.com"
className="p-1.5 border border-gray-400 rounded-lg" className="p-1.5 border border-gray-400 rounded-lg w-full md:w-60"
/> />
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
@@ -28,7 +28,7 @@ const ChannelFields = () => {
type="text" type="text"
id="username" id="username"
placeholder="Back office username" placeholder="Back office username"
className="p-1.5 border border-gray-400 rounded-lg" className="p-1.5 border border-gray-400 rounded-lg w-full md:w-60"
/> />
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
@@ -38,7 +38,7 @@ const ChannelFields = () => {
type={showPwd ? "text" : "password"} type={showPwd ? "text" : "password"}
id="password" id="password"
placeholder="Back office password" placeholder="Back office password"
className="p-1.5 border border-gray-400 rounded-lg" className="p-1.5 border border-gray-400 rounded-lg w-full md:w-60"
/> />
<FontAwesomeIcon <FontAwesomeIcon
type="button" type="button"
@@ -53,7 +53,7 @@ const ChannelFields = () => {
name={"connectTimeoutSeconds"} name={"connectTimeoutSeconds"}
type="number" type="number"
id="connectTimeoutSeconds" id="connectTimeoutSeconds"
className="p-1.5 border border-gray-400 rounded-lg" className="p-1.5 border border-gray-400 rounded-lg w-full md:w-60"
/> />
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
@@ -63,7 +63,7 @@ const ChannelFields = () => {
type="number" type="number"
id="readTimeoutSeconds" id="readTimeoutSeconds"
placeholder="https://example.com" placeholder="https://example.com"
className="p-1.5 border border-gray-400 rounded-lg" className="p-1.5 border border-gray-400 rounded-lg w-full md:w-60"
/> />
</FormGroup> </FormGroup>
</div> </div>

View File

@@ -2,7 +2,7 @@ import { Field } from "formik";
const FormToggle = ({ name, label }: { name: string; label?: string }) => { const FormToggle = ({ name, label }: { name: string; label?: string }) => {
return ( return (
<label className="flex items-center gap-3 cursor-pointer select-none w-50"> <label className="flex items-center gap-3 cursor-pointer select-none w-50 justify-between">
<span className="text-sm">{label}</span> <span className="text-sm">{label}</span>
<Field id={name} type="checkbox" name={name} className="sr-only peer" /> <Field id={name} type="checkbox" name={name} className="sr-only peer" />
<div <div

View File

@@ -6,6 +6,9 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useAlertHitContext } from "../../context/AlertHitContext"; import { useAlertHitContext } from "../../context/AlertHitContext";
import { toast, Toaster } from "sonner"; import { toast, Toaster } from "sonner";
import HotListImg from "/Hotlist_Hit.svg"; import HotListImg from "/Hotlist_Hit.svg";
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";
type SightingModalProps = { type SightingModalProps = {
isSightingModalOpen: boolean; isSightingModalOpen: boolean;
@@ -33,6 +36,10 @@ const SightingModal = ({
const motionAway = (sighting?.motion ?? "").toUpperCase() === "AWAY"; const motionAway = (sighting?.motion ?? "").toUpperCase() === "AWAY";
const isHotListHit = sighting?.metadata?.hotlistMatches?.Hotlist0 === true; const isHotListHit = sighting?.metadata?.hotlistMatches?.Hotlist0 === true;
const isNPEDHitA = sighting?.metadata?.npedJSON?.["NPED CATEGORY"] === "A";
const isNPEDHitB = sighting?.metadata?.npedJSON?.["NPED CATEGORY"] === "B";
const isNPEDHitC = sighting?.metadata?.npedJSON?.["NPED CATEGORY"] === "C";
const isNPEDHitD = sighting?.metadata?.npedJSON?.["NPED CATEGORY"] === "D";
return ( return (
<> <>
@@ -60,6 +67,34 @@ const SightingModal = ({
className="h-20 object-contain rounded-md" className="h-20 object-contain rounded-md"
/> />
)} )}
{isNPEDHitA && (
<img
src={NPED_CAT_A}
alt="hotlistHit"
className="h-20 object-contain rounded-md"
/>
)}
{isNPEDHitB && (
<img
src={NPED_CAT_B}
alt="hotlistHit"
className="h-20 object-contain rounded-md"
/>
)}
{isNPEDHitC && (
<img
src={NPED_CAT_C}
alt="hotlistHit"
className="h-20 object-contain rounded-md"
/>
)}
{isNPEDHitD && (
<img
src={NPED_CAT_A}
alt="hotlistHit"
className="h-20 object-contain rounded-md"
/>
)}
</div> </div>
<div className="flex flex-col md:flex-row items-center gap-3"> <div className="flex flex-col md:flex-row items-center gap-3">
<img <img

View File

@@ -6,6 +6,7 @@ type InfoBarprops = {
}; };
const InfoBar = ({ obj }: InfoBarprops) => { const InfoBar = ({ obj }: InfoBarprops) => {
// const isNPEDHit = obj?.metadata?.npedJSON?.status_code === 404; // const isNPEDHit = obj?.metadata?.npedJSON?.status_code === 404;
const isNPEDHitD = obj?.metadata?.npedJSON?.["NPED CATEGORY"] === "D";
return ( return (
<div className="flex items-center gap-3 text-xs bg-neutral-900 px-2 py-1 rounded justify-between"> <div className="flex items-center gap-3 text-xs bg-neutral-900 px-2 py-1 rounded justify-between">
@@ -19,11 +20,11 @@ const InfoBar = ({ obj }: InfoBarprops) => {
</div> </div>
<div className="min-w-14 opacity-80 "> <div className="min-w-14 opacity-80 ">
{/* {isNPEDHit ? ( {isNPEDHitD ? (
<span className="text-red-500 font-semibold">NPED HIT</span> <span className="text-amber-500 font-semibold">NPED HIT CAT D</span>
) : ( ) : (
"" ""
)} */} )}
</div> </div>
</div> </div>
); );

View File

@@ -7,9 +7,12 @@ import CardHeader from "../UI/CardHeader";
import clsx from "clsx"; import clsx from "clsx";
import { useSightingFeedContext } from "../../context/SightingFeedContext"; import { useSightingFeedContext } from "../../context/SightingFeedContext";
import SightingModal from "../SightingModal/SightingModal"; import SightingModal from "../SightingModal/SightingModal";
// import { useAlertHitContext } from "../../context/AlertHitContext"; import { useAlertHitContext } from "../../context/AlertHitContext";
import InfoBar from "./InfoBar"; import InfoBar from "./InfoBar";
import HotListImg from "/Hotlist_Hit.svg"; import HotListImg from "/Hotlist_Hit.svg";
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";
function useNow(tickMs = 1000) { function useNow(tickMs = 1000) {
const [, setNow] = useState(() => Date.now()); const [, setNow] = useState(() => Date.now());
@@ -17,16 +20,16 @@ function useNow(tickMs = 1000) {
const id = setInterval(() => setNow(Date.now()), tickMs); const id = setInterval(() => setNow(Date.now()), tickMs);
return () => clearInterval(id); return () => clearInterval(id);
}, [tickMs]); }, [tickMs]);
return undefined; return null;
} }
type SightingHistoryProps = { type SightingHistoryProps = {
baseUrl?: string; baseUrl?: string;
entries?: number; // number of rows to show entries?: number;
pollMs?: number; // poll frequency pollMs?: number;
autoSelectLatest?: boolean; autoSelectLatest?: boolean;
title: string; title: string;
className: React.HTMLAttributes<HTMLDivElement> | string; className: string;
}; };
// /type SightingHistoryWidgetProps = React.HTMLAttributes<HTMLDivElement>; // /type SightingHistoryWidgetProps = React.HTMLAttributes<HTMLDivElement>;
@@ -45,17 +48,17 @@ export default function SightingHistoryWidget({
selectedSighting, selectedSighting,
} = useSightingFeedContext(); } = useSightingFeedContext();
// const { dispatch } = useAlertHitContext(); const { dispatch } = useAlertHitContext();
const hasAutoOpenedRef = useRef(false); const hasAutoOpenedRef = useRef(false);
const onRowClick = useCallback( const onRowClick = useCallback(
(sighting: SightingType) => { (sighting: SightingType) => {
if (!sighting) return; if (!sighting) return;
setSightingModalOpen(!isSightingModalOpen); setSightingModalOpen(true);
setSelectedSighting(sighting); setSelectedSighting(sighting);
}, },
[isSightingModalOpen, setSelectedSighting, setSightingModalOpen] [setSelectedSighting, setSightingModalOpen]
); );
const rows = useMemo( const rows = useMemo(
@@ -63,28 +66,34 @@ export default function SightingHistoryWidget({
[sightings] [sightings]
); );
// useEffect(() => { useEffect(() => {
// rows?.forEach((obj) => { rows?.forEach((obj) => {
// const isNPEDHit = obj?.metadata?.npedJSON?.status_code === 404; const isNPEDHitA = obj?.metadata?.npedJSON?.["NPED CATEGORY"] === "A";
const isNPEDHitB = obj?.metadata?.npedJSON?.["NPED CATEGORY"] === "B";
const isNPEDHitC = obj?.metadata?.npedJSON?.["NPED CATEGORY"] === "C";
// if (isNPEDHit) { if (isNPEDHitA || isNPEDHitB || isNPEDHitC) {
// dispatch({ dispatch({
// type: "ADD", type: "ADD",
// payload: obj, payload: obj,
// }); });
// } }
// }); });
// }, [rows, dispatch]); }, [rows, dispatch]);
useEffect(() => { useEffect(() => {
if (hasAutoOpenedRef.current) return; if (hasAutoOpenedRef.current) return;
const firstHot = rows?.find( const firstHot = rows?.find((r) => {
(r) => r?.metadata?.hotlistMatches?.Hotlist0 === true // const hotlistHit = r?.metadata?.hotlistMatches?.Hotlist0 === true;
); const isNPEDHitA = r?.metadata?.npedJSON?.["NPED CATEGORY"] === "A";
const isNPEDHitB = r?.metadata?.npedJSON?.["NPED CATEGORY"] === "B";
const isNPEDHitC = r?.metadata?.npedJSON?.["NPED CATEGORY"] === "C";
return isNPEDHitA || isNPEDHitB || isNPEDHitC;
});
if (firstHot) { if (firstHot) {
setSelectedSighting(firstHot); setSelectedSighting(firstHot);
setSightingModalOpen(true); setSightingModalOpen(true);
hasAutoOpenedRef.current = true; // prevent future auto-opens hasAutoOpenedRef.current = true;
} }
}, [rows, setSelectedSighting, setSightingModalOpen]); }, [rows, setSelectedSighting, setSightingModalOpen]);
@@ -98,8 +107,15 @@ export default function SightingHistoryWidget({
<div className="flex flex-col gap-3 "> <div className="flex flex-col gap-3 ">
{/* Rows */} {/* Rows */}
<div className="flex flex-col"> <div className="flex flex-col">
{rows?.map((obj, idx) => { {rows?.map((obj) => {
// const isNPEDHit = obj?.metadata?.npedJSON?.status_code === 404; const isNPEDHitA =
obj?.metadata?.npedJSON?.["NPED CATEGORY"] === "A";
const isNPEDHitB =
obj?.metadata?.npedJSON?.["NPED CATEGORY"] === "B";
const isNPEDHitC =
obj?.metadata?.npedJSON?.["NPED CATEGORY"] === "C";
const isNPEDHitD =
obj?.metadata?.npedJSON?.["NPED CATEGORY"] === "D";
const motionAway = (obj?.motion ?? "").toUpperCase() === "AWAY"; const motionAway = (obj?.motion ?? "").toUpperCase() === "AWAY";
const primaryIsColour = obj?.srcCam === 1; const primaryIsColour = obj?.srcCam === 1;
const secondaryMissing = (obj?.vrmSecondary ?? "") === ""; const secondaryMissing = (obj?.vrmSecondary ?? "") === "";
@@ -107,14 +123,16 @@ export default function SightingHistoryWidget({
obj?.metadata?.hotlistMatches?.Hotlist0 === true; obj?.metadata?.hotlistMatches?.Hotlist0 === true;
return ( return (
<div <div
key={idx} key={obj.ref}
className={`border border-neutral-700 rounded-md mb-2 p-2 cursor-pointer`} className={`border border-neutral-700 rounded-md mb-2 p-2 cursor-pointer `}
onClick={() => onRowClick(obj)} onClick={() => onRowClick(obj)}
> >
<InfoBar obj={obj} /> <InfoBar obj={obj} />
{/* Patch row */} {/* Patch row */}
<div <div
className={`flex items-center gap-3 mt-2 justify-between`} className={`flex items-center gap-3 mt-2 justify-between ${
isNPEDHitD ? "border border-amber-600" : ""
}`}
> >
<div <div
className={`border p-1 ${ className={`border p-1 ${
@@ -135,6 +153,28 @@ export default function SightingHistoryWidget({
className="h-20 object-contain rounded-md" className="h-20 object-contain rounded-md"
/> />
)} )}
{isNPEDHitA && (
<img
src={NPED_CAT_A}
alt="hotlistHit"
className="h-20 object-contain rounded-md"
/>
)}
{isNPEDHitB && (
<img
src={NPED_CAT_B}
alt="hotlistHit"
className="h-20 object-contain rounded-md"
/>
)}
{isNPEDHitC && (
<img
src={NPED_CAT_C}
alt="hotlistHit"
className="h-20 object-contain rounded-md"
/>
)}
<NumberPlate motion={motionAway} vrm={obj?.vrm} /> <NumberPlate motion={motionAway} vrm={obj?.vrm} />
</div> </div>
</div> </div>

View File

@@ -41,7 +41,6 @@ const SightingWidgetDetails = ({
</div> </div>
{advancedDetailsEnabled && ( {advancedDetailsEnabled && (
<> <>
{" "}
<div> <div>
Country:{" "} Country:{" "}
<span className="opacity-90"> <span className="opacity-90">

View File

@@ -97,7 +97,7 @@ export default function Header() {
</Link> </Link>
</div> </div>
<div className="flex flex-col md:flex-row items-center space-x-24 justify-items-center"> <div className="flex flex-col md:flex-row items-center space-x-24 justify-items-center">
<div className="flex flex-col leading-tight text-white tabular-nums text-xl text-right mx-auto md:mx-10"> <div className="flex flex-col leading-tight text-white tabular-nums text-xl text-right mx-auto md:mx-10 space-y-1 my-2">
<h2>Local: {localStr}</h2> <h2>Local: {localStr}</h2>
<h2>UTC: {utcStr}</h2> <h2>UTC: {utcStr}</h2>
</div> </div>

View File

@@ -97,6 +97,7 @@ export type HotlistMatches = {
export type NpedJSON = { export type NpedJSON = {
status_code: number; status_code: number;
reason_phrase: string; reason_phrase: string;
"NPED CATEGORY": "A" | "B" | "C" | "D";
}; };
export type NPEDUser = { export type NPEDUser = {