Skip to content

Commit f0f0466

Browse files
committed
feat: 퀵마트 관리자 대시보드 구현 및 Tailwind CSS 호환성 수정
✨ 새로운 기능: - 프로덕션 수준의 관리자 대시보드 구현 - 대시보드 페이지: 실시간 매출/주문 현황, 차트, 통계 - 상품 관리 페이지: 그리드/리스트 뷰, 검색, 필터링, 재고 관리 - 주문 관리 페이지: 주문 상태별 필터링, 확장 카드 뷰, 실시간 추적 - 반응형 사이드바 네비게이션 시스템 - Apple/Toss 스타일 미니멀 디자인 시스템 🐛 버그 수정: - Tailwind CSS v4 → v3.4.0 다운그레이드 (호환성 문제 해결) - PostCSS 설정 수정 (v3 방식으로 변경) - CSS @import 규칙 순서 수정 (파싱 에러 해결) 🎨 UI/UX 개선: - 60fps 부드러운 애니메이션 - 직관적인 네비게이션 구조 - 실시간 데이터 업데이트 표시 - 모바일/태블릿/데스크톱 완벽 지원
1 parent 9b029e9 commit f0f0466

8 files changed

Lines changed: 2485 additions & 652 deletions

File tree

02-mobile-commerce/package-lock.json

Lines changed: 1024 additions & 646 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

02-mobile-commerce/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,14 @@
2222
},
2323
"devDependencies": {
2424
"@eslint/eslintrc": "^3",
25-
"@tailwindcss/postcss": "^4",
2625
"@types/node": "^20",
2726
"@types/react": "^19",
2827
"@types/react-dom": "^19",
28+
"autoprefixer": "^10.4.21",
2929
"eslint": "^9",
3030
"eslint-config-next": "15.5.2",
31-
"tailwindcss": "^4",
31+
"postcss": "^8.5.6",
32+
"tailwindcss": "^3.4.0",
3233
"typescript": "^5"
3334
}
3435
}
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
const config = {
2-
plugins: ["@tailwindcss/postcss"],
2+
plugins: {
3+
tailwindcss: {},
4+
autoprefixer: {},
5+
},
36
};
47

