// 🏠 톡합 λŒ€μ‹œλ³΄λ“œ (UI μ „μš©). 학생/ꡐ사/κ΄€λ¦¬μžκ°€ μ „λΆ€ 이 ν™”λ©΄ ν•˜λ‚˜λ₯Ό κ³΅μœ ν•˜κ³ , // κΆŒν•œ(role)에 따라 λ°°λ„ˆ 색상/문ꡬ와 λ³΄μ΄λŠ” νƒ€μΌλ§Œ 달라진닀. // λ ˆμ΄μ•„μ›ƒ(κ·Έλ¦¬λ“œ, μΉ΄λ“œ λ””μžμΈ)을 ν•œ κ³³μ—μ„œ 고치면 λͺ¨λ“  역할에 λ™μ‹œμ— μ μš©λœλ‹€. // 계정 κ°•μ œ μ‚­μ œ μ„œλ²„ 톡신은 lib/function/student_dashboard_controller.dartκ°€ λ‹΄λ‹Ήν•œλ‹€. import 'package:flutter/material.dart'; import '../function/student_dashboard_controller.dart'; import '../theme/app_palette.dart'; import 'admin_dashboard.dart'; import 'device_checkout_ledger_page.dart'; import 'device_checkout_request_screen.dart'; import 'login_screen.dart'; import 'nfc_poccket_checkin_screen.dart'; import 'nfc_tag_writer_screen.dart'; import 'teacher_attendance_page.dart'; import 'teacher_student_management_page.dart'; class MainDashboard extends StatefulWidget { final String? userId; final String? userName; final String role; // 'student' | 'teacher' | 'admin' final bool isDeviceMatched; const MainDashboard({ super.key, this.userId, this.userName, required this.role, this.isDeviceMatched = true, }); @override State createState() => _MainDashboardState(); } class _MainDashboardState extends State { final StudentDashboardController _controller = StudentDashboardController(); // πŸ”‘ [κΆŒν•œ νŒμ •] 2061은 role이 'student'둜 μ €μž₯돼 μžˆμ§€λ§Œ μ‹€μ§ˆμ μœΌλ‘œ 졜고 κΆŒν•œμ„ κ°€μ§„λ‹€. bool get _isDeveloper => widget.userId == '2061'; bool get _isAdmin => widget.role == 'admin' || _isDeveloper; bool get _isTeacherOrAbove => widget.role == 'teacher' || _isAdmin; bool get _isStudent => widget.role == 'student'; String get _displayId => widget.userId ?? (widget.role == 'admin' ? 'κ΄€λ¦¬μž' : 'κ°„νŽΈμΈμ¦'); String get _displayName => widget.userName ?? (widget.role == 'admin' ? 'μ‹œμŠ€ν…œ κ΄€λ¦¬μž' : 'κ°„νŽΈμΈμ¦ 선생'); @override void dispose() { _controller.dispose(); super.dispose(); } void _openPocketCheckIn(BuildContext context) { Navigator.push( context, MaterialPageRoute( builder: (context) => NfcPocketCheckInScreen( studentId: _displayId, studentName: _displayName, ), ), ); } Future _deleteUser(String userId) async { final (_, message) = await _controller.deleteUser(userId); if (!mounted) return; ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text(message))); } void _showDeleteUserDialog(BuildContext context) { final TextEditingController idController = TextEditingController(); showDialog( context: context, builder: (context) { return AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(24), ), title: const Row( children: [ Icon(Icons.warning_amber_rounded, color: Colors.redAccent), SizedBox(width: 10), Text( '계정 κ°•μ œ μ‚­μ œ', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18), ), ], ), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'ν•™μƒμ˜ ν•™λ²ˆ λ˜λŠ” κ΅μ‚¬μ˜ ꡐ직원 번호λ₯Ό μž…λ ₯ν•˜μ„Έμš”.\nDBμ—μ„œ ν•΄λ‹Ή 계정과 토큰이 μ¦‰μ‹œ μ‚­μ œλ©λ‹ˆλ‹€.', style: TextStyle( color: Colors.black54, fontSize: 13, height: 1.4, ), ), const SizedBox(height: 16), TextField( controller: idController, decoration: InputDecoration( labelText: 'ν•™λ²ˆ λ˜λŠ” ꡐ직원 번호', hintText: '예: 201101 λ˜λŠ” T1001', labelStyle: TextStyle(color: Colors.red[400]), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(16), borderSide: BorderSide(color: Colors.red[400]!, width: 2), ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(16), ), prefixIcon: const Icon(Icons.person_remove_alt_1_rounded), ), ), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text( 'μ·¨μ†Œ', style: TextStyle( color: Colors.grey, fontWeight: FontWeight.bold, ), ), ), ElevatedButton( onPressed: () { final inputId = idController.text.trim(); if (inputId.isNotEmpty) { Navigator.pop(context); _deleteUser(inputId); } }, style: ElevatedButton.styleFrom( backgroundColor: Colors.redAccent, foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), elevation: 0, ), child: const Text( 'μ‚­μ œ μ‹€ν–‰', style: TextStyle(fontWeight: FontWeight.bold), ), ), ], ); }, ); } // 🎨 [역할별 ν…Œλ§ˆ] λ°°λ„ˆ 색상은 νŒ”λ ˆνŠΈλ‘œ ν†΅μΌν•˜κ³ , 제λͺ©/문ꡬ/μ•„μ΄μ½˜λ§Œ 역할에 따라 λ‹€λ₯΄κ²Œ. ({String title, String subtitle, Color color, IconData icon}) _theme() { if (_isDeveloper) { return ( title: 'πŸ‘‘ MASTER CONTROL', subtitle: '졜고 관리 κΆŒν•œ ν™œμ„±ν™”λ¨', color: AppPalette.ink, icon: Icons.admin_panel_settings_rounded, ); } else if (widget.role == 'admin') { return ( title: 'πŸ› οΈ ADMIN CONTROL', subtitle: 'μ‹œμŠ€ν…œ κ΄€λ¦¬μž κΆŒν•œ ν™œμ„±ν™”λ¨', color: AppPalette.ink, icon: Icons.admin_panel_settings_rounded, ); } else if (widget.role == 'teacher') { return ( title: 'πŸ‘¨β€πŸ« TEACHER PORTAL', subtitle: 'ꡐ직원 번호: $_displayId | ꡐ사 κΆŒν•œ ν™œμ„±ν™”λ¨', color: AppPalette.ink, icon: Icons.admin_panel_settings_rounded, ); } return ( title: 'πŸŽ“ STUDENT PORTAL', subtitle: 'ν•™λ²ˆ: $_displayId | 인증 μ™„λ£Œ', color: AppPalette.ink, icon: Icons.school_rounded, ); } @override Widget build(BuildContext context) { return ListenableBuilder( listenable: _controller, builder: (context, _) { final theme = _theme(); return Scaffold( backgroundColor: AppPalette.mist, appBar: AppBar( title: Text( theme.title, style: const TextStyle( fontWeight: FontWeight.bold, letterSpacing: 1.2, ), ), centerTitle: true, backgroundColor: theme.color, foregroundColor: Colors.white, elevation: 0, actions: [ IconButton( icon: const Icon(Icons.logout_rounded), onPressed: () => Navigator.pushReplacement( context, MaterialPageRoute(builder: (context) => const LoginScreen()), ), ), ], ), body: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: double.infinity, padding: const EdgeInsets.symmetric( horizontal: 24, vertical: 28, ), decoration: BoxDecoration( color: theme.color, borderRadius: const BorderRadius.only( bottomLeft: Radius.circular(32), bottomRight: Radius.circular(32), ), ), child: Row( children: [ Container( padding: const EdgeInsets.all(4), decoration: const BoxDecoration( color: Colors.white, shape: BoxShape.circle, ), child: CircleAvatar( radius: 30, backgroundColor: AppPalette.linen, child: Icon(theme.icon, size: 32, color: theme.color), ), ), const SizedBox(width: 18), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '$_displayName λ‹˜', style: const TextStyle( fontSize: 22, fontWeight: FontWeight.bold, color: Colors.white, ), ), const SizedBox(height: 4), Text( theme.subtitle, style: TextStyle( color: Colors.white.withValues(alpha: 0.8), fontSize: 14, ), ), ], ), ], ), ), const SizedBox(height: 32), Padding( padding: const EdgeInsets.symmetric(horizontal: 24.0), child: Row( children: [ Container( width: 4, height: 18, decoration: BoxDecoration( color: theme.color, borderRadius: BorderRadius.circular(2), ), ), const SizedBox(width: 10), const Text( '슀마트 관리 μ‹œμŠ€ν…œ 메뉴', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: AppPalette.ink, ), ), ], ), ), const SizedBox(height: 16), LayoutBuilder( builder: (context, constraints) { // πŸ–₯️ μ›Ή(넓은 ν™”λ©΄)은 ν•œ 쀄에 5κ°œμ”©, 폰(쒁은 ν™”λ©΄)은 κΈ°μ‘΄ 2개 κ·ΈλŒ€λ‘œ. final bool isWide = constraints.maxWidth >= 800; return Center( child: ConstrainedBox( constraints: BoxConstraints( maxWidth: isWide ? 1300 : 700, ), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 24.0), child: GridView.count( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), crossAxisCount: isWide ? 5 : 2, crossAxisSpacing: 16, mainAxisSpacing: 16, childAspectRatio: 0.95, children: [ // πŸ‘¨β€πŸŽ“ 학생 μ „μš© ----------------------------------- if (_isStudent) _buildModernCard( icon: widget.isDeviceMatched ? Icons.contactless_rounded : Icons.lock_rounded, title: 'NFC νƒœκ·Έ', subtitle: widget.isDeviceMatched ? 'μΆœμ„ 및 폰 수거 μ™„λ£Œ' : '⚠️ 본인 인증 κΈ°κΈ° μ „μš©', color: widget.isDeviceMatched ? AppPalette.ink : AppPalette.sage, onTap: widget.isDeviceMatched ? () => _openPocketCheckIn(context) : () { ScaffoldMessenger.of( context, ).showSnackBar( const SnackBar( content: Text( '🚨 λŒ€λ¦¬ μΆœμ„ λ°©μ§€λ₯Ό μœ„ν•΄ λ“±λ‘λœ 본인 μŠ€λ§ˆνŠΈν°μ—μ„œλ§Œ μΆœμ„ κ°€λŠ₯ν•©λ‹ˆλ‹€.', ), ), ); }, isActionButton: widget.isDeviceMatched, isLoading: _controller.isLoading, ), if (_isStudent) _buildModernCard( icon: Icons.fastfood_rounded, title: 'μ‹€μ‹œκ°„ 학ꡐ 상황', subtitle: '급식싀 쀄 & 맀점 재고 확인', color: AppPalette.ink, onTap: () { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text( 'πŸ” μ‹€μ‹œκ°„ 학ꡐ 상황 νŽ˜μ΄μ§€λ‘œ μ΄λ™ν•©λ‹ˆλ‹€.', ), ), ); }, ), if (_isStudent) _buildModernCard( icon: Icons.tablet_mac_rounded, title: '슀마트기기 반좜', subtitle: 'νŒ¨λ“œ μ‚¬μš© μ‹ μ²­ (μ‹œκ°„/λͺ©μ )', color: AppPalette.ink, onTap: () => Navigator.push( context, MaterialPageRoute( builder: (context) => DeviceCheckoutRequestScreen( studentId: _displayId, studentName: _displayName, ), ), ), ), // πŸ‘¨β€πŸ« ꡐ사 이상 ----------------------------------- if (_isTeacherOrAbove) _buildModernCard( icon: Icons.assignment_turned_in_rounded, title: 'μ‹€μ‹œκ°„ μΆœμ„ 확인', subtitle: '학생 제좜 둜그 λͺ¨λ‹ˆν„°λ§', color: AppPalette.ink, onTap: () => Navigator.push( context, MaterialPageRoute( builder: (context) => const TeacherAttendancePage(), ), ), ), if (_isTeacherOrAbove) _buildModernCard( icon: Icons.manage_accounts_rounded, title: '학생 계정 관리', subtitle: '계정 μΆ”κ°€ 및 κ°•μ œ 리셋', color: AppPalette.ink, onTap: () => Navigator.push( context, MaterialPageRoute( builder: (context) => const TeacherStudentManagementPage(), ), ), ), if (_isTeacherOrAbove) _buildModernCard( icon: Icons.tablet_mac_rounded, title: '슀마트기기 반좜 λŒ€μž₯', subtitle: 'νŒ¨λ“œ 반좜 μ‹ μ²­ 승인/거절', color: AppPalette.ink, onTap: () => Navigator.push( context, MaterialPageRoute( builder: (context) => DeviceCheckoutLedgerPage( teacherId: widget.userId, teacherName: widget.userName, ), ), ), ), // πŸ› οΈ κ΄€λ¦¬μž μ „μš© ----------------------------------- if (_isAdmin) _buildModernCard( icon: Icons.terminal_rounded, title: 'μ„œλ²„ DB μ œμ–΄', subtitle: 'μ‹œμŠ€ν…œ 원격 μ΄ˆκΈ°ν™”', color: AppPalette.ink, onTap: () => Navigator.push( context, MaterialPageRoute( builder: (context) => const AdminDashboard(), ), ), ), if (_isAdmin) _buildModernCard( icon: Icons.delete_sweep_rounded, title: '계정 κ°•μ œ μ‚­μ œ', subtitle: '학생 및 ꡐ사 DB μ‚­μ œ', color: Colors.red[600]!, onTap: () => _showDeleteUserDialog(context), ), if (_isAdmin) _buildModernCard( icon: Icons.edit_note_rounded, title: 'NFC νƒœκ·Έ μ“°κΈ°', subtitle: 'μ£Όλ¨Έλ‹ˆ μŠ€ν‹°μ»€ 초기 μ„€μ •', color: AppPalette.ink, onTap: () => Navigator.push( context, MaterialPageRoute( builder: (context) => const NfcTagWriterScreen(), ), ), ), ], ), ), ), ); }, ), const SizedBox(height: 32), ], ), ), ); }, ); } // πŸ’Ž [μΉ΄λ“œ λ””μžμΈ μœ„μ ―] 학생/ꡐ사 λŒ€μ‹œλ³΄λ“œμ—μ„œ μ“°λ˜ 것과 동일 β€” 이제 ν•œ κ³³μ—λ§Œ 쑴재. Widget _buildModernCard({ required IconData icon, required String title, required String subtitle, required Color color, required VoidCallback onTap, bool isActionButton = false, bool isLoading = false, }) { return InkWell( onTap: isLoading ? null : onTap, borderRadius: BorderRadius.circular(24), child: Ink( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: AppPalette.paper, borderRadius: BorderRadius.circular(24), border: Border.all(color: AppPalette.sage, width: 1), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppPalette.linen, borderRadius: BorderRadius.circular(16), ), child: Icon(icon, color: color, size: 28), ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( title, style: const TextStyle( fontSize: 15, fontWeight: FontWeight.bold, color: AppPalette.ink, ), ), ), if (isLoading) const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2), ) else if (isActionButton) Icon( Icons.touch_app_rounded, size: 16, color: color.withValues(alpha: 0.5), ), ], ), const SizedBox(height: 4), Text( subtitle, style: TextStyle( fontSize: 12, color: AppPalette.ink.withValues(alpha: 0.55), height: 1.2, ), ), ], ), ], ), ), ); } }