From 7a9673e2c21cfda456f9e29abc87f97f2bc0723c Mon Sep 17 00:00:00 2001 From: Manish Date: Tue, 18 Nov 2025 17:13:44 +0530 Subject: [PATCH] added multiselect bottom sheet in assign task bottom sheet form --- .../assign_task_bottom_sheet .dart | 197 +++++++++++------- lib/model/employees/employee_model.dart | 2 +- 2 files changed, 124 insertions(+), 75 deletions(-) diff --git a/lib/model/dailyTaskPlanning/assign_task_bottom_sheet .dart b/lib/model/dailyTaskPlanning/assign_task_bottom_sheet .dart index 4db2228..fdfcced 100644 --- a/lib/model/dailyTaskPlanning/assign_task_bottom_sheet .dart +++ b/lib/model/dailyTaskPlanning/assign_task_bottom_sheet .dart @@ -13,6 +13,10 @@ import 'package:marco/helpers/widgets/tenant/service_selector.dart'; import 'package:marco/model/attendance/organization_per_project_list_model.dart'; import 'package:marco/model/tenant/tenant_services_model.dart'; +// Added imports for employee selection +import 'package:marco/model/employees/employee_model.dart'; +import 'package:marco/model/employees/multiple_select_bottomsheet.dart'; + class AssignTaskBottomSheet extends StatefulWidget { final String workLocation; final String activityName; @@ -43,7 +47,8 @@ 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(); @@ -79,7 +84,8 @@ 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 @@ -142,10 +148,11 @@ class _AssignTaskBottomSheetState extends State { const Divider(), // Pending Task Info - _infoRow(Icons.pending_actions, "Pending Task", "${widget.pendingTask}"), + _infoRow( + Icons.pending_actions, "Pending Task", "${widget.pendingTask}"), const Divider(), - // Role Selector + // Role Selector (kept as-is) GestureDetector( onTap: _onRoleMenuPressed, child: Row( @@ -158,18 +165,52 @@ class _AssignTaskBottomSheetState extends State { ), MySpacing.height(8), - // Employee List - Container( - constraints: const BoxConstraints(maxHeight: 180), - decoration: BoxDecoration( - border: Border.all(color: Colors.grey.shade300), - borderRadius: BorderRadius.circular(6), + // ------------------------------- + // Employee selector (REPLACED) + // ------------------------------- + // We show a button-like container (with border) that opens the reusable + // EmployeeSelectionBottomSheet. Selected employees are reflected using + // existing controller.uploadingStates & controller.selectedEmployees. + 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), + Expanded( + child: Obx(() { + final count = controller.selectedEmployees.length; + if (count == 0) { + return MyText("Select team members", + color: Colors.grey.shade700); + } + // show summary text when there are selected employees + final names = controller.selectedEmployees + .map((e) => e.name) + .join(", "); + return Text( + names, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: const TextStyle(fontSize: 14), + ); + }), + ), + const SizedBox(width: 8), + const Icon(Icons.arrow_drop_down, color: Colors.grey), + ], + ), ), - child: _buildEmployeeList(), ), MySpacing.height(8), - // Selected Employees Chips + // Selected Employees Chips (keeps existing behavior) _buildSelectedEmployees(), MySpacing.height(8), @@ -198,7 +239,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( @@ -219,63 +261,12 @@ class _AssignTaskBottomSheetState extends State { }), ], ).then((value) { - if (value != null) controller.onRoleSelected(value == 'all' ? null : value); + if (value != null) + controller.onRoleSelected(value == 'all' ? null : value); }); } - Widget _buildEmployeeList() { - return Obx(() { - if (controller.isFetchingEmployees.value) { - return Center(child: CircularProgressIndicator()); - } - - 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, - )); - }, - ), - ); - }); - } + // Removed old inline employee list; selection handled by bottom sheet. Widget _buildSelectedEmployees() { return Obx(() { @@ -329,9 +320,12 @@ class _AssignTaskBottomSheetState extends State { decoration: InputDecoration( hintText: hintText, border: OutlineInputBorder(borderRadius: BorderRadius.circular(6)), - contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + 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,9 +344,11 @@ 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)), ], ), ), @@ -362,6 +358,50 @@ class _AssignTaskBottomSheetState extends State { ); } + Future _openEmployeeSelectionSheet() async { + // Open the existing EmployeeSelectionBottomSheet + final result = await showModalBottomSheet>( + context: context, + isScrollControlled: true, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(20)), + ), + builder: (context) => EmployeeSelectionBottomSheet( + initiallySelected: controller.selectedEmployees.toList(), + multipleSelection: true, + title: 'Select Team Members', + ), + ); + + if (result == null) return; + + // Merge returned employees into controller.uploadingStates & controller.selectedEmployees + // 1) Reset all uploadingStates to false, then set true for selected + controller.uploadingStates.forEach((key, rx) { + rx.value = false; + }); + + for (final emp in result) { + final idStr = emp.id.toString(); + if (controller.uploadingStates.containsKey(idStr)) { + controller.uploadingStates[idStr]?.value = true; + } else { + // if uploadingStates doesn't have the id yet, add it (safe fallback) + controller.uploadingStates[idStr] = RxBool(true); + } + } + + // 2) Update selectedEmployees list in controller + controller.selectedEmployees.assignAll(result); + + // 3) Call controller helper (keeps existing behavior) + try { + controller.updateSelectedEmployees(); + } catch (_) { + // If controller does not implement updateSelectedEmployees, ignore. + } + } + void _onAssignTaskPressed() { final selectedTeam = controller.uploadingStates.entries .where((e) => e.value.value) @@ -369,13 +409,19 @@ class _AssignTaskBottomSheetState extends State { .toList(); 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 +436,10 @@ 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; } diff --git a/lib/model/employees/employee_model.dart b/lib/model/employees/employee_model.dart index d774f23..0abc764 100644 --- a/lib/model/employees/employee_model.dart +++ b/lib/model/employees/employee_model.dart @@ -72,7 +72,7 @@ class EmployeeModel { }; } - /// ✅ Add equality based on unique `id` + ///Equality based on unique `id` — required for multi-selection to work @override bool operator ==(Object other) { if (identical(this, other)) return true;