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

311 lines
13 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 NPED_CAT_D from "/NPED_Cat_D.svg";
import { checkIsHotListHit, getHotlistName, getNPEDCategory, getNPEDReason } 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-12-15 14:46:48 +00:00
console.log(sighting);
2025-09-18 15:14:47 +01:00
const { dispatch } = useAlertHitContext();
const { query, mutation } = useCameraBlackboard();
const hotlistNames = getHotlistName(sighting?.metadata?.hotlistMatches);
2025-11-17 10:19:44 +00:00
const handleAcknowledgeButton = async () => {
try {
if (!sighting) {
toast.error("Cannot add sighting to alert list");
handleClose();
return;
}
if (!query.data.alertHistory) {
2025-11-17 10:19:44 +00:00
await mutation.mutateAsync({
operation: "INSERT",
path: "alertHistory",
value: [sighting],
});
2025-11-17 10:19:44 +00:00
await mutation.mutateAsync({
operation: "SAVE",
path: "",
value: null,
});
} else {
2025-11-17 10:19:44 +00:00
await mutation.mutateAsync({
operation: "APPEND",
path: "alertHistory",
value: sighting,
});
2025-11-17 10:19:44 +00:00
await mutation.mutateAsync({
operation: "SAVE",
path: "",
value: null,
});
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";
const isNPEDHitD = cat === "D";
const reason = getNPEDReason(sighting);
const insuranceStatus = reason ? reason["INSURANCE STATUS"] : null;
const motStatus = reason ? reason["MOT STATUS"] : null;
const taxStatus = reason ? reason["TAX STATUS"] : null;
2025-09-18 15:14:47 +01:00
return (
<>
<ModalComponent isModalOpen={isSightingModalOpen} close={handleClose}>
2025-11-19 15:03:20 +00:00
<div className="mx-auto py-4 px-6 overflow-y-scroll">
2025-09-18 15:14:47 +01:00
<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" />
</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" />}
{isNPEDHitD && <img src={NPED_CAT_D} alt="hotlistHit" className="h-20 object-contain rounded-md" />}
2025-09-18 15:14:47 +01:00
</div>
2025-11-19 15:03:20 +00:00
{hotlistNames && hotlistNames.length > 0 && (
<div className="flex flex-col border-b border-gray-600 mb-4">
<p className="text-gray-300">Hotlists</p>
2025-12-15 14:46:48 +00:00
<div className="grid grid-cols-1 md:grid-cols-2 md:gap-x-[20%] w-[50%]">
{hotlistNames.map((hotlistName, index) => (
2025-12-15 14:46:48 +00:00
<span className="items-center px-2.5 py-0.5 rounded-sm me-2 bg-amber-500 w-50 m-2" key={index}>
<p className="font-medium text-2xl break-all text-amber-800">
{hotlistName ? hotlistName?.replace(/\.csv$/i, "") : "-"}
</p>
2025-12-15 14:46:48 +00:00
</span>
))}
</div>
</div>
)}
2025-11-19 15:03:20 +00:00
<div className="flex flex-col lg:flex-row items-center gap-3 ">
<img
src={sighting?.overviewUrl}
alt="overview patch"
2025-11-19 15:03:20 +00:00
className="w-full h-56 sm:h-72 md:h-72 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>
<dd className="font-medium text-2xl break-all font-mono tracking-wide">{sighting?.vrm ?? "-"}</dd>
2025-09-18 15:14:47 +01:00
</div>
{isNPEDHitA || isNPEDHitB || isNPEDHitC || isNPEDHitD ? (
<>
<div className="">
<dt className="text-gray-300 text-xl">Insurance</dt>
<dd className="font-medium text-2xl flex">
{insuranceStatus ? (
<span
className={`text-green-600 bg-green-300 w-[30%] px-2 rounded-lg border border-green-900`}
>
YES
</span>
) : (
<span className={`text-red-300 bg-red-600 w-[30%] px-2 rounded-lg border border-red-900`}>
NO
</span>
)}
</dd>
</div>
<div>
<dt className="text-gray-300 text-3xl">MOT</dt>
<dd className="font-medium text-2xl">
{motStatus ? (
<span
className={`text-green-700 bg-green-400 w-[30%] px-2 rounded-lg border border-green-900`}
>
VALID
</span>
) : (
<span className={`text-red-600 bg-red-300 w-[30%] px-2 rounded-lg border border-red-900`}>
EXPIRED
</span>
)}
</dd>
</div>
<div>
<dt className="text-gray-300 text-3xl">Tax</dt>
<dd className="font-medium text-2xl">
{taxStatus ? (
<span
className={`text-green-700 bg-green-400 w-[30%] px-2 rounded-lg border border-green-900`}
>
VALID
</span>
) : (
<span className={`text-red-600 bg-red-300 w-[30%] px-2 rounded-lg border border-red-900`}>
EXPIRED
</span>
)}
</dd>
</div>
</>
) : (
<>
<div>
<dt className="text-gray-300">Motion</dt>
<dd className="font-medium text-2xl ">{sighting?.motion ?? "-"}</dd>
</div>
<div>
<dt className="text-gray-300">Seen Count</dt>
<dd className="font-medium text-2xl">{sighting?.seenCount ?? "-"}</dd>
</div>
{sighting?.make && sighting.make.trim() && sighting.make.toLowerCase() !== "disabled" && (
<div>
<dt className="text-gray-300">Make</dt>
<dd className="font-medium text-2xl">{sighting.make}</dd>
</div>
)}
{sighting?.model && sighting.model.trim() && sighting.model.toLowerCase() !== "disabled" && (
<div>
<dt className="text-gray-300">Model</dt>
<dd className="font-medium text-2xl">{sighting.model}</dd>
</div>
)}
{sighting?.color && sighting.color.trim() && sighting.color.toLowerCase() !== "disabled" && (
<div className="sm:col-span-2">
<dt className="text-gray-300">Colour</dt>
<dd className="font-medium text-2xl">{sighting.color}</dd>
</div>
)}
<div>
<dt className="text-gray-300">Time</dt>
<dd className="font-medium text-xl">{sighting?.timeStamp ?? "-"}</dd>
</div>
</>
)}
2025-09-18 15:14:47 +01:00
</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;