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/club_service.dart';
6 : import '../../services/member_service.dart';
7 : import '../../services/event_service.dart';
8 : import '../../services/score_service.dart';
9 :
10 : class ScoreEntryScreen extends StatefulWidget {
11 : final String? memberId;
12 : final String? eventId;
13 :
14 24 : const ScoreEntryScreen({
15 : super.key,
16 : this.memberId,
17 : this.eventId,
18 : });
19 :
20 0 : @override
21 0 : State<ScoreEntryScreen> createState() => _ScoreEntryScreenState();
22 : }
23 :
24 : class _ScoreEntryScreenState extends State<ScoreEntryScreen> {
25 : final _formKey = GlobalKey<FormState>();
26 : bool _isLoading = false;
27 :
28 : // 점수 입력 관련 상태
29 : final List<TextEditingController> _frameControllers = List.generate(
30 : 10,
31 : (_) => TextEditingController(),
32 : );
33 : final TextEditingController _notesController = TextEditingController();
34 :
35 : // 선택된 회원 및 이벤트
36 : String? _selectedMemberId;
37 : String? _selectedEventId;
38 : DateTime _selectedDate = DateTime.now();
39 :
40 0 : @override
41 : void initState() {
42 0 : super.initState();
43 0 : _selectedMemberId = widget.memberId;
44 0 : _selectedEventId = widget.eventId;
45 : }
46 :
47 0 : @override
48 : void dispose() {
49 0 : for (var controller in _frameControllers) {
50 0 : controller.dispose();
51 : }
52 0 : _notesController.dispose();
53 0 : super.dispose();
54 : }
55 :
56 : // 총점 계산
57 0 : int _calculateTotalScore() {
58 : int total = 0;
59 0 : for (var controller in _frameControllers) {
60 0 : final value = int.tryParse(controller.text) ?? 0;
61 0 : total += value;
62 : }
63 : return total;
64 : }
65 :
66 : // 점수 저장
67 0 : Future<void> _saveScore() async {
68 0 : if (!_formKey.currentState!.validate()) {
69 : return;
70 : }
71 :
72 0 : if (_selectedMemberId == null) {
73 0 : ScaffoldMessenger.of(context).showSnackBar(
74 : const SnackBar(content: Text('회원을 선택해주세요')),
75 : );
76 : return;
77 : }
78 :
79 0 : setState(() {
80 0 : _isLoading = true;
81 : });
82 :
83 : try {
84 0 : final scoreService = Provider.of<ScoreService>(context, listen: false);
85 0 : final clubService = Provider.of<ClubService>(context, listen: false);
86 :
87 0 : if (clubService.currentClub != null) {
88 : // 프레임 점수 배열 생성
89 0 : final frames = _frameControllers
90 0 : .map((controller) => int.tryParse(controller.text) ?? 0)
91 0 : .toList();
92 :
93 : // 총점 계산
94 0 : final totalScore = _calculateTotalScore();
95 :
96 : // 점수 데이터 생성
97 0 : final scoreData = {
98 0 : 'memberId': _selectedMemberId,
99 0 : 'eventId': _selectedEventId,
100 0 : 'clubId': clubService.currentClub!.id,
101 : 'frames': frames,
102 : 'totalScore': totalScore,
103 0 : 'date': _selectedDate.toIso8601String(),
104 0 : 'notes': _notesController.text.isNotEmpty ? _notesController.text : null,
105 : };
106 :
107 : // 점수 저장
108 0 : await scoreService.addScore(scoreData);
109 :
110 0 : if (mounted) {
111 0 : ScaffoldMessenger.of(context).showSnackBar(
112 : const SnackBar(content: Text('점수가 저장되었습니다')),
113 : );
114 0 : Navigator.of(context).pop();
115 : }
116 : }
117 : } catch (e) {
118 0 : ScaffoldMessenger.of(context).showSnackBar(
119 0 : SnackBar(content: Text('점수 저장에 실패했습니다: $e')),
120 : );
121 : } finally {
122 0 : setState(() {
123 0 : _isLoading = false;
124 : });
125 : }
126 : }
127 :
128 0 : @override
129 : Widget build(BuildContext context) {
130 0 : return Scaffold(
131 0 : appBar: AppBar(
132 : title: const Text('점수 입력'),
133 : ),
134 0 : body: _isLoading
135 : ? const Center(child: CircularProgressIndicator())
136 0 : : Form(
137 0 : key: _formKey,
138 0 : child: SingleChildScrollView(
139 : padding: const EdgeInsets.all(16.0),
140 0 : child: Column(
141 : crossAxisAlignment: CrossAxisAlignment.start,
142 0 : children: [
143 : // 회원 선택
144 0 : _buildMemberSelector(),
145 : const SizedBox(height: 16),
146 :
147 : // 이벤트 선택
148 0 : _buildEventSelector(),
149 : const SizedBox(height: 16),
150 :
151 : // 날짜 선택
152 0 : _buildDateSelector(),
153 : const SizedBox(height: 24),
154 :
155 : // 프레임 점수 입력
156 0 : _buildFrameInputs(),
157 : const SizedBox(height: 24),
158 :
159 : // 총점 표시
160 0 : _buildTotalScore(),
161 : const SizedBox(height: 16),
162 :
163 : // 메모 입력
164 0 : TextFormField(
165 0 : controller: _notesController,
166 : decoration: const InputDecoration(
167 : labelText: '메모',
168 : border: OutlineInputBorder(),
169 : ),
170 : maxLines: 3,
171 : ),
172 : const SizedBox(height: 24),
173 :
174 : // 저장 버튼
175 0 : SizedBox(
176 : width: double.infinity,
177 : height: 50,
178 0 : child: ElevatedButton(
179 0 : onPressed: _saveScore,
180 0 : style: ElevatedButton.styleFrom(
181 : backgroundColor: Colors.blue,
182 : foregroundColor: Colors.white,
183 : ),
184 : child: const Text('점수 저장'),
185 : ),
186 : ),
187 : ],
188 : ),
189 : ),
190 : ),
191 : );
192 : }
193 :
194 0 : Widget _buildMemberSelector() {
195 0 : return Consumer<MemberService>(
196 0 : builder: (context, memberService, _) {
197 0 : final members = memberService.members;
198 :
199 0 : return DropdownButtonFormField<String>(
200 : decoration: const InputDecoration(
201 : labelText: '회원 선택',
202 : border: OutlineInputBorder(),
203 : ),
204 0 : value: _selectedMemberId,
205 0 : items: members.map((member) {
206 0 : return DropdownMenuItem<String>(
207 0 : value: member.id,
208 0 : child: Text(member.name),
209 : );
210 0 : }).toList(),
211 0 : onChanged: (value) {
212 0 : setState(() {
213 0 : _selectedMemberId = value;
214 : });
215 : },
216 0 : validator: (value) {
217 0 : if (value == null || value.isEmpty) {
218 : return '회원을 선택해주세요';
219 : }
220 : return null;
221 : },
222 : );
223 : },
224 : );
225 : }
226 :
227 0 : Widget _buildEventSelector() {
228 0 : return Consumer<EventService>(
229 0 : builder: (context, eventService, _) {
230 0 : final events = eventService.events;
231 :
232 0 : return DropdownButtonFormField<String>(
233 : decoration: const InputDecoration(
234 : labelText: '이벤트 선택 (선택사항)',
235 : border: OutlineInputBorder(),
236 : ),
237 0 : value: _selectedEventId,
238 0 : items: [
239 : const DropdownMenuItem<String>(
240 : value: null,
241 : child: Text('이벤트 없음'),
242 : ),
243 0 : ...events.map((event) {
244 0 : return DropdownMenuItem<String>(
245 0 : value: event.id,
246 0 : child: Text(event.title),
247 : );
248 : }),
249 : ],
250 0 : onChanged: (value) {
251 0 : setState(() {
252 0 : _selectedEventId = value;
253 : });
254 : },
255 : );
256 : },
257 : );
258 : }
259 :
260 0 : Widget _buildDateSelector() {
261 0 : return InkWell(
262 0 : onTap: () async {
263 0 : final pickedDate = await showDatePicker(
264 0 : context: context,
265 0 : initialDate: _selectedDate,
266 0 : firstDate: DateTime.now().subtract(const Duration(days: 365)),
267 0 : lastDate: DateTime.now(),
268 : );
269 :
270 : if (pickedDate != null) {
271 0 : setState(() {
272 0 : _selectedDate = pickedDate;
273 : });
274 : }
275 : },
276 0 : child: InputDecorator(
277 : decoration: const InputDecoration(
278 : labelText: '날짜',
279 : border: OutlineInputBorder(),
280 : ),
281 0 : child: Row(
282 : mainAxisAlignment: MainAxisAlignment.spaceBetween,
283 0 : children: [
284 0 : Text(DateFormat('yyyy년 MM월 dd일').format(_selectedDate)),
285 : const Icon(Icons.calendar_today),
286 : ],
287 : ),
288 : ),
289 : );
290 : }
291 :
292 0 : Widget _buildFrameInputs() {
293 0 : return Column(
294 : crossAxisAlignment: CrossAxisAlignment.start,
295 0 : children: [
296 : const Text(
297 : '프레임 점수',
298 : style: TextStyle(
299 : fontSize: 16,
300 : fontWeight: FontWeight.bold,
301 : ),
302 : ),
303 : const SizedBox(height: 8),
304 0 : GridView.builder(
305 : shrinkWrap: true,
306 : physics: const NeverScrollableScrollPhysics(),
307 : gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
308 : crossAxisCount: 5,
309 : childAspectRatio: 1.5,
310 : crossAxisSpacing: 8,
311 : mainAxisSpacing: 8,
312 : ),
313 : itemCount: 10,
314 0 : itemBuilder: (context, index) {
315 0 : return TextFormField(
316 0 : controller: _frameControllers[index],
317 : keyboardType: TextInputType.number,
318 : textAlign: TextAlign.center,
319 0 : decoration: InputDecoration(
320 0 : labelText: '${index + 1}프레임',
321 : border: const OutlineInputBorder(),
322 : ),
323 0 : onChanged: (_) {
324 0 : setState(() {});
325 : },
326 0 : validator: (value) {
327 0 : if (value == null || value.isEmpty) {
328 : return '입력';
329 : }
330 0 : final score = int.tryParse(value);
331 : if (score == null) {
332 : return '숫자';
333 : }
334 0 : if (score < 0 || score > 30) {
335 : return '0-30';
336 : }
337 : return null;
338 : },
339 : );
340 : },
341 : ),
342 : ],
343 : );
344 : }
345 :
346 0 : Widget _buildTotalScore() {
347 0 : final totalScore = _calculateTotalScore();
348 :
349 0 : return Container(
350 : padding: const EdgeInsets.all(16),
351 0 : decoration: BoxDecoration(
352 0 : color: Colors.blue.shade50,
353 0 : borderRadius: BorderRadius.circular(8),
354 0 : border: Border.all(color: Colors.blue.shade200),
355 : ),
356 0 : child: Row(
357 : mainAxisAlignment: MainAxisAlignment.spaceBetween,
358 0 : children: [
359 : const Text(
360 : '총점',
361 : style: TextStyle(
362 : fontSize: 18,
363 : fontWeight: FontWeight.bold,
364 : ),
365 : ),
366 0 : Text(
367 0 : '$totalScore',
368 : style: const TextStyle(
369 : fontSize: 24,
370 : fontWeight: FontWeight.bold,
371 : color: Colors.blue,
372 : ),
373 : ),
374 : ],
375 : ),
376 : );
377 : }
378 : }
|