737 lines
20 KiB
Dart
737 lines
20 KiB
Dart
import 'package:flutter/foundation.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
import '../config/api_config.dart';
|
|
import '../models/event_model.dart';
|
|
import '../models/participant_model.dart';
|
|
import '../models/score_model.dart';
|
|
import '../models/team_model.dart';
|
|
import './api_service.dart';
|
|
|
|
class EventService with ChangeNotifier {
|
|
List<Event> _events = [];
|
|
Event? _currentEvent;
|
|
List<Participant> _participants = [];
|
|
List<Score> _scores = [];
|
|
List<Team> _teams = [];
|
|
bool _isLoading = false;
|
|
String? _token;
|
|
final ApiService _apiService;
|
|
String? _clubId;
|
|
|
|
// 기본 생성자
|
|
EventService() : _apiService = ApiService();
|
|
|
|
// 테스트용 생성자
|
|
EventService.forTest(this._apiService);
|
|
|
|
List<Event> get events => [..._events];
|
|
Event? get currentEvent => _currentEvent;
|
|
List<Participant> get participants => [..._participants];
|
|
List<Score> get scores => [..._scores];
|
|
List<Team> get teams => [..._teams];
|
|
bool get isLoading => _isLoading;
|
|
|
|
// 초기화 함수
|
|
Future<void> initialize(String token) async {
|
|
_token = token;
|
|
_apiService.setToken(token);
|
|
}
|
|
|
|
// 클럽 ID 설정
|
|
void setClubId(String clubId) {
|
|
_clubId = clubId;
|
|
notifyListeners();
|
|
}
|
|
|
|
// 클럽의 모든 이벤트 가져오기
|
|
Future<void> fetchClubEvents() async {
|
|
print('fetchClubEvents 호출됨: token=$_token');
|
|
|
|
_isLoading = true;
|
|
notifyListeners();
|
|
|
|
try {
|
|
// 클럽 ID 가져오기
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final clubId = prefs.getString(ApiConfig.clubIdKey);
|
|
|
|
if (clubId == null) {
|
|
throw Exception('선택된 클럽이 없습니다');
|
|
}
|
|
|
|
final data = await _apiService.post(
|
|
'${ApiConfig.clubs}/events',
|
|
data: {'clubId': clubId},
|
|
);
|
|
|
|
// 응답 데이터가 리스트 형태로 직접 왔는지 확인
|
|
if (data is List) {
|
|
final List<dynamic> eventsData = data;
|
|
_events = eventsData
|
|
.map((eventData) => Event.fromJson(eventData))
|
|
.toList();
|
|
} else {
|
|
// 응답 데이터가 객체 형태로 왔을 경우 events 키를 통해 접근
|
|
final List<dynamic> eventsData = data['events'] ?? [];
|
|
_events = eventsData
|
|
.map((eventData) => Event.fromJson(eventData))
|
|
.toList();
|
|
}
|
|
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
} catch (e) {
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
throw Exception('이벤트 목록을 불러오는데 실패했습니다: $e');
|
|
}
|
|
}
|
|
|
|
// ID로 이벤트 정보 가져오기
|
|
Future<Event> fetchEventById(String eventId) async {
|
|
if (_token == null || _clubId == null) {
|
|
throw Exception('인증 또는 클럽 정보가 필요합니다');
|
|
}
|
|
|
|
try {
|
|
final data = await _apiService.post(
|
|
'${ApiConfig.clubs}/events/detail',
|
|
data: {'eventId': eventId},
|
|
);
|
|
|
|
return Event.fromJson(data['event']);
|
|
} catch (e) {
|
|
throw Exception('이벤트 정보를 불러오는데 실패했습니다: $e');
|
|
}
|
|
}
|
|
|
|
// 이벤트 생성
|
|
Future<Event> createEvent(Map<String, dynamic> eventData) async {
|
|
if (_token == null || _clubId == null) {
|
|
throw Exception('인증 또는 클럽 정보가 필요합니다');
|
|
}
|
|
|
|
_isLoading = true;
|
|
notifyListeners();
|
|
|
|
try {
|
|
final data = await _apiService.post(
|
|
'${ApiConfig.clubs}/events',
|
|
data: eventData,
|
|
);
|
|
|
|
final newEvent = Event.fromJson(data['event']);
|
|
|
|
_events.add(newEvent);
|
|
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
|
|
return newEvent;
|
|
} catch (e) {
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
throw Exception('이벤트 생성에 실패했습니다: $e');
|
|
}
|
|
}
|
|
|
|
// 이벤트 정보 업데이트
|
|
Future<Event> updateEvent(
|
|
String eventId,
|
|
Map<String, dynamic> updates,
|
|
) async {
|
|
if (_token == null || _clubId == null) {
|
|
throw Exception('인증 또는 클럽 정보가 필요합니다');
|
|
}
|
|
|
|
_isLoading = true;
|
|
notifyListeners();
|
|
|
|
try {
|
|
final data = await _apiService.put(
|
|
'${ApiConfig.clubs}/events/$eventId',
|
|
data: updates,
|
|
);
|
|
|
|
final updatedEvent = Event.fromJson(data['event']);
|
|
|
|
// 이벤트 목록 업데이트
|
|
final index = _events.indexWhere((event) => event.id == eventId);
|
|
if (index >= 0) {
|
|
_events[index] = updatedEvent;
|
|
}
|
|
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
|
|
return updatedEvent;
|
|
} catch (e) {
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
throw Exception('이벤트 정보 업데이트에 실패했습니다: $e');
|
|
}
|
|
}
|
|
|
|
// 이벤트 삭제
|
|
Future<bool> deleteEvent(String eventId) async {
|
|
if (_token == null || _clubId == null) {
|
|
throw Exception('인증 또는 클럽 정보가 필요합니다');
|
|
}
|
|
|
|
_isLoading = true;
|
|
notifyListeners();
|
|
|
|
try {
|
|
await _apiService.delete('${ApiConfig.clubs}/events/$eventId');
|
|
|
|
// 이벤트 목록에서 삭제
|
|
_events.removeWhere((event) => event.id == eventId);
|
|
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
|
|
return true;
|
|
} catch (e) {
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
throw Exception('이벤트 삭제에 실패했습니다: $e');
|
|
}
|
|
}
|
|
|
|
// 참가자 관리 기능
|
|
// 이벤트 참가자 목록 가져오기
|
|
Future<List<Participant>> fetchEventParticipants(String eventId) async {
|
|
if (_token == null || _clubId == null) {
|
|
throw Exception('인증 또는 클럽 정보가 필요합니다');
|
|
}
|
|
|
|
_isLoading = true;
|
|
notifyListeners();
|
|
|
|
try {
|
|
// userId는 일부 라우트에서 필수이므로 prefs에서 읽어 포함
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final userId = prefs.getString(ApiConfig.userIdKey);
|
|
final payload = {
|
|
'clubId': _clubId,
|
|
if (userId != null) 'userId': userId,
|
|
'eventId': eventId,
|
|
};
|
|
final data = await _apiService.post(
|
|
'${ApiConfig.clubs}/events/$eventId/participants/list',
|
|
data: payload,
|
|
);
|
|
|
|
// 응답이 리스트이거나 객체 내 participants 키로 올 수 있어 모두 처리
|
|
final List<dynamic> participantsData = (data is List)
|
|
? data
|
|
: (data['participants'] ?? []);
|
|
_participants = participantsData
|
|
.map((participantData) => Participant.fromJson(participantData))
|
|
.toList();
|
|
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
|
|
return _participants;
|
|
} catch (e) {
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
throw Exception('참가자 목록을 불러오는데 실패했습니다: $e');
|
|
}
|
|
}
|
|
|
|
// 참가자 추가
|
|
Future<Participant> addParticipant(
|
|
String eventId,
|
|
Map<String, dynamic> participantData,
|
|
) async {
|
|
if (_token == null || _clubId == null) {
|
|
throw Exception('인증 또는 클럽 정보가 필요합니다');
|
|
}
|
|
|
|
_isLoading = true;
|
|
notifyListeners();
|
|
|
|
try {
|
|
// 참가자 페이로드 정규화: 상태/결제 상태 표준화 및 빈 문자열 null 처리
|
|
final normalized = _sanitizeParticipantPayload(participantData);
|
|
final payload = {'clubId': _clubId, ...normalized};
|
|
final data = await _apiService.post(
|
|
'${ApiConfig.clubs}/events/$eventId/participants',
|
|
data: payload,
|
|
);
|
|
|
|
final newParticipant = Participant.fromJson(data['participant']);
|
|
|
|
_participants.add(newParticipant);
|
|
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
|
|
return newParticipant;
|
|
} catch (e) {
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
throw Exception('참가자 추가에 실패했습니다: $e');
|
|
}
|
|
}
|
|
|
|
// 참가자 정보 업데이트
|
|
Future<Participant> updateParticipant(
|
|
String eventId,
|
|
String participantId,
|
|
Map<String, dynamic> updates,
|
|
) async {
|
|
if (_token == null || _clubId == null) {
|
|
throw Exception('인증 또는 클럽 정보가 필요합니다');
|
|
}
|
|
|
|
_isLoading = true;
|
|
notifyListeners();
|
|
|
|
try {
|
|
// 업데이트 페이로드도 동일한 규칙으로 정규화
|
|
final normalized = _sanitizeParticipantPayload(updates);
|
|
final data = await _apiService.put(
|
|
'${ApiConfig.clubs}/events/$eventId/participants/$participantId',
|
|
data: normalized,
|
|
);
|
|
|
|
final updatedParticipant = Participant.fromJson(data['participant']);
|
|
|
|
// 참가자 목록 업데이트
|
|
final index = _participants.indexWhere(
|
|
(participant) => participant.id == participantId,
|
|
);
|
|
if (index >= 0) {
|
|
_participants[index] = updatedParticipant;
|
|
}
|
|
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
|
|
return updatedParticipant;
|
|
} catch (e) {
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
throw Exception('참가자 정보 업데이트에 실패했습니다: $e');
|
|
}
|
|
}
|
|
|
|
// 참가자 페이로드 정규화 헬퍼
|
|
Map<String, dynamic> _sanitizeParticipantPayload(Map<String, dynamic> data) {
|
|
final Map<String, dynamic> out = Map<String, dynamic>.from(data);
|
|
|
|
// 상태 표준화: 구버전/철자 변형 호환
|
|
final statusRaw = (out['status']?.toString().trim() ?? '').toLowerCase();
|
|
if (statusRaw.isNotEmpty) {
|
|
String status = statusRaw;
|
|
if (status == 'cancelled') status = 'canceled';
|
|
// 허용 목록: registered, pending, confirmed, canceled
|
|
if (![
|
|
'registered',
|
|
'pending',
|
|
'confirmed',
|
|
'canceled',
|
|
].contains(status)) {
|
|
// 허용 외 값은 안전하게 pending 처리
|
|
status = 'pending';
|
|
}
|
|
out['status'] = status;
|
|
}
|
|
|
|
// 결제 상태 표준화: isPaid -> paymentStatus 변환 및 강제
|
|
if (out.containsKey('isPaid') &&
|
|
(out['paymentStatus'] == null ||
|
|
(out['paymentStatus'].toString().isEmpty))) {
|
|
final isPaidVal = out['isPaid'] == true;
|
|
out['paymentStatus'] = isPaidVal ? 'paid' : 'unpaid';
|
|
}
|
|
final payRaw = (out['paymentStatus']?.toString().trim() ?? '')
|
|
.toLowerCase();
|
|
if (payRaw.isNotEmpty) {
|
|
out['paymentStatus'] = (payRaw == 'paid') ? 'paid' : 'unpaid';
|
|
}
|
|
// 서버 스키마에 맞추기 위해 isPaid 키는 제거 (서버는 paymentStatus만 사용)
|
|
out.remove('isPaid');
|
|
|
|
// 빈 문자열을 명시적 null로 변환 (이름/이메일/전화/메모 등)
|
|
out.updateAll((key, value) {
|
|
if (value is String && value.trim().isEmpty) return null;
|
|
return value;
|
|
});
|
|
|
|
return out;
|
|
}
|
|
|
|
// 참가자 삭제
|
|
Future<bool> removeParticipant(String eventId, String participantId) async {
|
|
if (_token == null || _clubId == null) {
|
|
throw Exception('인증 또는 클럽 정보가 필요합니다');
|
|
}
|
|
|
|
_isLoading = true;
|
|
notifyListeners();
|
|
|
|
try {
|
|
await _apiService.delete(
|
|
'${ApiConfig.clubs}/events/$eventId/participants/$participantId',
|
|
);
|
|
|
|
// 참가자 목록에서 삭제
|
|
_participants.removeWhere(
|
|
(participant) => participant.id == participantId,
|
|
);
|
|
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
|
|
return true;
|
|
} catch (e) {
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
throw Exception('참가자 삭제에 실패했습니다: $e');
|
|
}
|
|
}
|
|
|
|
// 참가자 상태 업데이트
|
|
Future<Participant> updateParticipantStatus(
|
|
String eventId,
|
|
String participantId,
|
|
String status,
|
|
) async {
|
|
return updateParticipant(eventId, participantId, {'status': status});
|
|
}
|
|
|
|
// 점수 관리 기능
|
|
// 이벤트 점수 목록 가져오기
|
|
Future<List<Score>> fetchEventScores(String eventId) async {
|
|
if (_token == null || _clubId == null) {
|
|
throw Exception('인증 또는 클럽 정보가 필요합니다');
|
|
}
|
|
|
|
_isLoading = true;
|
|
notifyListeners();
|
|
|
|
try {
|
|
final data = await _apiService.post(
|
|
'${ApiConfig.clubs}/events/scores',
|
|
data: {'eventId': eventId},
|
|
);
|
|
|
|
final List<dynamic> scoresData = data['scores'] ?? [];
|
|
_scores = scoresData
|
|
.map((scoreData) => Score.fromJson(scoreData))
|
|
.toList();
|
|
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
|
|
return _scores;
|
|
} catch (e) {
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
throw Exception('점수 목록을 불러오는데 실패했습니다: $e');
|
|
}
|
|
}
|
|
|
|
// 점수 추가
|
|
Future<Score> addScore(String eventId, Map<String, dynamic> scoreData) async {
|
|
if (_token == null || _clubId == null) {
|
|
throw Exception('인증 또는 클럽 정보가 필요합니다');
|
|
}
|
|
|
|
_isLoading = true;
|
|
notifyListeners();
|
|
|
|
try {
|
|
final data = await _apiService.post(
|
|
'${ApiConfig.clubs}/events/$eventId/scores',
|
|
data: scoreData,
|
|
);
|
|
|
|
final newScore = Score.fromJson(data['score']);
|
|
|
|
_scores.add(newScore);
|
|
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
|
|
return newScore;
|
|
} catch (e) {
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
throw Exception('점수 추가에 실패했습니다: $e');
|
|
}
|
|
}
|
|
|
|
// 점수 업데이트
|
|
Future<Score> updateScore(
|
|
String eventId,
|
|
String scoreId,
|
|
Map<String, dynamic> updates,
|
|
) async {
|
|
if (_token == null || _clubId == null) {
|
|
throw Exception('인증 또는 클럽 정보가 필요합니다');
|
|
}
|
|
|
|
_isLoading = true;
|
|
notifyListeners();
|
|
|
|
try {
|
|
final data = await _apiService.put(
|
|
'${ApiConfig.clubs}/events/$eventId/scores/$scoreId',
|
|
data: updates,
|
|
);
|
|
|
|
final updatedScore = Score.fromJson(data['score']);
|
|
|
|
// 점수 목록 업데이트
|
|
final index = _scores.indexWhere((score) => score.id == scoreId);
|
|
if (index >= 0) {
|
|
_scores[index] = updatedScore;
|
|
}
|
|
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
|
|
return updatedScore;
|
|
} catch (e) {
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
throw Exception('점수 업데이트에 실패했습니다: $e');
|
|
}
|
|
}
|
|
|
|
// 점수 삭제
|
|
Future<bool> deleteScore(String eventId, String scoreId) async {
|
|
if (_token == null || _clubId == null) {
|
|
throw Exception('인증 또는 클럽 정보가 필요합니다');
|
|
}
|
|
|
|
_isLoading = true;
|
|
notifyListeners();
|
|
|
|
try {
|
|
await _apiService.delete(
|
|
'${ApiConfig.clubs}/events/$eventId/scores/$scoreId',
|
|
);
|
|
|
|
// 점수 목록에서 삭제
|
|
_scores.removeWhere((score) => score.id == scoreId);
|
|
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
|
|
return true;
|
|
} catch (e) {
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
throw Exception('점수 삭제에 실패했습니다: $e');
|
|
}
|
|
}
|
|
|
|
// 팀 관리 기능
|
|
// 이벤트 팀 목록 가져오기
|
|
Future<List<Team>> fetchEventTeams(String eventId) async {
|
|
if (_token == null) {
|
|
throw Exception('인증 정보가 필요합니다');
|
|
}
|
|
|
|
try {
|
|
final data = await _apiService.post(
|
|
'${ApiConfig.events}/teams',
|
|
data: {'eventId': eventId},
|
|
);
|
|
|
|
final List<dynamic> teamsData = data['teams'] ?? [];
|
|
_teams = teamsData.map((teamData) => Team.fromJson(teamData)).toList();
|
|
|
|
notifyListeners();
|
|
return _teams;
|
|
} catch (e) {
|
|
throw Exception('팀 목록을 불러오는데 실패했습니다: $e');
|
|
}
|
|
}
|
|
|
|
// 이벤트에 팀이 있는지 확인 (간략 조회)
|
|
Future<bool> hasEventTeams(String eventId) async {
|
|
if (_token == null) {
|
|
throw Exception('인증 정보가 필요합니다');
|
|
}
|
|
|
|
try {
|
|
final data = await _apiService.post(
|
|
'${ApiConfig.events}/teams',
|
|
data: {'eventId': eventId},
|
|
);
|
|
|
|
final List<dynamic> teamsData = data['teams'] ?? [];
|
|
return teamsData.isNotEmpty;
|
|
} catch (e) {
|
|
print('팀 확인 실패: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// 팀 생성
|
|
Future<List<Team>> generateTeams(
|
|
String eventId,
|
|
Map<String, dynamic> teamConfig,
|
|
) async {
|
|
if (_token == null || _clubId == null) {
|
|
throw Exception('인증 또는 클럽 정보가 필요합니다');
|
|
}
|
|
|
|
_isLoading = true;
|
|
notifyListeners();
|
|
|
|
try {
|
|
final data = await _apiService.post(
|
|
'${ApiConfig.clubs}/events/$eventId/teams/generate',
|
|
data: teamConfig,
|
|
);
|
|
|
|
final List<dynamic> teamsData = data['teams'] ?? [];
|
|
_teams = teamsData.map((teamData) => Team.fromJson(teamData)).toList();
|
|
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
|
|
return _teams;
|
|
} catch (e) {
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
throw Exception('팀 생성에 실패했습니다: $e');
|
|
}
|
|
}
|
|
|
|
// 팀 저장
|
|
Future<bool> saveTeams(String eventId, List<Team> teams) async {
|
|
if (_token == null || _clubId == null) {
|
|
throw Exception('인증 또는 클럽 정보가 필요합니다');
|
|
}
|
|
|
|
_isLoading = true;
|
|
notifyListeners();
|
|
|
|
try {
|
|
await _apiService.post(
|
|
'${ApiConfig.clubs}/events/$eventId/teams/save',
|
|
data: {'teams': teams.map((team) => team.toJson()).toList()},
|
|
);
|
|
|
|
_teams = teams;
|
|
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
|
|
return true;
|
|
} catch (e) {
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
throw Exception('팀 저장에 실패했습니다: $e');
|
|
}
|
|
}
|
|
|
|
// 파일에서 이벤트 일괄 생성
|
|
Future<List<Event>> createEventsFromFile(
|
|
List<Map<String, dynamic>> eventsData,
|
|
) async {
|
|
if (_token == null || _clubId == null) {
|
|
throw Exception('인증 또는 클럽 정보가 필요합니다');
|
|
}
|
|
|
|
_isLoading = true;
|
|
notifyListeners();
|
|
|
|
try {
|
|
final List<Event> createdEvents = [];
|
|
|
|
// 각 이벤트 데이터에 클럽 ID 추가
|
|
for (var eventData in eventsData) {
|
|
eventData['clubId'] = _clubId;
|
|
|
|
// null 값 처리 - 빈 문자열을 명시적 null로 변환
|
|
eventData.forEach((key, value) {
|
|
if (value is String && value.isEmpty) {
|
|
eventData[key] = null;
|
|
}
|
|
});
|
|
|
|
// 이벤트 생성 API 호출
|
|
final data = await _apiService.post(
|
|
'${ApiConfig.clubs}/events',
|
|
data: eventData,
|
|
);
|
|
|
|
final newEvent = Event.fromJson(data['event']);
|
|
createdEvents.add(newEvent);
|
|
_events.add(newEvent);
|
|
}
|
|
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
|
|
return createdEvents;
|
|
} catch (e) {
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
throw Exception('파일에서 이벤트 생성에 실패했습니다: $e');
|
|
}
|
|
}
|
|
|
|
// 이벤트 복제
|
|
Future<Event> cloneEvent(String eventId, {String? newName}) async {
|
|
if (_token == null || _clubId == null) {
|
|
throw Exception('인증 또는 클럽 정보가 필요합니다');
|
|
}
|
|
|
|
_isLoading = true;
|
|
notifyListeners();
|
|
|
|
try {
|
|
// 원본 이벤트 정보 가져오기
|
|
final originalEvent = await fetchEventById(eventId);
|
|
|
|
// 복제할 이벤트 데이터 준비
|
|
final Map<String, dynamic> cloneData = {
|
|
'clubId': _clubId,
|
|
'title': newName ?? '${originalEvent.title} (복사본)',
|
|
'description': originalEvent.description,
|
|
'location': originalEvent.location,
|
|
'type': originalEvent.type,
|
|
'status': 'draft', // 복제된 이벤트는 초안 상태로 시작
|
|
'startDate': originalEvent.startDate.toIso8601String(),
|
|
'endDate': originalEvent.endDate?.toIso8601String(),
|
|
'maxParticipants': originalEvent.maxParticipants,
|
|
'participantFee': originalEvent.participantFee,
|
|
'gameCount': originalEvent.gameCount,
|
|
'publicHash': originalEvent.publicHash,
|
|
'accessPassword': originalEvent.accessPassword,
|
|
'isActive': true,
|
|
};
|
|
|
|
// 이벤트 생성 API 호출
|
|
final data = await _apiService.post(
|
|
'${ApiConfig.clubs}/events',
|
|
data: cloneData,
|
|
);
|
|
|
|
final newEvent = Event.fromJson(data['event']);
|
|
_events.add(newEvent);
|
|
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
|
|
return newEvent;
|
|
} catch (e) {
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
throw Exception('이벤트 복제에 실패했습니다: $e');
|
|
}
|
|
}
|
|
}
|