From 95b08ccabaa3cd548807faf7ef57c30c7421dcb1 Mon Sep 17 00:00:00 2001 From: DaAn Date: Thu, 23 Jul 2026 01:40:57 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=ED=9E=88=EC=8A=A4=ED=86=A0?= =?UTF-8?q?=EB=A6=AC=20=ED=8E=98=EC=9D=B4=EC=A7=80=20UI=EA=B5=AC=ED=98=84(?= =?UTF-8?q?#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../history/GrowthProgressSection.tsx | 179 +++++++++++++++ .../history/HistoryRecentPractices.tsx | 190 ++++++++++++++++ .../history/HistorySummaryCards.tsx | 171 +++++++++++++++ src/components/history/WeeklyTrendChart.tsx | 206 ++++++++++++++++++ .../main}/AttendanceSection.tsx | 0 .../main}/DashboardNoti.tsx | 0 .../main}/LearningBanner.tsx | 0 .../main}/RecentPractices.tsx | 0 .../main}/RecommandLearn.tsx | 0 src/pages/history/HistoryPage.tsx | 177 ++++++++++++++- src/pages/main/MainPage.tsx | 10 +- tsconfig.app.json | 1 + 12 files changed, 924 insertions(+), 10 deletions(-) create mode 100644 src/components/history/GrowthProgressSection.tsx create mode 100644 src/components/history/HistoryRecentPractices.tsx create mode 100644 src/components/history/HistorySummaryCards.tsx create mode 100644 src/components/history/WeeklyTrendChart.tsx rename src/{pages/main/components => components/main}/AttendanceSection.tsx (100%) rename src/{pages/main/components => components/main}/DashboardNoti.tsx (100%) rename src/{pages/main/components => components/main}/LearningBanner.tsx (100%) rename src/{pages/main/components => components/main}/RecentPractices.tsx (100%) rename src/{pages/main/components => components/main}/RecommandLearn.tsx (100%) diff --git a/src/components/history/GrowthProgressSection.tsx b/src/components/history/GrowthProgressSection.tsx new file mode 100644 index 0000000..8ce9f71 --- /dev/null +++ b/src/components/history/GrowthProgressSection.tsx @@ -0,0 +1,179 @@ +// //1. 이번주 연주 요약 카드(3개) + +// // src/components/history/GrowthProgressSection.tsx + +// export default function GrowthProgressSection() { +// return ( +//
+//
+//

영역별 성장 변화

+// 지난주 대비 영역별 평균 점수 변화를 보여드려요. +//
+//
+// {/* 스케일 */} +//
+//
+// 스케일 +// +8 +//
+//
+//
+//
+//
+ +// {/* 텐션 */} +//
+//
+// 텐션 +// +6 +//
+//
+//
+//
+//
+ +// {/* 진행 */} +//
+//
+// 진행 +// +4 +//
+//
+//
+//
+//
+ +// {/* 코드 연결 */} +//
+//
+// 코드 연결 +// -10 +//
+//
+//
+//
+//
+//
+//
+// ); +// } + +// src/components/history/GrowthProgressSection.tsx + +// export default function GrowthProgressSection() { +// return ( +//
+ +// {/* 그래프 및 라벨 리스트 영역 (행마다 32px 간격) */} +//
+ +// {/* 스케일 (+8) */} +//
+//
+// 스케일 +// +8 +//
+//
+//
+//
+//
+//
+ +// {/* 텐션 (+6) */} +//
+//
+// 텐션 +// +6 +//
+//
+//
+//
+//
+//
+ +// {/* 진행 (+4) */} +//
+//
+// 진행 +// +4 +//
+//
+//
+//
+//
+//
+ +// {/* 코드 연결 (-10) */} +//
+//
+// 코드 연결 +// -10 +//
+//
+//
+//
+//
+//
+ +//
+//
+// ); +// } +// src/components/history/GrowthProgressSection.tsx + +export default function GrowthProgressSection() { + return ( +
+ {/* 그래프 및 라벨 리스트 영역 (행마다 32px 간격) */} +
+ {/* 스케일 (+8) */} +
+
+ 스케일 + +8 +
+
+
+
+
+
+ + {/* 텐션 (+6) */} +
+
+ 텐션 + +6 +
+
+
+
+
+
+ + {/* 진행 (+4) */} +
+
+ 진행 + +4 +
+
+
+
+
+
+ + {/* 코드 연결 (-10) */} +
+
+ 코드 연결 + -10 +
+
+
+
+
+
+
+
+ ); +} diff --git a/src/components/history/HistoryRecentPractices.tsx b/src/components/history/HistoryRecentPractices.tsx new file mode 100644 index 0000000..c5812b1 --- /dev/null +++ b/src/components/history/HistoryRecentPractices.tsx @@ -0,0 +1,190 @@ +// src/components/history/HistoryRecentPractices.tsx + +import { useNavigate } from 'react-router-dom'; + +interface HistoryPracticeItem { + practiceId: number; + title: string; + scoreChange: string; + scoreType: 'up' | 'down' | 'neutral'; + description: string; + timeLabel: string; + date: string; +} + +interface HistoryRecentPracticesProps { + data: HistoryPracticeItem[]; +} + +export default function HistoryRecentPractices({ data }: HistoryRecentPracticesProps) { + const navigate = useNavigate(); + + return ( +
+ {data.map((item) => { + return ( +
navigate(`/practice/${item.practiceId}`)} + className="cursor-pointer text-white transition-colors select-none hover:border-[#3A3F4A]" + style={{ + display: 'flex', + width: '100%', + maxWidth: '1196px', + padding: '24px', + justifyContent: 'space-between', + alignItems: 'center', + borderRadius: '6px', + background: '#161B22', + border: '1px solid #2E3142', + boxSizing: 'border-box', + }}> + {/* 좌측 구역 */} +
+
+ + {item.title} + + +
+ {item.scoreType === 'down' ? ( + + + + ) : item.scoreType === 'up' ? ( + + + + ) : ( + + + + )} + + {item.scoreType !== 'neutral' && ( + + {item.scoreChange} + + )} +
+
+ +

+ {item.description} +

+
+ + {/* 우측 구역 (소요시간과 꺾쇠 위치를 고정하고, 날짜 영역만 폭을 주어 글자 수 차이 흡수) */} +
+ {/* 소요 시간 (위치 고정) */} +
+ + + + + {item.timeLabel} +
+ + {/* 날짜 (고정 폭 영역 안에서 우측 정렬되어 글자 길이에 따른 간격 차이 흡수) */} +
+ {item.date} +
+ + {/* > 아이콘 (위치 고정) */} + + + +
+
+ ); + })} +
+ ); +} diff --git a/src/components/history/HistorySummaryCards.tsx b/src/components/history/HistorySummaryCards.tsx new file mode 100644 index 0000000..81b66fc --- /dev/null +++ b/src/components/history/HistorySummaryCards.tsx @@ -0,0 +1,171 @@ +// src/components/history/HistorySummaryCards.tsx + +export default function HistorySummaryCards() { + return ( +
+ {/* 1번 카드: 정확도 */} +
+
+
+ {/* 정확도 아이콘 */} + + + + + 정확도 + +
+ + 91% + +
+
+ 전 주보다 4% 증가했어요 +
+
+ + {/* 2번 카드: 연습 시간 */} +
+
+
+ {/* 연습 시간 아이콘 */} + + + + + 연습 시간 + +
+ + 65분 + +
+
+ 전 주보다 18분 증가했어요 +
+
+ + {/* 3번 카드: 완료 세션 */} +
+
+
+ {/* 완료 세션 아이콘 */} + + + + + 완료 세션 + +
+ + 6개 + +
+
+ 전 주보다 2개 증가했어요 +
+
+
+ ); +} diff --git a/src/components/history/WeeklyTrendChart.tsx b/src/components/history/WeeklyTrendChart.tsx new file mode 100644 index 0000000..d85bf42 --- /dev/null +++ b/src/components/history/WeeklyTrendChart.tsx @@ -0,0 +1,206 @@ +// src/components/history/WeeklyTrendChart.tsx + +export default function WeeklyTrendChart() { + // 스크린샷의 완벽한 밸런스에 맞춘 포인트 좌표 설정 + const points = [ + { label: '3주 전', score: 60, x: 40, y: 110 }, + { label: '2주 전', score: 78, x: 330, y: 65 }, + { label: '지난주', score: 63, x: 620, y: 100 }, + { label: '이번주', score: 93, x: 910, y: 24 }, + ]; + + const pathString = points.reduce((acc, cur, idx) => { + return idx === 0 ? `M ${cur.x} ${cur.y}` : `${acc} L ${cur.x} ${cur.y}`; + }, ''); + + return ( +
+ {/* 전 주 대비 +9점 향상 텍스트 */} +
+ 전 주 대비 +9점 향상 +
+ + {/* 차트 메인 박스 (상하좌우 패딩 40px) */} +
+ {/* 전체 차트 영역 */} +
+ {/* 1. Y축 수치 레이블 */} +
+ 100 + 80 + 60 + 40 +
+ + {/* 2. 그래프 및 그리드 영역 */} +
+ {/* 52px 간격의 점선 4개 */} +
+
+
+
+
+
+ + {/* X축 기준 실선 */} +
+ + {/* SVG 연결선 및 점 */} +
+ + + + + {points.map((pt, idx) => { + const leftPercent = (pt.x / 950) * 100; + return ( +
+ {/* 수치 텍스트 */} +
+ + {pt.score} + +
+ + {/* 민트색 점 */} +
+ + + {pt.label === '이번주' && ( + + )} + + {pt.label === '이번주' && ( + + + + + + + + + + + + + )} +
+
+ ); + })} +
+ + {/* 3. 하단 X축 레이블 (스크린샷과 동일한 간격 및 위치) */} +
+ {points.map((pt, idx) => { + const leftPercent = (pt.x / 950) * 100; + return ( + + {pt.label} + + ); + })} +
+
+
+
+
+ ); +} diff --git a/src/pages/main/components/AttendanceSection.tsx b/src/components/main/AttendanceSection.tsx similarity index 100% rename from src/pages/main/components/AttendanceSection.tsx rename to src/components/main/AttendanceSection.tsx diff --git a/src/pages/main/components/DashboardNoti.tsx b/src/components/main/DashboardNoti.tsx similarity index 100% rename from src/pages/main/components/DashboardNoti.tsx rename to src/components/main/DashboardNoti.tsx diff --git a/src/pages/main/components/LearningBanner.tsx b/src/components/main/LearningBanner.tsx similarity index 100% rename from src/pages/main/components/LearningBanner.tsx rename to src/components/main/LearningBanner.tsx diff --git a/src/pages/main/components/RecentPractices.tsx b/src/components/main/RecentPractices.tsx similarity index 100% rename from src/pages/main/components/RecentPractices.tsx rename to src/components/main/RecentPractices.tsx diff --git a/src/pages/main/components/RecommandLearn.tsx b/src/components/main/RecommandLearn.tsx similarity index 100% rename from src/pages/main/components/RecommandLearn.tsx rename to src/components/main/RecommandLearn.tsx diff --git a/src/pages/history/HistoryPage.tsx b/src/pages/history/HistoryPage.tsx index 0c1ace6..e4d8bc0 100644 --- a/src/pages/history/HistoryPage.tsx +++ b/src/pages/history/HistoryPage.tsx @@ -1,6 +1,173 @@ -// 히스토리 목록 페이지 -function HistoryPage() { - return

