import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../core/providers/auth_provider.dart'; import '../../../core/providers/locale_provider.dart'; import '../../../core/theme/app_theme.dart'; import '../../../core/utils/currency_formatter.dart'; import '../../../core/widgets/gradient_app_bar.dart'; import '../../../core/widgets/pill_tabs.dart'; import '../../../models/finance_entry.dart'; import 'clinic_finance_repository.dart'; enum _FinanceSort { newestFirst, byAmountDesc, byAmountAsc } class ClinicFinanceScreen extends ConsumerStatefulWidget { const ClinicFinanceScreen({super.key}); @override ConsumerState createState() => _ClinicFinanceScreenState(); } class _ClinicFinanceScreenState extends ConsumerState with SingleTickerProviderStateMixin { late TabController _tabController; late Future<_ClinicFinanceHeaderData> _headerFuture; _FinanceSort _sort = _FinanceSort.newestFirst; @override void initState() { super.initState(); _tabController = TabController(length: 2, vsync: this); _tabController.addListener(() { if (mounted) setState(() {}); }); _loadSummary(); } @override void dispose() { _tabController.dispose(); super.dispose(); } void _loadSummary() { final tenantId = ref.read(authProvider).activeTenant!.tenant.id; setState(() { _headerFuture = Future.wait([ ClinicFinanceRepository.instance.summary(tenantId), ClinicFinanceRepository.instance.byCounterparty(tenantId), ]).then( (results) => _ClinicFinanceHeaderData( summary: results[0] as Map, counterparties: results[1] as List, ), ); }); } Future _showSortOptions() async { final s = ref.read(stringsProvider); final result = await showSortSheet( context, title: s.sort, options: [s.sortNewest, s.sortAmountDesc, s.sortAmountAsc], current: _sort.index, ); if (result != null) { setState(() => _sort = _FinanceSort.values[result]); } } @override Widget build(BuildContext context) { final isSortActive = _sort != _FinanceSort.newestFirst; final s = ref.watch(stringsProvider); final currencyCode = ref.watch(authProvider).activeTenant?.tenant.defaultCurrency ?? 'TRY'; return Scaffold( backgroundColor: AppColors.background, appBar: GradientAppBar( title: s.finance, category: s.clinicCategory, actions: [ IconButton( onPressed: _showSortOptions, tooltip: 'Sırala', icon: Badge( isLabelVisible: isSortActive, smallSize: 8, backgroundColor: AppColors.accent, child: const Icon(Icons.sort_rounded), ), ), ], ), body: Column( children: [ FutureBuilder<_ClinicFinanceHeaderData>( future: _headerFuture, builder: (ctx, snap) { if (snap.connectionState == ConnectionState.waiting) { return const LinearProgressIndicator(color: AppColors.accent); } final data = snap.data ?? const _ClinicFinanceHeaderData( summary: {'pending': 0.0, 'paid': 0.0}, counterparties: [], ); return Column( children: [ Padding( padding: const EdgeInsets.all(16), child: Row( children: [ Expanded( child: _SummaryCard( label: 'Açık Borç', amount: data.summary['pending'] ?? 0.0, currencyCode: currencyCode, color: AppColors.pending, bgColor: AppColors.pendingBg, icon: Icons.hourglass_empty_rounded, ), ), const SizedBox(width: 12), Expanded( child: _SummaryCard( label: 'Onaylanan Ödeme', amount: data.summary['paid'] ?? 0.0, currencyCode: currencyCode, color: AppColors.success, bgColor: AppColors.successBg, icon: Icons.check_circle_outline, ), ), ], ), ), if (data.counterparties.isNotEmpty) Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 12), child: _CounterpartySummaryList( title: 'Laboratuvar Bazlı Borç', items: data.counterparties, currencyCode: currencyCode, ), ), ], ); }, ), PillTabs( tabs: [s.pending, s.collected], selected: _tabController.index, onSelect: (i) => _tabController.animateTo(i), ), Expanded( child: TabBarView( controller: _tabController, children: [ _FinanceTab( status: 'pending', sort: _sort, onPaymentMade: _loadSummary, currencyCode: currencyCode, ), _FinanceTab( status: 'paid', sort: _sort, onPaymentMade: _loadSummary, currencyCode: currencyCode, ), ], ), ), ], ), ); } } class _ClinicFinanceHeaderData { const _ClinicFinanceHeaderData({ required this.summary, required this.counterparties, }); final Map summary; final List counterparties; } class _SummaryCard extends StatelessWidget { const _SummaryCard({ required this.label, required this.amount, required this.currencyCode, required this.color, required this.bgColor, required this.icon, }); final String label; final double amount; final String currencyCode; final Color color; final Color bgColor; final IconData icon; @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(16), border: Border.all(color: AppColors.border), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.06), blurRadius: 12, offset: const Offset(0, 4)) ], ), child: Row( children: [ Container( width: 44, height: 44, decoration: BoxDecoration( color: bgColor, borderRadius: BorderRadius.circular(12)), child: Icon(icon, color: color, size: 22), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( CurrencyFormatter.format(amount, currencyCode), style: TextStyle( fontSize: 20, fontWeight: FontWeight.w800, color: color, height: 1), ), const SizedBox(height: 3), Text(label, style: const TextStyle( fontSize: 12, color: AppColors.textSecondary, fontWeight: FontWeight.w500)), ], ), ), ], ), ); } } class _FinanceTab extends ConsumerStatefulWidget { const _FinanceTab({ required this.status, required this.sort, required this.onPaymentMade, required this.currencyCode, }); final String status; final _FinanceSort sort; final VoidCallback onPaymentMade; final String currencyCode; @override ConsumerState<_FinanceTab> createState() => _FinanceTabState(); } class _FinanceTabState extends ConsumerState<_FinanceTab> { late Future> _future; @override void initState() { super.initState(); _load(); } void _load() { final tenantId = ref.read(authProvider).activeTenant!.tenant.id; setState(() { _future = ClinicFinanceRepository.instance.listEntries( tenantId, status: widget.status, limit: 100, ); }); } List _sorted(List entries) { final list = List.from(entries); switch (widget.sort) { case _FinanceSort.newestFirst: list.sort((a, b) { final da = a.dateCreated != null ? DateTime.tryParse(a.dateCreated!) : null; final db = b.dateCreated != null ? DateTime.tryParse(b.dateCreated!) : null; if (da == null && db == null) return 0; if (da == null) return 1; if (db == null) return -1; return db.compareTo(da); }); case _FinanceSort.byAmountDesc: list.sort((a, b) => b.amount.compareTo(a.amount)); case _FinanceSort.byAmountAsc: list.sort((a, b) => a.amount.compareTo(b.amount)); } return list; } Future _reportPayment(FinanceEntry entry) async { final confirmed = await showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('Ödeme Bildir'), content: Text( '${entry.counterpartyName ?? "Bu kayıt"} için ' '${CurrencyFormatter.format(entry.amount, widget.currencyCode)} tutarındaki borcu ' 'laboratuvara ödendi olarak bildirmek istiyor musunuz?', ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx, false), child: const Text('İptal'), ), FilledButton( onPressed: () => Navigator.pop(ctx, true), child: const Text('Bildir'), ), ], ), ); if (confirmed != true || !mounted) return; try { await ClinicFinanceRepository.instance.reportPayment(entry.id); _load(); widget.onPaymentMade(); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Ödeme bildirildi. Laboratuvar onayı bekleniyor.'), ), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Hata: $e')), ); } } } @override Widget build(BuildContext context) { return RefreshIndicator( color: AppColors.accent, onRefresh: () async => _load(), child: FutureBuilder>( future: _future, builder: (ctx, snap) { if (snap.connectionState == ConnectionState.waiting) { return const Center( child: CircularProgressIndicator(color: AppColors.accent)); } if (snap.hasError) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 64, height: 64, decoration: BoxDecoration( color: AppColors.cancelledBg, borderRadius: BorderRadius.circular(16)), child: const Icon(Icons.wifi_off_rounded, color: AppColors.cancelled, size: 30), ), const SizedBox(height: 16), Text('Hata: ${snap.error}', style: const TextStyle(color: AppColors.textSecondary)), const SizedBox(height: 12), FilledButton.icon( onPressed: _load, icon: const Icon(Icons.refresh_rounded, size: 18), label: const Text('Tekrar Dene'), ), ], ), ); } final entries = _sorted(snap.data!); if (entries.isEmpty) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 72, height: 72, decoration: BoxDecoration( color: AppColors.inProgressBg, borderRadius: BorderRadius.circular(20)), child: const Icon(Icons.receipt_long_outlined, color: AppColors.inProgress, size: 32), ), const SizedBox(height: 16), const Text( 'Kayıt bulunamadı', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary), ), ], ), ); } return ListView.builder( padding: const EdgeInsets.fromLTRB(16, 12, 16, 24), itemCount: entries.length, itemBuilder: (context, index) { final entry = entries[index]; final isPending = entry.status == FinanceStatus.pending; final isReported = entry.status == FinanceStatus.reported; final statusColor = isPending ? AppColors.pending : isReported ? AppColors.accent : AppColors.success; final statusBg = isPending ? AppColors.pendingBg : isReported ? AppColors.inProgressBg : AppColors.successBg; return Padding( padding: const EdgeInsets.only(bottom: 10), child: Material( color: AppColors.surface, borderRadius: BorderRadius.circular(14), child: InkWell( onTap: isPending ? () => _reportPayment(entry) : null, borderRadius: BorderRadius.circular(14), child: Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( borderRadius: BorderRadius.circular(14), border: Border.all(color: AppColors.border), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.04), blurRadius: 8, offset: const Offset(0, 2)) ]), child: Row( children: [ Container( width: 44, height: 44, decoration: BoxDecoration( color: statusBg, borderRadius: BorderRadius.circular(12)), child: Icon( isPending ? Icons.hourglass_empty_rounded : isReported ? Icons.verified_outlined : Icons.check_circle_outline, color: statusColor, size: 22, ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( entry.counterpartyName ?? 'Bilinmiyor', style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w600, color: AppColors.textPrimary), ), ), Text( CurrencyFormatter.format( entry.amount, widget.currencyCode), style: TextStyle( fontSize: 15, fontWeight: FontWeight.w700, color: statusColor), ), ], ), if (entry.patientCode != null) ...[ const SizedBox(height: 2), Text( 'Protokol: ${entry.patientCode}', style: const TextStyle( fontSize: 12, color: AppColors.textSecondary), ), ], if (entry.dateCreated != null) ...[ const SizedBox(height: 2), Text( _formatDate(entry.dateCreated!), style: const TextStyle( fontSize: 12, color: AppColors.textMuted), ), ], if (isReported) ...[ const SizedBox(height: 4), const Text( 'Ödeme bildirildi, laboratuvar onayı bekleniyor.', style: TextStyle( fontSize: 12, color: AppColors.textSecondary, ), ), ] else if (isPending) ...[ const SizedBox(height: 4), const Text( 'Dokunarak ödeme bildirimi yapabilirsiniz.', style: TextStyle( fontSize: 12, color: AppColors.textSecondary, ), ), ], ], ), ), if (isPending) ...[ const SizedBox(width: 8), Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 5), decoration: BoxDecoration( color: AppColors.pending, borderRadius: BorderRadius.circular(8), ), child: const Text( 'Öde', style: TextStyle( color: Colors.white, fontSize: 12, fontWeight: FontWeight.w600, ), ), ), ], ], ), ), ), ), ); }, ); }, ), ); } String _formatDate(String dateStr) { try { final d = DateTime.parse(dateStr); return '${d.day.toString().padLeft(2, '0')}.${d.month.toString().padLeft(2, '0')}.${d.year}'; } catch (_) { return dateStr; } } } class _CounterpartySummaryList extends StatelessWidget { const _CounterpartySummaryList({ required this.title, required this.items, required this.currencyCode, }); final String title; final List items; final String currencyCode; @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(16), border: Border.all(color: AppColors.border), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w700, color: AppColors.textPrimary, ), ), const SizedBox(height: 10), for (final item in items.take(5)) ...[ Row( children: [ Expanded( child: Text( item.counterpartyName, style: const TextStyle( fontSize: 13, color: AppColors.textPrimary, fontWeight: FontWeight.w600, ), ), ), Text( CurrencyFormatter.format(item.pendingAmount, currencyCode), style: const TextStyle( fontSize: 13, color: AppColors.pending, fontWeight: FontWeight.w700, ), ), ], ), const SizedBox(height: 8), ], ], ), ); } }