import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../models/profile.dart'; import '../providers/profile_provider.dart'; import '../providers/tasks_provider.dart'; class TaskAssignmentSection extends ConsumerWidget { const TaskAssignmentSection({ super.key, required this.taskId, required this.canAssign, }); final String taskId; final bool canAssign; @override Widget build(BuildContext context, WidgetRef ref) { final profilesAsync = ref.watch(profilesProvider); final tasksAsync = ref.watch(tasksProvider); final assignmentsAsync = ref.watch(taskAssignmentsProvider); final profiles = profilesAsync.valueOrNull ?? []; final tasks = tasksAsync.valueOrNull ?? []; final taskTicketId = tasks .where((task) => task.id == taskId) .map((task) => task.ticketId) .firstOrNull; final assignments = assignmentsAsync.valueOrNull ?? []; final itStaff = profiles.where((profile) => profile.role == 'it_staff').toList() ..sort((a, b) => a.fullName.compareTo(b.fullName)); final assignedForTask = assignments .where((assignment) => assignment.taskId == taskId) .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 = >{}; for (final assignment in assignments) { if (!activeTaskIds.contains(assignment.taskId)) { continue; } activeAssignmentsByUser .putIfAbsent(assignment.userId, () => {}) .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(); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( 'Assigned IT Staff', style: Theme.of( context, ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w700), ), const Spacer(), if (canAssign) TextButton.icon( onPressed: () => _showAssignmentDialog( context, ref, eligibleStaff, assignedIds, taskTicketId, ), icon: const Icon(Icons.group_add), label: const Text('Assign'), ), ], ), const SizedBox(height: 8), if (assignedForTask.isEmpty) Text( 'No IT staff assigned.', style: Theme.of(context).textTheme.bodyMedium, ) else Wrap( spacing: 8, runSpacing: 6, children: assignedForTask.map((assignment) { final profile = profiles .where((item) => item.id == assignment.userId) .firstOrNull; final label = profile?.fullName.isNotEmpty == true ? profile!.fullName : assignment.userId; return InputChip( label: Text(label), onDeleted: canAssign ? () => ref .read(taskAssignmentsControllerProvider) .removeAssignment( taskId: taskId, userId: assignment.userId, ) : null, ); }).toList(), ), ], ); } Future _showAssignmentDialog( BuildContext context, WidgetRef ref, List eligibleStaff, Set assignedIds, String? taskTicketId, ) async { if (eligibleStaff.isEmpty && assignedIds.isEmpty) { await showDialog( context: context, builder: (dialogContext) { return AlertDialog( title: const Text('Assign IT Staff'), content: const Text('No vacant IT staff available.'), actions: [ TextButton( onPressed: () => Navigator.of(dialogContext).pop(), child: const Text('Close'), ), ], ); }, ); return; } final selection = assignedIds.toSet(); await showDialog( context: context, builder: (dialogContext) { return StatefulBuilder( builder: (context, setState) { return AlertDialog( title: const Text('Assign IT Staff'), contentPadding: const EdgeInsets.fromLTRB(24, 20, 24, 12), content: SizedBox( width: 360, child: Padding( padding: const EdgeInsets.symmetric(vertical: 12), child: ListView.builder( shrinkWrap: true, itemCount: eligibleStaff.length, itemBuilder: (context, index) { final staff = eligibleStaff[index]; final name = staff.fullName.isNotEmpty ? staff.fullName : staff.id; final selected = selection.contains(staff.id); return CheckboxListTile( value: selected, title: Text(name), contentPadding: const EdgeInsets.symmetric( horizontal: 12, vertical: 2, ), onChanged: (value) { setState(() { if (value == true) { selection.add(staff.id); } else { selection.remove(staff.id); } }); }, ); }, ), ), ), actions: [ TextButton( onPressed: () => 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'), ), ], ); }, ); }, ); } } extension _FirstOrNull on Iterable { T? get firstOrNull => isEmpty ? null : first; }