import 'package:flutter/material.dart'; import '../../../models/participant_model.dart'; import '../../../models/score_model.dart'; import '../../../utils/enum_mappings.dart'; import '../../../theme/badges.dart'; class ParticipantsTab extends StatefulWidget { final List participants; final List scores; final void Function(Participant participant) onEditParticipant; const ParticipantsTab({ super.key, required this.participants, required this.scores, required this.onEditParticipant, }); @override State createState() => _ParticipantsTabState(); } class _ParticipantsTabState extends State { String? _filterMemberType; // null: 전체 String? _filterStatus; // null: 전체 bool? _filterPaid; // null: 전체 final TextEditingController _searchController = TextEditingController(); @override void dispose() { _searchController.dispose(); super.dispose(); } String _buildFilterSummary() { final parts = []; if (_filterStatus != null && _filterStatus!.isNotEmpty) parts.add('상태: ${_filterStatus!}'); if (_filterMemberType != null && _filterMemberType!.isNotEmpty) parts.add('유형: ${_filterMemberType!}'); if (_filterPaid != null) parts.add('결제: ${_filterPaid! ? '완료' : '미결제'}'); final q = _searchController.text.trim(); if (q.isNotEmpty) parts.add("검색: '$q'"); return parts.isEmpty ? '필터 없음' : parts.join(' · '); } bool _hasActiveFilters() { return (_filterStatus != null && _filterStatus!.isNotEmpty) || (_filterMemberType != null && _filterMemberType!.isNotEmpty) || (_filterPaid != null) || (_searchController.text.trim().isNotEmpty); } @override Widget build(BuildContext context) { final Set memberTypeOptions = widget.participants .where((p) => (p.memberType ?? '').isNotEmpty) .map((p) => p.memberType!) .toSet(); final Set statusOptions = widget.participants .where((p) => (p.status ?? '').isNotEmpty) .map((p) => p.status!) .toSet(); final List filtered = widget.participants.where((p) { final matchesType = _filterMemberType == null || (_filterMemberType!.isEmpty) || p.memberType == _filterMemberType; final matchesStatus = _filterStatus == null || (_filterStatus!.isEmpty) || p.status == _filterStatus; final matchesPaid = _filterPaid == null || p.isPaid == _filterPaid; final q = _searchController.text.trim().toLowerCase(); final matchesQuery = q.isEmpty || (p.name ?? '').toLowerCase().contains(q); return matchesType && matchesStatus && matchesPaid && matchesQuery; }).toList(); return ListView.builder( itemCount: filtered.length + 1, itemBuilder: (context, index) { if (index == 0) { return Padding( padding: const EdgeInsets.fromLTRB(12, 8, 12, 4), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ExpansionTile( initiallyExpanded: false, leading: Icon( Icons.filter_list, color: _hasActiveFilters() ? Theme.of(context).colorScheme.primary : null, ), title: Text( '필터/검색', style: TextStyle( color: _hasActiveFilters() ? Theme.of(context).colorScheme.primary : null, fontWeight: _hasActiveFilters() ? FontWeight.w600 : FontWeight.w500, ), ), subtitle: Text( _buildFilterSummary(), style: TextStyle( color: _hasActiveFilters() ? Colors.black87 : Colors.black54, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), childrenPadding: const EdgeInsets.only( bottom: 8, left: 8, right: 8, ), children: [ TextField( controller: _searchController, decoration: InputDecoration( hintText: '이름 검색', prefixIcon: const Icon(Icons.search), suffixIcon: _searchController.text.isNotEmpty ? IconButton( icon: const Icon(Icons.clear), onPressed: () { setState(() => _searchController.clear()); }, tooltip: '검색 지우기', ) : null, border: const OutlineInputBorder(), ), onChanged: (_) => setState(() {}), ), const SizedBox(height: 8), Wrap( spacing: 8, runSpacing: 4, children: [ FilterChip( selected: _filterStatus == null, label: const Text('상태: 전체'), onSelected: (_) => setState(() => _filterStatus = null), ), ...statusOptions.map( (s) => FilterChip( selected: _filterStatus == s, label: Text('상태: $s'), onSelected: (_) => setState(() => _filterStatus = s), ), ), FilterChip( selected: _filterMemberType == null, label: const Text('유형: 전체'), onSelected: (_) => setState(() => _filterMemberType = null), ), ...memberTypeOptions.map( (t) => FilterChip( selected: _filterMemberType == t, label: Text('유형: $t'), onSelected: (_) => setState(() => _filterMemberType = t), ), ), FilterChip( selected: _filterPaid == null, label: const Text('결제: 전체'), onSelected: (_) => setState(() => _filterPaid = null), ), FilterChip( selected: _filterPaid == true, label: const Text('결제: 완료'), onSelected: (_) => setState(() => _filterPaid = true), ), FilterChip( selected: _filterPaid == false, label: const Text('결제: 미결제'), onSelected: (_) => setState(() => _filterPaid = false), ), ActionChip( label: const Text('필터 초기화'), avatar: const Icon(Icons.refresh, size: 16), onPressed: () { setState(() { _filterMemberType = null; _filterStatus = null; _filterPaid = null; _searchController.clear(); }); }, ), ], ), ], ), ], ), ); } final participant = filtered[index - 1]; // handicap: participant.handicap 없으면 가장 최근 score.handicap 사용 (부호 무관) int? handicap = participant.handicap; if (handicap == null) { Score? latest = widget.scores .where((s) => s.memberId == participant.memberId && s.handicap != null) .fold( null, (prev, s) => prev == null || s.date.isAfter(prev.date) ? s : prev, ); handicap = latest?.handicap; } return Card( margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), child: ListTile( leading: Builder( builder: (_) { final gender = (participant.gender ?? '').toLowerCase(); final bool isFemale = gender == 'female' || gender == 'f' || gender == '여' || gender == '여성'; final icon = isFemale ? Icons.female : Icons.male; final color = isFemale ? Colors.pink : Colors.blue; return CircleAvatar( backgroundColor: color.withValues(alpha: 0.1), child: Icon(icon, size: 18, color: color), ); }, ), title: Row( children: [ Expanded( child: Text( participant.name ?? '알 수 없음', style: const TextStyle(fontWeight: FontWeight.bold), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), const SizedBox(width: 6), // Guest small badge if ((participant.memberType ?? '').toLowerCase() == 'guest') Padding( padding: const EdgeInsets.only(right: 2), child: BadgeStyles.guestSmall(size: 16), ), if (handicap != null && handicap != 0) Container( padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 2, ), decoration: BoxDecoration( color: Colors.deepPurple.withValues(alpha: 0.10), borderRadius: BorderRadius.circular(12), border: Border.all( color: Colors.deepPurple.withValues(alpha: 0.25), ), ), child: Text( handicap > 0 ? '+$handicap' : '$handicap', style: const TextStyle( fontSize: 11, fontWeight: FontWeight.w600, color: Colors.deepPurple, ), ), ), if (participant.average != null) Padding( padding: const EdgeInsets.only(left: 4), child: Container( padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 2, ), decoration: BoxDecoration( color: Colors.indigo.withValues(alpha: 0.10), borderRadius: BorderRadius.circular(12), border: Border.all( color: Colors.indigo.withValues(alpha: 0.25), ), ), child: Text( participant.average!.toStringAsFixed(1), style: const TextStyle( fontSize: 11, fontWeight: FontWeight.w600, color: Colors.indigo, ), ), ), ), ], ), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (participant.memberType != null && participant.memberType!.isNotEmpty && participant.memberType!.toLowerCase() != 'guest') Padding( padding: const EdgeInsets.only(bottom: 4), child: BadgeStyles.memberType(getMemberTypeLabel(participant.memberType)), ), SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( mainAxisSize: MainAxisSize.min, children: [ if (participant.status != null) Padding( padding: const EdgeInsets.only(right: 4), child: BadgeStyles.participantStatus( toKoreanParticipantStatus(participant.status), ), ), BadgeStyles.payment(participant.isPaid), ], ), ), if ((participant.notes != null) && participant.notes!.trim().isNotEmpty) ...[ const SizedBox(height: 4), Row( crossAxisAlignment: CrossAxisAlignment.center, children: const [ Icon(Icons.notes, size: 14, color: Colors.black54), SizedBox(width: 4), ], ), ], ], ), onTap: () => widget.onEditParticipant(participant), ), ); }, ); } }