diff --git a/src/App.tsx b/src/App.tsx
index bc8254c..bca4baf 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -25,7 +25,8 @@ import {
ProblemData,
ProblemLeaderBoard,
ProblemSubmission,
- ProblemForm,
+ ProblemCreate,
+ ProblemEdit,
ClassEditContestList,
AdminFaqList,
AdminNewFaq,
@@ -59,7 +60,10 @@ export default function App() {
} />
} />
- }>
+ } />
+ } />
+ } />
+ } />
}>
} />
diff --git a/src/components/atom/index.ts b/src/components/atom/index.ts
index 2462131..224856a 100644
--- a/src/components/atom/index.ts
+++ b/src/components/atom/index.ts
@@ -6,3 +6,4 @@ export * from './ErrorMessage';
export * from './Select';
export * from './LinkButton';
export * from './Switch';
+export * from './Editor';
diff --git a/src/constants/paths.ts b/src/constants/paths.ts
index 50d6af7..47779a9 100644
--- a/src/constants/paths.ts
+++ b/src/constants/paths.ts
@@ -21,8 +21,10 @@ export const SUB_PATH = {
LEADERBOARD: 'leaderboard',
SUBMISSON: 'submission',
- PROBLEM: ':problemId',
+ PROBLEM: 'all-problems/:problemId',
PROBLEM_CREATE: ':contestId/create',
+ PROBLEM_EDIT_LIST: ':contestId/edit',
+ PROBLEM_EDIT: ':contestId/edit/:problemId',
ALL_PROBLEMS: 'all-problems',
ALL_CLASSES: 'all-classes',
ANNOUNCEMENTS: 'announcements',
@@ -33,6 +35,7 @@ export const SUB_PATH = {
CONTEST: 'contest',
CONTEST_DETAIL: ':contestId',
CONTEST_PROBLEM: ':contestId/:contestProblemId',
+ CONTEST_PROBLEM_EDIT: ':contestId/:problemId/edit',
CONTEST_LIST_EDIT: 'edit',
};
diff --git a/src/pages/Class/ClassContestProblemList.tsx b/src/pages/Class/ClassContestProblemList.tsx
index 69573c9..88c0c94 100644
--- a/src/pages/Class/ClassContestProblemList.tsx
+++ b/src/pages/Class/ClassContestProblemList.tsx
@@ -1,10 +1,13 @@
-import { useNavigate, useParams } from 'react-router-dom';
+import { useParams } from 'react-router-dom';
+import { useState } from 'react';
import { Button, Heading, Table } from '@/components';
+
import { useContestProblemListTable, useClassContestListQuery } from './hooks';
+import { ContestProblemCreateModal } from './components';
export function ClassContestProblemList() {
- const navigate = useNavigate();
+ const [showModal, setShowModal] = useState(false);
const { classId, contestId } = useParams() as { classId: string; contestId: string };
const {
data: { results },
@@ -14,7 +17,7 @@ export function ClassContestProblemList() {
const tableProps = useContestProblemListTable(classId, contestId);
const handleCreateButtonClick = () => {
- navigate('create');
+ setShowModal(true);
};
return (
@@ -24,6 +27,7 @@ export function ClassContestProblemList() {
+
>
);
}
diff --git a/src/pages/Class/Problem/ProblemCreate.tsx b/src/pages/Class/Problem/ProblemCreate.tsx
new file mode 100644
index 0000000..11f7a50
--- /dev/null
+++ b/src/pages/Class/Problem/ProblemCreate.tsx
@@ -0,0 +1,7 @@
+import { ProblemForm } from './components';
+import { useCreateProblemMutation } from './hooks';
+
+export function ProblemCreate() {
+ const { mutate } = useCreateProblemMutation();
+ return ;
+}
diff --git a/src/pages/Class/Problem/ProblemEdit.tsx b/src/pages/Class/Problem/ProblemEdit.tsx
new file mode 100644
index 0000000..cb60100
--- /dev/null
+++ b/src/pages/Class/Problem/ProblemEdit.tsx
@@ -0,0 +1,12 @@
+import { useParams } from 'react-router-dom';
+import { ProblemForm } from './components';
+import { useEditProblemMutation, useProblemQuery } from './hooks';
+
+export function ProblemEdit() {
+ const { problemId } = useParams() as { problemId: string };
+ /** FIXME: 파일이 존재하는 쿼리로 변경 */
+ const { data } = useProblemQuery(problemId);
+ const { mutate } = useEditProblemMutation(problemId);
+
+ return ;
+}
diff --git a/src/pages/Class/Problem/ProblemForm.tsx b/src/pages/Class/Problem/ProblemForm.tsx
deleted file mode 100644
index 62e9ced..0000000
--- a/src/pages/Class/Problem/ProblemForm.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import { useParams } from 'react-router-dom';
-
-import { Button, Heading, Label, Input, Select, Switch } from '@/components';
-import { METRICS } from '@/constants';
-
-import { useCreateProblemMutation } from './hooks';
-
-export function ProblemForm() {
- const options = METRICS.map((metric) => ({ value: metric }));
- const { id: classId } = useParams() as { id: string };
- const { mutate: createProblem } = useCreateProblemMutation();
-
- const handleFormSubmit = (e: React.FormEvent) => {
- e.preventDefault();
-
- const formValue = Object.values(e.target)
- .filter(({ nodeName }) => nodeName === 'INPUT' || nodeName === 'SELECT')
- .map(({ value, files }) => (files ? files[0] : value));
-
- const payloadKey = [
- 'title',
- 'description',
- 'evaluation',
- 'public',
- 'data_description',
- 'data',
- 'solution',
- ];
-
- const formData = new FormData();
- formData.append('class_id', classId);
- payloadKey.forEach((key, index) => formData.append(key, formValue[index]));
-
- createProblem(formData);
- };
-
- return (
-
- );
-}
diff --git a/src/pages/Class/Problem/api/index.ts b/src/pages/Class/Problem/api/index.ts
index 391a791..da098f9 100644
--- a/src/pages/Class/Problem/api/index.ts
+++ b/src/pages/Class/Problem/api/index.ts
@@ -10,6 +10,10 @@ const createProblem = (payload: FormData) => {
return fileApi.post(`${API_URL}/`, payload);
};
+const editProblem = (problemId: string, payload: FormData) => {
+ return fileApi.put(`${API_URL}/${problemId}/`, payload);
+};
+
const getContestProblem = ({ classId, contestId, contestProblemId }: ContestProblemRequest) => {
return api.get(`/class/${classId}/contests/${contestId}/${contestProblemId}`);
};
@@ -46,6 +50,7 @@ const createContestProblemSumbissionCheck = ({
export {
getProblem,
createProblem,
+ editProblem,
getContestProblem,
getContestProblemSubmission,
createContestProblemSubmission,
diff --git a/src/pages/Class/Problem/components/ProblemForm.tsx b/src/pages/Class/Problem/components/ProblemForm.tsx
new file mode 100644
index 0000000..bfe038f
--- /dev/null
+++ b/src/pages/Class/Problem/components/ProblemForm.tsx
@@ -0,0 +1,74 @@
+import { useState } from 'react';
+import { useParams } from 'react-router-dom';
+import { UseMutateFunction } from 'react-query';
+import { AxiosResponse, AxiosError } from 'axios';
+
+import { Button, Heading, Label, Input, Select, Switch, Editor } from '@/components';
+import { METRICS } from '@/constants';
+
+type ProblemFormProps = Component & {
+ data?: ProblemRequest;
+ mutate: UseMutateFunction, AxiosError, FormData, unknown>;
+};
+
+export function ProblemForm({ data, mutate }: ProblemFormProps<'form'>) {
+ const {
+ title,
+ description: _description,
+ evaluation,
+ public: _public,
+ data_description,
+ data: dataFile,
+ solution,
+ } = data ?? {};
+ const [description, setDescription] = useState(_description ?? '');
+ const [dataDescription, setDataDescription] = useState(data_description ?? '');
+
+ const { classId } = useParams() as { classId: string };
+
+ const options = METRICS.map((metric) => ({ value: metric }));
+
+ const handleFormSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+
+ const payloadKey = ['title', 'evaluation', 'public', 'data', 'solution'];
+
+ const formValue = Object.values(e.target)
+ .filter(({ name }) => payloadKey.includes(name))
+ .map(({ name, checked, value, files }) =>
+ name === 'public' ? checked : files ? files[0] : value
+ );
+
+ const formData = new FormData();
+ formData.append('class_id', classId);
+ formData.append('description', description);
+ formData.append('data_description', dataDescription);
+ payloadKey.forEach((key, index) => formData.append(key, formValue[index]));
+
+ mutate(formData);
+ };
+
+ return (
+
+ );
+}
diff --git a/src/pages/Class/Problem/components/index.ts b/src/pages/Class/Problem/components/index.ts
index ef243d0..99e64d9 100644
--- a/src/pages/Class/Problem/components/index.ts
+++ b/src/pages/Class/Problem/components/index.ts
@@ -1,3 +1,4 @@
export * from './Problem';
export * from './FileSubmissionForm';
export * from './LeaderboardSubmissionForm';
+export * from './ProblemForm';
diff --git a/src/pages/Class/Problem/hooks/query/index.ts b/src/pages/Class/Problem/hooks/query/index.ts
index b25562f..d894dac 100644
--- a/src/pages/Class/Problem/hooks/query/index.ts
+++ b/src/pages/Class/Problem/hooks/query/index.ts
@@ -1,5 +1,6 @@
export * from './useProblemQuery';
export * from './useCreateProblemMutation';
+export * from './useEditProblemMutation';
export * from './useContestProblemQuery';
export * from './useContestProblemSubmissionQuery';
export * from './useCreateContestProblemSubmissionMutation';
diff --git a/src/pages/Class/Problem/hooks/query/useCreateProblemMutation.ts b/src/pages/Class/Problem/hooks/query/useCreateProblemMutation.ts
index 2e8bec1..e1070a3 100644
--- a/src/pages/Class/Problem/hooks/query/useCreateProblemMutation.ts
+++ b/src/pages/Class/Problem/hooks/query/useCreateProblemMutation.ts
@@ -1,12 +1,17 @@
import { useMutation, UseMutationOptions } from 'react-query';
import { AxiosError, AxiosResponse } from 'axios';
+import { useNavigate } from 'react-router-dom';
import { createProblem } from '../../api';
export const useCreateProblemMutation = (
options?: UseMutationOptions
) => {
+ const navigate = useNavigate();
return useMutation((payload) => createProblem(payload), {
...options,
+ onSuccess: () => {
+ navigate(-1);
+ },
});
};
diff --git a/src/pages/Class/Problem/hooks/query/useEditProblemMutation.ts b/src/pages/Class/Problem/hooks/query/useEditProblemMutation.ts
new file mode 100644
index 0000000..98adc1a
--- /dev/null
+++ b/src/pages/Class/Problem/hooks/query/useEditProblemMutation.ts
@@ -0,0 +1,16 @@
+import { useMutation, UseMutationOptions } from 'react-query';
+import { AxiosError, AxiosResponse } from 'axios';
+
+import { editProblem } from '../../api';
+
+export const useEditProblemMutation = (
+ problemId: string,
+ options?: UseMutationOptions
+) => {
+ return useMutation((payload) => editProblem(problemId, payload), {
+ ...options,
+ onSuccess: () => {
+ alert('편집 완료');
+ },
+ });
+};
diff --git a/src/pages/Class/Problem/index.ts b/src/pages/Class/Problem/index.ts
index b999143..1806f2f 100644
--- a/src/pages/Class/Problem/index.ts
+++ b/src/pages/Class/Problem/index.ts
@@ -1,4 +1,5 @@
-export * from './ProblemForm';
+export * from './ProblemCreate';
+export * from './ProblemEdit';
export * from './AllProblemDetail';
export * from './ContestProblemDetail';
diff --git a/src/pages/Class/components/ContestProblemCreateModal.tsx b/src/pages/Class/components/ContestProblemCreateModal.tsx
new file mode 100644
index 0000000..4d8ae16
--- /dev/null
+++ b/src/pages/Class/components/ContestProblemCreateModal.tsx
@@ -0,0 +1,37 @@
+import { useNavigate } from 'react-router-dom';
+
+import { Modal, Button } from '@/components';
+import { StateAndAction } from '@/types/state';
+
+type ContestProblemCreateModal = Component &
+ StateAndAction;
+
+export function ContestProblemCreateModal({
+ showModal,
+ setShowModal,
+}: ContestProblemCreateModal<'div'>) {
+ const navigate = useNavigate();
+
+ const handleCreateButtonClick = () => {
+ navigate('create');
+ };
+
+ const handleGetButtonClick = () => {
+ navigate('edit');
+ };
+
+ return (
+
+ 문제 만들기
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/pages/Class/components/index.ts b/src/pages/Class/components/index.ts
index ca98a09..15a2e2b 100644
--- a/src/pages/Class/components/index.ts
+++ b/src/pages/Class/components/index.ts
@@ -3,3 +3,4 @@ export * from './ClassStudentForm';
export * from './ClassFormModal';
export * from './ContestFormModal';
export * from './ContestEditModal';
+export * from './ContestProblemCreateModal';
diff --git a/src/pages/Class/hooks/useClassProblemListTable.tsx b/src/pages/Class/hooks/useClassProblemListTable.tsx
index db2ae36..0506f5b 100644
--- a/src/pages/Class/hooks/useClassProblemListTable.tsx
+++ b/src/pages/Class/hooks/useClassProblemListTable.tsx
@@ -57,7 +57,7 @@ export const useClassProblemListTable = (keyword: string) => {
e: React.MouseEvent,
id: number | string
) => {
- navigate(`../${id}`);
+ navigate(`${id}`);
};
return {
diff --git a/src/pages/Class/hooks/useContestProblemListTable.tsx b/src/pages/Class/hooks/useContestProblemListTable.tsx
index 7e2b0f9..edb5b14 100644
--- a/src/pages/Class/hooks/useContestProblemListTable.tsx
+++ b/src/pages/Class/hooks/useContestProblemListTable.tsx
@@ -2,7 +2,7 @@ import { useNavigate } from 'react-router-dom';
import { Button } from '@/components';
-import { useClassContestProblemListQuery } from './query';
+import { useClassContestProblemListQuery, useDeleteProblemMutation } from './query';
import { formatTime } from '@/utils/time';
export const useContestProblemListTable = (classId: string, contestId: string) => {
@@ -16,21 +16,39 @@ export const useContestProblemListTable = (classId: string, contestId: string) =
{ Header: '삭제', accessor: 'delete' },
];
+ const handleEditButtonClick = (e: React.MouseEvent, id: number) => {
+ e.stopPropagation();
+ navigate(`${id}/edit`);
+ };
+
+ const { mutate: deleteProblem } = useDeleteProblemMutation();
+ const handleDeleteButtonClick = (
+ e: React.MouseEvent,
+ id: number,
+ title: string
+ ) => {
+ e.stopPropagation();
+ const isConfirmed = confirm(`${title}을 삭제하시겠습니까?`);
+ if (isConfirmed) deleteProblem(`${id}`);
+ };
+
const {
data: { results },
} = useClassContestProblemListQuery(classId, contestId);
- const data = results.map((problem) => ({
- ...problem,
- endTime: formatTime(problem.end_time),
- edit: ,
- delete: ,
- }));
+ const data = results.map((problem) => {
+ const { problem_id, title, end_time } = problem;
+ return {
+ ...problem,
+ endTime: formatTime(end_time),
+ edit: ,
+ delete: ,
+ };
+ });
const handleRowClick = (
e: React.MouseEvent,
id: number | string
) => {
- /** FIXME: 해당 문제 상세 페이지로 이동하게 수정 */
navigate(`${id}`);
};