Skip to content

Commit

Permalink
Merge pull request #8 from abhitrueprogrammer/master
Browse files Browse the repository at this point in the history
Added filter and search bar on catalogue page. Other minor bug fixes
  • Loading branch information
NishantGupt786 authored Oct 6, 2024
2 parents 227bd1c + 2f7ddda commit abca4c8
Show file tree
Hide file tree
Showing 10 changed files with 1,486 additions and 115 deletions.
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,17 @@
"@ilovepdf/ilovepdf-nodejs": "^0.2.6",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@t3-oss/env-nextjs": "^0.10.1",
"@types/mongoose": "^5.11.97",
"axios": "^1.7.2",
"bcrypt": "^5.1.1",
"class-variance-authority": "^0.7.0",
"cloudinary": "^2.2.0",
"clsx": "^2.1.1",
"cmdk": "1.0.0",
"cryptr": "^6.3.0",
"debounce": "^2.1.1",
"file-saver": "^2.0.5",
Expand Down
710 changes: 640 additions & 70 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

192 changes: 148 additions & 44 deletions src/app/catalogue/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import axios, { type AxiosError } from "axios";
import Cryptr from "cryptr";
import { Suspense } from "react";
import Image from "next/image";
import { Download, Eye } from "lucide-react";
import { Search, Download, Eye, Filter} from "lucide-react";
import { boolean } from "zod";
import {
Dialog,
Expand All @@ -16,6 +16,11 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";

import { MultiSelect } from "@/components/multi-select";
import { Button } from "@/components/ui/button";
import { slots } from "../upload/select_options";

interface Paper {
_id: string;
exam: string;
Expand All @@ -25,6 +30,7 @@ interface Paper {
subject: string;
year: string;
}

interface Filters {
paper: Paper;
uniqueExams: string[];
Expand All @@ -39,16 +45,22 @@ const CatalogueContent = () => {
const router = useRouter();
const searchParams = useSearchParams();
const subject = searchParams.get("subject");
const exams = searchParams.get("exams")?.split(",");
const slots = searchParams.get("slots")?.split(",");
const years = searchParams.get("years")?.split(",");

const [papers, setPapers] = useState<Paper[]>([]);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const [filterOptions, setFilterOptions] = useState<Filters[]>([]);
const [filterOptions, setFilterOptions] = useState<Filters>();

useEffect(() => {
if (subject) {
const fetchPapers = async () => {
setLoading(true);

try {
console.log(subject)
const papersResponse = await axios.get("/api/papers", {
// Digital Logic and Microprocessors[BITE202L]
params: { subject },
Expand All @@ -60,55 +72,70 @@ const CatalogueContent = () => {
const papersData: Paper[] = JSON.parse(
decryptedPapersResponse,
).papers;
const filters: Filters[] = JSON.parse(decryptedPapersResponse);
const filters: Filters = JSON.parse(decryptedPapersResponse);
setFilterOptions(filters);
const papersDataWithFilters = papersData
.filter((paper)=>{
const examCondition = exams && exams.length ? exams.includes(paper.exam) : true;
const slotCondition = slots && slots.length ? slots.includes(paper.slot) : true;
const yearCondition = years && years.length ? years.includes(paper.year) : true;

return examCondition && slotCondition && yearCondition;
})

if(papersDataWithFilters.length > 0)
// setPapers(papersData);

setPapers(papersDataWithFilters);
else
setPapers(papersData);
} catch (error) {
if (axios.isAxiosError(error)) {
const axiosError = error as AxiosError<{ message?: string }>;
const errorMessage =
axiosError.response?.data?.message ?? "Error fetching papers";
setError(errorMessage);
} else {
setError("Error fetching papers");
}
} finally {
setLoading(false);
} catch (error) {
if (axios.isAxiosError(error)) {
const axiosError = error as AxiosError<{ message?: string }>;
const errorMessage =
axiosError.response?.data?.message ?? "Error fetching papers";
setError(errorMessage);
} else {
setError("Error fetching papers");
}
};

void fetchPapers();
} finally {
setLoading(false);
}
};

void fetchPapers();
}
}, [subject]);
}, [subject, searchParams]);
// console.log(papers);

return (
<div className="min-h-screen bg-gray-50 p-8">
<button
onClick={() => router.push("/")}
className="mb-4 rounded-md bg-blue-500 px-4 py-2 text-white"
>
Back to Search
</button>
<Dialog>
<DialogTrigger className=" rounded-lg bg-[#7480FF] px-8 py-3 text-white">
Filter
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Choose your filters</DialogTitle>
<DialogDescription>{}</DialogDescription>
</DialogHeader>
</DialogContent>
</Dialog>

<div className="flex mb-4 mx-40 justify-center gap-10 items-center">
{/* <button
onClick={() => router.push("/")}
className=" rounded-md bg-blue-500 px-4 py-2 text-white"
>
Back to Search
</button> */}
<div className="relative w-full">
<div className="absolute flex-grow flex top-3 left-2">
<Search className="w-5 text-gray-400" />
</div>
<input
type="search"
className="border w-full px-10 py-3 bg-[#7480FF33] bg-opacity-20 rounded-2xl "
placeholder="Search..."
></input>
</div>
{ subject && filterOptions && <FilterDialog subject={subject} filterOptions={filterOptions}/>}
</div>
{/* <h1 className="mb-4 text-2xl font-bold">Papers for {subject}</h1> */}
{error && <p className="text-red-500">{error}</p>}

{loading ? (
<p>Loading papers...</p>
) : papers.length > 0 ? (
<div className="grid grid-cols-5 gap-10">
<div className="flex flex-wrap gap-10">
{papers.map((paper) => (
<Card key={paper._id} paper={paper} />
))}
Expand All @@ -134,11 +161,11 @@ function Card({ paper }: { paper: Paper }) {
function handleCheckboxChange(): void {
setChecked(!checked);
}

return (
<div
key={paper._id}
className={`space-y-1 rounded-md border border-black border-opacity-50 ${checked ? "bg-[#EEF2FF]" : "bg-white"} p-4 `}
key={paper._id}
className={`space-y-1 w-56 rounded-md border border-black border-opacity-50 ${checked ? "bg-[#EEF2FF]" : "bg-white"} p-4 `}
>
<Image
src={paper.thumbnailUrl}
Expand All @@ -147,7 +174,7 @@ function Card({ paper }: { paper: Paper }) {
width={320} // Adjust width to maintain aspect ratio if needed
height={180} // Fixed height
className="mb-2 h-[180px] w-full object-cover" // Ensure it takes the full width of the container
></Image>
></Image>
<div className="text-sm font-medium">
{extractBracketContent(paper.subject)}
</div>
Expand All @@ -166,7 +193,7 @@ function Card({ paper }: { paper: Paper }) {
onChange={handleCheckboxChange}
className="h-3 w-3 rounded-lg"
type="checkbox"
/>
/>
<p className="text-sm">Select</p>
</div>
<div className="flex gap-2">
Expand All @@ -175,7 +202,7 @@ function Card({ paper }: { paper: Paper }) {
target="_blank"
rel="noopener noreferrer"
// className="text-blue-500 hover:underline"
>
>
<Eye />
</a>
<a href={paper.finalUrl} download>
Expand All @@ -192,6 +219,82 @@ function capsule(data: string) {
<div className=" rounded-md bg-[#7480FF] p-1 px-3 text-sm">{data}</div>
);
}
const FilterDialog = ({subject, filterOptions}: {subject:string, filterOptions: Filters}) => {
const router = useRouter()
const [filterExams, setuniqueExams] = useState<string[]>();
const [FilterSlots, setFilterSlots] = useState<string[]>();
const [filterYears, setFilterYears] = useState<string[]>();
const handleFilterClick = () => {
if (subject) {
let pushContent = "/catalogue"
if(subject)
{
pushContent = pushContent.concat(`?subject=${encodeURIComponent(subject)}`)
}
if(filterExams)
{
pushContent = pushContent.concat(`&exams=${encodeURIComponent(filterExams.join(','))}`)
}
if(FilterSlots)
{
pushContent= pushContent.concat(`&slots=${encodeURIComponent(FilterSlots.join(','))}`)
}
if(filterYears)
{
pushContent = pushContent.concat(`&years=${encodeURIComponent(filterYears.join(','))}`)

}
router.push(pushContent);
}
};

return (
<Dialog>
<DialogTrigger className="rounded-lg bg-[#7480FF] px-8 py-3 text-white">
<div className="flex gap-3">Filter <Filter className=""/></div>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle className="mb-5">Choose your filters</DialogTitle>
<DialogDescription className="space-y-5">
{filterOptions && (
<div className="space-y-5">
<MultiSelect
options={filterOptions.uniqueExams.map((exam: string) => ({
label: exam,
value: exam,
}))}
onValueChange={setuniqueExams}
placeholder="Exam"
/>
<MultiSelect
options={filterOptions.uniqueSlots.map((slots: string) => ({
label: slots,
value: slots,
}))}
onValueChange={setFilterSlots}
placeholder="Slots"
/>
<MultiSelect
options={filterOptions.uniqueYears.map((years: string) => ({
label: years,
value: years,
}))}
onValueChange={setFilterYears}
placeholder="Years"
/>
</div>
)}
<Button variant="outline" onClick={handleFilterClick}>
Filter
</Button>
</DialogDescription>
</DialogHeader>
</DialogContent>
</Dialog>
);
};


function extractBracketContent(subject: string): string | null {
const match = subject.match(/\[(.*?)\]/);
Expand All @@ -201,3 +304,4 @@ function extractBracketContent(subject: string): string | null {
function extractWithoutBracketContent(subject: string): string {
return subject.replace(/\s*\[.*?\]\s*/g, "").trim();
}

2 changes: 1 addition & 1 deletion src/app/components/searchbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const SearchBar = () => {
setSearchText(suggestion);
setSuggestions([]);
// console.log(encodeURI(suggestion))
router.push(`/catalogue?subject=${encodeURIComponent(suggestion).replace(/%20/g, '+').replace(/%5B/g, '[').replace(/%5D/g, ']')}`);
router.push(`/catalogue?subject=${encodeURIComponent(suggestion)}`);
};

return (
Expand Down
Loading

0 comments on commit abca4c8

Please sign in to comment.