diff --git a/lib/screens/tasks/task_detail_screen.dart b/lib/screens/tasks/task_detail_screen.dart index 4e1c7d37..950ab41e 100644 --- a/lib/screens/tasks/task_detail_screen.dart +++ b/lib/screens/tasks/task_detail_screen.dart @@ -2227,7 +2227,14 @@ class _TaskDetailScreenState extends ConsumerState : (profileById[l.actorId]?.fullName ?? l.actorId!); switch (l.actionType) { case 'created': - timeline.add(_activityRow('Task created', actorName, l.createdAt)); + timeline.add( + _activityRow( + 'Task created', + actorName, + l.createdAt, + icon: Icons.add_task, + ), + ); break; case 'assigned': final meta = l.meta ?? {}; @@ -2241,6 +2248,7 @@ class _TaskDetailScreenState extends ConsumerState auto ? 'Auto-assigned to $name' : 'Assigned to $name', actorName, l.createdAt, + icon: Icons.person_add, ), ); break; @@ -2252,7 +2260,12 @@ class _TaskDetailScreenState extends ConsumerState .map((id) => profileById[id]?.fullName ?? id) .join(', '); timeline.add( - _activityRow('Reassigned to $toNames', actorName, l.createdAt), + _activityRow( + 'Reassigned to $toNames', + actorName, + l.createdAt, + icon: Icons.swap_horiz, + ), ); break; case 'started': @@ -2268,7 +2281,14 @@ class _TaskDetailScreenState extends ConsumerState label = 'Task started — Response: ${_formatDuration(responseDuration)} ($assigneeName responded at ${AppTime.formatDate(resp)} ${AppTime.formatTime(resp)})'; } - timeline.add(_activityRow(label, actorName, l.createdAt)); + timeline.add( + _activityRow( + label, + actorName, + l.createdAt, + icon: Icons.play_arrow, + ), + ); } break; case 'completed': @@ -2283,15 +2303,69 @@ class _TaskDetailScreenState extends ConsumerState 'Task completed — Execution: ${_formatDuration(exec)} (${AppTime.formatDate(start)} ${AppTime.formatTime(start)} → ${AppTime.formatDate(end)} ${AppTime.formatTime(end)})'; } } - timeline.add(_activityRow(label, actorName, l.createdAt)); + timeline.add( + _activityRow( + label, + actorName, + l.createdAt, + icon: Icons.check_circle, + ), + ); } break; + case 'cancelled': + final meta = l.meta ?? {}; + final reason = (meta['reason'] as String?) ?? ''; + final base = reason.isNotEmpty + ? 'Task cancelled — $reason' + : 'Task cancelled'; + timeline.add( + _activityRow(base, actorName, l.createdAt, icon: Icons.cancel), + ); + break; default: - timeline.add(_activityRow(l.actionType, actorName, l.createdAt)); + timeline.add( + _activityRow( + l.actionType, + actorName, + l.createdAt, + icon: Icons.info, + ), + ); } } } + // If the task is cancelled but no explicit cancelled activity row exists, + // show a fallback using the task's cancellation fields. + final hasCancelledLog = logs.any((e) => e.actionType == 'cancelled'); + if (task.status == 'cancelled' && !hasCancelledLog) { + final reason = task.cancellationReason; + final at = task.cancelledAt ?? AppTime.now(); + String inferredActor = 'System'; + try { + final candidates = logs + .where((e) => e.actorId != null && e.createdAt.isBefore(at)) + .toList(); + if (candidates.isNotEmpty) { + candidates.sort((a, b) => a.createdAt.compareTo(b.createdAt)); + final last = candidates.last; + inferredActor = profileById[last.actorId]?.fullName ?? last.actorId!; + } + } catch (_) {} + + timeline.add( + _activityRow( + reason != null && reason.isNotEmpty + ? 'Task cancelled — $reason' + : 'Task cancelled', + inferredActor, + at, + icon: Icons.cancel, + ), + ); + } + // Response and execution times are now merged into the related // 'Task started' and 'Task completed' timeline entries above. @@ -2305,17 +2379,18 @@ class _TaskDetailScreenState extends ConsumerState // TAT helpers removed; timings are shown inline in the activity timeline. - Widget _activityRow(String title, String actor, DateTime at) { + Widget _activityRow( + String title, + String actor, + DateTime at, { + IconData icon = Icons.circle, + }) { return Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Icon( - Icons.circle, - size: 12, - color: Theme.of(context).colorScheme.primary, - ), + Icon(icon, size: 16, color: Theme.of(context).colorScheme.primary), const SizedBox(width: 8), Expanded( child: Column(