Skip to content

Commit abca4c8

Browse files
Merge pull request #8 from abhitrueprogrammer/master
Added filter and search bar on catalogue page. Other minor bug fixes
2 parents 227bd1c + 2f7ddda commit abca4c8

File tree

10 files changed

+1486
-115
lines changed

10 files changed

+1486
-115
lines changed

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,17 @@
1313
"@ilovepdf/ilovepdf-nodejs": "^0.2.6",
1414
"@radix-ui/react-dialog": "^1.1.2",
1515
"@radix-ui/react-icons": "^1.3.0",
16+
"@radix-ui/react-popover": "^1.1.2",
17+
"@radix-ui/react-separator": "^1.1.0",
18+
"@radix-ui/react-slot": "^1.1.0",
1619
"@t3-oss/env-nextjs": "^0.10.1",
1720
"@types/mongoose": "^5.11.97",
1821
"axios": "^1.7.2",
1922
"bcrypt": "^5.1.1",
2023
"class-variance-authority": "^0.7.0",
2124
"cloudinary": "^2.2.0",
2225
"clsx": "^2.1.1",
26+
"cmdk": "1.0.0",
2327
"cryptr": "^6.3.0",
2428
"debounce": "^2.1.1",
2529
"file-saver": "^2.0.5",

pnpm-lock.yaml

Lines changed: 640 additions & 70 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app/catalogue/page.tsx

