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,
}: HistoryFilterPopoverProps) {
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"], {
revalidateOnFocus: false,
});
@ -42,10 +52,21 @@ export default function HistoryFilterPopover({
const [selectedFilters, setSelectedFilters] = useState({
cameras: filter == undefined ? [] : filter.cameras,
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 (
<Popover>
<Popover open={open} onOpenChange={(open) => setOpen(open)}>
<PopoverTrigger asChild>
<Button>
<LuFilter className="mx-1" />
@ -57,7 +78,9 @@ export default function HistoryFilterPopover({
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button className="capitalize" variant="outline">
{""?.replaceAll("_", " ") || "All Cameras"}
{selectedFilters.cameras.length == 0
? "All Cameras"
: `${selectedFilters.cameras.length} Cameras`}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
@ -107,7 +130,9 @@ export default function HistoryFilterPopover({
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button className="capitalize" variant="outline">
{""?.replaceAll("_", " ") || "All Labels"}
{selectedFilters.labels.length == 0
? "All Labels"
: `${selectedFilters.labels.length} Labels`}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
@ -155,10 +180,35 @@ export default function HistoryFilterPopover({
</DropdownMenuContent>
</DropdownMenu>
</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
onClick={() => {
onUpdateFilter(selectedFilters);
setOpen(false);
}}
>
Save

View File

@ -1,17 +1,42 @@
import { useState } from "react";
import { useMemo, useState } from "react";
type useApiFilterReturn<F extends FilterType> = [
filter: F | undefined,
setFilter: (filter: F) => void,
searchParams: {
[key: string]: any;
},
searchParams:
| {
[key: string]: any;
}
| undefined,
];
export default function useApiFilter<
F extends FilterType,
>(): useApiFilterReturn<F> {
const [filter, setFilter] = useState<F>();
const [filter, setFilter] = useState<F | undefined>(undefined);
const searchParams = useMemo(() => {
if (filter == undefined) {
return undefined;
}
return [filter, setFilter, {}];
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);
}, []);
const getKey = useCallback((index: number, prevData: HourlyTimeline) => {
if (index > 0) {
const lastDate = prevData.end;
const pagedParams = { before: lastDate, timezone, limit: API_LIMIT };
return ["timeline/hourly", pagedParams];
}
const getKey = useCallback(
(index: number, prevData: HourlyTimeline) => {
if (index > 0) {
const lastDate = prevData.end;
const pagedParams =
historySearchParams == undefined
? { before: lastDate, timezone, limit: API_LIMIT }
: {
...historySearchParams,
before: lastDate,
timezone,
limit: API_LIMIT,
};
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 {
data: timelinePages,
@ -188,7 +203,7 @@ function History() {
return (
<div key={day}>
<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"
>
{formatUnixTimestampToDateTime(parseInt(day), {

View File

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