mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-12 22:25:24 +03:00
Implement searching
This commit is contained in:
parent
031d16e591
commit
350bffd3d3
@ -4,7 +4,6 @@ import useSWR from "swr";
|
|||||||
import { FrigateConfig } from "@/types/frigateConfig";
|
import { FrigateConfig } from "@/types/frigateConfig";
|
||||||
import { useCallback, useMemo, useState } from "react";
|
import { useCallback, useMemo, useState } from "react";
|
||||||
import { DropdownMenuSeparator } from "../ui/dropdown-menu";
|
import { DropdownMenuSeparator } from "../ui/dropdown-menu";
|
||||||
import { ReviewFilter, ReviewSeverity } from "@/types/review";
|
|
||||||
import { getEndOfDayTimestamp } from "@/utils/dateUtil";
|
import { getEndOfDayTimestamp } from "@/utils/dateUtil";
|
||||||
import { FaFilter } from "react-icons/fa";
|
import { FaFilter } from "react-icons/fa";
|
||||||
import { isDesktop, isMobile } from "react-device-detect";
|
import { isDesktop, isMobile } from "react-device-detect";
|
||||||
@ -18,6 +17,7 @@ import FilterSwitch from "./FilterSwitch";
|
|||||||
import { FilterList } from "@/types/filter";
|
import { FilterList } from "@/types/filter";
|
||||||
import CalendarFilterButton from "./CalendarFilterButton";
|
import CalendarFilterButton from "./CalendarFilterButton";
|
||||||
import { CamerasFilterButton } from "./CamerasFilterButton";
|
import { CamerasFilterButton } from "./CamerasFilterButton";
|
||||||
|
import { SearchFilter } from "@/types/search";
|
||||||
|
|
||||||
const SEARCH_FILTERS = ["cameras", "date", "general"] as const;
|
const SEARCH_FILTERS = ["cameras", "date", "general"] as const;
|
||||||
type SearchFilters = (typeof SEARCH_FILTERS)[number];
|
type SearchFilters = (typeof SEARCH_FILTERS)[number];
|
||||||
@ -25,9 +25,9 @@ const DEFAULT_REVIEW_FILTERS: SearchFilters[] = ["cameras", "date", "general"];
|
|||||||
|
|
||||||
type SearchFilterGroupProps = {
|
type SearchFilterGroupProps = {
|
||||||
filters?: SearchFilters[];
|
filters?: SearchFilters[];
|
||||||
filter?: ReviewFilter;
|
filter?: SearchFilter;
|
||||||
filterList?: FilterList;
|
filterList?: FilterList;
|
||||||
onUpdateFilter: (filter: ReviewFilter) => void;
|
onUpdateFilter: (filter: SearchFilter) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function SearchFilterGroup({
|
export default function SearchFilterGroup({
|
||||||
@ -170,12 +170,8 @@ export default function SearchFilterGroup({
|
|||||||
<GeneralFilterButton
|
<GeneralFilterButton
|
||||||
allLabels={filterValues.labels}
|
allLabels={filterValues.labels}
|
||||||
selectedLabels={filter?.labels}
|
selectedLabels={filter?.labels}
|
||||||
showAll={filter?.showAll == true}
|
|
||||||
allZones={filterValues.zones}
|
allZones={filterValues.zones}
|
||||||
selectedZones={filter?.zones}
|
selectedZones={filter?.zones}
|
||||||
setShowAll={(showAll) => {
|
|
||||||
onUpdateFilter({ ...filter, showAll });
|
|
||||||
}}
|
|
||||||
updateLabelFilter={(newLabels) => {
|
updateLabelFilter={(newLabels) => {
|
||||||
onUpdateFilter({ ...filter, labels: newLabels });
|
onUpdateFilter({ ...filter, labels: newLabels });
|
||||||
}}
|
}}
|
||||||
@ -207,22 +203,16 @@ export default function SearchFilterGroup({
|
|||||||
type GeneralFilterButtonProps = {
|
type GeneralFilterButtonProps = {
|
||||||
allLabels: string[];
|
allLabels: string[];
|
||||||
selectedLabels: string[] | undefined;
|
selectedLabels: string[] | undefined;
|
||||||
currentSeverity?: ReviewSeverity;
|
|
||||||
showAll: boolean;
|
|
||||||
allZones: string[];
|
allZones: string[];
|
||||||
selectedZones?: string[];
|
selectedZones?: string[];
|
||||||
setShowAll: (showAll: boolean) => void;
|
|
||||||
updateLabelFilter: (labels: string[] | undefined) => void;
|
updateLabelFilter: (labels: string[] | undefined) => void;
|
||||||
updateZoneFilter: (zones: string[] | undefined) => void;
|
updateZoneFilter: (zones: string[] | undefined) => void;
|
||||||
};
|
};
|
||||||
function GeneralFilterButton({
|
function GeneralFilterButton({
|
||||||
allLabels,
|
allLabels,
|
||||||
selectedLabels,
|
selectedLabels,
|
||||||
currentSeverity,
|
|
||||||
showAll,
|
|
||||||
allZones,
|
allZones,
|
||||||
selectedZones,
|
selectedZones,
|
||||||
setShowAll,
|
|
||||||
updateLabelFilter,
|
updateLabelFilter,
|
||||||
updateZoneFilter,
|
updateZoneFilter,
|
||||||
}: GeneralFilterButtonProps) {
|
}: GeneralFilterButtonProps) {
|
||||||
@ -257,14 +247,11 @@ function GeneralFilterButton({
|
|||||||
allLabels={allLabels}
|
allLabels={allLabels}
|
||||||
selectedLabels={selectedLabels}
|
selectedLabels={selectedLabels}
|
||||||
currentLabels={currentLabels}
|
currentLabels={currentLabels}
|
||||||
currentSeverity={currentSeverity}
|
|
||||||
showAll={showAll}
|
|
||||||
allZones={allZones}
|
allZones={allZones}
|
||||||
selectedZones={selectedZones}
|
selectedZones={selectedZones}
|
||||||
currentZones={currentZones}
|
currentZones={currentZones}
|
||||||
setCurrentZones={setCurrentZones}
|
setCurrentZones={setCurrentZones}
|
||||||
updateZoneFilter={updateZoneFilter}
|
updateZoneFilter={updateZoneFilter}
|
||||||
setShowAll={setShowAll}
|
|
||||||
updateLabelFilter={updateLabelFilter}
|
updateLabelFilter={updateLabelFilter}
|
||||||
setCurrentLabels={setCurrentLabels}
|
setCurrentLabels={setCurrentLabels}
|
||||||
onClose={() => setOpen(false)}
|
onClose={() => setOpen(false)}
|
||||||
@ -312,12 +299,9 @@ type GeneralFilterContentProps = {
|
|||||||
allLabels: string[];
|
allLabels: string[];
|
||||||
selectedLabels: string[] | undefined;
|
selectedLabels: string[] | undefined;
|
||||||
currentLabels: string[] | undefined;
|
currentLabels: string[] | undefined;
|
||||||
currentSeverity?: ReviewSeverity;
|
|
||||||
showAll?: boolean;
|
|
||||||
allZones?: string[];
|
allZones?: string[];
|
||||||
selectedZones?: string[];
|
selectedZones?: string[];
|
||||||
currentZones?: string[];
|
currentZones?: string[];
|
||||||
setShowAll?: (showAll: boolean) => void;
|
|
||||||
updateLabelFilter: (labels: string[] | undefined) => void;
|
updateLabelFilter: (labels: string[] | undefined) => void;
|
||||||
setCurrentLabels: (labels: string[] | undefined) => void;
|
setCurrentLabels: (labels: string[] | undefined) => void;
|
||||||
updateZoneFilter?: (zones: string[] | undefined) => void;
|
updateZoneFilter?: (zones: string[] | undefined) => void;
|
||||||
@ -328,12 +312,9 @@ export function GeneralFilterContent({
|
|||||||
allLabels,
|
allLabels,
|
||||||
selectedLabels,
|
selectedLabels,
|
||||||
currentLabels,
|
currentLabels,
|
||||||
currentSeverity,
|
|
||||||
showAll,
|
|
||||||
allZones,
|
allZones,
|
||||||
selectedZones,
|
selectedZones,
|
||||||
currentZones,
|
currentZones,
|
||||||
setShowAll,
|
|
||||||
updateLabelFilter,
|
updateLabelFilter,
|
||||||
setCurrentLabels,
|
setCurrentLabels,
|
||||||
updateZoneFilter,
|
updateZoneFilter,
|
||||||
@ -343,25 +324,6 @@ export function GeneralFilterContent({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="scrollbar-container h-auto max-h-[80dvh] overflow-y-auto overflow-x-hidden">
|
<div className="scrollbar-container h-auto max-h-[80dvh] overflow-y-auto overflow-x-hidden">
|
||||||
{currentSeverity && setShowAll && (
|
|
||||||
<div className="my-2.5 flex flex-col gap-2.5">
|
|
||||||
<FilterSwitch
|
|
||||||
label="Alerts"
|
|
||||||
disabled={currentSeverity == "alert"}
|
|
||||||
isChecked={currentSeverity == "alert" ? true : showAll == true}
|
|
||||||
onCheckedChange={setShowAll}
|
|
||||||
/>
|
|
||||||
<FilterSwitch
|
|
||||||
label="Detections"
|
|
||||||
disabled={currentSeverity == "detection"}
|
|
||||||
isChecked={
|
|
||||||
currentSeverity == "detection" ? true : showAll == true
|
|
||||||
}
|
|
||||||
onCheckedChange={setShowAll}
|
|
||||||
/>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="mb-5 mt-2.5 flex items-center justify-between">
|
<div className="mb-5 mt-2.5 flex items-center justify-between">
|
||||||
<Label
|
<Label
|
||||||
className="mx-2 cursor-pointer text-primary"
|
className="mx-2 cursor-pointer text-primary"
|
||||||
|
|||||||
@ -3,8 +3,9 @@ import ActivityIndicator from "@/components/indicators/activity-indicator";
|
|||||||
import SearchThumbnailPlayer from "@/components/player/SearchThumbnailPlayer";
|
import SearchThumbnailPlayer from "@/components/player/SearchThumbnailPlayer";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Toaster } from "@/components/ui/sonner";
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
import { SearchResult } from "@/types/search";
|
import useApiFilter from "@/hooks/use-api-filter";
|
||||||
import { useEffect, useState } from "react";
|
import { SearchFilter, SearchResult } from "@/types/search";
|
||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { LuSearchCheck, LuSearchX } from "react-icons/lu";
|
import { LuSearchCheck, LuSearchX } from "react-icons/lu";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
@ -15,6 +16,18 @@ export default function Search() {
|
|||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
|
|
||||||
|
// search filter
|
||||||
|
|
||||||
|
const [searchFilter, setSearchFilter, searchSearchParams] =
|
||||||
|
useApiFilter<SearchFilter>();
|
||||||
|
|
||||||
|
const onUpdateFilter = useCallback(
|
||||||
|
(newFilter: SearchFilter) => {
|
||||||
|
setSearchFilter(newFilter);
|
||||||
|
},
|
||||||
|
[setSearchFilter],
|
||||||
|
);
|
||||||
|
|
||||||
// search api
|
// search api
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -33,7 +46,19 @@ export default function Search() {
|
|||||||
}, [search]);
|
}, [search]);
|
||||||
|
|
||||||
const { data: searchResults, isLoading } = useSWR<SearchResult[]>(
|
const { data: searchResults, isLoading } = useSWR<SearchResult[]>(
|
||||||
searchTerm.length > 0 ? ["events/search", { query: searchTerm }] : null,
|
searchTerm.length > 0
|
||||||
|
? [
|
||||||
|
"events/search",
|
||||||
|
{
|
||||||
|
query: searchTerm,
|
||||||
|
cameras: searchSearchParams["cameras"],
|
||||||
|
labels: searchSearchParams["labels"],
|
||||||
|
zones: searchSearchParams["zones"],
|
||||||
|
before: searchSearchParams["before"],
|
||||||
|
after: searchSearchParams["after"],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -48,7 +73,10 @@ export default function Search() {
|
|||||||
onChange={(e) => setSearch(e.target.value)}
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SearchFilterGroup onUpdateFilter={() => {}} />
|
<SearchFilterGroup
|
||||||
|
filter={searchFilter}
|
||||||
|
onUpdateFilter={onUpdateFilter}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="no-scrollbar flex flex-1 flex-wrap content-start gap-2 overflow-y-auto md:gap-4">
|
<div className="no-scrollbar flex flex-1 flex-wrap content-start gap-2 overflow-y-auto md:gap-4">
|
||||||
|
|||||||
@ -10,3 +10,11 @@ export type SearchResult = {
|
|||||||
thumb_path?: string;
|
thumb_path?: string;
|
||||||
zones: string[];
|
zones: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type SearchFilter = {
|
||||||
|
cameras?: string[];
|
||||||
|
labels?: string[];
|
||||||
|
zones?: string[];
|
||||||
|
before?: number;
|
||||||
|
after?: number;
|
||||||
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user