Add support for sub label filtering

This commit is contained in:
Nicolas Mowen 2024-09-09 16:14:03 -06:00
parent 2ce03c0ae5
commit 6c2d9b15a6
3 changed files with 194 additions and 2 deletions

View File

@ -18,9 +18,14 @@ import { SearchFilter, SearchSource } from "@/types/search";
import { DateRange } from "react-day-picker";
import { cn } from "@/lib/utils";
const SEARCH_FILTERS = ["cameras", "date", "general"] as const;
const SEARCH_FILTERS = ["cameras", "date", "general", "sub"] as const;
type SearchFilters = (typeof SEARCH_FILTERS)[number];
const DEFAULT_REVIEW_FILTERS: SearchFilters[] = ["cameras", "date", "general"];
const DEFAULT_REVIEW_FILTERS: SearchFilters[] = [
"cameras",
"date",
"general",
"sub",
];
type SearchFilterGroupProps = {
className: string;
@ -72,6 +77,8 @@ export default function SearchFilterGroup({
return [...labels].sort();
}, [config, filterList, filter]);
const { data: allSubLabels } = useSWR("sub_labels");
const allZones = useMemo<string[]>(() => {
if (filterList?.zones) {
return filterList.zones;
@ -181,6 +188,15 @@ export default function SearchFilterGroup({
}
/>
)}
{filters.includes("sub") && (
<SubFilterButton
allSubLabels={allSubLabels}
selectedSubLabels={filter?.subLabels}
updateSubLabelFilter={(newSubLabels) =>
onUpdateFilter({ ...filter, subLabels: newSubLabels })
}
/>
)}
</div>
);
}
@ -509,3 +525,175 @@ export function GeneralFilterContent({
</>
);
}
type SubFilterButtonProps = {
allSubLabels: string[];
selectedSubLabels: string[] | undefined;
updateSubLabelFilter: (labels: string[] | undefined) => void;
};
function SubFilterButton({
allSubLabels,
selectedSubLabels,
updateSubLabelFilter,
}: SubFilterButtonProps) {
const [open, setOpen] = useState(false);
const [currentSubLabels, setCurrentSubLabels] = useState<
string[] | undefined
>(selectedSubLabels);
const trigger = (
<Button
size="sm"
variant={selectedSubLabels?.length ? "select" : "default"}
className="flex items-center gap-2 capitalize"
>
<FaFilter
className={`${selectedSubLabels?.length || selectedSubLabels?.length ? "text-selected-foreground" : "text-secondary-foreground"}`}
/>
<div
className={`hidden md:block ${selectedSubLabels?.length ? "text-selected-foreground" : "text-primary"}`}
>
Filter
</div>
</Button>
);
const content = (
<SubFilterContent
allSubLabels={allSubLabels}
selectedSubLabels={selectedSubLabels}
currentSubLabels={currentSubLabels}
setCurrentSubLabels={setCurrentSubLabels}
updateSubLabelFilter={updateSubLabelFilter}
onClose={() => setOpen(false)}
/>
);
if (isMobile) {
return (
<Drawer
open={open}
onOpenChange={(open) => {
if (!open) {
setCurrentSubLabels(selectedSubLabels);
}
setOpen(open);
}}
>
<DrawerTrigger asChild>{trigger}</DrawerTrigger>
<DrawerContent className="max-h-[75dvh] overflow-hidden">
{content}
</DrawerContent>
</Drawer>
);
}
return (
<Popover
open={open}
onOpenChange={(open) => {
if (!open) {
setCurrentSubLabels(selectedSubLabels);
}
setOpen(open);
}}
>
<PopoverTrigger asChild>{trigger}</PopoverTrigger>
<PopoverContent>{content}</PopoverContent>
</Popover>
);
}
type SubFilterContentProps = {
allSubLabels: string[];
selectedSubLabels: string[] | undefined;
currentSubLabels: string[] | undefined;
updateSubLabelFilter: (labels: string[] | undefined) => void;
setCurrentSubLabels: (labels: string[] | undefined) => void;
onClose: () => void;
};
export function SubFilterContent({
allSubLabels,
selectedSubLabels,
currentSubLabels,
updateSubLabelFilter,
setCurrentSubLabels,
onClose,
}: SubFilterContentProps) {
return (
<>
<div className="scrollbar-container h-auto max-h-[80dvh] overflow-y-auto overflow-x-hidden">
<div className="mb-5 mt-2.5 flex items-center justify-between">
<Label
className="mx-2 cursor-pointer text-primary"
htmlFor="allLabels"
>
All Sub Labels
</Label>
<Switch
className="ml-1"
id="allLabels"
checked={currentSubLabels == undefined}
onCheckedChange={(isChecked) => {
if (isChecked) {
setCurrentSubLabels(undefined);
}
}}
/>
</div>
<div className="my-2.5 flex flex-col gap-2.5">
{allSubLabels.map((item) => (
<FilterSwitch
key={item}
label={item.replaceAll("_", " ")}
isChecked={currentSubLabels?.includes(item) ?? false}
onCheckedChange={(isChecked) => {
if (isChecked) {
const updatedLabels = currentSubLabels
? [...currentSubLabels]
: [];
updatedLabels.push(item);
setCurrentSubLabels(updatedLabels);
} else {
const updatedLabels = currentSubLabels
? [...currentSubLabels]
: [];
// can not deselect the last item
if (updatedLabels.length > 1) {
updatedLabels.splice(updatedLabels.indexOf(item), 1);
setCurrentSubLabels(updatedLabels);
}
}
}}
/>
))}
</div>
</div>
<DropdownMenuSeparator />
<div className="flex items-center justify-evenly p-2">
<Button
variant="select"
onClick={() => {
if (selectedSubLabels != currentSubLabels) {
updateSubLabelFilter(currentSubLabels);
}
onClose();
}}
>
Apply
</Button>
<Button
onClick={() => {
setCurrentSubLabels(undefined);
}}
>
Reset
</Button>
</div>
</>
);
}

View File

@ -67,6 +67,7 @@ export default function Search() {
query: similaritySearch.id,
cameras: searchSearchParams["cameras"],
labels: searchSearchParams["labels"],
sub_labels: searchSearchParams["subLabels"],
zones: searchSearchParams["zones"],
before: searchSearchParams["before"],
after: searchSearchParams["after"],
@ -83,6 +84,7 @@ export default function Search() {
query: searchTerm,
cameras: searchSearchParams["cameras"],
labels: searchSearchParams["labels"],
sub_labels: searchSearchParams["subLabels"],
zones: searchSearchParams["zones"],
before: searchSearchParams["before"],
after: searchSearchParams["after"],
@ -97,6 +99,7 @@ export default function Search() {
{
cameras: searchSearchParams["cameras"],
labels: searchSearchParams["labels"],
sub_labels: searchSearchParams["subLabels"],
zones: searchSearchParams["zones"],
before: searchSearchParams["before"],
after: searchSearchParams["after"],

View File

@ -18,6 +18,7 @@ export type SearchResult = {
export type SearchFilter = {
cameras?: string[];
labels?: string[];
subLabels?: string[];
zones?: string[];
before?: number;
after?: number;