From 4b121834fcff0dee9e1af13447e55c93d6d70402 Mon Sep 17 00:00:00 2001 From: sahil-singh1107 Date: Sun, 21 Sep 2025 23:32:54 +0530 Subject: [PATCH] improved searching for tracks and problems --- apps/web/components/ContentSearch.tsx | 192 +++++++++++++++++++------- 1 file changed, 145 insertions(+), 47 deletions(-) diff --git a/apps/web/components/ContentSearch.tsx b/apps/web/components/ContentSearch.tsx index b8f8261d..5a8ec4d7 100644 --- a/apps/web/components/ContentSearch.tsx +++ b/apps/web/components/ContentSearch.tsx @@ -1,8 +1,20 @@ "use client"; import { useEffect, useRef, useState, useDeferredValue } from "react"; import Link from "next/link"; -import { Cross2Icon, MagnifyingGlassIcon } from "@radix-ui/react-icons"; -import { Dialog, DialogClose, DialogContent, Input, Card, CardDescription, CardHeader, CardTitle } from "@repo/ui"; +import { CheckIcon, CopyIcon, Cross2Icon, MagnifyingGlassIcon } from "@radix-ui/react-icons"; +import { + Dialog, + DialogClose, + DialogContent, + Input, + Card, + CardDescription, + CardHeader, + CardTitle, + Tabs, + TabsContent, + TabsList, TabsTrigger, Button, +} from "@repo/ui"; /* import { getSearchResults } from "../lib/search"; import Image from "next/image"; */ import Fuse from "fuse.js"; @@ -23,11 +35,16 @@ interface DataItem { export function ContentSearch({ tracks }: { tracks: TrackPros[] }) { const [dialogOpen, setDialogOpen] = useState(false); const [input, setInput] = useState(""); + const [searchProblems, setSearchProblems] = useState([]); const [searchTracks, setSearchTracks] = useState([]); const [selectedIndex, setSelectedIndex] = useState(-1); const scrollableContainerRef = useRef(null); const deferredInput = useDeferredValue(input); const [allTracks, setAllTracks] = useState([]); + const [activeTab, setActiveTab] = useState("tracks"); + const [copiedId, setCopiedId] = useState(null); + + useEffect(() => { const updatedTracks: DataItem[] = []; tracks.map((t) => { @@ -45,23 +62,30 @@ export function ContentSearch({ tracks }: { tracks: TrackPros[] }) { }); setAllTracks(updatedTracks); }, []); + useEffect(() => { - const fuse = new Fuse(allTracks, { + const fuseProblems = new Fuse(allTracks, { keys: ["payload.problemTitle"], + threshold: 0.5, + ignoreLocation: true, }); - async function fetchSearchResults() { - if (deferredInput.length > 0) { - /* const data = await getSearchResults(deferredInput); */ - const data = fuse.search(deferredInput); - const items = data.map((result) => result.item); - setSearchTracks(items); - } else { - setSearchTracks([]); - } + const fuseTracks = new Fuse(allTracks, { + keys: ["payload.trackTitle"], + threshold: 0.5, + ignoreLocation: true, + }); + + if (deferredInput.length > 0) { + const problemResults = fuseProblems.search(deferredInput); + setSearchProblems(problemResults.map(problemResult => problemResult.item)); + const tracksResults = fuseTracks.search(deferredInput) + setSearchTracks(tracksResults.map(tracksResult => tracksResult.item).filter((item, index,self) => self.findIndex(i => i.payload.trackId === item.payload.trackId) === index)) + } else { + setSearchProblems([]); + setSearchTracks([]); } - fetchSearchResults(); - }, [deferredInput]); + }, [deferredInput, allTracks]); useEffect(() => { const handleKeyPress = (event: KeyboardEvent) => { @@ -74,17 +98,22 @@ export function ContentSearch({ tracks }: { tracks: TrackPros[] }) { break; case "ArrowDown": event.preventDefault(); - setSelectedIndex((prevIndex) => (prevIndex + 1) % searchTracks.length); + setSelectedIndex((prevIndex) => (prevIndex + 1) % searchProblems.length); break; case "ArrowUp": event.preventDefault(); - setSelectedIndex((prevIndex) => (prevIndex - 1 + searchTracks.length) % searchTracks.length); + setSelectedIndex((prevIndex) => (prevIndex - 1 + searchProblems.length) % searchProblems.length); break; case "Enter": if (selectedIndex !== -1) { event.preventDefault(); - const selectedTrack = searchTracks[selectedIndex]; - window.open(`/tracks/${selectedTrack?.payload.trackId}/${selectedTrack?.payload.problemId}`, "_blank"); + if (activeTab === "problems") { + const selectedProblem = searchProblems[selectedIndex]; + window.open(`/tracks/${selectedProblem?.payload.trackId}/${selectedProblem?.payload.problemId}`, "_blank"); + } else { + const selectedTrack = searchTracks[selectedIndex]; + window.open(`/tracks/${selectedTrack?.payload.trackId}/${selectedTrack?.payload.problemId}`, "_blank"); + } } break; default: @@ -94,7 +123,7 @@ export function ContentSearch({ tracks }: { tracks: TrackPros[] }) { window.addEventListener("keydown", handleKeyPress); return () => window.removeEventListener("keydown", handleKeyPress); - }, [searchTracks, selectedIndex]); + }, [searchProblems, selectedIndex]); useEffect(() => { if (selectedIndex !== -1 && scrollableContainerRef.current) { @@ -109,9 +138,19 @@ export function ContentSearch({ tracks }: { tracks: TrackPros[] }) { if (!open) { setDialogOpen(false); setInput(""); + setSearchProblems([]); + setSearchTracks([]); } }; + const handleCopy = (e: any, trackId : string, problemId : string) => { + e.preventDefault() + const id = `${trackId}-${problemId}`; + navigator.clipboard.writeText(`${window.location.href}/tracks/${trackId}/${problemId}`); + setCopiedId(id); + setTimeout(() => setCopiedId(null), 1500); + } + return (
Close
-
- {searchTracks.length > 0 && - searchTracks.map((track, index) => ( -
- - -
-
- {track.payload.problemTitle} + + + + Tracks + Problems + + + {searchProblems.length > 0 ? ( + searchProblems.map((track, index) => ( +
+ + +
+
+ {track.payload.problemTitle} +
+
+ + {track.payload.problemTitle} + Track: {track.payload.trackTitle} + +
+
-
- - {track.payload.problemTitle} - Track: {track.payload.trackTitle} - + + +
+ ))) : ( +
+ No problems found +
+ )} + + + + {searchTracks.length > 0 ? ( + searchTracks.map((track, index) => ( +
+ + +
+
+ {track.payload.problemTitle} +
+
+ + Track: {track.payload.trackTitle} + + +
+ +
-
-
- + + +
+ ))) : ( +
+ No tracks found
- ))} -
+ )} + + +
);