Refactor general review filter to only call the update function once (#14866)

This commit is contained in:
Josh Hawkins 2024-11-08 07:45:00 -06:00 committed by GitHub
parent 46ed520886
commit ae30ac6e3c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 117 additions and 117 deletions

View File

@ -14,7 +14,7 @@ import MobileReviewSettingsDrawer, {
} from "../overlay/MobileReviewSettingsDrawer"; } from "../overlay/MobileReviewSettingsDrawer";
import useOptimisticState from "@/hooks/use-optimistic-state"; import useOptimisticState from "@/hooks/use-optimistic-state";
import FilterSwitch from "./FilterSwitch"; import FilterSwitch from "./FilterSwitch";
import { FilterList } from "@/types/filter"; import { FilterList, GeneralFilter } from "@/types/filter";
import CalendarFilterButton from "./CalendarFilterButton"; import CalendarFilterButton from "./CalendarFilterButton";
import { CamerasFilterButton } from "./CamerasFilterButton"; import { CamerasFilterButton } from "./CamerasFilterButton";
import PlatformAwareDialog from "../overlay/dialog/PlatformAwareDialog"; import PlatformAwareDialog from "../overlay/dialog/PlatformAwareDialog";
@ -214,15 +214,7 @@ export default function ReviewFilterGroup({
showAll={filter?.showAll == true} showAll={filter?.showAll == true}
allZones={filterValues.zones} allZones={filterValues.zones}
selectedZones={filter?.zones} selectedZones={filter?.zones}
setShowAll={(showAll) => { onUpdateFilter={onUpdateFilter}
onUpdateFilter({ ...filter, showAll });
}}
updateLabelFilter={(newLabels) => {
onUpdateFilter({ ...filter, labels: newLabels });
}}
updateZoneFilter={(newZones) =>
onUpdateFilter({ ...filter, zones: newZones })
}
/> />
)} )}
{isMobile && mobileSettingsFeatures.length > 0 && ( {isMobile && mobileSettingsFeatures.length > 0 && (
@ -300,37 +292,40 @@ type GeneralFilterButtonProps = {
showAll: boolean; showAll: boolean;
allZones: string[]; allZones: string[];
selectedZones?: string[]; selectedZones?: string[];
setShowAll: (showAll: boolean) => void; filter?: GeneralFilter;
updateLabelFilter: (labels: string[] | undefined) => void; onUpdateFilter: (filter: ReviewFilter) => void;
updateZoneFilter: (zones: string[] | undefined) => void;
}; };
function GeneralFilterButton({ function GeneralFilterButton({
allLabels, allLabels,
selectedLabels, selectedLabels,
filter,
currentSeverity, currentSeverity,
showAll, showAll,
allZones, allZones,
selectedZones, selectedZones,
setShowAll, onUpdateFilter,
updateLabelFilter,
updateZoneFilter,
}: GeneralFilterButtonProps) { }: GeneralFilterButtonProps) {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [currentLabels, setCurrentLabels] = useState<string[] | undefined>( const [currentFilter, setCurrentFilter] = useState<GeneralFilter>({
selectedLabels, labels: selectedLabels,
); zones: selectedZones,
const [currentZones, setCurrentZones] = useState<string[] | undefined>( showAll: showAll,
selectedZones, ...filter,
); });
// ui // Update local state when props change
useEffect(() => { useEffect(() => {
setCurrentLabels(selectedLabels); setCurrentFilter({
setCurrentZones(selectedZones); labels: selectedLabels,
zones: selectedZones,
showAll: showAll,
...filter,
});
// only refresh when state changes // only refresh when state changes
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedLabels, selectedZones]); }, [selectedLabels, selectedZones, showAll, filter]);
const trigger = ( const trigger = (
<Button <Button
@ -342,10 +337,18 @@ function GeneralFilterButton({
aria-label="Filter" aria-label="Filter"
> >
<FaFilter <FaFilter
className={`${selectedLabels?.length || selectedZones?.length ? "text-selected-foreground" : "text-secondary-foreground"}`} className={`${
selectedLabels?.length || selectedZones?.length
? "text-selected-foreground"
: "text-secondary-foreground"
}`}
/> />
<div <div
className={`hidden md:block ${selectedLabels?.length || selectedZones?.length ? "text-selected-foreground" : "text-primary"}`} className={`hidden md:block ${
selectedLabels?.length || selectedZones?.length
? "text-selected-foreground"
: "text-primary"
}`}
> >
Filter Filter
</div> </div>
@ -355,17 +358,22 @@ function GeneralFilterButton({
<GeneralFilterContent <GeneralFilterContent
allLabels={allLabels} allLabels={allLabels}
selectedLabels={selectedLabels} selectedLabels={selectedLabels}
currentLabels={currentLabels}
currentSeverity={currentSeverity} currentSeverity={currentSeverity}
showAll={showAll}
allZones={allZones} allZones={allZones}
filter={currentFilter}
selectedZones={selectedZones} selectedZones={selectedZones}
currentZones={currentZones} onUpdateFilter={setCurrentFilter}
setCurrentZones={setCurrentZones} onApply={() => {
updateZoneFilter={updateZoneFilter} if (currentFilter !== filter) {
setShowAll={setShowAll} onUpdateFilter(currentFilter);
updateLabelFilter={updateLabelFilter} }
setCurrentLabels={setCurrentLabels} setOpen(false);
}}
onReset={() => {
const resetFilter: GeneralFilter = {};
setCurrentFilter(resetFilter);
onUpdateFilter(resetFilter);
}}
onClose={() => setOpen(false)} onClose={() => setOpen(false)}
/> />
); );
@ -377,7 +385,12 @@ function GeneralFilterButton({
open={open} open={open}
onOpenChange={(open) => { onOpenChange={(open) => {
if (!open) { if (!open) {
setCurrentLabels(selectedLabels); setCurrentFilter({
labels: selectedLabels,
zones: selectedZones,
showAll: showAll,
...filter,
});
} }
setOpen(open); setOpen(open);
@ -388,54 +401,50 @@ function GeneralFilterButton({
type GeneralFilterContentProps = { type GeneralFilterContentProps = {
allLabels: string[]; allLabels: string[];
selectedLabels: string[] | undefined; allZones: string[];
currentLabels: string[] | undefined;
currentSeverity?: ReviewSeverity; currentSeverity?: ReviewSeverity;
showAll?: boolean; filter: GeneralFilter;
allZones?: string[]; selectedLabels?: string[];
selectedZones?: string[]; selectedZones?: string[];
currentZones?: string[]; onUpdateFilter: (filter: GeneralFilter) => void;
setShowAll?: (showAll: boolean) => void; onApply: () => void;
updateLabelFilter: (labels: string[] | undefined) => void; onReset: () => void;
setCurrentLabels: (labels: string[] | undefined) => void;
updateZoneFilter?: (zones: string[] | undefined) => void;
setCurrentZones?: (zones: string[] | undefined) => void;
onClose: () => void; onClose: () => void;
}; };
export function GeneralFilterContent({ export function GeneralFilterContent({
allLabels, allLabels,
selectedLabels,
currentLabels,
currentSeverity,
showAll,
allZones, allZones,
selectedZones, currentSeverity,
currentZones, filter,
setShowAll, onUpdateFilter,
updateLabelFilter, onApply,
setCurrentLabels, onReset,
updateZoneFilter,
setCurrentZones,
onClose, onClose,
}: GeneralFilterContentProps) { }: GeneralFilterContentProps) {
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 && ( {currentSeverity && (
<div className="my-2.5 flex flex-col gap-2.5"> <div className="my-2.5 flex flex-col gap-2.5">
<FilterSwitch <FilterSwitch
label="Alerts" label="Alerts"
disabled={currentSeverity == "alert"} disabled={currentSeverity == "alert"}
isChecked={currentSeverity == "alert" ? true : showAll == true} isChecked={
onCheckedChange={setShowAll} currentSeverity == "alert" ? true : filter.showAll === true
}
onCheckedChange={(checked) =>
onUpdateFilter({ ...filter, showAll: checked })
}
/> />
<FilterSwitch <FilterSwitch
label="Detections" label="Detections"
disabled={currentSeverity == "detection"} disabled={currentSeverity == "detection"}
isChecked={ isChecked={
currentSeverity == "detection" ? true : showAll == true currentSeverity == "detection" ? true : filter.showAll === true
}
onCheckedChange={(checked) =>
onUpdateFilter({ ...filter, showAll: checked })
} }
onCheckedChange={setShowAll}
/> />
<DropdownMenuSeparator /> <DropdownMenuSeparator />
</div> </div>
@ -450,10 +459,11 @@ export function GeneralFilterContent({
<Switch <Switch
className="ml-1" className="ml-1"
id="allLabels" id="allLabels"
checked={currentLabels == undefined} checked={filter.labels === undefined}
onCheckedChange={(isChecked) => { onCheckedChange={(isChecked) => {
if (isChecked) { if (isChecked) {
setCurrentLabels(undefined); const { labels: _labels, ...rest } = filter;
onUpdateFilter(rest);
} }
}} }}
/> />
@ -463,20 +473,19 @@ export function GeneralFilterContent({
<FilterSwitch <FilterSwitch
key={item} key={item}
label={item.replaceAll("_", " ")} label={item.replaceAll("_", " ")}
isChecked={currentLabels?.includes(item) ?? false} isChecked={filter.labels?.includes(item) ?? false}
onCheckedChange={(isChecked) => { onCheckedChange={(isChecked) => {
if (isChecked) { if (isChecked) {
const updatedLabels = currentLabels ? [...currentLabels] : []; const updatedLabels = filter.labels ? [...filter.labels] : [];
updatedLabels.push(item); updatedLabels.push(item);
setCurrentLabels(updatedLabels); onUpdateFilter({ ...filter, labels: updatedLabels });
} else { } else {
const updatedLabels = currentLabels ? [...currentLabels] : []; const updatedLabels = filter.labels ? [...filter.labels] : [];
// can not deselect the last item // can not deselect the last item
if (updatedLabels.length > 1) { if (updatedLabels.length > 1) {
updatedLabels.splice(updatedLabels.indexOf(item), 1); updatedLabels.splice(updatedLabels.indexOf(item), 1);
setCurrentLabels(updatedLabels); onUpdateFilter({ ...filter, labels: updatedLabels });
} }
} }
}} }}
@ -484,7 +493,7 @@ export function GeneralFilterContent({
))} ))}
</div> </div>
{allZones && setCurrentZones && ( {allZones && (
<> <>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<div className="mb-5 mt-2.5 flex items-center justify-between"> <div className="mb-5 mt-2.5 flex items-center justify-between">
@ -497,10 +506,11 @@ export function GeneralFilterContent({
<Switch <Switch
className="ml-1" className="ml-1"
id="allZones" id="allZones"
checked={currentZones == undefined} checked={filter.zones === undefined}
onCheckedChange={(isChecked) => { onCheckedChange={(isChecked) => {
if (isChecked) { if (isChecked) {
setCurrentZones(undefined); const { zones: _zones, ...rest } = filter;
onUpdateFilter(rest);
} }
}} }}
/> />
@ -510,24 +520,24 @@ export function GeneralFilterContent({
<FilterSwitch <FilterSwitch
key={item} key={item}
label={item.replaceAll("_", " ")} label={item.replaceAll("_", " ")}
isChecked={currentZones?.includes(item) ?? false} isChecked={filter.zones?.includes(item) ?? false}
onCheckedChange={(isChecked) => { onCheckedChange={(isChecked) => {
if (isChecked) { if (isChecked) {
const updatedZones = currentZones const updatedZones = filter.zones
? [...currentZones] ? [...filter.zones]
: []; : [];
updatedZones.push(item); updatedZones.push(item);
setCurrentZones(updatedZones); onUpdateFilter({ ...filter, zones: updatedZones });
} else { } else {
const updatedZones = currentZones const updatedZones = filter.zones
? [...currentZones] ? [...filter.zones]
: []; : [];
// can not deselect the last item // can not deselect the last item
if (updatedZones.length > 1) { if (updatedZones.length > 1) {
updatedZones.splice(updatedZones.indexOf(item), 1); updatedZones.splice(updatedZones.indexOf(item), 1);
setCurrentZones(updatedZones); onUpdateFilter({ ...filter, zones: updatedZones });
} }
} }
}} }}
@ -543,27 +553,13 @@ export function GeneralFilterContent({
aria-label="Apply" aria-label="Apply"
variant="select" variant="select"
onClick={() => { onClick={() => {
if (selectedLabels != currentLabels) { onApply();
updateLabelFilter(currentLabels);
}
if (updateZoneFilter && selectedZones != currentZones) {
updateZoneFilter(currentZones);
}
onClose(); onClose();
}} }}
> >
Apply Apply
</Button> </Button>
<Button <Button aria-label="Reset" onClick={onReset}>
aria-label="Reset"
onClick={() => {
setCurrentLabels(undefined);
setCurrentZones?.(undefined);
updateLabelFilter(undefined);
}}
>
Reset Reset
</Button> </Button>
</div> </div>

View File

@ -4,7 +4,7 @@ import { Button } from "../ui/button";
import { FaArrowDown, FaCalendarAlt, FaCog, FaFilter } from "react-icons/fa"; import { FaArrowDown, FaCalendarAlt, FaCog, FaFilter } from "react-icons/fa";
import { TimeRange } from "@/types/timeline"; import { TimeRange } from "@/types/timeline";
import { ExportContent, ExportPreviewDialog } from "./ExportDialog"; import { ExportContent, ExportPreviewDialog } from "./ExportDialog";
import { ExportMode } from "@/types/filter"; import { ExportMode, GeneralFilter } from "@/types/filter";
import ReviewActivityCalendar from "./ReviewActivityCalendar"; import ReviewActivityCalendar from "./ReviewActivityCalendar";
import { SelectSeparator } from "../ui/select"; import { SelectSeparator } from "../ui/select";
import { ReviewFilter, ReviewSeverity, ReviewSummary } from "@/types/review"; import { ReviewFilter, ReviewSeverity, ReviewSummary } from "@/types/review";
@ -114,12 +114,12 @@ export default function MobileReviewSettingsDrawer({
// filters // filters
const [currentLabels, setCurrentLabels] = useState<string[] | undefined>( const [currentFilter, setCurrentFilter] = useState<GeneralFilter>({
filter?.labels, labels: filter?.labels,
); zones: filter?.zones,
const [currentZones, setCurrentZones] = useState<string[] | undefined>( showAll: filter?.showAll,
filter?.zones, ...filter,
); });
if (!isMobile) { if (!isMobile) {
return; return;
@ -260,23 +260,21 @@ export default function MobileReviewSettingsDrawer({
<GeneralFilterContent <GeneralFilterContent
allLabels={allLabels} allLabels={allLabels}
selectedLabels={filter?.labels} selectedLabels={filter?.labels}
currentLabels={currentLabels}
currentSeverity={currentSeverity} currentSeverity={currentSeverity}
showAll={filter?.showAll == true}
allZones={allZones} allZones={allZones}
filter={currentFilter}
selectedZones={filter?.zones} selectedZones={filter?.zones}
currentZones={currentZones} onUpdateFilter={setCurrentFilter}
setCurrentZones={setCurrentZones} onApply={() => {
updateZoneFilter={(newZones) => if (currentFilter !== filter) {
onUpdateFilter({ ...filter, zones: newZones }) onUpdateFilter(currentFilter);
} }
setShowAll={(showAll) => {
onUpdateFilter({ ...filter, showAll });
}} }}
setCurrentLabels={setCurrentLabels} onReset={() => {
updateLabelFilter={(newLabels) => const resetFilter: GeneralFilter = {};
onUpdateFilter({ ...filter, labels: newLabels }) setCurrentFilter(resetFilter);
} onUpdateFilter(resetFilter);
}}
onClose={() => setDrawerMode("select")} onClose={() => setDrawerMode("select")}
/> />
</div> </div>

View File

@ -10,3 +10,9 @@ export type FilterList = {
}; };
export const LAST_24_HOURS_KEY = "last24Hours"; export const LAST_24_HOURS_KEY = "last24Hours";
export type GeneralFilter = {
showAll?: boolean;
labels?: string[];
zones?: string[];
};