import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import '../models/invoice_models.dart'; import '../models/sales_summary.dart'; import '../services/invoice_repository.dart'; import '../widgets/analytics/analytics_summary_card.dart'; import '../widgets/analytics/empty_state_card.dart'; class SalesReportScreen extends StatefulWidget { const SalesReportScreen({super.key}); @override State createState() => _SalesReportScreenState(); } class _SalesReportScreenState extends State { final _invoiceRepo = InvoiceRepository(); int _targetYear = DateTime.now().year; DocumentType? _selectedType; bool _includeDrafts = false; SalesSummary? _summary; bool _isLoading = true; @override void initState() { super.initState(); _loadData(); } Future _loadData() async { setState(() => _isLoading = true); final summary = await _invoiceRepo.fetchSalesSummary( year: _targetYear, documentType: _selectedType, includeDrafts: _includeDrafts, topCustomerLimit: 5, ); setState(() { _summary = summary; _isLoading = false; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: const BackButton(), title: const Text("R1:売上・資金レポート"), backgroundColor: Colors.indigo, foregroundColor: Colors.white, ), body: RefreshIndicator( onRefresh: _loadData, child: _isLoading ? const Center(child: CircularProgressIndicator()) : _summary == null ? const Center(child: Text('データを取得できませんでした')) : ListView( padding: const EdgeInsets.all(16), children: [ _buildYearSelector(), const SizedBox(height: 12), _buildFilterRow(), const SizedBox(height: 16), _buildSummaryCards(), const SizedBox(height: 16), _buildTopCustomers(), const SizedBox(height: 16), _buildMonthlyList(), ], ), ), ); } Widget _buildYearSelector() { return Container( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), decoration: BoxDecoration( color: Colors.indigo.shade50, borderRadius: BorderRadius.circular(16), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ IconButton( icon: const Icon(Icons.chevron_left), onPressed: () { setState(() => _targetYear--); _loadData(); }, ), Text( "$_targetYear年度", style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), IconButton( icon: const Icon(Icons.chevron_right), onPressed: () { setState(() => _targetYear++); _loadData(); }, ), ], ), ); } Widget _buildFilterRow() { final chips = []; chips.add(_buildFilterChip(label: '全て', isActive: _selectedType == null, onTap: () { setState(() => _selectedType = null); _loadData(); })); for (final type in DocumentType.values) { chips.add(_buildFilterChip(label: type.displayName, isActive: _selectedType == type, onTap: () { setState(() => _selectedType = type); _loadData(); })); } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Wrap( spacing: 8, runSpacing: 8, children: chips, ), const SizedBox(height: 8), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ const Text('下書きを含める'), Switch( value: _includeDrafts, onChanged: (value) { setState(() => _includeDrafts = value); _loadData(); }, ), ], ), ], ); } Widget _buildSummaryCards() { final summary = _summary!; final fmt = NumberFormat('#,###'); final cards = [ AnalyticsSummaryCard( title: '年間売上合計', value: '¥${fmt.format(summary.yearlyTotal)}', subtitle: summary.documentType == null ? '全ドキュメント種別' : summary.documentType!.displayName, icon: Icons.ssid_chart, color: Colors.indigo, ), AnalyticsSummaryCard( title: '最高月', value: summary.bestMonth == 0 ? '-' : '${summary.bestMonth}月', subtitle: summary.bestMonthTotal > 0 ? '¥${fmt.format(summary.bestMonthTotal)}' : 'データなし', icon: Icons.emoji_events, color: Colors.orange, ), AnalyticsSummaryCard( title: '平均月額', value: '¥${fmt.format(summary.averageMonthly.round())}', subtitle: '12ヶ月換算', icon: Icons.stacked_line_chart, color: Colors.teal, ), ]; return Column( children: [ cards[0], const SizedBox(height: 12), Row( children: [ Expanded(child: cards[1]), const SizedBox(width: 12), Expanded(child: cards[2]), ], ), ], ); } Widget _buildTopCustomers() { final summary = _summary!; if (summary.customerStats.isEmpty) { return const EmptyStateCard(message: '確定済みの売上データがありません', icon: Icons.person_off); } final total = summary.customerStats.fold(0, (sum, stat) => sum + stat.totalAmount); final fmt = NumberFormat('#,###'); return Card( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('トップ顧客', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)), const SizedBox(height: 12), ...summary.customerStats.map((stat) { final ratio = total == 0 ? 0.0 : stat.totalAmount / total; return Padding( padding: const EdgeInsets.only(bottom: 12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded(child: Text(stat.customerName, style: const TextStyle(fontWeight: FontWeight.w600))), Text('¥${fmt.format(stat.totalAmount)}'), ], ), const SizedBox(height: 6), ClipRRect( borderRadius: BorderRadius.circular(8), child: LinearProgressIndicator( value: ratio, minHeight: 8, color: Colors.indigo, backgroundColor: Colors.indigo.withValues(alpha: 0.15), ), ), ], ), ); }), ], ), ), ); } Widget _buildMonthlyList() { final summary = _summary!; final fmt = NumberFormat('#,###'); final months = List.generate(12, (index) => index + 1); if (summary.monthlyTotals.values.every((value) => value == 0)) { return const EmptyStateCard(message: 'この年度の売上データがありません'); } return Card( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Padding( padding: EdgeInsets.fromLTRB(20, 20, 20, 0), child: Text('月別サマリー', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)), ), const Divider(height: 24), ...months.map((month) { final amount = summary.monthlyTotals[month] ?? 0; final share = summary.yearlyTotal == 0 ? 0.0 : amount / summary.yearlyTotal; return ListTile( leading: CircleAvatar( backgroundColor: Colors.indigo.withValues(alpha: 0.1), foregroundColor: Colors.indigo, child: Text(month.toString()), ), title: Text('$month月の売上'), subtitle: amount > 0 ? Text('シェア ${(share * 100).toStringAsFixed(1)}%') : const Text('データなし'), trailing: Text( '¥${fmt.format(amount)}', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 16, color: amount > 0 ? Colors.black87 : Colors.grey, ), ), ); }), ], ), ); } Widget _buildFilterChip({required String label, required bool isActive, required VoidCallback onTap}) { return ChoiceChip( label: Text(label), selected: isActive, onSelected: (_) => onTap(), selectedColor: Colors.indigo, labelStyle: TextStyle(color: isActive ? Colors.white : Colors.black87), ); } } // FontWeight.bold in Text widget is TextStyle.fontWeight not pw.FontWeight // Corrected to FontWeight.bold below in replace or write.