Skip to content

Commit 9fff1e0

Browse files
authored
학생 페이지의 취업현황 탭 구현, MOU가 체결되지 않은 기업 등록, 기업에 학생 수동 취업 처리 기능구현 (#169)
2 parents fb1aaa1 + 00b5cea commit 9fff1e0

File tree

26 files changed

+1139
-28
lines changed

26 files changed

+1139
-28
lines changed

apps/admin/src/apis/applications/index.ts

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import type { ApplicationENStatusType } from "@/@types/types";
2-
import { instance, type ApplicationResponse } from "@/apis";
2+
import {
3+
instance,
4+
type ApplicationResponse,
5+
type EmploymentStatsResponse,
6+
} from "@/apis";
37
import { convertObjectToQueryString } from "@/utils";
48
import {
59
useMutation,
@@ -95,3 +99,49 @@ export const useRejectApplication = (
9599
},
96100
});
97101
};
102+
103+
/** 현재 취업 현황 조회 */
104+
export const useEmploymentStats = () => {
105+
return useQuery({
106+
queryKey: ["employmentStats"],
107+
queryFn: async () => {
108+
const { data } = await instance.get<EmploymentStatsResponse>(
109+
`${router}/employment`
110+
);
111+
return data;
112+
},
113+
});
114+
};
115+
116+
/** MOU회사 외 학생 합격처리 */
117+
export const useApproveStudents = (
118+
recruitmentId: number,
119+
studentGcns: Array<number>,
120+
option: MutationOptions
121+
) => {
122+
return useMutation({
123+
...option,
124+
mutationFn: () =>
125+
instance.post(`${router}/teacher/${recruitmentId}`, {
126+
student_gcns: studentGcns.map(e => e.toString()),
127+
}),
128+
onError: (err: AxiosError<AxiosError>) => {
129+
if (err.response) {
130+
switch (err.response.status) {
131+
case 401:
132+
toast.error("3학년이 아닌 학생이 있어요.");
133+
break;
134+
135+
case 404:
136+
toast.error("학생을 찾을 수 없어요.");
137+
break;
138+
139+
case 409:
140+
toast.error("이미 합격처리된 학생이 있어요.");
141+
}
142+
} else {
143+
toast.error("네트워크 연결을 확인해주세요.");
144+
}
145+
},
146+
});
147+
};

apps/admin/src/apis/applications/type.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,16 @@ export type AttachmentUrlType = {
1818
url: string;
1919
type: string;
2020
};
21+
22+
export type EmploymentStatsResponse = {
23+
classes: {
24+
class_id: number;
25+
employment_rate_response_list: {
26+
id: number;
27+
company_name: string;
28+
logo_url: string;
29+
}[];
30+
total_students: number;
31+
passed_students: number;
32+
}[];
33+
};

apps/admin/src/apis/companies/index.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,55 @@ export const useChangeCompanyMOU = (
7777
},
7878
});
7979
};
80+
81+
/** 기업체 등록(선생님) */
82+
export const useRegisterCompany = (
83+
companyName: string,
84+
businessNumber: string,
85+
companyProfileUrl: string,
86+
option: MutationOptions
87+
) => {
88+
return useMutation({
89+
...option,
90+
mutationFn: () =>
91+
instance
92+
.post(`${router}/teacher`, {
93+
company_name: companyName,
94+
business_number: businessNumber,
95+
company_profile_url: companyProfileUrl,
96+
})
97+
.then(async () => {
98+
const res = await instance.get("/recruitments/teacher/manual");
99+
const list: {
100+
id: number;
101+
company_name: string;
102+
company_profile_url: string;
103+
}[] = res.data.recruitments;
104+
105+
const target = list.find(e => e.company_name == companyName);
106+
107+
if (!target) {
108+
throw new Error("기업이 추가되지 않았습니다.");
109+
}
110+
return target.id;
111+
}),
112+
onError: (err: AxiosError<AxiosError>) => {
113+
if (err.response) {
114+
switch (err.response.status) {
115+
case 400:
116+
toast.error("정보를 전부 입력해주세요.");
117+
break;
118+
119+
case 404:
120+
toast.error("유효하지 않은 사업자 등록 번호입니다.");
121+
break;
122+
123+
case 409:
124+
toast.error("이미 존재하는 기업입니다.");
125+
}
126+
} else {
127+
toast.error("네트워크 연결을 확인해주세요.");
128+
}
129+
},
130+
});
131+
};

apps/admin/src/apis/companies/type.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,9 @@ export type CompanyType = {
1616
total_acceptance_count: number;
1717
review_count: number;
1818
};
19+
20+
export type RegisterCompanyRequest = {
21+
companyName: string;
22+
businessNumber: string;
23+
companyProfileUrl: string;
24+
};

apps/admin/src/apis/files/index.ts

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { useMutation } from "@tanstack/react-query";
2-
import axios from "axios";
1+
import { MutationOptions, useMutation } from "@tanstack/react-query";
2+
import axios, { AxiosError } from "axios";
33
import toast from "react-hot-toast";
44
import fileSaver from "file-saver";
55

