bugfix/minorFixes-NPED #15
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 { uploadSettings } = useSystemConfig();
|
||||
|
||||
const initialValue = {
|
||||
file: null,
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useEffect, useReducer, type ReactNode } from "react";
|
||||
import { IntegrationsContext } from "../IntegrationsContext";
|
||||
import { useCameraBlackboard } from "../../hooks/useCameraBlackboard";
|
||||
import { initialState, reducer } from "../reducers/IntegrationsContextReducer";
|
||||
import { useHotlistData } from "../../hooks/useHotListData";
|
||||
|
||||
type IntegrationsProviderType = {
|
||||
children: ReactNode;
|
||||
@@ -10,6 +11,7 @@ type IntegrationsProviderType = {
|
||||
export const IntegrationsProvider = ({ children }: IntegrationsProviderType) => {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
const { mutation } = useCameraBlackboard();
|
||||
const { query } = useHotlistData();
|
||||
|
||||
useEffect(() => {
|
||||
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 (
|
||||
<IntegrationsContext.Provider
|
||||
value={{
|
||||
|
||||
@@ -12,6 +12,7 @@ export const initialState = {
|
||||
catC: true,
|
||||
catD: false,
|
||||
},
|
||||
hotlistFiles: [],
|
||||
};
|
||||
|
||||
export function reducer(state: NPEDSTATE, action: NPEDACTION) {
|
||||
@@ -56,6 +57,17 @@ export function reducer(state: NPEDSTATE, action: NPEDACTION) {
|
||||
...state,
|
||||
iscatEnabled: action.payload,
|
||||
};
|
||||
|
||||
case "SETHOTLISTS":
|
||||
return {
|
||||
...state,
|
||||
hotlistFiles: action.payload,
|
||||
};
|
||||
case "DELETEHOTLIST":
|
||||
return {
|
||||
...state,
|
||||
hotlistFiles: state.hotlistFiles.filter((hotlist) => hotlist.filename !== action.payload),
|
||||
};
|
||||
default:
|
||||
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 SoundUploadCard from "../components/SettingForms/Sound/SoundUploadCard";
|
||||
import NPEDCategoryPopup from "../components/PopupSettings/NPEDCategoryPopup";
|
||||
import HotlistList from "../components/HotlistList/HotlistList";
|
||||
|
||||
const SystemSettings = () => {
|
||||
useNPEDAuth();
|
||||
@@ -40,6 +41,7 @@ const SystemSettings = () => {
|
||||
<NPEDCard />
|
||||
<NPEDHotlistCard />
|
||||
<NPEDCategoryPopup />
|
||||
<HotlistList />
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
|
||||
@@ -417,6 +417,12 @@ export type QueuedHit = {
|
||||
|
||||
export type DedupedSightings = ReducedSightingType[];
|
||||
|
||||
export type HotlistFile = {
|
||||
fileSizeBytes: number;
|
||||
filename: string;
|
||||
rowCount: number;
|
||||
};
|
||||
|
||||
export type NPEDSTATE = {
|
||||
sessionStarted: boolean;
|
||||
sessionList: ReducedSightingType[];
|
||||
@@ -424,6 +430,7 @@ export type NPEDSTATE = {
|
||||
savedSightings: DedupedSightings;
|
||||
npedUser: NPEDUser;
|
||||
iscatEnabled: CategoryPopups;
|
||||
hotlistFiles: HotlistFile[];
|
||||
};
|
||||
|
||||
export type NPEDACTION = {
|
||||
|
||||
Reference in New Issue
Block a user