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와의 통신 및 상태 관리, 폼 처리의 전반적인 흐름을 이해할 수 있었습니다. 특히, 사용자가 입력하는 데이터를 동적으로 처리하고, 서버와 상호작용하는 방식에 대해 더 깊이 배웠습니다. 에러 처리와 사용자 경험을 중요하게 생각하는 코드 구조도 유익했고, 실제 프로젝트에서 이와 같은 패턴을 적용할 수 있을 것 같아 자신감이 생겼습니다.