Skip to content

Commit c18b014

Browse files
authored
merge :: 면접 후기 작성
2 parents 49ff99d + 5e520d4 commit c18b014

File tree

15 files changed

+342
-23
lines changed

15 files changed

+342
-23
lines changed

src/apis/companies/index.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { useQuery } from "@tanstack/react-query";
22
import { useToastStore } from "@team-return/design-system";
33
import { instance } from "../axios";
44
import { GetNumberOfPagesType } from "../recruitments/type";
5-
import { CompaniesDetailsType, CompaniesListResponseType } from "./type";
5+
import { CompaniesDetailsType, CompaniesListResponseType, GetCompaniesForReviewingResponse } from "./type";
66

77
const router = "/companies";
88

@@ -56,3 +56,13 @@ export const useGetNumberOfCompaniesListPages = (queryString: string) => {
5656
);
5757
return data;
5858
};
59+
60+
export const useGetCompaniesForReviewing = () => {
61+
return useQuery(
62+
["getCompaniesForReviewing"],
63+
async () => {
64+
const {data} = await instance.get<GetCompaniesForReviewingResponse>(`${router}/review`);
65+
return data;
66+
}
67+
)
68+
}

src/apis/companies/type.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,13 @@ export interface CompaniesDetailsTable {
3939
attachments: any[];
4040
service_name: string;
4141
business_area: string;
42-
}
42+
}
43+
44+
export interface CompaniesForReviewType {
45+
id: number,
46+
name: string,
47+
}
48+
49+
export interface GetCompaniesForReviewingResponse {
50+
companies : CompaniesForReviewType[]
51+
}

src/apis/reviews/index.ts

+49-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
import { useQuery } from "@tanstack/react-query";
1+
import { MutationOptions, useMutation, UseMutationOptions, useQuery } from "@tanstack/react-query";
2+
import { useToastStore } from "@team-return/design-system";
3+
import { AxiosError, AxiosResponse } from "axios";
24
import { instance } from "../axios";
35
import {
46
getReviewDetailResponseProps,
5-
getReviewListResponseProps
7+
getReviewListResponseProps,
8+
createReviewRequestType,
69
} from "./type";
710

811
const router = "/reviews";
@@ -24,3 +27,47 @@ export const useGetReviewDetails = (reviewId: string) => {
2427
return data;
2528
});
2629
};
30+
31+
export const useCreateReviews = (options: Omit<UseMutationOptions<AxiosResponse<any, any>, unknown, createReviewRequestType, unknown>, "mutationFn">) => {
32+
const { append } = useToastStore();
33+
return useMutation(
34+
async (body: createReviewRequestType) =>
35+
await instance.post(`${router}`, body),
36+
{
37+
...options,
38+
onError: (err: AxiosError) => {
39+
const { response } = err;
40+
switch (response?.status) {
41+
case 400: {
42+
append({
43+
title: "",
44+
message: "입력값은 비어있으면 안됩니다.",
45+
type: "RED",
46+
});
47+
break;
48+
}
49+
case 404: {
50+
switch ((response as AxiosResponse<{ message: string }>).data.message) {
51+
case "Code Not Found": {
52+
append({
53+
title: "",
54+
message: "질문 분야가 누락되었습니다.",
55+
type: "RED",
56+
});
57+
break;
58+
}
59+
case 'ApplicationEntity Not Found': {
60+
append({
61+
title: '',
62+
message: '해당 기업에는 후기를 작성할 수 없습니다.',
63+
type: 'RED',
64+
})
65+
}
66+
}
67+
68+
}
69+
}
70+
},
71+
}
72+
);
73+
};

src/apis/reviews/type.ts

