diff --git a/web/src/components/filter/SearchFilterGroup.tsx b/web/src/components/filter/SearchFilterGroup.tsx index 5f3755e15..e8599895d 100644 --- a/web/src/components/filter/SearchFilterGroup.tsx +++ b/web/src/components/filter/SearchFilterGroup.tsx @@ -15,13 +15,15 @@ import { SearchFilter, SearchFilters, SearchSource, + SearchSortType, } from "@/types/search"; import { DateRange } from "react-day-picker"; import { cn } from "@/lib/utils"; -import { MdLabel } from "react-icons/md"; +import { MdLabel, MdSort } from "react-icons/md"; import PlatformAwareDialog from "../overlay/dialog/PlatformAwareDialog"; import SearchFilterDialog from "../overlay/dialog/SearchFilterDialog"; import { CalendarRangeFilterButton } from "./CalendarFilterButton"; +import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; type SearchFilterGroupProps = { className: string; @@ -107,6 +109,25 @@ export default function SearchFilterGroup({ [config, allLabels, allZones], ); + const availableSortTypes = useMemo(() => { + const sortTypes = ["date_asc", "date_desc"]; + if (filter?.min_score || filter?.max_score) { + sortTypes.push("score_desc", "score_asc"); + } + if (filter?.event_id || filter?.query) { + sortTypes.push("relevance"); + } + return sortTypes as SearchSortType[]; + }, [filter]); + + const defaultSortType = useMemo(() => { + if (filter?.query || filter?.event_id) { + return "relevance"; + } else { + return "date_desc"; + } + }, [filter]); + const groups = useMemo(() => { if (!config) { return []; @@ -179,6 +200,16 @@ export default function SearchFilterGroup({ filterValues={filterValues} onUpdateFilter={onUpdateFilter} /> + {filters.includes("sort") && Object.keys(filter ?? {}).length > 0 && ( + { + onUpdateFilter({ ...filter, sort: newSort }); + }} + /> + )} ); } @@ -362,3 +393,176 @@ export function GeneralFilterContent({ ); } + +type SortTypeButtonProps = { + availableSortTypes: SearchSortType[]; + defaultSortType: SearchSortType; + selectedSortType: SearchSortType | undefined; + updateSortType: (sortType: SearchSortType | undefined) => void; +}; +function SortTypeButton({ + availableSortTypes, + defaultSortType, + selectedSortType, + updateSortType, +}: SortTypeButtonProps) { + const [open, setOpen] = useState(false); + const [currentSortType, setCurrentSortType] = useState< + SearchSortType | undefined + >(selectedSortType as SearchSortType); + + // ui + + useEffect(() => { + setCurrentSortType(selectedSortType); + // only refresh when state changes + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedSortType]); + + const trigger = ( + + ); + const content = ( + setOpen(false)} + /> + ); + + return ( + { + if (!open) { + setCurrentSortType(selectedSortType); + } + + setOpen(open); + }} + /> + ); +} + +type SortTypeContentProps = { + availableSortTypes: SearchSortType[]; + defaultSortType: SearchSortType; + selectedSortType: SearchSortType | undefined; + currentSortType: SearchSortType | undefined; + updateSortType: (sort_type: SearchSortType | undefined) => void; + setCurrentSortType: (sort_type: SearchSortType | undefined) => void; + onClose: () => void; +}; +export function SortTypeContent({ + availableSortTypes, + defaultSortType, + selectedSortType, + currentSortType, + updateSortType, + setCurrentSortType, + onClose, +}: SortTypeContentProps) { + const sortLabels = { + date_asc: "Date (Ascending)", + date_desc: "Date (Descending)", + score_asc: "Object Score (Ascending)", + score_desc: "Object Score (Descending)", + relevance: "Relevance", + }; + + return ( + <> +
+
+ + setCurrentSortType(value as SearchSortType) + } + className="w-full space-y-1" + > + {availableSortTypes.map((value) => ( +
+ + +
+ ))} +
+
+
+ +
+ + +
+ + ); +} diff --git a/web/src/components/input/InputWithTags.tsx b/web/src/components/input/InputWithTags.tsx index 8f60bb73e..d5904b2a5 100644 --- a/web/src/components/input/InputWithTags.tsx +++ b/web/src/components/input/InputWithTags.tsx @@ -18,6 +18,7 @@ import { FilterType, SavedSearchQuery, SearchFilter, + SearchSortType, SearchSource, } from "@/types/search"; import useSuggestions from "@/hooks/use-suggestions"; @@ -323,6 +324,9 @@ export default function InputWithTags({ case "event_id": newFilters.event_id = value; break; + case "sort": + newFilters.sort = value as SearchSortType; + break; default: // Handle array types (cameras, labels, subLabels, zones) if (!newFilters[type]) newFilters[type] = [];