import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/theme/app_theme.dart'; import '../../models/finance.dart'; import '../../providers/finance_provider.dart'; import '../../widgets/data_table_card.dart'; import '../../widgets/page_scaffold.dart'; class FinanceScreen extends ConsumerWidget { const FinanceScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { return PageScaffold( title: '财务管理', tabs: const [ Tab(text: '全部记录'), Tab(text: '应付账款'), Tab(text: '应收账款'), ], tabViews: [ _FinanceTab(typeFilter: ''), _FinanceTab(typeFilter: 'payable'), _FinanceTab(typeFilter: 'receivable'), ], ); } } // Each tab has its own independent state — avoids shared-provider conflicts class _FinanceTab extends ConsumerStatefulWidget { final String typeFilter; const _FinanceTab({required this.typeFilter}); @override ConsumerState<_FinanceTab> createState() => _FinanceTabState(); } class _FinanceTabState extends ConsumerState<_FinanceTab> { late String _month; int _page = 1; // We drive fetches by maintaining a Future locally, bypassing the global provider late Future> _future; @override void initState() { super.initState(); final now = DateTime.now(); _month = '${now.year}-${now.month.toString().padLeft(2, '0')}'; _fetch(); } void _fetch() { _future = ref .read(financeRepositoryProvider) .listRecords( type: widget.typeFilter.isEmpty ? null : widget.typeFilter, month: _month, page: _page, pageSize: 50, ) .then((r) => r.data); } void _refetch() { setState(() => _fetch()); } @override Widget build(BuildContext context) { return FutureBuilder>( future: _future, builder: (context, snap) { if (snap.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } if (snap.hasError) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('加载失败:${snap.error}', style: const TextStyle(color: AppTheme.danger)), const SizedBox(height: 12), ElevatedButton( onPressed: _refetch, child: const Text('重试')), ], ), ); } return _buildContent(snap.data ?? []); }, ); } Widget _buildContent(List records) { final totalAmount = records.fold(0.0, (s, r) => s + r.amount); final totalBalance = records.fold(0.0, (s, r) => s + r.balance); final totalPaid = totalAmount - totalBalance; return Column( children: [ // Summary bar (only for type-filtered tabs) if (widget.typeFilter.isNotEmpty && records.isNotEmpty) Container( color: AppTheme.background, padding: const EdgeInsets.all(12), child: Row( children: [ _SummaryCard( title: '本期总额', value: '¥${(totalAmount / 10000).toStringAsFixed(2)}万', icon: Icons.account_balance_wallet, color: AppTheme.primary, ), const SizedBox(width: 12), _SummaryCard( title: '已结清', value: '¥${(totalPaid / 10000).toStringAsFixed(2)}万', icon: Icons.check_circle, color: AppTheme.success, ), const SizedBox(width: 12), _SummaryCard( title: '未结清', value: '¥${(totalBalance / 10000).toStringAsFixed(2)}万', icon: Icons.pending_actions, color: AppTheme.danger, ), ], ), ), if (widget.typeFilter.isNotEmpty && records.isNotEmpty) const Divider(height: 1), Expanded( child: DataTableCard( totalCount: records.length, page: _page, onPageChanged: (p) { setState(() { _page = p; _fetch(); }); }, toolbar: Row( children: [ const Spacer(), _MonthSelector( value: _month, onChanged: (v) { _month = v; _page = 1; _refetch(); }, ), ], ), columns: const [ DataColumn(label: Text('日期')), DataColumn(label: Text('类型')), DataColumn(label: Text('往来单位')), DataColumn(label: Text('关联单据')), DataColumn(label: Text('金额'), numeric: true), DataColumn(label: Text('余额'), numeric: true), DataColumn(label: Text('备注')), ], rows: records.isEmpty ? [ DataRow(cells: [ const DataCell(SizedBox()), const DataCell(Text('暂无记录', style: TextStyle(color: AppTheme.textSecondary))), const DataCell(SizedBox()), const DataCell(SizedBox()), const DataCell(SizedBox()), const DataCell(SizedBox()), const DataCell(SizedBox()), ]) ] : records .map((r) => DataRow(cells: [ DataCell(Text( r.recordDate?.substring(0, 10) ?? '-', style: const TextStyle(fontSize: 12), )), DataCell(_TypeBadge(r.typeLabel)), DataCell(SizedBox( width: 160, child: Text(r.partnerName ?? '-', overflow: TextOverflow.ellipsis), )), DataCell(Text( r.refType != null && r.refId != null ? '${r.refType!.replaceAll('_', '-')}#${r.refId}' : '-', style: const TextStyle( fontSize: 11, fontFamily: 'monospace', color: AppTheme.primary), )), DataCell(Text( '¥${r.amount.toStringAsFixed(2)}', style: const TextStyle(fontWeight: FontWeight.w500), )), DataCell(Text( '¥${r.balance.toStringAsFixed(2)}', style: TextStyle( color: r.balance > 0 ? AppTheme.danger : AppTheme.textSecondary, fontWeight: FontWeight.w600, ), )), DataCell(SizedBox( width: 160, child: Text(r.remark ?? '-', overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 12, color: AppTheme.textSecondary)), )), ])) .toList(), ), ), ], ); } } class _TypeBadge extends StatelessWidget { final String label; const _TypeBadge(this.label); @override Widget build(BuildContext context) { Color bg, fg; switch (label) { case '应收账款': bg = const Color(0xFFE3F2FD); fg = AppTheme.primary; break; case '应付账款': bg = const Color(0xFFFFEBEE); fg = AppTheme.danger; break; case '收款': bg = const Color(0xFFE8F5E9); fg = AppTheme.success; break; case '付款': bg = const Color(0xFFFFF3E0); fg = AppTheme.accent; break; default: bg = const Color(0xFFF5F5F5); fg = AppTheme.textSecondary; } return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration(color: bg, borderRadius: BorderRadius.circular(3)), child: Text(label, style: TextStyle( color: fg, fontSize: 12, fontWeight: FontWeight.w500)), ); } } class _SummaryCard extends StatelessWidget { final String title; final String value; final IconData icon; final Color color; const _SummaryCard({ required this.title, required this.value, required this.icon, required this.color, }); @override Widget build(BuildContext context) { return Expanded( child: Container( height: 72, padding: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( color: AppTheme.surface, borderRadius: BorderRadius.circular(4), border: Border.all(color: AppTheme.border, width: 0.5), ), child: Row( children: [ Container( width: 40, height: 40, decoration: BoxDecoration( color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Icon(icon, color: color, size: 22), ), const SizedBox(width: 12), Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Text(title, style: const TextStyle( fontSize: 12, color: AppTheme.textSecondary)), const SizedBox(height: 4), Text(value, style: TextStyle( fontSize: 18, fontWeight: FontWeight.w700, color: color)), ], ), ], ), ), ); } } class _MonthSelector extends StatelessWidget { final String value; final ValueChanged onChanged; const _MonthSelector({required this.value, required this.onChanged}); @override Widget build(BuildContext context) { final now = DateTime.now(); final months = List.generate(12, (i) { final d = DateTime(now.year, now.month - i, 1); return '${d.year}-${d.month.toString().padLeft(2, '0')}'; }); final safeValue = months.contains(value) ? value : months.first; return Container( height: 36, padding: const EdgeInsets.symmetric(horizontal: 8), decoration: BoxDecoration( border: Border.all(color: AppTheme.border), borderRadius: BorderRadius.circular(4), color: AppTheme.surface, ), child: DropdownButtonHideUnderline( child: DropdownButton( value: safeValue, items: months .map((m) => DropdownMenuItem( value: m, child: Text(m, style: const TextStyle(fontSize: 13)))) .toList(), onChanged: (v) => onChanged(v!), style: const TextStyle(fontSize: 13, color: AppTheme.textPrimary), ), ), ); } }