선생님 대시보드 학년별 카테고리 분류 + 학생 계정 관리 버그 수정

- 실시간 출석 현황을 학년별 섹션으로 묶어서 표시 (백엔드에 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>
This commit is contained in:
2026-08-28 16:29:40 +09:00
co-authored by Claude Sonnet 5
parent 9354e2998a
commit d1c59c521b
6 changed files with 278 additions and 152 deletions
+127 -110
View File
@@ -282,123 +282,140 @@ class _StudentDashboardState extends State<StudentDashboard> {
),
),
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 2,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
childAspectRatio: 0.95,
children: [
// 🆕 [변경 지점] 기기가 일치하면 정상 활성화, 일치하지 않으면 자물쇠Lock 처리
if (isDeveloper || widget.studentId != "2061")
_buildModernCard(
icon: widget.isDeviceMatched
? Icons.contactless_rounded
: Icons.lock_rounded,
title: 'NFC 태그',
subtitle: widget.isDeviceMatched
? '출석 및 폰 수거 완료'
: '⚠️ 본인 인증 기기 전용',
color: widget.isDeviceMatched
? Colors.blue
: Colors.grey[400]!,
onTap: widget.isDeviceMatched
? () => _openPocketCheckIn(context)
: () {
LayoutBuilder(
builder: (context, constraints) {
// 🖥️ 웹(넓은 화면)은 한 줄에 5개씩, 폰(좁은 화면)은 기존 2개 그대로.
final bool isWide = constraints.maxWidth >= 800;
return Center(
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: isWide ? 1300 : 700,
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: isWide ? 5 : 2,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
childAspectRatio: 0.95,
children: [
// 🆕 [변경 지점] 기기가 일치하면 정상 활성화, 일치하지 않으면 자물쇠Lock 처리
if (isDeveloper || widget.studentId != "2061")
_buildModernCard(
icon: widget.isDeviceMatched
? Icons.contactless_rounded
: Icons.lock_rounded,
title: 'NFC 태그',
subtitle: widget.isDeviceMatched
? '출석 및 폰 수거 완료'
: '⚠️ 본인 인증 기기 전용',
color: widget.isDeviceMatched
? Colors.blue
: Colors.grey[400]!,
onTap: widget.isDeviceMatched
? () => _openPocketCheckIn(context)
: () {
ScaffoldMessenger.of(
context,
).showSnackBar(
const SnackBar(
content: Text(
'🚨 대리 출석 방지를 위해 등록된 본인 스마트폰에서만 출석 가능합니다.',
),
),
);
},
isActionButton: widget.isDeviceMatched,
isLoading: _controller.isLoading,
),
// 🆕 [추가 지점] 기기 일치 여부 상관없이 태블릿에서도 누구나 확인 가능한 학교 상황판 카드
_buildModernCard(
icon: Icons.fastfood_rounded,
title: '실시간 학교 상황',
subtitle: '급식실 줄 & 매점 재고 확인',
color: Colors.orange[700]!,
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'🚨 대리 출석 방지를 위해 등록된 본인 스마트폰에서만 출석 가능합니다.',
),
content: Text('🍔 실시간 학교 상황 페이지로 이동합니다.'),
),
);
},
isActionButton: widget.isDeviceMatched,
isLoading: _controller.isLoading,
),
),
// 🆕 [추가 지점] 기기 일치 여부 상관없이 태블릿에서도 누구나 확인 가능한 학교 상황판 카드
_buildModernCard(
icon: Icons.fastfood_rounded,
title: '실시간 학교 상황',
subtitle: '급식실 줄 & 매점 재고 확인',
color: Colors.orange[700]!,
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('🍔 실시간 학교 상황 페이지로 이동합니다.'),
),
);
},
if (isDeveloper)
_buildModernCard(
icon: Icons.monitor_heart_rounded,
title: '실시간 현황',
subtitle: '교사용 수거 모니터링',
color: Colors.teal,
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const TeacherAttendancePage(),
),
),
),
if (isDeveloper)
_buildModernCard(
icon: Icons.terminal_rounded,
title: '서버 DB 제어',
subtitle: '시스템 원격 초기화',
color: Colors.amber[800]!,
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const AdminDashboard(),
),
),
),
if (isDeveloper)
_buildModernCard(
icon: Icons.add_moderator_rounded,
title: '학생 계정 관리',
subtitle: 'UUID 리셋 및 승인',
color: Colors.purple,
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const StudentManagementScreen(),
),
),
),
if (isDeveloper)
_buildModernCard(
icon: Icons.delete_sweep_rounded,
title: '계정 강제 삭제',
subtitle: '학생 및 교사 DB 삭제',
color: Colors.red[600]!,
onTap: () => _showDeleteUserDialog(context),
),
if (isDeveloper)
_buildModernCard(
icon: Icons.edit_note_rounded,
title: 'NFC 태그 쓰기',
subtitle: '주머니 스티커 초기 설정',
color: Colors.deepPurple,
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const NfcTagWriterScreen(),
),
),
),
],
),
),
),
if (isDeveloper)
_buildModernCard(
icon: Icons.monitor_heart_rounded,
title: '실시간 현황',
subtitle: '교사용 수거 모니터링',
color: Colors.teal,
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const TeacherAttendancePage(),
),
),
),
if (isDeveloper)
_buildModernCard(
icon: Icons.terminal_rounded,
title: '서버 DB 제어',
subtitle: '시스템 원격 초기화',
color: Colors.amber[800]!,
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const AdminDashboard(),
),
),
),
if (isDeveloper)
_buildModernCard(
icon: Icons.add_moderator_rounded,
title: '학생 계정 관리',
subtitle: 'UUID 리셋 및 승인',
color: Colors.purple,
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const StudentManagementScreen(),
),
),
),
if (isDeveloper)
_buildModernCard(
icon: Icons.delete_sweep_rounded,
title: '계정 강제 삭제',
subtitle: '학생 및 교사 DB 삭제',
color: Colors.red[600]!,
onTap: () => _showDeleteUserDialog(context),
),
if (isDeveloper)
_buildModernCard(
icon: Icons.edit_note_rounded,
title: 'NFC 태그 쓰기',
subtitle: '주머니 스티커 초기 설정',
color: Colors.deepPurple,
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const NfcTagWriterScreen(),
),
),
),
],
),
);
},
),
const SizedBox(height: 32),
],