사이트픽 블로그
← 목록으로

개발일지 2026-06-24

피아노 학원 플랫폼 개발일지 (2026-06-24)

오늘 추가/수정한 기능 (요약)

  1. 레슨(student) : "레슨" 하단 탭 신설 — 학생 앱 바텀 네비게이션에 레슨 탭을 추가하고, DB 메뉴로 등록.
  2. 레슨 > 레슨노트·진도 : 학습 진도 + 레슨노트 화면 추가 — 현재 레벨/진도바, 강사 코멘트, 레슨노트 목록·상세(요약/피드백/다음 과제).
  3. 레슨 > 연습 로그 : 주간 연습 통계 + 연습일지 추가 — 요일별 막대그래프, 연습 기록 추가(목업), 연습일지 카드 목록.

수정/생성 파일 목록

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

  • 첫 번째 댓글을 남겨보세요.