@@ -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 >
0 commit comments