프로젝트 최초 커밋
This commit is contained in:
169
lib/screens/sidebar/sidebar_screen.dart
Normal file
169
lib/screens/sidebar/sidebar_screen.dart
Normal file
@@ -0,0 +1,169 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:superport/utils/constants.dart';
|
||||
import 'package:superport/screens/sidebar/widgets/sidebar_menu_header.dart';
|
||||
import 'package:superport/screens/sidebar/widgets/sidebar_menu_footer.dart';
|
||||
import 'package:superport/screens/sidebar/widgets/sidebar_menu_item.dart';
|
||||
import 'package:superport/screens/sidebar/widgets/sidebar_menu_submenu.dart';
|
||||
import 'package:superport/screens/sidebar/widgets/sidebar_menu_types.dart';
|
||||
import 'package:superport/screens/common/theme_tailwind.dart';
|
||||
import 'package:superport/screens/login/widgets/login_view.dart'; // AnimatedBoatIcon import
|
||||
import 'package:wave/wave.dart';
|
||||
import 'package:wave/config.dart';
|
||||
|
||||
// 사이드바 메뉴 메인 위젯 (조립만 담당)
|
||||
class SidebarMenu extends StatefulWidget {
|
||||
final String currentRoute;
|
||||
final Function(String) onRouteChanged;
|
||||
|
||||
const SidebarMenu({
|
||||
super.key,
|
||||
required this.currentRoute,
|
||||
required this.onRouteChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
State<SidebarMenu> createState() => _SidebarMenuState();
|
||||
}
|
||||
|
||||
class _SidebarMenuState extends State<SidebarMenu> {
|
||||
// 장비 관리 메뉴 확장 상태
|
||||
bool _isEquipmentMenuExpanded = false;
|
||||
// hover 상태 관리
|
||||
String? _hoveredRoute;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_updateExpandedState();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(SidebarMenu oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.currentRoute != widget.currentRoute) {
|
||||
_updateExpandedState();
|
||||
}
|
||||
}
|
||||
|
||||
// 현재 경로에 따라 장비 관리 메뉴 확장 상태 업데이트
|
||||
void _updateExpandedState() {
|
||||
final bool isEquipmentRoute =
|
||||
widget.currentRoute == Routes.equipment ||
|
||||
widget.currentRoute == Routes.equipmentInList ||
|
||||
widget.currentRoute == Routes.equipmentOutList ||
|
||||
widget.currentRoute == Routes.equipmentRentList;
|
||||
setState(() {
|
||||
_isEquipmentMenuExpanded = isEquipmentRoute;
|
||||
});
|
||||
}
|
||||
|
||||
// 장비 관리 메뉴 확장/축소 토글
|
||||
void _toggleEquipmentMenu() {
|
||||
setState(() {
|
||||
_isEquipmentMenuExpanded = !_isEquipmentMenuExpanded;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// SRP 분할: 각 역할별 위젯 조립
|
||||
return Container(
|
||||
width: 260,
|
||||
color: const Color(0xFFF4F6F8), // 연회색 배경
|
||||
child: Column(
|
||||
children: [
|
||||
const SidebarMenuHeader(),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SidebarMenuItem(
|
||||
icon: Icons.dashboard,
|
||||
title: '대시보드',
|
||||
route: Routes.home,
|
||||
isActive: widget.currentRoute == Routes.home,
|
||||
isHovered: _hoveredRoute == Routes.home,
|
||||
onTap: () => widget.onRouteChanged(Routes.home),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
SidebarMenuWithSubmenu(
|
||||
icon: Icons.inventory,
|
||||
title: '장비 관리',
|
||||
route: Routes.equipment,
|
||||
subItems: const [
|
||||
SidebarSubMenuItem(
|
||||
title: '입고',
|
||||
route: Routes.equipmentInList,
|
||||
),
|
||||
SidebarSubMenuItem(
|
||||
title: '출고',
|
||||
route: Routes.equipmentOutList,
|
||||
),
|
||||
SidebarSubMenuItem(
|
||||
title: '대여',
|
||||
route: Routes.equipmentRentList,
|
||||
),
|
||||
],
|
||||
isExpanded: _isEquipmentMenuExpanded,
|
||||
isMenuActive: widget.currentRoute == Routes.equipment,
|
||||
isSubMenuActive: [
|
||||
Routes.equipmentInList,
|
||||
Routes.equipmentOutList,
|
||||
Routes.equipmentRentList,
|
||||
].contains(widget.currentRoute),
|
||||
isHovered: _hoveredRoute == Routes.equipment,
|
||||
onToggleExpanded: _toggleEquipmentMenu,
|
||||
currentRoute: widget.currentRoute,
|
||||
onRouteChanged: widget.onRouteChanged,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
SidebarMenuItem(
|
||||
icon: Icons.location_on,
|
||||
title: '입고지 관리',
|
||||
route: Routes.warehouseLocation,
|
||||
isActive: widget.currentRoute == Routes.warehouseLocation,
|
||||
isHovered: _hoveredRoute == Routes.warehouseLocation,
|
||||
onTap:
|
||||
() => widget.onRouteChanged(Routes.warehouseLocation),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
SidebarMenuItem(
|
||||
icon: Icons.business,
|
||||
title: '회사 관리',
|
||||
route: Routes.company,
|
||||
isActive: widget.currentRoute == Routes.company,
|
||||
isHovered: _hoveredRoute == Routes.company,
|
||||
onTap: () => widget.onRouteChanged(Routes.company),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
SidebarMenuItem(
|
||||
icon: Icons.vpn_key,
|
||||
title: '유지보수 관리',
|
||||
route: Routes.license,
|
||||
isActive: widget.currentRoute == Routes.license,
|
||||
isHovered: _hoveredRoute == Routes.license,
|
||||
onTap: () => widget.onRouteChanged(Routes.license),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
SidebarMenuItem(
|
||||
icon: Icons.category,
|
||||
title: '물품 관리',
|
||||
route: Routes.goods,
|
||||
isActive: widget.currentRoute == Routes.goods,
|
||||
isHovered: _hoveredRoute == Routes.goods,
|
||||
onTap: () => widget.onRouteChanged(Routes.goods),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SidebarMenuFooter(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
18
lib/screens/sidebar/widgets/sidebar_menu_footer.dart
Normal file
18
lib/screens/sidebar/widgets/sidebar_menu_footer.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// 사이드바 푸터 위젯
|
||||
class SidebarMenuFooter extends StatelessWidget {
|
||||
const SidebarMenuFooter({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 48,
|
||||
alignment: Alignment.center,
|
||||
child: const Text(
|
||||
'© 2025 CClabs. All rights reserved.',
|
||||
style: TextStyle(fontSize: 11, color: Colors.black), // 블랙으로 변경
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
69
lib/screens/sidebar/widgets/sidebar_menu_header.dart
Normal file
69
lib/screens/sidebar/widgets/sidebar_menu_header.dart
Normal file
@@ -0,0 +1,69 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:wave/wave.dart';
|
||||
import 'package:wave/config.dart';
|
||||
import 'package:superport/screens/login/widgets/login_view.dart'; // AnimatedBoatIcon import
|
||||
|
||||
// 사이드바 헤더 위젯
|
||||
class SidebarMenuHeader extends StatelessWidget {
|
||||
const SidebarMenuHeader({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 88,
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.only(left: 0, right: 0), // 아이콘을 더 좌측으로
|
||||
child: Stack(
|
||||
alignment: Alignment.centerLeft,
|
||||
children: [
|
||||
// Wave 배경
|
||||
Positioned.fill(
|
||||
child: Opacity(
|
||||
opacity: 0.50, // subtle하게
|
||||
child: WaveWidget(
|
||||
config: CustomConfig(
|
||||
gradients: [
|
||||
[Color(0xFFB6E0FE), Color(0xFF3182CE)],
|
||||
[
|
||||
Color.fromARGB(255, 31, 83, 132),
|
||||
Color.fromARGB(255, 9, 49, 92),
|
||||
],
|
||||
],
|
||||
durations: [4800, 6000],
|
||||
heightPercentages: [0.48, 0.38],
|
||||
blur: const MaskFilter.blur(BlurStyle.solid, 6),
|
||||
gradientBegin: Alignment.topLeft,
|
||||
gradientEnd: Alignment.bottomRight,
|
||||
),
|
||||
waveAmplitude: 8,
|
||||
size: Size.infinite,
|
||||
),
|
||||
),
|
||||
),
|
||||
// 아이콘+텍스트
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(width: 24), // 아이콘을 더 좌측으로
|
||||
SizedBox(
|
||||
width: 36,
|
||||
height: 36,
|
||||
child: AnimatedBoatIcon(color: Colors.white, size: 60),
|
||||
),
|
||||
const SizedBox(width: 24),
|
||||
const Text(
|
||||
'supERPort',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
letterSpacing: -2.5,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
75
lib/screens/sidebar/widgets/sidebar_menu_item.dart
Normal file
75
lib/screens/sidebar/widgets/sidebar_menu_item.dart
Normal file
@@ -0,0 +1,75 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:superport/screens/common/theme_tailwind.dart';
|
||||
|
||||
// 단일 메뉴 아이템 위젯
|
||||
class SidebarMenuItem extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final String route;
|
||||
final bool isActive;
|
||||
final bool isHovered;
|
||||
final bool isSubItem;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const SidebarMenuItem({
|
||||
super.key,
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.route,
|
||||
required this.isActive,
|
||||
required this.isHovered,
|
||||
this.isSubItem = false,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
height: 44,
|
||||
alignment: Alignment.centerLeft,
|
||||
margin: const EdgeInsets.symmetric(
|
||||
vertical: 2,
|
||||
horizontal: 6,
|
||||
), // 외부 여백
|
||||
padding: EdgeInsets.only(left: isSubItem ? 48 : 24, right: 24),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
isActive
|
||||
? Colors.white
|
||||
: (isHovered
|
||||
? const Color(0xFFE9EDF2)
|
||||
: Colors.transparent),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
size: 18,
|
||||
color:
|
||||
isActive ? AppThemeTailwind.primary : AppThemeTailwind.dark,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
color:
|
||||
isActive
|
||||
? AppThemeTailwind.primary
|
||||
: AppThemeTailwind.dark,
|
||||
fontWeight: isActive ? FontWeight.bold : FontWeight.normal,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
124
lib/screens/sidebar/widgets/sidebar_menu_submenu.dart
Normal file
124
lib/screens/sidebar/widgets/sidebar_menu_submenu.dart
Normal file
@@ -0,0 +1,124 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:superport/screens/sidebar/widgets/sidebar_menu_item.dart';
|
||||
import 'package:superport/screens/sidebar/widgets/sidebar_menu_types.dart';
|
||||
import 'package:superport/screens/common/theme_tailwind.dart';
|
||||
|
||||
// 서브메뉴(확장/축소, 하위 아이템) 위젯
|
||||
class SidebarMenuWithSubmenu extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final String route;
|
||||
final List<SidebarSubMenuItem> subItems;
|
||||
final bool isExpanded;
|
||||
final bool isMenuActive;
|
||||
final bool isSubMenuActive;
|
||||
final bool isHovered;
|
||||
final VoidCallback onToggleExpanded;
|
||||
final String currentRoute;
|
||||
final void Function(String) onRouteChanged;
|
||||
|
||||
const SidebarMenuWithSubmenu({
|
||||
super.key,
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.route,
|
||||
required this.subItems,
|
||||
required this.isExpanded,
|
||||
required this.isMenuActive,
|
||||
required this.isSubMenuActive,
|
||||
required this.isHovered,
|
||||
required this.onToggleExpanded,
|
||||
required this.currentRoute,
|
||||
required this.onRouteChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bool isHighlighted = isMenuActive || isSubMenuActive;
|
||||
return Column(
|
||||
children: [
|
||||
MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
onTap: () {
|
||||
onToggleExpanded();
|
||||
onRouteChanged(route);
|
||||
},
|
||||
child: Container(
|
||||
height: 44,
|
||||
margin: const EdgeInsets.symmetric(vertical: 2, horizontal: 6),
|
||||
padding: const EdgeInsets.only(left: 24, right: 24),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
isMenuActive
|
||||
? Colors.white
|
||||
: (isHovered
|
||||
? const Color(0xFFE9EDF2)
|
||||
: Colors.transparent),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
size: 18,
|
||||
color:
|
||||
isHighlighted
|
||||
? AppThemeTailwind.primary
|
||||
: AppThemeTailwind.dark,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
color:
|
||||
isHighlighted
|
||||
? AppThemeTailwind.primary
|
||||
: AppThemeTailwind.dark,
|
||||
fontWeight:
|
||||
isHighlighted ? FontWeight.bold : FontWeight.normal,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Icon(
|
||||
isExpanded
|
||||
? Icons.keyboard_arrow_up
|
||||
: Icons.keyboard_arrow_down,
|
||||
size: 20,
|
||||
color: AppThemeTailwind.muted,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeInOut,
|
||||
child: ClipRect(
|
||||
child: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
heightFactor: isExpanded ? 1 : 0,
|
||||
child: Column(
|
||||
children:
|
||||
subItems.map((item) {
|
||||
return SidebarMenuItem(
|
||||
icon: Icons.circle,
|
||||
title: item.title,
|
||||
route: item.route,
|
||||
isActive: currentRoute == item.route,
|
||||
isHovered: false, // hover는 상위에서 관리
|
||||
isSubItem: true,
|
||||
onTap: () => onRouteChanged(item.route),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
11
lib/screens/sidebar/widgets/sidebar_menu_types.dart
Normal file
11
lib/screens/sidebar/widgets/sidebar_menu_types.dart
Normal file
@@ -0,0 +1,11 @@
|
||||
// 서브메뉴 아이템 타입 정의 파일
|
||||
// 이 파일은 사이드바 메뉴에서 사용하는 서브메뉴 아이템 타입만 정의합니다.
|
||||
|
||||
class SidebarSubMenuItem {
|
||||
// 서브메뉴의 제목
|
||||
final String title;
|
||||
// 서브메뉴의 라우트
|
||||
final String route;
|
||||
|
||||
const SidebarSubMenuItem({required this.title, required this.route});
|
||||
}
|
||||
Reference in New Issue
Block a user