히스토리

; -} +// src/pages/history/HistoryPage.tsx + +import HistorySummaryCards from '@/components/history/HistorySummaryCards'; +import GrowthProgressSection from '@/components/history/GrowthProgressSection'; +import WeeklyTrendChart from '@/components/history/WeeklyTrendChart'; +import HistoryRecentPractices from '@/components/history/HistoryRecentPractices'; + +export default function HistoryPage() { + const historyPracticeData = [ + { + practiceId: 1, + title: 'Jazz Standard Practice', + scoreChange: '+8', + scoreType: 'up' as const, + description: + '리디안 스케일 활용이 우수하며, 텐션음 해결이 자연스러웠습니다.\n박자 안정성을 더 개선하면 좋겠습니다.', + timeLabel: '소요시간 10분', + date: '오늘', + }, + { + practiceId: 2, + title: 'Modal Interchange Practice', + scoreChange: '+6', + scoreType: 'up' as const, + description: '모달 인터체인지 개념을 잘 적용했으나, 전조 구간에서 약간의 불안정함이 있었습니다.', + timeLabel: '소요시간 10분', + date: '어제', + }, + { + practiceId: 3, + title: 'Voice Leading Exercise', + scoreChange: '-5', + scoreType: 'down' as const, + description: '보이스 리딩이 매끄럽지 못했고, 코드 톤 간 연결이 부자연스러웠습니다.\n더 많은 연습이 필요합니다.', + timeLabel: '소요시간 10분', + date: '4월 30일', + }, + { + practiceId: 4, + title: 'Blues Scale Improvisation', + scoreChange: '—', + scoreType: 'neutral' as const, + description: '블루스 스케일을 효과적으로 사용했고, 리듬감이 뛰어났습니다.', + timeLabel: '소요시간 10분', + date: '4월 29일', + }, + ]; + + return ( +
+ {/* 컨텐츠 묶음 (너비 1196px 고정) */} +
+ {/* 1. 상단 타이틀 및 설명 섹션 */} +
+