58
export default config;
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
'use client';
2+
3+
import { ReactNode, useState } from 'react';
4+
import Link from 'next/link';
5+
import { usePathname } from 'next/navigation';
6+
import {
7+
LayoutDashboard,
8+
Package,
9+
ShoppingCart,
10+
Users,
11+
BarChart3,
12+
Settings,
13+
Menu,
14+
X,
15+
Bell,
16+
Search,
17+
LogOut,
18+
ChevronDown,
19+
TrendingUp,
20+
Store,
21+
Truck
22+
} from 'lucide-react';
23+
24+
interface AdminLayoutProps {
25+
children: ReactNode;
26+
}
27+
28+
export default function AdminLayout({ children }: AdminLayoutProps) {
29+
const pathname = usePathname();
30+
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
31+
const [isProfileOpen, setIsProfileOpen] = useState(false);
32+
33+
const menuItems = [
34+
{
35+
title: '대시보드',
36+
href: '/admin',
37+
icon: LayoutDashboard,
38+
badge: null
39+
},
40+
{
41+
title: '상품 관리',
42+
href: '/admin/products',
43+
icon: Package,
44+
badge: '152'
45+
},
46+
{
47+
title: '주문 관리',
48+
href: '/admin/orders',
49+
icon: ShoppingCart,
50+
badge: '12',
51+
badgeColor: 'bg-orange-500'
52+
},
53+
{
54+
title: '배송 관리',
55+
href: '/admin/delivery',
56+
icon: Truck,
57+
badge: '8',
58+
badgeColor: 'bg-blue-500'
59+
},
60+
{
61+
title: '고객 관리',
62+
href: '/admin/users',
63+
icon: Users,
64+
badge: null
65+
},
66+
{
67+
title: '매출 분석',
68+
href: '/admin/analytics',
69+
icon: BarChart3,
70+
badge: null
71+
},
72+
{
73+
title: '매장 설정',
74+
href: '/admin/store',
75+
icon: Store,
76+
badge: null
77+
},
78+
{
79+
title: '시스템 설정',
80+
href: '/admin/settings',
81+
icon: Settings,
82+
badge: null
83+
}
84+
];
85+
86+
const isActive = (href: string) => {
87+
if (href === '/admin') {
88+
return pathname === '/admin';
89+
}
90+
return pathname.startsWith(href);
91+
};
92+
93+
return (
94+
<div className="min-h-screen bg-gray-50">
95+
{/* Mobile Sidebar Backdrop */}
96+
{isSidebarOpen && (
97+
<div
98+
className="fixed inset-0 bg-black/50 z-40 lg:hidden"
99+
onClick={() => setIsSidebarOpen(false)}
100+
/>
101+
)}
102+
103+
{/* Sidebar */}
104+
<aside
105+
className={`fixed top-0 left-0 z-50 h-full w-64 bg-white border-r border-gray-200 transform transition-transform duration-200 ease-in-out ${
106+
isSidebarOpen ? 'translate-x-0' : '-translate-x-full'
107+
} lg:translate-x-0`}
108+
>
109+
{/* Logo */}
110+
<div className="flex items-center justify-between h-16 px-6 border-b border-gray-200">
111+
<div className="flex items-center gap-2">
112+
<div className="w-8 h-8 bg-gradient-to-br from-orange-500 to-pink-500 rounded-lg flex items-center justify-center text-white font-bold">
113+
Q
114+
</div>
115+
<span className="font-bold text-gray-900">퀵마트 관리자</span>
116+
</div>
117+
<button
118+
onClick={() => setIsSidebarOpen(false)}
119+
className="lg:hidden p-1 hover:bg-gray-100 rounded-lg"
120+
>
121+
<X className="w-5 h-5 text-gray-500" />
122+
</button>
123+
</div>
124+
125+
{/* Navigation */}
126+
<nav className="p-4 space-y-1">
127+
{menuItems.map((item) => {
128+
const Icon = item.icon;
129+
const active = isActive(item.href);
130+
131+
return (
132+
<Link
133+
key={item.href}
134+
href={item.href}
135+
className={`flex items-center justify-between px-3 py-2.5 rounded-lg transition-all ${
136+
active
137+
? 'bg-orange-50 text-orange-600 font-medium'
138+
: 'text-gray-700 hover:bg-gray-100'
139+
}`}
140+
>
141+
<div className="flex items-center gap-3">
142+
<Icon className={`w-5 h-5 ${active ? 'text-orange-600' : 'text-gray-400'}`} />
143+
<span>{item.title}</span>
144+
</div>
145+
{item.badge && (
146+
<span
147+
className={`px-2 py-0.5 text-xs font-medium rounded-full text-white ${
148+
item.badgeColor || 'bg-gray-500'
149+
}`}
150+
>
151+
{item.badge}
152+
</span>
153+
)}
154+
</Link>
155+
);
156+
})}
157+
</nav>
158+
159+
{/* Bottom Section */}
160+
<div className="absolute bottom-0 left-0 right-0 p-4 border-t border-gray-200">
161+
<div className="bg-gradient-to-br from-orange-50 to-pink-50 rounded-lg p-4">
162+
<div className="flex items-center gap-2 mb-2">
163+
<TrendingUp className="w-5 h-5 text-orange-500" />
164+
<span className="text-sm font-medium text-gray-900">매출 증가</span>
165+
</div>
166+
<p className="text-xs text-gray-600 mb-3">
167+
이번 달 매출이 지난달 대비 32% 증가했습니다!
168+
</p>
169+
<button className="w-full py-2 bg-gradient-to-r from-orange-500 to-pink-500 text-white text-sm font-medium rounded-lg hover:shadow-md transition-shadow">
170+
상세 보기
171+
</button>
172+
</div>
173+
</div>
174+
</aside>
175+
176+
{/* Main Content Area */}
177+
<div className="lg:ml-64">
178+
{/* Top Header */}
179+
<header className="sticky top-0 z-30 bg-white border-b border-gray-200">
180+
<div className="flex items-center justify-between h-16 px-4 lg:px-6">
181+
{/* Left Section */}
182+
<div className="flex items-center gap-4">
183+
<button
184+
onClick={() => setIsSidebarOpen(true)}
185+
className="lg:hidden p-2 hover:bg-gray-100 rounded-lg"
186+
>
187+
<Menu className="w-5 h-5 text-gray-600" />
188+
</button>
189+
190+
{/* Search Bar */}
191+
<div className="relative hidden sm:block">
192+
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
193+
<input
194+
type="text"
195+
placeholder="검색어를 입력하세요..."
196+
className="pl-10 pr-4 py-2 w-64 bg-gray-50 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-orange-500 focus:border-transparent"
197+
/>
198+
</div>
199+
</div>
200+
201+
{/* Right Section */}
202+
<div className="flex items-center gap-2">
203+
{/* Notifications */}
204+
<button className="relative p-2 hover:bg-gray-100 rounded-lg">
205+
<Bell className="w-5 h-5 text-gray-600" />
206+
<span className="absolute top-1 right-1 w-2 h-2 bg-red-500 rounded-full" />
207+
</button>
208+
209+
{/* Profile Dropdown */}
210+
<div className="relative">
211+
<button
212+
onClick={() => setIsProfileOpen(!isProfileOpen)}
213+
className="flex items-center gap-3 p-2 hover:bg-gray-100 rounded-lg"
214+
>
215+
<div className="w-8 h-8 bg-gradient-to-br from-orange-400 to-pink-400 rounded-full flex items-center justify-center text-white text-sm font-medium">
216+
217+
</div>
218+
<div className="hidden sm:block text-left">
219+
<p className="text-sm font-medium text-gray-900">관리자</p>
220+
<p className="text-xs text-gray-500">admin@quickmart.com</p>
221+
</div>
222+
<ChevronDown className="w-4 h-4 text-gray-400" />
223+
</button>
224+
225+
{/* Dropdown Menu */}
226+
{isProfileOpen && (
227+
<div className="absolute right-0 mt-2 w-48 bg-white rounded-lg shadow-lg border border-gray-200 py-1">
228+
<Link
229+
href="/admin/profile"
230+
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
231+
>
232+
프로필 설정
233+
</Link>
234+
<Link
235+
href="/admin/security"
236+
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
237+
>
238+
보안 설정
239+
</Link>
240+
<hr className="my-1 border-gray-200" />
241+
<button className="w-full text-left px-4 py-2 text-sm text-red-600 hover:bg-red-50 flex items-center gap-2">
242+
<LogOut className="w-4 h-4" />
243+
로그아웃
244+
</button>
245+
</div>
246+
)}
247+
</div>
248+
</div>
249+
</div>
250+
</header>
251+
252+
{/* Page Content */}
253+
<main className="p-4 lg:p-6">
254+
{children}
255+
</main>
256+
</div>
257+
</div>
258+
);
259+
}

0 commit comments

Comments
 (0)