From e4f55d82f7f3b5ba1dd4c4239ddac69f1c16d28d Mon Sep 17 00:00:00 2001 From: Manish Date: Mon, 24 Nov 2025 15:15:35 +0530 Subject: [PATCH] reenhacenment of employee selector --- .../assign_task_bottom_sheet .dart | 298 ++++++++++-------- 1 file changed, 167 insertions(+), 131 deletions(-) diff --git a/lib/model/dailyTaskPlanning/assign_task_bottom_sheet .dart b/lib/model/dailyTaskPlanning/assign_task_bottom_sheet .dart index 99dae49..69b4104 100644 --- a/lib/model/dailyTaskPlanning/assign_task_bottom_sheet .dart +++ b/lib/model/dailyTaskPlanning/assign_task_bottom_sheet .dart @@ -12,6 +12,8 @@ import 'package:on_field_work/helpers/widgets/tenant/organization_selector.dart' import 'package:on_field_work/helpers/widgets/tenant/service_selector.dart'; import 'package:on_field_work/model/attendance/organization_per_project_list_model.dart'; import 'package:on_field_work/model/tenant/tenant_services_model.dart'; +import 'package:on_field_work/model/employees/employee_model.dart'; +import 'package:on_field_work/model/employees/multiple_select_role_bottomsheet.dart'; class AssignTaskBottomSheet extends StatefulWidget { final String workLocation; @@ -43,14 +45,15 @@ class _AssignTaskBottomSheetState extends State { final DailyTaskPlanningController controller = Get.find(); final ProjectController projectController = Get.find(); - final OrganizationController orgController = Get.put(OrganizationController()); + final OrganizationController orgController = + Get.put(OrganizationController()); final ServiceController serviceController = Get.put(ServiceController()); final TextEditingController targetController = TextEditingController(); final TextEditingController descriptionController = TextEditingController(); - final ScrollController _employeeListScrollController = ScrollController(); String? selectedProjectId; + String? selectedRoleId; Organization? selectedOrganization; Service? selectedService; @@ -79,12 +82,14 @@ class _AssignTaskBottomSheetState extends State { serviceId: selectedService?.id, organizationId: selectedOrganization?.id, ); - await controller.fetchTaskData(selectedProjectId, serviceId: selectedService?.id); + await controller.fetchTaskData( + selectedProjectId, + serviceId: selectedService?.id, + ); } @override void dispose() { - _employeeListScrollController.dispose(); targetController.dispose(); descriptionController.dispose(); super.dispose(); @@ -92,20 +97,21 @@ class _AssignTaskBottomSheetState extends State { @override Widget build(BuildContext context) { - return Obx(() => BaseBottomSheet( - title: "Assign Task", - child: _buildAssignTaskForm(), - onCancel: () => Get.back(), - onSubmit: _onAssignTaskPressed, - isSubmitting: controller.isAssigningTask.value, - )); + return Obx( + () => BaseBottomSheet( + title: "Assign Task", + child: _buildAssignTaskForm(), + onCancel: () => Get.back(), + onSubmit: _onAssignTaskPressed, + isSubmitting: controller.isAssigningTask.value, + ), + ); } Widget _buildAssignTaskForm() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Organization Selector SizedBox( height: 50, child: OrganizationSelector( @@ -117,9 +123,9 @@ class _AssignTaskBottomSheetState extends State { }, ), ), + MySpacing.height(12), - // Service Selector SizedBox( height: 50, child: ServiceSelector( @@ -131,49 +137,75 @@ class _AssignTaskBottomSheetState extends State { }, ), ), + MySpacing.height(16), - - // Work Location Info + _infoRow(Icons.location_on, "Work Location", + "${widget.buildingName} > ${widget.floorName} > ${widget.workAreaName} > ${widget.activityName}"), + const Divider(), _infoRow( - Icons.location_on, - "Work Location", - "${widget.buildingName} > ${widget.floorName} > ${widget.workAreaName} > ${widget.activityName}", - ), + Icons.pending_actions, "Pending Task", "${widget.pendingTask}"), const Divider(), - // Pending Task Info - _infoRow(Icons.pending_actions, "Pending Task", "${widget.pendingTask}"), - const Divider(), - - // Role Selector GestureDetector( onTap: _onRoleMenuPressed, - child: Row( - children: [ - MyText.titleMedium("Select Team :", fontWeight: 600), - const SizedBox(width: 4), - const Icon(Icons.tune, color: Color.fromARGB(255, 95, 132, 255)), - ], - ), + child: Row(children: [ + MyText.titleMedium("Select Team :", fontWeight: 600), + const SizedBox(width: 4), + const Icon(Icons.tune, color: Color.fromARGB(255, 95, 132, 255)), + ]), ), + MySpacing.height(8), - // Employee List - Container( - constraints: const BoxConstraints(maxHeight: 180), - decoration: BoxDecoration( - border: Border.all(color: Colors.grey.shade300), - borderRadius: BorderRadius.circular(6), - ), - child: _buildEmployeeList(), - ), - MySpacing.height(8), + /// TEAM SELECT BOX + GestureDetector( + onTap: _openEmployeeSelectionSheet, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade300), + borderRadius: BorderRadius.circular(6), + ), + child: Row( + children: [ + const Icon(Icons.group, color: Colors.black54), + const SizedBox(width: 10), - // Selected Employees Chips + // Expanded name area + Expanded( + child: Obx(() { + final count = controller.selectedEmployees.length; + if (count == 0) { + return MyText( + "Select team members", + color: Colors.grey.shade700, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ); + } + + final names = controller.selectedEmployees + .map((e) => e.name) + .join(", "); + + return Text( + names, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: const TextStyle(fontSize: 14), + ); + }), + ), + + const Icon(Icons.arrow_drop_down, color: Colors.grey), + ], + )), + ), + + MySpacing.height(8), _buildSelectedEmployees(), MySpacing.height(8), - // Target Input _buildTextField( icon: Icons.track_changes, label: "Target for Today :", @@ -182,9 +214,9 @@ class _AssignTaskBottomSheetState extends State { keyboardType: const TextInputType.numberWithOptions(decimal: true), validatorType: "target", ), + MySpacing.height(16), - // Description Input _buildTextField( icon: Icons.description, label: "Description :", @@ -198,7 +230,8 @@ class _AssignTaskBottomSheetState extends State { } void _onRoleMenuPressed() { - final RenderBox overlay = Overlay.of(context).context.findRenderObject() as RenderBox; + final RenderBox overlay = + Overlay.of(context).context.findRenderObject() as RenderBox; final Size screenSize = overlay.size; showMenu( @@ -211,69 +244,18 @@ class _AssignTaskBottomSheetState extends State { ), items: [ const PopupMenuItem(value: 'all', child: Text("All Roles")), - ...controller.roles.map((role) { - return PopupMenuItem( + ...controller.roles.map( + (role) => PopupMenuItem( value: role['id'].toString(), child: Text(role['name'] ?? 'Unknown Role'), - ); - }), + ), + ), ], ).then((value) { - if (value != null) controller.onRoleSelected(value == 'all' ? null : value); - }); - } - - Widget _buildEmployeeList() { - return Obx(() { - if (controller.isFetchingEmployees.value) { - return Center(child: CircularProgressIndicator()); + if (value != null) { + selectedRoleId = value == 'all' ? null : value; + controller.onRoleSelected(selectedRoleId); } - - final filteredEmployees = controller.selectedRoleId.value == null - ? controller.employees - : controller.employees - .where((e) => e.jobRoleID.toString() == controller.selectedRoleId.value) - .toList(); - - if (filteredEmployees.isEmpty) { - return Center(child: Text("No employees available for selected role.")); - } - - return Scrollbar( - controller: _employeeListScrollController, - thumbVisibility: true, - child: ListView.builder( - controller: _employeeListScrollController, - itemCount: filteredEmployees.length, - itemBuilder: (context, index) { - final employee = filteredEmployees[index]; - final rxBool = controller.uploadingStates[employee.id]; - - return Obx(() => ListTile( - dense: true, - contentPadding: const EdgeInsets.symmetric(horizontal: 8), - leading: Checkbox( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), - value: rxBool?.value ?? false, - onChanged: (selected) { - if (rxBool != null) { - rxBool.value = selected ?? false; - controller.updateSelectedEmployees(); - } - }, - fillColor: MaterialStateProperty.resolveWith((states) => - states.contains(MaterialState.selected) - ? const Color.fromARGB(255, 95, 132, 255) - : Colors.transparent), - checkColor: Colors.white, - side: const BorderSide(color: Colors.black), - ), - title: Text(employee.name, style: const TextStyle(fontSize: 14)), - visualDensity: VisualDensity.compact, - )); - }, - ), - ); }); } @@ -285,20 +267,14 @@ class _AssignTaskBottomSheetState extends State { spacing: 4, runSpacing: 4, children: controller.selectedEmployees.map((e) { - return Obx(() { - final isSelected = controller.uploadingStates[e.id]?.value ?? false; - if (!isSelected) return Container(); - - return Chip( - label: Text(e.name, style: const TextStyle(color: Colors.white)), - backgroundColor: const Color.fromARGB(255, 95, 132, 255), - deleteIcon: const Icon(Icons.close, color: Colors.white), - onDeleted: () { - controller.uploadingStates[e.id]?.value = false; - controller.updateSelectedEmployees(); - }, - ); - }); + return Chip( + label: Text(e.name, style: const TextStyle(color: Colors.white)), + backgroundColor: const Color.fromARGB(255, 95, 132, 255), + deleteIcon: const Icon(Icons.close, color: Colors.white), + onDeleted: () { + controller.selectedEmployees.remove(e); + }, + ); }).toList(), ); }); @@ -328,10 +304,15 @@ class _AssignTaskBottomSheetState extends State { maxLines: maxLines, decoration: InputDecoration( hintText: hintText, - border: OutlineInputBorder(borderRadius: BorderRadius.circular(6)), - contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(6), + ), + contentPadding: + const EdgeInsets.symmetric(horizontal: 12, vertical: 8), ), - validator: (value) => this.controller.formFieldValidator(value, fieldType: validatorType), + validator: (value) => this + .controller + .formFieldValidator(value, fieldType: validatorType), ), ], ); @@ -350,32 +331,83 @@ class _AssignTaskBottomSheetState extends State { text: TextSpan( children: [ WidgetSpan( - child: MyText.titleMedium("$title: ", fontWeight: 600, color: Colors.black), + child: MyText.titleMedium( + "$title: ", + fontWeight: 600, + color: Colors.black, + ), + ), + TextSpan( + text: value, + style: const TextStyle(color: Colors.black), ), - TextSpan(text: value, style: const TextStyle(color: Colors.black)), ], ), ), - ), + ) ], ), ); } + Future _openEmployeeSelectionSheet() async { + final result = await showModalBottomSheet>( + context: context, + isScrollControlled: true, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(20)), + ), + builder: (context) { + return DraggableScrollableSheet( + expand: false, + initialChildSize: 0.85, + minChildSize: 0.6, + maxChildSize: 1.0, + builder: (_, scrollController) { + return Container( + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.vertical(top: Radius.circular(20)), + ), + child: MultipleSelectRoleBottomSheet( + projectId: selectedProjectId!, + organizationId: selectedOrganization?.id, + serviceId: selectedService?.id, + roleId: selectedRoleId, + initiallySelected: controller.selectedEmployees.toList(), + scrollController: scrollController, + ), + ); + }, + ); + }, + ); + + if (result != null) { + controller.selectedEmployees + .assignAll(result); // RxList updates UI automatically + } + } + void _onAssignTaskPressed() { - final selectedTeam = controller.uploadingStates.entries - .where((e) => e.value.value) - .map((e) => e.key) - .toList(); + final selectedTeam = controller.selectedEmployees; if (selectedTeam.isEmpty) { - showAppSnackbar(title: "Team Required", message: "Please select at least one team member", type: SnackbarType.error); + showAppSnackbar( + title: "Team Required", + message: "Please select at least one team member", + type: SnackbarType.error, + ); return; } final target = double.tryParse(targetController.text.trim()); if (target == null || target <= 0) { - showAppSnackbar(title: "Invalid Input", message: "Please enter a valid target number", type: SnackbarType.error); + showAppSnackbar( + title: "Invalid Input", + message: "Please enter a valid target number", + type: SnackbarType.error, + ); return; } @@ -390,7 +422,11 @@ class _AssignTaskBottomSheetState extends State { final description = descriptionController.text.trim(); if (description.isEmpty) { - showAppSnackbar(title: "Description Required", message: "Please enter a description", type: SnackbarType.error); + showAppSnackbar( + title: "Description Required", + message: "Please enter a description", + type: SnackbarType.error, + ); return; } @@ -398,7 +434,7 @@ class _AssignTaskBottomSheetState extends State { workItemId: widget.workItemId, plannedTask: target.toInt(), description: description, - taskTeam: selectedTeam, + taskTeam: selectedTeam.map((e) => e.id).toList(), // pass IDs assignmentDate: widget.assignmentDate, organizationId: selectedOrganization?.id, serviceId: selectedService?.id,