+ 연주 히스토리 +

+

+ 최근 연주 기록과 흐름을 한눈에 확인해보세요. +

+
-export default HistoryPage; + {/* 2. 이번주 연주 요약 섹션 */} +
+

+ 이번주 연주 요약 +

+ +
+ + {/* 3. 영역별 성장 변화 섹션 */} +
+
+

+ 영역별 성장 변화 +

+

+ 지난주 대비 영역별 평균 점수 변화를 보여드려요. +

+
+ +
+ + {/* 4. 최근 4주 학습 추이 섹션 */} +
+
+

+ 최근 4주 학습 추이 +

+

+ 최근 4주간 평균 점수 변화를 보여드려요. +

+
+ +
+ + {/* 5. 최근 연주 리스트 (타이틀 포함) */} +
+

+ 최근 연주 +

+ +
+
+
+ ); +} diff --git a/src/pages/main/MainPage.tsx b/src/pages/main/MainPage.tsx index 75de7f6..7abcf34 100644 --- a/src/pages/main/MainPage.tsx +++ b/src/pages/main/MainPage.tsx @@ -1,10 +1,10 @@ // src/pages/main/MainPage.tsx import { useOutletContext } from 'react-router-dom'; -import AttendanceSection from './components/AttendanceSection'; -import LearningBanner from './components/LearningBanner'; -import RecommendedLearnings from './components/RecommandLearn'; -import RecentPractices from './components/RecentPractices'; -import DashboardNoti from './components/DashboardNoti'; +import AttendanceSection from '@/components/main/AttendanceSection'; +import LearningBanner from '@/components/main/LearningBanner'; +import RecommendedLearnings from '@/components/main/RecommandLearn'; +import RecentPractices from '@/components/main/RecentPractices'; +import DashboardNoti from '@/components/main/DashboardNoti'; import type { NotiItem } from '@/types/notification'; interface LayoutContextType { diff --git a/tsconfig.app.json b/tsconfig.app.json index 41750a6..5cbd9a4 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -10,6 +10,7 @@ /* Paths */ "baseUrl": ".", + "ignoreDeprecations": "6.0", "paths": { "@/*": ["src/*"] }, From 02b07d7146518d408ba7a9b37bc3d87ed13903c8 Mon Sep 17 00:00:00 2001 From: DaAn Date: Thu, 23 Jul 2026 02:00:16 +0900 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20=EC=B5=9C=EA=B7=BC=20=EC=97=B0?= =?UTF-8?q?=EC=A3=BC=20=EC=B9=B4=EB=93=9C=20=ED=81=B4=EB=A6=AD=20=EC=8B=9C?= =?UTF-8?q?=20=ED=9E=88=EC=8A=A4=ED=86=A0=EB=A6=AC=20=EC=83=81=EC=84=B8=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=9D=BC=EC=9A=B0=ED=8C=85=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../history/GrowthProgressSection.tsx | 119 ------------------ .../history/HistoryRecentPractices.tsx | 12 +- src/components/history/WeeklyTrendChart.tsx | 7 +- src/pages/history/HistoryPage.tsx | 4 +- 4 files changed, 11 insertions(+), 131 deletions(-) diff --git a/src/components/history/GrowthProgressSection.tsx b/src/components/history/GrowthProgressSection.tsx index 8ce9f71..c99295b 100644 --- a/src/components/history/GrowthProgressSection.tsx +++ b/src/components/history/GrowthProgressSection.tsx @@ -1,124 +1,5 @@ // //1. 이번주 연주 요약 카드(3개) -// // src/components/history/GrowthProgressSection.tsx - -// export default function GrowthProgressSection() { -// return ( -//
-//
-//

