mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-07 03:35:26 +03:00
Get history filtering working for cameras and labels
This commit is contained in:
parent
70ef2c0508
commit
dac8c1f975
@ -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
|
||||
|
||||
@ -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];
|
||||
}
|
||||
|
||||
@ -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), {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user