From db903fcdeffff264d7445894c3efd6551a1c3342 Mon Sep 17 00:00:00 2001 From: sihoo Date: Fri, 28 Aug 2026 22:05:36 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B8=B0=EC=A1=B4=20=ED=95=99=EC=83=9D=20?= =?UTF-8?q?=EA=B3=84=EC=A0=95=20=ED=95=99=EB=85=84=20=EC=A7=80=EC=A0=95/?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 학생 계정 관리 화면에 학번으로 학년을 지정/변경하는 카드+다이얼로그 추가 - 계정 생성 성공 판정을 HTTP 상태코드가 아닌 응답 status 필드로 정확히 확인하도록 수정 Co-Authored-By: Claude Sonnet 5 --- ...teacher_student_management_controller.dart | 33 +++- lib/ui/teacher_student_management_page.dart | 164 ++++++++++++++++++ 2 files changed, 195 insertions(+), 2 deletions(-) diff --git a/lib/function/teacher_student_management_controller.dart b/lib/function/teacher_student_management_controller.dart index 35f48e2..465cf9a 100644 --- a/lib/function/teacher_student_management_controller.dart +++ b/lib/function/teacher_student_management_controller.dart @@ -31,8 +31,8 @@ class TeacherStudentManagementController extends ChangeNotifier { ); final result = jsonDecode(utf8.decode(response.bodyBytes)); - if (response.statusCode == 200 || response.statusCode == 201) { - return (true, '✅ 계정 생성 완료: ${result['message'] ?? '성공'}'); + if (response.statusCode == 200 && result['status'] == 'success') { + return (true, '✅ ${result['message'] ?? '계정이 생성되었습니다.'}'); } else { return (false, '❌ 생성 실패: ${result['message'] ?? '오류 발생'}'); } @@ -44,6 +44,35 @@ class TeacherStudentManagementController extends ChangeNotifier { } } + /// 🏫 이미 만들어진 학생 계정의 학년을 지정/변경한다. grade가 null이면 "미배정"으로 되돌린다. + Future<(bool success, String message)> updateStudentGrade({ + required String studentId, + int? grade, + }) async { + _isWorking = true; + notifyListeners(); + try { + final url = Uri.parse('$baseUrl/api/users/update-grade'); + final response = await http.post( + url, + headers: {"Content-Type": "application/json"}, + body: jsonEncode({"studentId": studentId, "grade": grade}), + ); + final result = jsonDecode(utf8.decode(response.bodyBytes)); + + if (response.statusCode == 200 && result['status'] == 'success') { + return (true, '✅ ${result['message'] ?? '학년이 지정되었습니다.'}'); + } else { + return (false, '❌ 지정 실패: ${result['message'] ?? '오류 발생'}'); + } + } catch (e) { + return (false, '🚨 네트워크 에러: $e'); + } finally { + _isWorking = false; + notifyListeners(); + } + } + /// ❌ 학생 계정 삭제. Future<(bool success, String message)> deleteStudentAccount( String studentId, diff --git a/lib/ui/teacher_student_management_page.dart b/lib/ui/teacher_student_management_page.dart index 6bbc10a..a71263b 100644 --- a/lib/ui/teacher_student_management_page.dart +++ b/lib/ui/teacher_student_management_page.dart @@ -63,6 +63,99 @@ class _TeacherStudentManagementPageState ).showSnackBar(SnackBar(content: Text(message))); } + // 🏫 [기존 학생 학년 지정/변경 다이얼로그] + void _showUpdateGradeDialog() { + final TextEditingController idController = TextEditingController(); + int? grade; + + showDialog( + context: context, + builder: (context) { + return StatefulBuilder( + builder: (context, setDialogState) { + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(24), + ), + title: const Text( + '🏫 학년 지정/변경', + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18), + ), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + '이미 만들어진 학생 계정의 학년을 지정하거나 바꿉니다.', + style: TextStyle(color: Colors.black54, fontSize: 13), + ), + const SizedBox(height: 16), + TextField( + controller: idController, + keyboardType: TextInputType.number, + decoration: const InputDecoration( + labelText: '학번', + border: OutlineInputBorder(), + prefixIcon: Icon(Icons.badge), + ), + ), + const SizedBox(height: 12), + DropdownButtonFormField( + initialValue: grade, + decoration: const InputDecoration( + labelText: '학년', + border: OutlineInputBorder(), + prefixIcon: Icon(Icons.class_), + ), + items: const [ + DropdownMenuItem(value: null, child: Text('미배정')), + DropdownMenuItem(value: 1, child: Text('1학년')), + DropdownMenuItem(value: 2, child: Text('2학년')), + DropdownMenuItem(value: 3, child: Text('3학년')), + ], + onChanged: (value) => setDialogState(() => grade = value), + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('취소', style: TextStyle(color: Colors.grey)), + ), + ElevatedButton( + onPressed: () async { + final inputId = idController.text.trim(); + if (inputId.isEmpty) return; + Navigator.pop(context); + final (_, message) = await _controller.updateStudentGrade( + studentId: inputId, + grade: grade, + ); + if (!mounted) return; + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text(message))); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: const Text( + '적용', + style: TextStyle(fontWeight: FontWeight.bold), + ), + ), + ], + ); + }, + ); + }, + ); + } + // ❌ [학생 삭제 버튼 동작] Future _deleteStudentAccount(String studentId) async { final (_, message) = await _controller.deleteStudentAccount(studentId); @@ -284,6 +377,77 @@ class _TeacherStudentManagementPageState ), const SizedBox(height: 36), + // 🏷️ 인디케이터 바 (기존 학생 학년 지정/변경) + Row( + children: [ + Container( + width: 4, + height: 16, + decoration: BoxDecoration( + color: Colors.blue, + borderRadius: BorderRadius.circular(2), + ), + ), + const SizedBox(width: 8), + const Text( + '기존 학생 학년 지정/변경', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const SizedBox(height: 16), + + InkWell( + onTap: _showUpdateGradeDialog, + borderRadius: BorderRadius.circular(24), + child: Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.blue[50], + borderRadius: BorderRadius.circular(24), + border: Border.all(color: Colors.blue.shade200), + ), + child: const Row( + children: [ + Icon(Icons.class_rounded, color: Colors.blue, size: 32), + SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '학번으로 학년 지정', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.blue, + ), + ), + SizedBox(height: 2), + Text( + '이미 만든 계정의 학년을 나중에 지정하거나 바꿀 때 사용', + style: TextStyle( + fontSize: 12, + color: Colors.blueAccent, + ), + ), + ], + ), + ), + Icon( + Icons.arrow_forward_ios_rounded, + color: Colors.blue, + size: 16, + ), + ], + ), + ), + ), + const SizedBox(height: 36), + // 🏷️ 인디케이터 바 2 (삭제 권한 제어메뉴) Row( children: [