영역별 성장 변화

-// 지난주 대비 영역별 평균 점수 변화를 보여드려요. -//
-//
-// {/* 스케일 */} -//
-//
-// 스케일 -// +8 -//
-//
-//
-//
-//
- -// {/* 텐션 */} -//
-//
-// 텐션 -// +6 -//
-//
-//
-//
-//
- -// {/* 진행 */} -//
-//
-// 진행 -// +4 -//
-//
-//
-//
-//
- -// {/* 코드 연결 */} -//
-//
-// 코드 연결 -// -10 -//
-//
-//
-//
-//
-//
-//
-// ); -// } - -// src/components/history/GrowthProgressSection.tsx - -// export default function GrowthProgressSection() { -// return ( -//
- -// {/* 그래프 및 라벨 리스트 영역 (행마다 32px 간격) */} -//
- -// {/* 스케일 (+8) */} -//
-//
-// 스케일 -// +8 -//
-//
-//
-//
-//
-//
- -// {/* 텐션 (+6) */} -//
-//
-// 텐션 -// +6 -//
-//
-//
-//
-//
-//
- -// {/* 진행 (+4) */} -//
-//
-// 진행 -// +4 -//
-//
-//
-//
-//
-//
- -// {/* 코드 연결 (-10) */} -//
-//
-// 코드 연결 -// -10 -//
-//
-//
-//
-//
-//
- -//
-//
-// ); -// } // src/components/history/GrowthProgressSection.tsx export default function GrowthProgressSection() { diff --git a/src/components/history/HistoryRecentPractices.tsx b/src/components/history/HistoryRecentPractices.tsx index c5812b1..cf56ba1 100644 --- a/src/components/history/HistoryRecentPractices.tsx +++ b/src/components/history/HistoryRecentPractices.tsx @@ -21,11 +21,11 @@ export default function HistoryRecentPractices({ data }: HistoryRecentPracticesP return (
- {data.map((item) => { + {data?.map((item) => { return (
navigate(`/practice/${item.practiceId}`)} + onClick={() => navigate(`/history/${item.practiceId}`)} className="cursor-pointer text-white transition-colors select-none hover:border-[#3A3F4A]" style={{ display: 'flex', @@ -124,9 +124,9 @@ export default function HistoryRecentPractices({ data }: HistoryRecentPracticesP

- {/* 우측 구역 (소요시간과 꺾쇠 위치를 고정하고, 날짜 영역만 폭을 주어 글자 수 차이 흡수) */} + {/* 우측 구역 */}
- {/* 소요 시간 (위치 고정) */} + {/* 소요 시간 */}
{item.timeLabel}
- {/* 날짜 (고정 폭 영역 안에서 우측 정렬되어 글자 길이에 따른 간격 차이 흡수) */} + {/* 날짜 */}
- {/* > 아이콘 (위치 고정) */} + {/* > 아이콘 */} +9점 향상
- {/* 차트 메인 박스 (상하좌우 패딩 40px) */} + {/* 차트 메인 박스 */}
- {/* 52px 간격의 점선 4개 */} + {/* 점선 4개 */}
@@ -175,7 +174,7 @@ export default function WeeklyTrendChart() { })}
- {/* 3. 하단 X축 레이블 (스크린샷과 동일한 간격 및 위치) */} + {/* 3. 하단 X축 레이블*/}
{points.map((pt, idx) => { const leftPercent = (pt.x / 950) * 100; diff --git a/src/pages/history/HistoryPage.tsx b/src/pages/history/HistoryPage.tsx index e4d8bc0..d9e259a 100644 --- a/src/pages/history/HistoryPage.tsx +++ b/src/pages/history/HistoryPage.tsx @@ -48,7 +48,7 @@ export default function HistoryPage() { return (
- {/* 컨텐츠 묶음 (너비 1196px 고정) */} + {/* 컨텐츠 묶음 */}
{/* 1. 상단 타이틀 및 설명 섹션 */}
@@ -152,7 +152,7 @@ export default function HistoryPage() {
- {/* 5. 최근 연주 리스트 (타이틀 포함) */} + {/* 5. 최근 연주 리스트 */}

Date: Thu, 23 Jul 2026 02:07:12 +0900 Subject: [PATCH 3/5] =?UTF-8?q?refactor:=20=EC=BD=94=EB=93=9C=20=EB=82=B4?= =?UTF-8?q?=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/history/GrowthProgressSection.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/history/GrowthProgressSection.tsx b/src/components/history/GrowthProgressSection.tsx index c99295b..f7a4186 100644 --- a/src/components/history/GrowthProgressSection.tsx +++ b/src/components/history/GrowthProgressSection.tsx @@ -1,5 +1,3 @@ -// //1. 이번주 연주 요약 카드(3개) - // src/components/history/GrowthProgressSection.tsx export default function GrowthProgressSection() { From 3d66fbb4d9efde75697617b45fcc5b73107591a2 Mon Sep 17 00:00:00 2001 From: DaAn Date: Fri, 24 Jul 2026 16:28:36 +0900 Subject: [PATCH 4/5] =?UTF-8?q?refactor:=20=ED=94=BC=EA=B7=B8=EB=A7=88=20?= =?UTF-8?q?=EC=9D=B8=EB=9D=BC=EC=9D=B8=20=EC=8A=A4=ED=83=80=EC=9D=BC=20Tai?= =?UTF-8?q?lwind=20CSS=EB=A1=9C=20=EB=B3=80=ED=99=98=20=EB=B0=8F=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=EC=B9=B4=EB=93=9C=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EB=B6=84=EB=A6=AC(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../history/HistoryRecentPractices.tsx | 204 ++++++++- .../history/HistorySummaryCards.tsx | 415 +++++++++++------- tsconfig.app.json | 1 - 3 files changed, 448 insertions(+), 172 deletions(-) diff --git a/src/components/history/HistoryRecentPractices.tsx b/src/components/history/HistoryRecentPractices.tsx index cf56ba1..2d7e71c 100644 --- a/src/components/history/HistoryRecentPractices.tsx +++ b/src/components/history/HistoryRecentPractices.tsx @@ -1,5 +1,193 @@ -// src/components/history/HistoryRecentPractices.tsx +// // src/components/history/HistoryRecentPractices.tsx +// import { useNavigate } from 'react-router-dom'; + +// interface HistoryPracticeItem { +// practiceId: number; +// title: string; +// scoreChange: string; +// scoreType: 'up' | 'down' | 'neutral'; +// description: string; +// timeLabel: string; +// date: string; +// } + +// interface HistoryRecentPracticesProps { +// data: HistoryPracticeItem[]; +// } + +// export default function HistoryRecentPractices({ data }: HistoryRecentPracticesProps) { +// const navigate = useNavigate(); + +// return ( +//
+// {data?.map((item) => { +// return ( +//
navigate(`/history/${item.practiceId}`)} +// className="cursor-pointer text-white transition-colors select-none hover:border-[#3A3F4A]" +// style={{ +// display: 'flex', +// width: '100%', +// maxWidth: '1196px', +// padding: '24px', +// justifyContent: 'space-between', +// alignItems: 'center', +// borderRadius: '6px', +// background: '#161B22', +// border: '1px solid #2E3142', +// boxSizing: 'border-box', +// }}> +// {/* 좌측 구역 */} +//
+//
+// +// {item.title} +// + +//
+// {item.scoreType === 'down' ? ( +// +// +// +// ) : item.scoreType === 'up' ? ( +// +// +// +// ) : ( +// +// +// +// )} + +// {item.scoreType !== 'neutral' && ( +// +// {item.scoreChange} +// +// )} +//
+//
+ +//

+// {item.description} +//

+//
+ +// {/* 우측 구역 */} +//
+// {/* 소요 시간 */} +//
+// +// +// +// +// {item.timeLabel} +//
+ +// {/* 날짜 */} +//
+// {item.date} +//
+ +// {/* > 아이콘 */} +// +// +// +//
+//
+// ); +// })} +//
+// ); +// } import { useNavigate } from 'react-router-dom'; interface HistoryPracticeItem { @@ -26,19 +214,7 @@ export default function HistoryRecentPractices({ data }: HistoryRecentPracticesP
navigate(`/history/${item.practiceId}`)} - className="cursor-pointer text-white transition-colors select-none hover:border-[#3A3F4A]" - style={{ - display: 'flex', - width: '100%', - maxWidth: '1196px', - padding: '24px', - justifyContent: 'space-between', - alignItems: 'center', - borderRadius: '6px', - background: '#161B22', - border: '1px solid #2E3142', - boxSizing: 'border-box', - }}> + className="box-border flex w-full max-w-[1196px] cursor-pointer items-center justify-between rounded-[6px] border border-[#2E3142] bg-[#161B22] p-[24px] text-white transition-colors select-none hover:border-[#3A3F4A]"> {/* 좌측 구역 */}
diff --git a/src/components/history/HistorySummaryCards.tsx b/src/components/history/HistorySummaryCards.tsx index 81b66fc..c69cdc1 100644 --- a/src/components/history/HistorySummaryCards.tsx +++ b/src/components/history/HistorySummaryCards.tsx @@ -1,171 +1,272 @@ -// src/components/history/HistorySummaryCards.tsx +// // src/components/history/HistorySummaryCards.tsx -export default function HistorySummaryCards() { +// export default function HistorySummaryCards() { +// return ( +//
+// {/* 1번 카드: 정확도 */} +//
+//
+//
+// {/* 정확도 아이콘 */} +// +// +// +// +// 정확도 +// +//
+// +// 91% +// +//
+//
+// 전 주보다 4% 증가했어요 +//
+//
+ +// {/* 2번 카드: 연습 시간 */} +//
+//
+//
+// {/* 연습 시간 아이콘 */} +// +// +// +// +// 연습 시간 +// +//
+// +// 65분 +// +//
+//
+// 전 주보다 18분 증가했어요 +//
+//
+ +// {/* 3번 카드: 완료 세션 */} +//
+//
+//
+// {/* 완료 세션 아이콘 */} +// +// +// +// +// 완료 세션 +// +//
+// +// 6개 +// +//
+//
+// 전 주보다 2개 증가했어요 +//
+//
+//
+// ); +// } +import React from 'react'; + +interface SummaryCardProps { + icon: React.ReactNode; + title: string; + value: string; + deltaText: string; +} + +// 1. 반복되는 카드를 깔끔하게 그려줄 공통 컴포넌트 추출 +function SummaryCard({ icon, title, value, deltaText }: SummaryCardProps) { return ( -
- {/* 1번 카드: 정확도 */} -
-
-
- {/* 정확도 아이콘 */} - - - - - 정확도 - -
+
+
+
+ {icon} - 91% + {title}
-
- 전 주보다 4% 증가했어요 -
+ + {value} +
- - {/* 2번 카드: 연습 시간 */} -
-
-
- {/* 연습 시간 아이콘 */} - - - - - 연습 시간 - -
- - 65분 - -
-
- 전 주보다 18분 증가했어요 -
+
+ 전 주보다 {deltaText}했어요
+
+ ); +} - {/* 3번 카드: 완료 세션 */} -
-
-
- {/* 완료 세션 아이콘 */} - - - - - 완료 세션 - -
- - 6개 - -
-
- 전 주보다 2개 증가했어요 -
-
+// 2. 메인 컴포넌트 +export default function HistorySummaryCards() { + const cards = [ + { + title: '정확도', + value: '91%', + deltaText: '4% 증가', + icon: ( + + + + ), + }, + { + title: '연습 시간', + value: '65분', + deltaText: '18분 증가', + icon: ( + + + + ), + }, + { + title: '완료 세션', + value: '6개', + deltaText: '2개 증가', + icon: ( + + + + ), + }, + ]; + + return ( +
+ {cards.map((card) => ( + + ))}
); } diff --git a/tsconfig.app.json b/tsconfig.app.json index 5cbd9a4..41750a6 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -10,7 +10,6 @@ /* Paths */ "baseUrl": ".", - "ignoreDeprecations": "6.0", "paths": { "@/*": ["src/*"] }, From a673ae9b671b6dd9d8f53c7bf3cc19f16fc425b7 Mon Sep 17 00:00:00 2001 From: DaAn Date: Fri, 24 Jul 2026 17:23:13 +0900 Subject: [PATCH 5/5] =?UTF-8?q?refactor(history):=20=EC=B5=9C=EA=B7=BC=20?= =?UTF-8?q?=EC=97=B0=EC=A3=BC=20=EC=B9=B4=EB=93=9C=20=EC=A0=90=EC=88=98=20?= =?UTF-8?q?=EB=B3=80=EB=8F=99=20=EC=95=84=EC=9D=B4=EC=BD=98=20=EB=B6=84?= =?UTF-8?q?=EA=B8=B0=20=EC=B2=98=EB=A6=AC=20=EB=B0=8F=20=EC=8A=A4=ED=83=80?= =?UTF-8?q?=EC=9D=BC=20=EC=B5=9C=EC=A0=81=ED=99=94(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../history/GrowthProgressSection.tsx | 2 +- .../history/HistoryRecentPractices.tsx | 361 ++++++------------ .../history/HistorySummaryCards.tsx | 173 +-------- src/components/history/WeeklyTrendChart.tsx | 12 +- src/pages/history/HistoryPage.tsx | 86 +---- 5 files changed, 134 insertions(+), 500 deletions(-) diff --git a/src/components/history/GrowthProgressSection.tsx b/src/components/history/GrowthProgressSection.tsx index f7a4186..6466ce8 100644 --- a/src/components/history/GrowthProgressSection.tsx +++ b/src/components/history/GrowthProgressSection.tsx @@ -3,7 +3,7 @@ export default function GrowthProgressSection() { return (
- {/* 그래프 및 라벨 리스트 영역 (행마다 32px 간격) */} + {/* 그래프 및 라벨 리스트 영역 */}
{/* 스케일 (+8) */}
diff --git a/src/components/history/HistoryRecentPractices.tsx b/src/components/history/HistoryRecentPractices.tsx index 2d7e71c..657eba3 100644 --- a/src/components/history/HistoryRecentPractices.tsx +++ b/src/components/history/HistoryRecentPractices.tsx @@ -1,223 +1,86 @@ // // src/components/history/HistoryRecentPractices.tsx -// import { useNavigate } from 'react-router-dom'; - -// interface HistoryPracticeItem { -// practiceId: number; -// title: string; -// scoreChange: string; -// scoreType: 'up' | 'down' | 'neutral'; -// description: string; -// timeLabel: string; -// date: string; -// } - -// interface HistoryRecentPracticesProps { -// data: HistoryPracticeItem[]; -// } - -// export default function HistoryRecentPractices({ data }: HistoryRecentPracticesProps) { -// const navigate = useNavigate(); - -// return ( -//
-// {data?.map((item) => { -// return ( -//
navigate(`/history/${item.practiceId}`)} -// className="cursor-pointer text-white transition-colors select-none hover:border-[#3A3F4A]" -// style={{ -// display: 'flex', -// width: '100%', -// maxWidth: '1196px', -// padding: '24px', -// justifyContent: 'space-between', -// alignItems: 'center', -// borderRadius: '6px', -// background: '#161B22', -// border: '1px solid #2E3142', -// boxSizing: 'border-box', -// }}> -// {/* 좌측 구역 */} -//
-//
-// -// {item.title} -// - -//
-// {item.scoreType === 'down' ? ( -// -// -// -// ) : item.scoreType === 'up' ? ( -// -// -// -// ) : ( -// -// -// -// )} - -// {item.scoreType !== 'neutral' && ( -// -// {item.scoreChange} -// -// )} -//
-//
- -//

-// {item.description} -//

-//
- -// {/* 우측 구역 */} -//
-// {/* 소요 시간 */} -//
-// -// -// -// -// {item.timeLabel} -//
- -// {/* 날짜 */} -//
-// {item.date} -//
- -// {/* > 아이콘 */} -// -// -// -//
-//
-// ); -// })} -//
-// ); -// } import { useNavigate } from 'react-router-dom'; -interface HistoryPracticeItem { - practiceId: number; +export interface HistoryPracticeItem { + practiceId?: number; + playingId?: number; title: string; - scoreChange: string; + scoreChange: string | number; scoreType: 'up' | 'down' | 'neutral'; - description: string; - timeLabel: string; - date: string; + description?: string; + summary?: string; + timeLabel?: string; + durationMinutes?: number; + date?: string; + relativeDate?: string; } interface HistoryRecentPracticesProps { - data: HistoryPracticeItem[]; + data?: HistoryPracticeItem[]; } +const FALLBACK_MOCK_ITEMS: HistoryPracticeItem[] = [ + { + practiceId: 1, + title: 'Jazz Standard Practice', + scoreChange: '+8점', + scoreType: 'up', + description: + '리디안 스케일 활용이 우수하며, 텐션음 해결이 자연스러웠습니다.\n박자 안정성을 더 개선하면 좋겠습니다.', + timeLabel: '소요시간 10분', + date: '오늘', + }, + { + practiceId: 2, + title: 'Modal Interchange Practice', + scoreChange: '+6점', + scoreType: 'up', + description: '모달 인터체인지 개념을 잘 적용했으나, 전조 구간에서 약간의 불안정함이 있었습니다.', + timeLabel: '소요시간 10분', + date: '어제', + }, + { + practiceId: 3, + title: 'Voice Leading Exercise', + scoreChange: '-5점', + scoreType: 'down', + description: '보이스 리딩이 매끄럽지 못했고, 코드 톤 간 연결이 부자연스러웠습니다.\n더 많은 연습이 필요합니다.', + timeLabel: '소요시간 10분', + date: '4월 30일', + }, + { + practiceId: 4, + title: 'Blues Scale Improvisation', + scoreChange: '—', + scoreType: 'neutral', + description: '블루스 스케일을 효과적으로 사용했고, 리듬감이 뛰어났습니다.', + timeLabel: '소요시간 10분', + date: '4월 29일', + }, +]; + export default function HistoryRecentPractices({ data }: HistoryRecentPracticesProps) { const navigate = useNavigate(); + const practiceList = data && data.length > 0 ? data : FALLBACK_MOCK_ITEMS; return (
- {data?.map((item) => { + {practiceList.map((item) => { + const id = item.practiceId ?? item.playingId ?? 1; + const desc = item.description ?? item.summary ?? ''; + const time = item.timeLabel ?? (item.durationMinutes ? `소요시간 ${item.durationMinutes}분` : ''); + const when = item.date ?? item.relativeDate ?? ''; + return (
navigate(`/history/${item.practiceId}`)} + key={id} + onClick={() => navigate(`/history/${id}`)} className="box-border flex w-full max-w-[1196px] cursor-pointer items-center justify-between rounded-[6px] border border-[#2E3142] bg-[#161B22] p-[24px] text-white transition-colors select-none hover:border-[#3A3F4A]"> {/* 좌측 구역 */}
+ {/* 곡명 */} + {/* 점수 변화 아이콘 및 텍스트 영역 */}
{item.scoreType === 'down' ? ( - - - + <> + + + + + {item.scoreChange} + + ) : item.scoreType === 'up' ? ( - - - + <> + + + + + {item.scoreChange} + + ) : ( + /* neutral(중립)일 때는 텍스트를 숨기고 수평 바 SVG 아이콘만 깔끔하게 출력 */ - + style={{ width: '24px', height: '24px', flexShrink: 0 }}> + )} - - {item.scoreType !== 'neutral' && ( - - {item.scoreChange} - - )}
+ {/* 상세 설명 */}

- {item.description} + {desc}

@@ -323,7 +200,7 @@ export default function HistoryRecentPractices({ data }: HistoryRecentPracticesP - {item.timeLabel} + {time}
{/* 날짜 */} @@ -338,10 +215,10 @@ export default function HistoryRecentPractices({ data }: HistoryRecentPracticesP lineHeight: '20px', color: '#AEB1B6', }}> - {item.date} + {when}
- {/* > 아이콘 */} + {/* 우측 단일 화살표 아이콘 */} -// {/* 1번 카드: 정확도 */} -//
-//
-//
-// {/* 정확도 아이콘 */} -// -// -// -// -// 정확도 -// -//
-// -// 91% -// -//
-//
-// 전 주보다 4% 증가했어요 -//
-//
- -// {/* 2번 카드: 연습 시간 */} -//
-//
-//
-// {/* 연습 시간 아이콘 */} -// -// -// -// -// 연습 시간 -// -//
-// -// 65분 -// -//
-//
-// 전 주보다 18분 증가했어요 -//
-//
- -// {/* 3번 카드: 완료 세션 */} -//
-//
-//
-// {/* 완료 세션 아이콘 */} -// -// -// -// -// 완료 세션 -// -//
-// -// 6개 -// -//
-//
-// 전 주보다 2개 증가했어요 -//
-//
-//
-// ); -// } import React from 'react'; interface SummaryCardProps { @@ -178,7 +9,7 @@ interface SummaryCardProps { deltaText: string; } -// 1. 반복되는 카드를 깔끔하게 그려줄 공통 컴포넌트 추출 +// 1. 이번주 요약 카드 공통 컴포넌트 추출 function SummaryCard({ icon, title, value, deltaText }: SummaryCardProps) { return (
diff --git a/src/components/history/WeeklyTrendChart.tsx b/src/components/history/WeeklyTrendChart.tsx index b37c16a..0bfce85 100644 --- a/src/components/history/WeeklyTrendChart.tsx +++ b/src/components/history/WeeklyTrendChart.tsx @@ -1,4 +1,4 @@ -// src/components/history/WeeklyTrendChart.tsx +// // src/components/history/WeeklyTrendChart.tsx export default function WeeklyTrendChart() { const points = [ @@ -30,15 +30,7 @@ export default function WeeklyTrendChart() {
{/* 차트 메인 박스 */} -
+
{/* 전체 차트 영역 */}
{/* 1. Y축 수치 레이블 */} diff --git a/src/pages/history/HistoryPage.tsx b/src/pages/history/HistoryPage.tsx index d9e259a..6ef9cee 100644 --- a/src/pages/history/HistoryPage.tsx +++ b/src/pages/history/HistoryPage.tsx @@ -48,47 +48,21 @@ export default function HistoryPage() { return (
- {/* 컨텐츠 묶음 */} + {/* 컨텐츠 묶음 */}
{/* 1. 상단 타이틀 및 설명 섹션 */}
-

+

연주 히스토리

-

+

최근 연주 기록과 흐름을 한눈에 확인해보세요.

{/* 2. 이번주 연주 요약 섹션 */}
-

+

이번주 연주 요약

@@ -97,26 +71,10 @@ export default function HistoryPage() { {/* 3. 영역별 성장 변화 섹션 */}
-

+

영역별 성장 변화

-

+

지난주 대비 영역별 평균 점수 변화를 보여드려요.

@@ -126,43 +84,19 @@ export default function HistoryPage() { {/* 4. 최근 4주 학습 추이 섹션 */}
-

+

최근 4주 학습 추이

-

+

최근 4주간 평균 점수 변화를 보여드려요.

- {/* 5. 최근 연주 리스트 */} + {/* 5. 최근 연주 리스트 */}
-

+

최근 연주