Skip to content

Commit b1dd96c

Browse files
committed
feat: add history page
1 parent 1d5a38d commit b1dd96c

File tree

5 files changed

+302
-19
lines changed

5 files changed

+302
-19
lines changed
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
"use client";
2+
import Header from "@/components/Header/header";
3+
import { Layout, message, PaginationProps, Row, Table, Tag } from "antd";
4+
import { Content } from "antd/es/layout/layout";
5+
import { HistoryOutlined } from "@ant-design/icons";
6+
import "./styles.scss";
7+
import { useEffect, useState } from "react";
8+
import React from "react";
9+
import { useRouter } from "next/navigation";
10+
import { GetUserHistories, History } from "@/app/services/history";
11+
import { ValidateUser, VerifyTokenResponseType } from "@/app/services/user";
12+
13+
interface TablePagination {
14+
totalCount: number;
15+
totalPages: number;
16+
currentPage: number;
17+
limit: number;
18+
}
19+
20+
export default function QuestionPage() {
21+
// Message States
22+
const [messageApi, contextHolder] = message.useMessage();
23+
24+
const error = (message: string) => {
25+
messageApi.open({
26+
type: "error",
27+
content: message,
28+
});
29+
};
30+
31+
const router = useRouter();
32+
33+
const [username, setUsername] = useState<string | undefined>(undefined);
34+
const [userQuestionHistories, setUserQuestionHistories] =
35+
useState<History[]>();
36+
const [isHistoryLoading, setIsHistoryLoading] = useState<boolean>(true);
37+
const [paginationParams, setPaginationParams] = useState<TablePagination>({
38+
totalCount: 0,
39+
totalPages: 0,
40+
currentPage: 1,
41+
limit: 10,
42+
});
43+
44+
// Handler for change in page jumper
45+
const onPageJump: PaginationProps["onChange"] = (pageNumber) => {
46+
setPaginationParams((prev) => {
47+
loadQuestionHistories(pageNumber, paginationParams.limit);
48+
return { ...paginationParams, currentPage: pageNumber };
49+
});
50+
};
51+
52+
// Handler for show size change for pagination
53+
const onShowSizeChange: PaginationProps["onShowSizeChange"] = (
54+
current,
55+
pageSize
56+
) => {
57+
setPaginationParams((prev) => {
58+
loadQuestionHistories(current, pageSize);
59+
return { ...paginationParams, currentPage: current, limit: pageSize };
60+
});
61+
};
62+
63+
async function loadQuestionHistories(currentPage: number, limit: number) {
64+
if (username === undefined) return;
65+
setIsHistoryLoading(true);
66+
GetUserHistories(username, currentPage, limit)
67+
.then((data: any) => {
68+
setUserQuestionHistories(data.histories);
69+
setPaginationParams({
70+
...paginationParams,
71+
totalCount: data.totalCount,
72+
totalPages: data.totalPages,
73+
currentPage: data.currentPage,
74+
limit: data.limit,
75+
});
76+
})
77+
.finally(() => {
78+
setIsHistoryLoading(false);
79+
});
80+
}
81+
82+
useEffect(() => {
83+
loadQuestionHistories(paginationParams.currentPage, paginationParams.limit);
84+
}, [username]);
85+
86+
useEffect(() => {
87+
ValidateUser().then((data: VerifyTokenResponseType) => {
88+
setUsername(data.data.username);
89+
});
90+
}, []);
91+
92+
const columns = [
93+
{
94+
title: "Title",
95+
dataIndex: "title",
96+
key: "title",
97+
},
98+
{
99+
title: "Categories",
100+
dataIndex: "questionTopics",
101+
key: "questionTopics",
102+
render: (categories: string[]) =>
103+
categories.map((category) => <Tag key={category}>{category}</Tag>),
104+
},
105+
{
106+
title: "Difficulty",
107+
dataIndex: "questionDifficulty",
108+
key: "questionDifficulty",
109+
render: (difficulty: string) => {
110+
let color = "";
111+
if (difficulty === "easy") {
112+
color = "#2DB55D";
113+
} else if (difficulty === "medium") {
114+
color = "orange";
115+
} else if (difficulty === "hard") {
116+
color = "red";
117+
}
118+
return (
119+
<div style={{ color }}>
120+
{difficulty.charAt(0).toUpperCase() + difficulty.slice(1)}
121+
</div>
122+
);
123+
},
124+
},
125+
{
126+
title: "Submitted at",
127+
dataIndex: "createdAt",
128+
key: "createdAt",
129+
render: (date: string) => {
130+
return new Date(date).toLocaleString();
131+
},
132+
},
133+
{
134+
title: "Language",
135+
dataIndex: "language",
136+
key: "language",
137+
},
138+
{
139+
title: "Matched with",
140+
dataIndex: "matchedUser",
141+
key: "matchedUser",
142+
},
143+
];
144+
145+
const handleRowClick = (h: History) => {
146+
// Link to page
147+
// questionId is just read as "history", as only the doc ref id is involved in requests
148+
// If the question database is reloaded, then the questionDocRefId may not be correct
149+
router.push(
150+
`/question/history?data=${h.questionDocRefId}&history=${h.historyDocRefId}`
151+
);
152+
};
153+
154+
return (
155+
<div>
156+
{contextHolder}
157+
<Layout className="layout">
158+
<Header selectedKey={["0"]} />
159+
<Content className="content">
160+
<div className="content-card">
161+
<div className="content-row-1" style={{ marginBottom: "20px" }}>
162+
<div className="content-title">Submission History</div>
163+
</div>
164+
<div className="content-table">
165+
<Table
166+
rowKey="id"
167+
dataSource={userQuestionHistories}
168+
columns={columns}
169+
onRow={(record: any) => {
170+
return {
171+
onClick: () => handleRowClick(record),
172+
style: { cursor: "pointer" },
173+
};
174+
}}
175+
loading={isHistoryLoading}
176+
pagination={{
177+
current: paginationParams.currentPage,
178+
total: paginationParams.totalCount,
179+
pageSize: paginationParams.limit,
180+
onChange: onPageJump,
181+
showSizeChanger: true,
182+
onShowSizeChange: onShowSizeChange,
183+
}}
184+
/>
185+
</div>
186+
</div>
187+
</Content>
188+
</Layout>
189+
</div>
190+
);
191+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
.layout {
2+
background: white;
3+
}
4+
5+
.content {
6+
background: white;
7+
}
8+
9+
.content-card {
10+
margin: 4rem 8rem;
11+
padding: 2rem 4rem;
12+
background-color: white;
13+
min-height: 100vh;
14+
border-radius: 30px;
15+
box-shadow: 0px 10px 60px rgba(226, 236, 249, 0.5);
16+
}
17+
18+
.content-row-1 {
19+
display: flex;
20+
flex-direction: row;
21+
justify-content: space-between;
22+
}
23+
24+
.content-title {
25+
// font-family: "Poppins";
26+
// font-style: normal;
27+
font-weight: 600;
28+
font-size: 22px;
29+
line-height: 33px;
30+
letter-spacing: -0.01em;
31+
color: #000000;
32+
}
33+
34+
.content-filter {
35+
margin: 1.5rem 0;
36+
}
37+
38+
.edit-button {
39+
margin-right: 0.5rem;
40+
}
41+
42+
.filter-button {
43+
margin-left: 8px;
44+
box-shadow: 0 2px 0 rgba(0, 0, 0, 0.02) !important;
45+
}
46+
47+
.clear-button {
48+
width: 100%;
49+
}
50+
51+
.categories-multi-select,
52+
.difficulty-select,
53+
.order-select {
54+
width: 100%;
55+
}
56+
57+
.create-title,
58+
.new-problem-categories-multi-select,
59+
.new-problem-difficulty-select,
60+
.create-description,
61+
.create-problem-id {
62+
width: 100%;
63+
margin: 5px;
64+
}

apps/frontend/src/app/question/[id]/page.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ interface Submission {
2020
submittedAt: string;
2121
language: string;
2222
matchedUser: string;
23-
code: string;
2423
historyDocRefId: string;
24+
code: string;
2525
}
2626

2727
interface TablePagination {
@@ -44,13 +44,13 @@ export default function QuestionPage() {
4444
});
4545
};
4646

