Files

492 lines
20 KiB
Dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'dart:math';
import '../../models/participant_model.dart';
import '../../models/team_model.dart';
import '../../services/event_service.dart';
import '../../widgets/loading_indicator.dart';
import '../../widgets/dialog_actions.dart';
class TeamGeneratorScreen extends StatefulWidget {
final String eventId;
final List<Participant> participants;
final List<Team>? existingTeams;
const TeamGeneratorScreen({
Key? key,
required this.eventId,
required this.participants,
this.existingTeams,
}) : super(key: key);
@override
State<TeamGeneratorScreen> createState() => _TeamGeneratorScreenState();
}
class _TeamGeneratorScreenState extends State<TeamGeneratorScreen> {
bool _isLoading = false;
int _teamCount = 2;
bool _balanceTeams = true;
List<Team> _generatedTeams = [];
final _formKey = GlobalKey<FormState>();
// 참가자 선택 상태 관리
final Map<String, bool> _selectedParticipants = {};
@override
void initState() {
super.initState();
// 모든 참가자 기본 선택
for (var participant in widget.participants) {
if (participant.status == '참석함' || participant.status == '확인됨') {
_selectedParticipants[participant.memberId] = true;
} else {
_selectedParticipants[participant.memberId] = false;
}
}
// 기존 팀이 있으면 팀 수 설정
if (widget.existingTeams != null && widget.existingTeams!.isNotEmpty) {
_teamCount = widget.existingTeams!.length;
_generatedTeams = List.from(widget.existingTeams!);
}
}
// 팀 생성
void _generateTeams() {
if (!_formKey.currentState!.validate()) {
return;
}
// 선택된 참가자 목록 생성
final selectedParticipants = widget.participants
.where((p) => _selectedParticipants[p.memberId] == true)
.toList();
if (selectedParticipants.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('최소 한 명 이상의 참가자를 선택해주세요')),
);
return;
}
if (_teamCount <= 0 || _teamCount > selectedParticipants.length) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('유효한 팀 수를 입력해주세요')),
);
return;
}
setState(() {
_isLoading = true;
});
try {
// 참가자 섞기
final shuffledParticipants = List.from(selectedParticipants)..shuffle();
// 팀 생성
final teams = List.generate(_teamCount, (index) => <Participant>[]);
if (_balanceTeams) {
// 균등하게 팀 배분
for (int i = 0; i < shuffledParticipants.length; i++) {
teams[i % _teamCount].add(shuffledParticipants[i]);
}
} else {
// 랜덤 배분
final random = Random();
for (var participant in shuffledParticipants) {
teams[random.nextInt(_teamCount)].add(participant);
}
}
// Team 객체로 변환
_generatedTeams = List.generate(
_teamCount,
(index) => Team(
id: widget.existingTeams != null && index < widget.existingTeams!.length
? widget.existingTeams![index].id
: '',
eventId: widget.eventId,
name: '${index + 1}',
memberIds: teams[index].map((p) => p.memberId).toList(),
description: '자동 생성된 팀',
),
);
setState(() {
_isLoading = false;
});
} catch (e) {
setState(() {
_isLoading = false;
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('팀 생성 중 오류가 발생했습니다: $e')),
);
}
}
// 팀 저장
Future<void> _saveTeams() async {
if (_generatedTeams.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('먼저 팀을 생성해주세요')),
);
return;
}
setState(() {
_isLoading = true;
});
try {
final eventService = Provider.of<EventService>(context, listen: false);
// 팀 저장 (서비스는 List<Team>을 기대함)
await eventService.saveTeams(widget.eventId, _generatedTeams);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('팀이 저장되었습니다')),
);
Navigator.of(context).pop(true); // 성공 결과와 함께 이전 화면으로 돌아가기
}
} catch (e) {
if (mounted) {
setState(() {
_isLoading = false;
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('팀 저장에 실패했습니다: $e')),
);
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('팀 생성'),
actions: [
if (_generatedTeams.isNotEmpty)
IconButton(
icon: const Icon(Icons.save),
onPressed: _saveTeams,
),
],
),
body: _isLoading
? const LoadingIndicator()
: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 팀 생성 설정 섹션
_buildSectionTitle('팀 생성 설정'),
// 팀 수 설정
TextFormField(
initialValue: _teamCount.toString(),
decoration: const InputDecoration(
labelText: '팀 수',
hintText: '생성할 팀 수를 입력하세요',
),
keyboardType: TextInputType.number,
validator: (value) {
if (value == null || value.isEmpty) {
return '팀 수를 입력해주세요';
}
final teamCount = int.tryParse(value);
if (teamCount == null || teamCount <= 0) {
return '유효한 팀 수를 입력해주세요';
}
return null;
},
onChanged: (value) {
setState(() {
_teamCount = int.tryParse(value) ?? 2;
});
},
),
const SizedBox(height: 16),
// 균등 배분 설정
SwitchListTile(
title: const Text('균등하게 팀 배분'),
subtitle: const Text('참가자를 팀에 균등하게 배분합니다'),
value: _balanceTeams,
onChanged: (value) {
setState(() {
_balanceTeams = value;
});
},
),
const SizedBox(height: 16),
// 팀 생성 버튼
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _generateTeams,
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
),
child: const Text(
'팀 생성하기',
style: TextStyle(fontSize: 16),
),
),
),
const SizedBox(height: 24),
// 참가자 선택 섹션
_buildSectionTitle('참가자 선택'),
// 전체 선택/해제 버튼
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () {
setState(() {
for (var key in _selectedParticipants.keys) {
_selectedParticipants[key] = true;
}
});
},
child: const Text('전체 선택'),
),
const SizedBox(width: 8),
TextButton(
onPressed: () {
setState(() {
for (var key in _selectedParticipants.keys) {
_selectedParticipants[key] = false;
}
});
},
child: const Text('전체 해제'),
),
],
),
const SizedBox(height: 8),
// 참가자 목록
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: widget.participants.length,
itemBuilder: (context, index) {
final participant = widget.participants[index];
return CheckboxListTile(
title: Text(participant.name ?? '이름 없음'),
subtitle: Text(participant.status ?? '상태 없음'),
value: _selectedParticipants[participant.memberId] ?? false,
onChanged: (value) {
setState(() {
_selectedParticipants[participant.memberId] = value ?? false;
});
},
);
},
),
if (_generatedTeams.isNotEmpty) ...[
const SizedBox(height: 24),
// 생성된 팀 섹션
_buildSectionTitle('생성된 팀'),
// 팀 목록
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: _generatedTeams.length,
itemBuilder: (context, teamIndex) {
final team = _generatedTeams[teamIndex];
// 팀원 목록 생성
final teamMembers = widget.participants
.where((p) => team.memberIds.contains(p.memberId))
.toList();
return Card(
margin: const EdgeInsets.only(bottom: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ListTile(
title: Text(
'팀 ${team.name}',
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
subtitle: Text('${teamMembers.length}명'),
trailing: IconButton(
icon: const Icon(Icons.edit),
onPressed: () {
// 팀 이름 수정 다이얼로그
showDialog(
context: context,
builder: (context) {
String newName = team.name;
return AlertDialog(
title: const Text('팀 이름 수정'),
content: TextField(
autofocus: true,
decoration: const InputDecoration(
labelText: '팀 이름',
),
onChanged: (value) {
newName = value;
},
controller: TextEditingController(text: team.name),
),
actions: DialogActions.confirm(
onCancel: () => Navigator.of(context).pop(),
onConfirm: () {
setState(() {
_generatedTeams[teamIndex] = Team(
id: team.id,
eventId: team.eventId,
name: newName,
memberIds: team.memberIds,
description: team.description,
);
});
Navigator.of(context).pop();
},
confirmText: '저장',
),
);
},
);
},
),
),
const Divider(),
...teamMembers.map((member) => ListTile(
leading: const Icon(Icons.person),
title: Text(member.name ?? '이름 없음'),
trailing: IconButton(
icon: const Icon(Icons.swap_horiz),
onPressed: () {
// 팀원 이동 다이얼로그
showDialog(
context: context,
builder: (context) {
int targetTeamIndex = teamIndex;
return AlertDialog(
title: const Text('팀원 이동'),
content: DropdownButton<int>(
value: targetTeamIndex,
items: List.generate(
_generatedTeams.length,
(index) => DropdownMenuItem(
value: index,
child: Text('팀 ${_generatedTeams[index].name}'),
),
),
onChanged: (value) {
if (value != null) {
targetTeamIndex = value;
}
},
),
actions: DialogActions.confirm(
onCancel: () => Navigator.of(context).pop(),
onConfirm: () {
if (targetTeamIndex != teamIndex) {
setState(() {
_generatedTeams[teamIndex] = Team(
id: team.id,
eventId: team.eventId,
name: team.name,
memberIds: team.memberIds
.where((id) => id != member.memberId)
.toList(),
description: team.description,
);
final targetTeam = _generatedTeams[targetTeamIndex];
_generatedTeams[targetTeamIndex] = Team(
id: targetTeam.id,
eventId: targetTeam.eventId,
name: targetTeam.name,
memberIds: [
...targetTeam.memberIds,
member.memberId,
],
description: targetTeam.description,
);
});
}
Navigator.of(context).pop();
},
confirmText: '이동',
),
);
},
);
},
),
)),
],
),
);
},
),
// 팀 저장 버튼
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _saveTeams,
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
backgroundColor: Colors.green,
),
child: const Text(
'팀 저장하기',
style: TextStyle(fontSize: 16),
),
),
),
],
],
),
),
),
);
}
// 섹션 제목 위젯
Widget _buildSectionTitle(String title) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const Divider(),
const SizedBox(height: 8),
],
);
}
}