Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 합/불 상태관리 페이지 추가 #205

Merged
merged 22 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
6f7ace8
feat: 합불 관리 페이지 추가
geongyu09 Sep 13, 2024
96a5372
feat: 합불 요청 api 추가
geongyu09 Sep 13, 2024
410a1c9
feat: 합불 상태관리 네브바 추가
geongyu09 Sep 15, 2024
289604c
feat: pass state관련 api 로직 추가
geongyu09 Sep 15, 2024
023a3d4
design: 레이아웃 수정
geongyu09 Sep 15, 2024
bd64439
feat: 분야별 정렬 기능 추가
geongyu09 Sep 15, 2024
38890af
feat: TODO 작성
geongyu09 Sep 15, 2024
0c4a80c
refactor: 유틸 함수 function으로 옮김
geongyu09 Sep 15, 2024
9738a23
fix: 빌드 버그 수정
geongyu09 Sep 15, 2024
7cae5fa
fix: tailwind가 동적으로 적용이 되지 않던 버그 수정
geongyu09 Sep 15, 2024
192cb56
refactor: url을 기존 코딩 스타일에 맞추어 변경
geongyu09 Sep 16, 2024
7ec8cc1
refactor: 불필요한 조건문 제거
geongyu09 Sep 16, 2024
10af5ee
refactor: searchParams를 삼항연산자가 아닌 URLSearchParams로 변경
geongyu09 Sep 16, 2024
c6a4cd3
fix: query 문의 loading상태를 보여주도록 수정
geongyu09 Sep 16, 2024
ebb1b9a
refactor: 올바르게 카멜케이스가 되어있지 않던 부분 수정
geongyu09 Sep 16, 2024
26a4913
feat: query문 불필요한 추상화 제거
geongyu09 Sep 16, 2024
325aa41
refactor: 기존에 정의해둔 tailwind custom styles 사용
geongyu09 Sep 16, 2024
8d852bb
refactor: getApplicantPassState 유틸 함수에 불필요하게 적용된 switch-case 문을 객체로 변경
geongyu09 Sep 16, 2024
c522277
refactor: 합격 상태에 따른 비교 계층 변경
geongyu09 Sep 16, 2024
836ca3d
refactor: 불필요하게applicants에 조건문을 사용하던 부분 수정
geongyu09 Sep 16, 2024
e266f6a
fix: 분야별 정렬 부분에 들어가는 이미지에 alt 추가
geongyu09 Sep 16, 2024
46c03d1
feat: 목 데이터를 지우고 실제 서버와 통신하도록 수정
geongyu09 Sep 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions frontend/app/(WithNavbar)/pass-state/[generation]/page.tsx
Original file line number Diff line number Diff line change
@@ -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";
};
}
Comment on lines +7 to +11
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

next를 쓰고 있다는게 새삼스럽게 느껴지네요 ㅋㅋㅋ


const PassStatePage = ({ searchParams: { sortedBy } }: PassStatePageProps) => {
return (
<div className="flex-1 ml-32 min-w-[46rem] mb-12">
<div className="mt-20" />
<Txt typography="h3">
{CURRENT_GENERATION}기 지원자 합격 상태 관리 페이지
</Txt>
<div className="mt-8" />
{sortedBy === "field" && (
<Link href={`/pass-state/${CURRENT_GENERATION}`}>
<Txt typography="h5" className="text-secondary-200">
현재 분야별로 정렬되어 있습니다. (WEB - APP - AI - GAME)
</Txt>
<div className="mt-8" />
</Link>
)}
<div className="grid grid-cols-[8fr_8fr_4fr_3fr] gap-4">
<Txt typography="h6" className="text-left text-secondary-100">
지원자 이름
</Txt>
<Link
href={`/pass-state/${CURRENT_GENERATION}?${new URLSearchParams(
"sortedBy=field"
)}`}
className="text-start flex justify-between pr-4 items-center"
>
<Txt typography="h6" className="text-left text-secondary-100">
분야
</Txt>
<Image
src={"/icons/chevron-down.svg"}
alt="sort by field"
width={16}
height={16}
/>
</Link>
<Txt typography="h6" className="text-left text-secondary-100">
합격 상태
</Txt>
</div>
<div className="mt-2" />
<hr />
<div className="mt-4" />
<ApplicantsList sortedBy={sortedBy} />
</div>
);
};

export default PassStatePage;
1 change: 1 addition & 0 deletions frontend/components/common/Txt.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const colorType = {
white: "text-white",
blue: "text-primary",
light_gray: "text-secondary-200",
red: "text-error",
};

