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