Auto Task Assignment

This commit is contained in:
2026-02-18 23:14:50 +08:00
parent 372928d8e7
commit 5ec57a1cec
12 changed files with 1109 additions and 138 deletions
+30 -4
View File
@@ -6,6 +6,7 @@ import '../../models/notification_item.dart';
import '../../models/office.dart';
import '../../models/profile.dart';
import '../../models/task.dart';
import '../../models/task_assignment.dart';
import '../../models/ticket.dart';
import '../../providers/notifications_provider.dart';
import '../../providers/profile_provider.dart';
@@ -55,6 +56,7 @@ class _TasksListScreenState extends ConsumerState<TasksListScreen> {
final profileAsync = ref.watch(currentProfileProvider);
final notificationsAsync = ref.watch(notificationsProvider);
final profilesAsync = ref.watch(profilesProvider);
final assignmentsAsync = ref.watch(taskAssignmentsProvider);
final canCreate = profileAsync.maybeWhen(
data: (profile) =>
@@ -102,6 +104,22 @@ class _TasksListScreenState extends ConsumerState<TasksListScreen> {
];
final staffOptions = _staffOptions(profilesAsync.valueOrNull);
final statusOptions = _taskStatusOptions(tasks);
// derive latest assignee per task from task assignments stream
final assignments =
assignmentsAsync.valueOrNull ?? <TaskAssignment>[];
final assignmentsByTask = <String, TaskAssignment>{};
for (final a in assignments) {
final current = assignmentsByTask[a.taskId];
if (current == null || a.createdAt.isAfter(current.createdAt)) {
assignmentsByTask[a.taskId] = a;
}
}
final latestAssigneeByTaskId = <String, String?>{};
for (final entry in assignmentsByTask.entries) {
latestAssigneeByTaskId[entry.key] = entry.value.userId;
}
final filteredTasks = _applyTaskFilters(
tasks,
ticketById: ticketById,
@@ -110,6 +128,7 @@ class _TasksListScreenState extends ConsumerState<TasksListScreen> {
status: _selectedStatus,
assigneeId: _selectedAssigneeId,
dateRange: _selectedDateRange,
latestAssigneeByTaskId: latestAssigneeByTaskId,
);
final summaryDashboard = _StatusSummaryRow(
counts: _taskStatusCounts(filteredTasks),
@@ -249,8 +268,10 @@ class _TasksListScreenState extends ConsumerState<TasksListScreen> {
),
TasQColumn<Task>(
header: 'Assigned Agent',
cellBuilder: (context, task) =>
Text(_assignedAgent(profileById, task.creatorId)),
cellBuilder: (context, task) {
final assigneeId = latestAssigneeByTaskId[task.id];
return Text(_assignedAgent(profileById, assigneeId));
},
),
TasQColumn<Task>(
header: 'Status',
@@ -271,7 +292,10 @@ class _TasksListScreenState extends ConsumerState<TasksListScreen> {
final officeName = officeId == null
? 'Unassigned office'
: (officeById[officeId]?.name ?? officeId);
final assigned = _assignedAgent(profileById, task.creatorId);
final assigned = _assignedAgent(
profileById,
latestAssigneeByTaskId[task.id],
);
final subtitle = _buildSubtitle(officeName, task.status);
final hasMention = _hasTaskMention(notificationsAsync, task);
final typingState = ref.watch(
@@ -558,6 +582,7 @@ List<Task> _applyTaskFilters(
required String? status,
required String? assigneeId,
required DateTimeRange? dateRange,
required Map<String, String?> latestAssigneeByTaskId,
}) {
final query = subjectQuery.trim().toLowerCase();
return tasks.where((task) {
@@ -577,7 +602,8 @@ List<Task> _applyTaskFilters(
if (status != null && task.status != status) {
return false;
}
if (assigneeId != null && task.creatorId != assigneeId) {
final currentAssignee = latestAssigneeByTaskId[task.id];
if (assigneeId != null && currentAssignee != assigneeId) {
return false;
}
if (dateRange != null) {