import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; import '../../core/providers/auth_provider.dart'; import '../../core/theme/app_theme.dart'; import '../../core/widgets/gradient_app_bar.dart'; import '../../models/job.dart'; import '../../models/tenant.dart'; import 'reports_repository.dart'; class ReportsScreen extends ConsumerStatefulWidget { const ReportsScreen({super.key}); @override ConsumerState createState() => _ReportsScreenState(); } class _ReportsScreenState extends ConsumerState with SingleTickerProviderStateMixin { late TabController _tabController; late Future _future; @override void initState() { super.initState(); _tabController = TabController(length: 4, vsync: this); _tabController.addListener(() { if (mounted) setState(() {}); }); _load(); } @override void dispose() { _tabController.dispose(); super.dispose(); } void _load() { final auth = ref.read(authProvider); final tenantId = auth.activeTenant!.tenant.id; final kind = auth.activeTenant!.tenant.kind; setState(() { _future = ReportsRepository.instance.load(tenantId, kind); }); } @override Widget build(BuildContext context) { final kind = ref.watch(authProvider).activeTenant?.tenant.kind ?? TenantKind.lab; final counterpartLabel = kind == TenantKind.lab ? 'Klinikler' : 'Laboratuvarlar'; return Scaffold( backgroundColor: AppColors.background, appBar: GradientAppBar( title: 'Raporlar', category: 'YÖNETİCİ', actions: [ IconButton( icon: const Icon(Icons.refresh_rounded), onPressed: _load, tooltip: 'Yenile', ), ], ), body: FutureBuilder( future: _future, builder: (context, 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: [ const Icon(Icons.error_outline_rounded, color: AppColors.cancelled, size: 48), const SizedBox(height: 12), Text('Veriler yüklenemedi', 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 m = snap.data!; return Column( children: [ // Tab bar Container( color: AppColors.surface, child: TabBar( controller: _tabController, labelColor: AppColors.accent, unselectedLabelColor: AppColors.textSecondary, indicatorColor: AppColors.accent, indicatorSize: TabBarIndicatorSize.tab, labelStyle: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600), tabs: [ const Tab(text: 'Özet'), const Tab(text: 'Finans'), const Tab(text: 'Aktivite'), Tab(text: counterpartLabel), ], ), ), Expanded( child: TabBarView( controller: _tabController, children: [ _SummaryTab(metrics: m), _FinanceTab(metrics: m), _ActivityTab(metrics: m), _CounterpartTab(metrics: m, label: counterpartLabel), ], ), ), ], ); }, ), ); } } // ── Shared layout helpers ───────────────────────────────────────────────────── class _TabBody extends StatelessWidget { const _TabBody({required this.children}); final List children; @override Widget build(BuildContext context) => ListView( padding: const EdgeInsets.fromLTRB(16, 16, 16, 32), children: children, ); } class _Card extends StatelessWidget { const _Card({required this.child, this.padding = const EdgeInsets.all(16)}); final Widget child; final EdgeInsets padding; @override Widget build(BuildContext context) => Container( margin: const EdgeInsets.only(bottom: 12), padding: padding, decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(16), border: Border.all(color: AppColors.border), boxShadow: [BoxShadow(color: Colors.black.withValues(alpha: 0.03), blurRadius: 8, offset: const Offset(0, 2))], ), child: child, ); } class _SectionHeader extends StatelessWidget { const _SectionHeader(this.title, {this.subtitle}); final String title; final String? subtitle; @override Widget build(BuildContext context) => Padding( padding: const EdgeInsets.only(bottom: 10, top: 4), child: Row( children: [ Expanded( child: Text(title, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w700, color: AppColors.textPrimary)), ), if (subtitle != null) Text(subtitle!, style: const TextStyle(fontSize: 12, color: AppColors.textMuted)), ], ), ); } // ── KPI Chips ───────────────────────────────────────────────────────────────── class _KpiRow extends StatelessWidget { const _KpiRow({required this.metrics}); final ReportMetrics metrics; @override Widget build(BuildContext context) { final fmt = NumberFormat.currency(locale: 'tr_TR', symbol: metrics.currency, decimalDigits: 0); return SingleChildScrollView( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Row( children: [ _Kpi(label: 'Aktif İşler', value: '${metrics.activeJobs}', icon: Icons.work_outline_rounded, color: AppColors.inProgress), const SizedBox(width: 10), _Kpi(label: 'Bu Ay Tamamlandı', value: '${metrics.completedThisMonth}', icon: Icons.check_circle_outline_rounded, color: AppColors.success), const SizedBox(width: 10), _Kpi(label: 'Bekleyen Gelir', value: fmt.format(metrics.pendingRevenue), icon: Icons.hourglass_empty_rounded, color: AppColors.pending), const SizedBox(width: 10), _Kpi(label: 'Ort. Süre', value: '${metrics.avgCompletionDays.toStringAsFixed(1)} gün', icon: Icons.timer_outlined, color: AppColors.accent), const SizedBox(width: 10), _Kpi(label: 'Revizyon Oranı', value: '%${metrics.revisionRate.toStringAsFixed(0)}', icon: Icons.loop_rounded, color: metrics.revisionRate > 20 ? AppColors.cancelled : AppColors.textSecondary), if (metrics.overdueJobs > 0) ...[ const SizedBox(width: 10), _Kpi(label: 'Gecikmiş', value: '${metrics.overdueJobs}', icon: Icons.schedule_rounded, color: AppColors.cancelled), ], ], ), ); } } class _Kpi extends StatelessWidget { const _Kpi({required this.label, required this.value, required this.icon, required this.color}); final String label, value; final IconData icon; final Color color; @override Widget build(BuildContext context) => Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(14), border: Border.all(color: AppColors.border), boxShadow: [BoxShadow(color: Colors.black.withValues(alpha: 0.03), blurRadius: 6)], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, size: 14, color: color), const SizedBox(width: 4), Text(label, style: const TextStyle(fontSize: 11, color: AppColors.textMuted)), ], ), const SizedBox(height: 6), Text(value, style: TextStyle(fontSize: 20, fontWeight: FontWeight.w800, color: color)), ], ), ); } // ── Özet Tab ────────────────────────────────────────────────────────────────── class _SummaryTab extends StatelessWidget { const _SummaryTab({required this.metrics}); final ReportMetrics metrics; @override Widget build(BuildContext context) { return _TabBody(children: [ _KpiRow(metrics: metrics), const _SectionHeader('İş Durumu Dağılımı'), _Card( child: Column( children: _statusOrder.where((s) => metrics.jobsByStatus.containsKey(s)).map((s) { final count = metrics.jobsByStatus[s] ?? 0; final total = metrics.jobsByStatus.values.fold(0, (a, b) => a + b); return _HBarRow( label: _statusLabel(s), value: count, max: total, color: _statusColor(s), ); }).toList(), ), ), const _SectionHeader('Son 6 Aylık İş Trendi'), _Card(child: _VBarChart(data: metrics.monthlyCounts, color: AppColors.accent)), ]); } static const _statusOrder = ['in_progress', 'pending', 'sent', 'delivered', 'cancelled']; static String _statusLabel(String s) => switch (s) { 'pending' => 'Bekliyor', 'in_progress' => 'İşlemde', 'sent' => 'Gönderildi', 'delivered' => 'Teslim', 'cancelled' => 'İptal', _ => s, }; static Color _statusColor(String s) => switch (s) { 'pending' => AppColors.pending, 'in_progress' => AppColors.inProgress, 'sent' => AppColors.accent, 'delivered' => AppColors.success, 'cancelled' => AppColors.cancelled, _ => AppColors.textMuted, }; } // ── Finans Tab ──────────────────────────────────────────────────────────────── class _FinanceTab extends StatelessWidget { const _FinanceTab({required this.metrics}); final ReportMetrics metrics; @override Widget build(BuildContext context) { final fmt = NumberFormat.currency(locale: 'tr_TR', symbol: metrics.currency, decimalDigits: 0); final total = metrics.totalRevenue + metrics.pendingRevenue; return _TabBody(children: [ const _SectionHeader('Gelir Özeti'), Row( children: [ Expanded( child: _Card( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ Container(width: 8, height: 8, decoration: BoxDecoration(color: AppColors.success, shape: BoxShape.circle)), const SizedBox(width: 6), const Text('Tahsil Edildi', style: TextStyle(fontSize: 12, color: AppColors.textMuted)), ]), const SizedBox(height: 6), Text(fmt.format(metrics.totalRevenue), style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w800, color: AppColors.success)), ], ), ), ), const SizedBox(width: 10), Expanded( child: _Card( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ Container(width: 8, height: 8, decoration: BoxDecoration(color: AppColors.pending, shape: BoxShape.circle)), const SizedBox(width: 6), const Text('Bekleyen', style: TextStyle(fontSize: 12, color: AppColors.textMuted)), ]), const SizedBox(height: 6), Text(fmt.format(metrics.pendingRevenue), style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w800, color: AppColors.pending)), ], ), ), ), ], ), if (total > 0) _Card( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Tahsilat Oranı', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600)), const SizedBox(height: 10), ClipRRect( borderRadius: BorderRadius.circular(6), child: LinearProgressIndicator( value: total > 0 ? metrics.totalRevenue / total : 0, minHeight: 12, backgroundColor: AppColors.pendingBg, valueColor: const AlwaysStoppedAnimation(AppColors.success), ), ), const SizedBox(height: 6), Text('${(metrics.totalRevenue / total * 100).toStringAsFixed(0)}% tahsil edildi', style: const TextStyle(fontSize: 12, color: AppColors.textMuted)), ], ), ), const SizedBox(height: 4), const _SectionHeader('Aylık Gelir Trendi'), _Card(child: _VBarChart( data: metrics.monthlyRevenue.map((m) => MonthlyCount(year: m.year, month: m.month, count: m.amount.round())).toList(), color: AppColors.success, formatValue: (v) => NumberFormat.compactCurrency(locale: 'tr_TR', symbol: metrics.currency, decimalDigits: 0).format(v), )), ]); } } // ── Aktivite Tab ────────────────────────────────────────────────────────────── class _ActivityTab extends StatelessWidget { const _ActivityTab({required this.metrics}); final ReportMetrics metrics; @override Widget build(BuildContext context) { final items = metrics.recentActivity; if (items.isEmpty) { return const Center( child: Text('Henüz aktivite kaydı yok.', style: TextStyle(color: AppColors.textMuted)), ); } return _TabBody(children: [ const _SectionHeader('Son İşlemler'), _Card( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), child: Column( children: items.asMap().entries.map((entry) { final i = entry.key; final item = entry.value; return _ActivityRow(item: item, isLast: i == items.length - 1); }).toList(), ), ), ]); } } class _ActivityRow extends StatelessWidget { const _ActivityRow({required this.item, required this.isLast}); final ActivityItem item; final bool isLast; @override Widget build(BuildContext context) { final color = item.isNegative ? AppColors.cancelled : item.isPositive ? AppColors.success : AppColors.accent; final icon = switch (item.action) { 'accepted' => Icons.check_circle_outline_rounded, 'handed_to_clinic' => Icons.send_rounded, 'approved' => Icons.thumb_up_outlined, 'revision_requested' => Icons.loop_rounded, 'delivered' => Icons.local_shipping_outlined, 'cancelled' => Icons.cancel_outlined, _ => Icons.history_rounded, }; final df = DateFormat('dd.MM.yy HH:mm'); return IntrinsicHeight( child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Timeline line + dot SizedBox( width: 28, child: Column( children: [ Container( width: 24, height: 24, decoration: BoxDecoration( color: color.withValues(alpha: 0.12), shape: BoxShape.circle, ), child: Icon(icon, size: 13, color: color), ), if (!isLast) Expanded(child: Container(width: 1.5, color: AppColors.border)), ], ), ), const SizedBox(width: 10), Expanded( child: Padding( padding: EdgeInsets.only(bottom: isLast ? 0 : 10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(item.actionLabel, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: AppColors.textPrimary)), if (item.patientCode != null && item.patientCode!.isNotEmpty) Text(item.patientCode!, style: const TextStyle(fontSize: 11, color: AppColors.accent)), if (item.note != null && item.note!.isNotEmpty) Text(item.note!, style: const TextStyle(fontSize: 11, color: AppColors.textMuted)), const SizedBox(height: 2), Text(df.format(item.createdAt), style: const TextStyle(fontSize: 10, color: AppColors.textMuted)), ], ), ), ), ], ), ); } } // ── Counterpart Tab ─────────────────────────────────────────────────────────── class _CounterpartTab extends StatelessWidget { const _CounterpartTab({required this.metrics, required this.label}); final ReportMetrics metrics; final String label; @override Widget build(BuildContext context) { final stats = metrics.counterpartStats; if (stats.isEmpty) { return Center( child: Text('$label henüz yok.', style: const TextStyle(color: AppColors.textMuted)), ); } final fmt = NumberFormat.currency(locale: 'tr_TR', symbol: metrics.currency, decimalDigits: 0); final maxJobs = stats.fold(0, (m, s) => s.jobCount > m ? s.jobCount : m); return _TabBody(children: [ const _SectionHeader('Protez Türü Dağılımı'), _Card( child: Column( children: _buildTypeRows(metrics.byProstheticType), ), ), _SectionHeader('En Aktif $label'), _Card( child: Column( children: stats.asMap().entries.map((entry) { final i = entry.key; final s = entry.value; return Padding( padding: EdgeInsets.only(bottom: i < stats.length - 1 ? 12 : 0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( width: 22, height: 22, decoration: BoxDecoration( color: AppColors.inProgressBg, shape: BoxShape.circle, ), child: Center( child: Text('${i + 1}', style: const TextStyle(fontSize: 11, fontWeight: FontWeight.w700, color: AppColors.inProgress)), ), ), const SizedBox(width: 8), Expanded( child: Text(s.name, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: AppColors.textPrimary), maxLines: 1, overflow: TextOverflow.ellipsis), ), Text('${s.jobCount} iş', style: const TextStyle(fontSize: 12, color: AppColors.textSecondary, fontWeight: FontWeight.w500)), ], ), const SizedBox(height: 6), ClipRRect( borderRadius: BorderRadius.circular(4), child: LinearProgressIndicator( value: maxJobs > 0 ? s.jobCount / maxJobs : 0, minHeight: 6, backgroundColor: AppColors.surfaceVariant, valueColor: const AlwaysStoppedAnimation(AppColors.accent), ), ), if (s.totalRevenue > 0) Padding( padding: const EdgeInsets.only(top: 4), child: Text( '${fmt.format(s.paidRevenue)} tahsil · ${fmt.format(s.pendingRevenue)} bekliyor', style: const TextStyle(fontSize: 11, color: AppColors.textMuted), ), ), ], ), ); }).toList(), ), ), ]); } List _buildTypeRows(Map byType) { if (byType.isEmpty) return [const Text('Veri yok', style: TextStyle(color: AppColors.textMuted))]; final sorted = byType.entries.toList()..sort((a, b) => b.value.compareTo(a.value)); final max = sorted.first.value; return sorted.map((e) => _HBarRow( label: _typeLabel(e.key), value: e.value, max: max, color: AppColors.accent, )).toList(); } static String _typeLabel(String s) => switch (s) { 'metal_porselen' => 'Metal Porselen', 'zirkonyum' => 'Zirkonyum', 'implant_ustu_zirkonyum'=> 'İmplant Üstü', 'gecici' => 'Geçici', 'e_max' => 'E-Max', 'tam_protez' => 'Tam Protez', 'parsiyel' => 'Parsiyel', _ => 'Diğer', }; } // ── Chart widgets ───────────────────────────────────────────────────────────── class _HBarRow extends StatelessWidget { const _HBarRow({required this.label, required this.value, required this.max, required this.color}); final String label; final int value, max; final Color color; @override Widget build(BuildContext context) { final fraction = max > 0 ? value / max : 0.0; return Padding( padding: const EdgeInsets.symmetric(vertical: 5), child: Row( children: [ SizedBox( width: 100, child: Text(label, style: const TextStyle(fontSize: 12, color: AppColors.textSecondary), maxLines: 1, overflow: TextOverflow.ellipsis), ), Expanded( child: ClipRRect( borderRadius: BorderRadius.circular(4), child: Stack( children: [ Container(height: 22, color: AppColors.surfaceVariant), FractionallySizedBox( widthFactor: fraction, child: Container( height: 22, decoration: BoxDecoration( color: color.withValues(alpha: 0.8), borderRadius: BorderRadius.circular(4), ), ), ), ], ), ), ), const SizedBox(width: 8), SizedBox( width: 28, child: Text('$value', style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w700, color: AppColors.textPrimary), textAlign: TextAlign.right), ), ], ), ); } } class _VBarChart extends StatelessWidget { const _VBarChart({required this.data, required this.color, this.formatValue}); final List data; final Color color; final String Function(int)? formatValue; @override Widget build(BuildContext context) { if (data.isEmpty) return const SizedBox.shrink(); final maxVal = data.fold(0, (m, e) => e.count > m ? e.count : m); final fmt = formatValue ?? (v) => '$v'; return SizedBox( height: 140, child: Row( crossAxisAlignment: CrossAxisAlignment.end, children: data.map((d) { final fraction = maxVal > 0 ? d.count / maxVal : 0.0; return Expanded( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 2), child: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ if (d.count > 0) Text(fmt(d.count), style: const TextStyle(fontSize: 9, color: AppColors.textMuted), textAlign: TextAlign.center), const SizedBox(height: 2), AnimatedContainer( duration: const Duration(milliseconds: 600), curve: Curves.easeOut, height: (fraction * 90).clamp(2, 90), decoration: BoxDecoration( color: color, borderRadius: const BorderRadius.vertical(top: Radius.circular(4)), ), ), const SizedBox(height: 4), Text(d.label, style: const TextStyle(fontSize: 10, color: AppColors.textMuted), textAlign: TextAlign.center), ], ), ), ); }).toList(), ), ); } }