2024-02-25 20:04:44 +01:00
|
|
|
import useApiFilter from "@/hooks/use-api-filter";
|
2024-02-23 01:03:34 +01:00
|
|
|
import useOverlayState from "@/hooks/use-overlay-state";
|
2024-02-27 14:37:39 +01:00
|
|
|
import { ReviewFilter, ReviewSegment, ReviewSeverity } from "@/types/review";
|
2024-02-21 21:07:32 +01:00
|
|
|
import DesktopEventView from "@/views/events/DesktopEventView";
|
2024-02-23 01:03:34 +01:00
|
|
|
import DesktopRecordingView from "@/views/events/DesktopRecordingView";
|
2024-02-21 21:07:32 +01:00
|
|
|
import MobileEventView from "@/views/events/MobileEventView";
|
2024-02-23 01:03:34 +01:00
|
|
|
import axios from "axios";
|
2024-02-27 17:05:28 +01:00
|
|
|
import { useCallback, useMemo, useState } from "react";
|
2024-02-23 01:03:34 +01:00
|
|
|
import { isMobile } from "react-device-detect";
|
|
|
|
import useSWR from "swr";
|
|
|
|
import useSWRInfinite from "swr/infinite";
|
|
|
|
|
|
|
|
const API_LIMIT = 250;
|
2024-02-21 21:07:32 +01:00
|
|
|
|
|
|
|
export default function Events() {
|
2024-02-23 01:03:34 +01:00
|
|
|
// recordings viewer
|
2024-02-27 14:37:39 +01:00
|
|
|
|
|
|
|
const [severity, setSeverity] = useState<ReviewSeverity>("alert");
|
2024-02-23 01:03:34 +01:00
|
|
|
const [selectedReviewId, setSelectedReviewId] = useOverlayState("review");
|
|
|
|
|
2024-02-25 20:04:44 +01:00
|
|
|
// review filter
|
|
|
|
|
|
|
|
const [reviewFilter, setReviewFilter, reviewSearchParams] =
|
|
|
|
useApiFilter<ReviewFilter>();
|
|
|
|
|
|
|
|
const onUpdateFilter = useCallback((newFilter: ReviewFilter) => {
|
2024-02-25 23:36:18 +01:00
|
|
|
setSize(1);
|
|
|
|
setReviewFilter(newFilter);
|
|
|
|
}, []);
|
2024-02-25 20:04:44 +01:00
|
|
|
|
2024-02-23 01:03:34 +01:00
|
|
|
// review paging
|
|
|
|
|
2024-02-26 21:47:44 +01:00
|
|
|
const last24Hours = useMemo(() => {
|
2024-02-23 01:03:34 +01:00
|
|
|
return { before: Date.now() / 1000, after: getHoursAgo(24) };
|
|
|
|
}, []);
|
2024-02-26 21:47:44 +01:00
|
|
|
const selectedTimeRange = useMemo(() => {
|
|
|
|
if (reviewSearchParams["after"] == undefined) {
|
|
|
|
return last24Hours;
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
before: Math.floor(reviewSearchParams["before"]),
|
|
|
|
after: Math.floor(reviewSearchParams["after"]),
|
|
|
|
};
|
|
|
|
}, [reviewSearchParams]);
|
2024-02-23 01:03:34 +01:00
|
|
|
|
|
|
|
const reviewSegmentFetcher = useCallback((key: any) => {
|
|
|
|
const [path, params] = Array.isArray(key) ? key : [key, undefined];
|
|
|
|
return axios.get(path, { params }).then((res) => res.data);
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
const getKey = useCallback(
|
|
|
|
(index: number, prevData: ReviewSegment[]) => {
|
|
|
|
if (index > 0) {
|
|
|
|
const lastDate = prevData[prevData.length - 1].start_time;
|
2024-02-25 20:04:44 +01:00
|
|
|
reviewSearchParams;
|
|
|
|
const pagedParams = {
|
|
|
|
cameras: reviewSearchParams["cameras"],
|
|
|
|
labels: reviewSearchParams["labels"],
|
2024-02-25 23:36:18 +01:00
|
|
|
reviewed: reviewSearchParams["showReviewed"],
|
2024-02-25 20:04:44 +01:00
|
|
|
before: lastDate,
|
2024-02-26 21:47:44 +01:00
|
|
|
after: reviewSearchParams["after"] || last24Hours.after,
|
2024-02-25 20:04:44 +01:00
|
|
|
limit: API_LIMIT,
|
|
|
|
};
|
2024-02-23 01:03:34 +01:00
|
|
|
return ["review", pagedParams];
|
|
|
|
}
|
|
|
|
|
2024-02-25 20:04:44 +01:00
|
|
|
const params = {
|
|
|
|
cameras: reviewSearchParams["cameras"],
|
|
|
|
labels: reviewSearchParams["labels"],
|
2024-02-25 23:36:18 +01:00
|
|
|
reviewed: reviewSearchParams["showReviewed"],
|
2024-02-25 20:04:44 +01:00
|
|
|
limit: API_LIMIT,
|
2024-02-26 21:47:44 +01:00
|
|
|
before: reviewSearchParams["before"] || last24Hours.before,
|
|
|
|
after: reviewSearchParams["after"] || last24Hours.after,
|
2024-02-25 20:04:44 +01:00
|
|
|
};
|
2024-02-23 01:03:34 +01:00
|
|
|
return ["review", params];
|
|
|
|
},
|
|
|
|
[reviewSearchParams]
|
|
|
|
);
|
|
|
|
|
|
|
|
const {
|
|
|
|
data: reviewPages,
|
|
|
|
mutate: updateSegments,
|
|
|
|
size,
|
|
|
|
setSize,
|
|
|
|
isValidating,
|
|
|
|
} = useSWRInfinite<ReviewSegment[]>(getKey, reviewSegmentFetcher, {
|
|
|
|
revalidateOnFocus: false,
|
|
|
|
persistSize: true,
|
|
|
|
});
|
|
|
|
|
|
|
|
const isDone = useMemo(
|
|
|
|
() => (reviewPages?.at(-1)?.length ?? 0) < API_LIMIT,
|
|
|
|
[reviewPages]
|
|
|
|
);
|
|
|
|
|
2024-02-26 23:08:55 +01:00
|
|
|
const onLoadNextPage = useCallback(() => {
|
|
|
|
setSize(size + 1);
|
|
|
|
}, [size]);
|
|
|
|
|
2024-02-27 14:37:39 +01:00
|
|
|
const reloadData = useCallback(() => {
|
|
|
|
setSize(1);
|
|
|
|
updateSegments();
|
2024-02-27 17:05:28 +01:00
|
|
|
}, []);
|
2024-02-27 14:37:39 +01:00
|
|
|
|
2024-02-23 01:03:34 +01:00
|
|
|
// preview videos
|
|
|
|
|
|
|
|
const previewTimes = useMemo(() => {
|
|
|
|
if (
|
|
|
|
!reviewPages ||
|
|
|
|
reviewPages.length == 0 ||
|
|
|
|
reviewPages.at(-1)!!.length == 0
|
|
|
|
) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
const startDate = new Date();
|
|
|
|
startDate.setMinutes(0, 0, 0);
|
|
|
|
|
|
|
|
const endDate = new Date(reviewPages.at(-1)!!.at(-1)!!.end_time);
|
|
|
|
endDate.setHours(0, 0, 0, 0);
|
|
|
|
return {
|
|
|
|
start: startDate.getTime() / 1000,
|
|
|
|
end: endDate.getTime() / 1000,
|
|
|
|
};
|
|
|
|
}, [reviewPages]);
|
|
|
|
const { data: allPreviews } = useSWR<Preview[]>(
|
|
|
|
previewTimes
|
|
|
|
? `preview/all/start/${previewTimes.start}/end/${previewTimes.end}`
|
|
|
|
: null,
|
|
|
|
{ revalidateOnFocus: false }
|
|
|
|
);
|
|
|
|
|
|
|
|
// review status
|
|
|
|
|
|
|
|
const markItemAsReviewed = useCallback(
|
|
|
|
async (reviewId: string) => {
|
|
|
|
const resp = await axios.post(`review/${reviewId}/viewed`);
|
|
|
|
|
|
|
|
if (resp.status == 200) {
|
|
|
|
updateSegments(
|
|
|
|
(data: ReviewSegment[][] | undefined) => {
|
|
|
|
if (!data) {
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
const newData: ReviewSegment[][] = [];
|
|
|
|
|
|
|
|
data.forEach((page) => {
|
|
|
|
const reviewIndex = page.findIndex((item) => item.id == reviewId);
|
|
|
|
|
|
|
|
if (reviewIndex == -1) {
|
|
|
|
newData.push([...page]);
|
|
|
|
} else {
|
|
|
|
newData.push([
|
|
|
|
...page.slice(0, reviewIndex),
|
|
|
|
{ ...page[reviewIndex], has_been_reviewed: true },
|
|
|
|
...page.slice(reviewIndex + 1),
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return newData;
|
|
|
|
},
|
2024-02-25 20:04:44 +01:00
|
|
|
{ revalidate: false, populateCache: true }
|
2024-02-23 01:03:34 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
[updateSegments]
|
|
|
|
);
|
|
|
|
|
|
|
|
// selected items
|
|
|
|
|
|
|
|
const selectedData = useMemo(() => {
|
|
|
|
if (!selectedReviewId) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!reviewPages) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
const allReviews = reviewPages.flat();
|
|
|
|
const selectedReview = allReviews.find(
|
|
|
|
(item) => item.id == selectedReviewId
|
|
|
|
);
|
|
|
|
|
|
|
|
if (!selectedReview) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
selected: selectedReview,
|
|
|
|
cameraSegments: allReviews.filter(
|
|
|
|
(seg) => seg.camera == selectedReview.camera
|
|
|
|
),
|
|
|
|
cameraPreviews: allPreviews?.filter(
|
|
|
|
(seg) => seg.camera == selectedReview.camera
|
|
|
|
),
|
|
|
|
};
|
|
|
|
}, [selectedReviewId, reviewPages]);
|
|
|
|
|
|
|
|
if (selectedData) {
|
|
|
|
return (
|
|
|
|
<DesktopRecordingView
|
|
|
|
reviewItems={selectedData.cameraSegments}
|
|
|
|
selectedReview={selectedData.selected}
|
|
|
|
relevantPreviews={selectedData.cameraPreviews}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
if (isMobile) {
|
|
|
|
return (
|
|
|
|
<MobileEventView
|
|
|
|
reviewPages={reviewPages}
|
|
|
|
relevantPreviews={allPreviews}
|
|
|
|
reachedEnd={isDone}
|
|
|
|
isValidating={isValidating}
|
2024-02-27 14:37:39 +01:00
|
|
|
severity={severity}
|
|
|
|
setSeverity={setSeverity}
|
2024-02-26 23:08:55 +01:00
|
|
|
loadNextPage={onLoadNextPage}
|
2024-02-23 01:03:34 +01:00
|
|
|
markItemAsReviewed={markItemAsReviewed}
|
2024-02-27 14:37:39 +01:00
|
|
|
pullLatestData={reloadData}
|
2024-02-23 01:03:34 +01:00
|
|
|
/>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<DesktopEventView
|
|
|
|
reviewPages={reviewPages}
|
|
|
|
relevantPreviews={allPreviews}
|
2024-02-26 21:47:44 +01:00
|
|
|
timeRange={selectedTimeRange}
|
2024-02-23 01:03:34 +01:00
|
|
|
reachedEnd={isDone}
|
|
|
|
isValidating={isValidating}
|
2024-02-25 20:04:44 +01:00
|
|
|
filter={reviewFilter}
|
2024-02-27 14:37:39 +01:00
|
|
|
severity={severity}
|
|
|
|
setSeverity={setSeverity}
|
2024-02-26 23:08:55 +01:00
|
|
|
loadNextPage={onLoadNextPage}
|
2024-02-23 01:03:34 +01:00
|
|
|
markItemAsReviewed={markItemAsReviewed}
|
|
|
|
onSelectReview={setSelectedReviewId}
|
2024-02-27 14:37:39 +01:00
|
|
|
pullLatestData={reloadData}
|
2024-02-25 20:04:44 +01:00
|
|
|
updateFilter={onUpdateFilter}
|
2024-02-23 01:03:34 +01:00
|
|
|
/>
|
|
|
|
);
|
2024-02-21 21:07:32 +01:00
|
|
|
}
|
2024-02-23 01:03:34 +01:00
|
|
|
}
|
2024-02-21 21:07:32 +01:00
|
|
|
|
2024-02-23 01:03:34 +01:00
|
|
|
function getHoursAgo(hours: number): number {
|
|
|
|
const now = new Date();
|
|
|
|
now.setHours(now.getHours() - hours);
|
|
|
|
return now.getTime() / 1000;
|
2024-02-21 21:07:32 +01:00
|
|
|
}
|