diff --git a/frontend/components/applicant/DetailLeft.component.tsx b/frontend/components/applicant/DetailLeft.component.tsx index 7abe5b3f..f227c47b 100644 --- a/frontend/components/applicant/DetailLeft.component.tsx +++ b/frontend/components/applicant/DetailLeft.component.tsx @@ -24,7 +24,7 @@ const ApplicantDetailLeft = ({ return ( <> - <ApplicantResource data={data} postId={postId} /> + <ApplicantResource data={data} postId={postId} generation={generation} /> <ApplicantLabel postId={postId} generation={generation} /> <ApplicantComment cardId={cardId} diff --git a/frontend/components/applicant/applicantNode/CustomResource.component.tsx b/frontend/components/applicant/applicantNode/CustomResource.component.tsx index d6ff1ccb..ce057872 100644 --- a/frontend/components/applicant/applicantNode/CustomResource.component.tsx +++ b/frontend/components/applicant/applicantNode/CustomResource.component.tsx @@ -1,23 +1,131 @@ import Txt from "@/components/common/Txt.component"; -import { ApplicantReq } from "@/src/apis/applicant"; +import { ApplicantReq, patchApplicantState } from "@/src/apis/applicant"; import { applicantDataFinder } from "@/src/functions/finder"; import Portfolio from "./Portfolio"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { useSearchParams } from "next/navigation"; +import KanbanCardApplicantStatusLabel from "@/components/kanban/card/CardApplicantStatusLabel"; +import { useAtomValue } from "jotai"; +import { KanbanSelectedButtonNumberState } from "@/src/stores/kanban/Navbar.atoms"; +import { getMyInfo } from "@/src/apis/interview"; +import { findApplicantState } from "@/src/utils/applicant"; +import { ApplicantPassState, getKanbanCards } from "@/src/apis/kanban"; + interface ApplicantResourceProps { data: ApplicantReq[]; postId: string; + generation: string; } -const ApplicantResource = ({ data, postId }: ApplicantResourceProps) => { +const ApplicantResource = ({ + data, + postId, + generation, +}: ApplicantResourceProps) => { + const navbarId = useAtomValue(KanbanSelectedButtonNumberState); + const searchParams = useSearchParams(); + const applicantId = searchParams.get("applicantId"); + const queryClient = useQueryClient(); + + const { + data: initialState, + isLoading, + isError, + } = useQuery({ + queryKey: ["applicantState", applicantId, navbarId], + queryFn: async () => + findApplicantState( + await getKanbanCards(navbarId, generation), + `${applicantId}` + ), + staleTime: 3000, + }); + + const { + data: myInfo, + isLoading: myInfoLoading, + isError: myInfoError, + } = useQuery({ + queryKey: ["user"], + queryFn: getMyInfo, + }); + const { mutate } = useMutation({ + mutationFn: (afterState: "non-pass" | "pass") => + patchApplicantState(`${applicantId}`, afterState), + onMutate: async () => { + await queryClient.cancelQueries([ + "applicantState", + applicantId, + navbarId, + ]); + + const snapshotState = queryClient.getQueryData<ApplicantPassState>([ + "applicantState", + applicantId, + navbarId, + ]); + + return { snapshotState }; + }, + onSuccess: () => { + queryClient.invalidateQueries(["kanbanDataArray", generation]); + }, + onError: (error, variables, context) => { + window.alert("상태 변경에 실패했습니다."); + }, + onSettled: () => { + queryClient.invalidateQueries(["applicantState", applicantId, navbarId]); + }, + }); + + const onFailedButtonClick = () => { + mutate("non-pass"); + }; + + const onPassedButtonClick = () => { + mutate("pass"); + }; + + if (!initialState || isLoading || !myInfo || myInfoLoading) { + return <div>로딩중...</div>; + } + + if (isError || myInfoError) { + return <div>에러 발생</div>; + } + return ( <> - <div className="flex flex-col gap-1 mb-2"> - <Txt className="text-xl text-secondary-200 font-medium"> - {applicantDataFinder(data, "major")} - </Txt> - <Txt typography="h2">{`[${applicantDataFinder( - data, - "field" - )}] ${applicantDataFinder(data, "name")}`}</Txt> + <div className="flex justify-between items-center"> + <div className="flex flex-col gap-1 mb-2"> + <div className="flex justify-between items-center"> + <Txt className="text-xl text-secondary-200 font-medium"> + {applicantDataFinder(data, "major")} + </Txt> + <KanbanCardApplicantStatusLabel passState={initialState} /> + </div> + <Txt typography="h2">{`[${applicantDataFinder( + data, + "field" + )}] ${applicantDataFinder(data, "name")}`}</Txt> + </div> + {(myInfo.role === "ROLE_OPERATION" || + myInfo.role === "ROLE_PRESIDENT") && ( + <div className="flex gap-5"> + <button + onClick={onFailedButtonClick} + className="bg-zinc-200 w-20 h-20 hover:bg-sky-400 rounded-xl" + > + 불합격 + </button> + <button + onClick={onPassedButtonClick} + className="bg-zinc-200 w-20 h-20 hover:bg-sky-400 rounded-xl" + > + 합격 + </button> + </div> + )} </div> <div className="flex gap-4 mb-8"> <div className="flex gap-1"> diff --git a/frontend/components/kanban/column/ColumnWithBackButton.component.tsx b/frontend/components/kanban/column/ColumnWithBackButton.component.tsx index 423669d7..89caab0c 100644 --- a/frontend/components/kanban/column/ColumnWithBackButton.component.tsx +++ b/frontend/components/kanban/column/ColumnWithBackButton.component.tsx @@ -30,9 +30,11 @@ const KanbanColumnDetailCard = ({ data: kanbanDataArray, isError, isLoading, - } = useQuery<KanbanColumnData[]>(["kanbanDataArray", generation], () => - getAllKanbanData(navbarId, generation) - ); + } = useQuery<KanbanColumnData[]>({ + queryKey: ["kanbanDataArray", generation], + queryFn: () => getAllKanbanData(navbarId, generation), + staleTime: 3000, + }); if (!kanbanDataArray || isLoading) { return <div>로딩중...</div>; diff --git a/frontend/src/apis/applicant/index.ts b/frontend/src/apis/applicant/index.ts index a2a5db74..355e65dc 100644 --- a/frontend/src/apis/applicant/index.ts +++ b/frontend/src/apis/applicant/index.ts @@ -1,6 +1,7 @@ import { getAllInterviewerWithOrder } from "@/src/apis/interview"; import { APPLICANT_KEYS } from "@/src/constants"; import { https } from "@/src/functions/axios"; +import { ApplicantPassState, getKanbanCards, KanbanCardReq } from "../kanban"; export interface ApplicantReq { name: string; @@ -131,3 +132,18 @@ export const getApplicantTimeTables = async (id: string) => { return data; }; + +interface PatchApplicantStateRes { + passState: ApplicantPassState; +} + +export const patchApplicantState = async ( + id: string, + afterState: "non-pass" | "pass" +) => { + const { data } = await https.patch<PatchApplicantStateRes>( + `/applicants/${id}/state?afterState=${afterState}` + ); + + return data; +}; diff --git a/frontend/src/utils/applicant/index.ts b/frontend/src/utils/applicant/index.ts new file mode 100644 index 00000000..3210c4aa --- /dev/null +++ b/frontend/src/utils/applicant/index.ts @@ -0,0 +1,9 @@ +import { KanbanCardReq } from "@/src/apis/kanban"; + +export const findApplicantState = ( + cardsData: KanbanCardReq[], + applicantId: string +) => { + return cardsData.find((card) => card.applicantId === applicantId)?.state + .passState; +};