// ๐Ÿ“‹ ์„ ์ƒ๋‹˜์šฉ "์Šค๋งˆํŠธ๊ธฐ๊ธฐ ๋ฐ˜์ถœ ๋Œ€์žฅ" ํ™”๋ฉด์˜ ๊ธฐ๋Šฅ(์„œ๋ฒ„ ํ†ต์‹ /์ƒํƒœ) ๋‹ด๋‹น ์ปจํŠธ๋กค๋Ÿฌ. import 'dart:async'; import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import '../config.dart'; class DeviceCheckoutLedgerController extends ChangeNotifier { List _requests = []; bool _isLoading = true; bool _isWorking = false; Timer? _timer; bool _disposed = false; List get requests => _requests; bool get isLoading => _isLoading; bool get isWorking => _isWorking; /// โฐ ์š”์ฒญ ์‹œ๊ฐ„์ด ์–ผ๋งˆ๋‚˜ ๋˜๋Š”์ง€(์‹œ๊ฐ„ ๋‹จ์œ„). ํ˜•์‹์ด ์ด์ƒํ•˜๋ฉด null. double? durationHours(dynamic request) { try { final start = DateTime.parse(request['requestedStart']); final end = DateTime.parse(request['requestedEnd']); final minutes = end.difference(start).inMinutes; return minutes > 0 ? minutes / 60.0 : null; } catch (_) { return null; } } /// ๐Ÿšจ [์žฅ์‹œ๊ฐ„ ์š”์ฒญ ๊ฐ์ง€] 2์‹œ๊ฐ„ ๋„˜๊ฒŒ ์‹ ์ฒญํ•œ, ์•„์ง ๋Œ€๊ธฐ ์ค‘์ธ ์š”์ฒญ ๋ชฉ๋ก. /// ํ•™์ƒ์ด ์‹œ๊ฐ„์„ ๋น„์ •์ƒ์ ์œผ๋กœ ๊ธธ๊ฒŒ ์ ์–ด๋ƒˆ์„ ์ˆ˜ ์žˆ์œผ๋‹ˆ ์ „์ฒด ์Šน์ธ ์ „์— ๋”ฐ๋กœ ํ›‘์–ด๋ณผ ์ˆ˜ ์žˆ๊ฒŒ ํ•œ๋‹ค. List get longPendingRequests => _requests.where((r) { if (r['status'] != 'PENDING') return false; final hours = durationHours(r); return hours != null && hours >= 2; }).toList(); void _safeNotify() { if (!_disposed) notifyListeners(); } void init() { fetchAll(); _timer = Timer.periodic(const Duration(seconds: 5), (_) => fetchAll()); } @override void dispose() { _disposed = true; _timer?.cancel(); super.dispose(); } Future fetchAll() async { try { final response = await http.get( Uri.parse('$baseUrl/api/device-checkout/list'), ); if (response.statusCode == 200) { final data = jsonDecode(utf8.decode(response.bodyBytes)); _requests = data['requests'] ?? []; } } catch (_) { // ์ƒˆ๋กœ๊ณ ์นจ ์‹คํŒจ๋Š” ์กฐ์šฉํžˆ ๋ฌด์‹œํ•˜๊ณ  ๋งˆ์ง€๋ง‰์œผ๋กœ ๋ฐ›์•„์˜จ ๋ชฉ๋ก์„ ์œ ์ง€ํ•œ๋‹ค. } finally { _isLoading = false; _safeNotify(); } } // โœ… ์Šน์ธ ์‹œ ์Šน์ธํ•œ ์„ ์ƒ๋‹˜ ๊ณ„์ •์„ ํ•จ๊ป˜ ๋ณด๋‚ธ๋‹ค (๋Œ€์žฅ์— "OOO ์„ ์ƒ๋‹˜์ด ํ—ˆ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค" ํ‘œ์‹œ + // ๋ฏธ๋ฐ˜๋‚ฉ ์•Œ๋ฆผ์„ ๊ทธ ์„ ์ƒ๋‹˜์—๊ฒŒ๋งŒ ๋ณด๋‚ด๊ธฐ ์œ„ํ•จ). 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, }); } // ๐Ÿ™Œ [์ „์ฒด ํ•™์ƒ ํ—ˆ์šฉ] ๋Œ€๊ธฐ ์ค‘์ธ ์š”์ฒญ์„ ํ•œ ๋ช…์”ฉ ๋ˆ„๋ฅด๊ธฐ ๊ท€์ฐฎ์„ ๋•Œ, ํ•œ ๋ฒˆ์— ์ „๋ถ€ ์Šน์ธํ•œ๋‹ค. // ์„œ๋ฒ„์— ์ผ๊ด„ ์Šน์ธ API๊ฐ€ ๋”ฐ๋กœ ์—†์–ด์„œ, ๋Œ€๊ธฐ ์ค‘์ธ ์š”์ฒญ๋งˆ๋‹ค ์Šน์ธ ์š”์ฒญ์„ ์ˆœ์„œ๋Œ€๋กœ ๋ณด๋‚ธ๋‹ค. Future<(bool success, String message)> approveAllPending({ required String teacherId, required String teacherName, }) async { final pendingIds = _requests .where((r) => r['status'] == 'PENDING') .map((r) => r['id'] as int) .toList(); if (pendingIds.isEmpty) { return (true, '์Šน์ธ ๋Œ€๊ธฐ ์ค‘์ธ ์š”์ฒญ์ด ์—†์Šต๋‹ˆ๋‹ค.'); } _isWorking = true; _safeNotify(); int successCount = 0; for (final id in pendingIds) { try { final response = await http.post( Uri.parse('$baseUrl/api/device-checkout/approve'), headers: {"Content-Type": "application/json"}, body: jsonEncode({ "requestId": id, "teacherId": teacherId, "teacherName": teacherName, }), ); final result = jsonDecode(utf8.decode(response.bodyBytes)); if (response.statusCode == 200 && result['status'] == 'success') { successCount++; } } catch (_) { // ๊ฐœ๋ณ„ ์š”์ฒญ ์‹คํŒจ๋Š” ๊ฑด๋„ˆ๋›ฐ๊ณ  ๋‚˜๋จธ์ง€๋Š” ๊ณ„์† ์ง„ํ–‰ํ•œ๋‹ค. } } await fetchAll(); _isWorking = false; _safeNotify(); final allOk = successCount == pendingIds.length; return ( allOk, '${pendingIds.length}๊ฑด ์ค‘ $successCount๊ฑด ์Šน์ธ ์™„๋ฃŒ${allOk ? '' : ' (์ผ๋ถ€ ์‹คํŒจ)'}', ); } Future<(bool success, String message)> reject(int requestId) async { return _postAction('/api/device-checkout/reject', {"requestId": requestId}); } // ๐Ÿ–๏ธ [ํ•˜๋“œ์›จ์–ด ์ž๋™ ๊ฐ์ง€ ์ „๊นŒ์ง€ ์ž„์‹œ] ์„ ์ƒ๋‹˜์ด ๊ธฐ๊ธฐ๋ฅผ ์‹ค์ œ๋กœ ๋Œ๋ ค๋ฐ›์•˜์„ ๋•Œ ๋ˆ„๋ฅด๋Š” ๋ฒ„ํŠผ. Future<(bool success, String message)> confirmReturn(int requestId) async { return _postAction('/api/device-checkout/return', {"requestId": requestId}); } Future<(bool success, String message)> _postAction( String path, Map body, ) async { _isWorking = true; _safeNotify(); try { final response = await http.post( Uri.parse('$baseUrl$path'), headers: {"Content-Type": "application/json"}, body: jsonEncode(body), ); final result = jsonDecode(utf8.decode(response.bodyBytes)); if (response.statusCode == 200 && result['status'] == 'success') { await fetchAll(); return (true, '${result['message'] ?? '์ฒ˜๋ฆฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.'}'); } return (false, '${result['message'] ?? '์ฒ˜๋ฆฌ ์‹คํŒจ'}'); } catch (e) { return (false, '๋„คํŠธ์›Œํฌ ์—๋Ÿฌ: $e'); } finally { _isWorking = false; _safeNotify(); } } }