+11
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,14 @@ export interface getReviewDetailProps {
1818
answer: string;
1919
area: string;
2020
}
21+
22+
export interface qnaElementsType {
23+
question: string;
24+
answer: string;
25+
code_id: number;
26+
}
27+
28+
export interface createReviewRequestType {
29+
company_id: number;
30+
qna_elements: qnaElementsType[]
31+
}
+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
"use client";
2+
3+
import { useState } from "react";
4+
import ReviewForm from "@/components/company/ReviewForm";
5+
import FillBtn from "@/components/common/Button/FillBtn";
6+
import { useRouter, useSearchParams } from "next/navigation";
7+
import { useCreateReviews } from "@/apis/reviews";
8+
import { qnaElementsType } from "@/apis/reviews/type";
9+
10+
export default function CreateReviews() {
11+
const router = useRouter();
12+
const params = useSearchParams();
13+
const companyId = Number(params.get('id'))
14+
const [qnaElements, setQnaElements] = useState<qnaElementsType[]>([
15+
{ question: "", answer: "", code_id: 0 },
16+
]);
17+
const mutateOption = {
18+
onSuccess: () => {
19+
router.push(`/companies/reviews/?id=${companyId}`)
20+
}
21+
}
22+
const { mutate: createReviews } = useCreateReviews(mutateOption);
23+
24+
const handleClickCreateRevies = () => {
25+
createReviews({
26+
company_id:companyId,
27+
qna_elements: qnaElements
28+
})
29+
}
30+
31+
const handleChange = (
32+
index: number,
33+
name: keyof qnaElementsType,
34+
value: string | number
35+
) => {
36+
setQnaElements(prev => {
37+
const newReviews = [...prev];
38+
if (typeof value === "number") {
39+
newReviews[index].code_id = value;
40+
} else if (name !== "code_id") {
41+
newReviews[index][name] = value;
42+
}
43+
return newReviews;
44+
});
45+
};
46+
47+
const removeReviewList = (index: number) => {
48+
setQnaElements(prev=>{
49+
let newReviews = [...prev];
50+
newReviews = newReviews.filter((_,idx)=>idx !== index);
51+
return newReviews;
52+
})
53+
}
54+
55+
return (
56+
<div className="w-2/3 mx-auto my-5">
57+
<p className="py-12 leading-10 text-center text-h4 font-b text-primaryBlue03">
58+
후기작성
59+
</p>
60+
{qnaElements
61+
.map((qnaElement, idx) => (
62+
<ReviewForm key={idx} onChange={handleChange} removeReviews={removeReviewList} setState={setQnaElements} index={idx} {...qnaElement} />
63+
))}
64+
<div className="flex justify-between">
65+
<FillBtn
66+
backgroundColor="#ccc"
67+
onClick={() => {
68+
setQnaElements(prev => ([
69+
...prev,
70+
{ question: "", answer: "", code_id: 0 }
71+
]));
72+
}}
73+
>
74+
면접질문 추가
75+
</FillBtn>
76+
<div className="flex gap-3">
77+
<FillBtn
78+
backgroundColor="#ccc"
79+
onClick={() => {
80+
router.back();
81+
}}
82+
>
83+
이전으로
84+
</FillBtn>
85+
<FillBtn onClick={handleClickCreateRevies}>완료</FillBtn>
86+
</div>
87+
</div>
88+
</div>
89+
);
90+
}