interface TxtProps extends PropsWithChildren {
Expand Down
4 changes: 3 additions & 1 deletion frontend/components/common/navbar/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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("/");
Expand All @@ -31,7 +32,8 @@ const CommonNavbar = () => {
/>
))}
<NavbarGenerationToggle generation={generation} isShort={isShort} />
<NavbarOperation currentType={currentType} />
<NavbarManagePassState currentType={currentType} isShort={isShort} />
<NavbarOperation currentType={currentType} isShort={isShort} />
Comment on lines 34 to +36
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

죄송합니다.. 제가 isShort라는 괴물을 키웠군요...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@loopy-lim ㅋㅋㅋㅋㅋㅋㅋ

</div>
<NavbarUserInfo />
</nav>
Expand Down
38 changes: 38 additions & 0 deletions frontend/components/common/navbar/NavbarManagePassState.tsx
Original file line number Diff line number Diff line change
@@ -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 <div>Loading...</div>;
}

if (userData.role !== "ROLE_OPERATION") {
return <div>관리자만 접근이 가능합니다.</div>;
}

return (
<CommonNavbarCell
currentType={currentType}
isShort={isShort}
href={`/pass-state/${generation}`}
short_title="합/불 관리"
title="합/불 상태 관리"
target="_self"
type="pass-state"
/>
);
};
8 changes: 6 additions & 2 deletions frontend/components/common/navbar/NavbarOperation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -23,7 +27,7 @@ export const NavbarOperation = ({ currentType }: NavbarOperationProps) => {
return (
<CommonNavbarCell
currentType={currentType}
isShort={false}
isShort={isShort}
href={`/admin/${generation}`}
short_title="관리자"
title="관리자 페이지"
Expand Down
161 changes: 161 additions & 0 deletions frontend/components/passState/ApplicantsList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
"use client";

import {
getAllApplicantsWithPassState,
postApplicantPassState,
} from "@/src/apis/passState";
import { CURRENT_GENERATION } from "@/src/constants";
import { usePathname } from "next/navigation";
import Txt from "../common/Txt.component";
import { getApplicantPassState } from "@/src/functions/formatter";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import type {
PostApplicantPassStateParams,
Answer,
} from "@/src/apis/passState";

function sortApplicantsByField1(applicants: Answer[]) {
const passStateOrder = {
"final-passed": 0,
"first-passed": 1,
"non-passed": 2,
"non-processed": 3,
};

const field1Order = {
WEB: 0,
APP: 1,
AI: 2,
GAME: 3,
};

return applicants.sort((a, b) => {
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];
});
}
Comment on lines +17 to +42
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

나중에 시간이 되신다면 함수형으로 다시 작성해보는 것도 이쁘지 않을까 라는 생각이 듭니다. 특히 filter에 대한 좋은 방법론들이 많기 때문에 해보는 것을 추천드립니다.(먼가 힘들다 싶으면 backend친구들에게 요청해보세요. 그들은 filter전문가들이에요)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 해당 부분도 고려해보겠습니다!!


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 <div>현재 지원중인 기수만 확인 가능합니다.</div>;
}

if (isLoading) {
return <div>Loading...</div>;
}

if (isError) {
return <div>Error</div>;
}

if (!allApplicants) {
return <div>아직은 지원자가 없습니다 🥲</div>;
}
}

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 (
<ul className="flex flex-col gap-4">
{applicants.map(
({ state: { passState }, field, field1, field2, id, name }) => (
<li
key={id}
className="grid grid-cols-[8fr_8fr_4fr_3fr] gap-4 items-center"
>
Comment on lines +107 to +110
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

먼가 마스터한 자의 css가 보이는 듯 하다....ㄷㄷㄷ

<Txt typography="h6" className="text-left truncate">
{`[${field}] ${name}`}
</Txt>
<Txt
className="text-left truncate"
color="gray"
>{`${field1}/${field2}`}</Txt>
<Txt
className="text-left truncate"
color={
passState === "non-passed"
? "red"
: passState === "final-passed"
? "blue"
: "black"
}
>
{getApplicantPassState(passState)}
</Txt>
<div className="flex justify-between">
<button
className="border px-4 py-2 rounded-lg truncate hover:bg-primary-100"
onClick={() =>
onChangeApplicantsPassState(name, {
applicantsId: id,
afterState: "pass",
})
}
>
합격
</button>
<button
className="border px-4 rounded-lg truncate hover:bg-primary-100"
onClick={() =>
onChangeApplicantsPassState(name, {
applicantsId: id,
afterState: "non-pass",
})
}
>
불합격
</button>
</div>
</li>
)
)}
</ul>
);
};

export default ApplicantsList;
Loading