From 5e1379b74ba449807cf12162af3a700f0e933996 Mon Sep 17 00:00:00 2001 From: Manish Date: Mon, 24 Nov 2025 14:56:09 +0530 Subject: [PATCH] reenhancement of employee selector --- .../directory/edit_bucket_bottom_sheet.dart | 181 +++++++----------- .../expense/expense_filter_bottom_sheet.dart | 12 +- 2 files changed, 78 insertions(+), 115 deletions(-) diff --git a/lib/model/directory/edit_bucket_bottom_sheet.dart b/lib/model/directory/edit_bucket_bottom_sheet.dart index f4dedfb..f3086a1 100644 --- a/lib/model/directory/edit_bucket_bottom_sheet.dart +++ b/lib/model/directory/edit_bucket_bottom_sheet.dart @@ -9,6 +9,7 @@ import 'package:on_field_work/helpers/widgets/my_spacing.dart'; import 'package:on_field_work/helpers/widgets/my_snackbar.dart'; import 'package:on_field_work/model/employees/employee_model.dart'; import 'package:on_field_work/model/directory/contact_bucket_list_model.dart'; +import 'package:on_field_work/model/employees/multiple_select_bottomsheet.dart'; class EditBucketBottomSheet { static void show( @@ -21,10 +22,8 @@ class EditBucketBottomSheet { final nameController = TextEditingController(text: bucket.name); final descController = TextEditingController(text: bucket.description); - final searchController = TextEditingController(); final selectedIds = RxSet({...bucket.employeeIds}); - final searchText = ''.obs; InputDecoration _inputDecoration(String label) { return InputDecoration( @@ -84,6 +83,15 @@ class EditBucketBottomSheet { } } + Future _handleSubmitBottomSheet(BuildContext sheetContext) async { + await _handleSubmit(); + + // close bottom sheet safely + if (Navigator.of(sheetContext).canPop()) { + Navigator.of(sheetContext).pop(); + } + } + Widget _formContent() { return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -101,117 +109,72 @@ class EditBucketBottomSheet { MySpacing.height(20), MyText.labelLarge('Shared With', fontWeight: 600), MySpacing.height(8), - Obx(() => TextField( - controller: searchController, - onChanged: (value) => searchText.value = value.toLowerCase(), - decoration: InputDecoration( - hintText: 'Search employee...', - prefixIcon: const Icon(Icons.search, size: 20), - suffixIcon: searchText.value.isNotEmpty - ? IconButton( - icon: const Icon(Icons.clear, size: 18), - onPressed: () { - searchController.clear(); - searchText.value = ''; - }, - ) - : null, - isDense: true, - contentPadding: - const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - filled: true, - fillColor: Colors.grey.shade100, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10), - borderSide: BorderSide(color: Colors.grey.shade300), - ), - ), - )), - MySpacing.height(8), Obx(() { - final filtered = allEmployees.where((emp) { - final fullName = '${emp.firstName} ${emp.lastName}'.toLowerCase(); - return fullName.contains(searchText.value); - }).toList(); + if (selectedIds.isEmpty) return const SizedBox.shrink(); - return SizedBox( - height: 180, - child: ListView.separated( - itemCount: filtered.length, - separatorBuilder: (_, __) => const SizedBox(height: 2), - itemBuilder: (context, index) { - final emp = filtered[index]; - final fullName = '${emp.firstName} ${emp.lastName}'.trim(); + final selectedEmployees = + allEmployees.where((e) => selectedIds.contains(e.id)).toList(); - return Obx(() => Theme( - data: Theme.of(context).copyWith( - unselectedWidgetColor: Colors.grey.shade500, - checkboxTheme: CheckboxThemeData( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(4)), - side: const BorderSide(color: Colors.grey), - fillColor: - MaterialStateProperty.resolveWith((states) { - if (states.contains(MaterialState.selected)) { - return Colors.blueAccent; - } - return Colors.white; - }), - checkColor: MaterialStateProperty.all(Colors.white), - ), - ), - child: CheckboxListTile( - dense: true, - contentPadding: EdgeInsets.zero, - visualDensity: const VisualDensity(vertical: -4), - controlAffinity: ListTileControlAffinity.leading, - value: selectedIds.contains(emp.id), - onChanged: emp.id == ownerId - ? null - : (val) { - if (val == true) { - selectedIds.add(emp.id); - } else { - selectedIds.remove(emp.id); - } - }, - title: Row( - children: [ - Expanded( - child: MyText.bodyMedium( - fullName.isNotEmpty ? fullName : 'Unnamed', - fontWeight: 600, - ), - ), - if (emp.id == ownerId) - Container( - margin: const EdgeInsets.only(left: 6), - padding: const EdgeInsets.symmetric( - horizontal: 6, vertical: 2), - decoration: BoxDecoration( - color: Colors.red.shade50, - borderRadius: BorderRadius.circular(4), - ), - child: MyText.labelSmall( - "Owner", - fontWeight: 600, - color: Colors.red, - ), - ), - ], - ), - subtitle: emp.jobRole.isNotEmpty - ? MyText.bodySmall( - emp.jobRole, - color: Colors.grey.shade600, - ) - : null, - ), - )); - }, - ), + return Wrap( + spacing: 8, + children: selectedEmployees.map((emp) { + final fullName = '${emp.firstName} ${emp.lastName}'.trim(); + + return Chip( + label: Text(fullName), + onDeleted: emp.id == ownerId + ? null + : () => selectedIds.remove(emp.id), + ); + }).toList(), ); }), + MySpacing.height(8), + +// --- Open new EmployeeSelectionBottomSheet --- + GestureDetector( + onTap: () async { + final initiallySelected = allEmployees + .where((e) => selectedIds.contains(e.id)) + .toList(); + + final result = await showModalBottomSheet>( + context: context, + isScrollControlled: true, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(22)), + ), + builder: (_) => EmployeeSelectionBottomSheet( + initiallySelected: initiallySelected, + multipleSelection: true, + title: "Shared With", + ), + ); + + if (result != null) { + selectedIds + ..clear() + ..addAll(result.map((e) => e.id)); + } + }, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey.shade300), + ), + child: Row( + children: const [ + Icon(Icons.search, color: Colors.grey), + SizedBox(width: 8), + Expanded(child: Text("Search & Select Employees")), + ], + ), + ), + ), + MySpacing.height(8), + const SizedBox.shrink(), ], ); } @@ -224,7 +187,7 @@ class EditBucketBottomSheet { return BaseBottomSheet( title: "Edit Bucket", onCancel: () => Navigator.pop(context), - onSubmit: _handleSubmit, + onSubmit: () => _handleSubmitBottomSheet(context), child: _formContent(), ); }, diff --git a/lib/view/expense/expense_filter_bottom_sheet.dart b/lib/view/expense/expense_filter_bottom_sheet.dart index f2da450..63e8b23 100644 --- a/lib/view/expense/expense_filter_bottom_sheet.dart +++ b/lib/view/expense/expense_filter_bottom_sheet.dart @@ -1,5 +1,3 @@ -// ignore_for_file: must_be_immutable - import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:on_field_work/controller/expense/expense_screen_controller.dart'; @@ -8,9 +6,10 @@ import 'package:on_field_work/helpers/widgets/my_spacing.dart'; import 'package:on_field_work/helpers/widgets/my_text.dart'; import 'package:on_field_work/helpers/widgets/my_text_style.dart'; import 'package:on_field_work/model/employees/employee_model.dart'; -import 'package:on_field_work/model/expense/employee_selector_for_filter_bottom_sheet.dart'; import 'package:on_field_work/helpers/utils/mixins/ui_mixin.dart'; import 'package:on_field_work/helpers/widgets/date_range_picker.dart'; +import 'package:on_field_work/model/employees/multiple_select_bottomsheet.dart'; + class ExpenseFilterBottomSheet extends StatefulWidget { final ExpenseController expenseController; @@ -303,12 +302,13 @@ class _ExpenseFilterBottomSheetState extends State shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), - builder: (context) => EmployeeSelectorBottomSheet( - selectedEmployees: selectedEmployees, - searchEmployees: searchEmployees, + builder: (context) => EmployeeSelectionBottomSheet( + initiallySelected: selectedEmployees.toList(), + multipleSelection: true, title: title, ), ); + if (result != null) selectedEmployees.assignAll(result); }, child: Container(