Skip to content

Commit

Permalink
feat(frontend): add search filter component
Browse files Browse the repository at this point in the history
  • Loading branch information
mmtftr committed May 14, 2024
1 parent e7b875f commit 68d7524
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 1 deletion.
9 changes: 9 additions & 0 deletions frontend/src/components/SearchBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ import { Button } from "./ui/button";
import { Input } from "./ui/input";
import { Search } from "lucide-react";
import { useId, useState } from "react";
import SearchFilterPopover from "./SearchFilterPopover";

export const SearchBar = () => {
const id = useId();
const [params] = useSearchParams();
const [search, setSearch] = useState(params.get("q") || "");
const [cuisine, setCuisine] = useState(params.get("cuisine") || "");
const [foodType, setFoodType] = useState(params.get("foodType") || "");

const navigate = useNavigate();

Expand All @@ -29,6 +32,12 @@ export const SearchBar = () => {
id={id}
name="search"
/>
<SearchFilterPopover
cuisine={cuisine}
setCuisine={setCuisine}
foodType={foodType}
setFoodType={setFoodType}
/>
<Button type="submit" className="gap-2">
<Search size={16} />
Search
Expand Down
71 changes: 71 additions & 0 deletions frontend/src/components/SearchFilterPopover.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { render, fireEvent, screen } from "@testing-library/react";
import SearchFilterPopover from "./SearchFilterPopover";
import { beforeEach, describe, expect, test, vi } from "vitest";

describe("SearchFilterPopover", () => {
let cuisine: string,
setCuisine: (s: string) => void,
foodType: string,
setFoodType: (s: string) => void;

beforeEach(() => {
// Arrange
cuisine = "";
setCuisine = vi.fn();
foodType = "";
setFoodType = vi.fn();
});

test("does not call set functions when closed without confirming", () => {
render(
<SearchFilterPopover
cuisine={cuisine}
setCuisine={setCuisine}
foodType={foodType}
setFoodType={setFoodType}
/>,
);

// Act
const filterButton = screen.getByRole("button", { name: /filter/i });
fireEvent.click(filterButton); // Open the popover

const italianCheckbox = screen.getByLabelText(/italian/i);
fireEvent.click(italianCheckbox);

const popoverCloseButton = screen.getByRole("button", { name: /close/i });
fireEvent.click(popoverCloseButton); // Close the popover without confirming

// Assert
expect(setCuisine).not.toHaveBeenCalled();
expect(setFoodType).not.toHaveBeenCalled();
});

test("calls set functions with correct values when confirmed", () => {
render(
<SearchFilterPopover
cuisine={cuisine}
setCuisine={setCuisine}
foodType={foodType}
setFoodType={setFoodType}
/>,
);

// Act
const filterButton = screen.getByRole("button", { name: /filter/i });
fireEvent.click(filterButton); // Open the popover

const italianCheckbox = screen.getByLabelText(/italian/i);
fireEvent.click(italianCheckbox);

const meatCheckbox = screen.getByLabelText(/meat/i);
fireEvent.click(meatCheckbox);

const confirmButton = screen.getByRole("button", { name: /confirm/i });
fireEvent.click(confirmButton); // Confirm the selection

// Assert
expect(setCuisine).toHaveBeenCalledWith("Italian");
expect(setFoodType).toHaveBeenCalledWith("Meat");
});
});
122 changes: 122 additions & 0 deletions frontend/src/components/SearchFilterPopover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import Filter from "@/assets/Icon/General/Filter.svg";
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
import FilterCheckbox from "./FilterCheckbox";
import { Button } from "./ui/button";
import { useState } from "react";
import { PopoverClose } from "@radix-ui/react-popover";
import { XIcon } from "lucide-react";

export default function SearchFilterPopover({
cuisine,
setCuisine,
foodType,
setFoodType,
}: {
cuisine: string;
setCuisine: (cuisine: string) => void;
foodType: string;
setFoodType: (foodType: string) => void;
}) {
const [tempCuisine, setTempCuisine] = useState(cuisine);
const [tempFoodType, setTempFoodType] = useState(foodType);

return (
<Popover
onOpenChange={(open) => {
if (!open) {
setTempCuisine(cuisine);
setTempFoodType(foodType);
}
}}
>
<PopoverTrigger asChild>
<Button
size="icon"
variant={!!cuisine || !!foodType ? "default" : "outline"}
aria-label="Filter"
className="flex-shrink-0"
>
<img src={Filter} className="h-6 w-6" />
</Button>
</PopoverTrigger>
<PopoverContent
align="end"
className="flex w-80 flex-col gap-4 px-6 py-8"
>
<PopoverClose aria-label="Close" className="absolute right-4 top-4">
<XIcon />
</PopoverClose>
<h4 className="text-2xl font-bold">Filter</h4>
<div className="flex flex-col gap-3">
<h5 className="text-xl font-semibold">Cuisine</h5>
<div className="flex flex-wrap gap-3 gap-y-2">
<FilterCheckbox
label="Italian"
checked={tempCuisine === "Italian"}
onChange={(e) =>
setTempCuisine(e.target.checked ? "Italian" : "")
}
/>
<FilterCheckbox
label="Chinese"
checked={tempCuisine === "Chinese"}
onChange={(e) =>
setTempCuisine(e.target.checked ? "Chinese" : "")
}
/>
<FilterCheckbox
label="Japanese"
checked={tempCuisine === "Japanese"}
onChange={(e) =>
setTempCuisine(e.target.checked ? "Japanese" : "")
}
/>
<FilterCheckbox
label="Turkish"
checked={tempCuisine === "Turkish"}
onChange={(e) =>
setTempCuisine(e.target.checked ? "Turkish" : "")
}
/>
</div>
</div>
<div className="flex flex-col gap-3">
<h5 className="text-xl font-semibold">Type of Food</h5>
<div className="flex flex-wrap gap-3 gap-y-2">
<FilterCheckbox
label="Meat"
checked={tempFoodType === "Meat"}
onChange={(e) => setTempFoodType(e.target.checked ? "Meat" : "")}
/>
<FilterCheckbox
label="Baked"
checked={tempFoodType === "Baked"}
onChange={(e) => setTempFoodType(e.target.checked ? "Baked" : "")}
/>
<FilterCheckbox
label="Dairy"
checked={tempFoodType === "Dairy"}
onChange={(e) => setTempFoodType(e.target.checked ? "Dairy" : "")}
/>
<FilterCheckbox
label="Eggs"
checked={tempFoodType === "Eggs"}
onChange={(e) => setTempFoodType(e.target.checked ? "Eggs" : "")}
/>
</div>
</div>
<PopoverClose asChild>
<Button
onClick={() => {
setCuisine(tempCuisine);
setFoodType(tempFoodType);
}}
className="self-end"
>
Confirm
</Button>
</PopoverClose>
</PopoverContent>
</Popover>
);
}
4 changes: 3 additions & 1 deletion frontend/src/components/ui/popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import * as PopoverPrimitive from "@radix-ui/react-popover";

import { cn } from "@/lib/utils";

const PopoverArrow = PopoverPrimitive.Arrow;

const Popover = PopoverPrimitive.Root;

const PopoverTrigger = PopoverPrimitive.Trigger;
Expand All @@ -26,4 +28,4 @@ const PopoverContent = React.forwardRef<
));
PopoverContent.displayName = PopoverPrimitive.Content.displayName;

export { Popover, PopoverTrigger, PopoverContent };
export { PopoverArrow, Popover, PopoverTrigger, PopoverContent };

0 comments on commit 68d7524

Please sign in to comment.