Added saving indicator when assigning a task
This commit is contained in:
@@ -5,6 +5,7 @@ import '../models/profile.dart';
|
||||
import '../providers/profile_provider.dart';
|
||||
import '../providers/tasks_provider.dart';
|
||||
import '../theme/app_surfaces.dart';
|
||||
import '../utils/snackbar.dart';
|
||||
|
||||
class TaskAssignmentSection extends ConsumerWidget {
|
||||
const TaskAssignmentSection({
|
||||
@@ -39,36 +40,11 @@ class TaskAssignmentSection extends ConsumerWidget {
|
||||
.toList();
|
||||
final assignedIds = assignedForTask.map((a) => a.userId).toSet();
|
||||
|
||||
final activeTaskIds = tasks
|
||||
.where(
|
||||
(task) => task.status == 'queued' || task.status == 'in_progress',
|
||||
)
|
||||
.map((task) => task.id)
|
||||
.toSet();
|
||||
|
||||
final activeAssignmentsByUser = <String, Set<String>>{};
|
||||
for (final assignment in assignments) {
|
||||
if (!activeTaskIds.contains(assignment.taskId)) {
|
||||
continue;
|
||||
}
|
||||
activeAssignmentsByUser
|
||||
.putIfAbsent(assignment.userId, () => <String>{})
|
||||
.add(assignment.taskId);
|
||||
}
|
||||
|
||||
bool isVacant(String userId) {
|
||||
final active = activeAssignmentsByUser[userId];
|
||||
if (active == null || active.isEmpty) {
|
||||
return true;
|
||||
}
|
||||
return active.length == 1 && active.contains(taskId);
|
||||
}
|
||||
|
||||
final eligibleStaff = itStaff
|
||||
.where(
|
||||
(profile) => isVacant(profile.id) || assignedIds.contains(profile.id),
|
||||
)
|
||||
.toList();
|
||||
// With concurrent assignments allowed we no longer restrict the
|
||||
// eligible list based on whether the staff member already has an active
|
||||
// task. All IT staff can be assigned to any number of tasks at once. Keep
|
||||
// the original sort order derived from itStaff rather than filtering.
|
||||
final eligibleStaff = List<Profile>.from(itStaff);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -137,6 +113,9 @@ class TaskAssignmentSection extends ConsumerWidget {
|
||||
Set<String> assignedIds,
|
||||
String? taskTicketId,
|
||||
) async {
|
||||
// If there are no IT staff at all we still need to bail out. We don't
|
||||
// consider vacancy anymore because everyone is eligible, so the only
|
||||
// reason for the dialog to be unusable is an empty staff list.
|
||||
if (eligibleStaff.isEmpty && assignedIds.isEmpty) {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
@@ -144,7 +123,7 @@ class TaskAssignmentSection extends ConsumerWidget {
|
||||
return AlertDialog(
|
||||
shape: AppSurfaces.of(context).dialogShape,
|
||||
title: const Text('Assign IT Staff'),
|
||||
content: const Text('No vacant IT staff available.'),
|
||||
content: const Text('No IT staff available.'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(dialogContext).pop(),
|
||||
@@ -161,6 +140,7 @@ class TaskAssignmentSection extends ConsumerWidget {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (dialogContext) {
|
||||
var isSaving = false;
|
||||
return StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
return AlertDialog(
|
||||
@@ -187,15 +167,17 @@ class TaskAssignmentSection extends ConsumerWidget {
|
||||
horizontal: 12,
|
||||
vertical: 2,
|
||||
),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
if (value == true) {
|
||||
selection.add(staff.id);
|
||||
} else {
|
||||
selection.remove(staff.id);
|
||||
}
|
||||
});
|
||||
},
|
||||
onChanged: isSaving
|
||||
? null
|
||||
: (value) {
|
||||
setState(() {
|
||||
if (value == true) {
|
||||
selection.add(staff.id);
|
||||
} else {
|
||||
selection.remove(staff.id);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -203,24 +185,59 @@ class TaskAssignmentSection extends ConsumerWidget {
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(dialogContext).pop(),
|
||||
onPressed: isSaving
|
||||
? null
|
||||
: () => Navigator.of(dialogContext).pop(),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () async {
|
||||
await ref
|
||||
.read(taskAssignmentsControllerProvider)
|
||||
.replaceAssignments(
|
||||
taskId: taskId,
|
||||
ticketId: taskTicketId,
|
||||
newUserIds: selection.toList(),
|
||||
currentUserIds: assignedIds.toList(),
|
||||
);
|
||||
if (context.mounted) {
|
||||
Navigator.of(dialogContext).pop();
|
||||
}
|
||||
},
|
||||
child: const Text('Save'),
|
||||
onPressed: isSaving
|
||||
? null
|
||||
: () async {
|
||||
setState(() => isSaving = true);
|
||||
try {
|
||||
await ref
|
||||
.read(taskAssignmentsControllerProvider)
|
||||
.replaceAssignments(
|
||||
taskId: taskId,
|
||||
ticketId: taskTicketId,
|
||||
newUserIds: selection.toList(),
|
||||
currentUserIds: assignedIds.toList(),
|
||||
);
|
||||
if (context.mounted) {
|
||||
showSuccessSnackBar(
|
||||
context,
|
||||
'Assignment saved successfully',
|
||||
);
|
||||
}
|
||||
if (context.mounted) {
|
||||
Navigator.of(dialogContext).pop();
|
||||
}
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
showErrorSnackBar(
|
||||
context,
|
||||
'Failed to save assignment',
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (context.mounted) {
|
||||
setState(() => isSaving = false);
|
||||
}
|
||||
}
|
||||
},
|
||||
child: isSaving
|
||||
? SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation(
|
||||
Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
)
|
||||
: const Text('Save'),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user