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