9.4 KiB
Phase 16: 반응형 웹 디자인 (Responsive Web Design)
목표
모바일 화면(360~430px 기준)에서도 관리자 패널을 정상적으로 사용할 수 있도록 세 가지 영역을 개선한다.
- 사이드바 — 모바일 햄버거 메뉴 + 슬라이드인 패널
- 표 — 섹션 경계 내에서 가로 스크롤되도록 헤더 레이아웃 수정
- 페이지네이션 — 모바일에서도 오른쪽 정렬 유지
1. 모바일 사이드바
현재 상태
app/(admin)/layout.tsx의 <aside>는 hidden md:block으로 모바일에서 완전히 숨겨진다. 헤더에 햄버거 버튼이 없어 모바일에서 내비게이션 자체에 접근할 수 없다.
layout.tsx는 서버 컴포넌트(await auth() 사용)이므로 useState를 직접 쓸 수 없다.
구현 방식
추천 닫기 UI: 배경 오버레이(backdrop) + 사이드바 내 X 버튼 조합
- 오버레이: 사이드바 외부 어디든 탭하면 닫힘 → 모바일 터치 UX에 가장 자연스러움
- X 버튼: 사이드바 상단에 명시적으로 표시 → 처음 쓰는 사용자도 직관적으로 이해
- ESC 키:
useEffect로keydown리스너 추가 → 접근성 향상
두 가지 닫기 수단을 모두 제공해 사용자가 선호하는 방식으로 닫을 수 있다.
생성할 파일
app/(admin)/MobileNav.tsx (신규, "use client")
"use client";
// useState(isOpen), useEffect(ESC 키)
// 렌더링:
// 1. <button> 햄버거 (md:hidden — 데스크탑에서 숨김)
// 2. isOpen일 때: fixed inset-0 z-20 bg-black/50 backdrop (클릭 시 닫힘)
// 3. 슬라이드인 패널: fixed inset-y-0 left-0 z-30 w-64
// - 상단: "chocoadmin" 로고 + X 버튼 (오른쪽)
// - 내비게이션 링크 목록 (navItems 배열)
// - 링크 클릭 시 자동 닫힘 (useRouter 또는 onClick)
// 4. 애니메이션: translate-x-0 ↔ -translate-x-full, transition-transform duration-200
패널이 열릴 때 document.body에 overflow: hidden을 걸어 배경 스크롤을 막는다.
수정할 파일
app/(admin)/layout.tsx
+ import { MobileNav } from "./MobileNav";
<header className="sticky top-0 z-10 flex h-16 items-center justify-between border-b bg-background px-5">
+ <MobileNav navItems={navItems} />
<div>
<p className="text-sm font-medium">{session.user.name}</p>
...
</div>
<form action={logoutAction}>...</form>
</header>
- 데스크탑
<aside>는 그대로 유지 (hidden md:block) MobileNav가 렌더링하는 햄버거 버튼은md:hidden이므로 데스크탑에서는 보이지 않음- 헤더 왼쪽: 햄버거(모바일) | 사용자 정보(기존) →
justify-between으로 양 끝 배치 유지
2. 표 — 섹션 경계 내 가로 스크롤
원인 분석
왜 StudentsSection / LogsSection은 정상 동작하는가
두 섹션 모두 "use client" 컴포넌트로, useEffect 안에서 데이터를 직접 fetch한다.
- SSR 시점:
data === null→ 로딩 중...<div class="h-24 ...">렌더 - 섹션 너비가 뷰포트 기준으로 확정된 뒤, 클라이언트에서 데이터를 받아 표를 삽입
- 표가 나타날 때 이미 섹션 너비가 확정되어 있으므로
overflow-x-auto가 스크롤을 정상 처리
왜 ExtensionRequestsSection / UpgradeRequestsSection은 섹션이 늘어나는가
두 섹션도 "use client"이지만, 데이터를 서버에서 props로 받아 SSR 초기 렌더에 표가 포함된다.
- SSR 시점:
extensionRequests/upgradeRequestsprops가 이미 채워진 상태로 렌더 - 초기 레이아웃 계산 시점에
min-w-[560px]표가 DOM에 존재 section요소에overflow설정이 없어(overflow: visible기본값) 표의 너비가 섹션을 밀어냄- 섹션 자체가 560px+ 로 확장 →
div.overflow-x-auto도 그 너비를 그대로 가져가므로 스크롤이 생기지 않음
핵심 수정: section에 overflow-hidden 추가
section 요소에 overflow: hidden을 적용하면 BFC(Block Formatting Context)가 확립되어 어떤 자식도 섹션 너비를 밀어낼 수 없다. 섹션은 항상 뷰포트 기준 너비로 고정되고, 표는 div.overflow-x-auto 안에서 가로 스크롤된다.
- <section className="rounded-lg border bg-background p-5">
+ <section className="overflow-hidden rounded-lg border bg-background p-5">
overflow: hidden은 섹션의 가로 팽창만 막는다. 섹션의 height는 auto이므로 세로로는 내용 크기에 맞게 늘어나며 내용이 잘리지 않는다. 또한 div.overflow-x-auto의 스크롤바는 섹션 내부에 위치하므로 정상 표시된다.
추가 수정: 섹션 헤더 레이아웃 (UX 목적)
overflow-hidden으로 표 팽창은 막을 수 있지만, 헤더의 버튼/셀렉트 영역이 모바일 뷰포트보다 넓을 경우 overflow: hidden에 의해 잘려 보이지 않는다. 버튼 접근성을 위해 헤더도 모바일에서 세로 스택으로 변경한다.
수정할 파일
app/(admin)/maestros/[maestroId]/ExtensionRequestsSection.tsx
- <section className="rounded-lg border bg-background p-5">
+ <section className="overflow-hidden rounded-lg border bg-background p-5">
- <div className="flex items-start justify-between gap-2">
+ <div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
<div>
<h2 ...>연장 신청 이력</h2>
<p ...>전체 N건 중 최근 20건</p>
</div>
- <div className="flex flex-col items-end gap-1">
+ <div className="flex flex-col items-start gap-1 sm:items-end">
<Button ...>연장</Button>
...
</div>
</div>
app/(admin)/maestros/[maestroId]/UpgradeRequestsSection.tsx
- <section className="rounded-lg border bg-background p-5">
+ <section className="overflow-hidden rounded-lg border bg-background p-5">
- <div className="flex items-start justify-between gap-2">
+ <div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
<div>
<h2 ...>업그레이드 신청 이력</h2>
<p ...>전체 N건 중 최근 20건</p>
</div>
- <div className="flex flex-col items-end gap-1">
+ <div className="flex flex-col items-start gap-1 sm:items-end">
- <div className="flex items-center gap-2">
+ <div className="flex flex-wrap items-center gap-2">
<select ...>...</select>
<Button ...>업그레이드</Button>
</div>
...
</div>
</div>
StudentsSection / LogsSection
수정 불필요. 클라이언트 fetch 방식으로 인해 SSR 초기 렌더에 표가 없어 섹션 너비가 먼저 확정되므로 현재 구조 그대로 정상 동작한다.
3. 페이지네이션 오른쪽 정렬
현재 상태
모든 페이지네이션 컨테이너:
flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between
모바일(flex-col)에서 nav(페이지 버튼)는 페이지 크기 선택 아래에 위치하며 왼쪽 정렬된다.
수정 방법
<nav> 요소에 self-end 추가, 데스크탑에서는 sm:self-auto로 복원:
<nav
aria-label="..."
- className="flex flex-wrap items-center gap-1.5"
+ className="self-end flex flex-wrap items-center gap-1.5 sm:self-auto"
>
- 모바일(flex-col):
align-self: flex-end→ nav가 오른쪽으로 붙음 - 데스크탑(flex-row + justify-between):
sm:self-auto→ 기존items-center적용, 수직 중앙 정렬 유지
페이지 크기 선택 요소("페이지당 N개 표시")는 변경하지 않아 왼쪽에 그대로 유지.
수정 대상 파일 및 위치
| 파일 | nav aria-label |
|---|---|
app/(admin)/extension-requests/page.tsx |
"연장 신청 목록 페이지" |
app/(admin)/upgrade-requests/page.tsx |
"업그레이드 신청 목록 페이지" |
app/(admin)/maestros/page.tsx |
"마에스트로 목록 페이지" |
app/(admin)/maestros/[maestroId]/StudentsSection.tsx |
"학생 목록 페이지" |
app/(admin)/maestros/[maestroId]/LogsSection.tsx |
"처리 로그 페이지" |
작업 순서
MobileNav.tsx생성 →layout.tsx수정 (사이드바)ExtensionRequestsSection.tsx:section에overflow-hidden추가 + 헤더 flex-col 수정UpgradeRequestsSection.tsx:section에overflow-hidden추가 + 헤더 flex-col 수정- 페이지네이션 nav 5개 파일 수정
검증 포인트
- 모바일(375px)에서 햄버거 버튼 표시 확인
- 사이드바 슬라이드인/아웃 애니메이션 확인
- 오버레이 탭 → 사이드바 닫힘 확인
- X 버튼 탭 → 사이드바 닫힘 확인
- 데스크탑(≥768px)에서 햄버거 버튼 미표시 확인
ExtensionRequestsSection섹션이 모바일 뷰포트 너비 안에 머무르는지 확인ExtensionRequestsSection표가 섹션 내부에서 가로 스크롤되는지 확인ExtensionRequestsSection헤더 버튼이 모바일에서 잘리지 않고 표시되는지 확인UpgradeRequestsSection섹션이 모바일 뷰포트 너비 안에 머무르는지 확인UpgradeRequestsSection표가 섹션 내부에서 가로 스크롤되는지 확인UpgradeRequestsSection요금제 셀렉트 + 버튼이 모바일에서 접근 가능한지 확인- 모든 페이지네이션 nav가 모바일에서 오른쪽 정렬 확인
- 데스크탑에서 기존 레이아웃 회귀 없음 확인