Files
bowlingManager/mobile/lib/screens/club/club_settings_screen.dart
T
2025-11-10 14:14:08 +09:00

381 lines
14 KiB
Dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../models/member_model.dart';
import '../../services/auth_service.dart';
import '../../services/club_service.dart';
import '../../services/member_service.dart';
import '../../utils/enum_mappings.dart';
import '../../widgets/owner_dropdown.dart';
class ClubSettingsScreen extends StatefulWidget {
static const routeName = '/club-settings';
const ClubSettingsScreen({super.key, this.onOwnerItemsBuilt, this.initialMembers});
// 테스트 전용: 모임장 드롭다운 항목 개수 리포트 콜백
final ValueChanged<int>? onOwnerItemsBuilt;
// 테스트 전용: 초기 멤버 목록을 직접 주입하여 로딩을 우회
final List<Member>? initialMembers;
@override
State<ClubSettingsScreen> createState() => _ClubSettingsScreenState();
}
class _ClubSettingsScreenState extends State<ClubSettingsScreen> {
final _formKey = GlobalKey<FormState>();
final _nameController = TextEditingController();
final _descriptionController = TextEditingController();
final _locationController = TextEditingController();
final _femaleHandicapController = TextEditingController();
String? _selectedAverageCalculationPeriod;
String? _selectedOwnerId;
List<Member> _members = [];
bool _isLoading = true;
bool _hasEditPermission = false;
@override
void initState() {
super.initState();
if (widget.initialMembers != null) {
// 테스트 경로: 초기 데이터 주입
_members = List<Member>.from(widget.initialMembers!);
_isLoading = false;
_hasEditPermission = true; // 테스트에서는 편집 가능 상태로 둠
} else {
// 첫 프레임 이후에 로드하여 InheritedWidget 접근 안전 보장
WidgetsBinding.instance.addPostFrameCallback((_) => _loadData());
}
}
@override
void dispose() {
_nameController.dispose();
_descriptionController.dispose();
_locationController.dispose();
_femaleHandicapController.dispose();
super.dispose();
}
Future<void> _loadData() async {
setState(() {
_isLoading = true;
});
// await 이전에 authService 캡처 (이후 권한 계산에 사용)
final authService = Provider.of<AuthService>(context, listen: false);
try {
// 클럽 정보 로드
final clubService = Provider.of<ClubService>(context, listen: false);
final club = clubService.currentClub;
if (club != null) {
_nameController.text = club.name;
_descriptionController.text = club.description ?? '';
_locationController.text = club.location ?? '';
_femaleHandicapController.text = club.femaleHandicap?.toString() ?? '';
_selectedAverageCalculationPeriod =
club.averageCalculationPeriod ?? 'total';
_selectedOwnerId = club.ownerId;
}
// 회원 목록 로드
final memberService = Provider.of<MemberService>(context, listen: false);
await memberService.fetchClubMembers();
setState(() {
_members = memberService.members;
});
// 권한 확인
final currentUserId = authService.currentUser?.id;
// 현재 사용자가 클럽 소유자이거나 관리자인지 확인
if (currentUserId != null) {
final currentMember = _members.firstWhere(
(member) => member.userId == currentUserId,
orElse: () => Member(
id: '',
userId: '',
name: '',
memberType: '',
clubId: club?.id ?? '',
email: '',
isActive: true,
),
);
setState(() {
_hasEditPermission =
club?.ownerId == currentUserId || // 클럽 소유자
currentMember.memberType == 'manager' || // 클럽 매니저
authService.currentUser?.role == 'admin'; // 시스템 관리자
});
}
} catch (error) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('데이터 로드 중 오류가 발생했습니다: $error')),
);
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
Future<void> _saveClubInfo() async {
if (!_formKey.currentState!.validate()) {
return;
}
setState(() {
_isLoading = true;
});
// await 이전에 messenger 캡처
final messenger = ScaffoldMessenger.of(context);
try {
final clubService = Provider.of<ClubService>(context, listen: false);
final club = clubService.currentClub;
if (club != null) {
// 기본 업데이트 데이터 준비
final Map<String, dynamic> updates = {
'clubId': club.id,
'name': _nameController.text.trim(),
};
// 빈 문자열이 아닌 경우에만 추가 (null 값 명시적 처리)
if (_descriptionController.text.trim().isNotEmpty) {
updates['description'] = _descriptionController.text.trim();
} else if (_descriptionController.text.trim().isEmpty &&
club.description != null) {
// 기존에 값이 있었는데 지금 비어있으면 명시적으로 null 설정
updates['description'] = null;
}
if (_locationController.text.trim().isNotEmpty) {
updates['location'] = _locationController.text.trim();
} else if (_locationController.text.trim().isEmpty &&
club.location != null) {
updates['location'] = null;
}
if (_femaleHandicapController.text.trim().isNotEmpty) {
updates['femaleHandicap'] = int.parse(
_femaleHandicapController.text.trim(),
);
}
if (_selectedAverageCalculationPeriod != null) {
updates['averageCalculationPeriod'] =
_selectedAverageCalculationPeriod;
}
if (_selectedOwnerId != null) {
updates['ownerId'] = _selectedOwnerId;
}
// 모임장 변경 시에만 이메일 정보 추가
if (_selectedOwnerId != club.ownerId) {
// 모임장이 변경된 경우 해당 회원의 이메일 정보 찾기
final selectedMember = _members.firstWhere(
(member) => member.userId == _selectedOwnerId,
orElse: () => Member(
id: '',
userId: '',
name: '',
memberType: '',
clubId: club.id,
email: '',
isActive: true,
),
);
// 이메일이 있는 경우에만 추가 (빈 문자열이나 null은 전송하지 않음)
if (selectedMember.email.isNotEmpty) {
updates['ownerEmail'] = selectedMember.email;
}
}
await clubService.updateClub(club.id, updates);
messenger.showSnackBar(
const SnackBar(content: Text('클럽 정보가 성공적으로 저장되었습니다')),
);
}
} catch (error) {
messenger.showSnackBar(
SnackBar(content: Text('클럽 정보 저장 중 오류가 발생했습니다: $error')),
);
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('클럽 설정')),
body: _isLoading
? const Center(child: CircularProgressIndicator())
: Column(
children: [
if (!_hasEditPermission)
Container(
width: double.infinity,
padding: const EdgeInsets.all(16.0),
color: Colors.amber.shade100,
child: const Text(
'권한 알림: 클럽 설정을 변경하려면 클럽 소유자 또는 관리자 권한이 필요합니다.',
style: TextStyle(fontWeight: FontWeight.bold),
),
),
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 클럽 이름
TextFormField(
controller: _nameController,
decoration: const InputDecoration(
labelText: '클럽 이름',
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return '클럽 이름을 입력해주세요';
}
return null;
},
enabled: _hasEditPermission,
),
// 설명
const SizedBox(height: 16),
TextFormField(
controller: _descriptionController,
decoration: const InputDecoration(
labelText: '설명',
border: OutlineInputBorder(),
),
maxLines: 3,
enabled: _hasEditPermission,
),
// 위치
const SizedBox(height: 16),
TextFormField(
controller: _locationController,
decoration: const InputDecoration(
labelText: '위치',
border: OutlineInputBorder(),
),
enabled: _hasEditPermission,
),
// 여성 기본 핸디캡
const SizedBox(height: 16),
TextFormField(
controller: _femaleHandicapController,
decoration: const InputDecoration(
labelText: '여성 기본 핸디캡',
border: OutlineInputBorder(),
),
keyboardType: TextInputType.number,
validator: (value) {
if (value != null && value.isNotEmpty) {
final handicap = int.tryParse(value);
if (handicap == null) {
return '숫자를 입력해주세요';
}
if (handicap < 0) {
return '0 이상의 값을 입력해주세요';
}
}
return null;
},
enabled: _hasEditPermission,
),
// 평균 산정 기준
const SizedBox(height: 16),
DropdownButtonFormField<String>(
key: const Key('club_settings_avg_period_dropdown'),
decoration: const InputDecoration(
labelText: '평균 산정 기준',
border: OutlineInputBorder(),
),
initialValue: _selectedAverageCalculationPeriod,
isExpanded: true,
items: averageCalculationPeriodOptions.entries
.map(
(entry) => DropdownMenuItem<String>(
value: entry.key,
child: Text(
entry.value,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
)
.toList(),
onChanged: _hasEditPermission
? (value) {
setState(() {
_selectedAverageCalculationPeriod = value;
});
}
: null,
),
// 모임장 설정
const SizedBox(height: 16),
OwnerDropdown(
key: const Key('owner_dropdown'),
members: _members,
value: _selectedOwnerId,
enabled: _hasEditPermission,
onChanged: (value) {
if (!_hasEditPermission) return;
setState(() {
_selectedOwnerId = value;
});
},
onItemsBuilt: widget.onOwnerItemsBuilt,
),
// 저장 버튼
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _hasEditPermission
? _saveClubInfo
: null,
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
vertical: 16.0,
),
),
child: const Text('저장'),
),
),
],
),
),
),
),
],
),
);
}
}