Get history filtering working for cameras and labels

This commit is contained in:
Nick Mowen 2023-12-20 11:10:47 -07:00
parent 70ef2c0508
commit dac8c1f975
4 changed files with 144 additions and 52 deletions

View File

@ -23,6 +23,16 @@ export default function HistoryFilterPopover({
onUpdateFilter, onUpdateFilter,
}: HistoryFilterPopoverProps) { }: HistoryFilterPopoverProps) {
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
const [open, setOpen] = useState(false);
const disabledDates = useMemo(() => {
const tomorrow = new Date();
tomorrow.setHours(tomorrow.getHours() + 24, -1, 0, 0);
const future = new Date();
future.setFullYear(2032);
return { from: tomorrow, to: future };
}, []);
const { data: allLabels } = useSWR<string[]>(["labels"], { const { data: allLabels } = useSWR<string[]>(["labels"], {
revalidateOnFocus: false, revalidateOnFocus: false,
}); });
@ -42,10 +52,21 @@ export default function HistoryFilterPopover({
const [selectedFilters, setSelectedFilters] = useState({ const [selectedFilters, setSelectedFilters] = useState({
cameras: filter == undefined ? [] : filter.cameras, cameras: filter == undefined ? [] : filter.cameras,
labels: filter == undefined ? [] : filter.labels, labels: filter == undefined ? [] : filter.labels,
before: filter?.before,
after: filter?.after,
}); });
const dateRange = useMemo(() => {
return selectedFilters?.before == undefined ||
selectedFilters?.after == undefined
? undefined
: {
from: new Date(selectedFilters.after * 1000),
to: new Date(selectedFilters.before * 1000),
};
}, [selectedFilters]);
return ( return (
<Popover> <Popover open={open} onOpenChange={(open) => setOpen(open)}>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button> <Button>
<LuFilter className="mx-1" /> <LuFilter className="mx-1" />
@ -57,7 +78,9 @@ export default function HistoryFilterPopover({
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button className="capitalize" variant="outline"> <Button className="capitalize" variant="outline">
{""?.replaceAll("_", " ") || "All Cameras"} {selectedFilters.cameras.length == 0
? "All Cameras"
: `${selectedFilters.cameras.length} Cameras`}
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent> <DropdownMenuContent>
@ -107,7 +130,9 @@ export default function HistoryFilterPopover({
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button className="capitalize" variant="outline"> <Button className="capitalize" variant="outline">
{""?.replaceAll("_", " ") || "All Labels"} {selectedFilters.labels.length == 0
? "All Labels"
: `${selectedFilters.labels.length} Labels`}
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent> <DropdownMenuContent>
@ -155,10 +180,35 @@ export default function HistoryFilterPopover({
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
</div> </div>
<Calendar mode="range" /> <Calendar
mode="range"
disabled={disabledDates}
selected={dateRange}
onSelect={(range) => {
let afterTime = undefined;
if (range?.from != undefined) {
afterTime = range.from.getTime() / 1000;
}
// need to make sure the day selected for before covers the entire day
let beforeTime = undefined;
if (range?.from != undefined) {
const beforeDate = range.to ?? range.from;
beforeDate.setHours(beforeDate.getHours() + 24, -1, 0, 0);
beforeTime = beforeDate.getTime() / 1000;
}
setSelectedFilters({
...selectedFilters,
after: afterTime,
before: beforeTime,
});
}}
/>
<Button <Button
onClick={() => { onClick={() => {
onUpdateFilter(selectedFilters); onUpdateFilter(selectedFilters);
setOpen(false);
}} }}
> >
Save Save

View File

@ -1,17 +1,42 @@
import { useState } from "react"; import { useMemo, useState } from "react";
type useApiFilterReturn<F extends FilterType> = [ type useApiFilterReturn<F extends FilterType> = [
filter: F | undefined, filter: F | undefined,
setFilter: (filter: F) => void, setFilter: (filter: F) => void,
searchParams: { searchParams:
| {
[key: string]: any; [key: string]: any;
}, }
| undefined,
]; ];
export default function useApiFilter< export default function useApiFilter<
F extends FilterType, F extends FilterType,
>(): useApiFilterReturn<F> { >(): useApiFilterReturn<F> {
const [filter, setFilter] = useState<F>(); const [filter, setFilter] = useState<F | undefined>(undefined);
const searchParams = useMemo(() => {
return [filter, setFilter, {}]; if (filter == undefined) {
return undefined;
}
const search: { [key: string]: string } = {};
Object.entries(filter).forEach(([key, value]) => {
if (Array.isArray(value)) {
if (value.length == 0) {
// empty array means all so ignore
} else {
search[key] = value.join(",");
}
} else {
if (value != undefined) {
search[key] = `${value}`;
}
}
});
return search;
}, [filter]);
return [filter, setFilter, searchParams];
} }

View File

@ -40,15 +40,30 @@ function History() {
return axios.get(path, { params }).then((res) => res.data); return axios.get(path, { params }).then((res) => res.data);
}, []); }, []);
const getKey = useCallback((index: number, prevData: HourlyTimeline) => { const getKey = useCallback(
(index: number, prevData: HourlyTimeline) => {
if (index > 0) { if (index > 0) {
const lastDate = prevData.end; const lastDate = prevData.end;
const pagedParams = { before: lastDate, timezone, limit: API_LIMIT }; const pagedParams =
historySearchParams == undefined
? { before: lastDate, timezone, limit: API_LIMIT }
: {
...historySearchParams,
before: lastDate,
timezone,
limit: API_LIMIT,
};
return ["timeline/hourly", pagedParams]; return ["timeline/hourly", pagedParams];
} }
return ["timeline/hourly", { timezone, limit: API_LIMIT }]; const params =
}, []); historySearchParams == undefined
? { timezone, limit: API_LIMIT }
: { ...historySearchParams, timezone, limit: API_LIMIT };
return ["timeline/hourly", params];
},
[historySearchParams]
);
const { const {
data: timelinePages, data: timelinePages,
@ -188,7 +203,7 @@ function History() {
return ( return (
<div key={day}> <div key={day}>
<Heading <Heading
className="sticky py-2 -top-4 left-0 bg-background w-full z-10" className="sticky py-2 -top-4 left-0 bg-background w-full z-20"
as="h3" as="h3"
> >
{formatUnixTimestampToDateTime(parseInt(day), { {formatUnixTimestampToDateTime(parseInt(day), {

View File

@ -1,45 +1,47 @@
type CardsData = { type CardsData = {
[key: string]: { [key: string]: {
[key: string]: { [key: string]: {
[key: string]: Card [key: string]: Card;
} };
} };
} };
type Card = { type Card = {
camera: string, camera: string;
time: number, time: number;
entries: Timeline[], entries: Timeline[];
uniqueKeys: string[], uniqueKeys: string[];
} };
type Preview = { type Preview = {
camera: string, camera: string;
src: string, src: string;
type: string, type: string;
start: number, start: number;
end: number, end: number;
} };
type Timeline = { type Timeline = {
camera: string, camera: string;
timestamp: number, timestamp: number;
data: { data: {
[key: string]: any [key: string]: any;
}, };
class_type: string, class_type: string;
source_id: string, source_id: string;
source: string, source: string;
} };
type HourlyTimeline = { type HourlyTimeline = {
start: number, start: number;
end: number, end: number;
count: number, count: number;
hours: { [key: string]: Timeline[] }; hours: { [key: string]: Timeline[] };
} };
interface HistoryFilter extends FilterType { interface HistoryFilter extends FilterType {
cameras: string[], cameras: string[];
labels: string[], labels: string[];
before: number | undefined;
after: number | undefined;
} }