Skip to content

Commit 2a90e7d

Browse files
authored
refactor: page.tsx를 server side로 변경하고 페이지명을 추가하기 (#162)
* refactor: /application 페이지 전환 * refactor: /application/apply 페이지 전환 * refactor: /community/[boardCode] 페이지 전환 * refactor: /community/[boardCode]/[postId] 페이지 전환 * refactor: /community/[boardCode]/[postId]/modify 페이지 전환 * refactor: /login 페이지 전환 * refactor: /my 페이지 전환 * refactor: /my/favorite 페이지 전환 * refactor: /my/modify 페이지 전환
1 parent c7abc58 commit 2a90e7d

18 files changed

Lines changed: 1158 additions & 1002 deletions
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
"use client";
2+
3+
import { useRouter } from "next/navigation";
4+
import { useEffect, useRef, useState } from "react";
5+
6+
import { getApplicationListApi, getCompetitorsApplicationListApi } from "@/services/application";
7+
8+
import CloudSpinnerPage from "@/components/loading/CloudSpinnerPage";
9+
import ConfirmCancelModal from "@/components/modal/ConfirmCancelModal";
10+
import ButtonTab from "@/components/ui/ButtonTab";
11+
import Tab from "@/components/ui/Tab";
12+
13+
import ScoreSearchBar from "./ScoreSearchBar";
14+
import ScoreSearchField from "./ScoreSearchField";
15+
import ScoreSheets from "./ScoreSheets";
16+
17+
import { REGIONS_KO } from "@/constants/university";
18+
import { ApplicationListResponse } from "@/types/application";
19+
import { RegionKo } from "@/types/university";
20+
21+
const PREFERENCE_CHOICE: string[] = ["1순위", "2순위", "3순위"];
22+
23+
const ScorePageContent = () => {
24+
const router = useRouter();
25+
const [loading, setLoading] = useState<boolean>(true);
26+
// 검색
27+
const [searchActive, setSearchActive] = useState<boolean>(false); // 검색 창 활성화 여부
28+
const searchRef = useRef<HTMLInputElement>(null);
29+
// 점수 데이터
30+
const [scoreData, setScoreData] = useState<ApplicationListResponse>({
31+
firstChoice: [],
32+
secondChoice: [],
33+
thirdChoice: [],
34+
});
35+
const [filteredScoreData, setFilteredScoreData] = useState<ApplicationListResponse>({
36+
firstChoice: [],
37+
secondChoice: [],
38+
thirdChoice: [],
39+
});
40+
const [preference, setPreference] = useState<"1순위" | "2순위" | "3순위">("1순위");
41+
const [filter, setFilter] = useState<RegionKo | "">("");
42+
43+
const [showNeedApply, setShowNeedApply] = useState<boolean>(false);
44+
45+
useEffect(() => {
46+
const fetchData = async () => {
47+
try {
48+
if (true) {
49+
const scoreResponse = await getCompetitorsApplicationListApi();
50+
51+
const scoreResponseData = scoreResponse.data;
52+
scoreResponseData.firstChoice.sort((a, b) => b.applicants.length - a.applicants.length);
53+
scoreResponseData.secondChoice.sort((a, b) => b.applicants.length - a.applicants.length);
54+
scoreResponseData.thirdChoice.sort((a, b) => b.applicants.length - a.applicants.length);
55+
setScoreData(scoreResponseData);
56+
setFilteredScoreData(scoreResponseData);
57+
}
58+
} catch (err) {
59+
if (err.response) {
60+
if (err.response.status === 404) {
61+
setShowNeedApply(true);
62+
} else if (err.response.status === 401 || err.response.status === 403) {
63+
alert("로그인이 필요합니다");
64+
document.location.href = "/login";
65+
} else {
66+
alert(err.response.data?.message);
67+
}
68+
} else {
69+
console.error("Error", err.message);
70+
}
71+
} finally {
72+
setLoading(false);
73+
}
74+
};
75+
fetchData();
76+
}, []);
77+
78+
const handleSearch = (event: React.FormEvent) => {
79+
event.preventDefault();
80+
const keyWord = searchRef.current?.value || "";
81+
setFilter("");
82+
setFilteredScoreData(
83+
keyWord
84+
? {
85+
firstChoice: scoreData.firstChoice.filter((sheet) => sheet.koreanName.includes(keyWord)),
86+
secondChoice: scoreData.secondChoice.filter((sheet) => sheet.koreanName.includes(keyWord)),
87+
thirdChoice: scoreData.thirdChoice.filter((sheet) => sheet.koreanName.includes(keyWord)),
88+
}
89+
: scoreData,
90+
);
91+
setSearchActive(false);
92+
};
93+
94+
const handleSearchField = (keyWord: string) => {
95+
if (searchRef.current) {
96+
searchRef.current.value = keyWord;
97+
}
98+
};
99+
100+
const handleSearchClick = () => {
101+
setSearchActive(true);
102+
};
103+
104+
const hotKeyWords = ["RMIT", "오스트라바", "칼스루에", "그라츠", "추오", "프라하", "보라스", "빈", "메모리얼"];
105+
106+
useEffect(() => {
107+
if (filter) {
108+
setFilteredScoreData({
109+
firstChoice: scoreData.firstChoice.filter((sheet) => sheet.region === filter),
110+
secondChoice: scoreData.secondChoice.filter((sheet) => sheet.region === filter),
111+
thirdChoice: scoreData.thirdChoice.filter((sheet) => sheet.region === filter),
112+
});
113+
} else {
114+
setFilteredScoreData(scoreData);
115+
}
116+
}, [filter, scoreData]);
117+
118+
if (loading) {
119+
return <CloudSpinnerPage />;
120+
}
121+
122+
const getScoreSheet = () => {
123+
if (preference === "1순위") {
124+
return filteredScoreData.firstChoice;
125+
}
126+
if (preference === "2순위") {
127+
return filteredScoreData.secondChoice;
128+
}
129+
if (preference === "3순위") {
130+
return filteredScoreData.thirdChoice;
131+
}
132+
return [];
133+
};
134+
135+
if (searchActive) {
136+
return (
137+
<>
138+
<ScoreSearchBar textRef={searchRef} searchHandler={handleSearch} onClick={() => {}} />
139+
<ScoreSearchField
140+
keyWords={hotKeyWords}
141+
setKeyWord={(e) => {
142+
handleSearchField(e);
143+
}}
144+
/>
145+
</>
146+
);
147+
}
148+
149+
return (
150+
<>
151+
<ScoreSearchBar onClick={handleSearchClick} textRef={searchRef} searchHandler={handleSearch} />
152+
<Tab choices={PREFERENCE_CHOICE} choice={preference} setChoice={setPreference} />
153+
<ButtonTab choices={REGIONS_KO} choice={filter} setChoice={setFilter} style={{ padding: "10px 0 10px 18px" }} />
154+
<ScoreSheets scoreSheets={getScoreSheet()} />
155+
<ConfirmCancelModal
156+
isOpen={showNeedApply}
157+
handleCancel={() => {
158+
router.push("/");
159+
}}
160+
handleConfirm={() => {
161+
router.push("/application/apply");
162+
}}
163+
title=""
164+
content={"점수 공유현황을 확인하려면 지원절차를\n진행해주세요."}
165+
cancelText="확인"
166+
approveText="학교 지원하기"
167+
/>
168+
</>
169+
);
170+
};
171+
172+
export default ScorePageContent;
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
"use client";
2+
3+
import { useRouter } from "next/navigation";
4+
import { useEffect, useState } from "react";
5+
6+
import { postApplicationApi } from "@/services/application";
7+
import { getMyGpaScoreApi, getMyLanguageTestScoreApi } from "@/services/score";
8+
import { getUniversityListPublicApi } from "@/services/university";
9+
10+
import TopDetailNavigation from "@/components/layout/TopDetailNavigation";
11+
import ProgressBar from "@/components/ui/ProgressBar";
12+
13+
import ConfirmStep from "./ConfirmStep";
14+
import DoneStep from "./DoneStep";
15+
import GpaStep from "./GpaStep";
16+
import LanguageStep from "./LanguageStep";
17+
import UniversityStep from "./UniversityStep";
18+
19+
import { GpaScore, LanguageTestScore } from "@/types/score";
20+
import { ListUniversity } from "@/types/university";
21+
22+
const ApplyPageContent = () => {
23+
const router = useRouter();
24+
const [step, setStep] = useState<number>(1);
25+
26+
const [languageTestScoreList, setLanguageTestScoreList] = useState<LanguageTestScore[]>([]);
27+
const [gpaScoreList, setGpaScoreList] = useState<GpaScore[]>([]);
28+
const [universityList, setUniversityList] = useState<ListUniversity[]>([]);
29+
30+
const [curLanguageTestScore, setCurLanguageTestScore] = useState<number | null>(null);
31+
const [curGpaScore, setCurGpaScore] = useState<number | null>(null);
32+
const [curUniversityList, setCurUniversityList] = useState<number[]>([]);
33+
34+
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
35+
36+
useEffect(() => {
37+
const fetchAll = async () => {
38+
try {
39+
const [gpaRes, languageRes, universityRes] = await Promise.all([
40+
getMyGpaScoreApi(),
41+
getMyLanguageTestScoreApi(),
42+
getUniversityListPublicApi(),
43+
]);
44+
setGpaScoreList(
45+
gpaRes.data.gpaScoreStatusResponseList.filter((score: GpaScore) => score.verifyStatus === "APPROVED"),
46+
);
47+
setLanguageTestScoreList(
48+
languageRes.data.languageTestScoreStatusResponseList.filter(
49+
(score: LanguageTestScore) => score.verifyStatus === "APPROVED",
50+
),
51+
);
52+
53+
// 대학명을 지역/나라, 대학명 가나다 순으로 정렬합니다
54+
const sortedUniversityList = [...universityRes.data].sort((a, b) => {
55+
// 1) region 비교
56+
const regionCompare = a.region.localeCompare(b.region);
57+
if (regionCompare !== 0) return regionCompare;
58+
59+
// 2) country 비교
60+
const countryCompare = a.country.localeCompare(b.country);
61+
if (countryCompare !== 0) return countryCompare;
62+
63+
// 3) 같은 region, country라면 대학명을 비교(가나다 순)
64+
return a.koreanName.localeCompare(b.koreanName);
65+
});
66+
setUniversityList(sortedUniversityList);
67+
} catch (err) {
68+
if (err.response) {
69+
console.error("Axios response error", err.response);
70+
if (err.response.status === 401 || err.response.status === 403) {
71+
alert("로그인이 필요합니다");
72+
document.location.href = "/login";
73+
} else {
74+
alert(err.response.data?.message);
75+
}
76+
} else {
77+
console.error("Error", err.message);
78+
alert(err.message);
79+
}
80+
}
81+
};
82+
fetchAll();
83+
}, []);
84+
85+
// 다음 스텝으로 넘어가기
86+
const goNextStep = () => setStep((prev) => prev + 1);
87+
// 이전 스텝으로 돌아가기
88+
const goPrevStep = () => {
89+
if (step === 1) {
90+
router.back();
91+
}
92+
setStep((prev) => prev - 1);
93+
};
94+
95+
const handleSubmit = async () => {
96+
if (!curGpaScore) {
97+
alert("GPA를 선택해주세요.");
98+
return;
99+
}
100+
101+
if (!curLanguageTestScore) {
102+
alert("어학성적을 선택해주세요.");
103+
return;
104+
}
105+
106+
if (curUniversityList.length === 0 || curUniversityList[0] === 0) {
107+
alert("대학교를 선택해주세요.");
108+
return;
109+
}
110+
111+
if (isSubmitting) return;
112+
setIsSubmitting(true); // TODO: 현재 임시 submit 처리, 이후에 통합 처리 추가
113+
try {
114+
await postApplicationApi({
115+
gpaScoreId: curGpaScore,
116+
languageTestScoreId: curLanguageTestScore,
117+
universityChoiceRequest: {
118+
firstChoiceUniversityId: curUniversityList[0] || null,
119+
secondChoiceUniversityId: curUniversityList[1] || null,
120+
thirdChoiceUniversityId: curUniversityList[2] || null,
121+
},
122+
});
123+
setStep(99);
124+
} catch (err) {
125+
alert(err.response.data.message);
126+
} finally {
127+
setIsSubmitting(false);
128+
}
129+
};
130+
131+
return (
132+
<>
133+
<TopDetailNavigation title="지원하기" handleBack={goPrevStep} />
134+
<div className="px-5">
135+
{(step === 1 || step === 2 || step === 3) && <ProgressBar currentStep={step} totalSteps={3} />}
136+
</div>
137+
{step === 1 && (
138+
<LanguageStep
139+
languageTestScoreList={languageTestScoreList}
140+
curLanguageTestScore={curLanguageTestScore}
141+
setCurLanguageTestScore={setCurLanguageTestScore}
142+
onNext={goNextStep}
143+
/>
144+
)}
145+
{step === 2 && (
146+
<GpaStep
147+
gpaScoreList={gpaScoreList}
148+
curGpaScore={curGpaScore}
149+
setCurGpaScore={setCurGpaScore}
150+
onNext={goNextStep}
151+
/>
152+
)}
153+
{step === 3 && (
154+
<UniversityStep
155+
universityList={universityList}
156+
curUniversityList={curUniversityList}
157+
setCurUniversityList={setCurUniversityList}
158+
onNext={goNextStep}
159+
/>
160+
)}
161+
{step === 4 && (
162+
<ConfirmStep
163+
languageTestScore={languageTestScoreList.find((score) => score.id === curLanguageTestScore)}
164+
gpaScore={gpaScoreList.find((score) => score.id === curGpaScore)}
165+
universityList={
166+
curUniversityList
167+
.map((id) => universityList.find((university) => university.id === id))
168+
.filter(Boolean) as ListUniversity[]
169+
}
170+
onNext={handleSubmit}
171+
/>
172+
)}
173+
{step === 99 && <DoneStep />}
174+
</>
175+
);
176+
};
177+
178+
export default ApplyPageContent;

0 commit comments

Comments
 (0)