From 37f9102d801b6b96844702dbb654008c1914600c Mon Sep 17 00:00:00 2001 From: Kai Chappell Date: Fri, 30 May 2025 19:27:31 +0100 Subject: [PATCH] feat(frontend): add question search --- frontend/src/app/questions/page.tsx | 3 +- .../components/questions/question-filters.tsx | 161 ++++++++++++++++++ 2 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 frontend/src/components/questions/question-filters.tsx diff --git a/frontend/src/app/questions/page.tsx b/frontend/src/app/questions/page.tsx index 0a02f25..f1188d9 100644 --- a/frontend/src/app/questions/page.tsx +++ b/frontend/src/app/questions/page.tsx @@ -94,6 +94,7 @@ export default async function QuestionsPage({ patterns={patternsResponse.items} currentCategory={params.category} currentPattern={params.pattern} + currentSearch={params.search} /> @@ -118,7 +119,7 @@ export default async function QuestionsPage({ params.difficulty ? `&difficulty=${params.difficulty}` : "" }${params.category ? `&category=${params.category}` : ""}${ params.pattern ? `&pattern=${params.pattern}` : "" - }`} + }${params.search ? `&search=${encodeURIComponent(params.search)}` : ""}`} className={`px-3 py-1 rounded ${ page === questionsResponse.page ? "bg-[var(--primary)] text-[var(--primary-foreground)]" diff --git a/frontend/src/components/questions/question-filters.tsx b/frontend/src/components/questions/question-filters.tsx new file mode 100644 index 0000000..9c0a458 --- /dev/null +++ b/frontend/src/components/questions/question-filters.tsx @@ -0,0 +1,161 @@ +"use client"; + +import { useRouter, useSearchParams } from "next/navigation"; +import { useCallback, useState, useEffect, useRef } from "react"; +import { Search } from "lucide-react"; +import type { Category, Pattern } from "@/types"; + +interface QuestionFiltersProps { + categories: Category[]; + patterns: Pattern[]; + currentCategory?: string; + currentPattern?: string; + currentSearch?: string; +} + +export function QuestionFilters({ + categories, + patterns, + currentCategory, + currentPattern, + currentSearch, +}: QuestionFiltersProps) { + const router = useRouter(); + const searchParams = useSearchParams(); + const [searchValue, setSearchValue] = useState(currentSearch || ""); + const debounceRef = useRef(null); + + // Update local state when URL changes + useEffect(() => { + setSearchValue(currentSearch || ""); + }, [currentSearch]); + + const handleSearchChange = useCallback( + (e: React.ChangeEvent) => { + const value = e.target.value; + setSearchValue(value); + + // Debounce the URL update + if (debounceRef.current) { + clearTimeout(debounceRef.current); + } + + debounceRef.current = setTimeout(() => { + const params = new URLSearchParams(searchParams.toString()); + if (value.trim()) { + params.set("search", value.trim()); + } else { + params.delete("search"); + } + params.delete("page"); + router.push(`/questions?${params.toString()}`); + }, 300); + }, + [router, searchParams] + ); + + // Cleanup timeout on unmount + useEffect(() => { + return () => { + if (debounceRef.current) { + clearTimeout(debounceRef.current); + } + }; + }, []); + + const handleCategoryChange = useCallback( + (e: React.ChangeEvent) => { + const params = new URLSearchParams(searchParams.toString()); + if (e.target.value) { + params.set("category", e.target.value); + } else { + params.delete("category"); + } + params.delete("page"); + router.push(`/questions?${params.toString()}`); + }, + [router, searchParams] + ); + + const handlePatternChange = useCallback( + (e: React.ChangeEvent) => { + const params = new URLSearchParams(searchParams.toString()); + if (e.target.value) { + params.set("pattern", e.target.value); + } else { + params.delete("pattern"); + } + params.delete("page"); + router.push(`/questions?${params.toString()}`); + }, + [router, searchParams] + ); + + return ( + <> +
+ +
+ + +
+
+ +
+ + +
+ +
+ + +
+ + ); +}