Skip to content

Commit de0ecb2

Browse files
Merge pull request #19 from CS3219-AY2425S1/ben/backup-emergency
Backup branch with the newest fix and implementation
2 parents 3efed18 + 653a79c commit de0ecb2

File tree

5 files changed

+368
-15
lines changed

5 files changed

+368
-15
lines changed

apps/question-service/src/app/page.tsx

Lines changed: 299 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
Tabs,
1616
Tag,
1717
Modal,
18+
Form,
1819
} from "antd";
1920
import { Content } from "antd/es/layout/layout";
2021
import {
@@ -29,13 +30,18 @@ import {
2930
DeleteQuestion as DeleteQuestionByDocref,
3031
GetQuestions,
3132
Question,
33+
CreateQuestion,
34+
NewQuestion,
35+
EditQuestion,
3236
} from "./services/question";
3337
import {
3438
CategoriesOption,
3539
DifficultyOption,
3640
OrderOption,
3741
} from "../utils/SelectOptions";
3842
import Link from "next/link";
43+
import TextArea from "antd/es/input/TextArea";
44+
import { title } from "process";
3945

4046
/**
4147
* defines the State of the page whe a user is deleing an object. Has 3 general states:
@@ -98,6 +104,77 @@ export default function Home() {
98104
// Message States
99105
const [messageApi, contextHolder] = message.useMessage();
100106

107+
// States for Create New Problem Modal
108+
const [form] = Form.useForm();
109+
const [isNewProblemModalOpen, setIsNewProblemModelOpen] = useState(false);
110+
111+
// States for Edit Existing Problem Modal
112+
const [editForm] = Form.useForm();
113+
const [isEditModalOpen, setIsEditModalOpen] = useState<boolean[] | undefined>(
114+
undefined
115+
);
116+
117+
// State for refreshing data
118+
const [refresh, setRefresh] = useState(false);
119+
120+
const handleEditClick = (index: number, question: Question) => {
121+
// Open the modal for the specific question
122+
const updatedModals =
123+
isEditModalOpen && isEditModalOpen.map((_, idx) => idx === index);
124+
setIsEditModalOpen(updatedModals); // Only the selected question's modal is open
125+
126+
// Set the form value
127+
editForm.setFieldsValue({
128+
title: question.title,
129+
description: question.description,
130+
complexity: question.complexity,
131+
categories: question.categories,
132+
});
133+
};
134+
135+
// Function to handle modal close
136+
const handleModalClose = (index: number) => {
137+
if (isEditModalOpen) {
138+
const updatedModals = [...isEditModalOpen];
139+
updatedModals[index] = false; // Close the specific modal
140+
setIsEditModalOpen(updatedModals);
141+
}
142+
};
143+
144+
const handleEditQuestion = async (
145+
values: NewQuestion,
146+
index: number,
147+
docRefId: string
148+
) => {
149+
try {
150+
const editedQuestion = await EditQuestion(values, docRefId);
151+
// Reset form or update UI as needed
152+
handleModalClose(index);
153+
editForm.resetFields();
154+
success("Problem Updated!");
155+
setRefresh(!refresh);
156+
} catch (err: any) {
157+
error(err.message);
158+
}
159+
};
160+
161+
const handleCreateQuestion = async (values: NewQuestion) => {
162+
try {
163+
const createdQuestion = await CreateQuestion(values);
164+
// Reset form or update UI as needed
165+
setIsNewProblemModelOpen(false);
166+
form.resetFields();
167+
success("New Problem Created!");
168+
setRefresh(!refresh);
169+
} catch (err: any) {
170+
error(err.message);
171+
}
172+
};
173+
174+
const showNewProblemModal = () => {
175+
setIsNewProblemModelOpen(true);
176+
};
177+
101178
const success = (message: string) => {
102179
messageApi.open({
103180
type: "success",
@@ -138,18 +215,27 @@ export default function Home() {
138215
setCurrentPage(data.currentPage);
139216
setLimit(data.limit);
140217
setIsLoading(false);
218+
setIsEditModalOpen(Array(data.questions.length).fill(false));
141219
}
142220

143221
useEffect(() => {
144222
loadQuestions();
145-
}, [limit, currentPage, sortBy, difficulty, categories, delayedSearch]);
223+
}, [
224+
limit,
225+
currentPage,
226+
sortBy,
227+
difficulty,
228+
categories,
229+
delayedSearch,
230+
refresh,
231+
]);
146232

147233
// Delay the fetching of data only after user stops typing for awhile
148234
useEffect(() => {
149235
const timeout = setTimeout(() => {
150236
setDelayedSearch(search);
151237
setCurrentPage(1); // Reset the current page
152-
}, 800);
238+
}, 500);
153239
return () => clearTimeout(timeout);
154240
}, [search]);
155241

@@ -174,7 +260,7 @@ export default function Home() {
174260
>
175261
<Button type="link">{text}</Button>
176262
</Link>
177-
), // TODO (Sean): Onclick links to the individual question page
263+
),
178264
},
179265
{
180266
title: "Categories",
@@ -207,10 +293,108 @@ export default function Home() {
207293
title: "Actions",
208294
key: "actions",
209295
dataIndex: "id",
210-
render: (_: number, question: Question) => (
296+
render: (_: number, question: Question, index: number) => (
211297
<div>
212298
{/* TODO (Sean): Include Logic to handle retrieving of editable data here and display in a modal component */}
213-
<Button className="edit-button" icon={<EditOutlined />}></Button>
299+
<Modal
300+
title="Edit Problem"
301+
open={isEditModalOpen && isEditModalOpen[index]}
302+
onCancel={() => handleModalClose(index)}
303+
footer={null}
304+
width={600}
305+
>
306+
<Form
307+
name="edit-form"
308+
{...layout}
309+
form={editForm}
310+
onFinish={(values) => {
311+
handleEditQuestion(values, index, question.docRefId);
312+
}}
313+
>
314+
<Form.Item
315+
name="title"
316+
label="Title"
317+
rules={[
318+
{
319+
required: true,
320+
message: "Please enter question title!",
321+
},
322+
]}
323+
>
324+
<Input name="title" />
325+
</Form.Item>
326+
<Form.Item
327+
name="description"
328+
label="Description"
329+
rules={[
330+
{
331+
required: true,
332+
message: "Please enter question description!",
333+
},
334+
]}
335+
>
336+
<TextArea name="description" />
337+
</Form.Item>
338+
<Form.Item
339+
name="complexity"
340+
label="Complexity"
341+
rules={[
342+
{
343+
required: true,
344+
message: "Please select a complexity!",
345+
},
346+
]}
347+
>
348+
<Select
349+
options={[
350+
{
351+
label: "Easy",
352+
value: "easy",
353+
},
354+
{
355+
label: "Medium",
356+
value: "medium",
357+
},
358+
{
359+
label: "Hard",
360+
value: "hard",
361+
},
362+
]}
363+
onChange={(value) => form.setFieldValue("complexity", value)}
364+
allowClear
365+
/>
366+
</Form.Item>
367+
<Form.Item
368+
name="categories"
369+
label="Categories"
370+
rules={[
371+
{
372+
required: true,
373+
message: "Please select the relevant categories!",
374+
},
375+
]}
376+
>
377+
<Select
378+
mode="multiple"
379+
options={CategoriesOption}
380+
onChange={(value) => form.setFieldValue("categories", value)}
381+
allowClear
382+
/>
383+
</Form.Item>
384+
<Form.Item
385+
style={{ display: "flex", justifyContent: "flex-end" }}
386+
>
387+
<Button type="primary" htmlType="submit">
388+
Save
389+
</Button>
390+
</Form.Item>
391+
</Form>
392+
</Modal>
393+
<Button
394+
className="edit-button"
395+
icon={<EditOutlined />}
396+
onClick={() => handleEditClick(index, question)}
397+
></Button>
214398
{/* TODO (Ryan): Include Pop-up confirmation for delete when clicked and link to delete API --> can also explore success notification or look into react-toast*/}
215399
<Button
216400
className="delete-button"
@@ -285,6 +469,12 @@ export default function Home() {
285469
}
286470
setDeletionStage({});
287471
};
472+
473+
const layout = {
474+
labelCol: { span: 4 },
475+
wrapperCol: { span: 20 },
476+
};
477+
288478
return (
289479
<div>
290480
{contextHolder}
@@ -296,9 +486,112 @@ export default function Home() {
296486
<div className="content-title">Problems</div>
297487
<div className="create-button">
298488
{/* TODO (Sean): Launch a popup modal that links to the backend api to create a new entry in db, --> look into success/error notification/react toast */}
299-
<Button type="primary" icon={<PlusCircleOutlined />}>
489+
<Button
490+
type="primary"
491+
icon={<PlusCircleOutlined />}
492+
onClick={showNewProblemModal}
493+
>
300494
Create New Problem
301495
</Button>
496+
<Modal
497+
title="Create New Problem"
498+
open={isNewProblemModalOpen}
499+
// onOk={() => setIsNewProblemModelOpen(false)} // Replace with handleSubmit
500+
onCancel={() => setIsNewProblemModelOpen(false)}
501+
footer={null}
502+
width={600}
503+
>
504+
<Form
505+
name="create-form"
506+
{...layout}
507+
form={form}
508+
onFinish={(values) => {
509+
handleCreateQuestion(values);
510+
}}
511+
>
512+
<Form.Item
513+
name="title"
514+
label="Title"
515+
rules={[
516+
{
517+
required: true,
518+
message: "Please enter question title!",
519+
},
520+
]}
521+
>
522+
<Input name="title" />
523+
</Form.Item>
524+
<Form.Item
525+
name="description"
526+
label="Description"
527+
rules={[
528+
{
529+
required: true,
530+
message: "Please enter question description!",
531+
},
532+
]}
533+
>
534+
<TextArea name="description" />
535+
</Form.Item>
536+
<Form.Item
537+
name="complexity"
538+
label="Complexity"
539+
rules={[
540+
{
541+
required: true,
542+
message: "Please select a complexity!",
543+
},
544+
]}
545+
>
546+
<Select
547+
options={[
548+
{
549+
label: "Easy",
550+
value: "easy",
551+
},
552+
{
553+
label: "Medium",
554+
value: "medium",
555+
},
556+
{
557+
label: "Hard",
558+
value: "hard",
559+
},
560+
]}
561+
onChange={(value) =>
562+
form.setFieldValue("complexity", value)
563+
}
564+
allowClear
565+
/>
566+
</Form.Item>
567+
<Form.Item
568+
name="categories"
569+
label="Categories"
570+
rules={[
571+
{
572+
required: true,
573+
message: "Please select the relevant categories!",
574+
},
575+
]}
576+
>
577+
<Select
578+
mode="multiple"
579+
options={CategoriesOption}
580+
onChange={(value) =>
581+
form.setFieldValue("categories", value)
582+
}
583+
allowClear
584+
/>
585+
</Form.Item>
586+
<Form.Item
587+
style={{ display: "flex", justifyContent: "flex-end" }}
588+
>
589+
<Button type="primary" htmlType="submit">
590+
Create
591+
</Button>
592+
</Form.Item>
593+
</Form>
594+
</Modal>
302595
</div>
303596
</div>
304597
{/* TODO (Ben/Ryan): Include and link search & filter parameters */}

0 commit comments

Comments
 (0)