diff --git a/web/src/components/filter/ReviewFilterGroup.tsx b/web/src/components/filter/ReviewFilterGroup.tsx index 4ecf71c4e..489a997fb 100644 --- a/web/src/components/filter/ReviewFilterGroup.tsx +++ b/web/src/components/filter/ReviewFilterGroup.tsx @@ -1,4 +1,4 @@ -import { LuCheck, LuVideo } from "react-icons/lu"; +import { LuCheck } from "react-icons/lu"; import { Button } from "../ui/button"; import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover"; import useSWR from "swr"; @@ -16,8 +16,11 @@ import { ReviewFilter } from "@/types/review"; import { getEndOfDayTimestamp } from "@/utils/dateUtil"; import { useFormattedTimestamp } from "@/hooks/use-date-utils"; import { FaCalendarAlt, FaFilter, FaVideo } from "react-icons/fa"; -import { getIconTypeForGroup } from "@/utils/iconUtil"; import { IconType } from "react-icons"; +import { isMobile } from "react-device-detect"; +import { Drawer, DrawerContent, DrawerTrigger } from "../ui/drawer"; +import { Switch } from "../ui/switch"; +import { Label } from "../ui/label"; const ATTRIBUTES = ["amazon", "face", "fedex", "license_plate", "ups"]; @@ -41,14 +44,21 @@ export default function ReviewFilterGroup({ const cameras = filter?.cameras || Object.keys(config.cameras); cameras.forEach((camera) => { - config.cameras[camera].objects.track.forEach((label) => { + const cameraConfig = config.cameras[camera]; + cameraConfig.objects.track.forEach((label) => { if (!ATTRIBUTES.includes(label)) { labels.add(label); } }); + + if (cameraConfig.audio.enabled_in_config) { + cameraConfig.audio.listen.forEach((label) => { + labels.add(label); + }); + } }); - return [...labels]; + return [...labels].sort(); }, [config, filter]); const filterValues = useMemo( @@ -125,88 +135,123 @@ function CamerasFilterButton({ selectedCameras, updateCameraFilter, }: CameraFilterButtonProps) { + const [open, setOpen] = useState(false); const [currentCameras, setCurrentCameras] = useState( selectedCameras, ); - return ( - { - if (!open) { - updateCameraFilter(currentCameras); - } - }} - > - - - - - Filter Cameras - + const trigger = ( + + ); + const content = ( + <> + + Filter Cameras + + + { + if (isChecked) { + setCurrentCameras(undefined); + } + }} + /> + {groups.length > 0 && ( + <> + + {groups.map(([name, conf]) => { + return ( + { + setCurrentCameras([...conf.cameras]); + }} + /> + ); + })} + + )} + + {allCameras.map((item) => ( { if (isChecked) { - setCurrentCameras(undefined); + const updatedCameras = currentCameras ? [...currentCameras] : []; + + updatedCameras.push(item); + setCurrentCameras(updatedCameras); + } else { + const updatedCameras = currentCameras ? [...currentCameras] : []; + + // can not deselect the last item + if (updatedCameras.length > 1) { + updatedCameras.splice(updatedCameras.indexOf(item), 1); + setCurrentCameras(updatedCameras); + } } }} /> - {groups.length > 0 && ( - <> - - {groups.map(([name, conf]) => { - return ( - { - setCurrentCameras([...conf.cameras]); - }} - /> - ); - })} - - )} - - {allCameras.map((item) => ( - { - if (isChecked) { - const updatedCameras = currentCameras - ? [...currentCameras] - : []; + ))} + +
+ +
+ + ); - updatedCameras.push(item); - setCurrentCameras(updatedCameras); - } else { - const updatedCameras = currentCameras - ? [...currentCameras] - : []; + if (isMobile) { + return ( + { + if (!open) { + setCurrentCameras(selectedCameras); + } - // can not deselect the last item - if (updatedCameras.length > 1) { - updatedCameras.splice(updatedCameras.indexOf(item), 1); - setCurrentCameras(updatedCameras); - } - } - }} - /> - ))} - -
+ setOpen(open); + }} + > + {trigger} + {content} + + ); + } + + return ( + { + if (!open) { + setCurrentCameras(selectedCameras); + } + + setOpen(open); + }} + > + {trigger} + {content} ); } @@ -219,6 +264,7 @@ function CalendarFilterButton({ day, updateSelectedDay, }: CalendarFilterButtonProps) { + const [open, setOpen] = useState(false); const [selectedDay, setSelectedDay] = useState(day); const disabledDates = useMemo(() => { const tomorrow = new Date(); @@ -232,32 +278,71 @@ function CalendarFilterButton({ "%b %-d", ); + const trigger = ( + + ); + const content = ( + <> + { + setSelectedDay(day); + }} + /> + +
+ +
+ + ); + + if (isMobile) { + return ( + { + if (!open) { + setSelectedDay(day); + } + + setOpen(open); + }} + > + {trigger} + {content} + + ); + } + return ( { if (!open) { - updateSelectedDay(selectedDay); + setSelectedDay(day); } + + setOpen(open); }} > - - - - - { - setSelectedDay(day); - }} - /> - + {trigger} + {content} ); } @@ -276,108 +361,122 @@ function GeneralFilterButton({ showReviewed, setShowReviewed, }: GeneralFilterButtonProps) { - return ( - - - - - -
- - -
-
-
- ); -} - -type LabelFilterButtonProps = { - allLabels: string[]; - selectedLabels: string[] | undefined; - updateLabelFilter: (labels: string[] | undefined) => void; -}; -function LabelsFilterButton({ - allLabels, - selectedLabels, - updateLabelFilter, -}: LabelFilterButtonProps) { + const [open, setOpen] = useState(false); + const [reviewed, setReviewed] = useState(showReviewed ?? 0); const [currentLabels, setCurrentLabels] = useState( selectedLabels, ); - return ( - { - if (!open) { - updateLabelFilter(currentLabels); - } - }} - > - - - - - Filter Labels - + const trigger = ( + + ); + const content = ( + <> +
+ setReviewed(reviewed == 0 ? 1 : 0)} + /> + +
+ + + Filter Labels + + + { + if (isChecked) { + setCurrentLabels(undefined); + } + }} + /> + + {allLabels.map((item) => ( { if (isChecked) { - setCurrentLabels(undefined); + const updatedLabels = currentLabels ? [...currentLabels] : []; + + updatedLabels.push(item); + setCurrentLabels(updatedLabels); + } else { + const updatedLabels = currentLabels ? [...currentLabels] : []; + + // can not deselect the last item + if (updatedLabels.length > 1) { + updatedLabels.splice(updatedLabels.indexOf(item), 1); + setCurrentLabels(updatedLabels); + } } }} /> - - {allLabels.map((item) => ( - { - if (isChecked) { - const updatedLabels = currentLabels ? [...currentLabels] : []; + ))} + +
+ +
+ + ); + + if (isMobile) { + return ( + { + if (!open) { + setReviewed(showReviewed ?? 0); + setCurrentLabels(selectedLabels); + } + + setOpen(open); + }} + > + {trigger} + {content} + + ); + } + + return ( + { + if (!open) { + setReviewed(showReviewed ?? 0); + setCurrentLabels(selectedLabels); + } + + setOpen(open); + }} + > + {trigger} + {content} + ); } diff --git a/web/src/components/player/DynamicVideoPlayer.tsx b/web/src/components/player/DynamicVideoPlayer.tsx index 590c98481..4a96bf19c 100644 --- a/web/src/components/player/DynamicVideoPlayer.tsx +++ b/web/src/components/player/DynamicVideoPlayer.tsx @@ -301,6 +301,7 @@ export default function DynamicVideoPlayer({ autoPlay playsInline muted + disableRemotePlayback onSeeked={onPreviewSeeked} onLoadedData={() => controller.previewReady()} > @@ -308,9 +309,6 @@ export default function DynamicVideoPlayer({ )} - {onClick && !hasRecordingAtTime && ( -
- )}
); } diff --git a/web/src/components/ui/button.tsx b/web/src/components/ui/button.tsx index 60636b470..0078e3e1c 100644 --- a/web/src/components/ui/button.tsx +++ b/web/src/components/ui/button.tsx @@ -10,7 +10,7 @@ const buttonVariants = cva( variants: { variant: { default: "bg-primary text-primary-foreground hover:bg-primary/90", - select: "bg-select text-white hover:bg-select/90", + select: "bg-selected text-white hover:bg-opacity-90", destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", outline: diff --git a/web/src/components/ui/calendar.tsx b/web/src/components/ui/calendar.tsx index 850f6f03a..792df0890 100644 --- a/web/src/components/ui/calendar.tsx +++ b/web/src/components/ui/calendar.tsx @@ -25,24 +25,24 @@ function Calendar({ nav: "space-x-1 flex items-center", nav_button: cn( buttonVariants({ variant: "outline" }), - "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100" + "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100", ), nav_button_previous: "absolute left-1", nav_button_next: "absolute right-1", table: "w-full border-collapse space-y-1", - head_row: "flex", + head_row: "flex justify-center", head_cell: "text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]", - row: "flex w-full mt-2", + row: "flex w-full mt-2 justify-center", cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20", day: cn( buttonVariants({ variant: "ghost" }), - "h-9 w-9 p-0 font-normal aria-selected:opacity-100" + "h-9 w-9 p-0 font-normal aria-selected:opacity-100", ), day_range_end: "day-range-end", day_selected: - "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground", - day_today: "bg-accent text-accent-foreground", + "bg-selected text-white hover:bg-selected hover:text-white focus:bg-selected focus:text-white", + day_today: "bg-muted text-muted-foreground", day_outside: "day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30", day_disabled: "text-muted-foreground opacity-50", diff --git a/web/src/components/ui/slider-no-thumb.tsx b/web/src/components/ui/slider-no-thumb.tsx index 23f4d4ef1..021e6b806 100644 --- a/web/src/components/ui/slider-no-thumb.tsx +++ b/web/src/components/ui/slider-no-thumb.tsx @@ -18,7 +18,7 @@ const Slider = React.forwardRef< - + )); Slider.displayName = SliderPrimitive.Root.displayName; diff --git a/web/src/components/ui/switch.tsx b/web/src/components/ui/switch.tsx index aa58baa29..343df17d0 100644 --- a/web/src/components/ui/switch.tsx +++ b/web/src/components/ui/switch.tsx @@ -9,7 +9,7 @@ const Switch = React.forwardRef< >(({ className, ...props }, ref) => ( - Mark all items as reviewed + Mark these items as reviewed ) : (