import 'package:decimal/decimal.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/utils/ru_date.dart'; import '../../core/widgets/async_value_view.dart'; import '../../core/widgets/empty_state.dart'; import '../../core/widgets/money_text.dart'; import '../../core/widgets/section_card.dart'; import 'data/income_api.dart'; import 'labels.dart'; import 'providers.dart'; /// История: what was actually paid, by month and kind. Facts only — no basis column here, /// because every row is `paid` by construction. class IncomeHistoryTab extends ConsumerWidget { const IncomeHistoryTab({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final history = ref.watch(incomeHistoryProvider); return RefreshIndicator( onRefresh: () async => ref.invalidate(incomeHistoryProvider), child: AsyncValueView( value: history, onRetry: () => ref.invalidate(incomeHistoryProvider), data: (data) { if (data.rows.isEmpty) { return ListView( padding: const EdgeInsets.all(16), children: const [ _PeriodChips(), SizedBox(height: 48), EmptyState( icon: Icons.history, message: 'Выплат за период не было.', ), ], ); } final months = _byMonth(data.rows); return ListView( padding: const EdgeInsets.all(16), children: [ const _PeriodChips(), const SizedBox(height: 12), Wrap( spacing: 12, runSpacing: 12, children: [ StatTile( label: 'Получено', value: MoneyText(data.totalRub, currency: 'RUB'), note: 'за выбранный период, до налога', ), StatTile( label: 'Удержано налога', value: MoneyText(data.taxWithheldRub, currency: 'RUB'), note: 'по данным брокера', ), StatTile( label: 'Месяцев с выплатами', value: Text('${months.length}'), note: 'строк в таблице: ${data.rows.length}', ), ], ), const SizedBox(height: 16), SectionCard( title: 'Выплаты по месяцам', child: SizedBox( height: 220, child: SingleChildScrollView( scrollDirection: Axis.horizontal, reverse: true, child: SizedBox( width: (months.length * 44).toDouble().clamp(320, double.infinity), child: _HistoryChart(months: months), ), ), ), ), const SizedBox(height: 16), SectionCard( title: 'Помесячно', child: _HistoryTable(rows: data.rows), ), ], ); }, ), ); } } class _PeriodChips extends ConsumerWidget { const _PeriodChips(); @override Widget build(BuildContext context, WidgetRef ref) { final months = ref.watch(historyMonthsProvider); return Wrap( spacing: 8, children: [ for (final m in const [12, 24, 36, 120]) ChoiceChip( label: Text(m >= 120 ? 'Всё время' : '$m мес'), selected: months == m, onSelected: (_) => ref.read(historyMonthsProvider.notifier).state = m, ), ], ); } } /// One bar per month, in rubles. Rows of several kinds inside a month are summed for the /// chart; the breakdown by kind stays in the table below. class _MonthTotal { _MonthTotal(this.month, this.amountRub); final DateTime month; final Decimal amountRub; } List<_MonthTotal> _byMonth(List rows) { final sums = {}; for (final r in rows) { final m = r.month; if (m == null) continue; final key = DateTime.utc(m.year, m.month); sums[key] = (sums[key] ?? Decimal.zero) + (Decimal.tryParse(r.amountRub ?? '') ?? Decimal.zero); } final keys = sums.keys.toList()..sort(); return [for (final k in keys) _MonthTotal(k, sums[k]!)]; } class _HistoryChart extends StatelessWidget { const _HistoryChart({required this.months}); final List<_MonthTotal> months; @override Widget build(BuildContext context) { final theme = Theme.of(context); final maxY = months.fold(0, (m, r) { final v = r.amountRub.toDouble(); return v > m ? v : m; }); return BarChart( BarChartData( maxY: maxY == 0 ? 1 : maxY * 1.15, gridData: const FlGridData(show: true, drawVerticalLine: false), borderData: FlBorderData(show: false), titlesData: FlTitlesData( topTitles: const AxisTitles(), rightTitles: const AxisTitles(), leftTitles: const AxisTitles( sideTitles: SideTitles(showTitles: true, reservedSize: 56), ), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 28, getTitlesWidget: (value, meta) { final i = value.round(); if (i < 0 || i >= months.length) return const SizedBox.shrink(); if (months.length > 14 && i % 3 != 0) return const SizedBox.shrink(); return Text(ruMonthYearShort(months[i].month), style: theme.textTheme.bodySmall); }, ), ), ), barTouchData: BarTouchData( touchTooltipData: BarTouchTooltipData( getTooltipItem: (group, _, rod, _) => BarTooltipItem( '${ruMonthYearShort(months[group.x].month)}\n' '${MoneyText.format(months[group.x].amountRub.toString(), 'RUB')}', theme.textTheme.bodySmall ?? const TextStyle(), ), ), ), barGroups: [ for (var i = 0; i < months.length; i++) BarChartGroupData( x: i, barRods: [ BarChartRodData( toY: months[i].amountRub.toDouble(), color: basisColor('paid'), width: 12, borderRadius: const BorderRadius.vertical(top: Radius.circular(2)), ), ], ), ], ), ); } } class _HistoryTable extends StatelessWidget { const _HistoryTable({required this.rows}); final List rows; @override Widget build(BuildContext context) { final headerStyle = Theme.of(context).textTheme.labelMedium; return SingleChildScrollView( scrollDirection: Axis.horizontal, child: DataTable( columns: [ DataColumn(label: Text('Месяц', style: headerStyle)), DataColumn(label: Text('Тип', style: headerStyle)), DataColumn(label: Text('Валюта', style: headerStyle)), DataColumn(label: Text('Сумма', style: headerStyle), numeric: true), DataColumn(label: Text('В рублях', style: headerStyle), numeric: true), DataColumn(label: Text('Налог', style: headerStyle), numeric: true), DataColumn(label: Text('Выплат', style: headerStyle), numeric: true), ], rows: [ for (final r in rows) DataRow( cells: [ DataCell(Text(r.month == null ? '—' : ruMonthYearShort(r.month!))), DataCell(Text(incomeKindLabel(r.kind))), DataCell(Text(r.currency)), DataCell(MoneyText(r.amount, currency: r.currency)), DataCell(r.amountRub == null ? const Text('—') : MoneyText(r.amountRub!, currency: 'RUB')), DataCell(r.taxWithheld == null ? const Text('—') : MoneyText(r.taxWithheld!, currency: r.currency)), DataCell(Text('${r.paymentCount}')), ], ), ], ), ); } }