diff --git a/frontend/app/(WithNavbar)/pass-state/[generation]/page.tsx b/frontend/app/(WithNavbar)/pass-state/[generation]/page.tsx new file mode 100644 index 00000000..6decf046 --- /dev/null +++ b/frontend/app/(WithNavbar)/pass-state/[generation]/page.tsx @@ -0,0 +1,61 @@ +import Txt from "@/components/common/Txt.component"; +import ApplicantsList from "@/components/passState/ApplicantsList"; +import { CURRENT_GENERATION } from "@/src/constants"; +import Image from "next/image"; +import Link from "next/link"; + +interface PassStatePageProps { + searchParams: { + sortedBy?: "field"; + }; +} + +const PassStatePage = ({ searchParams: { sortedBy } }: PassStatePageProps) => { + return ( +
+
+ + {CURRENT_GENERATION}기 지원자 합격 상태 관리 페이지 + +
+ {sortedBy === "field" && ( + + + 현재 분야별로 정렬되어 있습니다. (WEB - APP - AI - GAME) + +
+ + )} +
+ + 지원자 이름 + + + + 분야 + + sort by field + + + 합격 상태 + +
+
+
+
+ +
+ ); +}; + +export default PassStatePage; diff --git a/frontend/components/common/Txt.component.tsx b/frontend/components/common/Txt.component.tsx index 4cb918a8..c33a0b40 100644 --- a/frontend/components/common/Txt.component.tsx +++ b/frontend/components/common/Txt.component.tsx @@ -18,6 +18,7 @@ const colorType = { white: "text-white", blue: "text-primary", light_gray: "text-secondary-200", + red: "text-error", }; interface TxtProps extends PropsWithChildren { diff --git a/frontend/components/common/navbar/Navbar.tsx b/frontend/components/common/navbar/Navbar.tsx index 903dc90f..b02c756c 100644 --- a/frontend/components/common/navbar/Navbar.tsx +++ b/frontend/components/common/navbar/Navbar.tsx @@ -6,6 +6,7 @@ import { NavbarOperation } from "./NavbarOperation"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { NavbarGenerationToggle } from "./NavbarGenerationToggle"; +import { NavbarManagePassState } from "./NavbarManagePassState"; const CommonNavbar = () => { const [_, currentType, generation] = usePathname().split("/"); @@ -31,7 +32,8 @@ const CommonNavbar = () => { /> ))} - + +
diff --git a/frontend/components/common/navbar/NavbarManagePassState.tsx b/frontend/components/common/navbar/NavbarManagePassState.tsx new file mode 100644 index 00000000..80290883 --- /dev/null +++ b/frontend/components/common/navbar/NavbarManagePassState.tsx @@ -0,0 +1,38 @@ +"use client"; + +import { useQuery } from "@tanstack/react-query"; +import CommonNavbarCell from "./NavbarCell"; +import { getMyInfo } from "@/src/apis/interview"; +import { usePathname } from "next/navigation"; + +type NavbarManagePassStateProps = { + isShort: boolean; + currentType: string; +}; +export const NavbarManagePassState = ({ + currentType, + isShort, +}: NavbarManagePassStateProps) => { + const currentPath = usePathname(); + const generation = currentPath.split("/")[2]; + const { data: userData, isLoading } = useQuery(["user"], getMyInfo); + if (isLoading || !userData) { + return
Loading...
; + } + + if (userData.role !== "ROLE_OPERATION") { + return
관리자만 접근이 가능합니다.
; + } + + return ( + + ); +}; diff --git a/frontend/components/common/navbar/NavbarOperation.tsx b/frontend/components/common/navbar/NavbarOperation.tsx index 9b491e5e..d27374a2 100644 --- a/frontend/components/common/navbar/NavbarOperation.tsx +++ b/frontend/components/common/navbar/NavbarOperation.tsx @@ -7,8 +7,12 @@ import { usePathname } from "next/navigation"; type NavbarOperationProps = { currentType: string; + isShort: boolean; }; -export const NavbarOperation = ({ currentType }: NavbarOperationProps) => { +export const NavbarOperation = ({ + currentType, + isShort, +}: NavbarOperationProps) => { const currentPath = usePathname(); const generation = currentPath.split("/")[2]; const { data: userData } = useQuery(["user"], getMyInfo); @@ -23,7 +27,7 @@ export const NavbarOperation = ({ currentType }: NavbarOperationProps) => { return ( { + if ( + passStateOrder[a.state.passState] !== passStateOrder[b.state.passState] + ) { + return ( + passStateOrder[a.state.passState] - passStateOrder[b.state.passState] + ); + } + return field1Order[a.field1] - field1Order[b.field1]; + }); +} + +interface ApplicantsListProps { + sortedBy?: "position" | "field"; +} +const ApplicantsList = ({ sortedBy }: ApplicantsListProps) => { + const selectedGeneration = usePathname().split("/")[2]; + const queryClient = useQueryClient(); + + const { + data: allApplicants, + isLoading, + isError, + } = useQuery(["allApplicantsWithPassState", selectedGeneration], () => + getAllApplicantsWithPassState(selectedGeneration) + ); + + const { mutate: updateApplicantPassState } = useMutation({ + mutationFn: (params: PostApplicantPassStateParams) => + postApplicantPassState(params), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ["allApplicantsWithPassState", selectedGeneration], + }); + }, + }); + + { + if (+selectedGeneration !== CURRENT_GENERATION) { + return
현재 지원중인 기수만 확인 가능합니다.
; + } + + if (isLoading) { + return
Loading...
; + } + + if (isError) { + return
Error
; + } + + if (!allApplicants) { + return
아직은 지원자가 없습니다 🥲
; + } + } + + const onChangeApplicantsPassState = ( + applicantName: string, + params: PostApplicantPassStateParams + ) => { + const ok = confirm( + `${applicantName}님을 ${params.afterState} 처리하시겠습니까?` + ); + if (!ok) return; + updateApplicantPassState(params); + }; + + const applicants = + sortedBy === "field" + ? sortApplicantsByField1(allApplicants) + : allApplicants; + + return ( +
    + {applicants.map( + ({ state: { passState }, field, field1, field2, id, name }) => ( +
  • + + {`[${field}] ${name}`} + + {`${field1}/${field2}`} + + {getApplicantPassState(passState)} + +
    + + +
    +
  • + ) + )} +
+ ); +}; + +export default ApplicantsList; diff --git a/frontend/src/apis/passState/index.tsx b/frontend/src/apis/passState/index.tsx new file mode 100644 index 00000000..dd7e2f1e --- /dev/null +++ b/frontend/src/apis/passState/index.tsx @@ -0,0 +1,36 @@ +import { https } from "../../functions/axios"; +import { ApplicantPassState } from "../kanban"; + +export interface Answer { + field: "개발자" | "디자이너" | "기획자"; + field1: "APP" | "WEB" | "AI" | "GAME"; + field2: "APP" | "WEB" | "AI" | "GAME" | "선택 없음"; + name: string; + id: string; + year: number; + state: { + passState: ApplicantPassState; + }; +} + +export const getAllApplicantsWithPassState = async (generation: string) => { + // TODO: 머지 하기 전 주석 해제 및 목데이터 삭제 + const { data } = await https.get( + `year/${generation}/applicants/pass-state?order=newest` + ); + + return data; +}; + +export interface PostApplicantPassStateParams { + applicantsId: string; + afterState: "non-pass" | "pass"; +} +export const postApplicantPassState = async ({ + afterState, + applicantsId, +}: PostApplicantPassStateParams) => { + await https.post( + `/applicants/${applicantsId}/state?afterState=${afterState}` + ); +}; diff --git a/frontend/src/constants/index.ts b/frontend/src/constants/index.ts index 3176cdd6..394f705e 100644 --- a/frontend/src/constants/index.ts +++ b/frontend/src/constants/index.ts @@ -43,7 +43,8 @@ export interface NavbarItem { | "applicant" | "sharepoint" | "admin" - | "toggle"; + | "toggle" + | "pass-state"; target?: "_blank" | "_self" | "_parent" | "_top"; href: string; } @@ -74,13 +75,13 @@ export const MainNavbar = (generation: number): NavbarItem[] => [ type: "applicant", href: `/applicant/${generation}`, }, - { - title: "신입모집 쉐어 포인트", - short_title: "쉐어포인트", - type: "sharepoint", - target: "_blank", - href: "https://ejnu.sharepoint.com/sites/msteams_bbf640/Shared%20Documents/Forms/AllItems.aspx?id=%2Fsites%2Fmsteams_bbf640%2FShared%20Documents%2F2023%EB%85%84%2F1%ED%95%99%EA%B8%B0%2F%EC%8B%A0%EC%9E%85%EB%AA%A8%EC%A7%91&p=true&ga=1", - }, + // { + // title: "신입모집 쉐어 포인트", + // short_title: "쉐어포인트", + // type: "sharepoint", + // target: "_blank", + // href: "https://ejnu.sharepoint.com/sites/msteams_bbf640/Shared%20Documents/Forms/AllItems.aspx?id=%2Fsites%2Fmsteams_bbf640%2FShared%20Documents%2F2023%EB%85%84%2F1%ED%95%99%EA%B8%B0%2F%EC%8B%A0%EC%9E%85%EB%AA%A8%EC%A7%91&p=true&ga=1", + // }, ]; export const APPLICANT_KEYS = [ diff --git a/frontend/src/functions/formatter.ts b/frontend/src/functions/formatter.ts index d4db27b9..75d4dfb0 100644 --- a/frontend/src/functions/formatter.ts +++ b/frontend/src/functions/formatter.ts @@ -1,3 +1,4 @@ +import { ApplicantPassState } from "../apis/kanban"; import { Score, ScoreKeyword } from "../constants/applicant/28"; export const scoreListToObject = ( @@ -24,3 +25,14 @@ export const scoreObjectToList = (scores: Score[]) => { [] ); }; + +export function getApplicantPassState(passState: ApplicantPassState) { + const passStateMap = { + "non-processed": "처리중", + "first-passed": "1차 합격", + "final-passed": "최종 합격", + "non-passed": "탈락", + }; + + return passStateMap[passState]; +}