mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +01:00 
			
		
		
		
	Cleanup event filters (#10724)
* Add specific button / switch for showing reviewed items and use intermediate drawer for mobile * Match design for filters
This commit is contained in:
		
							parent
							
								
									35ecb342bb
								
							
						
					
					
						commit
						985b2d7b27
					
				@ -13,19 +13,35 @@ import {
 | 
				
			|||||||
import { ReviewFilter, ReviewSummary } from "@/types/review";
 | 
					import { ReviewFilter, ReviewSummary } from "@/types/review";
 | 
				
			||||||
import { getEndOfDayTimestamp } from "@/utils/dateUtil";
 | 
					import { getEndOfDayTimestamp } from "@/utils/dateUtil";
 | 
				
			||||||
import { useFormattedTimestamp } from "@/hooks/use-date-utils";
 | 
					import { useFormattedTimestamp } from "@/hooks/use-date-utils";
 | 
				
			||||||
import { FaCalendarAlt, FaFilter, FaRunning, FaVideo } from "react-icons/fa";
 | 
					import {
 | 
				
			||||||
import { isMobile } from "react-device-detect";
 | 
					  FaCalendarAlt,
 | 
				
			||||||
 | 
					  FaCheckCircle,
 | 
				
			||||||
 | 
					  FaFilter,
 | 
				
			||||||
 | 
					  FaRunning,
 | 
				
			||||||
 | 
					  FaVideo,
 | 
				
			||||||
 | 
					} from "react-icons/fa";
 | 
				
			||||||
 | 
					import { isDesktop, isMobile } from "react-device-detect";
 | 
				
			||||||
import { Drawer, DrawerContent, DrawerTrigger } from "../ui/drawer";
 | 
					import { Drawer, DrawerContent, DrawerTrigger } from "../ui/drawer";
 | 
				
			||||||
import { Switch } from "../ui/switch";
 | 
					import { Switch } from "../ui/switch";
 | 
				
			||||||
import { Label } from "../ui/label";
 | 
					import { Label } from "../ui/label";
 | 
				
			||||||
import FilterCheckBox from "./FilterCheckBox";
 | 
					import FilterCheckBox from "./FilterCheckBox";
 | 
				
			||||||
import ReviewActivityCalendar from "../overlay/ReviewActivityCalendar";
 | 
					import ReviewActivityCalendar from "../overlay/ReviewActivityCalendar";
 | 
				
			||||||
 | 
					import MobileReviewSettingsDrawer, {
 | 
				
			||||||
 | 
					  DrawerFeatures,
 | 
				
			||||||
 | 
					} from "../overlay/MobileReviewSettingsDrawer";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ATTRIBUTES = ["amazon", "face", "fedex", "license_plate", "ups"];
 | 
					const ATTRIBUTES = ["amazon", "face", "fedex", "license_plate", "ups"];
 | 
				
			||||||
const REVIEW_FILTERS = ["cameras", "date", "general", "motionOnly"] as const;
 | 
					const REVIEW_FILTERS = [
 | 
				
			||||||
 | 
					  "cameras",
 | 
				
			||||||
 | 
					  "reviewed",
 | 
				
			||||||
 | 
					  "date",
 | 
				
			||||||
 | 
					  "general",
 | 
				
			||||||
 | 
					  "motionOnly",
 | 
				
			||||||
 | 
					] as const;
 | 
				
			||||||
type ReviewFilters = (typeof REVIEW_FILTERS)[number];
 | 
					type ReviewFilters = (typeof REVIEW_FILTERS)[number];
 | 
				
			||||||
const DEFAULT_REVIEW_FILTERS: ReviewFilters[] = [
 | 
					const DEFAULT_REVIEW_FILTERS: ReviewFilters[] = [
 | 
				
			||||||
  "cameras",
 | 
					  "cameras",
 | 
				
			||||||
 | 
					  "reviewed",
 | 
				
			||||||
  "date",
 | 
					  "date",
 | 
				
			||||||
  "general",
 | 
					  "general",
 | 
				
			||||||
  "motionOnly",
 | 
					  "motionOnly",
 | 
				
			||||||
@ -94,6 +110,20 @@ export default function ReviewFilterGroup({
 | 
				
			|||||||
    );
 | 
					    );
 | 
				
			||||||
  }, [config]);
 | 
					  }, [config]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const mobileSettingsFeatures = useMemo<DrawerFeatures[]>(() => {
 | 
				
			||||||
 | 
					    const features: DrawerFeatures[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (filters.includes("date")) {
 | 
				
			||||||
 | 
					      features.push("calendar");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (filters.includes("general")) {
 | 
				
			||||||
 | 
					      features.push("filter");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return features;
 | 
				
			||||||
 | 
					  }, [filters]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // handle updating filters
 | 
					  // handle updating filters
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const onUpdateSelectedDay = useCallback(
 | 
					  const onUpdateSelectedDay = useCallback(
 | 
				
			||||||
@ -119,7 +149,15 @@ export default function ReviewFilterGroup({
 | 
				
			|||||||
          }}
 | 
					          }}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
      )}
 | 
					      )}
 | 
				
			||||||
      {filters.includes("date") && (
 | 
					      {filters.includes("reviewed") && (
 | 
				
			||||||
 | 
					        <ShowReviewFilter
 | 
				
			||||||
 | 
					          showReviewed={filter?.showReviewed || 0}
 | 
				
			||||||
 | 
					          setShowReviewed={(reviewed) =>
 | 
				
			||||||
 | 
					            onUpdateFilter({ ...filter, showReviewed: reviewed })
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					      {isDesktop && filters.includes("date") && (
 | 
				
			||||||
        <CalendarFilterButton
 | 
					        <CalendarFilterButton
 | 
				
			||||||
          reviewSummary={reviewSummary}
 | 
					          reviewSummary={reviewSummary}
 | 
				
			||||||
          day={
 | 
					          day={
 | 
				
			||||||
@ -136,17 +174,27 @@ export default function ReviewFilterGroup({
 | 
				
			|||||||
          setMotionOnly={setMotionOnly}
 | 
					          setMotionOnly={setMotionOnly}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
      )}
 | 
					      )}
 | 
				
			||||||
      {filters.includes("general") && (
 | 
					      {isDesktop && filters.includes("general") && (
 | 
				
			||||||
        <GeneralFilterButton
 | 
					        <GeneralFilterButton
 | 
				
			||||||
          allLabels={filterValues.labels}
 | 
					          allLabels={filterValues.labels}
 | 
				
			||||||
          selectedLabels={filter?.labels}
 | 
					          selectedLabels={filter?.labels}
 | 
				
			||||||
          updateLabelFilter={(newLabels) => {
 | 
					          updateLabelFilter={(newLabels) => {
 | 
				
			||||||
            onUpdateFilter({ ...filter, labels: newLabels });
 | 
					            onUpdateFilter({ ...filter, labels: newLabels });
 | 
				
			||||||
          }}
 | 
					          }}
 | 
				
			||||||
          showReviewed={filter?.showReviewed || 0}
 | 
					        />
 | 
				
			||||||
          setShowReviewed={(reviewed) =>
 | 
					      )}
 | 
				
			||||||
            onUpdateFilter({ ...filter, showReviewed: reviewed })
 | 
					      {isMobile && mobileSettingsFeatures.length > 0 && (
 | 
				
			||||||
          }
 | 
					        <MobileReviewSettingsDrawer
 | 
				
			||||||
 | 
					          features={mobileSettingsFeatures}
 | 
				
			||||||
 | 
					          filter={filter}
 | 
				
			||||||
 | 
					          onUpdateFilter={onUpdateFilter}
 | 
				
			||||||
 | 
					          // not applicable as exports are not used
 | 
				
			||||||
 | 
					          camera=""
 | 
				
			||||||
 | 
					          latestTime={0}
 | 
				
			||||||
 | 
					          currentTime={0}
 | 
				
			||||||
 | 
					          mode="none"
 | 
				
			||||||
 | 
					          setMode={() => {}}
 | 
				
			||||||
 | 
					          setRange={() => {}}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
      )}
 | 
					      )}
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
@ -307,6 +355,41 @@ function CamerasFilterButton({
 | 
				
			|||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ShowReviewedFilterProps = {
 | 
				
			||||||
 | 
					  showReviewed?: 0 | 1;
 | 
				
			||||||
 | 
					  setShowReviewed: (reviewed?: 0 | 1) => void;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					function ShowReviewFilter({
 | 
				
			||||||
 | 
					  showReviewed,
 | 
				
			||||||
 | 
					  setShowReviewed,
 | 
				
			||||||
 | 
					}: ShowReviewedFilterProps) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <>
 | 
				
			||||||
 | 
					      <div className="hidden h-9 md:flex p-2 justify-start items-center text-sm bg-secondary hover:bg-secondary/80 text-secondary-foreground rounded-md cursor-pointer">
 | 
				
			||||||
 | 
					        <Switch
 | 
				
			||||||
 | 
					          id="reviewed"
 | 
				
			||||||
 | 
					          checked={showReviewed == 1}
 | 
				
			||||||
 | 
					          onCheckedChange={() => setShowReviewed(showReviewed == 0 ? 1 : 0)}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					        <Label className="ml-2 cursor-pointer" htmlFor="reviewed">
 | 
				
			||||||
 | 
					          Show Reviewed
 | 
				
			||||||
 | 
					        </Label>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <Button
 | 
				
			||||||
 | 
					        className="block md:hidden ml-1"
 | 
				
			||||||
 | 
					        size="sm"
 | 
				
			||||||
 | 
					        variant="secondary"
 | 
				
			||||||
 | 
					        onClick={() => setShowReviewed(showReviewed == 0 ? 1 : 0)}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <FaCheckCircle
 | 
				
			||||||
 | 
					          className={`${showReviewed == 1 ? "text-selected" : "text-muted-foreground"}`}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      </Button>
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type CalendarFilterButtonProps = {
 | 
					type CalendarFilterButtonProps = {
 | 
				
			||||||
  reviewSummary?: ReviewSummary;
 | 
					  reviewSummary?: ReviewSummary;
 | 
				
			||||||
  day?: Date;
 | 
					  day?: Date;
 | 
				
			||||||
@ -371,19 +454,14 @@ function CalendarFilterButton({
 | 
				
			|||||||
type GeneralFilterButtonProps = {
 | 
					type GeneralFilterButtonProps = {
 | 
				
			||||||
  allLabels: string[];
 | 
					  allLabels: string[];
 | 
				
			||||||
  selectedLabels: string[] | undefined;
 | 
					  selectedLabels: string[] | undefined;
 | 
				
			||||||
  showReviewed?: 0 | 1;
 | 
					 | 
				
			||||||
  updateLabelFilter: (labels: string[] | undefined) => void;
 | 
					  updateLabelFilter: (labels: string[] | undefined) => void;
 | 
				
			||||||
  setShowReviewed: (reviewed?: 0 | 1) => void;
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
function GeneralFilterButton({
 | 
					function GeneralFilterButton({
 | 
				
			||||||
  allLabels,
 | 
					  allLabels,
 | 
				
			||||||
  selectedLabels,
 | 
					  selectedLabels,
 | 
				
			||||||
  showReviewed,
 | 
					 | 
				
			||||||
  updateLabelFilter,
 | 
					  updateLabelFilter,
 | 
				
			||||||
  setShowReviewed,
 | 
					 | 
				
			||||||
}: GeneralFilterButtonProps) {
 | 
					}: GeneralFilterButtonProps) {
 | 
				
			||||||
  const [open, setOpen] = useState(false);
 | 
					  const [open, setOpen] = useState(false);
 | 
				
			||||||
  const [reviewed, setReviewed] = useState(showReviewed ?? 0);
 | 
					 | 
				
			||||||
  const [currentLabels, setCurrentLabels] = useState<string[] | undefined>(
 | 
					  const [currentLabels, setCurrentLabels] = useState<string[] | undefined>(
 | 
				
			||||||
    selectedLabels,
 | 
					    selectedLabels,
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
@ -399,12 +477,8 @@ function GeneralFilterButton({
 | 
				
			|||||||
      allLabels={allLabels}
 | 
					      allLabels={allLabels}
 | 
				
			||||||
      selectedLabels={selectedLabels}
 | 
					      selectedLabels={selectedLabels}
 | 
				
			||||||
      currentLabels={currentLabels}
 | 
					      currentLabels={currentLabels}
 | 
				
			||||||
      showReviewed={showReviewed}
 | 
					 | 
				
			||||||
      reviewed={reviewed}
 | 
					 | 
				
			||||||
      updateLabelFilter={updateLabelFilter}
 | 
					      updateLabelFilter={updateLabelFilter}
 | 
				
			||||||
      setShowReviewed={setShowReviewed}
 | 
					 | 
				
			||||||
      setCurrentLabels={setCurrentLabels}
 | 
					      setCurrentLabels={setCurrentLabels}
 | 
				
			||||||
      setReviewed={setReviewed}
 | 
					 | 
				
			||||||
      onClose={() => setOpen(false)}
 | 
					      onClose={() => setOpen(false)}
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
@ -415,7 +489,6 @@ function GeneralFilterButton({
 | 
				
			|||||||
        open={open}
 | 
					        open={open}
 | 
				
			||||||
        onOpenChange={(open) => {
 | 
					        onOpenChange={(open) => {
 | 
				
			||||||
          if (!open) {
 | 
					          if (!open) {
 | 
				
			||||||
            setReviewed(showReviewed ?? 0);
 | 
					 | 
				
			||||||
            setCurrentLabels(selectedLabels);
 | 
					            setCurrentLabels(selectedLabels);
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -435,7 +508,6 @@ function GeneralFilterButton({
 | 
				
			|||||||
      open={open}
 | 
					      open={open}
 | 
				
			||||||
      onOpenChange={(open) => {
 | 
					      onOpenChange={(open) => {
 | 
				
			||||||
        if (!open) {
 | 
					        if (!open) {
 | 
				
			||||||
          setReviewed(showReviewed ?? 0);
 | 
					 | 
				
			||||||
          setCurrentLabels(selectedLabels);
 | 
					          setCurrentLabels(selectedLabels);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -443,7 +515,7 @@ function GeneralFilterButton({
 | 
				
			|||||||
      }}
 | 
					      }}
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      <PopoverTrigger asChild>{trigger}</PopoverTrigger>
 | 
					      <PopoverTrigger asChild>{trigger}</PopoverTrigger>
 | 
				
			||||||
      <PopoverContent side="left">{content}</PopoverContent>
 | 
					      <PopoverContent>{content}</PopoverContent>
 | 
				
			||||||
    </Popover>
 | 
					    </Popover>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -452,87 +524,84 @@ type GeneralFilterContentProps = {
 | 
				
			|||||||
  allLabels: string[];
 | 
					  allLabels: string[];
 | 
				
			||||||
  selectedLabels: string[] | undefined;
 | 
					  selectedLabels: string[] | undefined;
 | 
				
			||||||
  currentLabels: string[] | undefined;
 | 
					  currentLabels: string[] | undefined;
 | 
				
			||||||
  showReviewed?: 0 | 1;
 | 
					 | 
				
			||||||
  reviewed: 0 | 1;
 | 
					 | 
				
			||||||
  updateLabelFilter: (labels: string[] | undefined) => void;
 | 
					  updateLabelFilter: (labels: string[] | undefined) => void;
 | 
				
			||||||
  setCurrentLabels: (labels: string[] | undefined) => void;
 | 
					  setCurrentLabels: (labels: string[] | undefined) => void;
 | 
				
			||||||
  setShowReviewed: (reviewed?: 0 | 1) => void;
 | 
					 | 
				
			||||||
  setReviewed: (reviewed: 0 | 1) => void;
 | 
					 | 
				
			||||||
  onClose: () => void;
 | 
					  onClose: () => void;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
export function GeneralFilterContent({
 | 
					export function GeneralFilterContent({
 | 
				
			||||||
  allLabels,
 | 
					  allLabels,
 | 
				
			||||||
  selectedLabels,
 | 
					  selectedLabels,
 | 
				
			||||||
  currentLabels,
 | 
					  currentLabels,
 | 
				
			||||||
  showReviewed,
 | 
					 | 
				
			||||||
  reviewed,
 | 
					 | 
				
			||||||
  updateLabelFilter,
 | 
					  updateLabelFilter,
 | 
				
			||||||
  setCurrentLabels,
 | 
					  setCurrentLabels,
 | 
				
			||||||
  setShowReviewed,
 | 
					 | 
				
			||||||
  setReviewed,
 | 
					 | 
				
			||||||
  onClose,
 | 
					  onClose,
 | 
				
			||||||
}: GeneralFilterContentProps) {
 | 
					}: GeneralFilterContentProps) {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <>
 | 
				
			||||||
      <div className="flex p-2 justify-start items-center">
 | 
					 | 
				
			||||||
        <Switch
 | 
					 | 
				
			||||||
          id="reviewed"
 | 
					 | 
				
			||||||
          checked={reviewed == 1}
 | 
					 | 
				
			||||||
          onCheckedChange={() => setReviewed(reviewed == 0 ? 1 : 0)}
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
        <Label className="ml-2" htmlFor="reviewed">
 | 
					 | 
				
			||||||
          Show Reviewed
 | 
					 | 
				
			||||||
        </Label>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
      <DropdownMenuSeparator />
 | 
					 | 
				
			||||||
      <DropdownMenuLabel className="flex justify-center items-center">
 | 
					 | 
				
			||||||
        Filter Labels
 | 
					 | 
				
			||||||
      </DropdownMenuLabel>
 | 
					 | 
				
			||||||
      <DropdownMenuSeparator />
 | 
					 | 
				
			||||||
      <div className="h-auto overflow-y-auto overflow-x-hidden">
 | 
					      <div className="h-auto overflow-y-auto overflow-x-hidden">
 | 
				
			||||||
        <FilterCheckBox
 | 
					        <div className="flex justify-between items-center my-2.5">
 | 
				
			||||||
          isChecked={currentLabels == undefined}
 | 
					          <Label
 | 
				
			||||||
          label="All Labels"
 | 
					            className="mx-2 text-secondary-foreground cursor-pointer"
 | 
				
			||||||
          onCheckedChange={(isChecked) => {
 | 
					            htmlFor="allLabels"
 | 
				
			||||||
            if (isChecked) {
 | 
					          >
 | 
				
			||||||
              setCurrentLabels(undefined);
 | 
					            All Labels
 | 
				
			||||||
            }
 | 
					          </Label>
 | 
				
			||||||
          }}
 | 
					          <Switch
 | 
				
			||||||
        />
 | 
					            className="ml-1"
 | 
				
			||||||
        <DropdownMenuSeparator />
 | 
					            id="allLabels"
 | 
				
			||||||
        {allLabels.map((item) => (
 | 
					            checked={currentLabels == undefined}
 | 
				
			||||||
          <FilterCheckBox
 | 
					 | 
				
			||||||
            key={item}
 | 
					 | 
				
			||||||
            isChecked={currentLabels?.includes(item) ?? false}
 | 
					 | 
				
			||||||
            label={item.replaceAll("_", " ")}
 | 
					 | 
				
			||||||
            onCheckedChange={(isChecked) => {
 | 
					            onCheckedChange={(isChecked) => {
 | 
				
			||||||
              if (isChecked) {
 | 
					              if (isChecked) {
 | 
				
			||||||
                const updatedLabels = currentLabels ? [...currentLabels] : [];
 | 
					                setCurrentLabels(undefined);
 | 
				
			||||||
 | 
					 | 
				
			||||||
                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);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            }}
 | 
					            }}
 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
        ))}
 | 
					        </div>
 | 
				
			||||||
 | 
					        <DropdownMenuSeparator />
 | 
				
			||||||
 | 
					        <div className="my-2.5 flex flex-col gap-2.5">
 | 
				
			||||||
 | 
					          {allLabels.map((item) => (
 | 
				
			||||||
 | 
					            <div className="flex justify-between items-center">
 | 
				
			||||||
 | 
					              <Label
 | 
				
			||||||
 | 
					                className="w-full mx-2 text-secondary-foreground capitalize cursor-pointer"
 | 
				
			||||||
 | 
					                htmlFor={item}
 | 
				
			||||||
 | 
					              >
 | 
				
			||||||
 | 
					                {item.replaceAll("_", " ")}
 | 
				
			||||||
 | 
					              </Label>
 | 
				
			||||||
 | 
					              <Switch
 | 
				
			||||||
 | 
					                key={item}
 | 
				
			||||||
 | 
					                className="ml-1"
 | 
				
			||||||
 | 
					                id={item}
 | 
				
			||||||
 | 
					                checked={currentLabels?.includes(item) ?? false}
 | 
				
			||||||
 | 
					                onCheckedChange={(isChecked) => {
 | 
				
			||||||
 | 
					                  if (isChecked) {
 | 
				
			||||||
 | 
					                    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);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					                }}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          ))}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <DropdownMenuSeparator />
 | 
					      <DropdownMenuSeparator />
 | 
				
			||||||
      <div className="p-2 flex justify-evenly items-center">
 | 
					      <div className="p-2 flex justify-evenly items-center">
 | 
				
			||||||
        <Button
 | 
					        <Button
 | 
				
			||||||
          variant="select"
 | 
					          variant="select"
 | 
				
			||||||
          onClick={() => {
 | 
					          onClick={() => {
 | 
				
			||||||
            if (reviewed != showReviewed) {
 | 
					 | 
				
			||||||
              setShowReviewed(reviewed);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (selectedLabels != currentLabels) {
 | 
					            if (selectedLabels != currentLabels) {
 | 
				
			||||||
              updateLabelFilter(currentLabels);
 | 
					              updateLabelFilter(currentLabels);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -545,8 +614,6 @@ export function GeneralFilterContent({
 | 
				
			|||||||
        <Button
 | 
					        <Button
 | 
				
			||||||
          variant="secondary"
 | 
					          variant="secondary"
 | 
				
			||||||
          onClick={() => {
 | 
					          onClick={() => {
 | 
				
			||||||
            setReviewed(0);
 | 
					 | 
				
			||||||
            setShowReviewed(undefined);
 | 
					 | 
				
			||||||
            setCurrentLabels(undefined);
 | 
					            setCurrentLabels(undefined);
 | 
				
			||||||
            updateLabelFilter(undefined);
 | 
					            updateLabelFilter(undefined);
 | 
				
			||||||
          }}
 | 
					          }}
 | 
				
			||||||
@ -568,7 +635,7 @@ function ShowMotionOnlyButton({
 | 
				
			|||||||
}: ShowMotionOnlyButtonProps) {
 | 
					}: ShowMotionOnlyButtonProps) {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <>
 | 
				
			||||||
      <div className="hidden md:inline-flex items-center justify-center whitespace-nowrap text-sm bg-secondary text-secondary-foreground h-9 rounded-md md:px-3 md:mx-1">
 | 
					      <div className="hidden md:inline-flex items-center justify-center whitespace-nowrap text-sm bg-secondary hover:bg-secondary/80 text-secondary-foreground h-9 rounded-md px-3 mx-1 cursor-pointer">
 | 
				
			||||||
        <Switch
 | 
					        <Switch
 | 
				
			||||||
          className="ml-1"
 | 
					          className="ml-1"
 | 
				
			||||||
          id="collapse-motion"
 | 
					          id="collapse-motion"
 | 
				
			||||||
@ -578,7 +645,7 @@ function ShowMotionOnlyButton({
 | 
				
			|||||||
          }}
 | 
					          }}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
        <Label
 | 
					        <Label
 | 
				
			||||||
          className="mx-2 text-secondary-foreground"
 | 
					          className="mx-2 text-secondary-foreground cursor-pointer"
 | 
				
			||||||
          htmlFor="collapse-motion"
 | 
					          htmlFor="collapse-motion"
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          Motion only
 | 
					          Motion only
 | 
				
			||||||
 | 
				
			|||||||
@ -20,7 +20,16 @@ import { isMobile } from "react-device-detect";
 | 
				
			|||||||
const ATTRIBUTES = ["amazon", "face", "fedex", "license_plate", "ups"];
 | 
					const ATTRIBUTES = ["amazon", "face", "fedex", "license_plate", "ups"];
 | 
				
			||||||
type DrawerMode = "none" | "select" | "export" | "calendar" | "filter";
 | 
					type DrawerMode = "none" | "select" | "export" | "calendar" | "filter";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DRAWER_FEATURES = ["export", "calendar", "filter"] as const;
 | 
				
			||||||
 | 
					export type DrawerFeatures = (typeof DRAWER_FEATURES)[number];
 | 
				
			||||||
 | 
					const DEFAULT_DRAWER_FEATURES: DrawerFeatures[] = [
 | 
				
			||||||
 | 
					  "export",
 | 
				
			||||||
 | 
					  "calendar",
 | 
				
			||||||
 | 
					  "filter",
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type MobileReviewSettingsDrawerProps = {
 | 
					type MobileReviewSettingsDrawerProps = {
 | 
				
			||||||
 | 
					  features?: DrawerFeatures[];
 | 
				
			||||||
  camera: string;
 | 
					  camera: string;
 | 
				
			||||||
  filter?: ReviewFilter;
 | 
					  filter?: ReviewFilter;
 | 
				
			||||||
  latestTime: number;
 | 
					  latestTime: number;
 | 
				
			||||||
@ -32,6 +41,7 @@ type MobileReviewSettingsDrawerProps = {
 | 
				
			|||||||
  setMode: (mode: ExportMode) => void;
 | 
					  setMode: (mode: ExportMode) => void;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
export default function MobileReviewSettingsDrawer({
 | 
					export default function MobileReviewSettingsDrawer({
 | 
				
			||||||
 | 
					  features = DEFAULT_DRAWER_FEATURES,
 | 
				
			||||||
  camera,
 | 
					  camera,
 | 
				
			||||||
  filter,
 | 
					  filter,
 | 
				
			||||||
  latestTime,
 | 
					  latestTime,
 | 
				
			||||||
@ -123,27 +133,33 @@ export default function MobileReviewSettingsDrawer({
 | 
				
			|||||||
  if (drawerMode == "select") {
 | 
					  if (drawerMode == "select") {
 | 
				
			||||||
    content = (
 | 
					    content = (
 | 
				
			||||||
      <div className="w-full p-4 flex flex-col gap-2">
 | 
					      <div className="w-full p-4 flex flex-col gap-2">
 | 
				
			||||||
        <Button
 | 
					        {features.includes("export") && (
 | 
				
			||||||
          className="w-full flex justify-center items-center gap-2"
 | 
					          <Button
 | 
				
			||||||
          onClick={() => setDrawerMode("export")}
 | 
					            className="w-full flex justify-center items-center gap-2"
 | 
				
			||||||
        >
 | 
					            onClick={() => setDrawerMode("export")}
 | 
				
			||||||
          <FaArrowDown className="p-1 fill-secondary bg-muted-foreground rounded-md" />
 | 
					          >
 | 
				
			||||||
          Export
 | 
					            <FaArrowDown className="p-1 fill-secondary bg-muted-foreground rounded-md" />
 | 
				
			||||||
        </Button>
 | 
					            Export
 | 
				
			||||||
        <Button
 | 
					          </Button>
 | 
				
			||||||
          className="w-full flex justify-center items-center gap-2"
 | 
					        )}
 | 
				
			||||||
          onClick={() => setDrawerMode("calendar")}
 | 
					        {features.includes("calendar") && (
 | 
				
			||||||
        >
 | 
					          <Button
 | 
				
			||||||
          <FaCalendarAlt className="fill-muted-foreground" />
 | 
					            className="w-full flex justify-center items-center gap-2"
 | 
				
			||||||
          Calendar
 | 
					            onClick={() => setDrawerMode("calendar")}
 | 
				
			||||||
        </Button>
 | 
					          >
 | 
				
			||||||
        <Button
 | 
					            <FaCalendarAlt className="fill-muted-foreground" />
 | 
				
			||||||
          className="w-full flex justify-center items-center gap-2"
 | 
					            Calendar
 | 
				
			||||||
          onClick={() => setDrawerMode("filter")}
 | 
					          </Button>
 | 
				
			||||||
        >
 | 
					        )}
 | 
				
			||||||
          <FaFilter className="fill-muted-foreground" />
 | 
					        {features.includes("filter") && (
 | 
				
			||||||
          Filter
 | 
					          <Button
 | 
				
			||||||
        </Button>
 | 
					            className="w-full flex justify-center items-center gap-2"
 | 
				
			||||||
 | 
					            onClick={() => setDrawerMode("filter")}
 | 
				
			||||||
 | 
					          >
 | 
				
			||||||
 | 
					            <FaFilter className="fill-muted-foreground" />
 | 
				
			||||||
 | 
					            Filter
 | 
				
			||||||
 | 
					          </Button>
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  } else if (drawerMode == "export") {
 | 
					  } else if (drawerMode == "export") {
 | 
				
			||||||
@ -230,17 +246,13 @@ export default function MobileReviewSettingsDrawer({
 | 
				
			|||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <GeneralFilterContent
 | 
					        <GeneralFilterContent
 | 
				
			||||||
          allLabels={allLabels.concat(allLabels)}
 | 
					          allLabels={allLabels}
 | 
				
			||||||
          selectedLabels={filter?.labels}
 | 
					          selectedLabels={filter?.labels}
 | 
				
			||||||
          currentLabels={currentLabels}
 | 
					          currentLabels={currentLabels}
 | 
				
			||||||
          showReviewed={0}
 | 
					 | 
				
			||||||
          reviewed={0}
 | 
					 | 
				
			||||||
          setCurrentLabels={setCurrentLabels}
 | 
					          setCurrentLabels={setCurrentLabels}
 | 
				
			||||||
          updateLabelFilter={(newLabels) =>
 | 
					          updateLabelFilter={(newLabels) =>
 | 
				
			||||||
            onUpdateFilter({ ...filter, labels: newLabels })
 | 
					            onUpdateFilter({ ...filter, labels: newLabels })
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          setShowReviewed={() => {}}
 | 
					 | 
				
			||||||
          setReviewed={() => {}}
 | 
					 | 
				
			||||||
          onClose={() => setDrawerMode("select")}
 | 
					          onClose={() => setDrawerMode("select")}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
@ -280,10 +292,3 @@ export default function MobileReviewSettingsDrawer({
 | 
				
			|||||||
    </>
 | 
					    </>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * <MobileTimelineDrawer
 | 
					 | 
				
			||||||
              selected={timelineType ?? "timeline"}
 | 
					 | 
				
			||||||
              onSelect={setTimelineType}
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -257,7 +257,7 @@ export default function EventView({
 | 
				
			|||||||
            filters={
 | 
					            filters={
 | 
				
			||||||
              severity == "significant_motion"
 | 
					              severity == "significant_motion"
 | 
				
			||||||
                ? ["cameras", "date", "motionOnly"]
 | 
					                ? ["cameras", "date", "motionOnly"]
 | 
				
			||||||
                : ["cameras", "date", "general"]
 | 
					                : ["cameras", "reviewed", "date", "general"]
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            reviewSummary={reviewSummary}
 | 
					            reviewSummary={reviewSummary}
 | 
				
			||||||
            filter={filter}
 | 
					            filter={filter}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user