47-
const router = useRouter();
4847
const editorRef = useRef(null);
4948
const languageConf = new Compartment();
5049

51-
// Retrieve the docRefId from query params during page navigation
50+
// Retrieve the questionDocRefId and historyDocRefId from query params during page navigation
5251
const searchParams = useSearchParams();
53-
const docRefId: string = searchParams?.get("data") ?? "";
52+
const questionDocRefId: string = searchParams?.get("data") ?? "";
53+
const historyDocRefId: string = searchParams?.get("history") ?? "";
5454
// Code Editor States
5555
const [questionTitle, setQuestionTitle] = useState<string | undefined>(
5656
undefined
@@ -65,7 +65,7 @@ export default function QuestionPage() {
6565
const [isHistoryLoading, setIsHistoryLoading] = useState<boolean>(true);
6666
const [currentSubmissionId, setCurrentSubmissionId] = useState<
6767
string | undefined
68-
>(undefined);
68+
>(historyDocRefId == "" ? undefined : historyDocRefId);
6969
const [paginationParams, setPaginationParams] = useState<TablePagination>({
7070
totalCount: 0,
7171
totalPages: 0,
@@ -96,7 +96,7 @@ export default function QuestionPage() {
9696
async function loadQuestionHistories(currentPage: number, limit: number) {
9797
if (username === undefined) return;
9898
setIsHistoryLoading(true);
99-
GetUserQuestionHistories(username, docRefId, currentPage, limit)
99+
GetUserQuestionHistories(username, questionDocRefId, currentPage, limit)
100100
.then((data: any) => {
101101
setUserQuestionHistories(data.histories);
102102
setPaginationParams({
@@ -118,7 +118,7 @@ export default function QuestionPage() {
118118
setIsLoading(true);
119119
}
120120

121-
GetSingleQuestion(docRefId)
121+
GetSingleQuestion(questionDocRefId)
122122
.then((data: any) => {
123123
setQuestionTitle(data.title);
124124
setComplexity(data.complexity);
@@ -128,7 +128,7 @@ export default function QuestionPage() {
128128
.finally(() => {
129129
setIsLoading(false);
130130
});
131-
}, [docRefId]);
131+
}, [questionDocRefId]);
132132

133133
useEffect(() => {
134134
loadQuestionHistories(paginationParams.currentPage, paginationParams.limit);
@@ -156,8 +156,8 @@ export default function QuestionPage() {
156156
language: data.language,
157157
matchedUser:
158158
username == data.matchedUser ? data.user : data.matchedUser,
159-
code: data.code,
160159
historyDocRefId: data.historyDocRefId,
160+
code: data.code,
161161
});
162162

163163
view.dispatch(

apps/frontend/src/app/services/history.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,29 @@ export const GetHistory = async (historyDocRefId: string): Promise<History> => {
5757
};
5858

5959
export const GetUserHistories = async (
60-
username: string
60+
username: string,
61+
currentPage?: number,
62+
limit?: number
6163
): Promise<History[]> => {
62-
const response = await fetch(`${HISTORY_SERVICE_URL}histories/${username}`, {
63-
method: "GET",
64-
headers: {
65-
"Content-Type": "application/json",
66-
},
67-
});
64+
let query_params = "";
65+
66+
if (currentPage) {
67+
query_params += `?offset=${(currentPage - 1) * (limit ? limit : 10)}`;
68+
}
69+
70+
if (limit) {
71+
query_params += `${query_params.length > 0 ? "&" : "?"}limit=${limit}`;
72+
}
73+
74+
const response = await fetch(
75+
`${HISTORY_SERVICE_URL}histories/user/${username}${query_params}`,
76+
{
77+
method: "GET",
78+
headers: {
79+
"Content-Type": "application/json",
80+
},
81+
}
82+
);
6883

6984
if (response.status === 200) {
7085
return response.json();

0 commit comments

Comments
 (0)