From 77688170b71d00838713fd79194add1490f2bcaf Mon Sep 17 00:00:00 2001 From: sihoo Date: Tue, 4 Aug 2026 01:08:48 +0900 Subject: [PATCH] =?UTF-8?q?=ED=95=98=EA=B5=90=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20+=20=EB=AC=B4=EB=8B=A8=EB=B0=98=EC=B6=9C?= =?UTF-8?q?=20=ED=9B=84=20=EC=9E=AC=ED=83=9C=EA=B9=85=20=EC=8B=9C=20?= =?UTF-8?q?=EC=9E=AC=EC=B6=9C=EC=84=9D=20=EC=B2=98=EB=A6=AC=20+=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=9A=A9=20=EA=B0=90=EC=8B=9C?= =?UTF-8?q?=EC=A2=85=EB=A3=8C=20=EB=B2=84=ED=8A=BC=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 선생님 대시보드에 "하교" 버튼 추가: 시간 입력 없이 바로 전체 학생 반출 허용 (실수 클릭 방지를 위해 확인 다이얼로그 한 번 거침) - 무단반출이 감지되면 클라이언트의 감시 상태를 해제해서, 다음 태깅이 체크아웃이 아니라 새 출석으로 처리되도록 수정 - 주머니 감시 화면에 있던 "폰 회수하고 감시 종료 (테스트용)" 버튼 제거 (정식 플로우는 재태깅으로만 체크아웃/재출석 처리) Co-Authored-By: Claude Sonnet 5 --- lib/nfc_poccket_checkin_screen.dart | 20 +++++--------- lib/screens/teacher_attendance_page.dart | 34 ++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/lib/nfc_poccket_checkin_screen.dart b/lib/nfc_poccket_checkin_screen.dart index 12ea5cd..7b13729 100644 --- a/lib/nfc_poccket_checkin_screen.dart +++ b/lib/nfc_poccket_checkin_screen.dart @@ -43,10 +43,14 @@ class _NfcPocketCheckInScreenState extends State { _startNfcSession(); if (_watchServiceSupported) { // 화면이 열려있는 동안은 실시간으로 위반 알림을 받아 팝업을 띄운다. + // 무단 반출이 한 번 감지되면 "신뢰된 감시 세션"은 끝난 것으로 보고, + // 다음 태깅은 체크아웃이 아니라 새 출석(재출석)으로 처리되도록 감시 상태를 해제한다. _violationSub = FlutterBackgroundService().on('violation_detected').listen(( event, ) { - if (mounted) _showViolationDialog(event?['pocketNumber']?.toString()); + if (!mounted) return; + setState(() => _isWatching = false); + _showViolationDialog(event?['pocketNumber']?.toString()); }); } } @@ -354,22 +358,10 @@ class _NfcPocketCheckInScreenState extends State { ), const SizedBox(height: 8), const Text( - "📴 화면을 꺼도 감시는 계속됩니다.\n알림바에서 상태를 확인할 수 있어요.", + "📴 화면을 꺼도 감시는 계속됩니다.\n알림바에서 상태를 확인할 수 있어요.\n(폰을 꺼내 다시 태깅하면 반출 처리됩니다)", textAlign: TextAlign.center, style: TextStyle(color: Colors.white38, fontSize: 13), ), - const SizedBox(height: 40), - OutlinedButton.icon( - onPressed: _checkOut, - icon: const Icon(Icons.logout_rounded, color: Colors.white70), - label: const Text( - "폰 회수하고 감시 종료 (테스트용)", - style: TextStyle(color: Colors.white70), - ), - style: OutlinedButton.styleFrom( - side: const BorderSide(color: Colors.white30), - ), - ), ], ), ), diff --git a/lib/screens/teacher_attendance_page.dart b/lib/screens/teacher_attendance_page.dart index 731b3b9..3629ff4 100644 --- a/lib/screens/teacher_attendance_page.dart +++ b/lib/screens/teacher_attendance_page.dart @@ -205,6 +205,32 @@ class _TeacherAttendancePageState extends State { ); } + /// 🏫 하교 처리: 시간 입력 없이 바로 전체 학생의 반출을 (사실상 무기한) 허용한다. + void _showDismissalConfirmDialog() { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('🏫 하교 처리'), + content: const Text( + '지금부터 모든 학생의 반출이 자동으로 허용되며, 더 이상 무단반출 경고가 뜨지 않습니다.\n하교 처리하시겠습니까?', + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('취소'), + ), + ElevatedButton( + onPressed: () { + Navigator.pop(context); + _setPermissionWindow(720); // 12시간 (사실상 하교~다음날까지 허용) + }, + child: const Text('하교 처리'), + ), + ], + ), + ); + } + /// 오늘 날짜 기준으로 학번별 "가장 최근 출석 기록"만 남긴 맵을 만든다. Map> get _todaysCheckInsByStudentId { final String todayPrefix = DateTime.now().toIso8601String().substring( @@ -303,6 +329,14 @@ class _TeacherAttendancePageState extends State { style: TextStyle(color: Colors.white), ), ), + TextButton.icon( + onPressed: _showDismissalConfirmDialog, + icon: const Icon(Icons.school_rounded, color: Colors.white), + label: const Text( + '하교', + style: TextStyle(color: Colors.white), + ), + ), const SizedBox(width: 8), ], ),