LCOV - code coverage report
Current view: top level - lib/screens/club - club_settings_screen.dart Coverage Total Hit
Test: lcov.info Lines: 0.7 % 152 1
Test Date: 2025-08-12 03:21:51 Functions: - 0 0

            Line data    Source code
       1              : import 'package:flutter/material.dart';
       2              : import 'package:provider/provider.dart';
       3              : 
       4              : import '../../models/member_model.dart';
       5              : import '../../services/auth_service.dart';
       6              : import '../../services/club_service.dart';
       7              : import '../../services/member_service.dart';
       8              : import '../../utils/enum_mappings.dart';
       9              : 
      10              : class ClubSettingsScreen extends StatefulWidget {
      11              :   static const routeName = '/club-settings';
      12              : 
      13           24 :   const ClubSettingsScreen({super.key});
      14              : 
      15            0 :   @override
      16            0 :   _ClubSettingsScreenState createState() => _ClubSettingsScreenState();
      17              : }
      18              : 
      19              : class _ClubSettingsScreenState extends State<ClubSettingsScreen> {
      20              :   final _formKey = GlobalKey<FormState>();
      21              :   final _nameController = TextEditingController();
      22              :   final _descriptionController = TextEditingController();
      23              :   final _locationController = TextEditingController();
      24              :   final _femaleHandicapController = TextEditingController();
      25              : 
      26              :   String? _selectedAverageCalculationPeriod;
      27              :   String? _selectedOwnerId;
      28              :   List<Member> _members = [];
      29              :   bool _isLoading = true;
      30              :   bool _hasEditPermission = false;
      31              : 
      32            0 :   @override
      33              :   void initState() {
      34            0 :     super.initState();
      35            0 :     _loadData();
      36              :   }
      37              : 
      38            0 :   @override
      39              :   void dispose() {
      40            0 :     _nameController.dispose();
      41            0 :     _descriptionController.dispose();
      42            0 :     _locationController.dispose();
      43            0 :     _femaleHandicapController.dispose();
      44            0 :     super.dispose();
      45              :   }
      46              : 
      47            0 :   Future<void> _loadData() async {
      48            0 :     setState(() {
      49            0 :       _isLoading = true;
      50              :     });
      51              : 
      52              :     try {
      53              :       // 클럽 정보 로드
      54            0 :       final clubService = Provider.of<ClubService>(context, listen: false);
      55            0 :       final club = clubService.currentClub;
      56              : 
      57              :       if (club != null) {
      58            0 :         _nameController.text = club.name;
      59            0 :         _descriptionController.text = club.description ?? '';
      60            0 :         _locationController.text = club.location ?? '';
      61            0 :         _femaleHandicapController.text = club.femaleHandicap?.toString() ?? '';
      62            0 :         _selectedAverageCalculationPeriod =
      63            0 :             club.averageCalculationPeriod ?? 'total';
      64            0 :         _selectedOwnerId = club.ownerId;
      65              :       }
      66              : 
      67              :       // 회원 목록 로드
      68            0 :       final memberService = Provider.of<MemberService>(context, listen: false);
      69            0 :       await memberService.fetchClubMembers();
      70            0 :       setState(() {
      71            0 :         _members = memberService.members;
      72              :       });
      73              : 
      74              :       // 권한 확인
      75            0 :       final authService = Provider.of<AuthService>(context, listen: false);
      76            0 :       final currentUserId = authService.currentUser?.id;
      77              : 
      78              :       // 현재 사용자가 클럽 소유자이거나 관리자인지 확인
      79              :       if (currentUserId != null) {
      80            0 :         final currentMember = _members.firstWhere(
      81            0 :           (member) => member.userId == currentUserId,
      82            0 :           orElse: () => Member(
      83              :             id: '',
      84              :             userId: '',
      85              :             name: '',
      86              :             memberType: '',
      87            0 :             clubId: club?.id ?? '',
      88              :             email: '',
      89              :             isActive: true,
      90              :           ),
      91              :         );
      92              : 
      93            0 :         setState(() {
      94            0 :           _hasEditPermission =
      95            0 :               club?.ownerId == currentUserId || // 클럽 소유자
      96            0 :               currentMember.memberType == 'manager' || // 클럽 매니저
      97            0 :               authService.currentUser?.role == 'admin'; // 시스템 관리자
      98              :         });
      99              :       }
     100              :     } catch (error) {
     101            0 :       ScaffoldMessenger.of(
     102            0 :         context,
     103            0 :       ).showSnackBar(SnackBar(content: Text('데이터 로드 중 오류가 발생했습니다: $error')));
     104              :     } finally {
     105            0 :       if (mounted) {
     106            0 :         setState(() {
     107            0 :           _isLoading = false;
     108              :         });
     109              :       }
     110              :     }
     111              :   }
     112              : 
     113            0 :   Future<void> _saveClubInfo() async {
     114            0 :     if (!_formKey.currentState!.validate()) {
     115              :       return;
     116              :     }
     117              : 
     118            0 :     setState(() {
     119            0 :       _isLoading = true;
     120              :     });
     121              : 
     122              :     try {
     123            0 :       final clubService = Provider.of<ClubService>(context, listen: false);
     124            0 :       final club = clubService.currentClub;
     125              : 
     126              :       if (club != null) {
     127              :         // 기본 업데이트 데이터 준비
     128            0 :         final Map<String, dynamic> updates = {
     129            0 :           'clubId': club.id,
     130            0 :           'name': _nameController.text.trim(),
     131              :         };
     132              : 
     133              :         // 빈 문자열이 아닌 경우에만 추가 (null 값 명시적 처리)
     134            0 :         if (_descriptionController.text.trim().isNotEmpty) {
     135            0 :           updates['description'] = _descriptionController.text.trim();
     136            0 :         } else if (_descriptionController.text.trim().isEmpty &&
     137            0 :             club.description != null) {
     138              :           // 기존에 값이 있었는데 지금 비어있으면 명시적으로 null 설정
     139            0 :           updates['description'] = null;
     140              :         }
     141              : 
     142            0 :         if (_locationController.text.trim().isNotEmpty) {
     143            0 :           updates['location'] = _locationController.text.trim();
     144            0 :         } else if (_locationController.text.trim().isEmpty &&
     145            0 :             club.location != null) {
     146            0 :           updates['location'] = null;
     147              :         }
     148              : 
     149            0 :         if (_femaleHandicapController.text.trim().isNotEmpty) {
     150            0 :           updates['femaleHandicap'] = int.parse(
     151            0 :             _femaleHandicapController.text.trim(),
     152              :           );
     153              :         }
     154              : 
     155            0 :         if (_selectedAverageCalculationPeriod != null) {
     156            0 :           updates['averageCalculationPeriod'] =
     157            0 :               _selectedAverageCalculationPeriod;
     158              :         }
     159              : 
     160            0 :         if (_selectedOwnerId != null) {
     161            0 :           updates['ownerId'] = _selectedOwnerId;
     162              :         }
     163              : 
     164              :         // 모임장 변경 시에만 이메일 정보 추가
     165            0 :         if (_selectedOwnerId != club.ownerId) {
     166              :           // 모임장이 변경된 경우 해당 회원의 이메일 정보 찾기
     167            0 :           final selectedMember = _members.firstWhere(
     168            0 :             (member) => member.userId == _selectedOwnerId,
     169            0 :             orElse: () => Member(
     170              :               id: '',
     171              :               userId: '',
     172              :               name: '',
     173              :               memberType: '',
     174            0 :               clubId: club.id,
     175              :               email: '',
     176              :               isActive: true,
     177              :             ),
     178              :           );
     179              : 
     180              :           // 이메일이 있는 경우에만 추가 (빈 문자열이나 null은 전송하지 않음)
     181            0 :           if (selectedMember.email.isNotEmpty) {
     182            0 :             updates['ownerEmail'] = selectedMember.email;
     183              :           }
     184              :         }
     185              : 
     186            0 :         await clubService.updateClub(club.id, updates);
     187              : 
     188            0 :         if (context.mounted) {
     189            0 :           ScaffoldMessenger.of(
     190            0 :             context,
     191            0 :           ).showSnackBar(const SnackBar(content: Text('클럽 정보가 성공적으로 저장되었습니다')));
     192              :         }
     193              :       }
     194              :     } catch (error) {
     195            0 :       if (context.mounted) {
     196            0 :         ScaffoldMessenger.of(context).showSnackBar(
     197            0 :           SnackBar(content: Text('클럽 정보 저장 중 오류가 발생했습니다: $error')),
     198              :         );
     199              :       }
     200              :     } finally {
     201            0 :       if (mounted) {
     202            0 :         setState(() {
     203            0 :           _isLoading = false;
     204              :         });
     205              :       }
     206              :     }
     207              :   }
     208              : 
     209            0 :   @override
     210              :   Widget build(BuildContext context) {
     211            0 :     return Scaffold(
     212            0 :       appBar: AppBar(title: const Text('클럽 설정')),
     213            0 :       body: _isLoading
     214              :           ? const Center(child: CircularProgressIndicator())
     215            0 :           : Column(
     216            0 :               children: [
     217            0 :                 if (!_hasEditPermission)
     218            0 :                   Container(
     219              :                     width: double.infinity,
     220              :                     padding: const EdgeInsets.all(16.0),
     221            0 :                     color: Colors.amber.shade100,
     222              :                     child: const Text(
     223              :                       '권한 알림: 클럽 설정을 변경하려면 클럽 소유자 또는 관리자 권한이 필요합니다.',
     224              :                       style: TextStyle(fontWeight: FontWeight.bold),
     225              :                     ),
     226              :                   ),
     227            0 :                 Expanded(
     228            0 :                   child: SingleChildScrollView(
     229              :                     padding: const EdgeInsets.all(16.0),
     230            0 :                     child: Form(
     231            0 :                       key: _formKey,
     232            0 :                       child: Column(
     233              :                         crossAxisAlignment: CrossAxisAlignment.start,
     234            0 :                         children: [
     235              :                           // 클럽 이름
     236            0 :                           TextFormField(
     237            0 :                             controller: _nameController,
     238              :                             decoration: const InputDecoration(
     239              :                               labelText: '클럽 이름',
     240              :                               border: OutlineInputBorder(),
     241              :                             ),
     242            0 :                             validator: (value) {
     243            0 :                               if (value == null || value.trim().isEmpty) {
     244              :                                 return '클럽 이름을 입력해주세요';
     245              :                               }
     246              :                               return null;
     247              :                             },
     248            0 :                             enabled: _hasEditPermission,
     249              :                           ),
     250              :                           // 설명
     251              :                           const SizedBox(height: 16),
     252            0 :                           TextFormField(
     253            0 :                             controller: _descriptionController,
     254              :                             decoration: const InputDecoration(
     255              :                               labelText: '설명',
     256              :                               border: OutlineInputBorder(),
     257              :                             ),
     258              :                             maxLines: 3,
     259            0 :                             enabled: _hasEditPermission,
     260              :                           ),
     261              :                           // 위치
     262              :                           const SizedBox(height: 16),
     263            0 :                           TextFormField(
     264            0 :                             controller: _locationController,
     265              :                             decoration: const InputDecoration(
     266              :                               labelText: '위치',
     267              :                               border: OutlineInputBorder(),
     268              :                             ),
     269            0 :                             enabled: _hasEditPermission,
     270              :                           ),
     271              :                           // 여성 기본 핸디캡
     272              :                           const SizedBox(height: 16),
     273            0 :                           TextFormField(
     274            0 :                             controller: _femaleHandicapController,
     275              :                             decoration: const InputDecoration(
     276              :                               labelText: '여성 기본 핸디캡',
     277              :                               border: OutlineInputBorder(),
     278              :                             ),
     279              :                             keyboardType: TextInputType.number,
     280            0 :                             validator: (value) {
     281            0 :                               if (value != null && value.isNotEmpty) {
     282            0 :                                 final handicap = int.tryParse(value);
     283              :                                 if (handicap == null) {
     284              :                                   return '숫자를 입력해주세요';
     285              :                                 }
     286            0 :                                 if (handicap < 0) {
     287              :                                   return '0 이상의 값을 입력해주세요';
     288              :                                 }
     289              :                               }
     290              :                               return null;
     291              :                             },
     292            0 :                             enabled: _hasEditPermission,
     293              :                           ),
     294              :                           // 평균 산정 기준
     295              :                           const SizedBox(height: 16),
     296            0 :                           DropdownButtonFormField<String>(
     297              :                             decoration: const InputDecoration(
     298              :                               labelText: '평균 산정 기준',
     299              :                               border: OutlineInputBorder(),
     300              :                             ),
     301            0 :                             value: _selectedAverageCalculationPeriod,
     302            0 :                             items: averageCalculationPeriodOptions.entries
     303            0 :                                 .map(
     304            0 :                                   (entry) => DropdownMenuItem<String>(
     305            0 :                                     value: entry.key,
     306            0 :                                     child: Text(entry.value),
     307              :                                   ),
     308              :                                 )
     309            0 :                                 .toList(),
     310            0 :                             onChanged: _hasEditPermission
     311            0 :                                 ? (value) {
     312            0 :                                     setState(() {
     313            0 :                                       _selectedAverageCalculationPeriod = value;
     314              :                                     });
     315              :                                   }
     316              :                                 : null,
     317              :                           ),
     318              :                           // 모임장 설정
     319              :                           const SizedBox(height: 16),
     320            0 :                           DropdownButtonFormField<String>(
     321              :                             decoration: const InputDecoration(
     322              :                               labelText: '모임장 설정',
     323              :                               border: OutlineInputBorder(),
     324              :                             ),
     325            0 :                             value: _selectedOwnerId,
     326            0 :                             items: _members.map((member) {
     327            0 :                               return DropdownMenuItem<String>(
     328            0 :                                 value: member.userId,
     329            0 :                                 child: Text(
     330            0 :                                   '${member.name} (${getMemberTypeLabel(member.memberType)})',
     331              :                                 ),
     332              :                               );
     333            0 :                             }).toList(),
     334            0 :                             onChanged: _hasEditPermission
     335            0 :                                 ? (value) {
     336            0 :                                     setState(() {
     337            0 :                                       _selectedOwnerId = value;
     338              :                                     });
     339              :                                   }
     340              :                                 : null,
     341              :                           ),
     342              :                           // 저장 버튼
     343              :                           const SizedBox(height: 24),
     344            0 :                           SizedBox(
     345              :                             width: double.infinity,
     346            0 :                             child: ElevatedButton(
     347            0 :                               onPressed: _hasEditPermission
     348            0 :                                   ? _saveClubInfo
     349              :                                   : null,
     350            0 :                               style: ElevatedButton.styleFrom(
     351              :                                 padding: const EdgeInsets.symmetric(
     352              :                                   vertical: 16.0,
     353              :                                 ),
     354              :                               ),
     355              :                               child: const Text('저장'),
     356              :                             ),
     357              :                           ),
     358              :                         ],
     359              :                       ),
     360              :                     ),
     361              :                   ),
     362              :                 ),
     363              :               ],
     364              :             ),
     365              :     );
     366              :   }
     367              : }
        

Generated by: LCOV version 2.3.1-1