import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; import 'package:superport/screens/common/theme_shadcn.dart'; import 'package:superport/screens/common/components/shadcn_components.dart'; import 'package:superport/screens/overview/overview_screen_redesign.dart'; import 'package:superport/screens/equipment/equipment_list_redesign.dart'; import 'package:superport/screens/company/company_list_redesign.dart'; import 'package:superport/screens/user/user_list_redesign.dart'; import 'package:superport/screens/license/license_list_redesign.dart'; import 'package:superport/screens/warehouse_location/warehouse_location_list_redesign.dart'; import 'package:superport/services/auth_service.dart'; import 'package:superport/utils/constants.dart'; import 'package:superport/data/models/auth/auth_user.dart'; /// Microsoft Dynamics 365 스타일의 메인 레이아웃 /// 상단 헤더 + 좌측 사이드바 + 메인 콘텐츠 구조 class AppLayoutRedesign extends StatefulWidget { final String initialRoute; const AppLayoutRedesign({Key? key, this.initialRoute = Routes.home}) : super(key: key); @override State createState() => _AppLayoutRedesignState(); } class _AppLayoutRedesignState extends State with TickerProviderStateMixin { late String _currentRoute; bool _sidebarCollapsed = false; late AnimationController _sidebarAnimationController; AuthUser? _currentUser; late final AuthService _authService; late Animation _sidebarAnimation; @override void initState() { super.initState(); _currentRoute = widget.initialRoute; _setupAnimations(); _authService = GetIt.instance(); _loadCurrentUser(); } Future _loadCurrentUser() async { final user = await _authService.getCurrentUser(); if (mounted) { setState(() { _currentUser = user; }); } } void _setupAnimations() { _sidebarAnimationController = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); _sidebarAnimation = Tween(begin: 280.0, end: 72.0).animate( CurvedAnimation( parent: _sidebarAnimationController, curve: Curves.easeInOut, ), ); } @override void dispose() { _sidebarAnimationController.dispose(); super.dispose(); } /// 현재 경로에 따라 적절한 컨텐츠 섹션을 반환 Widget _getContentForRoute(String route) { switch (route) { case Routes.home: return const OverviewScreenRedesign(); case Routes.equipment: case Routes.equipmentInList: case Routes.equipmentOutList: case Routes.equipmentRentList: return EquipmentListRedesign(currentRoute: route); case Routes.company: return const CompanyListRedesign(); case Routes.user: return const UserListRedesign(); case Routes.license: return const LicenseListRedesign(); case Routes.warehouseLocation: return const WarehouseLocationListRedesign(); case '/test/api': // Navigator를 사용하여 별도 화면으로 이동 WidgetsBinding.instance.addPostFrameCallback((_) { Navigator.pushNamed(context, '/test/api'); }); return const Center(child: CircularProgressIndicator()); default: return const OverviewScreenRedesign(); } } /// 경로 변경 메서드 void _navigateTo(String route) { setState(() { _currentRoute = route; }); } /// 사이드바 토글 void _toggleSidebar() { setState(() { _sidebarCollapsed = !_sidebarCollapsed; }); if (_sidebarCollapsed) { _sidebarAnimationController.forward(); } else { _sidebarAnimationController.reverse(); } } /// 현재 페이지 제목 가져오기 String _getPageTitle() { switch (_currentRoute) { case Routes.home: return '대시보드'; case Routes.equipment: case Routes.equipmentInList: case Routes.equipmentOutList: case Routes.equipmentRentList: return '장비 관리'; case Routes.company: return '회사 관리'; case Routes.license: return '유지보수 관리'; case Routes.warehouseLocation: return '입고지 관리'; case '/test/api': return 'API 테스트'; default: return '대시보드'; } } /// 브레드크럼 경로 가져오기 List _getBreadcrumbs() { switch (_currentRoute) { case Routes.home: return ['홈', '대시보드']; case Routes.equipment: return ['홈', '장비 관리', '전체']; case Routes.equipmentInList: return ['홈', '장비 관리', '입고']; case Routes.equipmentOutList: return ['홈', '장비 관리', '출고']; case Routes.equipmentRentList: return ['홈', '장비 관리', '대여']; case Routes.company: return ['홈', '회사 관리']; case Routes.license: return ['홈', '유지보수 관리']; case Routes.warehouseLocation: return ['홈', '입고지 관리']; case '/test/api': return ['홈', '개발자 도구', 'API 테스트']; default: return ['홈', '대시보드']; } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: ShadcnTheme.muted, body: Column( children: [ // 상단 헤더 _buildTopHeader(), // 메인 콘텐츠 영역 Expanded( child: Row( children: [ // 좌측 사이드바 AnimatedBuilder( animation: _sidebarAnimation, builder: (context, child) { return SizedBox( width: _sidebarAnimation.value, child: _buildSidebar(), ); }, ), // 메인 콘텐츠 Expanded( child: Container( margin: const EdgeInsets.all(ShadcnTheme.spacing4), decoration: BoxDecoration( color: ShadcnTheme.background, borderRadius: BorderRadius.circular(ShadcnTheme.radiusLg), border: Border.all(color: Colors.black), boxShadow: ShadcnTheme.cardShadow, ), child: Column( children: [ // 페이지 헤더 _buildPageHeader(), // 메인 콘텐츠 Expanded( child: ClipRRect( borderRadius: const BorderRadius.only( bottomLeft: Radius.circular(ShadcnTheme.radiusLg), bottomRight: Radius.circular( ShadcnTheme.radiusLg, ), ), child: _getContentForRoute(_currentRoute), ), ), ], ), ), ), ], ), ), ], ), ); } /// 상단 헤더 빌드 Widget _buildTopHeader() { return Container( height: 64, decoration: BoxDecoration( color: ShadcnTheme.background, boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Padding( padding: const EdgeInsets.symmetric(horizontal: ShadcnTheme.spacing4), child: Row( children: [ // 사이드바 토글 버튼 IconButton( onPressed: _toggleSidebar, icon: Icon( _sidebarCollapsed ? Icons.menu : Icons.menu_open, color: ShadcnTheme.foreground, ), tooltip: _sidebarCollapsed ? '사이드바 펼치기' : '사이드바 접기', ), const SizedBox(width: ShadcnTheme.spacing4), // 앱 로고 및 제목 Container( padding: const EdgeInsets.all(ShadcnTheme.spacing2), decoration: BoxDecoration( gradient: LinearGradient( colors: [ShadcnTheme.gradient1, ShadcnTheme.gradient2], ), borderRadius: BorderRadius.circular(ShadcnTheme.radiusMd), ), child: Icon( Icons.directions_boat, size: 24, color: ShadcnTheme.primaryForeground, ), ), const SizedBox(width: ShadcnTheme.spacing3), Text('supERPort', style: ShadcnTheme.headingH4), const Spacer(), // 상단 액션 버튼들 _buildTopActions(), ], ), ), ); } /// 상단 액션 버튼들 Widget _buildTopActions() { return Row( children: [ // 알림 버튼 Container( decoration: BoxDecoration( color: ShadcnTheme.muted, borderRadius: BorderRadius.circular(ShadcnTheme.radiusMd), ), child: IconButton( onPressed: () { // 알림 기능 }, icon: Stack( children: [ Icon( Icons.notifications_outlined, color: ShadcnTheme.foreground, ), Positioned( right: 0, top: 0, child: Container( width: 8, height: 8, decoration: BoxDecoration( color: ShadcnTheme.destructive, shape: BoxShape.circle, ), ), ), ], ), tooltip: '알림', ), ), const SizedBox(width: ShadcnTheme.spacing2), // 설정 버튼 Container( decoration: BoxDecoration( color: ShadcnTheme.muted, borderRadius: BorderRadius.circular(ShadcnTheme.radiusMd), ), child: IconButton( onPressed: () { // 설정 기능 }, icon: Icon(Icons.settings_outlined, color: ShadcnTheme.foreground), tooltip: '설정', ), ), const SizedBox(width: ShadcnTheme.spacing4), // 프로필 아바타 GestureDetector( onTap: () { _showProfileMenu(context); }, child: ShadcnAvatar( initials: _currentUser != null ? _currentUser!.name.substring(0, 1).toUpperCase() : 'U', size: 36, ), ), ], ); } /// 사이드바 빌드 Widget _buildSidebar() { return Container( decoration: BoxDecoration( color: ShadcnTheme.background, ), child: SidebarMenuRedesign( currentRoute: _currentRoute, onRouteChanged: _navigateTo, collapsed: _sidebarCollapsed, ), ); } /// 페이지 헤더 빌드 Widget _buildPageHeader() { final breadcrumbs = _getBreadcrumbs(); return Container( padding: const EdgeInsets.all(ShadcnTheme.spacing6), decoration: BoxDecoration( border: Border(bottom: BorderSide(color: Colors.black)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 브레드크럼 Row( children: [ for (int i = 0; i < breadcrumbs.length; i++) ...[ if (i > 0) ...[ const SizedBox(width: ShadcnTheme.spacing2), Icon( Icons.chevron_right, size: 16, color: ShadcnTheme.mutedForeground, ), const SizedBox(width: ShadcnTheme.spacing2), ], Text( breadcrumbs[i], style: i == breadcrumbs.length - 1 ? ShadcnTheme.bodyMedium : ShadcnTheme.bodyMuted, ), ], ], ), ], ), ); } /// 프로필 메뉴 표시 void _showProfileMenu(BuildContext context) { showModalBottomSheet( context: context, backgroundColor: ShadcnTheme.background, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical( top: Radius.circular(ShadcnTheme.radiusXl), ), ), builder: (context) => Container( padding: const EdgeInsets.all(ShadcnTheme.spacing6), child: Column( mainAxisSize: MainAxisSize.min, children: [ // 프로필 정보 Row( children: [ ShadcnAvatar( initials: _currentUser != null ? _currentUser!.name.substring(0, 1).toUpperCase() : 'U', size: 48, ), const SizedBox(width: ShadcnTheme.spacing4), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( _currentUser?.name ?? '사용자', style: ShadcnTheme.headingH4, ), Text( _currentUser?.email ?? '', style: ShadcnTheme.bodyMuted, ), ], ), ], ), const SizedBox(height: ShadcnTheme.spacing6), const ShadcnSeparator(), const SizedBox(height: ShadcnTheme.spacing4), // 로그아웃 버튼 ShadcnButton( text: '로그아웃', onPressed: () async { // 로딩 다이얼로그 표시 showDialog( context: context, barrierDismissible: false, builder: (context) => Center( child: CircularProgressIndicator(), ), ); try { // AuthService를 사용하여 로그아웃 final authService = GetIt.instance(); await authService.logout(); // 로딩 다이얼로그와 현재 모달 닫기 if (context.mounted) { Navigator.of(context).pop(); // 로딩 다이얼로그 Navigator.of(context).pop(); // 프로필 메뉴 // 로그인 화면으로 이동 Navigator.of(context).pushNamedAndRemoveUntil( '/login', (route) => false, ); } } catch (e) { // 에러 처리 if (context.mounted) { Navigator.of(context).pop(); // 로딩 다이얼로그 닫기 ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('로그아웃 중 오류가 발생했습니다.'), backgroundColor: Colors.red, ), ); } } }, variant: ShadcnButtonVariant.destructive, fullWidth: true, icon: Icon(Icons.logout), ), ], ), ), ); } } /// 재설계된 사이드바 메뉴 (접기/펼치기 지원) class SidebarMenuRedesign extends StatelessWidget { final String currentRoute; final Function(String) onRouteChanged; final bool collapsed; const SidebarMenuRedesign({ Key? key, required this.currentRoute, required this.onRouteChanged, required this.collapsed, }) : super(key: key); @override Widget build(BuildContext context) { return Column( children: [ Expanded( child: SingleChildScrollView( padding: const EdgeInsets.all(ShadcnTheme.spacing4), child: Column( children: [ _buildMenuItem( icon: Icons.dashboard, title: '대시보드', route: Routes.home, isActive: currentRoute == Routes.home, ), const SizedBox(height: ShadcnTheme.spacing2), _buildMenuItem( icon: Icons.inventory, title: '장비 관리', route: Routes.equipment, isActive: [ Routes.equipment, Routes.equipmentInList, Routes.equipmentOutList, Routes.equipmentRentList, ].contains(currentRoute), ), const SizedBox(height: ShadcnTheme.spacing2), _buildMenuItem( icon: Icons.location_on, title: '입고지 관리', route: Routes.warehouseLocation, isActive: currentRoute == Routes.warehouseLocation, ), const SizedBox(height: ShadcnTheme.spacing2), _buildMenuItem( icon: Icons.business, title: '회사 관리', route: Routes.company, isActive: currentRoute == Routes.company, ), const SizedBox(height: ShadcnTheme.spacing2), _buildMenuItem( icon: Icons.vpn_key, title: '유지보수 관리', route: Routes.license, isActive: currentRoute == Routes.license, ), const SizedBox(height: ShadcnTheme.spacing4), const Divider(), const SizedBox(height: ShadcnTheme.spacing4), _buildMenuItem( icon: Icons.bug_report, title: 'API 테스트', route: '/test/api', isActive: currentRoute == '/test/api', ), ], ), ), ), ], ); } Widget _buildMenuItem({ required IconData icon, required String title, required String route, required bool isActive, }) { return GestureDetector( onTap: () => onRouteChanged(route), child: Container( width: double.infinity, padding: EdgeInsets.symmetric( horizontal: collapsed ? ShadcnTheme.spacing2 : ShadcnTheme.spacing4, vertical: ShadcnTheme.spacing3, ), decoration: BoxDecoration( color: isActive ? ShadcnTheme.primary : Colors.transparent, borderRadius: BorderRadius.circular(ShadcnTheme.radiusMd), ), child: Row( children: [ Icon( icon, size: 20, color: isActive ? ShadcnTheme.primaryForeground : ShadcnTheme.foreground, ), if (!collapsed) ...[ const SizedBox(width: ShadcnTheme.spacing3), Expanded( child: Text( title, style: ShadcnTheme.bodyMedium.copyWith( color: isActive ? ShadcnTheme.primaryForeground : ShadcnTheme.foreground, fontWeight: isActive ? FontWeight.w600 : FontWeight.w400, ), ), ), ], ], ), ), ); } }