Lines changed: 148 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import axios, { type AxiosError } from "axios";
66
import Cryptr from "cryptr";
77
import { Suspense } from "react";
88
import Image from "next/image";
9-
import { Download, Eye } from "lucide-react";
9+
import { Search, Download, Eye, Filter} from "lucide-react";
1010
import { boolean } from "zod";
1111
import {
1212
Dialog,
@@ -16,6 +16,11 @@ import {
1616
DialogTitle,
1717
DialogTrigger,
1818
} from "@/components/ui/dialog";
19+
20+
import { MultiSelect } from "@/components/multi-select";
21+
import { Button } from "@/components/ui/button";
22+
import { slots } from "../upload/select_options";
23+
1924
interface Paper {
2025
_id: string;
2126
exam: string;
@@ -25,6 +30,7 @@ interface Paper {
2530
subject: string;
2631
year: string;
2732
}
33+
2834
interface Filters {
2935
paper: Paper;
3036
uniqueExams: string[];
@@ -39,16 +45,22 @@ const CatalogueContent = () => {
3945
const router = useRouter();
4046
const searchParams = useSearchParams();
4147
const subject = searchParams.get("subject");
48+
const exams = searchParams.get("exams")?.split(",");
49+
const slots = searchParams.get("slots")?.split(",");
50+
const years = searchParams.get("years")?.split(",");
51+
4252
const [papers, setPapers] = useState<Paper[]>([]);
4353
const [error, setError] = useState<string | null>(null);
4454
const [loading, setLoading] = useState<boolean>(false);
45-
const [filterOptions, setFilterOptions] = useState<Filters[]>([]);
55+
const [filterOptions, setFilterOptions] = useState<Filters>();
56+
4657
useEffect(() => {
4758
if (subject) {
4859
const fetchPapers = async () => {
4960
setLoading(true);
5061

5162
try {
63+
console.log(subject)
5264
const papersResponse = await axios.get("/api/papers", {
5365
// Digital Logic and Microprocessors[BITE202L]
5466
params: { subject },
@@ -60,55 +72,70 @@ const CatalogueContent = () => {
6072
const papersData: Paper[] = JSON.parse(
6173
decryptedPapersResponse,
6274
).papers;
63-
const filters: Filters[] = JSON.parse(decryptedPapersResponse);
75+
const filters: Filters = JSON.parse(decryptedPapersResponse);
6476
setFilterOptions(filters);
77+
const papersDataWithFilters = papersData
78+
.filter((paper)=>{
79+
const examCondition = exams && exams.length ? exams.includes(paper.exam) : true;
80+
const slotCondition = slots && slots.length ? slots.includes(paper.slot) : true;
81+
const yearCondition = years && years.length ? years.includes(paper.year) : true;
82+
83+
return examCondition && slotCondition && yearCondition;
84+
})
85+
86+
if(papersDataWithFilters.length > 0)
87+
// setPapers(papersData);
88+
89+
setPapers(papersDataWithFilters);
90+
else
6591
setPapers(papersData);
66-
} catch (error) {
67-
if (axios.isAxiosError(error)) {
68-
const axiosError = error as AxiosError<{ message?: string }>;
69-
const errorMessage =
70-
axiosError.response?.data?.message ?? "Error fetching papers";
71-
setError(errorMessage);
72-
} else {
73-
setError("Error fetching papers");
74-
}
75-
} finally {
76-
setLoading(false);
92+
} catch (error) {
93+
if (axios.isAxiosError(error)) {
94+
const axiosError = error as AxiosError<{ message?: string }>;
95+
const errorMessage =
96+
axiosError.response?.data?.message ?? "Error fetching papers";
97+
setError(errorMessage);
98+
} else {
99+
setError("Error fetching papers");
77100
}
78-
};
79-
80-
void fetchPapers();
101+
} finally {
102+
setLoading(false);
103+
}
104+
};
105+
106+
void fetchPapers();
81107
}
82-
}, [subject]);
108+
}, [subject, searchParams]);
83109
// console.log(papers);
84-
110+
85111
return (
86112
<div className="min-h-screen bg-gray-50 p-8">
87-
<button
88-
onClick={() => router.push("/")}
89-
className="mb-4 rounded-md bg-blue-500 px-4 py-2 text-white"
90-
>
91-
Back to Search
92-
</button>
93-
<Dialog>
94-
<DialogTrigger className=" rounded-lg bg-[#7480FF] px-8 py-3 text-white">
95-
Filter
96-
</DialogTrigger>
97-
<DialogContent>
98-
<DialogHeader>
99-
<DialogTitle>Choose your filters</DialogTitle>
100-
<DialogDescription>{}</DialogDescription>
101-
</DialogHeader>
102-
</DialogContent>
103-
</Dialog>
104-
113+
<div className="flex mb-4 mx-40 justify-center gap-10 items-center">
114+
{/* <button
115+
onClick={() => router.push("/")}
116+
className=" rounded-md bg-blue-500 px-4 py-2 text-white"
117+
>
118+
Back to Search
119+
</button> */}
120+
<div className="relative w-full">
121+
<div className="absolute flex-grow flex top-3 left-2">
122+
<Search className="w-5 text-gray-400" />
123+
</div>
124+
<input
125+
type="search"
126+
className="border w-full px-10 py-3 bg-[#7480FF33] bg-opacity-20 rounded-2xl "
127+
placeholder="Search..."
128+
></input>
129+
</div>
130+
{ subject && filterOptions && <FilterDialog subject={subject} filterOptions={filterOptions}/>}
131+
</div>
105132
{/* <h1 className="mb-4 text-2xl font-bold">Papers for {subject}</h1> */}
106133
{error && <p className="text-red-500">{error}</p>}
107134

108135
{loading ? (
109136
<p>Loading papers...</p>
110137
) : papers.length > 0 ? (
111-
<div className="grid grid-cols-5 gap-10">
138+
<div className="flex flex-wrap gap-10">
112139
{papers.map((paper) => (
113140
<Card key={paper._id} paper={paper} />
114141
))}
@@ -134,11 +161,11 @@ function Card({ paper }: { paper: Paper }) {
134161
function handleCheckboxChange(): void {
135162
setChecked(!checked);
136163
}
137-
164+
138165
return (
139166
<div
140-
key={paper._id}
141-
className={`space-y-1 rounded-md border border-black border-opacity-50 ${checked ? "bg-[#EEF2FF]" : "bg-white"} p-4 `}
167+
key={paper._id}
168+
className={`space-y-1 w-56 rounded-md border border-black border-opacity-50 ${checked ? "bg-[#EEF2FF]" : "bg-white"} p-4 `}
142169
>
143170
<Image
144171
src={paper.thumbnailUrl}
@@ -147,7 +174,7 @@ function Card({ paper }: { paper: Paper }) {
147174
width={320} // Adjust width to maintain aspect ratio if needed
148175
height={180} // Fixed height
149176
className="mb-2 h-[180px] w-full object-cover" // Ensure it takes the full width of the container
150-
></Image>
177+
></Image>
151178
<div className="text-sm font-medium">
152179
{extractBracketContent(paper.subject)}
153180
</div>
@@ -166,7 +193,7 @@ function Card({ paper }: { paper: Paper }) {
166193
onChange={handleCheckboxChange}
167194
className="h-3 w-3 rounded-lg"
168195
type="checkbox"
169-
/>
196+
/>
170197
<p className="text-sm">Select</p>
171198
</div>
172199
<div className="flex gap-2">
@@ -175,7 +202,7 @@ function Card({ paper }: { paper: Paper }) {
175202
target="_blank"
176203
rel="noopener noreferrer"
177204
// className="text-blue-500 hover:underline"
178-
>
205+
>
179206
<Eye />
180207
</a>
181208
<a href={paper.finalUrl} download>
@@ -192,6 +219,82 @@ function capsule(data: string) {
192219
<div className=" rounded-md bg-[#7480FF] p-1 px-3 text-sm">{data}</div>
193220
);
194221
}
222+
const FilterDialog = ({subject, filterOptions}: {subject:string, filterOptions: Filters}) => {
223+
const router = useRouter()
224+
const [filterExams, setuniqueExams] = useState<string[]>();
225+
const [FilterSlots, setFilterSlots] = useState<string[]>();
226+
const [filterYears, setFilterYears] = useState<string[]>();
227+
const handleFilterClick = () => {
228+
if (subject) {
229+
let pushContent = "/catalogue"
230+
if(subject)
231+
{
232+
pushContent = pushContent.concat(`?subject=${encodeURIComponent(subject)}`)
233+
}
234+
if(filterExams)
235+
{
236+
pushContent = pushContent.concat(`&exams=${encodeURIComponent(filterExams.join(','))}`)
237+
}
238+
if(FilterSlots)
239+
{
240+
pushContent= pushContent.concat(`&slots=${encodeURIComponent(FilterSlots.join(','))}`)
241+
}
242+
if(filterYears)
243+
{
244+
pushContent = pushContent.concat(`&years=${encodeURIComponent(filterYears.join(','))}`)
245+
246+
}
247+
router.push(pushContent);
248+
}
249+
};
250+
251+
return (
252+
<Dialog>
253+
<DialogTrigger className="rounded-lg bg-[#7480FF] px-8 py-3 text-white">
254+
<div className="flex gap-3">Filter <Filter className=""/></div>
255+
</DialogTrigger>
256+
<DialogContent>
257+
<DialogHeader>
258+
<DialogTitle className="mb-5">Choose your filters</DialogTitle>
259+
<DialogDescription className="space-y-5">
260+
{filterOptions && (
261+
<div className="space-y-5">
262+
<MultiSelect
263+
options={filterOptions.uniqueExams.map((exam: string) => ({
264+
label: exam,
265+
value: exam,
266+
}))}
267+
onValueChange={setuniqueExams}
268+
placeholder="Exam"
269+
/>
270+
<MultiSelect
271+
options={filterOptions.uniqueSlots.map((slots: string) => ({
272+
label: slots,
273+
value: slots,
274+
}))}
275+
onValueChange={setFilterSlots}
276+
placeholder="Slots"
277+
/>
278+
<MultiSelect
279+
options={filterOptions.uniqueYears.map((years: string) => ({
280+
label: years,
281+
value: years,
282+
}))}
283+
onValueChange={setFilterYears}
284+
placeholder="Years"
285+
/>
286+
</div>
287+
)}
288+
<Button variant="outline" onClick={handleFilterClick}>
289+
Filter
290+
</Button>
291+
</DialogDescription>
292+
</DialogHeader>
293+
</DialogContent>
294+
</Dialog>
295+
);
296+
};
297+
195298

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

src/app/components/searchbar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ const SearchBar = () => {
5454
setSearchText(suggestion);
5555
setSuggestions([]);
5656
// console.log(encodeURI(suggestion))
57-
router.push(`/catalogue?subject=${encodeURIComponent(suggestion).replace(/%20/g, '+').replace(/%5B/g, '[').replace(/%5D/g, ']')}`);
57+
router.push(`/catalogue?subject=${encodeURIComponent(suggestion)}`);
5858
};
5959

6060
return (

0 commit comments

Comments
 (0)