Line data Source code
1 : import 'package:flutter/foundation.dart';
2 : import 'package:shared_preferences/shared_preferences.dart';
3 :
4 : import '../config/api_config.dart';
5 : import '../models/event_model.dart';
6 : import '../models/participant_model.dart';
7 : import '../models/score_model.dart';
8 : import '../models/team_model.dart';
9 : import './api_service.dart';
10 :
11 : class EventService with ChangeNotifier {
12 : List<Event> _events = [];
13 : Event? _currentEvent;
14 : List<Participant> _participants = [];
15 : List<Score> _scores = [];
16 : List<Team> _teams = [];
17 : bool _isLoading = false;
18 : String? _token;
19 : ApiService _apiService;
20 : String? _clubId;
21 :
22 : // 기본 생성자
23 0 : EventService() : _apiService = ApiService();
24 :
25 : // 테스트용 생성자
26 3 : EventService.forTest(this._apiService);
27 :
28 9 : List<Event> get events => [..._events];
29 0 : Event? get currentEvent => _currentEvent;
30 6 : List<Participant> get participants => [..._participants];
31 6 : List<Score> get scores => [..._scores];
32 3 : List<Team> get teams => [..._teams];
33 0 : bool get isLoading => _isLoading;
34 :
35 : // 초기화 함수
36 3 : Future<void> initialize(String token) async {
37 3 : _token = token;
38 6 : _apiService.setToken(token);
39 : }
40 :
41 : // 클럽 ID 설정
42 3 : void setClubId(String clubId) {
43 3 : _clubId = clubId;
44 3 : notifyListeners();
45 : }
46 :
47 : // 클럽의 모든 이벤트 가져오기
48 3 : Future<void> fetchClubEvents() async {
49 9 : print('fetchClubEvents 호출됨: token=$_token');
50 :
51 3 : _isLoading = true;
52 3 : notifyListeners();
53 :
54 : try {
55 : // 클럽 ID 가져오기
56 3 : final prefs = await SharedPreferences.getInstance();
57 3 : final clubId = prefs.getString(ApiConfig.clubIdKey);
58 :
59 : if (clubId == null) {
60 0 : throw Exception('선택된 클럽이 없습니다');
61 : }
62 :
63 6 : final data = await _apiService.post(
64 : '${ApiConfig.clubs}/events',
65 3 : data: {'clubId': clubId},
66 : );
67 :
68 : // 응답 데이터가 리스트 형태로 직접 왔는지 확인
69 3 : if (data is List) {
70 : final List<dynamic> eventsData = data;
71 1 : _events = eventsData
72 3 : .map((eventData) => Event.fromJson(eventData))
73 1 : .toList();
74 : } else {
75 : // 응답 데이터가 객체 형태로 왔을 경우 events 키를 통해 접근
76 2 : final List<dynamic> eventsData = data['events'] ?? [];
77 2 : _events = eventsData
78 6 : .map((eventData) => Event.fromJson(eventData))
79 2 : .toList();
80 : }
81 :
82 3 : _isLoading = false;
83 3 : notifyListeners();
84 : } catch (e) {
85 1 : _isLoading = false;
86 1 : notifyListeners();
87 2 : throw Exception('이벤트 목록을 불러오는데 실패했습니다: $e');
88 : }
89 : }
90 :
91 : // ID로 이벤트 정보 가져오기
92 2 : Future<Event> fetchEventById(String eventId) async {
93 4 : if (_token == null || _clubId == null) {
94 0 : throw Exception('인증 또는 클럽 정보가 필요합니다');
95 : }
96 :
97 : try {
98 4 : final data = await _apiService.post(
99 : '${ApiConfig.clubs}/events/detail',
100 2 : data: {'eventId': eventId},
101 : );
102 :
103 4 : return Event.fromJson(data['event']);
104 : } catch (e) {
105 2 : throw Exception('이벤트 정보를 불러오는데 실패했습니다: $e');
106 : }
107 : }
108 :
109 : // 이벤트 생성
110 2 : Future<Event> createEvent(Map<String, dynamic> eventData) async {
111 4 : if (_token == null || _clubId == null) {
112 0 : throw Exception('인증 또는 클럽 정보가 필요합니다');
113 : }
114 :
115 2 : _isLoading = true;
116 2 : notifyListeners();
117 :
118 : try {
119 4 : final data = await _apiService.post(
120 : '${ApiConfig.clubs}/events',
121 : data: eventData,
122 : );
123 :
124 4 : final newEvent = Event.fromJson(data['event']);
125 :
126 4 : _events.add(newEvent);
127 :
128 2 : _isLoading = false;
129 2 : notifyListeners();
130 :
131 : return newEvent;
132 : } catch (e) {
133 0 : _isLoading = false;
134 0 : notifyListeners();
135 0 : throw Exception('이벤트 생성에 실패했습니다: $e');
136 : }
137 : }
138 :
139 : // 이벤트 정보 업데이트
140 3 : Future<Event> updateEvent(
141 : String eventId,
142 : Map<String, dynamic> updates,
143 : ) async {
144 6 : if (_token == null || _clubId == null) {
145 0 : throw Exception('인증 또는 클럽 정보가 필요합니다');
146 : }
147 :
148 3 : _isLoading = true;
149 3 : notifyListeners();
150 :
151 : try {
152 6 : final data = await _apiService.put(
153 3 : '${ApiConfig.clubs}/events/$eventId',
154 : data: updates,
155 : );
156 :
157 6 : final updatedEvent = Event.fromJson(data['event']);
158 :
159 : // 이벤트 목록 업데이트
160 9 : final index = _events.indexWhere((event) => event.id == eventId);
161 3 : if (index >= 0) {
162 2 : _events[index] = updatedEvent;
163 : }
164 :
165 3 : _isLoading = false;
166 3 : notifyListeners();
167 :
168 : return updatedEvent;
169 : } catch (e) {
170 0 : _isLoading = false;
171 0 : notifyListeners();
172 0 : throw Exception('이벤트 정보 업데이트에 실패했습니다: $e');
173 : }
174 : }
175 :
176 : // 이벤트 삭제
177 3 : Future<bool> deleteEvent(String eventId) async {
178 6 : if (_token == null || _clubId == null) {
179 0 : throw Exception('인증 또는 클럽 정보가 필요합니다');
180 : }
181 :
182 3 : _isLoading = true;
183 3 : notifyListeners();
184 :
185 : try {
186 9 : await _apiService.delete('${ApiConfig.clubs}/events/$eventId');
187 :
188 : // 이벤트 목록에서 삭제
189 9 : _events.removeWhere((event) => event.id == eventId);
190 :
191 3 : _isLoading = false;
192 3 : notifyListeners();
193 :
194 : return true;
195 : } catch (e) {
196 0 : _isLoading = false;
197 0 : notifyListeners();
198 0 : throw Exception('이벤트 삭제에 실패했습니다: $e');
199 : }
200 : }
201 :
202 : // 참가자 관리 기능
203 : // 이벤트 참가자 목록 가져오기
204 2 : Future<List<Participant>> fetchEventParticipants(String eventId) async {
205 4 : if (_token == null || _clubId == null) {
206 0 : throw Exception('인증 또는 클럽 정보가 필요합니다');
207 : }
208 :
209 2 : _isLoading = true;
210 2 : notifyListeners();
211 :
212 : try {
213 4 : final data = await _apiService.post(
214 : '${ApiConfig.clubs}/events/participants',
215 2 : data: {'eventId': eventId},
216 : );
217 :
218 2 : final List<dynamic> participantsData = data['participants'] ?? [];
219 2 : _participants = participantsData
220 6 : .map((participantData) => Participant.fromJson(participantData))
221 2 : .toList();
222 :
223 2 : _isLoading = false;
224 2 : notifyListeners();
225 :
226 2 : return _participants;
227 : } catch (e) {
228 0 : _isLoading = false;
229 0 : notifyListeners();
230 0 : throw Exception('참가자 목록을 불러오는데 실패했습니다: $e');
231 : }
232 : }
233 :
234 : // 참가자 추가
235 2 : Future<Participant> addParticipant(
236 : String eventId,
237 : Map<String, dynamic> participantData,
238 : ) async {
239 4 : if (_token == null || _clubId == null) {
240 0 : throw Exception('인증 또는 클럽 정보가 필요합니다');
241 : }
242 :
243 2 : _isLoading = true;
244 2 : notifyListeners();
245 :
246 : try {
247 4 : final data = await _apiService.post(
248 2 : '${ApiConfig.clubs}/events/$eventId/participants',
249 : data: participantData,
250 : );
251 :
252 4 : final newParticipant = Participant.fromJson(data['participant']);
253 :
254 4 : _participants.add(newParticipant);
255 :
256 2 : _isLoading = false;
257 2 : notifyListeners();
258 :
259 : return newParticipant;
260 : } catch (e) {
261 0 : _isLoading = false;
262 0 : notifyListeners();
263 0 : throw Exception('참가자 추가에 실패했습니다: $e');
264 : }
265 : }
266 :
267 : // 참가자 정보 업데이트
268 2 : Future<Participant> updateParticipant(
269 : String eventId,
270 : String participantId,
271 : Map<String, dynamic> updates,
272 : ) async {
273 4 : if (_token == null || _clubId == null) {
274 0 : throw Exception('인증 또는 클럽 정보가 필요합니다');
275 : }
276 :
277 2 : _isLoading = true;
278 2 : notifyListeners();
279 :
280 : try {
281 4 : final data = await _apiService.put(
282 2 : '${ApiConfig.clubs}/events/$eventId/participants/$participantId',
283 : data: updates,
284 : );
285 :
286 4 : final updatedParticipant = Participant.fromJson(data['participant']);
287 :
288 : // 참가자 목록 업데이트
289 4 : final index = _participants.indexWhere(
290 3 : (participant) => participant.id == participantId,
291 : );
292 2 : if (index >= 0) {
293 2 : _participants[index] = updatedParticipant;
294 : }
295 :
296 2 : _isLoading = false;
297 2 : notifyListeners();
298 :
299 : return updatedParticipant;
300 : } catch (e) {
301 0 : _isLoading = false;
302 0 : notifyListeners();
303 0 : throw Exception('참가자 정보 업데이트에 실패했습니다: $e');
304 : }
305 : }
306 :
307 : // 참가자 삭제
308 2 : Future<bool> removeParticipant(String eventId, String participantId) async {
309 4 : if (_token == null || _clubId == null) {
310 0 : throw Exception('인증 또는 클럽 정보가 필요합니다');
311 : }
312 :
313 2 : _isLoading = true;
314 2 : notifyListeners();
315 :
316 : try {
317 4 : await _apiService.delete(
318 2 : '${ApiConfig.clubs}/events/$eventId/participants/$participantId',
319 : );
320 :
321 : // 참가자 목록에서 삭제
322 4 : _participants.removeWhere(
323 3 : (participant) => participant.id == participantId,
324 : );
325 :
326 2 : _isLoading = false;
327 2 : notifyListeners();
328 :
329 : return true;
330 : } catch (e) {
331 0 : _isLoading = false;
332 0 : notifyListeners();
333 0 : throw Exception('참가자 삭제에 실패했습니다: $e');
334 : }
335 : }
336 :
337 : // 참가자 상태 업데이트
338 0 : Future<Participant> updateParticipantStatus(
339 : String eventId,
340 : String participantId,
341 : String status,
342 : ) async {
343 0 : return updateParticipant(eventId, participantId, {'status': status});
344 : }
345 :
346 : // 점수 관리 기능
347 : // 이벤트 점수 목록 가져오기
348 2 : Future<List<Score>> fetchEventScores(String eventId) async {
349 4 : if (_token == null || _clubId == null) {
350 0 : throw Exception('인증 또는 클럽 정보가 필요합니다');
351 : }
352 :
353 2 : _isLoading = true;
354 2 : notifyListeners();
355 :
356 : try {
357 4 : final data = await _apiService.post(
358 : '${ApiConfig.clubs}/events/scores',
359 2 : data: {'eventId': eventId},
360 : );
361 :
362 2 : final List<dynamic> scoresData = data['scores'] ?? [];
363 2 : _scores = scoresData
364 6 : .map((scoreData) => Score.fromJson(scoreData))
365 2 : .toList();
366 :
367 2 : _isLoading = false;
368 2 : notifyListeners();
369 :
370 2 : return _scores;
371 : } catch (e) {
372 0 : _isLoading = false;
373 0 : notifyListeners();
374 0 : throw Exception('점수 목록을 불러오는데 실패했습니다: $e');
375 : }
376 : }
377 :
378 : // 점수 추가
379 2 : Future<Score> addScore(String eventId, Map<String, dynamic> scoreData) async {
380 4 : if (_token == null || _clubId == null) {
381 0 : throw Exception('인증 또는 클럽 정보가 필요합니다');
382 : }
383 :
384 2 : _isLoading = true;
385 2 : notifyListeners();
386 :
387 : try {
388 4 : final data = await _apiService.post(
389 2 : '${ApiConfig.clubs}/events/$eventId/scores',
390 : data: scoreData,
391 : );
392 :
393 4 : final newScore = Score.fromJson(data['score']);
394 :
395 4 : _scores.add(newScore);
396 :
397 2 : _isLoading = false;
398 2 : notifyListeners();
399 :
400 : return newScore;
401 : } catch (e) {
402 0 : _isLoading = false;
403 0 : notifyListeners();
404 0 : throw Exception('점수 추가에 실패했습니다: $e');
405 : }
406 : }
407 :
408 : // 점수 업데이트
409 1 : Future<Score> updateScore(
410 : String eventId,
411 : String scoreId,
412 : Map<String, dynamic> updates,
413 : ) async {
414 2 : if (_token == null || _clubId == null) {
415 0 : throw Exception('인증 또는 클럽 정보가 필요합니다');
416 : }
417 :
418 1 : _isLoading = true;
419 1 : notifyListeners();
420 :
421 : try {
422 2 : final data = await _apiService.put(
423 1 : '${ApiConfig.clubs}/events/$eventId/scores/$scoreId',
424 : data: updates,
425 : );
426 :
427 2 : final updatedScore = Score.fromJson(data['score']);
428 :
429 : // 점수 목록 업데이트
430 2 : final index = _scores.indexWhere((score) => score.id == scoreId);
431 1 : if (index >= 0) {
432 0 : _scores[index] = updatedScore;
433 : }
434 :
435 1 : _isLoading = false;
436 1 : notifyListeners();
437 :
438 : return updatedScore;
439 : } catch (e) {
440 0 : _isLoading = false;
441 0 : notifyListeners();
442 0 : throw Exception('점수 업데이트에 실패했습니다: $e');
443 : }
444 : }
445 :
446 : // 점수 삭제
447 1 : Future<bool> deleteScore(String eventId, String scoreId) async {
448 2 : if (_token == null || _clubId == null) {
449 0 : throw Exception('인증 또는 클럽 정보가 필요합니다');
450 : }
451 :
452 1 : _isLoading = true;
453 1 : notifyListeners();
454 :
455 : try {
456 2 : await _apiService.delete(
457 1 : '${ApiConfig.clubs}/events/$eventId/scores/$scoreId',
458 : );
459 :
460 : // 점수 목록에서 삭제
461 2 : _scores.removeWhere((score) => score.id == scoreId);
462 :
463 1 : _isLoading = false;
464 1 : notifyListeners();
465 :
466 : return true;
467 : } catch (e) {
468 0 : _isLoading = false;
469 0 : notifyListeners();
470 0 : throw Exception('점수 삭제에 실패했습니다: $e');
471 : }
472 : }
473 :
474 : // 팀 관리 기능
475 : // 이벤트 팀 목록 가져오기
476 1 : Future<List<Team>> fetchEventTeams(String eventId) async {
477 1 : if (_token == null) {
478 0 : throw Exception('인증 정보가 필요합니다');
479 : }
480 :
481 : try {
482 2 : final data = await _apiService.post(
483 : '${ApiConfig.events}/teams',
484 1 : data: {'eventId': eventId},
485 : );
486 :
487 1 : final List<dynamic> teamsData = data['teams'] ?? [];
488 1 : _teams = teamsData
489 3 : .map((teamData) => Team.fromJson(teamData))
490 1 : .toList();
491 :
492 1 : notifyListeners();
493 1 : return _teams;
494 : } catch (e) {
495 0 : throw Exception('팀 목록을 불러오는데 실패했습니다: $e');
496 : }
497 : }
498 :
499 : // 이벤트에 팀이 있는지 확인 (간략 조회)
500 0 : Future<bool> hasEventTeams(String eventId) async {
501 0 : if (_token == null) {
502 0 : throw Exception('인증 정보가 필요합니다');
503 : }
504 :
505 : try {
506 0 : final data = await _apiService.post(
507 : '${ApiConfig.events}/teams',
508 0 : data: {'eventId': eventId},
509 : );
510 :
511 0 : final List<dynamic> teamsData = data['teams'] ?? [];
512 0 : return teamsData.isNotEmpty;
513 : } catch (e) {
514 0 : print('팀 확인 실패: $e');
515 : return false;
516 : }
517 : }
518 :
519 : // 팀 생성
520 0 : Future<List<Team>> generateTeams(
521 : String eventId,
522 : Map<String, dynamic> teamConfig,
523 : ) async {
524 0 : if (_token == null || _clubId == null) {
525 0 : throw Exception('인증 또는 클럽 정보가 필요합니다');
526 : }
527 :
528 0 : _isLoading = true;
529 0 : notifyListeners();
530 :
531 : try {
532 0 : final data = await _apiService.post(
533 0 : '${ApiConfig.clubs}/events/$eventId/teams/generate',
534 : data: teamConfig,
535 : );
536 :
537 0 : final List<dynamic> teamsData = data['teams'] ?? [];
538 0 : _teams = teamsData.map((teamData) => Team.fromJson(teamData)).toList();
539 :
540 0 : _isLoading = false;
541 0 : notifyListeners();
542 :
543 0 : return _teams;
544 : } catch (e) {
545 0 : _isLoading = false;
546 0 : notifyListeners();
547 0 : throw Exception('팀 생성에 실패했습니다: $e');
548 : }
549 : }
550 :
551 : // 팀 저장
552 1 : Future<bool> saveTeams(String eventId, List<Team> teams) async {
553 2 : if (_token == null || _clubId == null) {
554 0 : throw Exception('인증 또는 클럽 정보가 필요합니다');
555 : }
556 :
557 1 : _isLoading = true;
558 1 : notifyListeners();
559 :
560 : try {
561 2 : await _apiService.post(
562 1 : '${ApiConfig.clubs}/events/$eventId/teams/save',
563 5 : data: {'teams': teams.map((team) => team.toJson()).toList()},
564 : );
565 :
566 1 : _teams = teams;
567 :
568 1 : _isLoading = false;
569 1 : notifyListeners();
570 :
571 : return true;
572 : } catch (e) {
573 0 : _isLoading = false;
574 0 : notifyListeners();
575 0 : throw Exception('팀 저장에 실패했습니다: $e');
576 : }
577 : }
578 :
579 : // 파일에서 이벤트 일괄 생성
580 0 : Future<List<Event>> createEventsFromFile(
581 : List<Map<String, dynamic>> eventsData,
582 : ) async {
583 0 : if (_token == null || _clubId == null) {
584 0 : throw Exception('인증 또는 클럽 정보가 필요합니다');
585 : }
586 :
587 0 : _isLoading = true;
588 0 : notifyListeners();
589 :
590 : try {
591 0 : final List<Event> createdEvents = [];
592 :
593 : // 각 이벤트 데이터에 클럽 ID 추가
594 0 : for (var eventData in eventsData) {
595 0 : eventData['clubId'] = _clubId;
596 :
597 : // null 값 처리 - 빈 문자열을 명시적 null로 변환
598 0 : eventData.forEach((key, value) {
599 0 : if (value is String && value.isEmpty) {
600 0 : eventData[key] = null;
601 : }
602 : });
603 :
604 : // 이벤트 생성 API 호출
605 0 : final data = await _apiService.post(
606 : '${ApiConfig.clubs}/events',
607 : data: eventData,
608 : );
609 :
610 0 : final newEvent = Event.fromJson(data['event']);
611 0 : createdEvents.add(newEvent);
612 0 : _events.add(newEvent);
613 : }
614 :
615 0 : _isLoading = false;
616 0 : notifyListeners();
617 :
618 : return createdEvents;
619 : } catch (e) {
620 0 : _isLoading = false;
621 0 : notifyListeners();
622 0 : throw Exception('파일에서 이벤트 생성에 실패했습니다: $e');
623 : }
624 : }
625 :
626 : // 이벤트 복제
627 1 : Future<Event> cloneEvent(String eventId, {String? newName}) async {
628 2 : if (_token == null || _clubId == null) {
629 0 : throw Exception('인증 또는 클럽 정보가 필요합니다');
630 : }
631 :
632 1 : _isLoading = true;
633 1 : notifyListeners();
634 :
635 : try {
636 : // 원본 이벤트 정보 가져오기
637 1 : final originalEvent = await fetchEventById(eventId);
638 :
639 : // 복제할 이벤트 데이터 준비
640 1 : final Map<String, dynamic> cloneData = {
641 1 : 'clubId': _clubId,
642 0 : 'title': newName ?? '${originalEvent.title} (복사본)',
643 1 : 'description': originalEvent.description,
644 1 : 'location': originalEvent.location,
645 1 : 'type': originalEvent.type,
646 : 'status': 'draft', // 복제된 이벤트는 초안 상태로 시작
647 2 : 'startDate': originalEvent.startDate.toIso8601String(),
648 1 : 'endDate': originalEvent.endDate?.toIso8601String(),
649 1 : 'maxParticipants': originalEvent.maxParticipants,
650 1 : 'participantFee': originalEvent.participantFee,
651 1 : 'gameCount': originalEvent.gameCount,
652 1 : 'publicHash': originalEvent.publicHash,
653 1 : 'accessPassword': originalEvent.accessPassword,
654 : 'isActive': true,
655 : };
656 :
657 : // 이벤트 생성 API 호출
658 2 : final data = await _apiService.post(
659 : '${ApiConfig.clubs}/events',
660 : data: cloneData,
661 : );
662 :
663 2 : final newEvent = Event.fromJson(data['event']);
664 2 : _events.add(newEvent);
665 :
666 1 : _isLoading = false;
667 1 : notifyListeners();
668 :
669 : return newEvent;
670 : } catch (e) {
671 0 : _isLoading = false;
672 0 : notifyListeners();
673 0 : throw Exception('이벤트 복제에 실패했습니다: $e');
674 : }
675 : }
676 : }
|