Header와 Bottom Navigation 추가

- Header 컴포넌트 개발 (뒤로가기, 메뉴, 프로필 버튼 지원)
- Bottom Navigation 컴포넌트 개발 (홈, 이벤트, 분석, 프로필)
- (main) 레이아웃 생성 및 Bottom Navigation 자동 적용
- 홈 페이지(대시보드)에 Header 추가
- 이벤트 목록 페이지에 Header 추가
- 성과 분석 페이지에 Header 추가
- 모든 (main) 그룹 페이지에서 Header와 Bottom Navigation 자동 표시
- Material-UI AppBar 및 BottomNavigation 컴포넌트 사용
- 반응형 디자인 적용

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
cherry2250 2025-10-24 16:27:49 +09:00
parent 7ebaa38807
commit ea94dc97a1
6 changed files with 281 additions and 29 deletions

View File

@ -15,6 +15,7 @@ import {
Payments,
People,
} from '@mui/icons-material';
import Header from '@/components/layout/Header';
// Mock 데이터
const mockAnalyticsData = {
@ -95,6 +96,8 @@ export default function AnalyticsPage() {
mockAnalyticsData;
return (
<>
<Header title="성과 분석" showBack={true} showMenu={false} showProfile={true} />
<Box sx={{ minHeight: '100vh', bgcolor: 'background.default', pb: 10 }}>
<Container maxWidth="lg" sx={{ pt: 4, pb: 4, px: { xs: 3, sm: 3, md: 4 } }}>
{/* Title with Real-time Indicator */}
@ -507,5 +510,6 @@ export default function AnalyticsPage() {
</Grid>
</Container>
</Box>
</>
);
}

View File

@ -19,6 +19,7 @@ import {
Grid,
} from '@mui/material';
import { Search, FilterList } from '@mui/icons-material';
import Header from '@/components/layout/Header';
// Mock 데이터
const mockEvents = [
@ -140,6 +141,8 @@ export default function EventsPage() {
};
return (
<>
<Header title="이벤트 목록" showBack={true} showMenu={false} showProfile={true} />
<Box sx={{ minHeight: '100vh', bgcolor: 'background.default', pb: 10 }}>
<Container maxWidth="lg" sx={{ pt: 4, pb: 4, px: { xs: 3, sm: 3, md: 4 } }}>
{/* Search Section */}
@ -323,5 +326,6 @@ export default function EventsPage() {
)}
</Container>
</Box>
</>
);
}

11
src/app/(main)/layout.tsx Normal file
View File

@ -0,0 +1,11 @@
import { Box } from '@mui/material';
import BottomNavigation from '@/components/layout/BottomNavigation';
export default function MainLayout({ children }: { children: React.ReactNode }) {
return (
<Box sx={{ pb: { xs: 7, sm: 8 }, pt: { xs: 7, sm: 8 } }}>
{children}
<BottomNavigation />
</Box>
);
}

View File

@ -21,6 +21,7 @@ import {
Edit,
CheckCircle,
} from '@mui/icons-material';
import Header from '@/components/layout/Header';
// Mock 사용자 데이터 (API 연동 전까지 임시 사용)
const mockUser = {
@ -79,6 +80,8 @@ export default function HomePage() {
};
return (
<>
<Header title="대시보드" showBack={false} showMenu={false} showProfile={true} />
<Box
sx={{
pb: 10,
@ -451,5 +454,6 @@ export default function HomePage() {
<Add sx={{ color: 'white' }} />
</Fab>
</Box>
</>
);
}

View File

@ -0,0 +1,115 @@
'use client';
import { usePathname, useRouter } from 'next/navigation';
import { BottomNavigation as MuiBottomNavigation, BottomNavigationAction, Paper } from '@mui/material';
import { Home, Celebration, Analytics, Person } from '@mui/icons-material';
export default function BottomNavigation() {
const pathname = usePathname();
const router = useRouter();
// Determine current value based on pathname
const getCurrentValue = () => {
if (pathname === '/' || pathname === '/dashboard') return 'home';
if (pathname.startsWith('/events')) return 'events';
if (pathname.startsWith('/analytics')) return 'analytics';
if (pathname.startsWith('/profile')) return 'profile';
return 'home';
};
const value = getCurrentValue();
const handleChange = (_event: React.SyntheticEvent, newValue: string) => {
switch (newValue) {
case 'home':
router.push('/');
break;
case 'events':
router.push('/events');
break;
case 'analytics':
router.push('/analytics');
break;
case 'profile':
router.push('/profile');
break;
}
};
return (
<Paper
sx={{
position: 'fixed',
bottom: 0,
left: 0,
right: 0,
zIndex: (theme) => theme.zIndex.drawer + 1,
borderTop: '1px solid',
borderColor: 'divider',
}}
elevation={3}
>
<MuiBottomNavigation
value={value}
onChange={handleChange}
showLabels
sx={{
height: { xs: 56, sm: 64 },
'& .MuiBottomNavigationAction-root': {
minWidth: 'auto',
px: 1,
},
'& .MuiBottomNavigationAction-label': {
fontSize: { xs: '0.7rem', sm: '0.75rem' },
fontWeight: 600,
mt: 0.5,
},
'& .MuiBottomNavigationAction-root.Mui-selected': {
color: 'primary.main',
},
}}
>
<BottomNavigationAction
label="홈"
value="home"
icon={<Home />}
sx={{
'& .MuiSvgIcon-root': {
fontSize: { xs: 24, sm: 28 },
},
}}
/>
<BottomNavigationAction
label="이벤트"
value="events"
icon={<Celebration />}
sx={{
'& .MuiSvgIcon-root': {
fontSize: { xs: 24, sm: 28 },
},
}}
/>
<BottomNavigationAction
label="분석"
value="analytics"
icon={<Analytics />}
sx={{
'& .MuiSvgIcon-root': {
fontSize: { xs: 24, sm: 28 },
},
}}
/>
<BottomNavigationAction
label="프로필"
value="profile"
icon={<Person />}
sx={{
'& .MuiSvgIcon-root': {
fontSize: { xs: 24, sm: 28 },
},
}}
/>
</MuiBottomNavigation>
</Paper>
);
}

View File

@ -0,0 +1,114 @@
'use client';
import { useRouter } from 'next/navigation';
import { AppBar, Toolbar, IconButton, Typography, Box } from '@mui/material';
import { ArrowBack, Menu, AccountCircle } from '@mui/icons-material';
interface HeaderProps {
title?: string;
showBack?: boolean;
showMenu?: boolean;
showProfile?: boolean;
onBackClick?: () => void;
onMenuClick?: () => void;
onProfileClick?: () => void;
}
export default function Header({
title = '',
showBack = true,
showMenu = false,
showProfile = true,
onBackClick,
onMenuClick,
onProfileClick,
}: HeaderProps) {
const router = useRouter();
const handleBackClick = () => {
if (onBackClick) {
onBackClick();
} else {
router.back();
}
};
const handleProfileClick = () => {
if (onProfileClick) {
onProfileClick();
} else {
router.push('/profile');
}
};
return (
<AppBar
position="fixed"
elevation={0}
sx={{
bgcolor: 'background.paper',
borderBottom: '1px solid',
borderColor: 'divider',
zIndex: (theme) => theme.zIndex.drawer + 1,
}}
>
<Toolbar
sx={{
minHeight: { xs: 56, sm: 64 },
px: { xs: 2, sm: 3 },
}}
>
{/* Left Section */}
<Box sx={{ display: 'flex', alignItems: 'center', flex: 1 }}>
{showMenu && (
<IconButton
edge="start"
color="inherit"
aria-label="메뉴"
onClick={onMenuClick}
sx={{ mr: 1, color: 'text.primary' }}
>
<Menu />
</IconButton>
)}
{showBack && (
<IconButton
edge="start"
color="inherit"
aria-label="뒤로가기"
onClick={handleBackClick}
sx={{ mr: 1, color: 'text.primary' }}
>
<ArrowBack />
</IconButton>
)}
{title && (
<Typography
variant="h6"
sx={{
fontWeight: 700,
color: 'text.primary',
fontSize: { xs: '1.1rem', sm: '1.25rem' },
}}
>
{title}
</Typography>
)}
</Box>
{/* Right Section */}
{showProfile && (
<IconButton
edge="end"
color="inherit"
aria-label="프로필"
onClick={handleProfileClick}
sx={{ color: 'text.primary' }}
>
<AccountCircle />
</IconButton>
)}
</Toolbar>
</AppBar>
);
}