개발일지 2026-06-24
피아노 학원 플랫폼 개발일지 (2026-06-24)
오늘 추가/수정한 기능 (요약)
- 레슨(student) : "레슨" 하단 탭 신설 — 학생 앱 바텀 네비게이션에
레슨탭을 추가하고, DB 메뉴로 등록. - 레슨 > 레슨노트·진도 : 학습 진도 + 레슨노트 화면 추가 — 현재 레벨/진도바, 강사 코멘트, 레슨노트 목록·상세(요약/피드백/다음 과제).
- 레슨 > 연습 로그 : 주간 연습 통계 + 연습일지 추가 — 요일별 막대그래프, 연습 기록 추가(목업), 연습일지 카드 목록.
수정/생성 파일 목록
DB (piano_dev)
menus(INSERT) — STUDENT 사이트에레슨(id=307, path=/lessons, icon=Assignment) 메뉴 추가, 정렬 재정렬.menu_roles(INSERT) — 레슨 메뉴에 role 5(학생) 매핑.
프론트 (student)
student/src/pages/LessonsPage.tsx(신규) — 레슨 페이지(2개 서브탭: 레슨노트·진도 / 연습 로그).student/src/data/dummy.ts(수정) — 레슨 관련 더미 데이터 4종 추가.student/src/App.tsx(수정) —/lessons라우트 추가.student/src/components/Layout.tsx(수정) — DB 아이콘 문자열Assignment매핑 추가.
파일별 상세
1. DB 메뉴 등록 — menus / menu_roles
학생 앱은 사이드/하단 메뉴를 코드에 박지 않고 DB의 menus 테이블을 /menus/my로 받아 토큰 역할에 맞게 렌더링한다. 그래서 새 탭을 추가하려면 화면 코드뿐 아니라 DB에도 메뉴를 한 줄 넣어야 한다.
-- STUDENT 사이트 레슨 메뉴
INSERT INTO menus (id, site, name, path, icon, parent_id, sort_order, visible)
VALUES (307, 'STUDENT', '레슨', '/lessons', 'Assignment', NULL, 3, 1);
-- 학생(role 5)에게 노출
INSERT INTO menu_roles (menu_id, role_id) VALUES (307, 5);
기존 STUDENT 메뉴의 sort_order도 손봐서 홈 → 예약 → 레슨 → 악보 → 구매 → 학원 → 마이 → 더보기 순서가 되도록 재정렬했다. 하단 탭은 상위 4개(홈/예약/레슨/악보) + 더보기로 구성되기 때문에, 레슨을 3번째에 끼워 넣는 게 핵심이었다.
실DB는
schema.sql자동적용이 아니라 SSH 터널로 직접 INSERT 했다.
2. 레슨 페이지 — LessonsPage.tsx (신규)
서브탭 2개로 구성했다.
const [tab, setTab] = useState(0); // 0: 레슨노트·진도, 1: 연습 로그
<Tabs value={tab} onChange={(_, v) => setTab(v)} variant="fullWidth">
<Tab label="레슨노트·진도" />
<Tab label="연습 로그" />
</Tabs>
탭 0 (레슨노트·진도)
- 현재 레벨 카드:
primary.main배경에Lv.3 초급 III, 진행률 65%LinearProgress(흰색 바). 학습 진도를 별도 화면으로 빼지 않고 레벨 카드에 통합했다. - 강사 코멘트 카드: 강사 아바타(이름 첫 글자) + 코멘트.
- 레슨노트 목록: 기본 2개만 보여주고
전체보기토글. 카드 클릭 시 상세 모달. - 레슨노트 상세 모달: 레슨 요약 / 강사 피드백(강조 박스) / 다음 과제(번호 칩 리스트).
탭 1 (연습 로그)
- 주간 연습 통계: 요일별 연습 시간을 막대그래프로. 최대값 기준으로 높이를 비율 계산.
const maxMin = Math.max(...weeklyPracticeStats.map(s => s.minutes), 1);
// ...
<Box sx={{ height: `${(s.minutes / maxMin) * 70}px`,
bgcolor: s.minutes > 0 ? 'primary.main' : 'grey.200' }} />
- 연습 기록 추가: 날짜/시간(분)/곡목/컨디션(좋음·보통·아쉬움)/메모 입력 모달. 저장 시 목업 토스트.
- 연습일지 카드 목록: 날짜 + 컨디션 칩(
moodColor로 색 구분) + 시간 + 곡목 칩 + 메모.
const moodColor = (m: string) =>
m === '좋음' ? 'success' : m === '아쉬움' ? 'error' : 'default';
모바일 하단 네비게이션에 토스트가 가리지 않도록 Snackbar 위치를 조정했다.
<Snackbar sx={{ bottom: { xs: 80, md: 24 } }} ... />
3. 더미 데이터 — dummy.ts (수정)
레슨 화면용 더미 4종을 추가했다.
lessonProgress— 레벨/레벨명/진행률/강사명/강사 코멘트.lessonNotes— 레슨노트 3건(날짜/제목/강사/요약/피드백/다음 과제/곡목).practiceLogs— 연습일지 5건(날짜/시간/곡목/메모/컨디션).weeklyPracticeStats— 월~일 요일별 연습 분(分) 배열.
4. 라우트 — App.tsx (수정)
import LessonsPage from './pages/LessonsPage';
// ...
<Route path="/lessons" element={<LessonsPage />} />
5. 아이콘 매핑 — Layout.tsx (수정)
DB menus.icon은 문자열("Assignment")로 저장돼 있어, 이를 실제 MUI 아이콘으로 바꿔주는 iconMap에 항목을 추가해야 메뉴가 올바른 아이콘으로 뜬다.
const iconMap: Record<string, React.ReactNode> = {
// ...
CalendarMonth: <CalendarMonth />,
Assignment: <Assignment />, // 레슨 메뉴 아이콘
Payment: <Payment />,
// ...
};
트러블슈팅 메모
- Edit 전 반드시 Read: 방금 생성한 파일이라도 Edit 도구가 "파일을 아직 읽지 않았다"고 막아서, App.tsx/Layout.tsx/LessonsPage.tsx 모두 수정 직전에 다시 읽어야 했다.
- 미사용 import 경고: 처음에
Assignment를 LessonsPage에도 import 했는데 거기선 안 써서 eslint 경고가 났다. LessonsPage에선 빼고Layout.tsx의 iconMap에서만 쓰도록 정리하니 경고 0. - 하단 탭 정렬: 하단 바텀 네비는 상위 4개 메뉴를 자르기 때문에, DB
sort_order를 안 맞추면 레슨이 하단 탭에서 빠지거나 엉뚱한 위치에 떴다. → 정렬 재조정으로 해결.
검증
- student:
npx tsc --noEmit통과,GENERATE_SOURCEMAP=false CI=false npm run build컴파일 성공(경고 0). - DB:
/menus/my응답에 레슨 메뉴 포함 확인.
결론 / 배운 점
- DB 주도 메뉴 구조에서는 화면 추가 = (페이지 코드 + 라우트 + DB 메뉴 + 아이콘 매핑) 4박자를 다 맞춰야 한 화면이 제대로 뜬다. 한 군데만 빠져도 메뉴가 안 보이거나 깨진다.
- 레슨 탭은 와이어프레임 기준 레슨노트·진도 / 연습 로그 2개 서브탭이 핵심이라, 학습 진도는 별도 화면 대신 레벨 카드에 통합해 화면 수를 줄였다.
- 다음 단계: 더미 → 실제 API 연동(레슨노트 조회, 연습 로그 CRUD), 그리고 와이어프레임의 구매/학원/더보기를 마이페이지 하위로 통합하는 정리.
댓글 0
- 첫 번째 댓글을 남겨보세요.