feat: enhance employee selection and validation in service project job form
This commit is contained in:
parent
0e575c393d
commit
49326e9929
@ -58,11 +58,10 @@ class _EmployeeSelectionBottomSheetState
|
|||||||
} else {
|
} else {
|
||||||
_selectedEmployees.add(emp);
|
_selectedEmployees.add(emp);
|
||||||
}
|
}
|
||||||
_selectedEmployees.refresh();
|
|
||||||
} else {
|
} else {
|
||||||
_selectedEmployees.assignAll([emp]);
|
_selectedEmployees.assignAll([emp]);
|
||||||
_selectedEmployees.refresh();
|
|
||||||
}
|
}
|
||||||
|
_selectedEmployees.refresh(); // important for Obx rebuild
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleSubmit() {
|
void _handleSubmit() {
|
||||||
@ -104,13 +103,22 @@ class _EmployeeSelectionBottomSheetState
|
|||||||
);
|
);
|
||||||
|
|
||||||
Widget _employeeList() => Expanded(
|
Widget _employeeList() => Expanded(
|
||||||
child: ListView.builder(
|
child: Obx(() {
|
||||||
|
if (_isSearching.value) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_searchResults.isEmpty) {
|
||||||
|
return const Center(child: Text("No employees found"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListView.builder(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||||
itemCount: _searchResults.length,
|
itemCount: _searchResults.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final emp = _searchResults[index];
|
final emp = _searchResults[index];
|
||||||
|
|
||||||
return Obx(() {
|
return Obx(() {
|
||||||
// wrap each tile
|
|
||||||
final isSelected = _selectedEmployees.contains(emp);
|
final isSelected = _selectedEmployees.contains(emp);
|
||||||
return ListTile(
|
return ListTile(
|
||||||
leading: CircleAvatar(
|
leading: CircleAvatar(
|
||||||
@ -125,20 +133,27 @@ class _EmployeeSelectionBottomSheetState
|
|||||||
subtitle: Text(emp.email),
|
subtitle: Text(emp.email),
|
||||||
trailing: Checkbox(
|
trailing: Checkbox(
|
||||||
value: isSelected,
|
value: isSelected,
|
||||||
onChanged: (_) => _toggleEmployee(emp),
|
onChanged: (_) {
|
||||||
|
FocusScope.of(context).unfocus(); // hide keyboard
|
||||||
|
_toggleEmployee(emp);
|
||||||
|
},
|
||||||
fillColor: MaterialStateProperty.resolveWith<Color>(
|
fillColor: MaterialStateProperty.resolveWith<Color>(
|
||||||
(states) => states.contains(MaterialState.selected)
|
(states) => states.contains(MaterialState.selected)
|
||||||
? Colors.blueAccent
|
? Colors.blueAccent
|
||||||
: Colors.white,
|
: Colors.white,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onTap: () => _toggleEmployee(emp),
|
onTap: () {
|
||||||
|
FocusScope.of(context).unfocus();
|
||||||
|
_toggleEmployee(emp);
|
||||||
|
},
|
||||||
contentPadding:
|
contentPadding:
|
||||||
const EdgeInsets.symmetric(horizontal: 0, vertical: 4),
|
const EdgeInsets.symmetric(horizontal: 0, vertical: 4),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
);
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@ -24,7 +24,6 @@ class _AddServiceProjectJobBottomSheetState
|
|||||||
final formKey = GlobalKey<FormState>();
|
final formKey = GlobalKey<FormState>();
|
||||||
final controller = Get.put(AddServiceProjectJobController());
|
final controller = Get.put(AddServiceProjectJobController());
|
||||||
|
|
||||||
final TextEditingController _searchController = TextEditingController();
|
|
||||||
late RxList<EmployeeModel> _selectedEmployees;
|
late RxList<EmployeeModel> _selectedEmployees;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -36,7 +35,6 @@ class _AddServiceProjectJobBottomSheetState
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_searchController.dispose();
|
|
||||||
Get.delete<AddServiceProjectJobController>();
|
Get.delete<AddServiceProjectJobController>();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
@ -91,6 +89,7 @@ class _AddServiceProjectJobBottomSheetState
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _employeeSelector() => Column(
|
Widget _employeeSelector() => Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -109,13 +108,12 @@ class _AddServiceProjectJobBottomSheetState
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (selectedEmployees != null) {
|
if (selectedEmployees != null) {
|
||||||
setState(() {
|
|
||||||
_selectedEmployees.assignAll(selectedEmployees);
|
_selectedEmployees.assignAll(selectedEmployees);
|
||||||
});
|
controller.selectedAssignees.assignAll(selectedEmployees);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: AbsorbPointer(
|
child: AbsorbPointer(
|
||||||
child: TextFormField(
|
child: Obx(() => TextFormField(
|
||||||
decoration: _inputDecoration("Select Employees"),
|
decoration: _inputDecoration("Select Employees"),
|
||||||
controller: TextEditingController(
|
controller: TextEditingController(
|
||||||
text: _selectedEmployees.isEmpty
|
text: _selectedEmployees.isEmpty
|
||||||
@ -127,9 +125,25 @@ class _AddServiceProjectJobBottomSheetState
|
|||||||
validator: (v) => _selectedEmployees.isEmpty
|
validator: (v) => _selectedEmployees.isEmpty
|
||||||
? "Please select employees"
|
? "Please select employees"
|
||||||
: null,
|
: null,
|
||||||
|
)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
MySpacing.height(8),
|
||||||
|
Obx(() => Wrap(
|
||||||
|
spacing: 8,
|
||||||
|
runSpacing: 4,
|
||||||
|
children: _selectedEmployees
|
||||||
|
.map(
|
||||||
|
(emp) => Chip(
|
||||||
|
label: Text('${emp.firstName} ${emp.lastName}'),
|
||||||
|
onDeleted: () {
|
||||||
|
_selectedEmployees.remove(emp);
|
||||||
|
controller.selectedAssignees.remove(emp);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
)),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -138,9 +152,9 @@ class _AddServiceProjectJobBottomSheetState
|
|||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 48,
|
height: 48,
|
||||||
child: TextField(
|
child: TextFormField(
|
||||||
controller: controller.tagCtrl,
|
controller: controller.tagCtrl,
|
||||||
onSubmitted: (v) {
|
onFieldSubmitted: (v) {
|
||||||
final value = v.trim();
|
final value = v.trim();
|
||||||
if (value.isNotEmpty &&
|
if (value.isNotEmpty &&
|
||||||
!controller.enteredTags.contains(value)) {
|
!controller.enteredTags.contains(value)) {
|
||||||
@ -149,6 +163,9 @@ class _AddServiceProjectJobBottomSheetState
|
|||||||
controller.tagCtrl.clear();
|
controller.tagCtrl.clear();
|
||||||
},
|
},
|
||||||
decoration: _inputDecoration("Start typing to add tags"),
|
decoration: _inputDecoration("Start typing to add tags"),
|
||||||
|
validator: (v) => controller.enteredTags.isEmpty
|
||||||
|
? "Please add at least one tag"
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
MySpacing.height(8),
|
MySpacing.height(8),
|
||||||
@ -157,24 +174,13 @@ class _AddServiceProjectJobBottomSheetState
|
|||||||
children: controller.enteredTags
|
children: controller.enteredTags
|
||||||
.map((tag) => Chip(
|
.map((tag) => Chip(
|
||||||
label: Text(tag),
|
label: Text(tag),
|
||||||
onDeleted: () => controller.enteredTags.remove(tag)))
|
onDeleted: () => controller.enteredTags.remove(tag),
|
||||||
|
))
|
||||||
.toList(),
|
.toList(),
|
||||||
)),
|
)),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void _toggleEmployee(EmployeeModel emp) {
|
|
||||||
final contains = _selectedEmployees.contains(emp);
|
|
||||||
if (contains) {
|
|
||||||
_selectedEmployees.remove(emp);
|
|
||||||
} else {
|
|
||||||
_selectedEmployees.add(emp);
|
|
||||||
}
|
|
||||||
controller.toggleAssignee(emp);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleSubmit() {
|
void _handleSubmit() {
|
||||||
if (!(formKey.currentState?.validate() ?? false)) return;
|
if (!(formKey.currentState?.validate() ?? false)) return;
|
||||||
controller.titleCtrl.text = controller.titleCtrl.text.trim();
|
controller.titleCtrl.text = controller.titleCtrl.text.trim();
|
||||||
@ -195,25 +201,9 @@ class _AddServiceProjectJobBottomSheetState
|
|||||||
children: [
|
children: [
|
||||||
_textField("Title", controller.titleCtrl, required: true),
|
_textField("Title", controller.titleCtrl, required: true),
|
||||||
MySpacing.height(16),
|
MySpacing.height(16),
|
||||||
Obx(() {
|
|
||||||
if (_searchController.text.isNotEmpty)
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
return Wrap(
|
|
||||||
spacing: 8,
|
|
||||||
runSpacing: 4,
|
|
||||||
children: _selectedEmployees
|
|
||||||
.map(
|
|
||||||
(emp) => Chip(
|
|
||||||
label: Text('${emp.firstName} ${emp.lastName}'),
|
|
||||||
onDeleted: () => _toggleEmployee(emp),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
_employeeSelector(),
|
_employeeSelector(),
|
||||||
MySpacing.height(16),
|
MySpacing.height(16),
|
||||||
MyText.labelMedium("Tags (Optional)"),
|
_labelWithStar("Tags", required: true),
|
||||||
MySpacing.height(8),
|
MySpacing.height(8),
|
||||||
_tagInput(),
|
_tagInput(),
|
||||||
MySpacing.height(16),
|
MySpacing.height(16),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user