Skip to content

Commit

Permalink
Merge pull request #130 from CS3219-AY2425S1/feature/question-service…
Browse files Browse the repository at this point in the history
…/filter-questions

Add filter questions
  • Loading branch information
wr1159 authored Sep 27, 2024
2 parents 710f1f1 + 71a71da commit 45ee483
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 10 deletions.
7 changes: 6 additions & 1 deletion frontend/app/app/questions/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import QuestionListing from "@/components/questions/questions-listing";
import { Suspense } from "react";

export default function QuestionListingPage() {
return <QuestionListing />;
return (
<Suspense>
<QuestionListing />
</Suspense>
);
}
74 changes: 74 additions & 0 deletions frontend/components/questions/question-filter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"use client";

import React from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";

interface QuestionFilterProps {
category: string;
onCategoryChange: (search: string) => void;
complexity: string;
onComplexityChange: (complexity: string) => void;
onReset: () => void;
}

const QuestionFilter: React.FC<QuestionFilterProps> = ({
category,
onCategoryChange,
complexity,
onComplexityChange,
onReset,
}) => {
return (
<Card className="mb-6">
<CardHeader>
<CardTitle>Filter Questions</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<Label htmlFor="category">Category</Label>
<Input
id="category"
value={category}
onChange={(e) => onCategoryChange(e.target.value)}
placeholder="Enter category"
className="mt-1"
/>
</div>
<div>
<Label htmlFor="complexity">Complexity</Label>
<Select
value={complexity}
onValueChange={(value) => onComplexityChange(value)}
>
<SelectTrigger id="complexity" className="mt-1">
<SelectValue placeholder="Select complexity" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All</SelectItem>
<SelectItem value="easy">Easy</SelectItem>
<SelectItem value="medium">Medium</SelectItem>
<SelectItem value="hard">Hard</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<Button onClick={onReset} variant="outline" className="mt-4">
Reset Filters
</Button>
</CardContent>
</Card>
);
};

export default QuestionFilter;
57 changes: 52 additions & 5 deletions frontend/components/questions/questions-listing.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
"use client";

import { useAuth } from "@/app/auth/auth-context";
import QuestionTable from "@/components/questions/questions-table";
import { useEffect, useState, ChangeEvent } from "react";
import useSWR from "swr";
import { Question, QuestionArraySchema } from "@/lib/schemas/question-schema";
import { useRouter, useSearchParams } from "next/navigation";

import QuestionTable from "@/components/questions/questions-table";
import LoadingScreen from "@/components/common/loading-screen";
import DeleteQuestionModal from "@/components/questions/delete-question-modal";
import { useRouter } from "next/navigation";
import QuestionFilter from "@/components/questions/question-filter";
import { Button } from "@/components/ui/button";
import { PlusIcon, Upload } from "lucide-react";
import { useToast } from "@/components/hooks/use-toast";
Expand Down Expand Up @@ -41,10 +43,18 @@ const fetcher = async (url: string): Promise<Question[]> => {
export default function QuestionListing() {
const auth = useAuth();
const router = useRouter();
const searchParams = useSearchParams();
const [category, setCategory] = useState(searchParams.get("category") || "");
const [complexity, setComplexity] = useState(
searchParams.get("complexity") || ""
);
const { toast } = useToast();
const { data, isLoading, mutate } = useSWR(
"http://localhost:8000/questions",
fetcher
`http://localhost:8000/questions?category=${encodeURIComponent(category)}&complexity=${encodeURIComponent(complexity)}`,
fetcher,
{
keepPreviousData: true,
}
);

const [questions, setQuestions] = useState<Question[]>([]);
Expand All @@ -57,6 +67,21 @@ export default function QuestionListing() {
setQuestions(data ?? []);
}, [data]);

useEffect(() => {
const params = new URLSearchParams(searchParams);
if (category) {
params.set("category", category);
} else {
params.delete("category");
}
if (complexity) {
params.set("complexity", complexity);
} else {
params.delete("complexity");
}
router.push(`?${params.toString()}`);
}, [category, complexity, router, searchParams]);

const handleView = (question: Question) => {
router.push(`/app/questions/${question.id}`);
};
Expand Down Expand Up @@ -187,7 +212,22 @@ export default function QuestionListing() {
}
};

if (isLoading) {
const handleSearchChange = (newSearch: string) => {
setCategory(newSearch);
};
const handleComplexityChange = (newComplexity: string) => {
if (newComplexity === "all") {
newComplexity = "";
}
setComplexity(newComplexity);
};

const handleReset = () => {
setCategory("");
router.push("");
};

if (isLoading && !data) {
return <LoadingScreen />;
}

Expand Down Expand Up @@ -216,6 +256,13 @@ export default function QuestionListing() {
<div>{createNewQuestion()}</div>
</div>
)}
<QuestionFilter
category={category}
onCategoryChange={handleSearchChange}
complexity={complexity}
onComplexityChange={handleComplexityChange}
onReset={handleReset}
/>
<QuestionTable
data={questions}
isAdmin={auth?.user?.isAdmin ?? false}
Expand Down
10 changes: 8 additions & 2 deletions question-service/app/crud/questions.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,14 @@ async def create_question(question: CreateQuestionModel):
new_question = await question_collection.insert_one(question.model_dump())
return await question_collection.find_one({"_id": new_question.inserted_id})

async def get_all_questions() -> QuestionCollection:
questions = await question_collection.find().to_list(1000)
async def get_all_questions(category: str, complexity: str) -> QuestionCollection:
query = {}
if category:
query["category"] = {"$regex": category, "$options": "i"}
if complexity:
query["complexity"] = complexity
questions = await question_collection.find(query).to_list(1000)

return QuestionCollection(questions=questions)

async def get_question_by_id(question_id: str) -> QuestionModel:
Expand Down
4 changes: 2 additions & 2 deletions question-service/app/routers/questions.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ async def create(question: CreateQuestionModel):
return existing_question

@router.get("/", response_description="Get all questions", response_model=QuestionCollection)
async def get_all():
return await get_all_questions()
async def get_all(category: str = None, complexity: str = None):
return await get_all_questions(category, complexity)

@router.get("/{question_id}", response_description="Get question with specified id", response_model=QuestionModel)
async def get_question(question_id: str):
Expand Down

0 comments on commit 45ee483

Please sign in to comment.