From 1ccbcddae34c21c27e2f347d3aaae3421d1e8dc2 Mon Sep 17 00:00:00 2001 From: mr-shortman Date: Sat, 15 Mar 2025 15:43:56 +0100 Subject: [PATCH] added category filter bar; clear filter button --- src/components/article/article-filter-bar.tsx | 61 ++++++------ .../category/category-filter-bar.tsx | 99 +++++++++++++++++++ .../category/grid/infinite-category-grid.tsx | 10 +- src/components/combobox.tsx | 2 +- src/components/sort-filter-select.tsx | 37 +++++++ 5 files changed, 169 insertions(+), 40 deletions(-) create mode 100644 src/components/sort-filter-select.tsx diff --git a/src/components/article/article-filter-bar.tsx b/src/components/article/article-filter-bar.tsx index 3497a3f..bcc113f 100644 --- a/src/components/article/article-filter-bar.tsx +++ b/src/components/article/article-filter-bar.tsx @@ -2,19 +2,10 @@ import { cn, debounce } from "@/lib/utils"; import React from "react"; import { Input } from "../ui/input"; -import { - ArrowDownAZ, - ArrowUpAz, - CalendarArrowDown, - CalendarArrowUp, - Eye, - FilterIcon, - LucideIcon, - MessageSquare, - SearchIcon, -} from "lucide-react"; +import { SearchIcon, XIcon } from "lucide-react"; import CategorySelect from "../category/category-select"; -import { Combobox } from "../combobox"; +import SortFilterSelect from "../sort-filter-select"; +import { Button } from "../ui/button"; export type ArticleFilter = { query?: string; @@ -22,6 +13,12 @@ export type ArticleFilter = { sort?: string; }; +const defaultFilter: ArticleFilter = { + query: "", + category: undefined, + sort: undefined, +}; + function ArticleFilterBar({ className, onFilterUpdate, @@ -29,9 +26,7 @@ function ArticleFilterBar({ className?: string; onFilterUpdate: (filter: ArticleFilter) => void; }) { - const [filter, setFilter] = React.useState({ - query: "", - }); + const [filter, setFilter] = React.useState(defaultFilter); const onFilterChange = React.useCallback( (newFilter: Partial, notify: boolean = true) => { @@ -49,6 +44,8 @@ function ArticleFilterBar({ }, 300), [], ); + const hasFilter = + filter.query?.length || filter.sort?.length || filter.category?.length; return ( <> @@ -71,6 +68,21 @@ function ArticleFilterBar({ />
+ + { @@ -82,9 +94,7 @@ function ArticleFilterBar({ size: "sm", }} /> - { onFilterChange({ @@ -92,10 +102,6 @@ function ArticleFilterBar({ }); }} className="w-full lg:max-w-64" - messageUi={{ - selectIcon: FilterIcon, - select: "Sortieren", - }} buttonProps={{ size: "sm", }} @@ -107,14 +113,3 @@ function ArticleFilterBar({ } export default ArticleFilterBar; - -const sortItems: Array<{ - Icon: LucideIcon; - value: string; - label: string; -}> = [ - { Icon: CalendarArrowUp, value: "newest", label: "Neueste" }, - { Icon: CalendarArrowDown, value: "oldest", label: "Älteste" }, - { Icon: ArrowDownAZ, value: "abc", label: "Alphabetisch A-Z" }, - { Icon: ArrowUpAz, value: "cba", label: "Alphabetisch Z-A" }, -]; diff --git a/src/components/category/category-filter-bar.tsx b/src/components/category/category-filter-bar.tsx index e69de29..c9c6d0f 100644 --- a/src/components/category/category-filter-bar.tsx +++ b/src/components/category/category-filter-bar.tsx @@ -0,0 +1,99 @@ +"use client"; +import { cn, debounce } from "@/lib/utils"; +import React from "react"; +import { Input } from "../ui/input"; +import { SearchIcon, XIcon } from "lucide-react"; +import CategorySelect from "../category/category-select"; +import SortFilterSelect from "../sort-filter-select"; +import { Button } from "../ui/button"; + +export type CategoryFilter = { + query?: string; + sort?: string; +}; + +const defaultFilter: CategoryFilter = { + query: "", + sort: undefined, +}; + +export default function CategoryFilterBar({ + className, + onFilterUpdate, +}: { + className?: string; + onFilterUpdate: (filter: CategoryFilter) => void; +}) { + const [filter, setFilter] = React.useState(defaultFilter); + + const onFilterChange = React.useCallback( + (newFilter: Partial, notify: boolean = true) => { + const f = { ...filter, ...newFilter }; + setFilter(f); + if (notify) onFilterUpdate(f); + }, + [filter, onFilterUpdate], // Ensure dependencies are listed + ); + + const debouncedSearch = React.useMemo( + () => + debounce((query: string) => { + onFilterChange({ query }); + }, 300), + [], + ); + + const hasFilter = filter.query?.length || filter.sort?.length; + + return ( + <> +
+
+ + { + onFilterChange({ query: e.currentTarget.value }, false); + debouncedSearch(e.currentTarget.value); + }} + placeholder="Kategorie Suchen..." + /> +
+
+ + { + onFilterChange({ + sort: currentValue?.length ? currentValue : undefined, + }); + }} + className="w-full lg:max-w-64" + buttonProps={{ + size: "sm", + }} + /> +
+
+ + ); +} diff --git a/src/components/category/grid/infinite-category-grid.tsx b/src/components/category/grid/infinite-category-grid.tsx index 1f92bc6..ca76780 100644 --- a/src/components/category/grid/infinite-category-grid.tsx +++ b/src/components/category/grid/infinite-category-grid.tsx @@ -7,16 +7,14 @@ import CategoryCard from "../category-card"; import { useInfiniteItemsObserver } from "@/lib/hooks/infinite-items-observer-hook"; import { Skeleton } from "@/components/ui/skeleton"; import { CATEGORY_GRID_CLASS } from "./category-grid"; -// import ArticleFilterBar, { ArticleFilter } from "../article-filter-bar"; +import CategoryFilterBar, { CategoryFilter } from "../category-filter-bar"; export default function InfiniteCategoryGrid() { - // const [filter, setFilter] = React.useState( - // undefined, - // ); + const [filter, setFilter] = React.useState({}); const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } = api.category.getByCursor.useInfiniteQuery( { - // filter, + filter, }, { getNextPageParam: (lastPage) => lastPage.nextCursor, @@ -39,7 +37,7 @@ export default function InfiniteCategoryGrid() { return (
- {/* */} + {data?.pages?.length ? allItems.map((category, idx) => ( diff --git a/src/components/combobox.tsx b/src/components/combobox.tsx index b7534cd..d995fbd 100644 --- a/src/components/combobox.tsx +++ b/src/components/combobox.tsx @@ -49,7 +49,7 @@ export function Combobox({ }: ComboboxProps) { const [open, setOpen] = React.useState(false); const [value, setValue] = React.useState(initialValue ?? ""); - const selectedItem = data.find((item) => item.value === value)!; + const selectedItem = data.find((item) => item.value === initialValue)!; return ( diff --git a/src/components/sort-filter-select.tsx b/src/components/sort-filter-select.tsx new file mode 100644 index 0000000..59c3faf --- /dev/null +++ b/src/components/sort-filter-select.tsx @@ -0,0 +1,37 @@ +import React from "react"; +import { + ArrowDownAZ, + ArrowUpAz, + CalendarArrowDown, + CalendarArrowUp, + FilterIcon, + LucideIcon, +} from "lucide-react"; +import { Combobox, ComboboxProps } from "./combobox"; + +function SortFilterSelect(props: Partial) { + return ( + + ); +} + +export default SortFilterSelect; + +const sortItems: Array<{ + Icon: LucideIcon; + value: string; + label: string; +}> = [ + { Icon: CalendarArrowUp, value: "newest", label: "Neueste" }, + { Icon: CalendarArrowDown, value: "oldest", label: "Älteste" }, + { Icon: ArrowDownAZ, value: "abc", label: "Alphabetisch A-Z" }, + { Icon: ArrowUpAz, value: "cba", label: "Alphabetisch Z-A" }, +];