Files
school-attendance/lib/ui/teacher_student_management_page.dart
T
sihooandClaude Sonnet 5 d1c59c521b 선생님 대시보드 학년별 카테고리 분류 + 학생 계정 관리 버그 수정
- 실시간 출석 현황을 학년별 섹션으로 묶어서 표시 (백엔드에 grade 필드 추가)
- 학생 계정 추가 폼에 학년 선택 드롭다운 추가
- 학생 계정 생성/삭제가 실제로는 존재하지 않는 엔드포인트를 호출하던
  버그 수정 (/api/users/register-student, /api/users/delete/{id} →
  /api/users/create, /api/users/delete)
- 학생 대시보드(개발자 모드) 카드 그리드가 넓은 화면에서 과도하게
  커지던 레이아웃 버그 수정, 웹은 5열/폰은 2열로 반응형 처리
- 버전 1.2.0+6

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 16:29:40 +09:00

370 lines
14 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 👥 교사용 학생 계정 관리 화면 (UI 전용). 신규 학생 계정 추가 폼과 강제 삭제 다이얼로그를 그린다.
// 서버 통신/상태는 lib/function/teacher_student_management_controller.dart가 담당한다.
import 'package:flutter/material.dart';
import '../function/teacher_student_management_controller.dart';
// -----------------------------------------------------------------------------
// 👥 [서브 화면 2] 학생 계정 관리 란 (추가 양식 폼 + 강제 삭제 다이얼로그 완전 내장)
// -----------------------------------------------------------------------------
class TeacherStudentManagementPage extends StatefulWidget {
const TeacherStudentManagementPage({super.key});
@override
State<TeacherStudentManagementPage> createState() =>
_TeacherStudentManagementPageState();
}
class _TeacherStudentManagementPageState
extends State<TeacherStudentManagementPage> {
final TeacherStudentManagementController _controller =
TeacherStudentManagementController();
final TextEditingController _addIdController = TextEditingController();
final TextEditingController _addNameController = TextEditingController();
final TextEditingController _addPwController = TextEditingController();
int? _selectedGrade;
@override
void dispose() {
_controller.dispose();
_addIdController.dispose();
_addNameController.dispose();
_addPwController.dispose();
super.dispose();
}
// ➕ [학생 추가 버튼 동작]
Future<void> _addStudentAccount() async {
final sId = _addIdController.text.trim();
final sName = _addNameController.text.trim();
final sPw = _addPwController.text.trim();
if (sId.isEmpty || sName.isEmpty || sPw.isEmpty) {
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('⚠️ 모든 입력란을 채워주세요.')));
return;
}
final (success, message) = await _controller.addStudentAccount(
studentId: sId,
name: sName,
password: sPw,
grade: _selectedGrade,
);
if (!mounted) return;
if (success) {
_addIdController.clear();
_addNameController.clear();
_addPwController.clear();
setState(() => _selectedGrade = null);
}
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(message)));
}
// ❌ [학생 삭제 버튼 동작]
Future<void> _deleteStudentAccount(String studentId) async {
final (_, message) = await _controller.deleteStudentAccount(studentId);
if (!mounted) return;
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(message)));
}
// 🚨 계정 삭제 확인 팝업창 모달
void _showDeleteDialog() {
final TextEditingController deleteIdController = TextEditingController();
showDialog(
context: context,
builder: (context) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
),
title: Row(
children: [
Icon(Icons.warning_amber_rounded, color: Colors.orange[700]),
const SizedBox(width: 10),
const Text(
'학생 계정 강제 삭제',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
),
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
'영구 삭제할 학생의 학번을 정확하게 입력하세요.',
style: TextStyle(color: Colors.black54, fontSize: 13),
),
const SizedBox(height: 16),
TextField(
controller: deleteIdController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
labelText: '학번 입력',
labelStyle: TextStyle(color: Colors.orange[700]),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide(
color: Colors.orange[700]!,
width: 2,
),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
),
prefixIcon: const Icon(Icons.no_accounts_rounded),
),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('취소', style: TextStyle(color: Colors.grey)),
),
ElevatedButton(
onPressed: () {
final inputId = deleteIdController.text.trim();
if (inputId.isNotEmpty) {
Navigator.pop(context);
_deleteStudentAccount(inputId);
}
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.orange[700],
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: const Text(
'삭제 확정',
style: TextStyle(fontWeight: FontWeight.bold),
),
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return ListenableBuilder(
listenable: _controller,
builder: (context, _) {
return Scaffold(
backgroundColor: Colors.grey[100],
appBar: AppBar(
title: const Text(
'⚙️ 학생 통합 관리 센터',
style: TextStyle(fontWeight: FontWeight.bold),
),
backgroundColor: Colors.orange,
foregroundColor: Colors.white,
elevation: 0,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 🏷️ 인디케이터 바 1 (추가 메뉴)
Row(
children: [
Container(
width: 4,
height: 16,
decoration: BoxDecoration(
color: Colors.orange,
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(width: 8),
const Text(
'신규 학생 계정 추가',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 16),
// 📝 학생 추가 컨테이너 폼
Container(
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,
),
],
),
child: Column(
children: [
TextField(
controller: _addIdController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
labelText: '학번',
prefixIcon: Icon(Icons.badge),
),
),
const SizedBox(height: 12),
TextField(
controller: _addNameController,
decoration: const InputDecoration(
labelText: '이름',
prefixIcon: Icon(Icons.person),
),
),
const SizedBox(height: 12),
TextField(
controller: _addPwController,
obscureText: true,
decoration: const InputDecoration(
labelText: '초기 비밀번호',
prefixIcon: Icon(Icons.lock),
),
),
const SizedBox(height: 12),
DropdownButtonFormField<int>(
initialValue: _selectedGrade,
decoration: const InputDecoration(
labelText: '학년 (선택)',
prefixIcon: Icon(Icons.class_),
),
items: const [
DropdownMenuItem(value: 1, child: Text('1학년')),
DropdownMenuItem(value: 2, child: Text('2학년')),
DropdownMenuItem(value: 3, child: Text('3학년')),
],
onChanged: (value) =>
setState(() => _selectedGrade = value),
),
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
onPressed: _controller.isWorking
? null
: _addStudentAccount,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.orange,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: _controller.isWorking
? const CircularProgressIndicator(
color: Colors.white,
)
: const Text(
'학생 등록 완료',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15,
),
),
),
),
],
),
),
const SizedBox(height: 36),
// 🏷️ 인디케이터 바 2 (삭제 권한 제어메뉴)
Row(
children: [
Container(
width: 4,
height: 16,
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(width: 8),
const Text(
'위험 구역 (Account Reset)',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.redAccent,
),
),
],
),
const SizedBox(height: 16),
// 🚨 학생 삭제 트리거 카드
InkWell(
onTap: _showDeleteDialog,
borderRadius: BorderRadius.circular(24),
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.red[50],
borderRadius: BorderRadius.circular(24),
border: Border.all(color: Colors.red.shade200),
),
child: const Row(
children: [
Icon(
Icons.delete_sweep_rounded,
color: Colors.red,
size: 32,
),
SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'학생 계정 강제 원격 삭제',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.red,
),
),
SizedBox(height: 2),
Text(
'인증 초기화 및 DB 제거용',
style: TextStyle(
fontSize: 12,
color: Colors.redAccent,
),
),
],
),
),
Icon(
Icons.arrow_forward_ios_rounded,
color: Colors.red,
size: 16,
),
],
),
),
),
],
),
),
);
},
);
}
}