diff --git a/lib/screens/tasks/tasks_list_screen.dart b/lib/screens/tasks/tasks_list_screen.dart index b82d9cf4..e3592c74 100644 --- a/lib/screens/tasks/tasks_list_screen.dart +++ b/lib/screens/tasks/tasks_list_screen.dart @@ -151,9 +151,7 @@ class _TasksListScreenState extends ConsumerState { dateRange: _selectedDateRange, latestAssigneeByTaskId: latestAssigneeByTaskId, ); - final summaryDashboard = _StatusSummaryRow( - counts: _taskStatusCounts(filteredTasks), - ); + final filterHeader = Wrap( spacing: 12, runSpacing: 12, @@ -241,127 +239,67 @@ class _TasksListScreenState extends ConsumerState { ), ], ); - final listBody = TasQAdaptiveList( - items: filteredTasks, - onRowTap: (task) => context.go('/tasks/${task.id}'), - summaryDashboard: summaryDashboard, - filterHeader: filterHeader, - onRequestRefresh: () { - // For server-side pagination, update the query provider - ref.read(tasksQueryProvider.notifier).state = const TaskQuery( - offset: 0, - limit: 50, - ); - }, - isLoading: false, - columns: [ - TasQColumn( - header: 'Task #', - technical: true, - cellBuilder: (context, task) => - Text(task.taskNumber ?? task.id), - ), - TasQColumn( - header: 'Subject', - cellBuilder: (context, task) { - final ticket = task.ticketId == null - ? null - : ticketById[task.ticketId]; - return Text( - task.title.isNotEmpty - ? task.title - : (ticket?.subject ?? 'Task ${task.id}'), - ); - }, - ), - TasQColumn( - header: 'Office', - cellBuilder: (context, task) { - final ticket = task.ticketId == null - ? null - : ticketById[task.ticketId]; - final officeId = ticket?.officeId ?? task.officeId; - return Text( - officeId == null - ? 'Unassigned office' - : (officeById[officeId]?.name ?? officeId), - ); - }, - ), - TasQColumn( - header: 'Assigned Agent', - cellBuilder: (context, task) { - final assigneeId = latestAssigneeByTaskId[task.id]; - return Text(_assignedAgent(profileById, assigneeId)); - }, - ), - TasQColumn( - header: 'Status', - cellBuilder: (context, task) => Row( - mainAxisSize: MainAxisSize.min, - children: [ - _StatusBadge(status: task.status), - if (task.status == 'completed' && - task.hasIncompleteDetails) ...[ - const SizedBox(width: 4), - const Icon( - Icons.warning_amber_rounded, - size: 16, - color: Colors.orange, - ), - ], - ], - ), - ), - TasQColumn( - header: 'Timestamp', - technical: true, - cellBuilder: (context, task) => - Text(_formatTimestamp(task.createdAt)), - ), - ], - mobileTileBuilder: (context, task, actions) { - 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 assigned = _assignedAgent( - profileById, - latestAssigneeByTaskId[task.id], - ); - final subtitle = _buildSubtitle(officeName, task.status); - final hasMention = _hasTaskMention(notificationsAsync, task); - final typingState = ref.watch( - typingIndicatorProvider(task.id), - ); - final showTyping = typingState.userIds.isNotEmpty; - return Card( - child: ListTile( - leading: _buildQueueBadge(context, task), - dense: true, - visualDensity: VisualDensity.compact, - title: Text( - task.title.isNotEmpty - ? task.title - : (ticket?.subject ?? - 'Task ${task.taskNumber ?? task.id}'), - ), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(subtitle), - const SizedBox(height: 2), - Text('Assigned: $assigned'), - const SizedBox(height: 4), - MonoText('ID ${task.taskNumber ?? task.id}'), - const SizedBox(height: 2), - Text(_formatTimestamp(task.createdAt)), - ], - ), - trailing: Row( + // reusable helper for rendering a list given a subset of tasks + Widget makeList(List tasksList) { + final summary = _StatusSummaryRow( + counts: _taskStatusCounts(tasksList), + ); + return TasQAdaptiveList( + items: tasksList, + onRowTap: (task) => context.go('/tasks/${task.id}'), + summaryDashboard: summary, + filterHeader: filterHeader, + onRequestRefresh: () { + // For server-side pagination, update the query provider + ref.read(tasksQueryProvider.notifier).state = + const TaskQuery(offset: 0, limit: 50); + }, + isLoading: false, + columns: [ + TasQColumn( + header: 'Task #', + technical: true, + cellBuilder: (context, task) => + Text(task.taskNumber ?? task.id), + ), + TasQColumn( + header: 'Subject', + cellBuilder: (context, task) { + final ticket = task.ticketId == null + ? null + : ticketById[task.ticketId]; + return Text( + task.title.isNotEmpty + ? task.title + : (ticket?.subject ?? 'Task ${task.id}'), + ); + }, + ), + TasQColumn( + header: 'Office', + cellBuilder: (context, task) { + final ticket = task.ticketId == null + ? null + : ticketById[task.ticketId]; + final officeId = ticket?.officeId ?? task.officeId; + return Text( + officeId == null + ? 'Unassigned office' + : (officeById[officeId]?.name ?? officeId), + ); + }, + ), + TasQColumn( + header: 'Assigned Agent', + cellBuilder: (context, task) { + final assigneeId = latestAssigneeByTaskId[task.id]; + return Text(_assignedAgent(profileById, assigneeId)); + }, + ), + TasQColumn( + header: 'Status', + cellBuilder: (context, task) => Row( mainAxisSize: MainAxisSize.min, children: [ _StatusBadge(status: task.status), @@ -374,29 +312,108 @@ class _TasksListScreenState extends ConsumerState { color: Colors.orange, ), ], - 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}'), ), - ); - }, - ); + TasQColumn( + header: 'Timestamp', + technical: true, + cellBuilder: (context, task) => + Text(_formatTimestamp(task.createdAt)), + ), + ], + mobileTileBuilder: (context, task, actions) { + 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 assigned = _assignedAgent( + profileById, + latestAssigneeByTaskId[task.id], + ); + final subtitle = _buildSubtitle(officeName, task.status); + final hasMention = _hasTaskMention( + notificationsAsync, + task, + ); + final typingState = ref.watch( + typingIndicatorProvider(task.id), + ); + final showTyping = typingState.userIds.isNotEmpty; + + return Card( + child: ListTile( + leading: _buildQueueBadge(context, task), + dense: true, + visualDensity: VisualDensity.compact, + title: Text( + task.title.isNotEmpty + ? task.title + : (ticket?.subject ?? + 'Task ${task.taskNumber ?? task.id}'), + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(subtitle), + const SizedBox(height: 2), + Text('Assigned: $assigned'), + const SizedBox(height: 4), + MonoText('ID ${task.taskNumber ?? task.id}'), + const SizedBox(height: 2), + Text(_formatTimestamp(task.createdAt)), + ], + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + _StatusBadge(status: task.status), + if (task.status == 'completed' && + task.hasIncompleteDetails) ...[ + const SizedBox(width: 4), + const Icon( + Icons.warning_amber_rounded, + size: 16, + color: Colors.orange, + ), + ], + 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}'), + ), + ); + }, + ); + } + + final currentUserId = profileAsync.valueOrNull?.id; + final myTasks = currentUserId == null + ? [] + : filteredTasks + .where( + (t) => latestAssigneeByTaskId[t.id] == currentUserId, + ) + .toList(); return Column( mainAxisSize: MainAxisSize.max, @@ -415,7 +432,29 @@ class _TasksListScreenState extends ConsumerState { ), ), ), - Expanded(child: listBody), + Expanded( + child: DefaultTabController( + length: 2, + child: Column( + children: [ + const TabBar( + tabs: [ + Tab(text: 'My Tasks'), + Tab(text: 'All Tasks'), + ], + ), + Expanded( + child: TabBarView( + children: [ + makeList(myTasks), + makeList(filteredTasks), + ], + ), + ), + ], + ), + ), + ), ], ); },