diff --git a/app/(business)/business/applicant-management/[jobPostResumeRelationId]/page.ts b/app/(business)/business/applicant-management/[jobPostResumeRelationId]/page.ts new file mode 100644 index 00000000..6f0d6e58 --- /dev/null +++ b/app/(business)/business/applicant-management/[jobPostResumeRelationId]/page.ts @@ -0,0 +1 @@ +export { ApplicantAnagementDetailPage as default } from 'pages/applicant-management' diff --git a/app/globals.css b/app/globals.css index 0315f3ba..94e45fd2 100644 --- a/app/globals.css +++ b/app/globals.css @@ -75,19 +75,33 @@ button { overflow: auto; } - .scrollbar::-webkit-scrollbar { + .scrollbar.gutter-stable { + scrollbar-gutter: stable; + } + + .scrollbar.light::-webkit-scrollbar { + @apply h-2 w-2 bg-transparent; + } + + .scrollbar.light::-webkit-scrollbar-thumb { + background-clip: padding-box; + border: 2px solid transparent; + @apply rounded-full bg-gray-300; + } + + .scrollbar.dark::-webkit-scrollbar { @apply h-3 w-3 bg-white; } - .scrollbar::-webkit-scrollbar-thumb { + .scrollbar.dark::-webkit-scrollbar-thumb { @apply rounded-full border border-solid border-transparent bg-gray-700 bg-clip-content; } - .scrollbar::-webkit-scrollbar:vertical { + .scrollbar.dark::-webkit-scrollbar:vertical { @apply border-0 border-l border-solid border-l-gray-300; } - .scrollbar::-webkit-scrollbar:horizontal { + .scrollbar.dark::-webkit-scrollbar:horizontal { @apply border-0 border-t border-solid border-t-gray-300; } } diff --git a/src/entities/job-post-resume-relation/api/get-job-post-resume.ts b/src/entities/job-post-resume-relation/api/get-job-post-resume.ts index 35dcc170..30c300fc 100644 --- a/src/entities/job-post-resume-relation/api/get-job-post-resume.ts +++ b/src/entities/job-post-resume-relation/api/get-job-post-resume.ts @@ -1,41 +1,23 @@ 'use server' -import { getBusinessSession, getTeacherSession } from 'entities/auth' -import { apiClient, Pagination, PaginationParams } from 'shared/api' +import { getBusinessSession } from 'entities/auth' +import { apiClient } from 'shared/api' -import { JobPostRelation } from '../model/application' -import { ApplicationStatus } from '../model/status' +import { JobPostRelationDetail } from '../model/application' -export type GetJobPostResumeRequest = PaginationParams<{ - status?: ApplicationStatus -}> - -type GetJobPostResumeResponse = Pagination - -export const getTeacherJobPostResumeRelations = async ( - queryParams: GetJobPostResumeRequest, -) => { - const { accessToken } = await getTeacherSession() - - const response = await apiClient.get({ - endpoint: '/job-post-resume-relations', - queryParams, - option: { - authorization: `Bearer ${accessToken}`, - }, - }) - - return response +export type GetJobPostResumeRequest = { + jobPostResumeRelationId: number } -export const getBusinessJobPostResumeRelations = async ( - queryParams: GetJobPostResumeRequest, -) => { +type GetJobPostResumeResponse = JobPostRelationDetail + +export const getBusinessJobPostResumeRelation = async ({ + jobPostResumeRelationId, +}: GetJobPostResumeRequest) => { const { accessToken } = await getBusinessSession() const response = await apiClient.get({ - endpoint: '/job-post-resume-relations', - queryParams, + endpoint: `/job-post-resume-relations/${jobPostResumeRelationId}`, option: { authorization: `Bearer ${accessToken}`, }, diff --git a/src/entities/job-post-resume-relation/api/get-job-post-resumes.ts b/src/entities/job-post-resume-relation/api/get-job-post-resumes.ts new file mode 100644 index 00000000..4ef54ece --- /dev/null +++ b/src/entities/job-post-resume-relation/api/get-job-post-resumes.ts @@ -0,0 +1,47 @@ +'use server' + +import { getBusinessSession, getTeacherSession } from 'entities/auth' +import { apiClient, Pagination, PaginationParams } from 'shared/api' + +import { JobPostRelation } from '../model/application' +import { ApplicationStatus } from '../model/status' + +export type GetJobPostResumeRequest = PaginationParams<{ + status?: ApplicationStatus +}> + +type GetJobPostResumeResponse = Pagination + +export const getTeacherJobPostResumeRelations = async ( + queryParams: GetJobPostResumeRequest, +) => { + const { accessToken } = await getTeacherSession() + + const response = await apiClient.get({ + endpoint: '/job-post-resume-relations', + queryParams, + option: { + authorization: `Bearer ${accessToken}`, + }, + }) + + return response +} + +export const getBusinessJobPostResumeRelations = async ( + queryParams: GetJobPostResumeRequest, +) => { + const { accessToken } = await getBusinessSession() + + console.log(accessToken) + + const response = await apiClient.get({ + endpoint: '/job-post-resume-relations', + queryParams, + option: { + authorization: `Bearer ${accessToken}`, + }, + }) + + return response +} diff --git a/src/entities/job-post-resume-relation/api/query.ts b/src/entities/job-post-resume-relation/api/query.ts index c3eac487..94be127c 100644 --- a/src/entities/job-post-resume-relation/api/query.ts +++ b/src/entities/job-post-resume-relation/api/query.ts @@ -4,7 +4,7 @@ import { getBusinessJobPostResumeRelations, getTeacherJobPostResumeRelations, GetJobPostResumeRequest, -} from './get-job-post-resume' +} from './get-job-post-resumes' export const jobPostResumeRelationQueries = { all: () => ['job-post-resume-relation'], diff --git a/src/entities/job-post-resume-relation/index.ts b/src/entities/job-post-resume-relation/index.ts index 679843aa..1d36be4d 100644 --- a/src/entities/job-post-resume-relation/index.ts +++ b/src/entities/job-post-resume-relation/index.ts @@ -1,8 +1,10 @@ export { getJobPostResumeSummary } from './api/get-job-post-resume-summary' export { getJobPostResumeByCode } from './api/get-job-post-resume-by-code' +export { getBusinessJobPostResumeRelation } from './api/get-job-post-resume' export { jobPostResumeRelationQueries } from './api/query' export { ApplicationStatus, type StatusSummary } from './model/status' export type { JobPostRelationDetail } from './model/application' export { HistoryPanel } from './ui/history-panel' export { StatusPanel } from './ui/status-panel' export { ApplicationTable } from './ui/application-table' +export { ApplicationStatusSelect } from './ui/application-status-select' diff --git a/src/entities/job-post-resume-relation/model/application.ts b/src/entities/job-post-resume-relation/model/application.ts index cddcee22..704564e4 100644 --- a/src/entities/job-post-resume-relation/model/application.ts +++ b/src/entities/job-post-resume-relation/model/application.ts @@ -5,7 +5,6 @@ export type JobPostRelation = { coverLetter: string status: ApplicationStatus submittedDate: string - resumeId: number resumeTitle: string resumeFirstName: string resumeLastName: string @@ -21,6 +20,7 @@ export type JobPostRelationDetail = { coverLetter: string status: ApplicationStatus submittedDate: string + academyMemo: string | null resumeTitle: string personalIntroduction: string firstName: string diff --git a/src/entities/job-post-resume-relation/ui/application-status-select/index.tsx b/src/entities/job-post-resume-relation/ui/application-status-select/index.tsx new file mode 100644 index 00000000..437f6ca2 --- /dev/null +++ b/src/entities/job-post-resume-relation/ui/application-status-select/index.tsx @@ -0,0 +1,30 @@ +import { ApplicationStatus } from 'entities/job-post-resume-relation' +import { Form, FormSelectProps } from 'shared/form' + +const StatusLabel = { + [ApplicationStatus.SUBMITTED]: '접수', + [ApplicationStatus.REVIEWED]: '검토', + [ApplicationStatus.ACCEPTED]: '합격', + [ApplicationStatus.REJECTED]: '불합격', +} + +export const ApplicationStatusSelect = (props: FormSelectProps) => { + return ( + StatusLabel[value as ApplicationStatus]} + {...props} + > + + 접수 + + 검토 + 합격 + + 불합격 + + + ) +} diff --git a/src/features/application-side-panel/index.ts b/src/features/application-side-panel/index.ts new file mode 100644 index 00000000..8fd6e8f6 --- /dev/null +++ b/src/features/application-side-panel/index.ts @@ -0,0 +1 @@ +export { ApplicationSidePanel } from './ui/side-panel' diff --git a/src/features/application-side-panel/ui/side-panel.tsx b/src/features/application-side-panel/ui/side-panel.tsx new file mode 100644 index 00000000..83887512 --- /dev/null +++ b/src/features/application-side-panel/ui/side-panel.tsx @@ -0,0 +1,56 @@ +'use client' + +import { useForm } from 'react-hook-form' + +import { + ApplicationStatus, + ApplicationStatusSelect, +} from 'entities/job-post-resume-relation' +import { Form } from 'shared/form' +import { Button, Separator } from 'shared/ui' + +type Props = { + values: { + status?: ApplicationStatus + memo?: string + } +} + +export const ApplicationSidePanel = ({ values }: Props) => { + const form = useForm({ + values, + }) + + return ( +
+
+
+ + + + +
+

+ 채용 단계를 변경하면 지원자에게 알림으로 결과를 알려줘요 +

+ +
+ + + + +
+ + +
+ ) +} diff --git a/src/pages/applicant-management/index.ts b/src/pages/applicant-management/index.ts index 9fa50ef8..512e57f6 100644 --- a/src/pages/applicant-management/index.ts +++ b/src/pages/applicant-management/index.ts @@ -1 +1,2 @@ export { ApplicantManagementListPage } from './ui/applicant-management-list-page' +export { ApplicantAnagementDetailPage } from './ui/applicant-management-detail-page' diff --git a/src/pages/applicant-management/ui/applicant-management-detail-page.tsx b/src/pages/applicant-management/ui/applicant-management-detail-page.tsx new file mode 100644 index 00000000..780381b2 --- /dev/null +++ b/src/pages/applicant-management/ui/applicant-management-detail-page.tsx @@ -0,0 +1,31 @@ +import { getBusinessJobPostResumeRelation } from 'entities/job-post-resume-relation' +import { Layout } from 'shared/ui' +import { FileResume, FormResume } from 'widgets/application-resume' + +type Params = { + jobPostResumeRelationId: string +} + +export const ApplicantAnagementDetailPage = async ({ + params, +}: { + params: Promise +}) => { + const { jobPostResumeRelationId } = await params + + const jobPostResumeRelation = await getBusinessJobPostResumeRelation({ + jobPostResumeRelationId: Number(jobPostResumeRelationId), + }) + + const isFileResume = jobPostResumeRelation.filePath !== null + + return ( + + {isFileResume ? ( + + ) : ( + + )} + + ) +} diff --git a/src/shared/config/middleware/with-business-auth.ts b/src/shared/config/middleware/with-business-auth.ts index 1ce956e7..24cf41b6 100644 --- a/src/shared/config/middleware/with-business-auth.ts +++ b/src/shared/config/middleware/with-business-auth.ts @@ -5,7 +5,10 @@ import { getBusinessSession } from 'auth' import { MiddlewareFactory } from './type' -const matchersForAuth = ['/business/setting/*path'] +const matchersForAuth = [ + '/business/setting/*path', + '/business/applicant-management/*path', +] const matchersForSignIn = ['/business/sign-up', '/business/sign-in'] export const withBusinessAuth: MiddlewareFactory = next => { diff --git a/src/shared/ui/dropdown/variants.ts b/src/shared/ui/dropdown/variants.ts index 6104274a..1ec35b73 100644 --- a/src/shared/ui/dropdown/variants.ts +++ b/src/shared/ui/dropdown/variants.ts @@ -19,7 +19,7 @@ export const dropdownWrapper = cva( export const dropdown = cva('', { variants: { scrollable: { - true: 'scrollbar overflow-auto', + true: 'scrollbar dark overflow-auto', false: 'overflow-visible', }, }, diff --git a/src/shared/ui/select-field/select.tsx b/src/shared/ui/select-field/select.tsx index ab623d82..2b3a1c1f 100644 --- a/src/shared/ui/select-field/select.tsx +++ b/src/shared/ui/select-field/select.tsx @@ -12,7 +12,6 @@ import { DropdownRootProps, } from '../dropdown/dropdown' import { Icon } from '../icon' - import SelectProvider, { useSelectContext } from './context' import { SelectVariants } from './variants' import * as css from './variants' @@ -166,7 +165,7 @@ const SelectRoot = ({ )} diff --git a/src/shared/ui/select-field/variants.ts b/src/shared/ui/select-field/variants.ts index ff09e49b..0b985585 100644 --- a/src/shared/ui/select-field/variants.ts +++ b/src/shared/ui/select-field/variants.ts @@ -14,9 +14,9 @@ export const selected = cva( { variants: { size: { - small: 'body-small min-h-8 px-3 py-2', - medium: 'body-large min-h-9 px-3 py-2', - large: 'body-large min-h-12 p-3', + small: 'body-small min-h-[30px] px-[11px] py-[6px]', + medium: 'body-large min-h-[36px] px-[11px] py-[7px]', + large: 'body-large min-h-[46px] px-[11px] py-[10px]', }, error: { true: '', @@ -84,6 +84,11 @@ export const arrowIcon = cva( true: 'rotate-180 transform', false: 'rotate-0', }, + size: { + small: 'h-4 w-4', + medium: 'h-5 w-5', + large: 'h-6 w-6', + }, }, }, ) diff --git a/src/widgets/application-resume/index.ts b/src/widgets/application-resume/index.ts new file mode 100644 index 00000000..81e8ca83 --- /dev/null +++ b/src/widgets/application-resume/index.ts @@ -0,0 +1,6 @@ +export { CoverLetter } from './ui/cover-letter' +export { FileResume } from './ui/file-resume' +export { FormResume } from './ui/form-resume' +export { Header } from './ui/header' +export { Introduction } from './ui/introduction' +export { PersonalInformation } from './ui/personal-information' diff --git a/src/widgets/application-resume/ui/cover-letter.tsx b/src/widgets/application-resume/ui/cover-letter.tsx new file mode 100644 index 00000000..3a7f6efa --- /dev/null +++ b/src/widgets/application-resume/ui/cover-letter.tsx @@ -0,0 +1,16 @@ +type Props = { + coverLetter: string +} + +export const CoverLetter = ({ coverLetter }: Props) => { + return ( +
+

+ Cover Letter +

+
+ {coverLetter} +
+
+ ) +} diff --git a/src/widgets/application-resume/ui/file-resume.tsx b/src/widgets/application-resume/ui/file-resume.tsx new file mode 100644 index 00000000..a3e3e61e --- /dev/null +++ b/src/widgets/application-resume/ui/file-resume.tsx @@ -0,0 +1,36 @@ +import { JobPostRelationDetail } from 'entities/job-post-resume-relation' +import { ApplicationSidePanel } from 'features/application-side-panel' +import { PDFViewer } from 'features/show-resume-file' + +import { CoverLetter } from './cover-letter' +import { Header } from './header' + +export const FileResume = ({ + jobPostResumeRelation, +}: { + jobPostResumeRelation: JobPostRelationDetail +}) => { + return ( +
+
+
+ + {jobPostResumeRelation.coverLetter && ( + + )} +
+ +
+ ) +} diff --git a/src/widgets/application-resume/ui/form-resume.tsx b/src/widgets/application-resume/ui/form-resume.tsx new file mode 100644 index 00000000..f99ede69 --- /dev/null +++ b/src/widgets/application-resume/ui/form-resume.tsx @@ -0,0 +1,42 @@ +import { JobPostRelationDetail } from 'entities/job-post-resume-relation' +import { ApplicationSidePanel } from 'features/application-side-panel' + +import { CoverLetter } from './cover-letter' +import { Header } from './header' +import { Introduction } from './introduction' +import { PersonalInformation } from './personal-information' + +type Props = { + jobPostResumeRelation: JobPostRelationDetail +} + +export const FormResume = ({ jobPostResumeRelation }: Props) => { + return ( + <> +
+
+ +
+
+ + + {jobPostResumeRelation.coverLetter && ( + + )} +
+ +
+
+ + ) +} diff --git a/src/widgets/application-resume/ui/header.tsx b/src/widgets/application-resume/ui/header.tsx new file mode 100644 index 00000000..d626fee5 --- /dev/null +++ b/src/widgets/application-resume/ui/header.tsx @@ -0,0 +1,20 @@ +type Props = { + jobPostTitle: string + submittedDate: string +} + +export const Header = ({ jobPostTitle, submittedDate }: Props) => { + return ( +
+

+ {jobPostTitle} +

+
+

+ 지원 일자: + {submittedDate} +

+
+
+ ) +} diff --git a/src/widgets/application-resume/ui/introduction.tsx b/src/widgets/application-resume/ui/introduction.tsx new file mode 100644 index 00000000..0223a0fd --- /dev/null +++ b/src/widgets/application-resume/ui/introduction.tsx @@ -0,0 +1,13 @@ +type Props = { + personalIntroduction: string +} + +export const Introduction = ({ personalIntroduction }: Props) => { + return ( +
+

+ {personalIntroduction} +

+
+ ) +} diff --git a/src/widgets/application-resume/ui/personal-information.tsx b/src/widgets/application-resume/ui/personal-information.tsx new file mode 100644 index 00000000..ce845cef --- /dev/null +++ b/src/widgets/application-resume/ui/personal-information.tsx @@ -0,0 +1,90 @@ +import { convertStudentTypeToArray } from 'entities/job-post' +import { JobPostRelationDetail } from 'entities/job-post-resume-relation' +import { colors } from 'shared/config' +import { Image, Icon } from 'shared/ui' + +type Props = { + jobPostResumeRelation: JobPostRelationDetail +} + +export const PersonalInformation = ({ jobPostResumeRelation }: Props) => { + const studentType = convertStudentTypeToArray(jobPostResumeRelation) + + return ( +
+ {jobPostResumeRelation.resumeTitle} + + + } + /> +
+
+

+ {jobPostResumeRelation.firstName} {jobPostResumeRelation.lastName} +

+

+ {jobPostResumeRelation.birthDate} + + {jobPostResumeRelation.genderType} +

+
+
+
+
+
+
이메일
+
+ {jobPostResumeRelation.email} +
+
+
+
국적
+
+ {jobPostResumeRelation.countryNameEn} +
+
+
+
거주 지역
+
+ {jobPostResumeRelation.residenceCountryNameEn} +
+
+
+
+
+
학력
+
+ {jobPostResumeRelation.degree} +
+
+
+
비자
+
+ {jobPostResumeRelation.hasVisa ? 'Yes' : 'No'} +
+
+
+
+
+
+
학생 유형
+
+ {studentType.map(type => ( + {type} + ))} +
+
+
+
+ ) +}