Files
Mav-Mobile-UI/src/components/SightingModal/SightingModal.tsx

232 lines
9.3 KiB
TypeScript
Raw Normal View History

import { faCheck, faTrash, faX } from "@fortawesome/free-solid-svg-icons";
2025-09-12 13:28:14 +01:00
import type { SightingType } from "../../types/types";
2025-09-12 08:21:52 +01:00
import NumberPlate from "../PlateStack/NumberPlate";
import ModalComponent from "../UI/ModalComponent";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
2025-09-18 15:14:47 +01:00
import { useAlertHitContext } from "../../context/AlertHitContext";
import { toast, Toaster } from "sonner";
import { useCameraBlackboard } from "../../hooks/useCameraBlackboard";
import HotListImg from "/Hotlist_Hit.svg";
2025-09-22 09:26:45 +01:00
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, getHotlistName, getNPEDCategory } from "../../utils/utils";
2025-09-12 08:21:52 +01:00
type SightingModalProps = {
isSightingModalOpen: boolean;
handleClose: () => void;
2025-09-12 13:28:14 +01:00
sighting: SightingType | null;
onDelete?: (deletedItem: SightingType | null) => void;
2025-09-12 08:21:52 +01:00
};
const SightingModal = ({ isSightingModalOpen, handleClose, sighting, onDelete }: SightingModalProps) => {
2025-09-18 15:14:47 +01:00
const { dispatch } = useAlertHitContext();
const { query, mutation } = useCameraBlackboard();
const hotlistName = getHotlistName(sighting?.metadata?.hotlistMatches);
2025-09-18 15:14:47 +01:00
const handleAcknowledgeButton = () => {
try {
if (!sighting) {
toast.error("Cannot add sighting to alert list");
handleClose();
return;
}
if (!query.data.alertHistory) {
mutation.mutate({
operation: "INSERT",
path: "alertHistory",
value: [sighting],
});
} else {
mutation.mutate({
operation: "APPEND",
path: "alertHistory",
value: sighting,
});
toast.success("Sighting Successfully added to alert list");
}
dispatch({ type: "ADD", payload: sighting });
handleClose();
} catch (error) {
2025-10-07 15:58:16 +01:00
console.error(error);
toast.error("Failed to add sighting to alert list");
2025-09-18 15:14:47 +01:00
handleClose();
}
};
const handleDeleteClick = (deletedItem: SightingType | null) => {
if (!onDelete) return;
onDelete(deletedItem);
handleClose();
toast.success("Sighting removed from alert list");
};
2025-09-18 15:14:47 +01:00
const motionAway = (sighting?.motion ?? "").toUpperCase() === "AWAY";
const isHotListHit = checkIsHotListHit(sighting);
2025-10-14 08:50:06 +01:00
const cat = getNPEDCategory(sighting);
const isNPEDHitA = cat === "A";
const isNPEDHitB = cat === "B";
const isNPEDHitC = cat === "C";
2025-09-18 15:14:47 +01:00
return (
<>
<ModalComponent isModalOpen={isSightingModalOpen} close={handleClose}>
<div className="max-w-screen-lg mx-auto py-4 px-2">
<div className="border-b border-gray-600 mb-4">
<h2 className="text-lg md:text-xl font-semibold">Sighting Details</h2>
2025-09-18 15:14:47 +01:00
</div>
<div className="mt-3 flex-col-reverse gap-3 md:flex-row md:justify-center flex md:hidden">
{onDelete ? (
<button
className="inline-flex items-center justify-center gap-2 rounded-lg px-5 py-3 bg-red-600 text-white hover:bg-red-700 w-full md:w-full"
onClick={() => handleDeleteClick(sighting)}
>
<FontAwesomeIcon icon={faTrash} />
Delete
</button>
) : (
<button
className="inline-flex items-center justify-center gap-2 rounded-lg px-5 py-3 bg-red-600 text-white hover:bg-red-700 w-full md:w-full"
onClick={handleClose}
>
<FontAwesomeIcon icon={faX} />
Deny
</button>
)}
{onDelete ? (
<button
className="inline-flex items-center justify-center gap-2 rounded-lg px-5 py-3 bg-blue-600 text-white hover:bg-blue-700 w-full md:w-full"
onClick={handleClose}
>
<FontAwesomeIcon icon={faX} />
Close
</button>
) : (
<button
className="inline-flex items-center justify-center gap-2 rounded-lg px-5 py-3 bg-green-600 text-white hover:bg-green-700 w-full md:w-full"
onClick={handleAcknowledgeButton}
>
<FontAwesomeIcon icon={faCheck} />
2025-10-17 10:17:01 +01:00
Acknowledge
</button>
)}
</div>
<div className="flex flex-col md:flex-row gap-3 items-center mb-2 justify-between">
<div className="flex flex-col md:flex-row gap-3 items-center">
<NumberPlate vrm={sighting?.vrm} motion={motionAway} />
<img src={sighting?.plateUrlColour} alt="plate patch" className="h-16 object-contain rounded-md" />
{hotlistName && (
<div>
<p className="text-gray-300">Hotlist</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>
{isHotListHit && <img src={HotListImg} alt="hotlistHit" 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" />}
2025-09-18 15:14:47 +01:00
</div>
<div className="flex flex-col lg:flex-row items-center gap-3">
<img
src={sighting?.overviewUrl}
alt="overview patch"
className="w-full h-56 sm:h-72 md:h-96 rounded-lg object-cover border border-gray-700"
/>
<aside className="w-full lg:w-80 bg-gray-800/70 text-white rounded-xl py-4 px-2 border h-[70%] border-gray-700">
<h3 className="text-base md:text-lg font-semibold pb-2 border-b border-gray-700">Vehicle Info</h3>
2025-09-18 15:14:47 +01:00
<dl className="mt-3 gap-x-4 gap-y-2 text-sm">
<div>
<dt className="text-gray-300">VRM</dt>
<dd className="font-medium text-2xl break-all">{sighting?.vrm ?? "-"}</dd>
2025-09-18 15:14:47 +01:00
</div>
2025-09-18 15:14:47 +01:00
<div>
<dt className="text-gray-300">Motion</dt>
<dd className="font-medium text-2xl">{sighting?.motion ?? "-"}</dd>
2025-09-18 15:14:47 +01:00
</div>
<div>
<dt className="text-gray-300">Seen Count</dt>
<dd className="font-medium text-2xl">{sighting?.seenCount ?? "-"}</dd>
2025-09-18 15:14:47 +01:00
</div>
2025-10-17 10:17:01 +01:00
{sighting?.make && (
<div>
<dt className="text-gray-300">Make</dt>
<dd className="font-medium text-2xl">{sighting?.make ?? "-"}</dd>
</div>
)}
{sighting?.model ||
(!sighting?.model.trim() && (
<div>
<dt className="text-gray-300">Model</dt>
<dd className="font-medium text-2xl">{sighting?.model ?? "-"}</dd>
</div>
))}
{sighting?.color && (
<div className="sm:col-span-2">
<dt className="text-gray-300">Colour</dt>
<dd className="font-medium text-2xl">{sighting?.color ?? "-"}</dd>
</div>
)}
2025-09-18 15:14:47 +01:00
<div>
<dt className="text-gray-300">Time</dt>
<dd className="font-medium text-xl">{sighting?.timeStamp ?? "-"}</dd>
2025-09-18 15:14:47 +01:00
</div>
</dl>
</aside>
</div>
<div className="mt-3 flex-col-reverse gap-3 md:flex-row md:justify-center hidden md:flex">
{onDelete ? (
<button
className="inline-flex items-center justify-center gap-2 rounded-lg px-5 py-3 bg-blue-600 text-white hover:bg-blue-700 w-full md:w-full"
onClick={handleClose}
>
<FontAwesomeIcon icon={faX} />
Close
</button>
) : (
<button
className="inline-flex items-center justify-center gap-2 rounded-lg px-5 py-3 bg-green-600 text-white hover:bg-green-700 w-full md:w-full"
onClick={handleAcknowledgeButton}
>
<FontAwesomeIcon icon={faCheck} />
2025-10-17 10:17:01 +01:00
Acknowledge
</button>
)}
{onDelete ? (
<button
className="inline-flex items-center justify-center gap-2 rounded-lg px-5 py-3 bg-red-600 text-white hover:bg-red-700 w-full md:w-full"
onClick={() => handleDeleteClick(sighting)}
>
<FontAwesomeIcon icon={faTrash} />
Delete
</button>
) : (
<button
className="inline-flex items-center justify-center gap-2 rounded-lg px-5 py-3 bg-red-600 text-white hover:bg-red-700 w-full md:w-full"
onClick={handleClose}
>
<FontAwesomeIcon icon={faX} />
Deny
</button>
)}
2025-09-18 15:14:47 +01:00
</div>
2025-09-12 13:28:14 +01:00
</div>
2025-09-18 15:14:47 +01:00
</ModalComponent>
<Toaster />
</>
2025-09-12 08:21:52 +01:00
);
};
export default SightingModal;