// ๐Ÿ“… ๋ฐฑํ’ˆํƒ€ ์ถœ์„ ๋‹ฌ๋ ฅ(๊ฐœ์ธ ์ŠคํŠธ๋ฆญ + ๊ด€๋ฆฌ์ž ์ด๋ฒคํŠธ) ํ™”๋ฉด์˜ ๊ธฐ๋Šฅ ๋‹ด๋‹น ์ปจํŠธ๋กค๋Ÿฌ. import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import '../config.dart'; class StudyCalendarController extends ChangeNotifier { final String studentId; StudyCalendarController({required this.studentId}); bool _isLoading = true; bool _isBusy = false; int _currentStreak = 0; int _longestStreak = 0; Set _studiedDates = {}; Map _events = {}; // date -> {title, description} DateTime _visibleMonth = DateTime.now(); bool _disposed = false; bool get isLoading => _isLoading; bool get isBusy => _isBusy; int get currentStreak => _currentStreak; int get longestStreak => _longestStreak; Set get studiedDates => _studiedDates; Map get events => _events; DateTime get visibleMonth => _visibleMonth; void _safeNotify() { if (!_disposed) notifyListeners(); } @override void dispose() { _disposed = true; super.dispose(); } Future init() => refresh(); Future goToPreviousMonth() async { _visibleMonth = DateTime(_visibleMonth.year, _visibleMonth.month - 1); await refresh(); } Future goToNextMonth() async { _visibleMonth = DateTime(_visibleMonth.year, _visibleMonth.month + 1); await refresh(); } Future refresh() async { _isLoading = true; _safeNotify(); await Future.wait([_fetchStreak(), _fetchEvents()]); _isLoading = false; _safeNotify(); } Future _fetchStreak() async { try { final response = await http.get( Uri.parse( '$baseUrl/api/study/streak?studentId=$studentId' '&year=${_visibleMonth.year}&month=${_visibleMonth.month}', ), ); if (response.statusCode == 200) { final data = jsonDecode(utf8.decode(response.bodyBytes)); _currentStreak = (data['currentStreak'] as num?)?.toInt() ?? 0; _longestStreak = (data['longestStreak'] as num?)?.toInt() ?? 0; _studiedDates = ((data['studiedDates'] as List?) ?? []) .map((d) => d.toString()) .toSet(); } } catch (_) { // ์กฐ์šฉํžˆ ๋ฌด์‹œ } } Future _fetchEvents() async { try { final response = await http.get( Uri.parse( '$baseUrl/api/calendar/events?year=${_visibleMonth.year}&month=${_visibleMonth.month}', ), ); if (response.statusCode == 200) { final data = jsonDecode(utf8.decode(response.bodyBytes)); final List list = data['events'] ?? []; _events = {for (final e in list) e['date']: e}; } } catch (_) { // ์กฐ์šฉํžˆ ๋ฌด์‹œ } } Future<(bool success, String message)> saveEvent({ required String date, required String title, required String description, required String createdBy, }) async { _isBusy = true; _safeNotify(); try { final response = await http.post( Uri.parse('$baseUrl/api/calendar/events'), headers: {"Content-Type": "application/json"}, body: jsonEncode({ "date": date, "title": title.trim(), "description": description.trim(), "createdBy": createdBy, }), ); final result = jsonDecode(utf8.decode(response.bodyBytes)); if (response.statusCode == 200 && result['status'] == 'success') { await _fetchEvents(); return (true, '${result['message'] ?? '๋“ฑ๋กํ–ˆ์Šต๋‹ˆ๋‹ค.'}'); } return (false, '${result['message'] ?? '๋“ฑ๋ก ์‹คํŒจ'}'); } catch (e) { return (false, '๋„คํŠธ์›Œํฌ ์—๋Ÿฌ: $e'); } finally { _isBusy = false; _safeNotify(); } } Future<(bool success, String message)> deleteEvent(String date) async { _isBusy = true; _safeNotify(); try { final response = await http.delete( Uri.parse('$baseUrl/api/calendar/events/$date'), ); final result = jsonDecode(utf8.decode(response.bodyBytes)); if (response.statusCode == 200 && result['status'] == 'success') { await _fetchEvents(); return (true, '${result['message'] ?? '์‚ญ์ œํ–ˆ์Šต๋‹ˆ๋‹ค.'}'); } return (false, '${result['message'] ?? '์‚ญ์ œ ์‹คํŒจ'}'); } catch (e) { return (false, '๋„คํŠธ์›Œํฌ ์—๋Ÿฌ: $e'); } finally { _isBusy = false; _safeNotify(); } } }