import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:lucide_icons_flutter/lucide_icons.dart'; import '../../domain/project_task.dart'; import '../../providers/project_provider.dart'; import 'task_details_sheet.dart'; class TaskCard extends ConsumerWidget { final ProjectTask task; final VoidCallback onGenerateInvoice; final VoidCallback? onEdit; const TaskCard({ super.key, required this.task, required this.onGenerateInvoice, this.onEdit, }); Color _statusColor(String status) { switch (status) { case 'TODO': return Colors.blueGrey; case 'IN_PROGRESS': return Colors.blue; case 'REVIEW': return Colors.orange; case 'DONE': return Colors.green; default: return Colors.grey; } } String _statusLabel(String status) { switch (status) { case 'TODO': return 'To Do'; case 'IN_PROGRESS': return 'In Progress'; case 'REVIEW': return 'Review'; case 'DONE': return 'Done'; default: return status; } } @override Widget build(BuildContext context, WidgetRef ref) { final statusColor = _statusColor(task.status); return GestureDetector( onTap: () { showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (_) => TaskDetailsSheet(task: task), ); }, onLongPress: () => _showTaskOptions(context, ref), child: Container( margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey.shade200), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.04), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: Padding( padding: const EdgeInsets.all(14.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Status chip + overflow menu Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 4, ), decoration: BoxDecoration( color: statusColor.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(20), ), child: Text( _statusLabel(task.status), style: TextStyle( color: statusColor, fontSize: 11, fontWeight: FontWeight.w600, ), ), ), _buildStatusDropdown(context, ref), ], ), const SizedBox(height: 10), // Task title Text( task.title, style: const TextStyle( fontWeight: FontWeight.w600, fontSize: 15, height: 1.3, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), // Description preview if (task.description != null && task.description!.isNotEmpty) ...[ const SizedBox(height: 4), Text( task.description!, style: TextStyle( fontSize: 12, color: Colors.grey.shade600, height: 1.4, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ], const SizedBox(height: 12), // Bottom row: assignee + billable badge + invoice button Row( children: [ if (task.assigneeName != null && task.assigneeName!.isNotEmpty) ...[ Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 3, ), decoration: BoxDecoration( color: Colors.indigo.withValues(alpha: 0.08), borderRadius: BorderRadius.circular(16), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( LucideIcons.user, size: 12, color: Colors.indigo.shade400, ), const SizedBox(width: 4), Text( task.assigneeName!, style: TextStyle( fontSize: 11, color: Colors.indigo.shade600, fontWeight: FontWeight.w500, ), ), ], ), ), const SizedBox(width: 6), ], if (task.isBillable) Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 3, ), decoration: BoxDecoration( color: Colors.green.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(16), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( LucideIcons.indianRupee, size: 11, color: Colors.green.shade600, ), const SizedBox(width: 2), Text( 'Billable', style: TextStyle( color: Colors.green.shade700, fontSize: 11, fontWeight: FontWeight.w500, ), ), ], ), ), const Spacer(), if (task.isBillable && task.status == 'DONE') SizedBox( height: 28, child: TextButton.icon( style: TextButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 8), backgroundColor: Colors.green.withValues(alpha: 0.1), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), icon: Icon( LucideIcons.receipt, size: 13, color: Colors.green.shade700, ), label: Text( 'Invoice', style: TextStyle( fontSize: 11, color: Colors.green.shade700, fontWeight: FontWeight.w600, ), ), onPressed: onGenerateInvoice, ), ), ], ), ], ), ), ), ); } void _showTaskOptions(BuildContext context, WidgetRef ref) { showModalBottomSheet( context: context, backgroundColor: Colors.transparent, builder: (ctx) => Container( decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), child: SafeArea( child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( margin: const EdgeInsets.only(top: 12), width: 40, height: 4, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(2), ), ), const SizedBox(height: 16), Text( task.title, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 16, ), ), const SizedBox(height: 16), ListTile( leading: const Icon(LucideIcons.eye), title: const Text('View Details'), onTap: () { Navigator.pop(ctx); showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (_) => TaskDetailsSheet(task: task), ); }, ), if (onEdit != null) ListTile( leading: const Icon(LucideIcons.edit), title: const Text('Edit Task'), onTap: () { Navigator.pop(ctx); onEdit!(); }, ), ListTile( leading: const Icon(LucideIcons.trash2, color: Colors.red), title: const Text( 'Delete Task', style: TextStyle(color: Colors.red), ), onTap: () async { Navigator.pop(ctx); final confirm = await showDialog( context: context, builder: (c) => AlertDialog( title: const Text('Delete Task'), content: Text( 'Are you sure you want to delete "${task.title}"?', ), actions: [ TextButton( onPressed: () => Navigator.pop(c, false), child: const Text('Cancel'), ), TextButton( onPressed: () => Navigator.pop(c, true), child: const Text( 'Delete', style: TextStyle(color: Colors.red), ), ), ], ), ); if (confirm == true) { try { await ref .read(projectRepositoryProvider) .deleteTask(task.id); ref.invalidate(projectTasksProvider(task.projectId)); } catch (e) { if (context.mounted) { ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text('Error: $e'))); } } } }, ), const SizedBox(height: 16), ], ), ), ), ); } Widget _buildStatusDropdown(BuildContext context, WidgetRef ref) { return PopupMenuButton( initialValue: task.status, tooltip: 'Change Status', padding: EdgeInsets.zero, constraints: const BoxConstraints(), child: Icon( LucideIcons.moreVertical, size: 18, color: Colors.grey.shade500, ), onSelected: (newStatus) async { if (newStatus != task.status) { try { await ref .read(projectRepositoryProvider) .updateTask(taskId: task.id, status: newStatus); ref.invalidate(projectTasksProvider(task.projectId)); } catch (e) { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Failed to update status: $e')), ); } } } }, itemBuilder: (context) => [ _buildStatusMenuItem('TODO', 'To Do', Colors.blueGrey), _buildStatusMenuItem('IN_PROGRESS', 'In Progress', Colors.blue), _buildStatusMenuItem('REVIEW', 'Review', Colors.orange), _buildStatusMenuItem('DONE', 'Done', Colors.green), ], ); } PopupMenuItem _buildStatusMenuItem( String value, String label, Color color, ) { return PopupMenuItem( value: value, child: Row( children: [ Container( width: 8, height: 8, decoration: BoxDecoration(color: color, shape: BoxShape.circle), ), const SizedBox(width: 8), Text(label), if (task.status == value) ...[ const Spacer(), const Icon(LucideIcons.check, size: 16, color: Colors.green), ], ], ), ); } }