전체 학생 목록에 학번 검색 + 학년 필터, 1학년부터 정렬 추가
목록을 학년 오름차순(1→2→3, 미배정은 맨 뒤)으로 정렬하고, 학번으로 검색하는 입력창과 전체/1학년/2학년/3학년 필터 칩을 추가. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -38,8 +38,42 @@ class TeacherStudentManagementController extends ChangeNotifier {
|
|||||||
|
|
||||||
List<dynamic> _students = [];
|
List<dynamic> _students = [];
|
||||||
bool _isLoadingStudents = true;
|
bool _isLoadingStudents = true;
|
||||||
|
String _searchQuery = '';
|
||||||
|
int? _gradeFilter; // null = 전체
|
||||||
List<dynamic> get students => _students;
|
List<dynamic> get students => _students;
|
||||||
bool get isLoadingStudents => _isLoadingStudents;
|
bool get isLoadingStudents => _isLoadingStudents;
|
||||||
|
String get searchQuery => _searchQuery;
|
||||||
|
int? get gradeFilter => _gradeFilter;
|
||||||
|
|
||||||
|
/// 🔍 학번 검색 + 학년 필터를 적용하고, 학년 오름차순(1→2→3, 미배정은 맨 뒤)으로
|
||||||
|
/// 정렬한 목록. 화면에는 이 목록을 그린다.
|
||||||
|
List<dynamic> get filteredStudents {
|
||||||
|
final query = _searchQuery.trim();
|
||||||
|
final filtered = _students.where((s) {
|
||||||
|
if (_gradeFilter != null && s['grade'] != _gradeFilter) return false;
|
||||||
|
if (query.isEmpty) return true;
|
||||||
|
return s['id'].toString().contains(query);
|
||||||
|
}).toList();
|
||||||
|
filtered.sort((a, b) {
|
||||||
|
final ga = a['grade'] as int?;
|
||||||
|
final gb = b['grade'] as int?;
|
||||||
|
if (ga == null && gb == null) return 0;
|
||||||
|
if (ga == null) return 1;
|
||||||
|
if (gb == null) return -1;
|
||||||
|
return ga.compareTo(gb);
|
||||||
|
});
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSearchQuery(String query) {
|
||||||
|
_searchQuery = query;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGradeFilter(int? grade) {
|
||||||
|
_gradeFilter = grade;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> init() => fetchStudents();
|
Future<void> init() => fetchStudents();
|
||||||
|
|
||||||
|
|||||||
@@ -358,6 +358,20 @@ class _TeacherStudentManagementPageState
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 🏷️ 섹션 제목 (색 인디케이터 바 + 텍스트).
|
// 🏷️ 섹션 제목 (색 인디케이터 바 + 텍스트).
|
||||||
|
Widget _gradeFilterChip(int? grade, String label) {
|
||||||
|
final bool selected = _controller.gradeFilter == grade;
|
||||||
|
return ChoiceChip(
|
||||||
|
label: Text(label),
|
||||||
|
selected: selected,
|
||||||
|
onSelected: (_) => _controller.setGradeFilter(grade),
|
||||||
|
selectedColor: AppPalette.ink,
|
||||||
|
labelStyle: TextStyle(
|
||||||
|
color: selected ? Colors.white : AppPalette.ink,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _sectionTitle(String text, {Color color = AppPalette.ink}) {
|
Widget _sectionTitle(String text, {Color color = AppPalette.ink}) {
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -753,6 +767,42 @@ class _TeacherStudentManagementPageState
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
onChanged: _controller.setSearchQuery,
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: '학번으로 검색',
|
||||||
|
prefixIcon: const Icon(
|
||||||
|
Icons.search_rounded,
|
||||||
|
),
|
||||||
|
filled: true,
|
||||||
|
fillColor: AppPalette.paper,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 0,
|
||||||
|
),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: AppPalette.sage,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
_gradeFilterChip(null, '전체'),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
_gradeFilterChip(1, '1학년'),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
_gradeFilterChip(2, '2학년'),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
_gradeFilterChip(3, '3학년'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
_controller.isLoadingStudents
|
_controller.isLoadingStudents
|
||||||
? const Padding(
|
? const Padding(
|
||||||
padding: EdgeInsets.symmetric(vertical: 40),
|
padding: EdgeInsets.symmetric(vertical: 40),
|
||||||
@@ -760,13 +810,19 @@ class _TeacherStudentManagementPageState
|
|||||||
child: CircularProgressIndicator(),
|
child: CircularProgressIndicator(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: _controller.students.isEmpty
|
: _controller.filteredStudents.isEmpty
|
||||||
? const Padding(
|
? Padding(
|
||||||
padding: EdgeInsets.symmetric(vertical: 24),
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 24,
|
||||||
|
),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'가입된 학생 계정이 없습니다.',
|
_controller.students.isEmpty
|
||||||
style: TextStyle(color: Colors.grey),
|
? '가입된 학생 계정이 없습니다.'
|
||||||
|
: '검색/필터 조건에 맞는 학생이 없습니다.',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -781,10 +837,11 @@ class _TeacherStudentManagementPageState
|
|||||||
crossAxisSpacing: 12,
|
crossAxisSpacing: 12,
|
||||||
mainAxisSpacing: 12,
|
mainAxisSpacing: 12,
|
||||||
),
|
),
|
||||||
itemCount: _controller.students.length,
|
itemCount:
|
||||||
|
_controller.filteredStudents.length,
|
||||||
itemBuilder: (context, index) =>
|
itemBuilder: (context, index) =>
|
||||||
_buildStudentTile(
|
_buildStudentTile(
|
||||||
_controller.students[index],
|
_controller.filteredStudents[index],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user