스마트기기 반출: 저울 하드웨어 제거, 승인 선생님 지정 알림으로 변경

계획 변경에 따라 저울/아두이노 기반 픽업·반납·무단반출 감지를 없애고
순수 시간 기반 알림만 남김 (백엔드는 별도 저장소에서 이미 배포 완료).

- 승인 시 현재 로그인한 선생님의 teacherId/teacherName을 서버에 함께
  전송하여 저장하고, 대장에 "OOO 선생님이 허용했습니다" 표시
- 반납 시간 초과 알림은 승인한 선생님에게만 전송 (서버가 해당 선생님의
  등록된 기기를 못 찾으면 전체 선생님에게 대체 발송)
- 대장 카드의 상태 표시에서 IN_USE/RETURNED(픽업 대기) 문구 제거

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-01 13:20:28 +09:00
co-authored by Claude Sonnet 5
parent 83cb4a5300
commit 383de8b3a1
3 changed files with 42 additions and 14 deletions
@@ -49,17 +49,27 @@ class DeviceCheckoutLedgerController extends ChangeNotifier {
} }
} }
Future<(bool success, String message)> approve(int requestId) async { // ✅ 승인 시 승인한 선생님 계정을 함께 보낸다 (대장에 "OOO 선생님이 허용했습니다" 표시 +
return _postAction('/api/device-checkout/approve', requestId); // 미반납 알림을 그 선생님에게만 보내기 위함).
Future<(bool success, String message)> approve(
int requestId, {
required String teacherId,
required String teacherName,
}) async {
return _postAction('/api/device-checkout/approve', {
"requestId": requestId,
"teacherId": teacherId,
"teacherName": teacherName,
});
} }
Future<(bool success, String message)> reject(int requestId) async { Future<(bool success, String message)> reject(int requestId) async {
return _postAction('/api/device-checkout/reject', requestId); return _postAction('/api/device-checkout/reject', {"requestId": requestId});
} }
Future<(bool success, String message)> _postAction( Future<(bool success, String message)> _postAction(
String path, String path,
int requestId, Map<String, dynamic> body,
) async { ) async {
_isWorking = true; _isWorking = true;
_safeNotify(); _safeNotify();
@@ -67,7 +77,7 @@ class DeviceCheckoutLedgerController extends ChangeNotifier {
final response = await http.post( final response = await http.post(
Uri.parse('$baseUrl$path'), Uri.parse('$baseUrl$path'),
headers: {"Content-Type": "application/json"}, headers: {"Content-Type": "application/json"},
body: jsonEncode({"requestId": requestId}), body: jsonEncode(body),
); );
final result = jsonDecode(utf8.decode(response.bodyBytes)); final result = jsonDecode(utf8.decode(response.bodyBytes));
if (response.statusCode == 200 && result['status'] == 'success') { if (response.statusCode == 200 && result['status'] == 'success') {
+23 -7
View File
@@ -4,7 +4,10 @@ import 'package:flutter/material.dart';
import '../function/device_checkout_ledger_controller.dart'; import '../function/device_checkout_ledger_controller.dart';
class DeviceCheckoutLedgerPage extends StatefulWidget { class DeviceCheckoutLedgerPage extends StatefulWidget {
const DeviceCheckoutLedgerPage({super.key}); final String? teacherId;
final String? teacherName;
const DeviceCheckoutLedgerPage({super.key, this.teacherId, this.teacherName});
@override @override
State<DeviceCheckoutLedgerPage> createState() => State<DeviceCheckoutLedgerPage> createState() =>
@@ -28,7 +31,11 @@ class _DeviceCheckoutLedgerPageState extends State<DeviceCheckoutLedgerPage> {
} }
Future<void> _approve(int id) async { Future<void> _approve(int id) async {
final (_, message) = await _controller.approve(id); final (_, message) = await _controller.approve(
id,
teacherId: widget.teacherId ?? '간편인증',
teacherName: widget.teacherName ?? '간편인증 선생',
);
if (!mounted) return; if (!mounted) return;
ScaffoldMessenger.of( ScaffoldMessenger.of(
context, context,
@@ -48,11 +55,7 @@ class _DeviceCheckoutLedgerPageState extends State<DeviceCheckoutLedgerPage> {
case 'PENDING': case 'PENDING':
return (color: Colors.orange, label: '⏳ 대기중'); return (color: Colors.orange, label: '⏳ 대기중');
case 'APPROVED': case 'APPROVED':
return (color: Colors.blue, label: '✅ 승인됨 (픽업 대기)'); return (color: Colors.blue, label: '✅ 승인됨');
case 'IN_USE':
return (color: Colors.teal, label: '📱 사용중');
case 'RETURNED':
return (color: Colors.grey, label: '↩️ 반납완료');
case 'REJECTED': case 'REJECTED':
return (color: Colors.red, label: '🚫 거절됨'); return (color: Colors.red, label: '🚫 거절됨');
default: default:
@@ -205,6 +208,19 @@ class _DeviceCheckoutLedgerPageState extends State<DeviceCheckoutLedgerPage> {
fontSize: 13, fontSize: 13,
), ),
), ),
if (status == 'APPROVED' &&
r['approvedByTeacherName'] !=
null) ...[
const SizedBox(height: 2),
Text(
'👤 ${r['approvedByTeacherName']} 선생님이 허용했습니다',
style: TextStyle(
color: Colors.blue[700],
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
],
if (isPending) ...[ if (isPending) ...[
const SizedBox(height: 12), const SizedBox(height: 12),
Row( Row(
+4 -2
View File
@@ -187,8 +187,10 @@ class _TeacherDashboardState extends State<TeacherDashboard> {
onTap: () => Navigator.push( onTap: () => Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => builder: (context) => DeviceCheckoutLedgerPage(
const DeviceCheckoutLedgerPage(), teacherId: widget.teacherId,
teacherName: widget.teacherName,
),
), ),
), ),
), ),