done with employee list dropdown updation

This commit is contained in:
Manish 2025-11-18 17:49:26 +05:30
parent d983620524
commit d4307814af

View File

@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:on_field_work/helpers/utils/base_bottom_sheet.dart'; import 'package:on_field_work/helpers/utils/base_bottom_sheet.dart';
@ -26,23 +27,31 @@ class _EmployeeSelectionBottomSheetState
extends State<EmployeeSelectionBottomSheet> { extends State<EmployeeSelectionBottomSheet> {
final TextEditingController _searchController = TextEditingController(); final TextEditingController _searchController = TextEditingController();
final RxBool _isSearching = false.obs; final RxBool _isSearching = false.obs;
final RxList<EmployeeModel> _allResults = <EmployeeModel>[].obs; final RxList<EmployeeModel> _allResults = <EmployeeModel>[].obs;
final RxList<EmployeeModel> _allResults = <EmployeeModel>[].obs;
late RxList<EmployeeModel> _selectedEmployees; late RxList<EmployeeModel> _selectedEmployees;
Timer? _debounce; Timer? _debounce;
Timer? _debounce;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_selectedEmployees = RxList<EmployeeModel>.from(widget.initiallySelected); _selectedEmployees = RxList<EmployeeModel>.from(widget.initiallySelected);
_performSearch(''); _performSearch('');
_performSearch('');
} }
@override @override
void dispose() { void dispose() {
_debounce?.cancel();
_debounce?.cancel(); _debounce?.cancel();
_searchController.dispose(); _searchController.dispose();
super.dispose(); super.dispose();
@ -58,28 +67,36 @@ class _EmployeeSelectionBottomSheetState
}); });
} }
Future<void> _performSearch(String query) async {
// ------------------------------------------------------
// 🔥 Optimized debounce-based search
// ------------------------------------------------------
void _onSearchChanged(String query) {
_debounce?.cancel();
_debounce = Timer(const Duration(milliseconds: 300), () {
_performSearch(query.trim());
});
}
Future<void> _performSearch(String query) async { Future<void> _performSearch(String query) async {
_isSearching.value = true; _isSearching.value = true;
final data = await ApiService.searchEmployeesBasic(searchString: query); final data = await ApiService.searchEmployeesBasic(searchString: query);
final results = (data as List) final results = (data as List)
.map((e) => EmployeeModel.fromJson(e as Map<String, dynamic>)) .map((e) => EmployeeModel.fromJson(e as Map<String, dynamic>))
.toList(); .toList();
// ------------------------------------------------------ // ------------------------------------------------------
// Auto-move selected employees to top // 🔥 Auto-move selected employees to top
// ------------------------------------------------------ // ------------------------------------------------------
results.sort((a, b) { results.sort((a, b) {
if (widget.multipleSelection) { final aSel = _selectedEmployees.contains(a) ? 0 : 1;
// Only move selected employees to top in multi-select final bSel = _selectedEmployees.contains(b) ? 0 : 1;
final aSel = _selectedEmployees.contains(a) ? 0 : 1;
final bSel = _selectedEmployees.contains(b) ? 0 : 1;
if (aSel != bSel) return aSel.compareTo(bSel); if (aSel != bSel) return aSel.compareTo(bSel);
}
// Otherwise, keep original order (or alphabetically if needed)
return a.name.toLowerCase().compareTo(b.name.toLowerCase()); return a.name.toLowerCase().compareTo(b.name.toLowerCase());
}); });
@ -88,6 +105,9 @@ class _EmployeeSelectionBottomSheetState
_isSearching.value = false; _isSearching.value = false;
} }
// ------------------------------------------------------
// Handle tap & checkbox
// ------------------------------------------------------
// ------------------------------------------------------ // ------------------------------------------------------
// Handle tap & checkbox // Handle tap & checkbox
// ------------------------------------------------------ // ------------------------------------------------------
@ -104,8 +124,14 @@ class _EmployeeSelectionBottomSheetState
// Re-sort list after each toggle // Re-sort list after each toggle
_performSearch(_searchController.text.trim()); _performSearch(_searchController.text.trim());
// Re-sort list after each toggle
_performSearch(_searchController.text.trim());
} }
// ------------------------------------------------------
// Submit selection
// ------------------------------------------------------
// ------------------------------------------------------ // ------------------------------------------------------
// Submit selection // Submit selection
// ------------------------------------------------------ // ------------------------------------------------------
@ -118,6 +144,9 @@ class _EmployeeSelectionBottomSheetState
} }
} }
// ------------------------------------------------------
// Search bar widget
// ------------------------------------------------------
// ------------------------------------------------------ // ------------------------------------------------------
// Search bar widget // Search bar widget
// ------------------------------------------------------ // ------------------------------------------------------
@ -126,6 +155,7 @@ class _EmployeeSelectionBottomSheetState
child: TextField( child: TextField(
controller: _searchController, controller: _searchController,
onChanged: _onSearchChanged, onChanged: _onSearchChanged,
onChanged: _onSearchChanged,
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Search employees...', hintText: 'Search employees...',
filled: true, filled: true,
@ -137,6 +167,7 @@ class _EmployeeSelectionBottomSheetState
onPressed: () { onPressed: () {
_searchController.clear(); _searchController.clear();
_performSearch(''); _performSearch('');
_performSearch('');
}, },
) )
: null, : null,
@ -150,40 +181,25 @@ class _EmployeeSelectionBottomSheetState
), ),
); );
// ------------------------------------------------------
// Employee list (optimized)
// ------------------------------------------------------
// ------------------------------------------------------ // ------------------------------------------------------
// Employee list (optimized) // Employee list (optimized)
// ------------------------------------------------------ // ------------------------------------------------------
Widget _employeeList() => Expanded( Widget _employeeList() => Expanded(
child: Obx(() { child: Obx(() {
final results = _allResults; final results = _allResults;
final results = _allResults;
return ListView.builder( return ListView.builder(
padding: const EdgeInsets.symmetric(vertical: 4), padding: const EdgeInsets.symmetric(vertical: 4),
itemCount: results.length, itemCount: results.length,
itemCount: results.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final emp = results[index]; final emp = results[index];
final isSelected = _selectedEmployees.contains(emp); final isSelected = _selectedEmployees.contains(emp);
Widget trailingWidget;
if (widget.multipleSelection) {
// Multiple selection normal checkbox
trailingWidget = Checkbox(
value: isSelected,
onChanged: (_) => _toggleEmployee(emp),
fillColor: MaterialStateProperty.resolveWith<Color>(
(states) => states.contains(MaterialState.selected)
? Colors.blueAccent
: Colors.white,
),
);
} else {
// Single selection check circle
trailingWidget = isSelected
? const Icon(Icons.check_circle, color: Colors.blueAccent)
: const Icon(Icons.circle_outlined, color: Colors.grey);
}
return ListTile( return ListTile(
leading: CircleAvatar( leading: CircleAvatar(
backgroundColor: Colors.blueAccent, backgroundColor: Colors.blueAccent,
@ -195,7 +211,15 @@ class _EmployeeSelectionBottomSheetState
), ),
title: Text('${emp.firstName} ${emp.lastName}'), title: Text('${emp.firstName} ${emp.lastName}'),
subtitle: Text(emp.email), subtitle: Text(emp.email),
trailing: trailingWidget, trailing: Checkbox(
value: isSelected,
onChanged: (_) => _toggleEmployee(emp),
fillColor: MaterialStateProperty.resolveWith<Color>(
(states) => states.contains(MaterialState.selected)
? Colors.blueAccent
: Colors.white,
),
),
onTap: () => _toggleEmployee(emp), onTap: () => _toggleEmployee(emp),
contentPadding: contentPadding:
const EdgeInsets.symmetric(horizontal: 0, vertical: 4), const EdgeInsets.symmetric(horizontal: 0, vertical: 4),
@ -205,6 +229,9 @@ class _EmployeeSelectionBottomSheetState
}), }),
); );
// ------------------------------------------------------
// Build bottom sheet
// ------------------------------------------------------
// ------------------------------------------------------ // ------------------------------------------------------
// Build bottom sheet // Build bottom sheet
// ------------------------------------------------------ // ------------------------------------------------------
@ -222,6 +249,12 @@ class _EmployeeSelectionBottomSheetState
_employeeList(), _employeeList(),
], ],
), ),
child: Column(
children: [
_searchBar(),
_employeeList(),
],
),
), ),
); );
} }