import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; import '../../core/auth/auth_notifier.dart'; import '../../core/auth/auth_state.dart'; import '../../core/theme/app_theme.dart'; import '../../core/api/api_client.dart'; import '../../shared/models/task.dart'; import '../../shared/models/contract.dart'; import '../../shared/providers/lookup_providers.dart'; // Dashboard stats provider final dashboardProvider = FutureProvider.autoDispose<_DashboardData>((ref) async { final futures = await Future.wait([ apiDio.get('/tasks/admin', queryParameters: {'page': 1, 'limit': 500}), apiDio.get('/surat', queryParameters: {'page': 1, 'limit': 500}), apiDio.get('/contracts', queryParameters: {'page': 1, 'limit': 500}), ]); final tasks = PagedTasks.fromJson(futures[0].data); final pendingCount = (futures[1].data['total'] as int? ?? 0); final contracts = PagedContracts.fromJson(futures[2].data); return _DashboardData( tasks: tasks, suratPendingCount: pendingCount, contracts: contracts, ); }); class _DashboardData { final PagedTasks tasks; final int suratPendingCount; final PagedContracts contracts; _DashboardData({required this.tasks, required this.suratPendingCount, required this.contracts}); } class DashboardScreen extends ConsumerWidget { const DashboardScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final authState = ref.watch(authProvider); final user = authState is AuthAuthenticated ? authState.user : null; final dashAsync = ref.watch(dashboardProvider); final unreadAsync = ref.watch(notifUnreadCountProvider); return Scaffold( appBar: AppBar( title: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Dashboard', style: TextStyle(fontSize: 17, fontWeight: FontWeight.w700)), if (user != null) Text('Hi, ${user.username}', style: const TextStyle(fontSize: 12, color: AppColors.textSub, fontWeight: FontWeight.w400)), ], ), actions: [ Stack( children: [ IconButton( icon: const Icon(Icons.notifications_outlined), onPressed: () => context.push('/notifications'), ), unreadAsync.whenData((count) => count > 0 ? Positioned( top: 8, right: 8, child: Container( width: 14, height: 14, decoration: const BoxDecoration(color: AppColors.danger, shape: BoxShape.circle), child: Center(child: Text('${count > 9 ? 9 : count}', style: const TextStyle(color: Colors.white, fontSize: 8, fontWeight: FontWeight.w700))), ), ) : const SizedBox()).value ?? const SizedBox(), ], ), IconButton(icon: const Icon(Icons.person_outline), onPressed: () => context.push('/profile')), ], ), body: RefreshIndicator( color: AppColors.primary, onRefresh: () async { ref.invalidate(dashboardProvider); ref.invalidate(notifUnreadCountProvider); }, child: dashAsync.when( loading: () => const Center(child: CircularProgressIndicator()), error: (e, _) => Center(child: Text(e.toString())), data: (data) { final todoCount = data.tasks.items.where((t) => t.status == 'todo').length; final inProgressCount = data.tasks.items.where((t) => t.status == 'in_progress').length; final doneCount = data.tasks.items.where((t) => t.status == 'done').length; final activeTasks = data.tasks.items .where((t) => t.status != 'done') .toList() ..sort((a, b) { const order = {'in_progress': 0, 'todo': 1}; return (order[a.status] ?? 2).compareTo(order[b.status] ?? 2); }); return ListView( padding: const EdgeInsets.all(16), children: [ // Stat cards GridView.count( crossAxisCount: 2, shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), mainAxisSpacing: 10, crossAxisSpacing: 10, childAspectRatio: 1.6, children: [ _StatCard( icon: Icons.assignment_outlined, label: 'Active Tasks', value: '${data.tasks.items.where((t) => t.status != 'done').length}', color: AppColors.blue, bgColor: AppColors.blueLight, onTap: () => context.go('/tasks'), ), _StatCard( icon: Icons.mail_outline, label: 'Pending Letters', value: '${data.suratPendingCount}', color: AppColors.warning, bgColor: AppColors.warningLight, onTap: () => context.go('/surat'), ), _StatCard( icon: Icons.description_outlined, label: 'Active Contracts', value: '${data.contracts.items.where((c) => c.status == 'Active').length}', color: AppColors.primary, bgColor: AppColors.primaryLight, onTap: () => context.go('/contracts'), ), _StatCard( icon: Icons.warning_amber_outlined, label: 'Expiring Contracts', value: '${data.contracts.items.where((c) => c.isExpiringSoon && c.status == 'Active').length}', color: AppColors.danger, bgColor: AppColors.dangerLight, onTap: () => context.go('/contracts'), ), ], ), const SizedBox(height: 20), // Quick actions const Text('Quick Actions', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w700, color: AppColors.text)), const SizedBox(height: 10), Row(children: [ _QuickAction(icon: Icons.add_task, label: 'New Task', onTap: () => context.push('/tasks/create')), const SizedBox(width: 10), _QuickAction(icon: Icons.mail_outlined, label: 'New Letter', onTap: () => context.push('/surat/create')), const SizedBox(width: 10), _QuickAction(icon: Icons.upload_file_outlined, label: 'Upload Contract', onTap: () => context.push('/contracts/upload')), ]), const SizedBox(height: 20), // Task Overview header Row(children: [ const Expanded(child: Text('Task Overview', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w700, color: AppColors.text))), TextButton( onPressed: () => context.go('/tasks'), child: const Text('View All →', style: TextStyle(fontSize: 13)), ), ]), const SizedBox(height: 8), // Status summary pills Row(children: [ _StatusPill(label: 'To Do', count: todoCount, color: AppColors.blue, bgColor: AppColors.blueLight), const SizedBox(width: 8), _StatusPill(label: 'In Progress', count: inProgressCount, color: AppColors.warning, bgColor: AppColors.warningLight), const SizedBox(width: 8), _StatusPill(label: 'Done', count: doneCount, color: AppColors.primary, bgColor: AppColors.primaryLight), ]), const SizedBox(height: 12), // Active tasks: in_progress first, then todo if (activeTasks.isEmpty) Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(10), border: Border.all(color: AppColors.border), ), child: const Center( child: Text('All tasks completed! 🎉', style: TextStyle(fontSize: 13, color: AppColors.textSub)), ), ) else ...activeTasks.take(6).map((t) => Padding( padding: const EdgeInsets.only(bottom: 8), child: GestureDetector( onTap: () => context.push('/tasks/${t.id}'), child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(10), border: Border.all( color: t.isOverdue ? AppColors.dangerLight : AppColors.border, ), ), child: Row(children: [ Container( width: 8, height: 8, decoration: BoxDecoration( color: t.status == 'in_progress' ? AppColors.warning : AppColors.blue, shape: BoxShape.circle, ), ), const SizedBox(width: 10), Expanded(child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(t.title, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500), maxLines: 1, overflow: TextOverflow.ellipsis), if (t.assignedUsername != null) Text(t.assignedUsername!, style: const TextStyle(fontSize: 11, color: AppColors.textSub)), ], )), const SizedBox(width: 8), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: t.status == 'in_progress' ? AppColors.warningLight : AppColors.blueLight, borderRadius: BorderRadius.circular(4), ), child: Text( t.status == 'in_progress' ? 'In Progress' : 'To Do', style: TextStyle( fontSize: 10, fontWeight: FontWeight.w600, color: t.status == 'in_progress' ? AppColors.warning : AppColors.blue, ), ), ), if (t.dueDate != null) ...[ const SizedBox(height: 2), Text( DateFormat('d MMM').format(t.dueDate!), style: TextStyle( fontSize: 10, color: t.isOverdue ? AppColors.danger : AppColors.textSub, ), ), ], ], ), ]), ), ), )), ], ); }, ), ), ); } } // ── Status pill ─────────────────────────────────────────────────────────────── class _StatusPill extends StatelessWidget { final String label; final int count; final Color color; final Color bgColor; const _StatusPill({ required this.label, required this.count, required this.color, required this.bgColor, }); @override Widget build(BuildContext context) => Expanded( child: Container( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 10), decoration: BoxDecoration(color: bgColor, borderRadius: BorderRadius.circular(8)), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('$count', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w800, color: color)), Text(label, style: TextStyle(fontSize: 10, fontWeight: FontWeight.w500, color: color)), ], ), ), ); } // ── Stat card ───────────────────────────────────────────────────────────────── class _StatCard extends StatelessWidget { final IconData icon; final String label; final String value; final Color color; final Color bgColor; final VoidCallback onTap; const _StatCard({ required this.icon, required this.label, required this.value, required this.color, required this.bgColor, required this.onTap, }); @override Widget build(BuildContext context) => GestureDetector( onTap: onTap, child: Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(12), border: Border.all(color: AppColors.border), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: const EdgeInsets.all(6), decoration: BoxDecoration(color: bgColor, borderRadius: BorderRadius.circular(8)), child: Icon(icon, size: 18, color: color), ), const Spacer(), Text(value, style: TextStyle(fontSize: 22, fontWeight: FontWeight.w800, color: color)), Text(label, style: const TextStyle(fontSize: 11, color: AppColors.textSub)), ], ), ), ); } // ── Quick action ────────────────────────────────────────────────────────────── class _QuickAction extends StatelessWidget { final IconData icon; final String label; final VoidCallback onTap; const _QuickAction({required this.icon, required this.label, required this.onTap}); @override Widget build(BuildContext context) => Expanded( child: GestureDetector( onTap: onTap, child: Container( padding: const EdgeInsets.symmetric(vertical: 14), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(12), border: Border.all(color: AppColors.border), ), child: Column( children: [ Icon(icon, color: AppColors.primary, size: 24), const SizedBox(height: 6), Text(label, style: const TextStyle(fontSize: 11, fontWeight: FontWeight.w600, color: AppColors.text), textAlign: TextAlign.center), ], ), ), ), ); }