Skip to content

Commit 5bf229b

Browse files
feat-fe: 지원자 평가 목록에서 개별 평가 삭제 기능 추가 (#968)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Seongjin Hong <[email protected]>
1 parent d7c73d3 commit 5bf229b

File tree

9 files changed

+213
-81
lines changed

9 files changed

+213
-81
lines changed

frontend/src/api/domain/evaluation.ts

+5
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ const evaluationApis = {
3737
})}`,
3838
body: { evaluator, score, content },
3939
}),
40+
41+
delete: async ({ evaluationId }: { evaluationId: number }) =>
42+
apiClient.delete({
43+
path: `/${evaluationId}`,
44+
}),
4045
};
4146

4247
export default evaluationApis;

frontend/src/components/ApplicantModal/ApplicantEvalInfo/EvaluationCard/index.tsx

+36-2
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,56 @@
11
import { EvaluationResult } from '@customTypes/applicant';
22
import formatDate from '@utils/formatDate';
33

4+
import { evaluationMutations } from '@hooks/evaluations';
5+
46
import { HiOutlineClock } from 'react-icons/hi';
7+
import { HiOutlineTrash } from 'react-icons/hi2';
58
import { FiUser } from 'react-icons/fi';
69

710
import { EVALUATION_SCORE } from '../constants';
811
import S from './style';
912

1013
interface EvaluationCardProps {
1114
evaluationResult: EvaluationResult;
15+
processId: number;
16+
applicantId: number;
17+
evaluationId: number;
1218
}
1319

14-
export default function EvaluationCard({ evaluationResult }: EvaluationCardProps) {
20+
export default function EvaluationCard({
21+
evaluationResult,
22+
processId,
23+
applicantId,
24+
evaluationId,
25+
}: EvaluationCardProps) {
1526
const createdDate = evaluationResult.createdDate ? formatDate(evaluationResult.createdDate) : '날짜 정보 없음';
1627

28+
const { mutate: deleteEvaluation, isPending: isDeletePending } = evaluationMutations.useDeleteEvaluation({
29+
processId,
30+
applicantId,
31+
});
32+
33+
const handleClickDeleteButton = () => {
34+
if (window.confirm('삭제하신 평가는 다시 복구할 수 없습니다.\n삭제하시겠습니까?')) {
35+
deleteEvaluation({ evaluationId });
36+
}
37+
};
38+
1739
return (
1840
<S.CardContainer>
19-
<S.ResultFlag $score={evaluationResult.score}>{EVALUATION_SCORE[evaluationResult.score]}</S.ResultFlag>
41+
<S.CardHeaderContainer>
42+
<S.ResultFlag $score={evaluationResult.score}>{EVALUATION_SCORE[evaluationResult.score]}</S.ResultFlag>
43+
<S.UtilButtonsContainer>
44+
<S.DeleteButton
45+
type="button"
46+
onClick={handleClickDeleteButton}
47+
disabled={isDeletePending}
48+
>
49+
<HiOutlineTrash width="1.2rem" />
50+
삭제
51+
</S.DeleteButton>
52+
</S.UtilButtonsContainer>
53+
</S.CardHeaderContainer>
2054
<S.ResultComment>{evaluationResult.content}</S.ResultComment>
2155
<S.EvaluatorDetailContainer>
2256
{evaluationResult.evaluator && (

frontend/src/components/ApplicantModal/ApplicantEvalInfo/EvaluationCard/style.ts

+41
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,44 @@ const CardContainer = styled.li`
99
background-color: ${({ theme }) => theme.baseColors.grayscale[50]};
1010
`;
1111

12+
const CardHeaderContainer = styled.div`
13+
display: flex;
14+
flex-direction: row;
15+
align-items: center;
16+
justify-content: space-between;
17+
`;
18+
19+
const UtilButtonsContainer = styled.div`
20+
display: flex;
21+
flex-direction: row;
22+
align-items: center;
23+
justify-content: flex-start;
24+
25+
gap: 0.4rem;
26+
`;
27+
28+
const DeleteButton = styled.button`
29+
display: flex;
30+
flex-direction: row;
31+
align-items: center;
32+
justify-content: center;
33+
gap: 0.2rem;
34+
35+
padding: 0.4rem 0.6rem;
36+
border: 1px solid ${({ theme }) => theme.baseColors.grayscale[400]};
37+
border-radius: 0.4rem;
38+
39+
color: ${({ theme }) => theme.colors.text.block};
40+
${({ theme }) => theme.typography.common.small};
41+
cursor: pointer;
42+
transition: 0.3s ease;
43+
44+
&:hover {
45+
border-color: ${({ theme }) => theme.baseColors.redscale[300]};
46+
color: ${({ theme }) => theme.baseColors.redscale[400]};
47+
}
48+
`;
49+
1250
const EvaluatorDetailContainer = styled.div`
1351
display: flex;
1452
flex-direction: row;
@@ -100,6 +138,9 @@ const ResultComment = styled.div`
100138

101139
const S = {
102140
CardContainer,
141+
CardHeaderContainer,
142+
UtilButtonsContainer,
143+
DeleteButton,
103144
EvaluatorDetailContainer,
104145
EvaluatorImagePlaceholder,
105146
EvaluatorDetail,

frontend/src/components/ApplicantModal/ApplicantEvalInfo/EvaluationForm/index.tsx

+5-5
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import { useState } from 'react';
33
import Button from '@components/_common/atoms/Button';
44
import InputField from '@components/_common/molecules/InputField';
55
import TextField from '@components/_common/molecules/TextField';
6+
import Spinner from '@components/_common/atoms/Spinner';
7+
import StarRating from '@components/_common/molecules/StarRating';
68

79
import { validateEvalContent, validateEvaluator } from '@domain/validations/evaluation';
8-
import useEvaluationMutation from '@hooks/useEvaluationMutation';
10+
import { evaluationMutations } from '@hooks/evaluations';
911
import ValidationError from '@utils/errors/ValidationError';
1012

11-
import Spinner from '@components/_common/atoms/Spinner';
12-
import StarRating from '@components/_common/molecules/StarRating';
1313
import { EVALUATION_CONTENT_MAX_LENGTH, EVALUATION_EVALUATOR_MAX_LENGTH } from '../constants';
1414
import S from './style';
1515

@@ -35,7 +35,7 @@ export default function EvaluationForm({ processId, applicantId, onClose }: Eval
3535
const [formState, setFormState] = useState<EvaluationData>({ evaluator: '', score: 0, content: '' });
3636
const [contentErrorMessage, setContentErrorMessage] = useState<string | undefined>();
3737
const [evaluatorErrorMessage, setEvaluatorErrorMessage] = useState<string | undefined>();
38-
const { mutate: submitNewEvaluation, isPending } = useEvaluationMutation({
38+
const { mutate: submitNewEvaluation, isPending } = evaluationMutations.useCreateEvaluation({
3939
processId,
4040
applicantId,
4141
closeOnSuccess: onClose,
@@ -95,7 +95,7 @@ export default function EvaluationForm({ processId, applicantId, onClose }: Eval
9595
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
9696
event.preventDefault();
9797

98-
if (window.confirm('평가를 등록한 후에는 수정하거나 삭제할 수 없습니다.\n등록하시겠습니까?')) {
98+
if (window.confirm('등록된 평가는 수정이 불가능하며, 삭제 후 재작성만 가능합니다.\n이대로 등록하시겠습니까?')) {
9999
submitNewEvaluation({ processId, applicantId, ...formState });
100100
}
101101
};

frontend/src/components/ApplicantModal/ApplicantEvalInfo/index.tsx

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
import { useState } from 'react';
2-
import useEvaluationQuery from '@hooks/useEvaluationQuery';
2+
3+
import { evaluationQueries } from '@hooks/evaluations';
4+
35
import EvaluationForm from './EvaluationForm';
46
import EvaluationAddButton from './EvaluationAddButton';
5-
import S from './style';
67
import EvaluationCard from './EvaluationCard';
78

9+
import S from './style';
10+
811
interface ApplicantEvalInfoProps {
912
applicantId: number;
1013
processId: number;
1114
isCurrentProcess: boolean;
1215
}
1316

1417
export default function ApplicantEvalInfo({ applicantId, processId, isCurrentProcess }: ApplicantEvalInfoProps) {
15-
const { evaluationList } = useEvaluationQuery({ applicantId, processId });
18+
const { evaluationList } = evaluationQueries.useGetEvaluations({ processId, applicantId });
1619
const [isFormOpened, setIsFormOpened] = useState<boolean>(false);
1720

1821
const renderFormSection = () => {
@@ -36,6 +39,9 @@ export default function ApplicantEvalInfo({ applicantId, processId, isCurrentPro
3639
{evaluationList.map((evaluationResult) => (
3740
<EvaluationCard
3841
key={evaluationResult.evaluationId}
42+
evaluationId={evaluationResult.evaluationId}
43+
processId={processId}
44+
applicantId={applicantId}
3945
evaluationResult={evaluationResult}
4046
/>
4147
))}

frontend/src/hooks/evaluations.ts

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
2+
import { useParams } from 'react-router-dom';
3+
4+
import { EvaluationResult } from '@customTypes/applicant';
5+
// import { useToast } from '@contexts/ToastContext';
6+
7+
import evaluationApis from '@api/domain/evaluation';
8+
import QUERY_KEYS from '@hooks/queryKeys';
9+
10+
interface DefaultQueryParams {
11+
processId: number;
12+
applicantId: number;
13+
}
14+
15+
interface UseCreateEvaluationMutationParams extends DefaultQueryParams {
16+
closeOnSuccess: () => void;
17+
}
18+
19+
interface CreateMutationParams extends DefaultQueryParams {
20+
evaluator: string;
21+
score: number;
22+
content: string;
23+
}
24+
25+
function useEvaluationQuery({ processId, applicantId }: DefaultQueryParams) {
26+
return useQuery<{ evaluations: EvaluationResult[] }>({
27+
queryKey: [QUERY_KEYS.EVALUATION, processId, applicantId],
28+
queryFn: () => evaluationApis.get({ processId, applicantId }),
29+
enabled: !!processId && !!applicantId,
30+
});
31+
}
32+
33+
function useEvaluationQueryInvalidation() {
34+
const queryClient = useQueryClient();
35+
const { dashboardId, applyFormId } = useParams() as { dashboardId: string; applyFormId: string };
36+
37+
return (processId: number, applicantId: number) => {
38+
queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.EVALUATION, processId, applicantId] });
39+
queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.DASHBOARD, dashboardId, applyFormId] });
40+
};
41+
}
42+
43+
export const evaluationQueries = {
44+
useGetEvaluations: ({ processId, applicantId }: DefaultQueryParams) => {
45+
const { data, error, isLoading } = useEvaluationQuery({ processId, applicantId });
46+
47+
const evaluations = data?.evaluations || [];
48+
49+
const evaluationList: EvaluationResult[] = evaluations.map((e) => ({
50+
evaluationId: e.evaluationId,
51+
evaluator: e.evaluator ?? undefined,
52+
score: e.score,
53+
content: e.content,
54+
createdDate: e.createdDate ?? undefined,
55+
}));
56+
57+
return {
58+
evaluations,
59+
evaluationList,
60+
error,
61+
isLoading,
62+
};
63+
},
64+
};
65+
66+
export const evaluationMutations = {
67+
useCreateEvaluation: ({ processId, applicantId, closeOnSuccess }: UseCreateEvaluationMutationParams) => {
68+
const invalidateQueries = useEvaluationQueryInvalidation();
69+
/**
70+
* 지원자 상세 모달이 <dialog>로 구현되어, 모달이 toast 메시지를 덮는 문제가 남아있습니다.
71+
* 모달이 <div> 기반으로 재구현될 때까지 toast 메시지 코드를 주석 처리합니다.
72+
* - 25/01/08 아르
73+
*/
74+
// const toast = useToast();
75+
76+
return useMutation({
77+
mutationFn: (params: CreateMutationParams) => evaluationApis.create(params),
78+
onSuccess: () => {
79+
invalidateQueries(processId, applicantId);
80+
closeOnSuccess();
81+
// toast.success('지원자에 대한 평가가 등록되었습니다.');
82+
},
83+
});
84+
},
85+
86+
useDeleteEvaluation: ({ processId, applicantId }: DefaultQueryParams) => {
87+
const invalidateQueries = useEvaluationQueryInvalidation();
88+
/**
89+
* 지원자 상세 모달이 <dialog>로 구현되어, 모달이 toast 메시지를 덮는 문제가 남아있습니다.
90+
* 모달이 <div> 기반으로 재구현될 때까지 toast 메시지 코드를 주석 처리합니다.
91+
* - 25/01/08 아르
92+
*/
93+
// const toast = useToast();
94+
95+
return useMutation({
96+
mutationFn: ({ evaluationId }: { evaluationId: number }) => evaluationApis.delete({ evaluationId }),
97+
onSuccess: () => {
98+
invalidateQueries(processId, applicantId);
99+
// toast.success('지원자에 대한 해당 평가가 삭제되었습니다.');
100+
},
101+
});
102+
},
103+
};

frontend/src/hooks/useEvaluationMutation/index.ts

-36
This file was deleted.

frontend/src/hooks/useEvaluationQuery/index.ts

-35
This file was deleted.

frontend/src/mocks/handlers/evaluationHandlers.ts

+14
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,20 @@ const evaluationHandlers = [
3131
statusText: 'Created',
3232
});
3333
}),
34+
35+
http.delete(`${EVALUATIONS}/:evaluationId`, async ({ params }) => {
36+
if (!params.evaluationId) {
37+
return new Response(null, {
38+
status: 400,
39+
statusText: 'Evaluation Id Not Found',
40+
});
41+
}
42+
43+
return new Response(null, {
44+
status: 204,
45+
statusText: 'No Content',
46+
});
47+
}),
3448
];
3549

3650
export default evaluationHandlers;

0 commit comments

Comments
 (0)