LCOV - code coverage report
Current view: top level - lib/screens/club - members_screen.dart Coverage Total Hit
Test: lcov.info Lines: 0.3 % 390 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              : import 'package:intl/intl.dart';
       4              : 
       5              : import '../../services/auth_service.dart';
       6              : import '../../services/club_service.dart';
       7              : import '../../services/member_service.dart';
       8              : import '../../models/member_model.dart';
       9              : import '../../utils/enum_mappings.dart';
      10              : 
      11              : class MembersScreen extends StatefulWidget {
      12           24 :   const MembersScreen({super.key});
      13              : 
      14            0 :   @override
      15            0 :   State<MembersScreen> createState() => _MembersScreenState();
      16              : }
      17              : 
      18              : class _MembersScreenState extends State<MembersScreen> {
      19              :   bool _isInit = false;
      20              :   String _searchQuery = '';
      21              :   final TextEditingController _searchController = TextEditingController();
      22              : 
      23            0 :   @override
      24              :   void dispose() {
      25            0 :     _searchController.dispose();
      26            0 :     super.dispose();
      27              :   }
      28              : 
      29            0 :   @override
      30              :   void didChangeDependencies() {
      31            0 :     super.didChangeDependencies();
      32            0 :     if (!_isInit) {
      33            0 :       _loadMembers();
      34            0 :       _isInit = true;
      35              :     }
      36              :   }
      37              : 
      38            0 :   Future<void> _loadMembers() async {
      39              :     try {
      40            0 :       print('_loadMembers 호출됨');
      41            0 :       final authService = Provider.of<AuthService>(context, listen: false);
      42            0 :       final clubService = Provider.of<ClubService>(context, listen: false);
      43            0 :       final memberService = Provider.of<MemberService>(context, listen: false);
      44              :       
      45            0 :       print('현재 클럽: ${clubService.currentClub?.name}');
      46            0 :       print('토큰: ${authService.token}');
      47              :       
      48            0 :       if (clubService.currentClub != null && authService.token != null) {
      49            0 :         print('멤버 서비스 초기화 시작');
      50            0 :         memberService.initialize(authService.token!);
      51            0 :         memberService.setClubId(clubService.currentClub!.id); // 클럽 ID 설정 추가
      52            0 :         print('멤버 서비스 초기화 완료');
      53              :         
      54            0 :         print('회원 목록 가져오기 시작');
      55            0 :         await memberService.fetchClubMembers();
      56            0 :         print('회원 목록 가져오기 완료: ${memberService.members.length}개');
      57              :       } else {
      58            0 :         print('클럽 또는 토큰이 없어서 회원 목록을 가져올 수 없습니다.');
      59              :       }
      60              :     } catch (e) {
      61            0 :       print('회원 목록 가져오기 오류: $e');
      62            0 :       if (mounted) {
      63            0 :         ScaffoldMessenger.of(context).showSnackBar(
      64            0 :           SnackBar(content: Text('회원 목록을 불러오는데 실패했습니다: $e')),
      65              :         );
      66              :       }
      67              :     }
      68              :   }
      69              : 
      70            0 :   List<Member> _getFilteredMembers(List<Member> members) {
      71            0 :     if (_searchQuery.isEmpty) {
      72              :       return members;
      73              :     }
      74              :     
      75            0 :     final query = _searchQuery.toLowerCase();
      76            0 :     return members.where((member) {
      77            0 :       return member.name.toLowerCase().contains(query) ||
      78            0 :           member.email.toLowerCase().contains(query);
      79            0 :     }).toList();
      80              :   }
      81              : 
      82            0 :   @override
      83              :   Widget build(BuildContext context) {
      84            0 :     return Scaffold(
      85            0 :       body: Column(
      86            0 :         children: [
      87              :           // 검색 바
      88            0 :           Padding(
      89              :             padding: const EdgeInsets.all(16.0),
      90            0 :             child: TextField(
      91            0 :               controller: _searchController,
      92            0 :               decoration: InputDecoration(
      93              :                 hintText: '회원 검색',
      94              :                 prefixIcon: const Icon(Icons.search),
      95            0 :                 border: OutlineInputBorder(
      96            0 :                   borderRadius: BorderRadius.circular(12),
      97              :                 ),
      98              :                 contentPadding: const EdgeInsets.symmetric(vertical: 0),
      99            0 :                 suffixIcon: _searchQuery.isNotEmpty
     100            0 :                     ? IconButton(
     101              :                         icon: const Icon(Icons.clear),
     102            0 :                         onPressed: () {
     103            0 :                           _searchController.clear();
     104            0 :                           setState(() {
     105            0 :                             _searchQuery = '';
     106              :                           });
     107              :                         },
     108              :                       )
     109              :                     : null,
     110              :               ),
     111            0 :               onChanged: (value) {
     112            0 :                 setState(() {
     113            0 :                   _searchQuery = value;
     114              :                 });
     115              :               },
     116              :             ),
     117              :           ),
     118              :           
     119              :           // 회원 목록
     120            0 :           Expanded(
     121            0 :             child: Consumer<MemberService>(
     122            0 :               builder: (context, memberService, _) {
     123            0 :                 if (memberService.isLoading) {
     124              :                   return const Center(child: CircularProgressIndicator());
     125              :                 }
     126              :                 
     127            0 :                 final filteredMembers = _getFilteredMembers(memberService.members);
     128              :                 
     129            0 :                 if (filteredMembers.isEmpty) {
     130            0 :                   return Center(
     131            0 :                     child: Text(
     132            0 :                       _searchQuery.isEmpty
     133              :                           ? '등록된 회원이 없습니다'
     134              :                           : '검색 결과가 없습니다',
     135              :                     ),
     136              :                   );
     137              :                 }
     138              :                 
     139            0 :                 return RefreshIndicator(
     140            0 :                   onRefresh: _loadMembers,
     141            0 :                   child: ListView.builder(
     142              :                     padding: const EdgeInsets.only(bottom: 16),
     143            0 :                     itemCount: filteredMembers.length,
     144            0 :                     itemBuilder: (context, index) {
     145            0 :                       final member = filteredMembers[index];
     146            0 :                       return _buildMemberCard(member);
     147              :                     },
     148              :                   ),
     149              :                 );
     150              :               },
     151              :             ),
     152              :           ),
     153              :         ],
     154              :       ),
     155            0 :       floatingActionButton: FloatingActionButton(
     156            0 :         onPressed: () {
     157              :           // 회원 추가 화면으로 이동 (추후 구현)
     158            0 :           _showAddMemberDialog();
     159              :         },
     160              :         backgroundColor: Colors.blue,
     161              :         child: const Icon(Icons.add, color: Colors.white),
     162              :       ),
     163              :     );
     164              :   }
     165              : 
     166            0 :   Widget _buildMemberCard(Member member) {
     167            0 :     final memberTypeText = _getMemberTypeDisplayName(member.memberType);
     168              :     
     169            0 :     return Card(
     170              :       margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
     171              :       elevation: 2,
     172            0 :       shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
     173            0 :       child: InkWell(
     174            0 :         onTap: () {
     175              :           // 회원 상세 정보 다이얼로그 표시
     176            0 :           _showMemberDetailDialog(member);
     177              :         },
     178            0 :         borderRadius: BorderRadius.circular(12),
     179            0 :         child: Padding(
     180              :           padding: const EdgeInsets.all(16.0),
     181            0 :           child: Row(
     182            0 :             children: [
     183              :               // 회원 정보
     184            0 :               Expanded(
     185            0 :                 child: Column(
     186              :                   crossAxisAlignment: CrossAxisAlignment.start,
     187            0 :                   children: [
     188            0 :                     Row(
     189            0 :                       children: [
     190            0 :                         Text(
     191            0 :                           member.name,
     192              :                           style: const TextStyle(
     193              :                             fontSize: 18,
     194              :                             fontWeight: FontWeight.bold,
     195              :                           ),
     196              :                         ),
     197              :                         const SizedBox(width: 8),
     198              :                         // 성별 아이콘
     199            0 :                         Icon(
     200            0 :                           member.gender == 'female' ? Icons.female : Icons.male,
     201              :                           size: 18,
     202            0 :                           color: member.gender == 'female' ? Colors.pink : Colors.blue,
     203              :                         ),
     204              :                         // 핸디캡
     205            0 :                         if (member.handicap != null && member.handicap != 0)
     206            0 :                           Container(
     207              :                             margin: const EdgeInsets.only(left: 8),
     208              :                             padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
     209            0 :                             decoration: BoxDecoration(
     210            0 :                               color: Colors.orange.shade100,
     211            0 :                               borderRadius: BorderRadius.circular(12),
     212              :                             ),
     213            0 :                             child: Text(
     214            0 :                               (member.handicap! > 0) ? "+${member.handicap}" : member.handicap.toString(),
     215            0 :                               style: TextStyle(
     216              :                                 fontSize: 12,
     217            0 :                                 color: Colors.orange.shade800,
     218              :                                 fontWeight: FontWeight.bold,
     219              :                               ),
     220              :                             ),
     221              :                           ),
     222              :                       ],
     223              :                     ),
     224            0 :                     if (member.email.isNotEmpty || member.phone != null)
     225            0 :                       const SizedBox(height: 4),
     226            0 :                     if (member.phone != null)
     227            0 :                       Text(
     228            0 :                         member.phone!,
     229            0 :                         style: TextStyle(
     230              :                           fontSize: 14,
     231            0 :                           color: Colors.grey.shade700,
     232              :                         ),
     233              :                       ),
     234            0 :                       const SizedBox(width: 8),
     235            0 :                     if (member.email.isNotEmpty)
     236            0 :                       Text(
     237            0 :                         member.email,
     238            0 :                         style: TextStyle(
     239              :                           fontSize: 14,
     240            0 :                           color: Colors.grey.shade700,
     241              :                         ),
     242              :                       ),
     243            0 :                     const SizedBox(height: 4),
     244            0 :                     Row(
     245            0 :                       children: [
     246            0 :                         Text(
     247              :                           memberTypeText, // 회원 유형으로 변경
     248            0 :                           style: TextStyle(
     249              :                             fontSize: 14,
     250            0 :                             color: Colors.blue.shade700,
     251              :                             fontWeight: FontWeight.w500,
     252              :                           ),
     253              :                         ),
     254              :                         const SizedBox(width: 8),
     255              :                         // 가입일 표시
     256            0 :                         if (member.joinDate != null)
     257            0 :                           Text(
     258            0 :                             '가입: ${formatDate(member.joinDate!)}',
     259            0 :                             style: TextStyle(
     260              :                               fontSize: 12,
     261            0 :                               color: Colors.grey.shade600,
     262              :                             ),
     263              :                           ),
     264            0 :                         const SizedBox(width: 8),
     265              :                         // 상태 표시
     266            0 :                         Container(
     267              :                           padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
     268            0 :                           decoration: BoxDecoration(
     269            0 :                             color: getStatusColor(member.status ?? 'inactive'),
     270            0 :                             borderRadius: BorderRadius.circular(12),
     271              :                           ),
     272            0 :                           child: Text(
     273            0 :                             getStatusLabel(member.status),
     274            0 :                             style: TextStyle(
     275            0 :                               color: getStatusTextColor(member.status ?? 'inactive'),
     276              :                               fontWeight: FontWeight.bold,
     277              :                               fontSize: 12,
     278              :                             ),
     279              :                           ),
     280              :                         ),
     281              :                       ],
     282              :                     ),
     283              :                   ],
     284              :                 ),
     285              :               ),
     286              :               // 액션 버튼
     287            0 :               PopupMenuButton<String>(
     288              :                 icon: const Icon(Icons.more_vert),
     289            0 :                 onSelected: (value) {
     290            0 :                   if (value == 'edit') {
     291            0 :                     _showMemberDetailDialog(member);
     292            0 :                   } else if (value == 'delete') {
     293            0 :                     _showDeleteConfirmationDialog(member);
     294              :                   }
     295              :                 },
     296            0 :                 itemBuilder: (context) => [
     297              :                   const PopupMenuItem(
     298              :                     value: 'delete',
     299              :                     child: Row(
     300              :                       children: [
     301              :                         Icon(Icons.delete, size: 20, color: Colors.red),
     302              :                         SizedBox(width: 8),
     303              :                         Text('삭제', style: TextStyle(color: Colors.red)),
     304              :                       ],
     305              :                     ),
     306              :                   ),
     307              :                 ],
     308              :               ),
     309              :             ],
     310              :           ),
     311              :         ),
     312              :       ),
     313              :     );
     314              :   }
     315              : 
     316              :   // 회원 추가 다이얼로그
     317            0 :   void _showAddMemberDialog() {
     318            0 :     final nameController = TextEditingController();
     319            0 :     final emailController = TextEditingController();
     320            0 :     final phoneController = TextEditingController();
     321            0 :     final handicapController = TextEditingController(text: '0');
     322            0 :     final joinDateController = TextEditingController(text: formatDate(DateTime.now()));
     323              :     
     324              :     // 선택형 필드를 위한 값 초기화
     325              :     String selectedGender = 'male';
     326              :     String selectedMemberType = 'regular';
     327              :     String selectedStatus = 'active';
     328              :     
     329            0 :     showDialog(
     330            0 :       context: context,
     331            0 :       builder: (context) => StatefulBuilder(
     332            0 :         builder: (context, setState) {
     333            0 :           return AlertDialog(
     334              :             title: const Text('회원 추가'),
     335            0 :             content: SingleChildScrollView(
     336            0 :               child: Column(
     337              :                 mainAxisSize: MainAxisSize.min,
     338            0 :                 children: [
     339            0 :                   TextField(
     340              :                     controller: nameController,
     341              :                     decoration: const InputDecoration(
     342              :                       labelText: '이름',
     343              :                       border: OutlineInputBorder(),
     344              :                     ),
     345              :                   ),
     346              :                   const SizedBox(height: 16),
     347            0 :                   TextField(
     348              :                     controller: emailController,
     349              :                     keyboardType: TextInputType.emailAddress,
     350              :                     decoration: const InputDecoration(
     351              :                       labelText: '이메일',
     352              :                       border: OutlineInputBorder(),
     353              :                     ),
     354              :                   ),
     355              :                   const SizedBox(height: 16),
     356            0 :                   TextField(
     357              :                     controller: phoneController,
     358              :                     keyboardType: TextInputType.phone,
     359              :                     decoration: const InputDecoration(
     360              :                       labelText: '전화번호 (선택사항)',
     361              :                       border: OutlineInputBorder(),
     362              :                     ),
     363              :                   ),
     364              :                   const SizedBox(height: 16),
     365            0 :                   TextField(
     366              :                     controller: handicapController,
     367              :                     keyboardType: TextInputType.number,
     368              :                     decoration: const InputDecoration(
     369              :                       labelText: '핸디캡',
     370              :                       border: OutlineInputBorder(),
     371              :                     ),
     372              :                   ),
     373              :                   const SizedBox(height: 16),
     374            0 :                   TextField(
     375              :                     controller: joinDateController,
     376              :                     readOnly: true,
     377              :                     decoration: const InputDecoration(
     378              :                       labelText: '가입일',
     379              :                       border: OutlineInputBorder(),
     380              :                       suffixIcon: Icon(Icons.calendar_today),
     381              :                     ),
     382            0 :                     onTap: () async {
     383            0 :                       final DateTime? picked = await showDatePicker(
     384              :                         context: context,
     385            0 :                         initialDate: DateTime.now(),
     386            0 :                         firstDate: DateTime(2000),
     387            0 :                         lastDate: DateTime(2101),
     388              :                       );
     389              :                       if (picked != null) {
     390            0 :                         setState(() {
     391            0 :                           joinDateController.text = formatDate(picked);
     392              :                         });
     393              :                       }
     394              :                     },
     395              :                   ),
     396              :                   const SizedBox(height: 16),
     397            0 :                   DropdownButtonFormField<String>(
     398              :                     value: selectedGender,
     399              :                     decoration: const InputDecoration(
     400              :                       labelText: '성별',
     401              :                       border: OutlineInputBorder(),
     402              :                     ),
     403              :                     items: const [
     404              :                       DropdownMenuItem(value: 'male', child: Text('남성')),
     405              :                       DropdownMenuItem(value: 'female', child: Text('여성')),
     406              :                     ],
     407            0 :                     onChanged: (value) {
     408              :                       if (value != null) {
     409            0 :                         setState(() {
     410              :                           selectedGender = value;
     411              :                         });
     412              :                       }
     413              :                     },
     414              :                   ),
     415              :                   const SizedBox(height: 16),
     416            0 :                   DropdownButtonFormField<String>(
     417              :                     value: selectedMemberType,
     418              :                     decoration: const InputDecoration(
     419              :                       labelText: '회원 유형',
     420              :                       border: OutlineInputBorder(),
     421              :                     ),
     422              :                     items: const [
     423              :                       DropdownMenuItem(value: 'regular', child: Text('정회원')),
     424              :                       DropdownMenuItem(value: 'associate', child: Text('준회원')),
     425              :                       DropdownMenuItem(value: 'guest', child: Text('게스트')),
     426              :                     ],
     427            0 :                     onChanged: (value) {
     428              :                       if (value != null) {
     429            0 :                         setState(() {
     430              :                           selectedMemberType = value;
     431              :                         });
     432              :                       }
     433              :                     },
     434              :                   ),
     435              :                   const SizedBox(height: 16),
     436            0 :                   DropdownButtonFormField<String>(
     437              :                     value: selectedStatus,
     438              :                     decoration: const InputDecoration(
     439              :                       labelText: '상태',
     440              :                       border: OutlineInputBorder(),
     441              :                     ),
     442              :                     items: const [
     443              :                       DropdownMenuItem(value: 'active', child: Text('활성')),
     444              :                       DropdownMenuItem(value: 'inactive', child: Text('비활성')),
     445              :                     ],
     446            0 :                     onChanged: (value) {
     447              :                       if (value != null) {
     448            0 :                         setState(() {
     449              :                           selectedStatus = value;
     450              :                         });
     451              :                       }
     452              :                     },
     453              :                   ),
     454              :                 ],
     455              :               ),
     456              :             ),
     457            0 :             actions: [
     458            0 :               TextButton(
     459            0 :                 onPressed: () => Navigator.of(context).pop(),
     460              :                 child: const Text('취소'),
     461              :               ),
     462            0 :               ElevatedButton(
     463            0 :                 onPressed: () async {
     464            0 :                   if (nameController.text.isEmpty) {
     465            0 :                     ScaffoldMessenger.of(context).showSnackBar(
     466              :                       const SnackBar(content: Text('이름은 필수 입력 항목입니다')),
     467              :                     );
     468              :                     return;
     469              :                   }
     470              :                   
     471              :                   try {
     472            0 :                     final memberService = Provider.of<MemberService>(context, listen: false);
     473            0 :                     final clubService = Provider.of<ClubService>(context, listen: false);
     474              :                     
     475            0 :                     if (clubService.currentClub != null) {
     476              :                       // 회원 추가 데이터 준비
     477            0 :                       final memberData = <String, dynamic>{
     478            0 :                         'name': nameController.text.trim(),
     479            0 :                         'clubId': clubService.currentClub!.id,
     480              :                         'gender': selectedGender,
     481              :                         'memberType': selectedMemberType,
     482              :                         'status': selectedStatus,
     483            0 :                         'handicap': int.tryParse(handicapController.text) ?? 0,
     484            0 :                         'isActive': selectedStatus == 'active',
     485              :                       };
     486              :                       
     487              :                       // 가입일 추가
     488              :                       try {
     489            0 :                         final joinDate = parseDate(joinDateController.text);
     490              :                         if (joinDate != null) {
     491            0 :                           memberData['joinDate'] = joinDate.toIso8601String();
     492              :                         }
     493              :                       } catch (e) {
     494              :                         // 가입일 파싱 오류 무시
     495              :                       }
     496              :                       
     497              :                       // 이메일이 비어있으면 null로 설정, 그렇지 않으면 트림한 값 사용
     498            0 :                       if (emailController.text.trim().isEmpty) {
     499            0 :                         memberData['email'] = null;
     500              :                       } else {
     501            0 :                         memberData['email'] = emailController.text.trim();
     502              :                       }
     503              :                       
     504              :                       // 전화번호가 비어있으면 null로 설정, 그렇지 않으면 트림한 값 사용
     505            0 :                       if (phoneController.text.trim().isEmpty) {
     506            0 :                         memberData['phone'] = null;
     507              :                       } else {
     508            0 :                         memberData['phone'] = phoneController.text.trim();
     509              :                       }
     510              :                       
     511            0 :                       await memberService.addMember(memberData);
     512              :                       
     513            0 :                       if (mounted) {
     514            0 :                         Navigator.of(context).pop();
     515            0 :                         ScaffoldMessenger.of(context).showSnackBar(
     516              :                           const SnackBar(content: Text('회원이 추가되었습니다')),
     517              :                         );
     518              :                       }
     519              :                     }
     520              :                   } catch (e) {
     521            0 :                     ScaffoldMessenger.of(context).showSnackBar(
     522            0 :                       SnackBar(content: Text('회원 추가에 실패했습니다: $e')),
     523              :                     );
     524              :                   }
     525              :                 },
     526              :                 child: const Text('추가'),
     527              :               ),
     528              :             ],
     529              :           );
     530              :         },
     531              :       ),
     532              :     );
     533              :   }
     534              : 
     535              :   // 날짜 포맷팅 함수
     536            0 :   String formatDate(DateTime? date) {
     537              :     if (date == null) return '정보 없음';
     538            0 :     return DateFormat('yyyy-MM-dd').format(date);
     539              :   }
     540              :   
     541              :   // 날짜 파싱 함수
     542            0 :   DateTime? parseDate(String? dateStr) {
     543              :     if (dateStr == null) return null;
     544            0 :     if (dateStr.isEmpty) return null;
     545              :     try {
     546              :       // 다양한 형식 지원
     547            0 :       if (dateStr.contains('T')) {
     548              :         // ISO 형식 (예: 2023-01-01T00:00:00.000Z)
     549            0 :         return DateTime.parse(dateStr);
     550            0 :       } else if (dateStr.contains('-')) {
     551              :         // yyyy-MM-dd 형식
     552            0 :         return DateFormat('yyyy-MM-dd').parse(dateStr);
     553              :       }
     554              :       return null;
     555              :     } catch (e) {
     556              :       return null;
     557              :     }
     558              :   }
     559              :   
     560              :   // 폼 필드 생성 헬퍼 메서드
     561              :   // 폼 필드 빌더 헬퍼 함수
     562            0 :   Widget buildFormField(String label, Widget field) {
     563            0 :     return Padding(
     564              :       padding: const EdgeInsets.only(bottom: 16.0),
     565            0 :       child: Column(
     566              :         crossAxisAlignment: CrossAxisAlignment.start,
     567            0 :         children: [
     568            0 :           Text(
     569              :             label,
     570              :             style: const TextStyle(
     571              :               fontSize: 14,
     572              :               fontWeight: FontWeight.w500,
     573              :               color: Colors.grey,
     574              :             ),
     575              :           ),
     576              :           const SizedBox(height: 4),
     577              :           field,
     578              :         ],
     579              :       ),
     580              :     );
     581              :   }
     582              :   
     583              :   // 성별 표시 이름 변환
     584            0 :   String _getGenderDisplayName(String? gender) {
     585            0 :     return getGenderLabel(gender);
     586              :   }
     587              :   
     588              :   // 회원 유형 표시 이름 변환
     589            0 :   String _getMemberTypeDisplayName(String? memberType) {
     590            0 :     return getMemberTypeLabel(memberType);
     591              :   }
     592              :   
     593              :   // 회원 상태에 따른 배경색 가져오기
     594            0 :   Color getStatusColor(String status) {
     595              :     switch (status) {
     596            0 :       case 'active': return Colors.green.shade100;
     597            0 :       case 'inactive': return Colors.grey.shade200;
     598            0 :       case 'suspended': return Colors.red.shade100;
     599            0 :       case 'deleted': return Colors.grey.shade300;
     600            0 :       default: return Colors.grey.shade200;
     601              :     }
     602              :   }
     603              :   
     604              :   // 회원 상태에 따른 텍스트 색상 가져오기
     605            0 :   Color getStatusTextColor(String status) {
     606              :     switch (status) {
     607            0 :       case 'active': return Colors.green.shade800;
     608            0 :       case 'inactive': return Colors.grey.shade700;
     609            0 :       case 'suspended': return Colors.red.shade800;
     610            0 :       case 'deleted': return Colors.grey.shade800;
     611            0 :       default: return Colors.grey.shade700;
     612              :     }
     613              :   }
     614              :   
     615              :   // 회원 상세 정보 다이얼로그
     616            0 :   void _showMemberDetailDialog(Member member) {
     617              :     bool isEditing = false;
     618              :     
     619              :     // 폼 컨트롤러 초기화
     620            0 :     final nameController = TextEditingController(text: member.name);
     621            0 :     final phoneController = TextEditingController(text: member.phone ?? '');
     622            0 :     final emailController = TextEditingController(text: member.email ?? '');
     623            0 :     final handicapController = TextEditingController(text: member.handicap?.toString() ?? '0');
     624            0 :     final joinDateController = TextEditingController(text: formatDate(member.joinDate));
     625              :     
     626              :     // 선택형 필드를 위한 값 초기화
     627            0 :     String selectedGender = member.gender ?? 'male';
     628            0 :     String selectedMemberType = member.memberType ?? 'regular';
     629            0 :     String selectedStatus = member.status ?? (member.isActive ? 'active' : 'inactive');
     630              :     
     631            0 :     showDialog(
     632            0 :       context: context,
     633            0 :       builder: (context) => StatefulBuilder(
     634            0 :         builder: (context, setState) {
     635            0 :           return AlertDialog(
     636            0 :             title: Text(isEditing ? '회원 정보 수정' : '회원 정보'),
     637            0 :             content: SingleChildScrollView(
     638            0 :               child: Column(
     639              :                 mainAxisSize: MainAxisSize.min,
     640              :                 crossAxisAlignment: CrossAxisAlignment.start,
     641            0 :                 children: [
     642              :                   // 회원 정보 헤더 (보기 모드에서만 표시)
     643            0 :                   if (!isEditing) ...[  
     644            0 :                     Row(
     645              :                       crossAxisAlignment: CrossAxisAlignment.center,
     646            0 :                       children: [
     647              :                         // 현재 회원 상태
     648            0 :                         Expanded(
     649            0 :                           child: Column(
     650              :                             crossAxisAlignment: CrossAxisAlignment.start,
     651            0 :                             children: [
     652            0 :                               Row(
     653            0 :                                 children: [
     654            0 :                                   Text(
     655            0 :                                     member.name,
     656              :                                     style: const TextStyle(
     657              :                                       fontSize: 18,
     658              :                                       fontWeight: FontWeight.bold,
     659              :                                     ),
     660              :                                   ),
     661              :                                 ],
     662              :                               ),
     663              :                               const SizedBox(height: 4),
     664            0 :                               Text(
     665            0 :                                 _getMemberTypeDisplayName(member.memberType),
     666            0 :                                 style: TextStyle(
     667              :                                   fontSize: 14,
     668            0 :                                   color: Colors.blue.shade700,
     669              :                                   fontWeight: FontWeight.w500,
     670              :                                 ),
     671              :                               ),
     672            0 :                               if (member.phone != null && member.phone!.isNotEmpty) ...[  
     673              :                                 const SizedBox(height: 4),
     674            0 :                                 Text(
     675            0 :                                   member.phone!,
     676            0 :                                   style: TextStyle(
     677              :                                     fontSize: 14,
     678            0 :                                     color: Colors.grey.shade700,
     679              :                                   ),
     680              :                                 ),
     681              :                               ],
     682            0 :                               if (member.email.isNotEmpty) ...[  
     683              :                                 const SizedBox(height: 2),
     684            0 :                                 Text(
     685            0 :                                   member.email,
     686            0 :                                   style: TextStyle(
     687              :                                     fontSize: 14,
     688            0 :                                     color: Colors.grey.shade700,
     689              :                                   ),
     690              :                                 ),
     691              :                               ],
     692              :                             ],
     693              :                           ),
     694              :                         ),
     695              :                       ],
     696              :                     ),
     697              :                     const SizedBox(height: 16),
     698              :                     const Divider(),
     699              :                   ],
     700              :                   
     701              :                   // 폼 필드
     702            0 :                   Container(
     703              :                     width: double.infinity,
     704              :                     padding: const EdgeInsets.symmetric(vertical: 8),
     705            0 :                     child: Column(
     706              :                       crossAxisAlignment: CrossAxisAlignment.start,
     707            0 :                       children: [
     708              :                         // 이름 필드 - 수정 모드에서만 표시 (상단에 이미 표시됨)
     709              :                         if (isEditing)
     710            0 :                           buildFormField('이름', TextField(
     711              :                             controller: nameController,
     712              :                             decoration: const InputDecoration(
     713              :                               border: OutlineInputBorder(),
     714              :                               contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
     715              :                             ),
     716              :                           )),
     717              :                         
     718              :                         // 성별 필드 - 상단에 없으므로 항상 표시
     719            0 :                         buildFormField('성별', isEditing ? 
     720            0 :                           DropdownButtonFormField<String>(
     721              :                             value: selectedGender,
     722              :                             decoration: const InputDecoration(
     723              :                               border: OutlineInputBorder(),
     724              :                               contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
     725              :                             ),
     726            0 :                             items: [
     727            0 :                               DropdownMenuItem<String>(
     728              :                                 value: 'male',
     729            0 :                                 child: Text('남성'),
     730              :                               ),
     731            0 :                               DropdownMenuItem<String>(
     732              :                                 value: 'female',
     733            0 :                                 child: Text('여성'),
     734              :                               ),
     735              :                             ],
     736            0 :                             onChanged: (value) {
     737              :                               if (value != null) {
     738              :                                 selectedGender = value;
     739              :                               }
     740              :                             },
     741              :                           ) : 
     742            0 :                           Text(_getGenderDisplayName(member.gender), style: const TextStyle(fontSize: 16))
     743              :                         ),
     744              :                         
     745              :                         // 회원 유형 필드 - 수정 모드에서만 표시 (상단에 이미 표시됨)
     746              :                         if (isEditing)
     747            0 :                           buildFormField('회원 유형', DropdownButtonFormField<String>(
     748              :                             value: selectedMemberType,
     749              :                             decoration: const InputDecoration(
     750              :                               border: OutlineInputBorder(),
     751              :                               contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
     752              :                             ),
     753            0 :                             items: [
     754            0 :                               DropdownMenuItem<String>(
     755              :                                 value: 'regular',
     756            0 :                                 child: Text('정회원'),
     757              :                               ),
     758            0 :                               DropdownMenuItem<String>(
     759              :                                 value: 'associate',
     760            0 :                                 child: Text('준회원'),
     761              :                               ),
     762            0 :                               DropdownMenuItem<String>(
     763              :                                 value: 'guest',
     764            0 :                                 child: Text('게스트'),
     765              :                               ),
     766            0 :                               DropdownMenuItem<String>(
     767              :                                 value: 'manager',
     768            0 :                                 child: Text('운영진'),
     769              :                               ),
     770            0 :                               DropdownMenuItem<String>(
     771              :                                 value: 'owner',
     772            0 :                                 child: Text('모임장'),
     773              :                               ),
     774              :                             ],
     775            0 :                             onChanged: (value) {
     776              :                               if (value != null) {
     777              :                                 selectedMemberType = value;
     778              :                               }
     779              :                             },
     780              :                           )),
     781              :                         
     782              :                         // 전화번호 필드 - 수정 모드에서만 표시 (상단에 이미 표시됨)
     783              :                         if (isEditing)
     784            0 :                           buildFormField('전화번호', TextField(
     785              :                             controller: phoneController,
     786              :                             keyboardType: TextInputType.phone,
     787              :                             decoration: const InputDecoration(
     788              :                               border: OutlineInputBorder(),
     789              :                               contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
     790              :                             ),
     791              :                           )),
     792              :                         
     793              :                         // 이메일 필드 - 수정 모드에서만 표시 (상단에 이미 표시됨)
     794              :                         if (isEditing)
     795            0 :                           buildFormField('이메일', TextField(
     796              :                             controller: emailController,
     797              :                             keyboardType: TextInputType.emailAddress,
     798              :                             decoration: const InputDecoration(
     799              :                               border: OutlineInputBorder(),
     800              :                               contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
     801              :                             ),
     802              :                           )),
     803              :                         
     804              :                         // 핸디캡 필드 - 상단에 없으므로 항상 표시
     805            0 :                         buildFormField('핸디캡', isEditing ? 
     806            0 :                           TextField(
     807              :                             controller: handicapController,
     808              :                             keyboardType: TextInputType.number,
     809              :                             decoration: const InputDecoration(
     810              :                               border: OutlineInputBorder(),
     811              :                               contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
     812              :                             ),
     813              :                           ) : 
     814            0 :                           Text(member.handicap?.toString() ?? '0', style: const TextStyle(fontSize: 16))
     815              :                         ),
     816              :                         
     817              :                         // 상태 필드 - 수정 모드에서만 표시 (상단에 이미 표시됨)
     818              :                         if (isEditing)
     819            0 :                           buildFormField('상태', DropdownButtonFormField<String>(
     820              :                             value: selectedStatus,
     821              :                             decoration: const InputDecoration(
     822              :                               border: OutlineInputBorder(),
     823              :                               contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
     824              :                             ),
     825            0 :                             items: [
     826            0 :                               DropdownMenuItem<String>(
     827              :                                 value: 'active',
     828            0 :                                 child: Text('활성'),
     829              :                               ),
     830            0 :                               DropdownMenuItem<String>(
     831              :                                 value: 'inactive',
     832            0 :                                 child: Text('비활성'),
     833              :                               ),
     834            0 :                               DropdownMenuItem<String>(
     835              :                                 value: 'suspended',
     836            0 :                                 child: Text('정지'),
     837              :                               ),
     838              :                             ],
     839            0 :                             onChanged: (value) {
     840              :                               if (value != null) {
     841              :                                 selectedStatus = value;
     842              :                               }
     843              :                             },
     844              :                           )),
     845              :                         
     846              :                         // 가입일 필드
     847            0 :                         buildFormField('가입일', isEditing ? 
     848            0 :                           InkWell(
     849            0 :                             onTap: () async {
     850            0 :                               final DateTime? picked = await showDatePicker(
     851              :                                 context: context,
     852            0 :                                 initialDate: member.joinDate ?? DateTime.now(),
     853            0 :                                 firstDate: DateTime(2000),
     854            0 :                                 lastDate: DateTime.now(),
     855              :                                 // locale은 앱 전체 설정을 사용합니다
     856              :                               );
     857              :                               if (picked != null) {
     858            0 :                                 setState(() {
     859              :                                   // 새로운 날짜로 업데이트
     860            0 :                                   joinDateController.text = formatDate(picked);
     861              :                                   
     862              :                                   // copyWith 메서드를 사용하여 효율적으로 업데이트
     863            0 :                                   member = member.copyWith(
     864              :                                     joinDate: picked, // 새로 선택한 날짜
     865              :                                   );
     866              :                                 });
     867              :                               }
     868              :                             },
     869            0 :                             child: InputDecorator(
     870              :                               decoration: const InputDecoration(
     871              :                                 border: OutlineInputBorder(),
     872              :                                 contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
     873              :                                 suffixIcon: Icon(Icons.calendar_today),
     874              :                               ),
     875            0 :                               child: Text(formatDate(member.joinDate), style: const TextStyle(fontSize: 16)),
     876              :                             ),
     877              :                           ) : 
     878            0 :                           Text(formatDate(member.joinDate), style: const TextStyle(fontSize: 16))
     879              :                         ),
     880              :                         
     881              :                         // 시스템 정보 (수정불가)
     882            0 :                         if (!isEditing) ...[  
     883              :                           const SizedBox(height: 16),
     884              :                           const Divider(),
     885              :                           const SizedBox(height: 8),
     886              :                           const Text('시스템 정보', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
     887              :                           const SizedBox(height: 8),
     888            0 :                           buildFormField('회원 ID', Text(member.id, style: const TextStyle(fontSize: 14, fontFamily: 'monospace'))),
     889            0 :                           if (member.userId.isNotEmpty)
     890            0 :                             buildFormField('유저 ID', Text(member.userId, style: const TextStyle(fontSize: 14, fontFamily: 'monospace'))),
     891            0 :                           if (member.clubId.isNotEmpty)
     892            0 :                             buildFormField('클럽 ID', Text(member.clubId, style: const TextStyle(fontSize: 14, fontFamily: 'monospace'))),
     893              :                         ],
     894              :                       ],
     895              :                     ),
     896              :                   ),
     897              :                 ],
     898              :               ),
     899              :             ),
     900            0 :             actions: [
     901              :               // 취소 버튼
     902            0 :               TextButton(
     903            0 :                 onPressed: () => Navigator.of(context).pop(),
     904              :                 child: const Text('취소'),
     905              :               ),
     906              :               // 수정/저장 버튼
     907            0 :               TextButton(
     908            0 :                 onPressed: () {
     909              :                   if (isEditing) {
     910              :                     // 수정 모드에서 저장 버튼 클릭 시
     911            0 :                     if (nameController.text.trim().isEmpty) {
     912            0 :                       ScaffoldMessenger.of(context).showSnackBar(
     913              :                         const SnackBar(content: Text('이름은 필수 입력 항목입니다.')),
     914              :                       );
     915              :                       return;
     916              :                     }
     917              :                     
     918              :                     // copyWith 메서드를 사용하여 회원 정보 업데이트
     919            0 :                     final updatedMember = member.copyWith(
     920            0 :                       name: nameController.text.trim(),
     921            0 :                       email: emailController.text.trim().isEmpty ? null : emailController.text.trim(),
     922            0 :                       phone: phoneController.text.trim().isEmpty ? null : phoneController.text.trim(),
     923              :                       memberType: selectedMemberType,
     924              :                       gender: selectedGender,
     925              :                       status: selectedStatus,
     926            0 :                       handicap: int.tryParse(handicapController.text) ?? member.handicap,
     927            0 :                       isActive: selectedStatus == 'active',
     928            0 :                       updatedAt: DateTime.now(),
     929              :                       // joinDate는 이미 날짜 선택기에서 업데이트되었으므로 여기서 다시 설정할 필요 없음
     930              :                     );
     931              :                     
     932              :                     // 회원 정보 저장
     933              :                     // toJson 결과에서 clubId 제거하여 MemberService에서 설정한 clubId만 사용
     934            0 :                     final memberData = updatedMember.toJson();
     935            0 :                     memberData.remove('clubId'); // clubId 필드 제거
     936              :                     
     937              :                     // 전화번호와 이메일 필드가 비어있을 때 null로 명시적 설정
     938            0 :                     if (phoneController.text.trim().isEmpty) {
     939            0 :                       memberData['phone'] = null;
     940              :                     }
     941              :                     
     942            0 :                     if (emailController.text.trim().isEmpty) {
     943            0 :                       memberData['email'] = null;
     944              :                     }
     945              :                     
     946              :                     // 가입일 필드 명시적으로 추가 (ISO 형식으로 변환)
     947            0 :                     if (member.joinDate != null) {
     948            0 :                       memberData['joinDate'] = member.joinDate!.toIso8601String();
     949              :                     }
     950              :                     
     951            0 :                     context.read<MemberService>().updateMember(
     952            0 :                       updatedMember.id,
     953              :                       memberData,
     954            0 :                     ).then((_) {
     955            0 :                       ScaffoldMessenger.of(context).showSnackBar(
     956              :                         const SnackBar(content: Text('회원 정보가 업데이트되었습니다.')),
     957              :                       );
     958            0 :                       Navigator.of(context).pop();
     959            0 :                     }).catchError((error) {
     960            0 :                       ScaffoldMessenger.of(context).showSnackBar(
     961            0 :                         SnackBar(content: Text('오류: ${error.toString()}')),
     962              :                       );
     963              :                     });
     964              :                   } else {
     965              :                     // 보기 모드에서 수정 버튼 클릭 시
     966            0 :                     setState(() {
     967              :                       isEditing = true;
     968              :                     });
     969              :                   }
     970              :                 },
     971            0 :                 child: Text(isEditing ? '저장' : '수정'),
     972              :               ),
     973              :             ],
     974              :           );
     975              :         },
     976              :       ),
     977              :     );
     978              :   }
     979              :   
     980              :   // 회원 삭제 확인 다이얼로그
     981            0 :   void _showDeleteConfirmationDialog(Member member) {
     982            0 :     showDialog(
     983            0 :       context: context,
     984            0 :       builder: (context) => AlertDialog(
     985              :         title: const Text('회원 삭제'),
     986            0 :         content: Text('${member.name} 회원을 정말 삭제하시겠습니까?'),
     987            0 :         actions: [
     988            0 :           TextButton(
     989            0 :             onPressed: () => Navigator.of(context).pop(),
     990              :             child: const Text('취소'),
     991              :           ),
     992            0 :           TextButton(
     993            0 :             onPressed: () async {
     994              :               try {
     995            0 :                 final memberService = Provider.of<MemberService>(context, listen: false);
     996              :                 
     997            0 :                 await memberService.deleteMember(member.id);
     998              :                 
     999            0 :                 if (mounted) {
    1000            0 :                   Navigator.of(context).pop();
    1001            0 :                   ScaffoldMessenger.of(context).showSnackBar(
    1002              :                     const SnackBar(content: Text('회원이 삭제되었습니다')),
    1003              :                   );
    1004              :                 }
    1005              :               } catch (e) {
    1006            0 :                 ScaffoldMessenger.of(context).showSnackBar(
    1007            0 :                   SnackBar(content: Text('회원 삭제에 실패했습니다: $e')),
    1008              :                 );
    1009              :               }
    1010              :             },
    1011            0 :             style: TextButton.styleFrom(foregroundColor: Colors.red),
    1012              :             child: const Text('삭제'),
    1013              :           ),
    1014              :         ],
    1015              :       ),
    1016              :     );
    1017              :   }
    1018              : }
        

Generated by: LCOV version 2.3.1-1