Skip to content

Commit 645ea76

Browse files
committed
add archived questions view and integrate restore question endpoint
1 parent d30001b commit 645ea76

File tree

8 files changed

+357
-124
lines changed

8 files changed

+357
-124
lines changed

backend/question-service/src/controllers/questionController.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -417,12 +417,12 @@ module.exports = {
417417
fetchAllQuestions,
418418
getAllActiveQuestions: async (req, res) => {
419419
// convenience wrapper
420-
req.query.status = 'Active'
421-
return fetchAllQuestions(req, res)
420+
const newReq = { ...req, query: { ...req.query, status: 'Active' } }
421+
return fetchAllQuestions(newReq, res)
422422
},
423423
getAllArchivedQuestions: async (req, res) => {
424-
req.query.status = 'Archived'
425-
return fetchAllQuestions(req, res)
424+
const newReq = { ...req, query: { ...req.query, status: 'Archived' } }
425+
return fetchAllQuestions(newReq, res)
426426
},
427427
pickQuestion,
428428
getQuestionById,

frontend/src/app/practice/[id]/page.tsx

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { useEffect } from "react";
3+
import { useEffect, useState } from "react";
44
import { useRouter, useParams } from "next/navigation";
55
import dynamic from "next/dynamic";
66
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
@@ -12,6 +12,11 @@ import { Button } from "@/components/ui/button";
1212
import { Spinner } from "@/components/ui/spinner";
1313
import { useCollaborationState, useCollaborationActions } from "@/stores/collaboration-store";
1414
import ProtectedRoute from "@/components/auth/protected-route";
15+
import { SolutionView } from "@/components/question/solution-view";
16+
import { Tabs, TabsList, TabsContent, TabsTrigger } from "@/components/ui/tabs";
17+
import { LanguageSelector } from "@/components/question/language-selector";
18+
import { Language } from "@/types/solution";
19+
import { CardHeader, CardTitle, CardContent } from "@/components/ui/card";
1520

1621
const CodeEditorPanel = dynamic(() => import("@/components/practice/code-editor-panel"), {
1722
ssr: false,
@@ -22,8 +27,9 @@ export default function PracticePage() {
2227
const router = useRouter();
2328
const params = useParams();
2429
const roomId = params?.id as string;
30+
const [selectedLang, setSelectedLang] = useState<Language>("Python");
2531

26-
const { roomDetails, isLoading, error } = useCollaborationState();
32+
const { roomDetails, isLoading, error, questionDetails } = useCollaborationState();
2733
const { fetchRoomDetails, fetchQuestionDetails, reset } = useCollaborationActions();
2834

2935
// Fetch room details on mount
@@ -101,7 +107,31 @@ export default function PracticePage() {
101107
{/* Left Panel */}
102108
<ResizablePanel>
103109
<div className="h-full overflow-y-auto">
104-
<QuestionPanel />
110+
<Tabs defaultValue="question">
111+
<TabsList className="flex justify-center items-center gap-4 mx-auto w-auto">
112+
<TabsTrigger value="question">Question</TabsTrigger>
113+
<TabsTrigger value="solution">Solution</TabsTrigger>
114+
</TabsList>
115+
<TabsContent value="question">
116+
<QuestionPanel />
117+
</TabsContent>
118+
<TabsContent value="solution">
119+
<CardHeader>
120+
<CardTitle>Solutions</CardTitle>
121+
<LanguageSelector selectedLang={selectedLang} onChange={setSelectedLang} />
122+
</CardHeader>
123+
<CardContent>
124+
{questionDetails ? (
125+
<SolutionView
126+
questionId={questionDetails.questionID}
127+
selectedLang={selectedLang}
128+
/>
129+
) : (
130+
<div>Loading question…</div>
131+
)}
132+
</CardContent>
133+
</TabsContent>
134+
</Tabs>
105135
</div>
106136
</ResizablePanel>
107137

frontend/src/components/question/columns.tsx

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
import { ColumnDef } from "@tanstack/react-table";
44
import { Button } from "@/components/ui/button";
5-
import { Eye, Archive } from "lucide-react";
5+
import { Eye, Archive, ArchiveRestore } from "lucide-react";
66
import { Question } from "@/lib/api-client";
77
import clsx from "clsx";
88
import { useRouter } from "next/navigation";
99
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
10-
import { archiveQuestion } from "@/hooks/use-question";
10+
import { archiveQuestion, restoreQuestion } from "@/hooks/use-question";
1111

1212
function ActionCell({ question }: { question: Question }) {
1313
const router = useRouter();
@@ -48,6 +48,28 @@ function ActionCell({ question }: { question: Question }) {
4848
);
4949
}
5050

51+
function RestoreActionCell({ question }: { question: Question }) {
52+
const handleRestore = () => {
53+
restoreQuestion(question._id);
54+
window.location.reload();
55+
};
56+
57+
return (
58+
<div className="flex space-x-4">
59+
<Tooltip>
60+
<TooltipTrigger asChild>
61+
<Button className="w-15" variant="default" size="sm" onClick={handleRestore}>
62+
<ArchiveRestore className="h-4 w-4" />
63+
</Button>
64+
</TooltipTrigger>
65+
<TooltipContent>
66+
<p>Restore</p>
67+
</TooltipContent>
68+
</Tooltip>
69+
</div>
70+
);
71+
}
72+
5173
const difficultyOrder: Record<string, number> = {
5274
Easy: 0,
5375
Medium: 1,
@@ -103,3 +125,53 @@ export const columns: ColumnDef<Question>[] = [
103125
cell: ({ row }) => <ActionCell question={row.original} />,
104126
},
105127
];
128+
129+
export const restoreColumns: ColumnDef<Question>[] = [
130+
{
131+
id: "index",
132+
header: "No.",
133+
cell: ({ row }) => <span>{row.index + 1}</span>,
134+
enableSorting: false,
135+
enableHiding: false,
136+
size: 50,
137+
},
138+
{
139+
accessorKey: "title",
140+
header: "Question Title",
141+
enableSorting: true,
142+
},
143+
{
144+
accessorKey: "topic",
145+
header: "Topic",
146+
enableSorting: true,
147+
},
148+
{
149+
accessorKey: "difficulty",
150+
header: "Difficulty",
151+
enableSorting: true,
152+
sortingFn: (rowA, rowB, columnId) => {
153+
const a = String(rowA.getValue(columnId));
154+
const b = String(rowB.getValue(columnId));
155+
const ai = difficultyOrder[a] ?? Number.POSITIVE_INFINITY;
156+
const bi = difficultyOrder[b] ?? Number.POSITIVE_INFINITY;
157+
return ai - bi;
158+
},
159+
cell: ({ getValue }) => {
160+
const difficulty = getValue() as Question["difficulty"];
161+
162+
const colorClass = clsx("px-2 py-1 rounded-full text-center font-medium w-fit", {
163+
"bg-green-100 text-green-800": difficulty === "Easy",
164+
"bg-yellow-100 text-yellow-800": difficulty === "Medium",
165+
"bg-red-100 text-red-800": difficulty === "Hard",
166+
});
167+
168+
return <span className={colorClass}>{difficulty}</span>;
169+
},
170+
},
171+
{
172+
accessorKey: "actions",
173+
header: "Actions",
174+
enableSorting: false,
175+
cell: ({ row }) => <RestoreActionCell question={row.original} />,
176+
},
177+
];
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"use client";
2+
3+
import { Label } from "@/components/ui/label";
4+
import {
5+
Select,
6+
SelectContent,
7+
SelectItem,
8+
SelectTrigger,
9+
SelectValue,
10+
} from "@/components/ui/select";
11+
import { Language } from "@/types/solution";
12+
13+
type SelectLanguageProps = {
14+
selectedLang: Language;
15+
onChange: (lang: Language) => void;
16+
};
17+
18+
export function LanguageSelector({ selectedLang, onChange }: SelectLanguageProps) {
19+
return (
20+
<div className="mt-4 flex items-center gap-3">
21+
<Label htmlFor="language-select" className="whitespace-nowrap">
22+
Language
23+
</Label>
24+
<Select value={selectedLang} onValueChange={(v) => onChange(v as Language)}>
25+
<SelectTrigger id="language-select" className="w-56">
26+
<SelectValue placeholder="Select language" />
27+
</SelectTrigger>
28+
<SelectContent>
29+
{(["JavaScript", "Python", "C++", "Java"] as Language[]).map((lang) => (
30+
<SelectItem key={lang} value={lang}>
31+
{lang}
32+
</SelectItem>
33+
))}
34+
</SelectContent>
35+
</Select>
36+
</div>
37+
);
38+
}

0 commit comments

Comments
 (0)