// πŸŽ“ 학생 λŒ€μ‹œλ³΄λ“œ. NFC μ£Όλ¨Έλ‹ˆ 체크인 μ§„μž…, μ‹€μ‹œκ°„ 학ꡐ 상황 μ•ˆλ‚΄, // 개발자 λ§ˆμŠ€ν„° 계정 μ „μš© 관리 메뉴(ν˜„ν™©/DBμ œμ–΄/계정관리/μ‚­μ œ)λ₯Ό λ‹΄λ‹Ήν•œλ‹€. import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import '../config.dart'; import '../nfc_poccket_checkin_screen.dart'; import 'login_screen.dart'; import 'teacher_attendance_page.dart'; import 'admin_dashboard.dart'; import 'student_management_screen.dart'; import 'nfc_tag_writer_screen.dart'; // ------------------------------------------------------------- // 2. 학생 λŒ€μ‹œλ³΄λ“œ (StudentDashboard) // ------------------------------------------------------------- class StudentDashboard extends StatefulWidget { final String studentId; final String studentName; final bool isDeviceMatched; // πŸ†• [λ³€κ²½] κΈ°κΈ° UUIDκ°€ λ§€μΉ­λ˜μ—ˆλŠ”μ§€ ν™•μΈν•˜λŠ” λ³€μˆ˜ μΆ”κ°€ const StudentDashboard({ super.key, required this.studentId, required this.studentName, required this.isDeviceMatched, // πŸ†• [λ³€κ²½] ν•„μˆ˜ λ§€κ°œλ³€μˆ˜λ‘œ 등둝 }); @override State createState() => _StudentDashboardState(); } class _StudentDashboardState extends State { bool _isLoading = false; // 1️⃣ NFC νƒœκ·Έ μΉ΄λ“œ β†’ μ‹€μ œ NFC μ£Όλ¨Έλ‹ˆ 체크인 ν™”λ©΄μœΌλ‘œ 이동 void _openPocketCheckIn(BuildContext context) { Navigator.push( context, MaterialPageRoute( builder: (context) => NfcPocketCheckInScreen( studentId: widget.studentId, studentName: widget.studentName, ), ), ); } // 2️⃣ [κΈ°μ‘΄ 동일] λ§ˆμŠ€ν„° 계정 μ „μš© νšŒμ› μ‚­μ œ API 호좜 ν•¨μˆ˜ Future _deleteUser(BuildContext context, String userId) async { setState(() => _isLoading = true); final url = Uri.parse('$baseUrl/api/users/delete/$userId'); try { final response = await http.delete(url); if (response.statusCode == 200) { final responseData = jsonDecode(response.body); ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text('βœ… ${responseData['message']}'))); } else { final errorData = jsonDecode(response.body); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('❌ μ‚­μ œ μ‹€νŒ¨: ${errorData['detail'] ?? 'μ•Œ 수 μ—†λŠ” 였λ₯˜'}'), ), ); } } catch (e) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('❌ μ„œλ²„μ™€ μ—°κ²°ν•  수 μ—†μŠ΅λ‹ˆλ‹€. (λ„€νŠΈμ›Œν¬ μ—λŸ¬)')), ); } finally { setState(() => _isLoading = false); } } // 3️⃣ [κΈ°μ‘΄ 동일] ν•™λ²ˆ/ꡐ직원 번호λ₯Ό μž…λ ₯λ°›λŠ” λͺ¨λ˜ νŒμ—…μ°½(Dialog) 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(context, 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), ), ), ], ); }, ); } @override Widget build(BuildContext context) { final bool isDeveloper = widget.studentId == "2061"; return Scaffold( backgroundColor: Colors.grey[100], appBar: AppBar( title: Text( isDeveloper ? 'πŸ‘‘ MASTER CONTROL' : 'πŸŽ“ STUDENT PORTAL', style: const TextStyle( fontWeight: FontWeight.bold, letterSpacing: 1.2, ), ), centerTitle: true, backgroundColor: isDeveloper ? Colors.deepPurple[700] : Colors.indigo[700], 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: isDeveloper ? Colors.deepPurple[700] : Colors.indigo[700], 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: isDeveloper ? Colors.deepPurple[50] : Colors.indigo[50], child: Icon( isDeveloper ? Icons.admin_panel_settings_rounded : Icons.school_rounded, size: 32, color: isDeveloper ? Colors.deepPurple : Colors.indigo, ), ), ), const SizedBox(width: 18), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '${widget.studentName} λ‹˜', style: const TextStyle( fontSize: 22, fontWeight: FontWeight.bold, color: Colors.white, ), ), const SizedBox(height: 4), Text( isDeveloper ? '졜고 관리 κΆŒν•œ ν™œμ„±ν™”λ¨' : 'ν•™λ²ˆ: ${widget.studentId} | 인증 μ™„λ£Œ', 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: isDeveloper ? Colors.deepPurple : Colors.indigo, borderRadius: BorderRadius.circular(2), ), ), const SizedBox(width: 10), const Text( '슀마트 관리 μ‹œμŠ€ν…œ 메뉴', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black87, ), ), ], ), ), const SizedBox(height: 16), Padding( padding: const EdgeInsets.symmetric(horizontal: 24.0), child: GridView.count( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), crossAxisCount: 2, crossAxisSpacing: 16, mainAxisSpacing: 16, childAspectRatio: 0.95, children: [ // πŸ†• [λ³€κ²½ 지점] κΈ°κΈ°κ°€ μΌμΉ˜ν•˜λ©΄ 정상 ν™œμ„±ν™”, μΌμΉ˜ν•˜μ§€ μ•ŠμœΌλ©΄ μžλ¬Όμ‡ Lock 처리 if (isDeveloper || widget.studentId != "2061") _buildModernCard( icon: widget.isDeviceMatched ? Icons.contactless_rounded : Icons.lock_rounded, title: 'NFC νƒœκ·Έ', subtitle: widget.isDeviceMatched ? 'μΆœμ„ 및 폰 수거 μ™„λ£Œ' : '⚠️ 본인 인증 κΈ°κΈ° μ „μš©', color: widget.isDeviceMatched ? Colors.blue : Colors.grey[400]!, onTap: widget.isDeviceMatched ? () => _openPocketCheckIn(context) : () { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text( '🚨 λŒ€λ¦¬ μΆœμ„ λ°©μ§€λ₯Ό μœ„ν•΄ λ“±λ‘λœ 본인 μŠ€λ§ˆνŠΈν°μ—μ„œλ§Œ μΆœμ„ κ°€λŠ₯ν•©λ‹ˆλ‹€.', ), ), ); }, isActionButton: widget.isDeviceMatched, isLoading: _isLoading, ), // πŸ†• [μΆ”κ°€ 지점] κΈ°κΈ° 일치 μ—¬λΆ€ 상관없이 νƒœλΈ”λ¦Ώμ—μ„œλ„ λˆ„κ΅¬λ‚˜ 확인 κ°€λŠ₯ν•œ 학ꡐ μƒν™©νŒ μΉ΄λ“œ _buildModernCard( icon: Icons.fastfood_rounded, title: 'μ‹€μ‹œκ°„ 학ꡐ 상황', subtitle: '급식싀 쀄 & 맀점 재고 확인', color: Colors.orange[700]!, onTap: () { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('πŸ” μ‹€μ‹œκ°„ 학ꡐ 상황 νŽ˜μ΄μ§€λ‘œ μ΄λ™ν•©λ‹ˆλ‹€.'), ), ); }, ), if (isDeveloper) _buildModernCard( icon: Icons.monitor_heart_rounded, title: 'μ‹€μ‹œκ°„ ν˜„ν™©', subtitle: 'κ΅μ‚¬μš© 수거 λͺ¨λ‹ˆν„°λ§', color: Colors.teal, onTap: () => Navigator.push( context, MaterialPageRoute( builder: (context) => const TeacherAttendancePage(), ), ), ), if (isDeveloper) _buildModernCard( icon: Icons.terminal_rounded, title: 'μ„œλ²„ DB μ œμ–΄', subtitle: 'μ‹œμŠ€ν…œ 원격 μ΄ˆκΈ°ν™”', color: Colors.amber[800]!, onTap: () => Navigator.push( context, MaterialPageRoute( builder: (context) => const AdminDashboard(), ), ), ), if (isDeveloper) _buildModernCard( icon: Icons.add_moderator_rounded, title: '학생 계정 관리', subtitle: 'UUID 리셋 및 승인', color: Colors.purple, onTap: () => Navigator.push( context, MaterialPageRoute( builder: (context) => const StudentManagementScreen(), ), ), ), if (isDeveloper) _buildModernCard( icon: Icons.delete_sweep_rounded, title: '계정 κ°•μ œ μ‚­μ œ', subtitle: '학생 및 ꡐ사 DB μ‚­μ œ', color: Colors.red[600]!, onTap: () => _showDeleteUserDialog(context), ), if (isDeveloper) _buildModernCard( icon: Icons.edit_note_rounded, title: 'NFC νƒœκ·Έ μ“°κΈ°', subtitle: 'μ£Όλ¨Έλ‹ˆ μŠ€ν‹°μ»€ 초기 μ„€μ •', color: Colors.deepPurple, onTap: () => Navigator.push( context, MaterialPageRoute( builder: (context) => const NfcTagWriterScreen(), ), ), ), ], ), ), const SizedBox(height: 32), ], ), ), ); } // 5️⃣ [μΉ΄λ“œ λ””μžμΈ μœ„μ ―] - κΈ°μ‘΄ ν˜•νƒœ μ™„μ „ 보쑴 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: Colors.white, borderRadius: BorderRadius.circular(24), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.04), blurRadius: 16, offset: const Offset(0, 4), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: color.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(16), ), child: Icon(icon, color: color, size: 28), ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( title, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black87, ), ), 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: Colors.grey[500], height: 1.2, ), ), ], ), ], ), ), ); } }