mirror of
https://github.com/ktds-dg0501/kt-event-marketing-fe.git
synced 2025-12-06 06:16:24 +00:00
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:
parent
7ebaa38807
commit
ea94dc97a1
@ -15,6 +15,7 @@ import {
|
|||||||
Payments,
|
Payments,
|
||||||
People,
|
People,
|
||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
|
import Header from '@/components/layout/Header';
|
||||||
|
|
||||||
// Mock 데이터
|
// Mock 데이터
|
||||||
const mockAnalyticsData = {
|
const mockAnalyticsData = {
|
||||||
@ -95,8 +96,10 @@ export default function AnalyticsPage() {
|
|||||||
mockAnalyticsData;
|
mockAnalyticsData;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ minHeight: '100vh', bgcolor: 'background.default', pb: 10 }}>
|
<>
|
||||||
<Container maxWidth="lg" sx={{ pt: 4, pb: 4, px: { xs: 3, sm: 3, md: 4 } }}>
|
<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 */}
|
{/* Title with Real-time Indicator */}
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -506,6 +509,7 @@ export default function AnalyticsPage() {
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Container>
|
</Container>
|
||||||
</Box>
|
</Box>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import {
|
|||||||
Grid,
|
Grid,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { Search, FilterList } from '@mui/icons-material';
|
import { Search, FilterList } from '@mui/icons-material';
|
||||||
|
import Header from '@/components/layout/Header';
|
||||||
|
|
||||||
// Mock 데이터
|
// Mock 데이터
|
||||||
const mockEvents = [
|
const mockEvents = [
|
||||||
@ -140,8 +141,10 @@ export default function EventsPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ minHeight: '100vh', bgcolor: 'background.default', pb: 10 }}>
|
<>
|
||||||
<Container maxWidth="lg" sx={{ pt: 4, pb: 4, px: { xs: 3, sm: 3, md: 4 } }}>
|
<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 */}
|
{/* Search Section */}
|
||||||
<Box sx={{ mb: 3 }}>
|
<Box sx={{ mb: 3 }}>
|
||||||
<TextField
|
<TextField
|
||||||
@ -322,6 +325,7 @@ export default function EventsPage() {
|
|||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
</Box>
|
</Box>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
11
src/app/(main)/layout.tsx
Normal file
11
src/app/(main)/layout.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -21,6 +21,7 @@ import {
|
|||||||
Edit,
|
Edit,
|
||||||
CheckCircle,
|
CheckCircle,
|
||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
|
import Header from '@/components/layout/Header';
|
||||||
|
|
||||||
// Mock 사용자 데이터 (API 연동 전까지 임시 사용)
|
// Mock 사용자 데이터 (API 연동 전까지 임시 사용)
|
||||||
const mockUser = {
|
const mockUser = {
|
||||||
@ -79,14 +80,16 @@ export default function HomePage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<>
|
||||||
sx={{
|
<Header title="대시보드" showBack={false} showMenu={false} showProfile={true} />
|
||||||
pb: 10,
|
<Box
|
||||||
bgcolor: 'background.default',
|
sx={{
|
||||||
minHeight: '100vh',
|
pb: 10,
|
||||||
}}
|
bgcolor: 'background.default',
|
||||||
>
|
minHeight: '100vh',
|
||||||
<Container maxWidth="lg" sx={{ pt: 4, pb: 4, px: { xs: 3, sm: 3, md: 4 } }}>
|
}}
|
||||||
|
>
|
||||||
|
<Container maxWidth="lg" sx={{ pt: 4, pb: 4, px: { xs: 3, sm: 3, md: 4 } }}>
|
||||||
{/* Welcome Section */}
|
{/* Welcome Section */}
|
||||||
<Box sx={{ mb: 5 }}>
|
<Box sx={{ mb: 5 }}>
|
||||||
<Typography
|
<Typography
|
||||||
@ -436,20 +439,21 @@ export default function HomePage() {
|
|||||||
|
|
||||||
{/* Floating Action Button */}
|
{/* Floating Action Button */}
|
||||||
<Fab
|
<Fab
|
||||||
sx={{
|
sx={{
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
bottom: 80,
|
bottom: 80,
|
||||||
right: 16,
|
right: 16,
|
||||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||||
transition: 'all 0.2s ease',
|
transition: 'all 0.2s ease',
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
background: 'linear-gradient(135deg, #5568d3 0%, #65408b 100%)',
|
background: 'linear-gradient(135deg, #5568d3 0%, #65408b 100%)',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
onClick={handleCreateEvent}
|
onClick={handleCreateEvent}
|
||||||
>
|
>
|
||||||
<Add sx={{ color: 'white' }} />
|
<Add sx={{ color: 'white' }} />
|
||||||
</Fab>
|
</Fab>
|
||||||
</Box>
|
</Box>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
115
src/components/layout/BottomNavigation.tsx
Normal file
115
src/components/layout/BottomNavigation.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
114
src/components/layout/Header.tsx
Normal file
114
src/components/layout/Header.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user