import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'package:intl/intl.dart'; import 'dart:math'; import '../../models/event_model.dart'; import '../../services/auth_service.dart'; import '../../services/event_service.dart'; import '../../widgets/loading_indicator.dart'; class EventFormScreen extends StatefulWidget { final Event? event; // null이면 새 이벤트 생성, 아니면 이벤트 수정 const EventFormScreen({ Key? key, this.event, }) : super(key: key); @override State createState() => _EventFormScreenState(); } class _EventFormScreenState extends State { final _formKey = GlobalKey(); bool _isLoading = false; bool _isInit = false; final _currencyFormat = NumberFormat.currency(locale: 'ko_KR', symbol: '₩', decimalDigits: 0); // 폼 필드 컨트롤러 final _titleController = TextEditingController(); final _descriptionController = TextEditingController(); final _locationController = TextEditingController(); final _maxParticipantsController = TextEditingController(); final _gameCountController = TextEditingController(); final _participantFeeController = TextEditingController(); final _publicHashController = TextEditingController(); final _accessPasswordController = TextEditingController(); // 이벤트 데이터 String? _type; String? _status; DateTime _startDate = DateTime.now(); DateTime? _endDate; DateTime? _registrationDeadline; // 이벤트 유형 및 상태 옵션 final List _typeOptions = ['개인전', '팀전', '리그', '토너먼트', '연습', '기타']; final List _statusOptions = ['활성', '대기', '취소', '완료']; // 이벤트 유형 및 상태별 색상 final Map _typeColors = { '개인전': Colors.blue, '팀전': Colors.green, '리그': Colors.purple, '토너먼트': Colors.orange, '연습': Colors.teal, '기타': Colors.grey, }; final Map _statusColors = { '활성': Colors.green, '대기': Colors.amber, '취소': Colors.red, '완료': Colors.blue, }; @override void initState() { super.initState(); // 수정 모드인 경우 기존 데이터 로드 if (widget.event != null) { _titleController.text = widget.event!.title; _descriptionController.text = widget.event!.description ?? ''; _locationController.text = widget.event!.location ?? ''; _maxParticipantsController.text = widget.event!.maxParticipants?.toString() ?? ''; _gameCountController.text = widget.event!.gameCount?.toString() ?? ''; _participantFeeController.text = widget.event!.participantFee?.toString() ?? ''; _publicHashController.text = widget.event!.publicHash ?? ''; _accessPasswordController.text = widget.event!.accessPassword ?? ''; _type = widget.event!.type; _status = widget.event!.status; _startDate = widget.event!.startDate; _endDate = widget.event!.endDate; _registrationDeadline = widget.event!.registrationDeadline; } else { // 새 이벤트 생성 시 기본값 설정 _type = _typeOptions[0]; _status = _statusOptions[0]; _generatePublicHash(); // 새 이벤트는 공개 해시 자동 생성 } } @override void dispose() { _titleController.dispose(); _descriptionController.dispose(); _locationController.dispose(); _maxParticipantsController.dispose(); _gameCountController.dispose(); _participantFeeController.dispose(); _publicHashController.dispose(); _accessPasswordController.dispose(); super.dispose(); } // 공개 URL 해시 생성 void _generatePublicHash() { const chars = 'abcdefghijklmnopqrstuvwxyz0123456789'; final random = Random(); final hash = String.fromCharCodes( Iterable.generate( 8, (_) => chars.codeUnitAt(random.nextInt(chars.length)), ), ); _publicHashController.text = hash; } // 이벤트 저장 Future _saveEvent() async { if (!_formKey.currentState!.validate()) { return; } setState(() { _isLoading = true; }); try { final eventService = Provider.of(context, listen: false); final authService = Provider.of(context, listen: false); // 이벤트 데이터 준비 final eventData = { 'title': _titleController.text.trim(), 'description': _descriptionController.text.trim().isEmpty ? null : _descriptionController.text.trim(), 'location': _locationController.text.trim().isEmpty ? null : _locationController.text.trim(), 'type': _type, 'status': _status, 'startDate': _startDate.toIso8601String(), 'endDate': _endDate?.toIso8601String(), 'maxParticipants': _maxParticipantsController.text.isEmpty ? null : int.parse(_maxParticipantsController.text), 'gameCount': _gameCountController.text.isEmpty ? null : int.parse(_gameCountController.text), 'participantFee': _participantFeeController.text.isEmpty ? null : double.parse(_participantFeeController.text), 'registrationDeadline': _registrationDeadline?.toIso8601String(), 'publicHash': _publicHashController.text.trim().isEmpty ? null : _publicHashController.text.trim(), 'accessPassword': _accessPasswordController.text.trim().isEmpty ? null : _accessPasswordController.text.trim(), }; if (widget.event == null) { // 새 이벤트 생성 await eventService.createEvent(eventData); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('이벤트가 생성되었습니다')), ); } } else { // 기존 이벤트 수정 await eventService.updateEvent(widget.event!.id, eventData); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('이벤트가 수정되었습니다')), ); } } if (mounted) { 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: Text(widget.event == null ? '새 이벤트 생성' : '이벤트 수정'), actions: [ IconButton( icon: const Icon(Icons.save), onPressed: _saveEvent, ), ], ), body: _isLoading ? const LoadingIndicator() : SingleChildScrollView( padding: const EdgeInsets.all(16.0), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 기본 정보 섹션 _buildSectionTitle('기본 정보'), // 제목 TextFormField( controller: _titleController, decoration: const InputDecoration( labelText: '이벤트 제목 *', hintText: '이벤트 제목을 입력하세요', ), validator: (value) { if (value == null || value.trim().isEmpty) { return '이벤트 제목을 입력해주세요'; } return null; }, ), const SizedBox(height: 16), // 설명 TextFormField( controller: _descriptionController, decoration: const InputDecoration( labelText: '이벤트 설명', hintText: '이벤트 설명을 입력하세요', ), maxLines: 3, ), const SizedBox(height: 16), // 이벤트 유형 DropdownButtonFormField( value: _type, decoration: const InputDecoration( labelText: '이벤트 유형', ), items: _typeOptions .map((type) => DropdownMenuItem( value: type, child: Row( children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration( color: _typeColors[type]?.withOpacity(0.2), borderRadius: BorderRadius.circular(12), border: Border.all(color: _typeColors[type] ?? Colors.grey), ), child: Text( type, style: TextStyle(color: _typeColors[type]), ), ), ], ), )) .toList(), onChanged: (value) { setState(() { _type = value; }); }, validator: (value) { if (value == null || value.isEmpty) { return '이벤트 유형을 선택해주세요'; } return null; }, ), const SizedBox(height: 8), if (_type != null) Align( alignment: Alignment.centerLeft, child: Container( margin: const EdgeInsets.only(left: 12), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration( color: _typeColors[_type]?.withOpacity(0.2), borderRadius: BorderRadius.circular(12), border: Border.all(color: _typeColors[_type] ?? Colors.grey), ), child: Text( _type!, style: TextStyle(color: _typeColors[_type], fontSize: 12), ), ), ), const SizedBox(height: 16), // 상태 DropdownButtonFormField( value: _status, decoration: const InputDecoration( labelText: '상태', ), items: _statusOptions .map((status) => DropdownMenuItem( value: status, child: Row( children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration( color: _statusColors[status]?.withOpacity(0.2), borderRadius: BorderRadius.circular(12), border: Border.all(color: _statusColors[status] ?? Colors.grey), ), child: Text( status, style: TextStyle(color: _statusColors[status]), ), ), ], ), )) .toList(), onChanged: (value) { setState(() { _status = value; }); }, validator: (value) { if (value == null || value.isEmpty) { return '상태를 선택해주세요'; } return null; }, ), const SizedBox(height: 8), if (_status != null) Align( alignment: Alignment.centerLeft, child: Container( margin: const EdgeInsets.only(left: 12), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration( color: _statusColors[_status]?.withOpacity(0.2), borderRadius: BorderRadius.circular(12), border: Border.all(color: _statusColors[_status] ?? Colors.grey), ), child: Text( _status!, style: TextStyle(color: _statusColors[_status], fontSize: 12), ), ), ), const SizedBox(height: 24), // 날짜 및 시간 섹션 _buildSectionTitle('날짜 및 시간'), // 시작 날짜 ListTile( title: const Text('시작 날짜 및 시간 *'), subtitle: Text( DateFormat('yyyy년 MM월 dd일 HH:mm').format(_startDate), ), trailing: const Icon(Icons.calendar_today), onTap: () async { final date = await showDatePicker( context: context, initialDate: _startDate, firstDate: DateTime(2020), lastDate: DateTime(2030), ); if (date != null) { final time = await showTimePicker( context: context, initialTime: TimeOfDay.fromDateTime(_startDate), ); if (time != null && mounted) { setState(() { _startDate = DateTime( date.year, date.month, date.day, time.hour, time.minute, ); }); } } }, ), const SizedBox(height: 8), // 종료 날짜 ListTile( title: const Text('종료 날짜 및 시간 (선택)'), subtitle: _endDate != null ? Text( DateFormat('yyyy년 MM월 dd일 HH:mm').format(_endDate!), ) : const Text('설정되지 않음'), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ if (_endDate != null) IconButton( icon: const Icon(Icons.clear), onPressed: () { setState(() { _endDate = null; }); }, ), const Icon(Icons.calendar_today), ], ), onTap: () async { final date = await showDatePicker( context: context, initialDate: _endDate ?? _startDate.add(const Duration(hours: 2)), firstDate: DateTime(2020), lastDate: DateTime(2030), ); if (date != null) { final time = await showTimePicker( context: context, initialTime: _endDate != null ? TimeOfDay.fromDateTime(_endDate!) : TimeOfDay.fromDateTime(_startDate.add(const Duration(hours: 2))), ); if (time != null && mounted) { setState(() { _endDate = DateTime( date.year, date.month, date.day, time.hour, time.minute, ); }); } } }, ), const SizedBox(height: 8), // 등록 마감일 ListTile( title: const Text('등록 마감일 (선택)'), subtitle: _registrationDeadline != null ? Text( DateFormat('yyyy년 MM월 dd일 HH:mm').format(_registrationDeadline!), ) : const Text('설정되지 않음'), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ if (_registrationDeadline != null) IconButton( icon: const Icon(Icons.clear), onPressed: () { setState(() { _registrationDeadline = null; }); }, ), const Icon(Icons.calendar_today), ], ), onTap: () async { final date = await showDatePicker( context: context, initialDate: _registrationDeadline ?? _startDate.subtract(const Duration(days: 1)), firstDate: DateTime(2020), lastDate: DateTime(2030), ); if (date != null) { final time = await showTimePicker( context: context, initialTime: _registrationDeadline != null ? TimeOfDay.fromDateTime(_registrationDeadline!) : TimeOfDay.fromDateTime(_startDate.subtract(const Duration(days: 1))), ); if (time != null && mounted) { setState(() { _registrationDeadline = DateTime( date.year, date.month, date.day, time.hour, time.minute, ); }); } } }, ), const SizedBox(height: 24), // 장소 및 참가자 섹션 _buildSectionTitle('장소 및 참가자'), // 장소 TextFormField( controller: _locationController, decoration: const InputDecoration( labelText: '장소', hintText: '이벤트 장소를 입력하세요', ), ), const SizedBox(height: 16), // 최대 참가자 수 TextFormField( controller: _maxParticipantsController, decoration: const InputDecoration( labelText: '최대 참가자 수', hintText: '최대 참가자 수를 입력하세요', suffixText: '명', ), keyboardType: TextInputType.number, validator: (value) { if (value != null && value.isNotEmpty) { if (int.tryParse(value) == null) { return '숫자를 입력해주세요'; } } return null; }, ), const SizedBox(height: 16), // 게임 수 TextFormField( controller: _gameCountController, decoration: const InputDecoration( labelText: '게임 수', hintText: '게임 수를 입력하세요', suffixText: '게임', ), keyboardType: TextInputType.number, validator: (value) { if (value != null && value.isNotEmpty) { final numValue = int.tryParse(value); if (numValue == null) { return '숫자를 입력해주세요'; } if (numValue <= 0) { return '1 이상의 숫자를 입력해주세요'; } } return null; }, ), const SizedBox(height: 16), // 게임 수 TextFormField( controller: _gameCountController, decoration: const InputDecoration( labelText: '게임 수', hintText: '게임 수를 입력하세요', ), keyboardType: TextInputType.number, validator: (value) { if (value != null && value.isNotEmpty) { if (int.tryParse(value) == null) { return '숫자를 입력해주세요'; } } return null; }, ), const SizedBox(height: 16), // 참가비 TextFormField( controller: _participantFeeController, decoration: const InputDecoration( labelText: '참가비', hintText: '참가비를 입력하세요', prefixText: '₩', ), keyboardType: TextInputType.number, validator: (value) { if (value != null && value.isNotEmpty) { if (double.tryParse(value) == null) { return '숫자를 입력해주세요'; } } return null; }, onChanged: (value) { if (value.isNotEmpty) { final numericValue = double.tryParse(value); if (numericValue != null) { // 입력 중에는 포맷을 적용하지 않음 } } }, ), const SizedBox(height: 4), if (_participantFeeController.text.isNotEmpty && double.tryParse(_participantFeeController.text) != null) Align( alignment: Alignment.centerRight, child: Text( '표시: ${_currencyFormat.format(double.parse(_participantFeeController.text))}', style: TextStyle(color: Colors.grey[600], fontSize: 12), ), ), const SizedBox(height: 24), // 공개 접근 섹션 _buildSectionTitle('공개 접근'), // 공개 URL 해시 Row( children: [ Expanded( child: TextFormField( controller: _publicHashController, decoration: const InputDecoration( labelText: '공개 URL 해시', hintText: '공개 URL 해시를 입력하세요', ), ), ), IconButton( icon: const Icon(Icons.refresh), onPressed: () { setState(() { _generatePublicHash(); }); }, tooltip: '새 해시 생성', ), IconButton( icon: const Icon(Icons.copy), onPressed: () { final publicUrl = 'https://bowling.example.com/events/${_publicHashController.text}'; Clipboard.setData(ClipboardData(text: publicUrl)); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('공개 URL이 클립보드에 복사되었습니다')), ); }, tooltip: 'URL 복사', ), ], ), const SizedBox(height: 4), if (_publicHashController.text.isNotEmpty) Padding( padding: const EdgeInsets.only(left: 12), child: Text( 'https://bowling.example.com/events/${_publicHashController.text}', style: TextStyle(color: Colors.blue[700], fontSize: 12), ), ), const SizedBox(height: 16), // 접근 비밀번호 TextFormField( controller: _accessPasswordController, decoration: const InputDecoration( labelText: '접근 비밀번호 (선택)', hintText: '접근 비밀번호를 입력하세요', ), obscureText: true, ), const SizedBox(height: 32), // 저장 버튼 SizedBox( width: double.infinity, child: ElevatedButton( onPressed: _saveEvent, style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 16), ), child: Text( widget.event == null ? '이벤트 생성' : '이벤트 수정', style: const 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), ], ); } }