스마트기기 반출: 저울 하드웨어 제거, 승인 선생님 지정 알림으로 변경
계획 변경에 따라 저울/아두이노 기반 픽업·반납·무단반출 감지를 없애고 순수 시간 기반 알림만 남김 (백엔드는 별도 저장소에서 이미 배포 완료). - 승인 시 현재 로그인한 선생님의 teacherId/teacherName을 서버에 함께 전송하여 저장하고, 대장에 "OOO 선생님이 허용했습니다" 표시 - 반납 시간 초과 알림은 승인한 선생님에게만 전송 (서버가 해당 선생님의 등록된 기기를 못 찾으면 전체 선생님에게 대체 발송) - 대장 카드의 상태 표시에서 IN_USE/RETURNED(픽업 대기) 문구 제거 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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') {
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user