[Project: 잡생각 - 진로탐색 및 채용연계 서비스] - 0516

0516 진행 사항
  • Fix: HeroSection-100vw로 인해 생기던 우측 여백 문제 해결
  • Feat: 직업 가치관 검사 페이지 검사 질문지 커리어넷 api 연동
  • Feat: 유저 선택값 저장 구현

 

Fix: HeroSection-100vw로 인해 생기던 우측 여백 문제 해결

[CSS] 100vw vs 100% – 스크롤바 때문에 생기는 의외의 레이아웃 깨짐 해결법

[CSS] 100vw vs 100% – 스크롤바 때문에 생기는 의외의 레이아웃 깨짐 해결법

제가 참여했던 잡생각 프로젝트에서는 HeroSection에 스크롤에 따른 타이포그래피 애니메이션을 넣게 되었습니다. 스크롤에 따라 폰트가 점점 커지는 모션에서 자연스럽게 다음 섹션의 black 화면

kenco.tistory.com

 

API 명세

written by 팀장님

직업 가치관 문항 요청 GET /questions

직업가치관검사에 필요한 질문지(문항) 목록을 반환합니다.
각 문항은 두 개의 선택지(가치관)를 포함하며, 사용자는 더 중요한 가치를 선택해야 합니다.

 
✅ Request

  • Method: GET
  • Endpoint: /questions
  • Query: 없음

 
✅ Response

 
🔹 Response Body Type (TypeScript)

interface Question {
  qitemNo: number;            // 문항 번호 (1~28)
  question: string;           // 질문 본문
  answer01: string;           // 선택지 1 (가치관명)
  answer02: string;           // 선택지 2
  answer03: string;           // 선택지 1에 대한 설명
  answer04: string;           // 선택지 2에 대한 설명
  answerScore01: string;      // 선택지 1의 점수 번호 (예: "1")
  answerScore02: string;      // 선택지 2의 점수 번호 (예: "2")
}

※ 나머지 answer05 ~ answer10, tip1Score ~ tip3Score, tip1Desc ~ tip3Desc는 모두 null이므로 생략 가능.
 
🔹 응답 예시 (일부)

[
  {
    "qitemNo": 1,
    "question": "두 개 가치 중에 자신에게 더 중요한 가치를 선택하세요.",
    "answer01": "능력발휘",
    "answer02": "자율성",
    "answer03": "직업을 통해 자신의 능력을 발휘하는 것입니다.",
    "answer04": "일하는 시간과 방식에 대해서 스스로 결정할 수 있는 것입니다.",
    "answerScore01": "1",
    "answerScore02": "2"
  },
  {
    "qitemNo": 2,
    "question": "두 개 가치 중에 자신에게 더 중요한 가치를 선택하세요.",
    "answer01": "창의성",
    "answer02": "안정성",
    "answer03": "스스로 아이디어를 내어 새로운 일을 해볼 수 있는 것입니다.",
    "answer04": "한 직장에서 오랫동안 일할 수 있는 것입니다.",
    "answerScore01": "3",
    "answerScore02": "4"
  },
  ...
]

 

직업 가치관 검사 응답 요청 POST /report

 
✅ POST /report

직업가치관 검사 결과를 생성하고 결과 데이터를 반환합니다.

  • URL: POST /report
  • Content-Type: application/json
  • 응답코드: 200 OK

 
✅ Request Body

{
  "answers": "B1=1 B2=4 B3=5 B4=8 ..." // (선택 시 서버에서 override 할 수 있음)
}

 

✅  Response Body

interface ReportResponse {
  scores: Score[];
  topValues: string[];
  jobsByMajor: Record<string, string[]>;
}

interface Score {
  name: string;
  score: number;
}

 

✅ 응답 예시