src/app/companies/reviews/page.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export default function Reviews() {
44
return (
55
<div className="w-2/3 mx-auto my-5">
66
<p className="py-12 leading-10 text-center text-h4 font-b text-primaryBlue03">
7-
면접 후기
7+
면접후기
88
</p>
99
<hr className="border-[#135C9D]" />
1010
<ReviewList />

src/app/globals.css

+4
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ input[type="number"]::-webkit-inner-spin-button {
5353
-webkit-appearance: none;
5454
}
5555

56+
textarea {
57+
resize: none;
58+
}
59+
5660
table {
5761
width: 100%;
5862
border: none;

src/app/mypage/page.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import AppliedCompaniesList from "@/components/mypage/AppliedCompaniesList";
22
import DetailProfile from "@/components/mypage/DetailProfile";
3+
import CompaniesForReviewing from "@/components/mypage/CompaniesForReviewing";
34

45
export default function MyPage() {
56
return (
67
<div className="py-[56px] mx-[9vw]">
78
<DetailProfile />
9+
<CompaniesForReviewing />
810
<AppliedCompaniesList />
911
</div>
1012
);
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import React from "react";
2+
3+
interface Propstype extends React.ButtonHTMLAttributes<HTMLButtonElement> {
4+
backgroundColor?: string
5+
}
6+
7+
export default function FillBtn({ children, backgroundColor, style, ...rest }: Propstype) {
8+
return (
9+
<button
10+
className='min-w-[122px] h-[48px] py-3 px-4 text-b2 leading-b2 font-b rounded-[8px] text-white'
11+
{...rest}
12+
style={{
13+
backgroundColor: backgroundColor || '#135c9d',
14+
...style,
15+
}}
16+
>
17+
{children}
18+
</button>
19+
);
20+
}

src/components/common/Button/SendBtn.tsx

-11
This file was deleted.

src/components/common/DropDown.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ interface PropsType {
1919
function DropDown({ title, items, onClickItem, selected }: PropsType) {
2020
const { toggleDropdown, DropDownComponent, closeDropDown } = useDropDown();
2121

22-
const selectedItemLabel = items.find((item) => item.code === selected);
22+
const selectedItemLabel = items.find((item) => item.code === selected.toString());
2323

2424
return (
2525
<div

src/components/common/TextFiled.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ function TextFiled({
4343
<p className="text-caption text-[#333333] font-m mb-[4px]">{label}</p>
4444
)}
4545
<div
46-
className={`w-full border border-solid rounded-[8px] flex align-center overflow-hidden`}
46+
className={`w-full border border-solid rounded-[8px] flex align-center overflow-hidden`}
4747
style={{
4848
borderColor: focus ? theme.color.liteBlue : "#cccccc",
4949
height: height

src/components/company/ReviewForm.tsx

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import React from "react";
2+
import { useGetCode } from "@/apis/code";
3+
import DropDown from "@/components/common/DropDown";
4+
import TextFiled from "@/components/common/TextFiled";
5+
import useForm from "@/hook/useForm";
6+
import { useRef, useState } from "react";
7+
import { theme } from "@team-return/design-system";
8+
import { qnaElementsType } from "@/apis/reviews/type";
9+
10+
interface PropsType {
11+
question: string;
12+
answer: string;
13+
code_id: number;
14+
index: number;
15+
onChange: (idx: number, name: keyof qnaElementsType, value: string) => void;
16+
setState: React.Dispatch<React.SetStateAction<qnaElementsType[]>>;
17+
removeReviews: (index:number) => void;
18+
}
19+
20+
export default function ReviewForm({
21+
question,
22+
answer,
23+
code_id,
24+
index,
25+
onChange,
26+
setState: setQnaElements,
27+
removeReviews
28+
}: PropsType) {
29+
const textarea = useRef<HTMLTextAreaElement | null>(null);
30+
31+
const { data: codes } = useGetCode("JOB");
32+
33+
const [isFocus, setIsFocus] = useState<boolean>(false);
34+
35+
const jobCodeDropdownItems = codes?.codes.map(item => ({
36+
code: item.code.toString(),
37+
label: item.keyword,
38+
}));
39+
40+
const handleResizeHeight = () => {
41+
if (textarea?.current) {
42+
textarea.current.style.height = "auto";
43+
textarea.current.style.height = textarea.current.scrollHeight + "px";
44+
}
45+
};
46+
return (
47+
<div className="mb-10 relative">
48+
<div className="flex gap-3">
49+
<TextFiled
50+
label="면접 질문"
51+
placeholder="면접 질문 입력"
52+
value={question}
53+
name="question"
54+
onChange={e => {
55+
onChange(index, "question", e.target.value);
56+
}}
57+
className="flex-1"
58+
/>
59+
<div className="w-fit">
60+
<p className="text-caption text-[#333333] font-m mb-[4px]">
61+
질문 분야
62+
</p>
63+
<DropDown
64+
title="분야선택"
65+
items={jobCodeDropdownItems ?? [{ code: "1", label: "" }]}
66+
onClickItem={(itemCode: string) => {
67+
setQnaElements(prev => {
68+
const updatedElements = [...prev];
69+
updatedElements[index].code_id = Number(itemCode);
70+
return updatedElements;
71+
});
72+
}}
73+
selected={code_id}
74+
/>
75+
</div>
76+
</div>
77+
<p className="text-caption text-[#333333] font-m mb-[4px] mt-4">답변</p>
78+
<textarea
79+
ref={textarea}
80+
value={answer}
81+
name="answer"
82+
onChange={e => {
83+
onChange(index, "answer", e.target.value);
84+
handleResizeHeight();
85+
}}
86+
className="border border-solid rounded-[8px] px-4 py-3 text-b3 font-r leading-b3 outline-none w-full min-h-[200px]"
87+
placeholder="질문에 어떻게 답했나요?"
88+
style={{
89+
borderColor: isFocus ? theme.color.liteBlue : "#cccccc",
90+
}}
91+
onFocus={() => {
92+
setIsFocus(true);
93+
}}
94+
onBlur={() => {
95+
setIsFocus(false);
96+
}}
97+
/>
98+
<button onClick={()=>{removeReviews(index)}} className="absolute bottom-1 right-[-8%] text-caption text-[#7f7f7f] font-r p-2">삭제</button>
99+
</div>
100+
);
101+
}

0 commit comments

Comments
 (0)