Skip to content

Commit 82406af

Browse files
committed
refactor: 마이페이지 카테고리별 정답률 스타일 개선
1 parent d7efba4 commit 82406af

File tree

2 files changed

+197
-37
lines changed

2 files changed

+197
-37
lines changed

src/components/profile/tabs/CorrectRateTab.tsx

Lines changed: 188 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,15 @@ const CorrectRateTab: React.FC<CorrectRateTabProps> = ({
3636
// 통계 계산
3737
const rates = Object.values(correctRateData.correctRates);
3838
const excellentCategories = rates.filter((rate) => Number(rate) >= 80).length;
39+
40+
// 0.0인 값들을 제외하고 평균 계산
41+
const nonZeroRates = rates.filter((rate) => Number(rate) > 0);
3942
const averageRate =
40-
rates.length > 0
41-
? rates.reduce((sum, rate) => sum + Number(rate), 0) / rates.length
43+
nonZeroRates.length > 0
44+
? nonZeroRates.reduce((sum, rate) => sum + Number(rate), 0) /
45+
nonZeroRates.length
4246
: 0;
47+
4348
const needImprovementCategories = rates.filter(
4449
(rate) => Number(rate) < 60,
4550
).length;
@@ -48,33 +53,179 @@ const CorrectRateTab: React.FC<CorrectRateTabProps> = ({
4853
<div className="space-y-6">
4954
<h3 className="text-xl font-bold text-gray-900">카테고리별 정답률</h3>
5055

51-
{/* 바 차트 */}
52-
<div className="rounded-xl bg-gray-50 p-6">
53-
<div className="space-y-4">
56+
{/* 세로 차트 그래프 */}
57+
{/* <div className="group mb-6 rounded-xl border border-blue-100 bg-white p-4 shadow-sm sm:p-6">
58+
<h4 className="mb-3 text-base font-semibold text-gray-900 sm:mb-4 sm:text-lg">
59+
정답률 차트
60+
</h4>
61+
<div className="relative h-48 w-full rounded-lg bg-gray-50 p-3 sm:h-56 sm:p-4">
62+
<div
63+
className={`flex h-full items-end justify-center ${
64+
Object.keys(correctRateData.correctRates).length > 6
65+
? "gap-1 sm:gap-2"
66+
: "gap-2 sm:gap-4"
67+
}`}
68+
>
69+
{Object.entries(correctRateData.correctRates).map(
70+
([category, rate], index) => {
71+
const rateNum = Number(rate);
72+
const heightPx = Math.max(12, (rateNum / 100) * 140); // 모바일에서 더 큰 최소 높이
73+
const opacity =
74+
rateNum === 0
75+
? 0.5
76+
: Math.max(0.7, Math.min(1, rateNum / 100 + 0.3));
77+
78+
// 그라데이션 색상 결정
79+
const getBarGradient = (rate: number) => {
80+
if (rate >= 80)
81+
return "bg-gradient-to-t from-blue-500 to-blue-600";
82+
if (rate >= 60)
83+
return "bg-gradient-to-t from-blue-400 to-blue-500";
84+
if (rate >= 40)
85+
return "bg-gradient-to-t from-blue-300 to-blue-400";
86+
if (rate >= 20)
87+
return "bg-gradient-to-t from-blue-200 to-blue-300";
88+
return "bg-gradient-to-t from-gray-400 to-gray-500";
89+
};
90+
91+
const getHoverColor = (rate: number) => {
92+
if (rate >= 80)
93+
return "hover:from-blue-600 hover:to-blue-700";
94+
if (rate >= 60)
95+
return "hover:from-blue-500 hover:to-blue-600";
96+
if (rate >= 40)
97+
return "hover:from-blue-400 hover:to-blue-500";
98+
if (rate >= 20)
99+
return "hover:from-blue-300 hover:to-blue-400";
100+
return "hover:from-gray-500 hover:to-gray-600";
101+
};
102+
103+
const categoryCount = Object.keys(
104+
correctRateData.correctRates,
105+
).length;
106+
107+
return (
108+
<div
109+
key={category}
110+
className={`group/bar flex flex-col items-center ${
111+
categoryCount > 8
112+
? "min-w-10 flex-1 sm:min-w-12"
113+
: categoryCount > 6
114+
? "min-w-12 flex-1 sm:min-w-14"
115+
: categoryCount > 4
116+
? "min-w-14 flex-1 sm:min-w-16"
117+
: "min-w-16 flex-1 sm:min-w-20"
118+
}`}
119+
>
120+
<div className="flex h-36 w-full flex-col justify-end sm:h-40">
121+
<div
122+
className={`w-full ${getBarGradient(rateNum)} ${getHoverColor(rateNum)} group relative cursor-pointer touch-manipulation rounded-t-md shadow-sm transition-all duration-1000 ease-out hover:shadow-md sm:rounded-t-lg`}
123+
style={{
124+
height: isVisible ? `${heightPx}px` : "0px",
125+
opacity: opacity,
126+
transitionDelay: `${index * 100}ms`,
127+
}}
128+
title={`${getSubCategoryLabel(category) || category}: ${rateNum.toFixed(1)}%`}
129+
>
130+
<div className="absolute inset-0 rounded-t-md bg-white/20 opacity-0 transition-opacity duration-300 group-hover:opacity-100 sm:rounded-t-lg"></div>
131+
132+
<div className="absolute -inset-2 sm:hidden"></div>
133+
134+
<div
135+
className={`absolute -top-4 left-1/2 -translate-x-1/2 transform text-xs font-semibold transition-colors duration-300 sm:-top-5 ${
136+
rateNum >= 60 ? "text-blue-700" : "text-gray-600"
137+
}`}
138+
>
139+
{(rateNum >= 5 && categoryCount <= 6) ||
140+
(rateNum >= 10 && categoryCount > 6)
141+
? rateNum.toFixed(0) + "%"
142+
: ""}
143+
</div>
144+
</div>
145+
</div>
146+
147+
<div className="mt-2 text-center sm:mt-3">
148+
<div
149+
className={`truncate font-medium text-gray-700 ${
150+
categoryCount > 8
151+
? "max-w-10 text-[9px] sm:text-[10px]"
152+
: categoryCount > 6
153+
? "max-w-12 text-[10px] sm:text-xs"
154+
: categoryCount > 4
155+
? "max-w-14 text-xs"
156+
: "max-w-16 text-xs sm:text-sm"
157+
}`}
158+
>
159+
{categoryCount > 8
160+
? getSubCategoryLabel(category)
161+
?.split(" ")[0]
162+
?.slice(0, 3) || category.slice(0, 3)
163+
: categoryCount > 6
164+
? getSubCategoryLabel(category)
165+
?.split(" ")[0]
166+
?.slice(0, 4) || category.slice(0, 4)
167+
: getSubCategoryLabel(category)?.split(" ")[0] ||
168+
category.slice(0, 6)}
169+
</div>
170+
</div>
171+
</div>
172+
);
173+
},
174+
)}
175+
</div>
176+
</div>
177+
</div> */}
178+
179+
{/* 애니메이션 바 차트 */}
180+
<div className="rounded-xl border border-gray-100 bg-white p-6 shadow-sm">
181+
{/* <h4 className="mb-4 text-lg font-semibold text-gray-900">
182+
카테고리별 세부 정답률
183+
</h4> */}
184+
<div className="space-y-6">
54185
{Object.entries(correctRateData.correctRates).map(
55-
([category, rate]) => {
186+
([category, rate], index) => {
56187
const rateNum = Number(rate);
188+
const delay = index * 100; // 순차적 애니메이션
189+
const opacity = Math.max(0.3, Math.min(1, rateNum / 100)); // 투명도 계산
190+
57191
return (
58-
<div key={category} className="space-y-2">
59-
<div className="flex items-center justify-between">
60-
<span className="font-medium text-gray-900">
192+
<div key={category} className="group">
193+
<div className="mb-2 flex items-center justify-between">
194+
<span className="text-sm font-semibold text-gray-900">
61195
{getSubCategoryLabel(category) || category}
62196
</span>
63-
<span className="text-brand-600 text-sm font-medium">
64-
{rateNum.toFixed(1)}%
65-
</span>
197+
<div className="flex items-center gap-2">
198+
<span className="text-sm font-bold text-blue-600 transition-colors duration-300">
199+
{rateNum.toFixed(1)}%
200+
</span>
201+
<div className="h-2 w-2 rounded-full bg-blue-500" />
202+
</div>
66203
</div>
67-
<div className="h-3 w-full rounded-full bg-gray-200">
204+
205+
<div className="relative h-4 overflow-hidden rounded-full bg-gray-100">
206+
{/* 배경 그라데이션 */}
207+
<div className="absolute inset-0 rounded-full bg-gradient-to-r from-gray-50 to-gray-100" />
208+
209+
{/* 메인 바 */}
68210
<div
69-
className={`h-3 rounded-full transition-all duration-700 ${
70-
rateNum >= 80
71-
? "bg-green-500"
72-
: rateNum >= 60
73-
? "bg-yellow-500"
74-
: "bg-red-500"
75-
}`}
76-
style={{ width: `${Math.max(rateNum, 5)}%` }}
77-
></div>
211+
className="relative h-full overflow-hidden rounded-full bg-gradient-to-r from-blue-400 to-blue-600 transition-all duration-1000 ease-out"
212+
style={{
213+
width: `${Math.max(rateNum, 3)}%`,
214+
opacity: opacity,
215+
animationDelay: `${delay}ms`,
216+
animation: `slideInX 1.2s ease-out ${delay}ms forwards`,
217+
transformOrigin: "left center",
218+
}}
219+
>
220+
{/* 반짝이는 효과 */}
221+
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/30 to-transparent opacity-0 group-hover:animate-pulse group-hover:opacity-100" />
222+
223+
{/* 글로우 효과 */}
224+
<div className="absolute inset-0 rounded-full bg-blue-300 opacity-50 blur-sm" />
225+
</div>
226+
227+
{/* 호버 시 확장 효과 */}
228+
<div className="absolute inset-0 rounded-full bg-white/10 opacity-0 transition-opacity duration-300 group-hover:opacity-100" />
78229
</div>
79230
</div>
80231
);
@@ -85,9 +236,9 @@ const CorrectRateTab: React.FC<CorrectRateTabProps> = ({
85236

86237
{/* 통계 카드 */}
87238
<div className="grid grid-cols-1 gap-6 md:grid-cols-3">
88-
<div className="rounded-xl border border-green-100 bg-green-50 p-6">
239+
<div className="border-brand-100 from-brand-50 to-brand-100 group rounded-xl border bg-gradient-to-br p-6 transition-all duration-300 hover:scale-105 hover:shadow-lg">
89240
<div className="text-center">
90-
<div className="mx-auto mb-3 flex h-12 w-12 items-center justify-center rounded-full bg-green-500">
241+
<div className="from-brand-500 to-brand-600 mx-auto mb-3 flex h-12 w-12 items-center justify-center rounded-full bg-gradient-to-r shadow-lg transition-transform duration-300 group-hover:scale-110">
91242
<svg
92243
className="h-6 w-6 text-white"
93244
fill="currentColor"
@@ -100,19 +251,19 @@ const CorrectRateTab: React.FC<CorrectRateTabProps> = ({
100251
/>
101252
</svg>
102253
</div>
103-
<div className="mb-1 text-2xl font-bold text-green-600">
254+
<div className="text-brand-700 group-hover:text-brand-800 mb-1 text-2xl font-bold transition-colors duration-300">
104255
{excellentCategories}
105256
</div>
106-
<div className="text-sm font-medium text-green-700">
257+
<div className="text-brand-800 text-sm font-medium">
107258
우수한 카테고리
108259
</div>
109-
<div className="mt-1 text-xs text-green-600">(80% 이상)</div>
260+
<div className="text-brand-600 mt-1 text-xs">(80% 이상)</div>
110261
</div>
111262
</div>
112263

113-
<div className="rounded-xl border border-blue-100 bg-blue-50 p-6">
264+
<div className="border-navy-100 from-navy-50 to-navy-100 group rounded-xl border bg-gradient-to-br p-6 transition-all duration-300 hover:scale-105 hover:shadow-lg">
114265
<div className="text-center">
115-
<div className="mx-auto mb-3 flex h-12 w-12 items-center justify-center rounded-full bg-blue-500">
266+
<div className="from-navy-500 to-navy-600 mx-auto mb-3 flex h-12 w-12 items-center justify-center rounded-full bg-gradient-to-r shadow-lg transition-transform duration-300 group-hover:scale-110">
116267
<svg
117268
className="h-6 w-6 text-white"
118269
fill="currentColor"
@@ -125,17 +276,17 @@ const CorrectRateTab: React.FC<CorrectRateTabProps> = ({
125276
/>
126277
</svg>
127278
</div>
128-
<div className="mb-1 text-2xl font-bold text-blue-600">
279+
<div className="text-navy-700 group-hover:text-navy-800 mb-1 text-2xl font-bold transition-colors duration-300">
129280
{averageRate.toFixed(1)}%
130281
</div>
131-
<div className="text-sm font-medium text-blue-700">전체 평균</div>
132-
<div className="mt-1 text-xs text-blue-600">정답률</div>
282+
<div className="text-navy-800 text-sm font-medium">전체 평균</div>
283+
<div className="text-navy-600 mt-1 text-xs">정답률</div>
133284
</div>
134285
</div>
135286

136-
<div className="rounded-xl border border-amber-100 bg-amber-50 p-6">
287+
<div className="group rounded-xl border border-red-100 bg-gradient-to-br from-red-50 to-red-100 p-6 transition-all duration-300 hover:scale-105 hover:shadow-lg">
137288
<div className="text-center">
138-
<div className="mx-auto mb-3 flex h-12 w-12 items-center justify-center rounded-full bg-amber-500">
289+
<div className="mx-auto mb-3 flex h-12 w-12 items-center justify-center rounded-full bg-gradient-to-r from-red-500 to-red-600 shadow-lg transition-transform duration-300 group-hover:scale-110">
139290
<svg
140291
className="h-6 w-6 text-white"
141292
fill="currentColor"
@@ -148,11 +299,11 @@ const CorrectRateTab: React.FC<CorrectRateTabProps> = ({
148299
/>
149300
</svg>
150301
</div>
151-
<div className="mb-1 text-2xl font-bold text-amber-600">
302+
<div className="mb-1 text-2xl font-bold text-red-700 transition-colors duration-300 group-hover:text-red-800">
152303
{needImprovementCategories}
153304
</div>
154-
<div className="text-sm font-medium text-amber-700">개선 필요</div>
155-
<div className="mt-1 text-xs text-amber-600">(60% 미만)</div>
305+
<div className="text-sm font-medium text-red-800">개선 필요</div>
306+
<div className="mt-1 text-xs text-red-600">(60% 미만)</div>
156307
</div>
157308
</div>
158309
</div>

tailwind.config.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,14 @@ module.exports = {
182182
boxShadow: "0 0 20px 8px rgba(37, 99, 235, 0.3)",
183183
},
184184
},
185+
"slideInX": {
186+
"0%": {
187+
transform: "scaleX(0)",
188+
},
189+
"100%": {
190+
transform: "scaleX(1)",
191+
},
192+
},
185193
},
186194
animation: {
187195
"fade-in-smooth": "fade-in-smooth 0.6s ease-out forwards",
@@ -195,6 +203,7 @@ module.exports = {
195203
sparkle: "sparkle 1.5s ease-in-out infinite",
196204
"text-shimmer": "text-shimmer 3s ease-in-out infinite",
197205
"subscribe-pulse": "subscribe-pulse 2s ease-in-out infinite",
206+
"slideInX": "slideInX 1.2s ease-out forwards",
198207
},
199208
},
200209
},

0 commit comments

Comments
 (0)