diff --git a/apps/web/src/app/(authenticated)/knowledge/search/page.tsx b/apps/web/src/app/(authenticated)/knowledge/search/page.tsx new file mode 100644 index 0000000..9e128ea --- /dev/null +++ b/apps/web/src/app/(authenticated)/knowledge/search/page.tsx @@ -0,0 +1,133 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { useSearchParams, useRouter } from "next/navigation"; +import { SearchInput, SearchResults } from "@/components/search"; +import type { SearchFiltersState, SearchResult, Tag } from "@/components/search/types"; +import { apiGet } from "@/lib/api/client"; +import type { SearchResponse } from "@/components/search/types"; + +interface TagsResponse { + data: Tag[]; +} + +/** + * Knowledge search page + * Supports full-text search with filters for tags and status + */ +export default function SearchPage(): React.JSX.Element { + const searchParams = useSearchParams(); + const router = useRouter(); + + const [query, setQuery] = useState(searchParams.get("q") ?? ""); + const [results, setResults] = useState([]); + const [totalResults, setTotalResults] = useState(0); + const [isLoading, setIsLoading] = useState(false); + const [selectedTags, setSelectedTags] = useState([]); + const [selectedStatus, setSelectedStatus] = useState(); + const [availableTags, setAvailableTags] = useState([]); + + // Fetch available tags on mount + useEffect(() => { + const fetchTags = async (): Promise => { + try { + const response = await apiGet("/api/knowledge/tags"); + setAvailableTags(response.data); + } catch (error) { + console.error("Failed to fetch tags:", error); + } + }; + + void fetchTags(); + }, []); + + // Perform search when query changes + useEffect(() => { + const performSearch = async (): Promise => { + if (!query.trim()) { + setResults([]); + setTotalResults(0); + return; + } + + setIsLoading(true); + try { + // Build query params + const params = new URLSearchParams({ q: query }); + if (selectedStatus) { + params.append("status", selectedStatus); + } + if (selectedTags.length > 0) { + params.append("tags", selectedTags.join(",")); + } + + const response = await apiGet(`/api/knowledge/search?${params.toString()}`); + + setResults(response.data); + setTotalResults(response.pagination.total); + } catch (error) { + console.error("Search failed:", error); + setResults([]); + setTotalResults(0); + } finally { + setIsLoading(false); + } + }; + + void performSearch(); + }, [query, selectedTags, selectedStatus]); + + const handleSearch = (newQuery: string): void => { + setQuery(newQuery); + // Update URL with query + const params = new URLSearchParams({ q: newQuery }); + router.push(`/knowledge/search?${params.toString()}`); + }; + + const handleFilterChange = (filters: SearchFiltersState): void => { + setSelectedStatus(filters.status); + setSelectedTags(filters.tags ?? []); + }; + + return ( +
+ {/* Search header */} +
+
+

Search Knowledge Base

+ +
+
+ + {/* Results area */} + {query && ( +
+ +
+ )} + + {/* Empty state when no query */} + {!query && ( +
+
πŸ”
+

Search Your Knowledge

+

+ Enter a search term above to find entries in your knowledge base +

+
+

Tip: Press Cmd+K (or Ctrl+K) to quickly focus the search box

+
+
+ )} +
+ ); +} diff --git a/apps/web/src/components/layout/Navigation.tsx b/apps/web/src/components/layout/Navigation.tsx index 90990a9..e961e47 100644 --- a/apps/web/src/components/layout/Navigation.tsx +++ b/apps/web/src/components/layout/Navigation.tsx @@ -1,20 +1,38 @@ "use client"; -import { usePathname } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; import Link from "next/link"; import { useAuth } from "@/lib/auth/auth-context"; import { LogoutButton } from "@/components/auth/LogoutButton"; +import { useEffect } from "react"; export function Navigation(): React.JSX.Element { const pathname = usePathname(); + const router = useRouter(); const { user } = useAuth(); const navItems = [ { href: "/", label: "Dashboard" }, { href: "/tasks", label: "Tasks" }, { href: "/calendar", label: "Calendar" }, + { href: "/knowledge", label: "Knowledge" }, ]; + // Global keyboard shortcut for search (Cmd+K or Ctrl+K) + useEffect((): (() => void) => { + const handleKeyDown = (e: KeyboardEvent): void => { + if (e.key === "k" && (e.metaKey || e.ctrlKey)) { + e.preventDefault(); + router.push("/knowledge/search"); + } + }; + + document.addEventListener("keydown", handleKeyDown); + return () => { + document.removeEventListener("keydown", handleKeyDown); + }; + }, [router]); + return (