// ๐Ÿ” (๋งˆ์Šคํ„ฐ ๊ณ„์ •์šฉ) ํ•™์ƒ ๊ณ„์ • ๋ฐ ๊ธฐ๊ธฐ ๊ด€๋ฆฌ ํ™”๋ฉด (UI ์ „์šฉ). ๊ธฐ๊ธฐ UUID ์ดˆ๊ธฐํ™”/๊ณ„์ • ์‚ญ์ œ/์ƒ์„ฑ ๋‹ค์ด์–ผ๋กœ๊ทธ๋ฅผ ๊ทธ๋ฆฐ๋‹ค. // ์„œ๋ฒ„ ํ†ต์‹ /์ƒํƒœ๋Š” lib/function/student_management_controller.dart๊ฐ€ ๋‹ด๋‹นํ•œ๋‹ค. import 'package:flutter/material.dart'; import '../function/student_management_controller.dart'; // ========================================== // ๐Ÿ” 6. ํ•™์ƒ ๊ณ„์ • ๋ฐ ๊ธฐ๊ธฐ ๊ด€๋ฆฌ ํ™”๋ฉด (๊ธฐ๊ธฐ ๋ฆฌ์…‹ + ๊ณ„์ • ์‚ญ์ œ ์™„๋ณธ) // ========================================== class StudentManagementScreen extends StatefulWidget { const StudentManagementScreen({super.key}); @override State createState() => _StudentManagementScreenState(); } class _StudentManagementScreenState extends State { final StudentManagementController _controller = StudentManagementController(); @override void initState() { super.initState(); _controller.init(); } @override void dispose() { _controller.dispose(); super.dispose(); } Future _fetchStudents() async { final error = await _controller.fetchStudents(); if (error != null && mounted) { ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text(error))); } } // ๐ŸŒ ์„œ๋ฒ„๋กœ ๊ธฐ๊ธฐ ์ดˆ๊ธฐํ™”(๋ฆฌ์…‹) ๋ช…๋ น ๋ณด๋‚ด๊ธฐ void _resetDevice(String studentId, String studentName) { showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text( 'โš ๏ธ ๊ธฐ๊ธฐ ์ž ๊ธˆ ํ•ด์ œ', style: TextStyle(fontWeight: FontWeight.bold, color: Colors.purple), ), content: Text('$studentName ํ•™์ƒ์˜ ์Šค๋งˆํŠธํฐ ๊ธฐ๊ธฐ ๋“ฑ๋ก์„ ์ดˆ๊ธฐํ™”ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?'), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: const Text('์ทจ์†Œ', style: TextStyle(color: Colors.grey)), ), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.purple, foregroundColor: Colors.white, ), onPressed: () async { Navigator.pop(ctx); final (_, message) = await _controller.resetDevice( studentId, studentName, ); if (!mounted) return; ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text(message))); }, child: const Text('์ดˆ๊ธฐํ™” ์Šน์ธ'), ), ], ), ); } // ๐ŸŒ [์ƒˆ ๊ธฐ๋Šฅ] ์„œ๋ฒ„๋กœ ๊ณ„์ • ์™„์ „ ์‚ญ์ œ ๋ช…๋ น ๋ณด๋‚ด๊ธฐ void _deleteUser(String studentId, String studentName) { showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text( '๐Ÿšจ ๊ณ„์ • ์™„์ „ ์‚ญ์ œ ๊ฒฝ๊ณ ', style: TextStyle(fontWeight: FontWeight.bold, color: Colors.red), ), content: Text( '์ •๋ง๋กœ $studentName ($studentId) ํ•™์ƒ์˜ ๊ณ„์ •์„ ์‹œ์Šคํ…œ์—์„œ ํƒˆํ‡ด(์‚ญ์ œ)์‹œํ‚ค๊ฒ ์Šต๋‹ˆ๊นŒ?\n\n์ด ์ž‘์—…์€ ๋˜๋Œ๋ฆด ์ˆ˜ ์—†์œผ๋ฉฐ, ํ•ด๋‹น ํ•™์ƒ์€ ๋‹ค์‹œ ํšŒ์›๊ฐ€์ž…์„ ์ง„ํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.', ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: const Text('์ทจ์†Œ', style: TextStyle(color: Colors.grey)), ), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.red, foregroundColor: Colors.white, ), onPressed: () async { Navigator.pop(ctx); final (_, message) = await _controller.deleteUser( studentId, studentName, ); if (!mounted) return; ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text(message))); }, child: const Text('์˜๊ตฌ ์‚ญ์ œ'), ), ], ), ); } void _showCreateUserDialog() { final idController = TextEditingController(); final nameController = TextEditingController(); showDialog( context: context, builder: (context) => AlertDialog( title: const Text('๐Ÿ‘ค ์‹ ๊ทœ ํ•™์ƒ ๊ณ„์ • ์ถ”๊ฐ€'), content: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( controller: idController, decoration: const InputDecoration(labelText: 'ํ•™๋ฒˆ ์ž…๋ ฅ (์˜ˆ: 20101)'), keyboardType: TextInputType.number, ), const SizedBox(height: 8), TextField( controller: nameController, decoration: const InputDecoration(labelText: 'ํ•™์ƒ ์ด๋ฆ„ ์ž…๋ ฅ'), ), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('์ทจ์†Œ'), ), ElevatedButton( onPressed: () async { String studentId = idController.text.trim(); String name = nameController.text.trim(); if (studentId.isEmpty || name.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('โš ๏ธ ํ•™๋ฒˆ๊ณผ ์ด๋ฆ„์„ ๋ชจ๋‘ ์ž…๋ ฅํ•˜์„ธ์š”.')), ); return; } final (success, message) = await _controller.createUser( studentId, name, ); if (!mounted) return; if (success) { Navigator.pop(context); // ํŒ์—… ๋‹ซ๊ธฐ } ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text(message))); }, child: const Text('์ƒ์„ฑ'), ), ], ), ); } @override Widget build(BuildContext context) { return ListenableBuilder( listenable: _controller, builder: (context, _) { final realStudents = _controller.students; return Scaffold( backgroundColor: Colors.grey[50], appBar: AppBar( title: const Text( 'ํ•™์ƒ ๊ณ„์ • ๋ฐ ๊ธฐ๊ธฐ ๊ด€๋ฆฌ', style: TextStyle(fontWeight: FontWeight.bold), ), backgroundColor: Colors.purple, foregroundColor: Colors.white, actions: [ IconButton( icon: const Icon(Icons.person_add_alt_1_rounded), onPressed: () => _showCreateUserDialog(), ), IconButton(icon: const Icon(Icons.refresh), onPressed: _fetchStudents), ], ), body: _controller.isLoading ? const Center( child: CircularProgressIndicator(color: Colors.purple), ) : realStudents.isEmpty ? const Center( child: Text( '๊ฐ€์ž…๋œ ํ•™์ƒ ๊ณ„์ •์ด ์—†์Šต๋‹ˆ๋‹ค.', style: TextStyle(color: Colors.grey, fontSize: 16), ), ) : ListView.builder( padding: const EdgeInsets.all(16), itemCount: realStudents.length, itemBuilder: (context, index) { final student = realStudents[index]; final bool needsReset = student['device'] == "์ดˆ๊ธฐํ™” ํ•„์š”"; return Card( elevation: 2, margin: const EdgeInsets.only(bottom: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), child: ListTile( contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12, ), leading: CircleAvatar( backgroundColor: needsReset ? Colors.red[50] : Colors.purple[50], child: Icon( needsReset ? Icons.lock_reset : Icons.person, color: needsReset ? Colors.red : Colors.purple, ), ), title: Text( '${student['name']} (${student['id']})', style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 16, ), ), subtitle: Text( needsReset ? '๊ธฐ๊ธฐ ์ดˆ๊ธฐํ™” ์Šน์ธ ๋Œ€๊ธฐ์ค‘' : '์ •์ƒ ๋“ฑ๋ก ์ƒํƒœ', style: TextStyle( color: needsReset ? Colors.red : Colors.grey, fontWeight: needsReset ? FontWeight.bold : FontWeight.normal, ), ), // ๐Ÿ•น๏ธ ์˜ค๋ฅธ์ชฝ ๋์— [๊ธฐ๊ธฐ ๋ฆฌ์…‹] ๋ฒ„ํŠผ๊ณผ [์“ฐ๋ ˆ๊ธฐํ†ต(์‚ญ์ œ)] ๋ฒ„ํŠผ์„ ๋‚˜๋ž€ํžˆ ๋ฐฐ์น˜ํ•˜๋Š” Row ๋ ˆ์ด์•„์›ƒ ๊ธฐํš trailing: Row( mainAxisSize: MainAxisSize.min, children: [ if (!needsReset) OutlinedButton( style: OutlinedButton.styleFrom( foregroundColor: Colors.purple, side: BorderSide( color: Colors.purple.withValues( alpha: 0.5, ), ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), onPressed: () => _resetDevice( student['id'], student['name'], ), child: const Text('๊ธฐ๊ธฐ ๋ฆฌ์…‹'), ) else const Icon( Icons.check_circle, color: Colors.green, ), const SizedBox(width: 8), // ๐Ÿ”ด [์ƒˆ ๋ฒ„ํŠผ] ์ตœ๊ณ ๊ถŒํ•œ ๋งˆ์Šคํ„ฐ ์ „์šฉ ๊ณ„์ • ์˜๊ตฌ ์‚ญ์ œ ์“ฐ๋ ˆ๊ธฐํ†ต ๋ฒ„ํŠผ! IconButton( icon: const Icon( Icons.delete_forever_rounded, color: Colors.redAccent, ), tooltip: '๊ณ„์ • ์˜๊ตฌ ์‚ญ์ œ', onPressed: () => _deleteUser(student['id'], student['name']), ), ], ), ), ); }, ), ); }, ); } }