diff --git a/frontend/src/routes/_app/beans/index.tsx b/frontend/src/routes/_app/beans/index.tsx index d4c7c95..aa68f77 100644 --- a/frontend/src/routes/_app/beans/index.tsx +++ b/frontend/src/routes/_app/beans/index.tsx @@ -29,6 +29,7 @@ import { import { Skeleton } from "@/components/ui/skeleton" import { StarRating } from "@/components/ui/star-rating" import { canEdit, useCurrentUser } from "@/lib/auth" +import { ROAST_TYPES } from "@/lib/domain" import { humanize } from "@/lib/format" import { useCrudFeedback } from "@/lib/mutations" import { useMutation, useQuery } from "@tanstack/react-query" @@ -48,11 +49,20 @@ function BeansPage() { const [dialogOpen, setDialogOpen] = useState(false) const [deleting, setDeleting] = useState(null) const [roasterFilter, setRoasterFilter] = useState("all") + const [roastTypeFilter, setRoastTypeFilter] = useState("all") + const [originFilter, setOriginFilter] = useState("all") const [search, setSearch] = useState("") + // Only the origins actually in use, so the dropdown never offers an empty result. + const origins = [ + ...new Set((data ?? []).flatMap((bean) => (bean.origin_country ? [bean.origin_country] : []))), + ].sort() + const query = search.trim().toLowerCase() const filtered = (data ?? []) .filter((bean) => roasterFilter === "all" || String(bean.roaster.id) === roasterFilter) + .filter((bean) => roastTypeFilter === "all" || bean.roast_type === roastTypeFilter) + .filter((bean) => originFilter === "all" || bean.origin_country === originFilter) .filter( (bean) => query === "" || @@ -67,6 +77,15 @@ function BeansPage() { onError: feedback.onError, }) + const hasFilters = + roasterFilter !== "all" || roastTypeFilter !== "all" || originFilter !== "all" + + function clearFilters() { + setRoasterFilter("all") + setRoastTypeFilter("all") + setOriginFilter("all") + } + function openCreate() { setEditing(null) setDialogOpen(true) @@ -111,8 +130,36 @@ function BeansPage() { - {roasterFilter !== "all" ? ( - diff --git a/frontend/src/routes/_app/brews/index.tsx b/frontend/src/routes/_app/brews/index.tsx index fe12a4c..56eb662 100644 --- a/frontend/src/routes/_app/brews/index.tsx +++ b/frontend/src/routes/_app/brews/index.tsx @@ -39,7 +39,7 @@ import { formatDateTime, formatNumber, formatSeconds } from "@/lib/format" import { useCrudFeedback } from "@/lib/mutations" import { useMutation, useQuery } from "@tanstack/react-query" import { Link, createFileRoute } from "@tanstack/react-router" -import { Plus, X } from "lucide-react" +import { Plus, StarOff, User, X } from "lucide-react" import type { ReactNode } from "react" import { useState } from "react" @@ -64,6 +64,9 @@ function BrewsPage() { const [deleting, setDeleting] = useState(null) const [roasterFilter, setRoasterFilter] = useState("all") const [beanFilter, setBeanFilter] = useState("all") + const [methodFilter, setMethodFilter] = useState("all") + const [mineOnly, setMineOnly] = useState(false) + const [untastedOnly, setUntastedOnly] = useState(false) const [search, setSearch] = useState("") const remove = useMutation({ @@ -96,6 +99,9 @@ function BrewsPage() { return false } if (beanFilter !== "all" && String(brew.bean_id) !== beanFilter) return false + if (methodFilter !== "all" && String(brew.method_id) !== methodFilter) return false + if (mineOnly && brew.user_id !== user?.id) return false + if (untastedOnly && myTastingByBrew.has(brew.id)) return false if (query !== "") { const haystack = [ beanName(brew.bean_id), @@ -112,7 +118,12 @@ function BrewsPage() { }) .sort((a, b) => (b.brewed_at ?? "").localeCompare(a.brewed_at ?? "")) - const hasFilters = roasterFilter !== "all" || beanFilter !== "all" + const hasFilters = + roasterFilter !== "all" || + beanFilter !== "all" || + methodFilter !== "all" || + mineOnly || + untastedOnly function pickRoaster(value: string) { setRoasterFilter(value) @@ -122,6 +133,9 @@ function BrewsPage() { function clearFilters() { setRoasterFilter("all") setBeanFilter("all") + setMethodFilter("all") + setMineOnly(false) + setUntastedOnly(false) } return ( @@ -176,6 +190,40 @@ function BrewsPage() { className="w-56" /> + + + + + + {hasFilters ? (