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