import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:lanebow/models/event_model.dart'; import 'package:lanebow/models/score_model.dart'; import 'package:lanebow/models/participant_model.dart'; import 'package:lanebow/models/member_model.dart'; import 'package:lanebow/utils/tie_breaker_util.dart'; import 'package:lanebow/models/tie_breaker_option.dart'; import 'package:lanebow/screens/club/team_generator_screen.dart'; import 'package:lanebow/services/event_service.dart'; import 'package:provider/provider.dart'; /// 테스트용 이벤트 상세 화면 위젯 /// 실제 EventDetailsScreen의 핵심 기능만 구현하여 테스트에 사용 class TestEventDetailsScreen extends StatefulWidget { final Event event; final List scores; final List participants; const TestEventDetailsScreen({ super.key, required this.event, this.scores = const [], this.participants = const [], }); @override State createState() => _TestEventDetailsScreenState(); } class _TestEventDetailsScreenState extends State { bool _includeHandicap = false; List _members = []; bool _isLoading = false; String? _scoreError; // 점수 탭 전용 에러 메시지 상태 String? _participantError; // 참가자 탭 전용 에러 메시지 상태 String _participantFilter = '전체'; // 참가자 필터 드롭다운 상태 // 점수에 따른 색상 반환 Color _getScoreColor(int score) { if (score == 10) { return Colors.red.shade700; // 스트라이크 } else if (score >= 7) { return Colors.orange.shade700; // 좋은 점수 } else if (score >= 5) { return Colors.green.shade700; // 평균 점수 } else { return Colors.blue.shade700; // 낮은 점수 } } // 프레임 표시 문자열 반환 String _getFrameDisplay(int frameIndex, int score, List frames) { // 스트라이크 표시 if (score == 10) { return 'X'; } // 스페어 표시 (이전 프레임과 합쳐서 10점이면 스페어) if (frameIndex > 0 && frames[frameIndex - 1] + score == 10) { return '/'; } // 일반 점수 표시 return score.toString(); } @override void initState() { super.initState(); // 테스트용 멤버 데이터 초기화 _members = widget.scores.map((score) { return Member( id: score.memberId, clubId: score.clubId, name: score.participantName ?? '알 수 없음', birthDate: DateTime(1990, 1, 1), // 테스트용 기본 생년월일 userId: 'test_user_${score.memberId}', email: 'test_${score.memberId}@example.com', isActive: true, ); }).toList(); // 실제 화면과 유사하게 초기 로드시 팀 데이터 조회 호출 // (Provider는 build 이후 사용 가능하므로 microtask로 지연) Future.microtask(() { if (mounted) { final eventService = Provider.of(context, listen: false); eventService.fetchEventTeams(widget.event.id); } }); } // 점수 탭 위젯 구현 Widget _buildScoresTab() { if (widget.scores.isEmpty) { return const Center(child: Text('등록된 점수가 없습니다')); } // TieBreakerUtil을 사용하여 점수 정렬 및 순위 계산 final tieBreakerOptions = [ TieBreakerOption.lowerHandicap, // 핸디캡이 적은 사람 우선 TieBreakerOption.olderAge, // 연장자 우선 TieBreakerOption.lowerScoreGap, // 점수 격차가 적은 사람 우선 ]; final sortedScores = TieBreakerUtil.sortScoresByOptions( widget.scores, tieBreakerOptions, _members, _includeHandicap, ); // 순위 계산 final ranks = TieBreakerUtil.calculateRanks(sortedScores, _includeHandicap); // 에러 상태면 에러 UI 표시 if (_scoreError != null) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Text(_scoreError!, key: const Key('score_error_text')), const SizedBox(height: 8), ElevatedButton( key: const Key('score_retry_button'), onPressed: () async { final eventService = Provider.of( context, listen: false, ); try { setState(() { _scoreError = null; _isLoading = true; }); await eventService.fetchEventScores(widget.event.id); } catch (_) { setState(() { _scoreError = '점수 조회 실패'; }); } finally { if (mounted) { setState(() { _isLoading = false; }); } } }, child: const Text('재시도'), ), ], ), ); } return Column( children: [ // 상단 제어 영역: 새로고침 + 핸디캡 토글 Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Row( children: [ ElevatedButton.icon( key: const Key('score_refresh_button'), onPressed: () async { final eventService = Provider.of( context, listen: false, ); try { setState(() { _scoreError = null; _isLoading = true; }); await eventService.fetchEventScores(widget.event.id); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('점수가 업데이트되었습니다')), ); } } catch (_) { setState(() { _scoreError = '점수 조회 실패'; }); if (context.mounted) { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('점수 갱신 실패'))); } } finally { if (mounted) { setState(() { _isLoading = false; }); } } }, icon: const Icon(Icons.refresh), label: const Text('새로고침'), ), const Spacer(), const Text( '핸디캡 포함:', style: TextStyle(fontWeight: FontWeight.bold), ), Switch( value: _includeHandicap, onChanged: (value) { setState(() { _includeHandicap = value; }); }, ), ], ), ), Expanded( child: ListView.builder( itemCount: sortedScores.length, itemBuilder: (context, index) { final score = sortedScores[index]; final rank = ranks[score.id] ?? (index + 1); final participant = widget.participants.firstWhere( (p) => p.memberId == score.memberId, orElse: () => Participant( id: 'unknown_${score.memberId}', eventId: widget.event.id, memberId: score.memberId, name: '알 수 없음', ), ); return Card( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( width: 24, height: 24, decoration: BoxDecoration( color: Colors.blue.shade700, shape: BoxShape.circle, ), child: Center( child: Text( '$rank', style: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 14, ), ), ), ), const SizedBox(width: 8), Text( participant.name ?? '알 수 없음', style: const TextStyle(fontWeight: FontWeight.bold), ), const Spacer(), Builder( builder: (context) { Future confirmAndDeleteScore() async { final confirmed = await showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('삭제 확인'), content: const Text('정말로 이 점수를 삭제하시겠습니까?'), actions: [ TextButton( onPressed: () => Navigator.pop(ctx, false), child: const Text('취소'), ), TextButton( onPressed: () => Navigator.pop(ctx, true), child: const Text('삭제'), ), ], ), ); if (confirmed == true) { final eventService = Provider.of( context, listen: false, ); try { if (mounted) { setState(() { _isLoading = true; }); } await eventService.fetchEventScores( widget.event.id, ); if (context.mounted) { ScaffoldMessenger.of( context, ).showSnackBar( const SnackBar( content: Text('점수가 삭제되었습니다'), ), ); } } catch (_) { if (context.mounted) { ScaffoldMessenger.of( context, ).showSnackBar( const SnackBar( content: Text('점수 삭제 실패'), ), ); } } finally { if (mounted) { setState(() { _isLoading = false; }); } } } } return Align( alignment: Alignment.topRight, child: IconButton( tooltip: '점수 삭제', icon: const Icon( Icons.delete, color: Colors.redAccent, ), onPressed: confirmAndDeleteScore, ), ); }, ), Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), decoration: BoxDecoration( color: Colors.blue.shade100, borderRadius: BorderRadius.circular(12), ), child: Text( _includeHandicap ? '총점: ${score.totalScore + (score.handicap ?? 0)} (${score.totalScore}+${score.handicap ?? 0})' : '총점: ${score.totalScore}', style: const TextStyle( fontWeight: FontWeight.bold, ), ), ), ], ), const SizedBox(height: 4), SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: score.frames.asMap().entries.map((entry) { return Container( width: 30, height: 30, margin: const EdgeInsets.only(right: 4), decoration: BoxDecoration( color: _getScoreColor(entry.value), borderRadius: BorderRadius.circular(4), ), child: Center( child: Text( _getFrameDisplay( entry.key, entry.value, score.frames, ), style: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold, ), ), ), ); }).toList(), ), ), ], ), ), ); }, ), ), ], ); } // 참가자 탭 위젯 구현 Widget _buildParticipantsTab() { // 에러 상태면 에러 UI 표시 if (_participantError != null) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Text(_participantError!, key: const Key('participant_error_text')), const SizedBox(height: 8), ElevatedButton( key: const Key('participant_retry_button'), onPressed: () async { final service = Provider.of( context, listen: false, ); try { setState(() { _participantError = null; _isLoading = true; }); await service.fetchEventParticipants(widget.event.id); } catch (_) { setState(() { _participantError = '참가자 조회 실패'; }); } finally { if (mounted) { setState(() { _isLoading = false; }); } } }, child: const Text('재시도'), ), ], ), ); } Future openGuestPreInput() async { // 모달을 열고 저장 시 true 반환 final result = await showModalBottomSheet( context: context, builder: (ctx) { return SafeArea( child: Padding( padding: const EdgeInsets.all(16.0), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Text( '게스트 사전입력', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), const Spacer(), IconButton( key: const Key('guest_preinput_close_button'), icon: const Icon(Icons.close), onPressed: () => Navigator.of(ctx).pop(null), ), ], ), const Divider(height: 1), const SizedBox(height: 12), const Text('상태: 등록됨'), const SizedBox(height: 8), const Text('결제: 미납'), const SizedBox(height: 16), Align( alignment: Alignment.centerRight, child: Row( mainAxisSize: MainAxisSize.min, children: [ ElevatedButton( key: const Key('guest_preinput_save_fail_button'), onPressed: () => Navigator.of(ctx).pop(false), child: const Text('저장 실패'), ), const SizedBox(width: 8), ElevatedButton( key: const Key('guest_preinput_save_button'), onPressed: () => Navigator.of(ctx).pop(true), child: const Text('저장'), ), ], ), ), ], ), ), ); }, ); if (!mounted) return; if (result == true) { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('게스트 기본값이 적용되었습니다'))); } else if (result == false) { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('게스트 기본값 적용에 실패했습니다'))); } } // 공통 헤더: 필터 드롭다운 + 새로고침 + 게스트 사전입력 버튼 final header = Padding( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), child: Row( children: [ DropdownButton( key: const Key('participant_filter_dropdown'), value: _participantFilter, items: const [ DropdownMenuItem(value: '전체', child: Text('전체')), DropdownMenuItem(value: '확인됨', child: Text('확인됨')), DropdownMenuItem(value: '대기', child: Text('대기')), DropdownMenuItem(value: '취소', child: Text('취소')), ], onChanged: (value) async { if (value == null) return; setState(() { _participantFilter = value; }); final service = Provider.of(context, listen: false); try { setState(() { _participantError = null; _isLoading = true; }); await service.fetchEventParticipants(widget.event.id); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('참가자가 업데이트되었습니다')), ); } } catch (e) { final msg = e.toString().contains('404') ? '참가자 정보가 없습니다 (404)' : '참가자 조회 실패'; setState(() { _participantError = msg; }); if (context.mounted) { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('참가자 갱신 실패'))); } } finally { if (mounted) { setState(() { _isLoading = false; }); } } }, ), const Spacer(), ElevatedButton.icon( key: const Key('participant_refresh_button'), onPressed: () async { final service = Provider.of(context, listen: false); try { setState(() { _participantError = null; _isLoading = true; }); await service.fetchEventParticipants(widget.event.id); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('참가자가 업데이트되었습니다')), ); } } catch (e) { final msg = e.toString().contains('404') ? '참가자 정보가 없습니다 (404)' : '참가자 조회 실패'; setState(() { _participantError = msg; }); if (context.mounted) { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('참가자 갱신 실패'))); } } finally { if (mounted) { setState(() { _isLoading = false; }); } } }, icon: const Icon(Icons.refresh), label: const Text('새로고침'), ), IconButton( tooltip: '게스트 사전입력', key: const Key('guest_preinput_button'), icon: const Icon(Icons.person_outline), onPressed: openGuestPreInput, ), ], ), ); if (widget.participants.isEmpty) { return Column( children: [ header, const Expanded(child: Center(child: Text('등록된 참가자가 없습니다'))), ], ); } return Column( children: [ header, Expanded( child: ListView.builder( itemCount: widget.participants.length, itemBuilder: (context, index) { final participant = widget.participants[index]; return ListTile( title: Text( participant.name ?? '알 수 없음', key: Key('participant_name_$index'), maxLines: 1, overflow: TextOverflow.ellipsis, ), subtitle: Text('회원 ID: ${participant.memberId}'), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ Builder( builder: (context) { Future confirmAndDeleteParticipant() async { final confirmed = await showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('삭제 확인'), content: Text( '정말로 ${participant.name ?? '참가자'}를 삭제하시겠습니까?', ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx, false), child: const Text('취소'), ), TextButton( onPressed: () => Navigator.pop(ctx, true), child: const Text('삭제'), ), ], ), ); if (confirmed == true) { final service = Provider.of( context, listen: false, ); try { if (mounted) { setState(() { _isLoading = true; }); } // 실제 삭제 호출은 생략하고 재조회로 시뮬레이션 await service.fetchEventParticipants( widget.event.id, ); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('참가자가 삭제되었습니다')), ); } } catch (_) { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('참가자 삭제 실패')), ); } } finally { if (mounted) { setState(() { _isLoading = false; }); } } } } return IconButton( tooltip: '참가자 삭제', icon: const Icon( Icons.delete, color: Colors.redAccent, ), onPressed: confirmAndDeleteParticipant, ); }, ), const Icon(Icons.chevron_right), ], ), ); }, ), ), ], ); } // 팀 탭 위젯 구현 Widget _buildTeamsTab() { // 팀 생성 옵션 상태 관리 final teamSizeController = TextEditingController(text: '3'); final teamCountController = TextEditingController(text: '2'); int selectedOption = 0; // 0: 팀 인원수로 나누기, 1: 팀 개수로 나누기 return StatefulBuilder( builder: (context, setState) { return Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 팀 관리 버튼 (실제 화면과 유사하게 구성) Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ ElevatedButton.icon( onPressed: () async { // TeamGeneratorScreen으로 이동 후 결과 처리 final result = await Navigator.of(context).push( MaterialPageRoute( builder: (_) => TeamGeneratorScreen( eventId: widget.event.id, participants: widget.participants, existingTeams: const [], ), ), ); if (result == true) { // 리프레시: 팀 데이터 재조회 final eventService = Provider.of( context, listen: false, ); await eventService.fetchEventTeams(widget.event.id); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('팀 구성이 업데이트되었습니다')), ); } } }, icon: const Icon(Icons.refresh), label: const Text('팀 재생성'), ), ElevatedButton.icon( onPressed: () {}, icon: const Icon(Icons.save), label: const Text('팀 저장'), ), ], ), const SizedBox(height: 16), // 팀 생성 옵션 카드 Card( child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '팀 생성 옵션', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 16), // 팀 인원수로 나누기 옵션 Row( children: [ Radio( value: 0, groupValue: selectedOption, onChanged: (value) { setState(() { selectedOption = value!; }); }, ), const Text('팀 인원수로 나누기'), ], ), // 팀 개수로 나누기 옵션 Row( children: [ Radio( value: 1, groupValue: selectedOption, onChanged: (value) { setState(() { selectedOption = value!; }); }, ), const Text('팀 개수로 나누기'), ], ), const SizedBox(height: 16), // 선택된 옵션에 따른 입력 필드 if (selectedOption == 0) ...[ // 팀 인원수로 나누기 Row( children: [ const Text('인원수:'), const SizedBox(width: 8), SizedBox( width: 60, child: TextField( controller: teamSizeController, keyboardType: TextInputType.number, decoration: const InputDecoration( border: OutlineInputBorder(), contentPadding: EdgeInsets.symmetric( horizontal: 8, vertical: 8, ), ), ), ), ], ), ] else ...[ // 팀 개수로 나누기 Row( children: [ const Text('팀 수:'), const SizedBox(width: 8), SizedBox( width: 60, child: TextField( controller: teamCountController, keyboardType: TextInputType.number, decoration: const InputDecoration( border: OutlineInputBorder(), contentPadding: EdgeInsets.symmetric( horizontal: 8, vertical: 8, ), ), ), ), ], ), ], const SizedBox(height: 8), // 참가자 수 표시 Text('총 ${widget.participants.length}명 참가자'), const SizedBox(height: 16), // 팀 생성 버튼 ElevatedButton( onPressed: () { // 실제 팀 생성 로직은 생략하고 스낵바만 표시 ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('팀이 생성되었습니다'), duration: Duration(seconds: 2), ), ); }, child: const Text('팀 생성하기'), ), ], ), ), ), const SizedBox(height: 16), // 팀 목록 표시 (테스트용 더미 데이터) const Text( '팀 목록', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), const SizedBox(height: 8), Expanded( child: ListView( children: [ // 팀 A 카드 Card( child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Text( '팀 A', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), const Spacer(), const Text('팀 에버리지: 180'), ], ), const SizedBox(height: 8), const Text('팀원: 참가자 1, 참가자 2, 참가자 3'), ], ), ), ), // 미배정 참가자 섹션 Card( child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '미배정 참가자', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 8), const Text('참가자 4'), const Text('참가자 5'), const Text('참가자 6'), ], ), ), ), ], ), ), ], ), ); }, ); } @override Widget build(BuildContext context) { return DefaultTabController( length: 4, child: Builder( builder: (context) { final tabController = DefaultTabController.of(context); return Scaffold( appBar: AppBar( title: Text(widget.event.title), bottom: const TabBar( tabs: [ Tab(text: '기본 정보'), Tab(text: '참가자'), Tab(text: '점수'), Tab(text: '팀'), ], ), ), body: Stack( children: [ TabBarView( children: [ // 기본 정보 탭 SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 이벤트 유형 및 상태 Row( children: [ Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 6, ), decoration: BoxDecoration( color: Colors.blue, borderRadius: BorderRadius.circular(16), ), child: Text( widget.event.type ?? '기타', style: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold, ), ), ), const SizedBox(width: 8), Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 6, ), decoration: BoxDecoration( color: Colors.green, borderRadius: BorderRadius.circular(16), ), child: Text( widget.event.status ?? '상태 없음', style: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold, ), ), ), ], ), const SizedBox(height: 16), // 공개 URL Container( width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.blue.withValues(alpha: 0.05), borderRadius: BorderRadius.circular(8), border: Border.all( color: Colors.blue.withValues(alpha: 0.3), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon( Icons.link, size: 18, color: Colors.blue, ), const SizedBox(width: 8), const Text( '공개 URL', style: TextStyle( fontWeight: FontWeight.bold, ), ), const Spacer(), ElevatedButton.icon( onPressed: () { final publicUrl = 'https://bowling.example.com/events/${widget.event.publicHash}'; Clipboard.setData( ClipboardData(text: publicUrl), ); ScaffoldMessenger.of( context, ).showSnackBar( SnackBar( content: const Text( '공개 URL이 클립보드에 복사되었습니다', ), action: SnackBarAction( label: '확인', onPressed: () {}, ), duration: const Duration( seconds: 2, ), ), ); }, icon: const Icon(Icons.copy, size: 16), label: const Text('복사'), style: ElevatedButton.styleFrom( backgroundColor: Colors.blue, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 0, ), minimumSize: const Size(0, 32), ), ), ], ), const SizedBox(height: 8), Text( 'https://lanebow.app/events/${widget.event.publicHash}', style: const TextStyle(color: Colors.blue), ), ], ), ), const SizedBox(height: 12), // 접근 비밀번호 if (widget.event.accessPassword != null && widget.event.accessPassword!.isNotEmpty) ...[ Container( width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.orange.withValues(alpha: 0.05), borderRadius: BorderRadius.circular(8), border: Border.all( color: Colors.orange.withValues(alpha: 0.3), ), ), child: Row( children: [ const Icon( Icons.lock, size: 18, color: Colors.orange, ), const SizedBox(width: 8), const Text( '접근 비밀번호: ', style: TextStyle( fontWeight: FontWeight.bold, ), ), Text( widget.event.accessPassword ?? '비밀번호 없음', ), const Spacer(), IconButton( icon: const Icon(Icons.copy, size: 18), onPressed: () { Clipboard.setData( ClipboardData( text: widget.event.accessPassword!, ), ); ScaffoldMessenger.of( context, ).showSnackBar( SnackBar( content: const Text( '비밀번호가 클립보드에 복사되었습니다', ), action: SnackBarAction( label: '확인', onPressed: () {}, ), duration: const Duration(seconds: 2), ), ); }, tooltip: '비밀번호 복사', color: Colors.orange, padding: EdgeInsets.zero, constraints: const BoxConstraints(), ), ], ), ), ], // 공유 버튼 const SizedBox(height: 16), ElevatedButton.icon( onPressed: () { showModalBottomSheet( context: context, builder: (context) => Column( mainAxisSize: MainAxisSize.min, children: [ Padding( padding: const EdgeInsets.all(16.0), child: Row( children: [ const Text( '이벤트 공유', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), const Spacer(), IconButton( icon: const Icon(Icons.close), onPressed: () => Navigator.pop(context), ), ], ), ), const Divider(height: 1), ListTile( title: const Text('기본 정보만 공유'), onTap: () { Navigator.pop(context); }, ), ListTile( title: const Text('참가 링크 포함 공유'), onTap: () { Navigator.pop(context); }, ), ListTile( title: const Text('상세 정보 포함 공유'), onTap: () { Navigator.pop(context); }, ), const SizedBox(height: 16), ], ), ); }, icon: const Icon(Icons.share), label: const Text('이벤트 공유'), style: ElevatedButton.styleFrom( backgroundColor: Colors.green, foregroundColor: Colors.white, ), ), ], ), ), // 참가자 탭 _buildParticipantsTab(), // 점수 탭 _buildScoresTab(), // 팀 탭 _buildTeamsTab(), ], ), if (_isLoading) Positioned.fill( child: Container( key: const Key('loading_overlay'), color: Colors.black38, child: const Center(child: CircularProgressIndicator()), ), ), ], ), floatingActionButton: AnimatedBuilder( animation: tabController, builder: (_, __) { final fab = _buildFabForTab(context, tabController.index); return fab ?? const SizedBox.shrink(); }, ), ); }, ), ); } // 로딩 상태 토글 헬퍼: 외부(top-level)에서 setState 직접 호출 경고를 피하기 위해 제공 void toggleLoading(bool value) { if (!mounted) return; setState(() { _isLoading = value; }); } } Widget? _buildFabForTab(BuildContext context, int tabIndex) { switch (tabIndex) { case 1: // 참가자 return FloatingActionButton( tooltip: '참가자 추가', onPressed: () async { final result = await Navigator.of(context).push( MaterialPageRoute(builder: (_) => const _DummyParticipantForm()), ); if (result == true) { final state = context .findAncestorStateOfType<_TestEventDetailsScreenState>(); if (state == null) return; final service = Provider.of(context, listen: false); try { state.toggleLoading(true); await service.fetchEventParticipants(state.widget.event.id); if (context.mounted) { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('참가자가 업데이트되었습니다'))); } } catch (e) { if (context.mounted) { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('참가자 갱신 실패'))); } } finally { state.toggleLoading(false); } } }, child: const Icon(Icons.person_add), ); case 2: // 점수 return FloatingActionButton( tooltip: '점수 추가', onPressed: () async { final result = await Navigator.of(context).push( MaterialPageRoute(builder: (_) => const _DummyScoreForm()), ); if (result == true) { final state = context .findAncestorStateOfType<_TestEventDetailsScreenState>(); if (state == null) return; final service = Provider.of(context, listen: false); try { state.toggleLoading(true); await service.fetchEventScores(state.widget.event.id); if (context.mounted) { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('점수가 업데이트되었습니다'))); } } catch (e) { if (context.mounted) { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('점수 갱신 실패'))); } } finally { state.toggleLoading(false); } } }, child: const Icon(Icons.add_chart), ); default: return null; } } class _DummyParticipantForm extends StatelessWidget { const _DummyParticipantForm(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('참가자 폼')), body: Center( child: ElevatedButton( onPressed: () => Navigator.of(context).pop(true), child: const Text('저장'), ), ), ); } } class _DummyScoreForm extends StatelessWidget { const _DummyScoreForm(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('점수 폼')), body: Center( child: ElevatedButton( onPressed: () => Navigator.of(context).pop(true), child: const Text('저장'), ), ), ); } }