@@ -36,3 +36,52 @@ export const useDownloadData = () => {
3636
},
3737
});
3838
};
39+
40+
/** S3에 이미지 미리보기 링크 생성 */
41+
export const usePresignLogoFile = (
42+
getFileCallback: (arg: void) => File,
43+
cleanUp: (arg: void) => void,
44+
option: MutationOptions<string>
45+
) => {
46+
const file = getFileCallback();
47+
const presign = async (targetFile: File) => {
48+
const logo = {
49+
type: "LOGO_IMAGE",
50+
file_name: targetFile.name,
51+
};
52+
const { data } = await axios.post<{
53+
urls: {
54+
file_path: string;
55+
pre_signed_url: string;
56+
}[];
57+
}>(`${import.meta.env.VITE_BASE_URL}/files/pre-signed`, {
58+
files: [logo],
59+
});
60+
return { data, presignedFile: targetFile };
61+
};
62+
return useMutation({
63+
...option,
64+
mutationFn: () =>
65+
presign(file).then(({ data, presignedFile }) => {
66+
const url = data.urls[0];
67+
68+
return new Promise<string>(resolve => {
69+
axios
70+
.put(url.pre_signed_url, presignedFile)
71+
.then(() =>
72+
resolve(`${import.meta.env.VITE_FILE_URL}${url.file_path}`)
73+
);
74+
});
75+
}),
76+
onError: (err: AxiosError<AxiosError>) => {
77+
if (err.response) {
78+
if (err.response?.data.message === "Invalid Extension File") {
79+
toast.error("지원하지 않는 형식의 파일입니다.");
80+
}
81+
} else {
82+
toast.error("네트워크 연결을 확인해주세요.");
83+
}
84+
cleanUp();
85+
},
86+
});
87+
};

apps/admin/src/apis/recruitments/index.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { instance } from "@/apis/axios";
22
import { MutationOptions, useMutation, useQuery } from "@tanstack/react-query";
3-
import type { RecruitmentFormResponse } from "@/apis";
3+
import type {
4+
RecruitmentFormResponse,
5+
TeacherRecruitmentListResponse,
6+
} from "@/apis";
47
import type { RecruitmentStatusType, WinterInternType } from "@/@types/types";
58
import toast from "react-hot-toast";
69
import { winterInternStringToBool } from "@/@types/enums";
@@ -58,3 +61,16 @@ export const useChangeRecruitmentsStatus = (
5861
},
5962
});
6063
};
64+
65+
/** 선생님 등록 모집 의뢰서 리스트 조회 */
66+
export const useTeacherRecruitmentList = () => {
67+
return useQuery({
68+
queryKey: ["teacherRecruitmentList"],
69+
queryFn: async () => {
70+
const { data } = await instance.get<TeacherRecruitmentListResponse>(
71+
`${router}/teacher/manual`
72+
);
73+
return data;
74+
},
75+
});
76+
};

apps/admin/src/apis/recruitments/type.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,13 @@ export type RecruitmentFormType = {
1717
status: RecruitmentStatusType;
1818
total_hiring_count: number;
1919
};
20+
21+
export type TeacherRecruitmentListResponse = {
22+
recruitments: TeacherRecruitmentType[];
23+
};
24+
25+
export type TeacherRecruitmentType = {
26+
id: number;
27+
company_name: string;
28+
company_logo_url: string;
29+
};

apps/admin/src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ export * from "./recruitment";
55
export * from "./company";
66
export * from "./application";
77
export * from "./modal";
8+
export * from "./student";
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { useState } from "react";
2+
import { BigButton, Stack } from "@/components";
3+
import { styled } from "styled-components";
4+
import { AddStudent, EmploymentCurrentSituation } from "@/components";
5+
import { useNavigate } from "react-router-dom";
6+
7+
export const EmploymentStatus = () => {
8+
const navigate = useNavigate();
9+
const [isAdd, setIsAdd] = useState(true);
10+
11+
return (
12+
<Stack direction="column" gap={isAdd ? 28 : 36}>
13+
<Stack justify="flex-end" gap={8}>
14+
<BigButton
15+
width={74}
16+
variant={isAdd ? "primary" : "solid"}
17+
onClick={() => setIsAdd(!isAdd)}
18+
>
19+
학생 추가
20+
</BigButton>
21+
<BigButton width={90} onClick={() => navigate("/company-register")}>
22+
기업 추가 +
23+
</BigButton>
24+
</Stack>
25+
{isAdd ? <AddStudent /> : <EmploymentCurrentSituation />}
26+
</Stack>
27+
);
28+
};
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { useTeacherRecruitmentList } from "@/apis";
2+
import { AddStudentRow, Loading } from "@/components";
3+
import { AddStudentCellData } from "@/constants";
4+
import { themes } from "@jobis/design-token";
5+
import { styled } from "styled-components";
6+
7+
export const AddStudent = () => {
8+
const { data, isPending, isRefetching } = useTeacherRecruitmentList();
9+
10+
return (
11+
<Container>
12+
<TableHeading>{AddStudentCellData[0].title}</TableHeading>
13+
{isPending || isRefetching ? (
14+
<Loading />
15+
) : (
16+
data?.recruitments.map((company, index) => (
17+
<AddStudentRow key={index} data={company} />
18+
))
19+
)}
20+
</Container>
21+
);
22+
};
23+
24+
const Container = styled.div`
25+
width: 100%;
26+
margin-bottom: 182px;
27+
`;
28+
29+
const TableHeading = styled.div`
30+
width: 100%;
31+
height: 40px;
32+
padding-left: 58px;
33+
border-bottom: 1px solid ${themes.Color.grayScale[40]};
34+
35+
color: ${themes.Color.grayScale[60]};
36+
`;

0 commit comments

Comments
 (0)