381 lines
14 KiB
Dart
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 {
|
|
_loadData();
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_nameController.dispose();
|
|
_descriptionController.dispose();
|
|
_locationController.dispose();
|
|
_femaleHandicapController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> _loadData() async {
|
|
setState(() {
|
|
_isLoading = true;
|
|
});
|
|
|
|
// await 이전에 messenger 캡처 (catch에서 사용)
|
|
final messenger = ScaffoldMessenger.of(context);
|
|
// 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) {
|
|
messenger.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(),
|
|
),
|
|
value: _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('저장'),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|