import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:table_calendar/table_calendar.dart'; import '../../../core/constants/app_colors.dart'; import '../../../core/constants/app_typography.dart'; import '../../../domain/entities/visit_record.dart'; import '../../providers/visit_provider.dart'; import 'widgets/visit_record_card.dart'; import 'widgets/visit_statistics.dart'; class CalendarScreen extends ConsumerStatefulWidget { const CalendarScreen({super.key}); @override ConsumerState createState() => _CalendarScreenState(); } class _CalendarScreenState extends ConsumerState with SingleTickerProviderStateMixin { late DateTime _selectedDay; late DateTime _focusedDay; CalendarFormat _calendarFormat = CalendarFormat.month; late TabController _tabController; Map> _visitRecordEvents = {}; @override void initState() { super.initState(); _selectedDay = DateTime.now(); _focusedDay = DateTime.now(); _tabController = TabController(length: 2, vsync: this); } @override void dispose() { _tabController.dispose(); super.dispose(); } List _getEventsForDay(DateTime day) { final normalizedDay = DateTime(day.year, day.month, day.day); return _visitRecordEvents[normalizedDay] ?? []; } @override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; return Scaffold( backgroundColor: isDark ? AppColors.darkBackground : AppColors.lightBackground, appBar: AppBar( title: const Text('방문 기록'), backgroundColor: isDark ? AppColors.darkPrimary : AppColors.lightPrimary, foregroundColor: Colors.white, elevation: 0, bottom: TabBar( controller: _tabController, indicatorColor: Colors.white, indicatorWeight: 3, tabs: const [ Tab(text: '캘린더'), Tab(text: '통계'), ], ), ), body: TabBarView( controller: _tabController, children: [ // 캘린더 탭 _buildCalendarTab(isDark), // 통계 탭 VisitStatistics(selectedMonth: _focusedDay), ], ), ); } Widget _buildCalendarTab(bool isDark) { return Consumer( builder: (context, ref, child) { final visitRecordsAsync = ref.watch(visitRecordsProvider); // 방문 기록을 날짜별로 그룹화 visitRecordsAsync.whenData((records) { _visitRecordEvents = {}; for (final record in records) { final normalizedDate = DateTime( record.visitDate.year, record.visitDate.month, record.visitDate.day, ); _visitRecordEvents[normalizedDate] = [ ...(_visitRecordEvents[normalizedDate] ?? []), record, ]; } }); return Column( children: [ // 캘린더 Card( margin: const EdgeInsets.all(16), color: isDark ? AppColors.darkSurface : AppColors.lightSurface, elevation: 2, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), child: TableCalendar( firstDay: DateTime.utc(2025, 1, 1), lastDay: DateTime.utc(2030, 12, 31), focusedDay: _focusedDay, calendarFormat: _calendarFormat, selectedDayPredicate: (day) => isSameDay(_selectedDay, day), onDaySelected: (selectedDay, focusedDay) { setState(() { _selectedDay = selectedDay; _focusedDay = focusedDay; }); }, onFormatChanged: (format) { setState(() { _calendarFormat = format; }); }, eventLoader: _getEventsForDay, calendarBuilders: CalendarBuilders( markerBuilder: (context, day, events) { if (events.isEmpty) return null; final visitRecords = events.cast(); final confirmedCount = visitRecords .where((r) => r.isConfirmed) .length; final unconfirmedCount = visitRecords.length - confirmedCount; return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ if (confirmedCount > 0) Container( width: 6, height: 6, margin: const EdgeInsets.symmetric(horizontal: 1), decoration: const BoxDecoration( color: AppColors.lightPrimary, shape: BoxShape.circle, ), ), if (unconfirmedCount > 0) Container( width: 6, height: 6, margin: const EdgeInsets.symmetric(horizontal: 1), decoration: const BoxDecoration( color: Colors.orange, shape: BoxShape.circle, ), ), ], ); }, ), calendarStyle: CalendarStyle( outsideDaysVisible: false, selectedDecoration: const BoxDecoration( color: AppColors.lightPrimary, shape: BoxShape.circle, ), todayDecoration: BoxDecoration( color: AppColors.lightPrimary.withOpacity(0.5), shape: BoxShape.circle, ), markersMaxCount: 2, markerDecoration: const BoxDecoration( color: AppColors.lightSecondary, shape: BoxShape.circle, ), weekendTextStyle: const TextStyle( color: AppColors.lightError, ), ), headerStyle: HeaderStyle( formatButtonVisible: true, titleCentered: true, formatButtonShowsNext: false, formatButtonDecoration: BoxDecoration( color: AppColors.lightPrimary.withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), formatButtonTextStyle: const TextStyle( color: AppColors.lightPrimary, ), ), ), ), // 범례 Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ _buildLegend('추천받음', Colors.orange, isDark), const SizedBox(width: 24), _buildLegend('방문완료', Colors.green, isDark), ], ), ), const SizedBox(height: 16), // 선택된 날짜의 기록 Expanded(child: _buildDayRecords(_selectedDay, isDark)), ], ); }, ); } Widget _buildLegend(String label, Color color, bool isDark) { return Row( children: [ Container( width: 14, height: 14, decoration: BoxDecoration(color: color, shape: BoxShape.circle), ), const SizedBox(width: 6), Text(label, style: AppTypography.body2(isDark)), ], ); } Widget _buildDayRecords(DateTime day, bool isDark) { final events = _getEventsForDay(day); if (events.isEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.event_available, size: 48, color: isDark ? AppColors.darkTextSecondary : AppColors.lightTextSecondary, ), const SizedBox(height: 16), Text('이날의 기록이 없습니다', style: AppTypography.body2(isDark)), ], ), ); } return Column( children: [ Padding( padding: const EdgeInsets.all(16), child: Row( children: [ Icon( Icons.calendar_today, size: 20, color: isDark ? AppColors.darkTextSecondary : AppColors.lightTextSecondary, ), const SizedBox(width: 8), Text( '${day.month}월 ${day.day}일 방문 기록', style: AppTypography.body1( isDark, ).copyWith(fontWeight: FontWeight.bold), ), const Spacer(), Text( '${events.length}건', style: AppTypography.body2(isDark).copyWith( color: AppColors.lightPrimary, fontWeight: FontWeight.bold, ), ), ], ), ), Expanded( child: ListView.builder( itemCount: events.length, itemBuilder: (context, index) { final sortedEvents = events ..sort((a, b) => b.visitDate.compareTo(a.visitDate)); return VisitRecordCard( visitRecord: sortedEvents[index], onTap: () { // TODO: 맛집 상세 페이지로 이동 }, ); }, ), ), ], ); } }