- added hotlists being displayed
- functionality to delete hotlist
This commit is contained in:
67
src/components/HotlistList/HotlistList.tsx
Normal file
67
src/components/HotlistList/HotlistList.tsx
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import { useHotlistData } from "../../hooks/useHotListData";
|
||||||
|
import { useIntegrationsContext } from "../../context/IntegrationsContext";
|
||||||
|
import Card from "../UI/Card";
|
||||||
|
import CardHeader from "../UI/CardHeader";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
const HotlistList = () => {
|
||||||
|
const { state, dispatch } = useIntegrationsContext();
|
||||||
|
const { mutation } = useHotlistData();
|
||||||
|
|
||||||
|
const hotlists = state?.hotlistFiles;
|
||||||
|
|
||||||
|
const handleDeleteClick = async (filename: string) => {
|
||||||
|
await mutation.mutateAsync(filename);
|
||||||
|
dispatch({ type: "DELETEHOTLIST", payload: filename });
|
||||||
|
toast.success(`${filename} successfully deleted`);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="p-4">
|
||||||
|
<CardHeader title="Uploaded hotlists" />
|
||||||
|
{hotlists.length > 0 ? (
|
||||||
|
<ul className="px-2">
|
||||||
|
{hotlists?.map((hotlist) => (
|
||||||
|
<li
|
||||||
|
key={hotlist.filename}
|
||||||
|
className="flex flex-row justify-between my-3 items-center border-b border-gray-700"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<p className="text-xl">{hotlist.filename}</p>
|
||||||
|
<div className="flex flex-row gap-3">
|
||||||
|
<div>
|
||||||
|
<p className="text-gray-400">
|
||||||
|
Number of records: <span>{hotlist.rowCount}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-gray-400">
|
||||||
|
File Size (bytes): <span>{hotlist.fileSizeBytes}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button onClick={() => handleDeleteClick(hotlist.filename)}>
|
||||||
|
<FontAwesomeIcon icon={faTrash} />
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : (
|
||||||
|
<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 Uploaded Hotlists
|
||||||
|
</div>
|
||||||
|
<p className="max-w-md text-slate-300">
|
||||||
|
<span className="text-emerald-400">Hotlists</span> will appear here once there are uploaded.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HotlistList;
|
||||||
@@ -5,6 +5,7 @@ import { CAM_BASE } from "../../../utils/config";
|
|||||||
|
|
||||||
const NPEDHotlist = () => {
|
const NPEDHotlist = () => {
|
||||||
const { uploadSettings } = useSystemConfig();
|
const { uploadSettings } = useSystemConfig();
|
||||||
|
|
||||||
const initialValue = {
|
const initialValue = {
|
||||||
file: null,
|
file: null,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useEffect, useReducer, type ReactNode } from "react";
|
|||||||
import { IntegrationsContext } from "../IntegrationsContext";
|
import { IntegrationsContext } from "../IntegrationsContext";
|
||||||
import { useCameraBlackboard } from "../../hooks/useCameraBlackboard";
|
import { useCameraBlackboard } from "../../hooks/useCameraBlackboard";
|
||||||
import { initialState, reducer } from "../reducers/IntegrationsContextReducer";
|
import { initialState, reducer } from "../reducers/IntegrationsContextReducer";
|
||||||
|
import { useHotlistData } from "../../hooks/useHotListData";
|
||||||
|
|
||||||
type IntegrationsProviderType = {
|
type IntegrationsProviderType = {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@@ -10,6 +11,7 @@ type IntegrationsProviderType = {
|
|||||||
export const IntegrationsProvider = ({ children }: IntegrationsProviderType) => {
|
export const IntegrationsProvider = ({ children }: IntegrationsProviderType) => {
|
||||||
const [state, dispatch] = useReducer(reducer, initialState);
|
const [state, dispatch] = useReducer(reducer, initialState);
|
||||||
const { mutation } = useCameraBlackboard();
|
const { mutation } = useCameraBlackboard();
|
||||||
|
const { query } = useHotlistData();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let isMounted = true;
|
let isMounted = true;
|
||||||
@@ -50,6 +52,13 @@ export const IntegrationsProvider = ({ children }: IntegrationsProviderType) =>
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchHotlistData = async () => {
|
||||||
|
dispatch({ type: "SETHOTLISTS", payload: query?.data?.hotlists });
|
||||||
|
};
|
||||||
|
fetchHotlistData();
|
||||||
|
}, [query?.data]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IntegrationsContext.Provider
|
<IntegrationsContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export const initialState = {
|
|||||||
catC: true,
|
catC: true,
|
||||||
catD: false,
|
catD: false,
|
||||||
},
|
},
|
||||||
|
hotlistFiles: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
export function reducer(state: NPEDSTATE, action: NPEDACTION) {
|
export function reducer(state: NPEDSTATE, action: NPEDACTION) {
|
||||||
@@ -56,6 +57,17 @@ export function reducer(state: NPEDSTATE, action: NPEDACTION) {
|
|||||||
...state,
|
...state,
|
||||||
iscatEnabled: action.payload,
|
iscatEnabled: action.payload,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
case "SETHOTLISTS":
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
hotlistFiles: action.payload,
|
||||||
|
};
|
||||||
|
case "DELETEHOTLIST":
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
hotlistFiles: state.hotlistFiles.filter((hotlist) => hotlist.filename !== action.payload),
|
||||||
|
};
|
||||||
default:
|
default:
|
||||||
return { ...state };
|
return { ...state };
|
||||||
}
|
}
|
||||||
|
|||||||
28
src/hooks/useHotListData.ts
Normal file
28
src/hooks/useHotListData.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||||
|
import { CAM_BASE } from "../utils/config";
|
||||||
|
|
||||||
|
const fetchHotlists = async () => {
|
||||||
|
const response = await fetch(`${CAM_BASE}/Hotlist-csv-metadata`);
|
||||||
|
if (!response.ok) throw new Error("Cannot reach hotlist endpoint");
|
||||||
|
return response.json();
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteHotlist = async (filename: string) => {
|
||||||
|
const response = await fetch(`${CAM_BASE}/Hotlist-csv-delete?filename=${filename}`);
|
||||||
|
if (!response.ok) throw new Error(`Cannot delte hotlist: ${filename}`);
|
||||||
|
return response.json();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useHotlistData = () => {
|
||||||
|
const query = useQuery({
|
||||||
|
queryKey: ["fetchHotlists"],
|
||||||
|
queryFn: fetchHotlists,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: ["deleteHotlist"],
|
||||||
|
mutationFn: (filename: string) => deleteHotlist(filename),
|
||||||
|
});
|
||||||
|
|
||||||
|
return { query, mutation };
|
||||||
|
};
|
||||||
@@ -11,6 +11,7 @@ import { useNPEDAuth } from "../hooks/useNPEDAuth";
|
|||||||
import SoundSettingsCard from "../components/SettingForms/Sound/SoundSettingsCard";
|
import SoundSettingsCard from "../components/SettingForms/Sound/SoundSettingsCard";
|
||||||
import SoundUploadCard from "../components/SettingForms/Sound/SoundUploadCard";
|
import SoundUploadCard from "../components/SettingForms/Sound/SoundUploadCard";
|
||||||
import NPEDCategoryPopup from "../components/PopupSettings/NPEDCategoryPopup";
|
import NPEDCategoryPopup from "../components/PopupSettings/NPEDCategoryPopup";
|
||||||
|
import HotlistList from "../components/HotlistList/HotlistList";
|
||||||
|
|
||||||
const SystemSettings = () => {
|
const SystemSettings = () => {
|
||||||
useNPEDAuth();
|
useNPEDAuth();
|
||||||
@@ -40,6 +41,7 @@ const SystemSettings = () => {
|
|||||||
<NPEDCard />
|
<NPEDCard />
|
||||||
<NPEDHotlistCard />
|
<NPEDHotlistCard />
|
||||||
<NPEDCategoryPopup />
|
<NPEDCategoryPopup />
|
||||||
|
<HotlistList />
|
||||||
</div>
|
</div>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
|
|||||||
@@ -417,6 +417,12 @@ export type QueuedHit = {
|
|||||||
|
|
||||||
export type DedupedSightings = ReducedSightingType[];
|
export type DedupedSightings = ReducedSightingType[];
|
||||||
|
|
||||||
|
export type HotlistFile = {
|
||||||
|
fileSizeBytes: number;
|
||||||
|
filename: string;
|
||||||
|
rowCount: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type NPEDSTATE = {
|
export type NPEDSTATE = {
|
||||||
sessionStarted: boolean;
|
sessionStarted: boolean;
|
||||||
sessionList: ReducedSightingType[];
|
sessionList: ReducedSightingType[];
|
||||||
@@ -424,6 +430,7 @@ export type NPEDSTATE = {
|
|||||||
savedSightings: DedupedSightings;
|
savedSightings: DedupedSightings;
|
||||||
npedUser: NPEDUser;
|
npedUser: NPEDUser;
|
||||||
iscatEnabled: CategoryPopups;
|
iscatEnabled: CategoryPopups;
|
||||||
|
hotlistFiles: HotlistFile[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NPEDACTION = {
|
export type NPEDACTION = {
|
||||||
|
|||||||
Reference in New Issue
Block a user