import React, { useState, useEffect } from 'react';
import axios from 'axios';
import * as m from './style';
const API_BASE_URL = process.env.REACT_APP_API_BASE_URL || '';
interface PredictionFormProps {
category: string | null;
baseCategory: string | null; // 동적으로 전달받을 상위 카테고리 (영어)
}
const PredictionForm: React.FC<PredictionFormProps> = ({ category, baseCategory }) => {
const [apiData, setApiData] = useState<any>(null);
const [currentStep, setCurrentStep] = useState(0);
const [selectedOptions, setSelectedOptions] = useState<{ [key: string]: string }>({});
const [isSubmitting, setIsSubmitting] = useState(false);
const [submissionResult, setSubmissionResult] = useState<any>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchData = async () => {
if (!category || !baseCategory) {
setError('카테고리가 유효하지 않습니다.');
return;
}
try {
const token = localStorage.getItem('token'); // 로컬스토리지에서 토큰 가져오기
if (!token) {
throw new Error('토큰이 없습니다. 로그인하세요.');
}
const response = await axios.get(
`${API_BASE_URL}/form/${baseCategory}/${category}`,
{
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
}
);
setApiData(response.data);
console.log(`${API_BASE_URL}/form/${baseCategory}/${category}`);
console.log(response.data);
setError(null);
} catch (error: any) {
setError('데이터 로드 실패');
console.error('API 요청 실패:', error);
console.log(`${API_BASE_URL}/form/${baseCategory}/${category}`);
}
};
fetchData();
}, [category, baseCategory]);
const currentName = apiData?.form[currentStep]?.name || '';
const currentScore = apiData?.form[currentStep]?.score || {};
const handleOptionChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
setSelectedOptions({
...selectedOptions,
[currentName]: e.target.value,
});
};
const handleNext = () => {
if (currentStep < apiData?.form.length - 1) {
setCurrentStep(currentStep + 1);
}
};
const handlePrev = () => {
if (currentStep > 0) {
setCurrentStep(currentStep - 1);
}
};
const prepareFormData = (apiData: any, selectedOptions: { [key: string]: string }) => {
return {
militaryType: apiData.militaryType || 'UNKNOWN', // militaryType 포함
form: apiData.form.map((item: any) => {
const selectedOption = selectedOptions[item.name]; // 사용자가 선택한 옵션
const score = selectedOption ? item.score[selectedOption] : null; // 선택된 점수
// group 데이터를 변환
const group = (item.group || []).map((groupItem: any) => ({
name: groupItem.name,
priority: groupItem.priority,
limit: groupItem.limit,
}));
// POST 데이터 구조에 맞게 변환
return {
name: item.name,
type: item.type,
group: group.length > 0 ? group : undefined, // group이 없으면 undefined
score: score !== null ? { [selectedOption]: score } : undefined, // score가 있으면 key-value 형태로 전달
};
}),
};
};
const handleSubmit = async () => {
setIsSubmitting(true);
const formData = prepareFormData(apiData, selectedOptions);
try {
const token = localStorage.getItem('token');
if (!token) {
throw new Error('토큰이 없습니다. 로그인하세요.');
}
const response = await axios.post(
`${API_BASE_URL}/form/calculate/${baseCategory}/${category}`,
formData,
{
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
}
);
setSubmissionResult(response.data);
console.log('전송된 데이터:', formData);
console.log('성공:', response.data);
} catch (error: any) {
setError('제출 실패');
console.error('제출 실패:', error);
console.log('전송된 데이터:', formData);
} finally {
setIsSubmitting(false);
}
};
if (submissionResult) {
return (
<m.Container>
<m.Title>제출이 완료되었습니다.</m.Title>
</m.Container>
);
}
return (
<m.Container>
{error ? (
<m.Title>{error}</m.Title>
) : !apiData ? (
<m.Title>로딩 중...</m.Title>
) : (
<>
<m.Title>{currentName}을(를) 선택하세요</m.Title>
<m.ProgressBar>
<m.Progress width={((currentStep + 1) / apiData.form.length) * 100} />
</m.ProgressBar>
<m.InputSection>
<m.DateOfBirthContainer>
<m.Dropdown
value={selectedOptions[currentName] || ''}
onChange={handleOptionChange}
>
<option value="" disabled>
{currentName}
</option>
{currentScore &&
Object.entries(currentScore).map(([key, value]) => (
<option key={key} value={key}>
{`${key} (${value}점)`}
</option>
))}
</m.Dropdown>
</m.DateOfBirthContainer>
</m.InputSection>
<m.ButtonContainer>
<m.Button onClick={handlePrev} disabled={currentStep === 0}>
이전
</m.Button>
{currentStep < apiData.form.length - 1 ? (
<m.Button onClick={handleNext} disabled={!selectedOptions[currentName]}>
다음
</m.Button>
) : (
<m.Button
onClick={handleSubmit}
disabled={!selectedOptions[currentName] || isSubmitting}
>
{isSubmitting ? '제출 중...' : '제출'}
</m.Button>
)}
</m.ButtonContainer>
</>
)}
</m.Container>
);
};
export default PredictionForm;
useEffect를 사용하여 컴포넌트가 렌더링 될 때마다 API 데이터를 가져오는 방식이 잘 구현되어 있습니다. 이를 통해 API에서 데이터를 비동기적으로 가져오고, 상태를 관리하는 방법을 배웠습니다. 특히, axios를 사용한 API 요청과 useState를 통한 상태 관리 방식이 직관적이고 효율적이었습니다.selectedOptions와 prepareFormData 함수가 어떻게 연동되는지 살펴보며, 실제 애플리케이션에서 사용자 입력을 어떻게 적절히 처리하고 서버에 제출하는지에 대해 배웠습니다.submissionResult를 통해 제출 완료 후 피드백을 제공하고, 진행 중인 단계에서 버튼을 동적으로 렌더링하는 부분도 직관적이고 사용자 경험을 고려한 디자인이라고 느꼈습니다.error 상태를 활용하여 사용자가 문제를 인지하고 해결할 수 있도록 돕는 방식이 실용적이었습니다.이번 공부를 통해 리액트에서 API와의 통신 및 상태 관리, 폼 처리의 전반적인 흐름을 이해할 수 있었습니다. 특히, 사용자가 입력하는 데이터를 동적으로 처리하고, 서버와 상호작용하는 방식에 대해 더 깊이 배웠습니다. 에러 처리와 사용자 경험을 중요하게 생각하는 코드 구조도 유익했고, 실제 프로젝트에서 이와 같은 패턴을 적용할 수 있을 것 같아 자신감이 생겼습니다.