LCOV - code coverage report
Current view: top level - lib/screens/score - score_entry_screen.dart Coverage Total Hit
Test: lcov.info Lines: 0.8 % 133 1
Test Date: 2025-08-12 03:21:51 Functions: - 0 0

            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              : }
        

Generated by: LCOV version 2.3.1-1