Line data Source code
1 : import 'package:flutter/material.dart';
2 : import 'package:provider/provider.dart';
3 :
4 : import '../../models/member_model.dart';
5 : import '../../services/auth_service.dart';
6 : import '../../services/club_service.dart';
7 : import '../../services/member_service.dart';
8 : import '../../utils/enum_mappings.dart';
9 :
10 : class ClubSettingsScreen extends StatefulWidget {
11 : static const routeName = '/club-settings';
12 :
13 24 : const ClubSettingsScreen({super.key});
14 :
15 0 : @override
16 0 : _ClubSettingsScreenState createState() => _ClubSettingsScreenState();
17 : }
18 :
19 : class _ClubSettingsScreenState extends State<ClubSettingsScreen> {
20 : final _formKey = GlobalKey<FormState>();
21 : final _nameController = TextEditingController();
22 : final _descriptionController = TextEditingController();
23 : final _locationController = TextEditingController();
24 : final _femaleHandicapController = TextEditingController();
25 :
26 : String? _selectedAverageCalculationPeriod;
27 : String? _selectedOwnerId;
28 : List<Member> _members = [];
29 : bool _isLoading = true;
30 : bool _hasEditPermission = false;
31 :
32 0 : @override
33 : void initState() {
34 0 : super.initState();
35 0 : _loadData();
36 : }
37 :
38 0 : @override
39 : void dispose() {
40 0 : _nameController.dispose();
41 0 : _descriptionController.dispose();
42 0 : _locationController.dispose();
43 0 : _femaleHandicapController.dispose();
44 0 : super.dispose();
45 : }
46 :
47 0 : Future<void> _loadData() async {
48 0 : setState(() {
49 0 : _isLoading = true;
50 : });
51 :
52 : try {
53 : // 클럽 정보 로드
54 0 : final clubService = Provider.of<ClubService>(context, listen: false);
55 0 : final club = clubService.currentClub;
56 :
57 : if (club != null) {
58 0 : _nameController.text = club.name;
59 0 : _descriptionController.text = club.description ?? '';
60 0 : _locationController.text = club.location ?? '';
61 0 : _femaleHandicapController.text = club.femaleHandicap?.toString() ?? '';
62 0 : _selectedAverageCalculationPeriod =
63 0 : club.averageCalculationPeriod ?? 'total';
64 0 : _selectedOwnerId = club.ownerId;
65 : }
66 :
67 : // 회원 목록 로드
68 0 : final memberService = Provider.of<MemberService>(context, listen: false);
69 0 : await memberService.fetchClubMembers();
70 0 : setState(() {
71 0 : _members = memberService.members;
72 : });
73 :
74 : // 권한 확인
75 0 : final authService = Provider.of<AuthService>(context, listen: false);
76 0 : final currentUserId = authService.currentUser?.id;
77 :
78 : // 현재 사용자가 클럽 소유자이거나 관리자인지 확인
79 : if (currentUserId != null) {
80 0 : final currentMember = _members.firstWhere(
81 0 : (member) => member.userId == currentUserId,
82 0 : orElse: () => Member(
83 : id: '',
84 : userId: '',
85 : name: '',
86 : memberType: '',
87 0 : clubId: club?.id ?? '',
88 : email: '',
89 : isActive: true,
90 : ),
91 : );
92 :
93 0 : setState(() {
94 0 : _hasEditPermission =
95 0 : club?.ownerId == currentUserId || // 클럽 소유자
96 0 : currentMember.memberType == 'manager' || // 클럽 매니저
97 0 : authService.currentUser?.role == 'admin'; // 시스템 관리자
98 : });
99 : }
100 : } catch (error) {
101 0 : ScaffoldMessenger.of(
102 0 : context,
103 0 : ).showSnackBar(SnackBar(content: Text('데이터 로드 중 오류가 발생했습니다: $error')));
104 : } finally {
105 0 : if (mounted) {
106 0 : setState(() {
107 0 : _isLoading = false;
108 : });
109 : }
110 : }
111 : }
112 :
113 0 : Future<void> _saveClubInfo() async {
114 0 : if (!_formKey.currentState!.validate()) {
115 : return;
116 : }
117 :
118 0 : setState(() {
119 0 : _isLoading = true;
120 : });
121 :
122 : try {
123 0 : final clubService = Provider.of<ClubService>(context, listen: false);
124 0 : final club = clubService.currentClub;
125 :
126 : if (club != null) {
127 : // 기본 업데이트 데이터 준비
128 0 : final Map<String, dynamic> updates = {
129 0 : 'clubId': club.id,
130 0 : 'name': _nameController.text.trim(),
131 : };
132 :
133 : // 빈 문자열이 아닌 경우에만 추가 (null 값 명시적 처리)
134 0 : if (_descriptionController.text.trim().isNotEmpty) {
135 0 : updates['description'] = _descriptionController.text.trim();
136 0 : } else if (_descriptionController.text.trim().isEmpty &&
137 0 : club.description != null) {
138 : // 기존에 값이 있었는데 지금 비어있으면 명시적으로 null 설정
139 0 : updates['description'] = null;
140 : }
141 :
142 0 : if (_locationController.text.trim().isNotEmpty) {
143 0 : updates['location'] = _locationController.text.trim();
144 0 : } else if (_locationController.text.trim().isEmpty &&
145 0 : club.location != null) {
146 0 : updates['location'] = null;
147 : }
148 :
149 0 : if (_femaleHandicapController.text.trim().isNotEmpty) {
150 0 : updates['femaleHandicap'] = int.parse(
151 0 : _femaleHandicapController.text.trim(),
152 : );
153 : }
154 :
155 0 : if (_selectedAverageCalculationPeriod != null) {
156 0 : updates['averageCalculationPeriod'] =
157 0 : _selectedAverageCalculationPeriod;
158 : }
159 :
160 0 : if (_selectedOwnerId != null) {
161 0 : updates['ownerId'] = _selectedOwnerId;
162 : }
163 :
164 : // 모임장 변경 시에만 이메일 정보 추가
165 0 : if (_selectedOwnerId != club.ownerId) {
166 : // 모임장이 변경된 경우 해당 회원의 이메일 정보 찾기
167 0 : final selectedMember = _members.firstWhere(
168 0 : (member) => member.userId == _selectedOwnerId,
169 0 : orElse: () => Member(
170 : id: '',
171 : userId: '',
172 : name: '',
173 : memberType: '',
174 0 : clubId: club.id,
175 : email: '',
176 : isActive: true,
177 : ),
178 : );
179 :
180 : // 이메일이 있는 경우에만 추가 (빈 문자열이나 null은 전송하지 않음)
181 0 : if (selectedMember.email.isNotEmpty) {
182 0 : updates['ownerEmail'] = selectedMember.email;
183 : }
184 : }
185 :
186 0 : await clubService.updateClub(club.id, updates);
187 :
188 0 : if (context.mounted) {
189 0 : ScaffoldMessenger.of(
190 0 : context,
191 0 : ).showSnackBar(const SnackBar(content: Text('클럽 정보가 성공적으로 저장되었습니다')));
192 : }
193 : }
194 : } catch (error) {
195 0 : if (context.mounted) {
196 0 : ScaffoldMessenger.of(context).showSnackBar(
197 0 : SnackBar(content: Text('클럽 정보 저장 중 오류가 발생했습니다: $error')),
198 : );
199 : }
200 : } finally {
201 0 : if (mounted) {
202 0 : setState(() {
203 0 : _isLoading = false;
204 : });
205 : }
206 : }
207 : }
208 :
209 0 : @override
210 : Widget build(BuildContext context) {
211 0 : return Scaffold(
212 0 : appBar: AppBar(title: const Text('클럽 설정')),
213 0 : body: _isLoading
214 : ? const Center(child: CircularProgressIndicator())
215 0 : : Column(
216 0 : children: [
217 0 : if (!_hasEditPermission)
218 0 : Container(
219 : width: double.infinity,
220 : padding: const EdgeInsets.all(16.0),
221 0 : color: Colors.amber.shade100,
222 : child: const Text(
223 : '권한 알림: 클럽 설정을 변경하려면 클럽 소유자 또는 관리자 권한이 필요합니다.',
224 : style: TextStyle(fontWeight: FontWeight.bold),
225 : ),
226 : ),
227 0 : Expanded(
228 0 : child: SingleChildScrollView(
229 : padding: const EdgeInsets.all(16.0),
230 0 : child: Form(
231 0 : key: _formKey,
232 0 : child: Column(
233 : crossAxisAlignment: CrossAxisAlignment.start,
234 0 : children: [
235 : // 클럽 이름
236 0 : TextFormField(
237 0 : controller: _nameController,
238 : decoration: const InputDecoration(
239 : labelText: '클럽 이름',
240 : border: OutlineInputBorder(),
241 : ),
242 0 : validator: (value) {
243 0 : if (value == null || value.trim().isEmpty) {
244 : return '클럽 이름을 입력해주세요';
245 : }
246 : return null;
247 : },
248 0 : enabled: _hasEditPermission,
249 : ),
250 : // 설명
251 : const SizedBox(height: 16),
252 0 : TextFormField(
253 0 : controller: _descriptionController,
254 : decoration: const InputDecoration(
255 : labelText: '설명',
256 : border: OutlineInputBorder(),
257 : ),
258 : maxLines: 3,
259 0 : enabled: _hasEditPermission,
260 : ),
261 : // 위치
262 : const SizedBox(height: 16),
263 0 : TextFormField(
264 0 : controller: _locationController,
265 : decoration: const InputDecoration(
266 : labelText: '위치',
267 : border: OutlineInputBorder(),
268 : ),
269 0 : enabled: _hasEditPermission,
270 : ),
271 : // 여성 기본 핸디캡
272 : const SizedBox(height: 16),
273 0 : TextFormField(
274 0 : controller: _femaleHandicapController,
275 : decoration: const InputDecoration(
276 : labelText: '여성 기본 핸디캡',
277 : border: OutlineInputBorder(),
278 : ),
279 : keyboardType: TextInputType.number,
280 0 : validator: (value) {
281 0 : if (value != null && value.isNotEmpty) {
282 0 : final handicap = int.tryParse(value);
283 : if (handicap == null) {
284 : return '숫자를 입력해주세요';
285 : }
286 0 : if (handicap < 0) {
287 : return '0 이상의 값을 입력해주세요';
288 : }
289 : }
290 : return null;
291 : },
292 0 : enabled: _hasEditPermission,
293 : ),
294 : // 평균 산정 기준
295 : const SizedBox(height: 16),
296 0 : DropdownButtonFormField<String>(
297 : decoration: const InputDecoration(
298 : labelText: '평균 산정 기준',
299 : border: OutlineInputBorder(),
300 : ),
301 0 : value: _selectedAverageCalculationPeriod,
302 0 : items: averageCalculationPeriodOptions.entries
303 0 : .map(
304 0 : (entry) => DropdownMenuItem<String>(
305 0 : value: entry.key,
306 0 : child: Text(entry.value),
307 : ),
308 : )
309 0 : .toList(),
310 0 : onChanged: _hasEditPermission
311 0 : ? (value) {
312 0 : setState(() {
313 0 : _selectedAverageCalculationPeriod = value;
314 : });
315 : }
316 : : null,
317 : ),
318 : // 모임장 설정
319 : const SizedBox(height: 16),
320 0 : DropdownButtonFormField<String>(
321 : decoration: const InputDecoration(
322 : labelText: '모임장 설정',
323 : border: OutlineInputBorder(),
324 : ),
325 0 : value: _selectedOwnerId,
326 0 : items: _members.map((member) {
327 0 : return DropdownMenuItem<String>(
328 0 : value: member.userId,
329 0 : child: Text(
330 0 : '${member.name} (${getMemberTypeLabel(member.memberType)})',
331 : ),
332 : );
333 0 : }).toList(),
334 0 : onChanged: _hasEditPermission
335 0 : ? (value) {
336 0 : setState(() {
337 0 : _selectedOwnerId = value;
338 : });
339 : }
340 : : null,
341 : ),
342 : // 저장 버튼
343 : const SizedBox(height: 24),
344 0 : SizedBox(
345 : width: double.infinity,
346 0 : child: ElevatedButton(
347 0 : onPressed: _hasEditPermission
348 0 : ? _saveClubInfo
349 : : null,
350 0 : style: ElevatedButton.styleFrom(
351 : padding: const EdgeInsets.symmetric(
352 : vertical: 16.0,
353 : ),
354 : ),
355 : child: const Text('저장'),
356 : ),
357 : ),
358 : ],
359 : ),
360 : ),
361 : ),
362 : ),
363 : ],
364 : ),
365 : );
366 : }
367 : }
|