// ๐Ÿ” (๋งˆ์Šคํ„ฐ ๊ณ„์ •์šฉ) ํ•™์ƒ ๊ณ„์ • ๋ฐ ๊ธฐ๊ธฐ ๊ด€๋ฆฌ ํ™”๋ฉด. ๊ธฐ๊ธฐ UUID ์ดˆ๊ธฐํ™”์™€ ๊ณ„์ • ์‚ญ์ œ๋ฅผ ๋‹ด๋‹นํ•œ๋‹ค. import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import '../config.dart'; // ========================================== // ๐Ÿ” 6. ํ•™์ƒ ๊ณ„์ • ๋ฐ ๊ธฐ๊ธฐ ๊ด€๋ฆฌ ํ™”๋ฉด (๊ธฐ๊ธฐ ๋ฆฌ์…‹ + ๊ณ„์ • ์‚ญ์ œ ์™„๋ณธ) // ========================================== class StudentManagementScreen extends StatefulWidget { const StudentManagementScreen({super.key}); @override State createState() => _StudentManagementScreenState(); } class _StudentManagementScreenState extends State { List realStudents = []; bool _isLoading = true; @override void initState() { super.initState(); _fetchStudents(); } // ๐ŸŒ ์„œ๋ฒ„์—์„œ ์ „์ฒด ํ•™์ƒ ๋ชฉ๋ก ๋ถˆ๋Ÿฌ์˜ค๊ธฐ Future _fetchStudents() async { setState(() => _isLoading = true); try { final response = await http.get(Uri.parse('$baseUrl/api/users')); if (response.statusCode == 200) { final data = jsonDecode(utf8.decode(response.bodyBytes)); setState(() { realStudents = data['users'] ?? []; _isLoading = false; }); } } catch (e) { ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text('๋ฐ์ดํ„ฐ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ์‹คํŒจ: $e'))); setState(() => _isLoading = false); } } // ๐ŸŒ ์„œ๋ฒ„๋กœ ๊ธฐ๊ธฐ ์ดˆ๊ธฐํ™”(๋ฆฌ์…‹) ๋ช…๋ น ๋ณด๋‚ด๊ธฐ Future _resetDevice(String studentId, String studentName) async { 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); try { final response = await http.post( Uri.parse('$baseUrl/api/users/reset'), headers: {"Content-Type": "application/json"}, body: jsonEncode({"studentId": studentId}), ); if (response.statusCode == 200) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('โœ… $studentName ํ•™์ƒ ๊ธฐ๊ธฐ ์ดˆ๊ธฐํ™” ์™„๋ฃŒ!')), ); _fetchStudents(); } } catch (e) { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('โŒ ์ดˆ๊ธฐํ™” ํ†ต์‹  ์‹คํŒจ'))); } }, child: const Text('์ดˆ๊ธฐํ™” ์Šน์ธ'), ), ], ), ); } // ๐ŸŒ [์ƒˆ ๊ธฐ๋Šฅ] ์„œ๋ฒ„๋กœ ๊ณ„์ • ์™„์ „ ์‚ญ์ œ ๋ช…๋ น ๋ณด๋‚ด๊ธฐ ํ•จ์ˆ˜ Future _deleteUser(String studentId, String studentName) async { 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); try { // ๐Ÿ’ก http.delete ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์„œ๋ฒ„์— ๊ณ„์ • ์‚ญ์ œ ์š”์ฒญ ๋ฐœ์†ก! final response = await http.delete( Uri.parse('$baseUrl/api/users/delete'), headers: {"Content-Type": "application/json"}, body: jsonEncode({"studentId": studentId}), ); if (response.statusCode == 200) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('๐Ÿ’ฅ $studentName ํ•™์ƒ์˜ ๊ณ„์ •์ด ์˜๊ตฌ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.'), ), ); _fetchStudents(); // ์„ฑ๊ณตํ•˜๋ฉด ๋ชฉ๋ก ์ƒˆ๋กœ๊ณ ์นจ! } } catch (e) { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('โŒ ๊ณ„์ • ์‚ญ์ œ ํ†ต์‹  ์‹คํŒจ'))); } }, child: const Text('์˜๊ตฌ ์‚ญ์ œ'), ), ], ), ); } // ๐Ÿ’ก _StudentManagementScreenState ํด๋ž˜์Šค ๋‚ด๋ถ€์— ๋ถ™์—ฌ๋„ฃ์œผ์„ธ์š”. 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; } try { // โš ๏ธ ์—ฌ๊ธฐ๋„ ํ›—์ถง๊ฐ€๋ฃป๋‹˜์˜ ํ”„๋กœ์ ํŠธ ์ „์—ญ baseUrl ๋ณ€์ˆ˜๋ช…์œผ๋กœ ๋งž์ถฐ์ฃผ์„ธ์š”! final response = await http.post( Uri.parse('$baseUrl/api/users/create'), headers: {"Content-Type": "application/json"}, body: jsonEncode({"studentId": studentId, "name": name}), ); final res = jsonDecode(response.body); if (res['status'] == 'success') { Navigator.pop(context); // ํŒ์—… ๋‹ซ๊ธฐ ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('โœ… ${res['message']}')), ); _fetchStudents(); // ๐Ÿ”„ ์ถ”๊ฐ€ ์™„๋ฃŒ ํ›„ ๋ชฉ๋ก ์ƒˆ๋กœ๊ณ ์นจ (๊ธฐ์กด ํ•จ์ˆ˜๋ช… ํ™•์ธ ํ•„์š”) } else { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('โŒ ${res['message']}')), ); } } catch (e) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('โŒ ํ•™์ƒ ๋“ฑ๋ก ์‹คํŒจ (ํ†ต์‹  ์—๋Ÿฌ)')), ); } }, child: const Text('์ƒ์„ฑ'), ), ], ), ); } @override Widget build(BuildContext context) { 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: _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']), ), ], ), ), ); }, ), ); } }