{
  "scores": [
    { "name": "능력발휘", "score": 3 },
    { "name": "자율성", "score": 1 },
    { "name": "보수", "score": 3 },
    ...
  ],
  "topValues": ["사회적 인정", "안정성"],
  "jobsByMajor": {
    "공학": ["공구제조원", "재료공학기술자"],
    "의학": ["한의사", "전문의사", "치과의사"]
  }
}

 

API 전체 흐름 (우리 프로젝트 버전)

1. 사용자가 문항에 응답
  ↓
2. useTestStore.answers에 선택값 저장
  ↓
3. answers → "B1=1 B2=4 ..." 포맷 변환
  ↓
4. API 요청 (POST /report)
  ↓
5. 응답 결과 → useResultStore에 저장
  ↓
6. 결과 페이지에서 결과 상태값 읽어오기

 
 

📌 1. 사용자 응답 저장 → useTestStore.ts

export interface Answer {
  qitemNo: number;        // 예: 1
  answerScore: string;    // 예: "4"
}

🟡 사용자가 각 문항에 응답하면:

addAnswer({ qitemNo: 1, answerScore: "4" });

🟢 결과적으로 answers 상태는 이런 식으로 저장됨:

[
  { qitemNo: 1, answerScore: "4" },
  { qitemNo: 2, answerScore: "2" },
  ...
]

➡️ 이 데이터를 API에 맞는 포맷 "B1=4 B2=2"로 바꾸기 쉽게 구성해둔 거예요!

 
📌 2. API 요청 전 가공

// utils/formatAnswers.ts (가정)
export const formatAnswers = (answers: Answer[]) =>
  answers.map((a) => `B${a.qitemNo}=${a.answerScore}`).join(" ");

 
 

📌 3. 서버에 POST 요청 → api/report.ts

const result = await fetch("/api/report", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ answers: formatAnswers(answers) }),
});

 
 

📌 4. 결과 저장 → useResultStore.ts

서버 응답 예시는 다음과 같을 수 있어요:

{
  scores: [ { name: "안정성", score: 14 }, { name: "성취", score: 13 }, ... ],
  topValues: ["성취", "안정성", "능력발휘"],
  jobsByMajor: {
    인문: ["교사", "작가"],
    공학: ["연구원", "개발자"],
  }
}

이 데이터를 다음처럼 저장합니다:

setResult(resultData); // useResultStore

 

 
📌 5. 결과 페이지에서 불러오기

const { result } = useResultStore();

return (
  <>
    <h2>가치관 우선순위</h2>
    <ul>
      {result?.topValues.map((v) => (
        <li key={v}>{v}</li>
      ))}
    </ul>

    <h2>추천 직업</h2>
    {Object.entries(result?.jobsByMajor || {}).map(([major, jobs]) => (
      <div key={major}>
        <h3>{major}</h3>
        <ul>{jobs.map((job) => <li key={job}>{job}</li>)}</ul>
      </div>
    ))}
  </>
);

 
 

회고

어우..애써 정리한 글이 다 날아갔었습니다................그래서 글이 짧아졌네요. 티스토리가 밉다 나는...
 
오늘은 zustand를 활용해 처음으로 상태를 전역으로 관리해봤고,
선택값을 서버에 API로 보내서 결과를 받는 흐름을 직접 구현해봤습니다.

사실 처음엔 너무 막막했지만, zustand로 상태를 쉽게 관리하고
API 연동 흐름을 차근차근 따라가다보니 하나씩 이해가 되기 시작했을까요...? 글쎄요...ㅎ
웹 아키텍처를 이해하고 server, client, 외부 api를 연결하는 것은 참으로 어려운 것 같습니다....
 
또한 오늘은 제가 api를 열심히 힘겹게 연결하는 과정에서 팀원과의 merge 충돌이 있어서 
새벽 내내 열심히 해결했습니다.... 아름답게 해결된 결과물을 보니 마음이 편안해졌습니다.
충돌 해결 경험도 해보고 굉장히 의미있는 하루였네요!