diff --git a/web/src/components/filter/SearchFilterGroup.tsx b/web/src/components/filter/SearchFilterGroup.tsx index bbdac7513..376c9b599 100644 --- a/web/src/components/filter/SearchFilterGroup.tsx +++ b/web/src/components/filter/SearchFilterGroup.tsx @@ -29,6 +29,7 @@ import PlatformAwareDialog from "../overlay/dialog/PlatformAwareDialog"; import { FaArrowRight, FaClock } from "react-icons/fa"; import { useFormattedHour } from "@/hooks/use-date-utils"; import SearchFilterDialog from "../overlay/dialog/SearchFilterDialog"; +import { CalendarRangeFilterButton } from "./CalendarFilterButton"; type SearchFilterGroupProps = { className: string; @@ -166,7 +167,22 @@ export default function SearchFilterGroup({ }} /> )} + {filters.includes("date") && ( + + )} void; -}; -function TimeRangeFilterButton({ - config, - timeRange, - updateTimeRange, -}: TimeRangeFilterButtonProps) { - const [open, setOpen] = useState(false); - const [startOpen, setStartOpen] = useState(false); - const [endOpen, setEndOpen] = useState(false); - - const [afterHour, beforeHour] = useMemo(() => { - if (!timeRange || !timeRange.includes(",")) { - return [DEFAULT_TIME_RANGE_AFTER, DEFAULT_TIME_RANGE_BEFORE]; - } - - return timeRange.split(","); - }, [timeRange]); - - const [selectedAfterHour, setSelectedAfterHour] = useState(afterHour); - const [selectedBeforeHour, setSelectedBeforeHour] = useState(beforeHour); - - // format based on locale - - const formattedAfter = useFormattedHour(config, afterHour); - const formattedBefore = useFormattedHour(config, beforeHour); - const formattedSelectedAfter = useFormattedHour(config, selectedAfterHour); - const formattedSelectedBefore = useFormattedHour(config, selectedBeforeHour); - - useEffect(() => { - setSelectedAfterHour(afterHour); - setSelectedBeforeHour(beforeHour); - // only refresh when state changes - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [timeRange]); - - const trigger = ( - - - - {timeRange ? `${formattedAfter} - ${formattedBefore}` : "All Times"} - - - ); - const content = ( - - - { - if (!open) { - setStartOpen(false); - } - }} - > - - { - setStartOpen(true); - setEndOpen(false); - }} - > - {formattedSelectedAfter} - - - - { - const clock = e.target.value; - const [hour, minute, _] = clock.split(":"); - setSelectedAfterHour(`${hour}:${minute}`); - }} - /> - - - - { - if (!open) { - setEndOpen(false); - } - }} - > - - { - setEndOpen(true); - setStartOpen(false); - }} - > - {formattedSelectedBefore} - - - - { - const clock = e.target.value; - const [hour, minute, _] = clock.split(":"); - setSelectedBeforeHour(`${hour}:${minute}`); - }} - /> - - - - - - { - if ( - selectedAfterHour == DEFAULT_TIME_RANGE_AFTER && - selectedBeforeHour == DEFAULT_TIME_RANGE_BEFORE - ) { - updateTimeRange(undefined); - } else { - updateTimeRange(`${selectedAfterHour},${selectedBeforeHour}`); - } - - setOpen(false); - }} - > - Apply - - { - setSelectedAfterHour(DEFAULT_TIME_RANGE_AFTER); - setSelectedBeforeHour(DEFAULT_TIME_RANGE_BEFORE); - updateTimeRange(undefined); - }} - > - Reset - - - - ); - - return ( - { - setOpen(open); - }} - /> - ); -} - type ZoneFilterButtonProps = { allZones: string[]; selectedZones?: string[]; diff --git a/web/src/components/overlay/dialog/SearchFilterDialog.tsx b/web/src/components/overlay/dialog/SearchFilterDialog.tsx index 27b172f14..17ed0cb30 100644 --- a/web/src/components/overlay/dialog/SearchFilterDialog.tsx +++ b/web/src/components/overlay/dialog/SearchFilterDialog.tsx @@ -1,14 +1,27 @@ -import { FaCog } from "react-icons/fa"; +import { FaArrowRight, FaCog } from "react-icons/fa"; -import { useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { PlatformAwareSheet } from "./PlatformAwareDialog"; import { Button } from "@/components/ui/button"; -import { CamerasFilterContent } from "@/components/filter/CamerasFilterButton"; import useSWR from "swr"; -import { SearchFilter, SearchSource } from "@/types/search"; -import { CameraGroupConfig } from "@/types/frigateConfig"; +import { + DEFAULT_TIME_RANGE_AFTER, + DEFAULT_TIME_RANGE_BEFORE, + SearchFilter, + SearchSource, +} from "@/types/search"; +import { CameraGroupConfig, FrigateConfig } from "@/types/frigateConfig"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { isDesktop } from "react-device-detect"; +import { useFormattedHour } from "@/hooks/use-date-utils"; +import Heading from "@/components/ui/heading"; type SearchFilterDialogProps = { + config?: FrigateConfig; filter?: SearchFilter; filterValues: { cameras: string[]; @@ -20,6 +33,7 @@ type SearchFilterDialogProps = { onUpdateFilter: (filter: SearchFilter) => void; }; export default function SearchFilterDialog({ + config, filter, filterValues, groups, @@ -27,10 +41,8 @@ export default function SearchFilterDialog({ }: SearchFilterDialogProps) { // data + const [currentFilter, setCurrentFilter] = useState(filter ?? {}); const { data: allSubLabels } = useSWR(["sub_labels", { split_joined: 1 }]); - const [currentCameras, setCurrentCameras] = useState( - filter?.cameras, - ); // state @@ -44,15 +56,12 @@ export default function SearchFilterDialog({ ); const content = ( <> - { - onUpdateFilter({ ...filter, cameras: newCameras }); - }} + + setCurrentFilter({ time_range: newRange, ...currentFilter }) + } /> > ); @@ -68,6 +77,139 @@ export default function SearchFilterDialog({ ); } +type TimeRangeFilterContentProps = { + config?: FrigateConfig; + timeRange?: string; + updateTimeRange: (range: string | undefined) => void; +}; +function TimeRangeFilterContent({ + config, + timeRange, + updateTimeRange, +}: TimeRangeFilterContentProps) { + const [startOpen, setStartOpen] = useState(false); + const [endOpen, setEndOpen] = useState(false); + + const [afterHour, beforeHour] = useMemo(() => { + if (!timeRange || !timeRange.includes(",")) { + return [DEFAULT_TIME_RANGE_AFTER, DEFAULT_TIME_RANGE_BEFORE]; + } + + return timeRange.split(","); + }, [timeRange]); + + const [selectedAfterHour, setSelectedAfterHour] = useState(afterHour); + const [selectedBeforeHour, setSelectedBeforeHour] = useState(beforeHour); + + // format based on locale + + const formattedSelectedAfter = useFormattedHour(config, selectedAfterHour); + const formattedSelectedBefore = useFormattedHour(config, selectedBeforeHour); + + useEffect(() => { + setSelectedAfterHour(afterHour); + setSelectedBeforeHour(beforeHour); + // only refresh when state changes + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [timeRange]); + + useEffect(() => { + if ( + selectedAfterHour == DEFAULT_TIME_RANGE_AFTER && + selectedBeforeHour == DEFAULT_TIME_RANGE_BEFORE + ) { + updateTimeRange(undefined); + } else { + updateTimeRange(`${selectedAfterHour},${selectedBeforeHour}`); + } + // only refresh when state changes + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedAfterHour, selectedBeforeHour]); + + return ( + + Time Range + + { + if (!open) { + setStartOpen(false); + } + }} + > + + { + setStartOpen(true); + setEndOpen(false); + }} + > + {formattedSelectedAfter} + + + + { + const clock = e.target.value; + const [hour, minute, _] = clock.split(":"); + setSelectedAfterHour(`${hour}:${minute}`); + }} + /> + + + + { + if (!open) { + setEndOpen(false); + } + }} + > + + { + setEndOpen(true); + setStartOpen(false); + }} + > + {formattedSelectedBefore} + + + + { + const clock = e.target.value; + const [hour, minute, _] = clock.split(":"); + setSelectedBeforeHour(`${hour}:${minute}`); + }} + /> + + + + + ); +} + /** * {filters.includes("date") && (