반응형 웹 디자인 적용

This commit is contained in:
2026-07-07 10:06:13 +09:00
parent e6882b158a
commit bd921528f4
11 changed files with 350 additions and 28 deletions
+222
View File
@@ -0,0 +1,222 @@
# Phase 16: 반응형 웹 디자인 (Responsive Web Design)
## 목표
모바일 화면(360~430px 기준)에서도 관리자 패널을 정상적으로 사용할 수 있도록 세 가지 영역을 개선한다.
1. 사이드바 — 모바일 햄버거 메뉴 + 슬라이드인 패널
2. 표 — 섹션 경계 내에서 가로 스크롤되도록 헤더 레이아웃 수정
3. 페이지네이션 — 모바일에서도 오른쪽 정렬 유지
---
## 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"`)
```tsx
"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`
```diff
+ 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` / `upgradeRequests` props가 이미 채워진 상태로 렌더
- 초기 레이아웃 계산 시점에 `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` 안에서 가로 스크롤된다.
```diff
- <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`
```diff
- <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`
```diff
- <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`로 복원:
```diff
<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` | "처리 로그 페이지" |
---
## 작업 순서
1. `MobileNav.tsx` 생성 → `layout.tsx` 수정 (사이드바)
2. `ExtensionRequestsSection.tsx`: `section``overflow-hidden` 추가 + 헤더 flex-col 수정
3. `UpgradeRequestsSection.tsx`: `section``overflow-hidden` 추가 + 헤더 flex-col 수정
4. 페이지네이션 nav 5개 파일 수정
## 검증 포인트
- [ ] 모바일(375px)에서 햄버거 버튼 표시 확인
- [ ] 사이드바 슬라이드인/아웃 애니메이션 확인
- [ ] 오버레이 탭 → 사이드바 닫힘 확인
- [ ] X 버튼 탭 → 사이드바 닫힘 확인
- [ ] 데스크탑(≥768px)에서 햄버거 버튼 미표시 확인
- [ ] `ExtensionRequestsSection` 섹션이 모바일 뷰포트 너비 안에 머무르는지 확인
- [ ] `ExtensionRequestsSection` 표가 섹션 내부에서 가로 스크롤되는지 확인
- [ ] `ExtensionRequestsSection` 헤더 버튼이 모바일에서 잘리지 않고 표시되는지 확인
- [ ] `UpgradeRequestsSection` 섹션이 모바일 뷰포트 너비 안에 머무르는지 확인
- [ ] `UpgradeRequestsSection` 표가 섹션 내부에서 가로 스크롤되는지 확인
- [ ] `UpgradeRequestsSection` 요금제 셀렉트 + 버튼이 모바일에서 접근 가능한지 확인
- [ ] 모든 페이지네이션 nav가 모바일에서 오른쪽 정렬 확인
- [ ] 데스크탑에서 기존 레이아웃 회귀 없음 확인