import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../models/notification_item.dart'; import '../../models/task.dart'; import '../../providers/notifications_provider.dart'; import '../../providers/profile_provider.dart'; import '../../providers/tasks_provider.dart'; import '../../providers/tickets_provider.dart'; import '../../providers/typing_provider.dart'; import '../../widgets/responsive_body.dart'; import '../../widgets/typing_dots.dart'; class TasksListScreen extends ConsumerWidget { const TasksListScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final tasksAsync = ref.watch(tasksProvider); final ticketsAsync = ref.watch(ticketsProvider); final officesAsync = ref.watch(officesProvider); final profileAsync = ref.watch(currentProfileProvider); final notificationsAsync = ref.watch(notificationsProvider); final canCreate = profileAsync.maybeWhen( data: (profile) => profile != null && (profile.role == 'admin' || profile.role == 'dispatcher' || profile.role == 'it_staff'), orElse: () => false, ); final ticketById = { for (final ticket in ticketsAsync.valueOrNull ?? []) ticket.id: ticket, }; final officeById = { for (final office in officesAsync.valueOrNull ?? []) office.id: office, }; return Scaffold( body: ResponsiveBody( child: tasksAsync.when( data: (tasks) { if (tasks.isEmpty) { return const Center(child: Text('No tasks yet.')); } return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Padding( padding: const EdgeInsets.only(top: 16, bottom: 8), child: Align( alignment: Alignment.center, child: Text( 'Tasks', textAlign: TextAlign.center, style: Theme.of(context).textTheme.titleLarge?.copyWith( fontWeight: FontWeight.w700, ), ), ), ), Expanded( child: ListView.separated( padding: const EdgeInsets.only(bottom: 24), itemCount: tasks.length, separatorBuilder: (context, index) => const SizedBox(height: 12), itemBuilder: (context, index) { final task = tasks[index]; final ticketId = task.ticketId; final ticket = ticketId == null ? null : ticketById[ticketId]; final officeId = ticket?.officeId ?? task.officeId; final officeName = officeId == null ? 'Unassigned office' : (officeById[officeId]?.name ?? officeId); final subtitle = _buildSubtitle(officeName, task.status); final hasMention = _hasTaskMention( notificationsAsync, task, ); final typingChannelId = task.id; final typingState = ref.watch( typingIndicatorProvider(typingChannelId), ); final showTyping = typingState.userIds.isNotEmpty; return ListTile( leading: _buildQueueBadge(context, task), title: Text( task.title.isNotEmpty ? task.title : (ticket?.subject ?? 'Task ${task.id}'), ), subtitle: Text(subtitle), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ _buildStatusChip(context, task.status), if (showTyping) ...[ const SizedBox(width: 6), TypingDots( size: 6, color: Theme.of(context).colorScheme.primary, ), ], if (hasMention) const Padding( padding: EdgeInsets.only(left: 8), child: Icon( Icons.circle, size: 10, color: Colors.red, ), ), ], ), onTap: () => context.go('/tasks/${task.id}'), ); }, ), ), ], ); }, loading: () => const Center(child: CircularProgressIndicator()), error: (error, _) => Center(child: Text('Failed to load tasks: $error')), ), ), floatingActionButton: canCreate ? FloatingActionButton.extended( onPressed: () => _showCreateTaskDialog(context, ref), icon: const Icon(Icons.add), label: const Text('New Task'), ) : null, ); } Future _showCreateTaskDialog( BuildContext context, WidgetRef ref, ) async { final titleController = TextEditingController(); final descriptionController = TextEditingController(); String? selectedOfficeId; await showDialog( context: context, builder: (dialogContext) { return StatefulBuilder( builder: (context, setState) { final officesAsync = ref.watch(officesProvider); return AlertDialog( title: const Text('Create Task'), content: SizedBox( width: 360, child: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( controller: titleController, decoration: const InputDecoration( labelText: 'Task title', ), ), const SizedBox(height: 12), TextField( controller: descriptionController, decoration: const InputDecoration( labelText: 'Description', ), maxLines: 3, ), const SizedBox(height: 12), officesAsync.when( data: (offices) { if (offices.isEmpty) { return const Text('No offices available.'); } selectedOfficeId ??= offices.first.id; return DropdownButtonFormField( initialValue: selectedOfficeId, decoration: const InputDecoration( labelText: 'Office', ), items: offices .map( (office) => DropdownMenuItem( value: office.id, child: Text(office.name), ), ) .toList(), onChanged: (value) => setState(() => selectedOfficeId = value), ); }, loading: () => const Align( alignment: Alignment.centerLeft, child: CircularProgressIndicator(), ), error: (error, _) => Text('Failed to load offices: $error'), ), ], ), ), actions: [ TextButton( onPressed: () => Navigator.of(dialogContext).pop(), child: const Text('Cancel'), ), FilledButton( onPressed: () async { final title = titleController.text.trim(); final description = descriptionController.text.trim(); final officeId = selectedOfficeId; if (title.isEmpty || officeId == null) { return; } await ref .read(tasksControllerProvider) .createTask( title: title, description: description, officeId: officeId, ); if (context.mounted) { Navigator.of(dialogContext).pop(); } }, child: const Text('Create'), ), ], ); }, ); }, ); } bool _hasTaskMention( AsyncValue> notificationsAsync, Task task, ) { return notificationsAsync.maybeWhen( data: (items) => items.any( (item) => item.isUnread && (item.taskId == task.id || item.ticketId == task.ticketId), ), orElse: () => false, ); } Widget _buildQueueBadge(BuildContext context, Task task) { final queueOrder = task.queueOrder; final isQueued = task.status == 'queued'; if (!isQueued || queueOrder == null) { return const Icon(Icons.fact_check_outlined); } return Container( width: 40, height: 40, alignment: Alignment.center, decoration: BoxDecoration( color: Theme.of(context).colorScheme.primaryContainer, borderRadius: BorderRadius.circular(12), ), child: Text( '#$queueOrder', style: Theme.of(context).textTheme.labelMedium?.copyWith( fontWeight: FontWeight.w700, color: Theme.of(context).colorScheme.onPrimaryContainer, ), ), ); } String _buildSubtitle(String officeName, String status) { final statusLabel = status.toUpperCase(); return '$officeName ยท $statusLabel'; } Widget _buildStatusChip(BuildContext context, String status) { return Chip( label: Text(status.toUpperCase()), backgroundColor: _statusColor(context, status), labelStyle: TextStyle( color: _statusTextColor(context, status), fontWeight: FontWeight.w600, ), ); } Color _statusColor(BuildContext context, String status) { return switch (status) { 'queued' => Colors.blueGrey.shade200, 'in_progress' => Colors.blue.shade300, 'completed' => Colors.green.shade300, _ => Theme.of(context).colorScheme.surfaceContainerHighest, }; } Color _statusTextColor(BuildContext context, String status) { return switch (status) { 'queued' => Colors.blueGrey.shade900, 'in_progress' => Colors.blue.shade900, 'completed' => Colors.green.shade900, _ => Theme.of(context).colorScheme.onSurfaceVariant, }; } }