From 4836dd994ce2df9cf445acd5d13178f43fa6542d Mon Sep 17 00:00:00 2001 From: Vaibhav Surve Date: Thu, 18 Sep 2025 17:42:27 +0530 Subject: [PATCH] feat: Implement employee editing functionality; add prefill logic and update API service for createOrUpdateEmployee --- .../employee/add_employee_controller.dart | 64 +++++++++++--- lib/helpers/services/api_service.dart | 6 +- .../employees/add_employee_bottom_sheet.dart | 85 ++++++------------- .../employees/employee_detail_screen.dart | 36 +++++++- 4 files changed, 114 insertions(+), 77 deletions(-) diff --git a/lib/controller/employee/add_employee_controller.dart b/lib/controller/employee/add_employee_controller.dart index b23e842..4d51351 100644 --- a/lib/controller/employee/add_employee_controller.dart +++ b/lib/controller/employee/add_employee_controller.dart @@ -7,6 +7,7 @@ import 'package:marco/helpers/widgets/my_snackbar.dart'; import 'package:flutter_contacts/flutter_contacts.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:marco/helpers/services/app_logger.dart'; +import 'package:collection/collection.dart'; enum Gender { male, @@ -17,6 +18,8 @@ enum Gender { } class AddEmployeeController extends MyController { + Map? editingEmployeeData; // For edit mode + List files = []; final MyFormValidator basicValidator = MyFormValidator(); Gender? selectedGender; @@ -33,12 +36,10 @@ class AddEmployeeController extends MyController { logSafe("Initializing AddEmployeeController..."); _initializeFields(); fetchRoles(); - } - void setJoiningDate(DateTime date) { - joiningDate = date; - logSafe("Joining date selected: $date"); - update(); + if (editingEmployeeData != null) { + prefillFields(); + } } void _initializeFields() { @@ -63,6 +64,37 @@ class AddEmployeeController extends MyController { logSafe("Fields initialized for first_name, phone_number, last_name."); } + /// Prefill fields in edit mode + // In AddEmployeeController + void prefillFields() { + logSafe("Prefilling data for editing..."); + basicValidator.getController('first_name')?.text = + editingEmployeeData?['first_name'] ?? ''; + basicValidator.getController('last_name')?.text = + editingEmployeeData?['last_name'] ?? ''; + basicValidator.getController('phone_number')?.text = + editingEmployeeData?['phone_number'] ?? ''; + + selectedGender = editingEmployeeData?['gender'] != null + ? Gender.values + .firstWhereOrNull((g) => g.name == editingEmployeeData!['gender']) + : null; + + selectedRoleId = editingEmployeeData?['job_role_id']; + + if (editingEmployeeData?['joining_date'] != null) { + joiningDate = DateTime.tryParse(editingEmployeeData!['joining_date']); + } + + update(); + } + + void setJoiningDate(DateTime date) { + joiningDate = date; + logSafe("Joining date selected: $date"); + update(); + } + void onGenderSelected(Gender? gender) { selectedGender = gender; logSafe("Gender selected: ${gender?.name}"); @@ -92,10 +124,13 @@ class AddEmployeeController extends MyController { update(); } - Future?> createEmployees() async { - logSafe("Starting employee creation..."); + /// Create or update employee + Future?> createOrUpdateEmployee() async { + logSafe(editingEmployeeData != null + ? "Starting employee update..." + : "Starting employee creation..."); + if (selectedGender == null || selectedRoleId == null) { - logSafe("Missing gender or role.", level: LogLevel.warning); showAppSnackbar( title: "Missing Fields", message: "Please select both Gender and Role.", @@ -111,6 +146,7 @@ class AddEmployeeController extends MyController { try { final response = await ApiService.createEmployee( + id: editingEmployeeData?['id'], // Pass id if editing firstName: firstName!, lastName: lastName!, phoneNumber: phoneNumber!, @@ -122,25 +158,25 @@ class AddEmployeeController extends MyController { logSafe("Response: $response"); if (response != null && response['success'] == true) { - logSafe("Employee created successfully."); showAppSnackbar( title: "Success", - message: "Employee created successfully!", + message: editingEmployeeData != null + ? "Employee updated successfully!" + : "Employee created successfully!", type: SnackbarType.success, ); return response; } else { - logSafe("Failed to create employee (response false)", - level: LogLevel.error); + logSafe("Failed operation", level: LogLevel.error); } } catch (e, st) { - logSafe("Error creating employee", + logSafe("Error creating/updating employee", level: LogLevel.error, error: e, stackTrace: st); } showAppSnackbar( title: "Error", - message: "Failed to create employee.", + message: "Failed to save employee.", type: SnackbarType.error, ); return null; diff --git a/lib/helpers/services/api_service.dart b/lib/helpers/services/api_service.dart index d0f12e2..68f58c9 100644 --- a/lib/helpers/services/api_service.dart +++ b/lib/helpers/services/api_service.dart @@ -1880,6 +1880,7 @@ class ApiService { _getRequest(ApiEndpoints.getRoles).then( (res) => res != null ? _parseResponse(res, label: 'Roles') : null); static Future?> createEmployee({ + String? id, // Optional, for editing required String firstName, required String lastName, required String phoneNumber, @@ -1888,12 +1889,13 @@ class ApiService { required String joiningDate, }) async { final body = { + if (id != null) "id": id, // Include id only if editing "firstName": firstName, "lastName": lastName, "phoneNumber": phoneNumber, "gender": gender, "jobRoleId": jobRoleId, - "joiningDate": joiningDate + "joiningDate": joiningDate, }; final response = await _postRequest( @@ -1907,7 +1909,7 @@ class ApiService { final json = jsonDecode(response.body); return { "success": response.statusCode == 200 && json['success'] == true, - "data": json + "data": json, }; } diff --git a/lib/model/employees/add_employee_bottom_sheet.dart b/lib/model/employees/add_employee_bottom_sheet.dart index a59d160..a4cc09a 100644 --- a/lib/model/employees/add_employee_bottom_sheet.dart +++ b/lib/model/employees/add_employee_bottom_sheet.dart @@ -11,15 +11,31 @@ import 'package:marco/helpers/utils/base_bottom_sheet.dart'; import 'package:intl/intl.dart'; import 'package:marco/helpers/widgets/my_snackbar.dart'; - class AddEmployeeBottomSheet extends StatefulWidget { + final Map? employeeData; + AddEmployeeBottomSheet({this.employeeData}); + @override State createState() => _AddEmployeeBottomSheetState(); } class _AddEmployeeBottomSheetState extends State with UIMixin { - final AddEmployeeController _controller = Get.put(AddEmployeeController()); + late final AddEmployeeController _controller; + + @override + void initState() { + super.initState(); + _controller = Get.put( + AddEmployeeController(), + tag: UniqueKey().toString(), + ); + + if (widget.employeeData != null) { + _controller.editingEmployeeData = widget.employeeData; + _controller.prefillFields(); + } + } @override Widget build(BuildContext context) { @@ -27,7 +43,7 @@ class _AddEmployeeBottomSheetState extends State init: _controller, builder: (_) { return BaseBottomSheet( - title: "Add Employee", + title: widget.employeeData != null ? "Edit Employee" : "Add Employee", onCancel: () => Navigator.pop(context), onSubmit: _handleSubmit, child: Form( @@ -98,7 +114,6 @@ class _AddEmployeeBottomSheetState extends State ); } - // --- Common label with red star --- Widget _requiredLabel(String text) { return Row( children: [ @@ -109,7 +124,6 @@ class _AddEmployeeBottomSheetState extends State ); } - // --- Date Picker field --- Widget _buildDatePickerField({ required String label, required String value, @@ -146,7 +160,7 @@ class _AddEmployeeBottomSheetState extends State Future _pickJoiningDate(BuildContext context) async { final picked = await showDatePicker( context: context, - initialDate: DateTime.now(), + initialDate: _controller.joiningDate ?? DateTime.now(), firstDate: DateTime(2000), lastDate: DateTime(2100), ); @@ -157,54 +171,25 @@ class _AddEmployeeBottomSheetState extends State } } - // --- Submit logic --- Future _handleSubmit() async { - // Run form validation first final isValid = _controller.basicValidator.formKey.currentState?.validate() ?? false; - if (!isValid) { + if (!isValid || + _controller.joiningDate == null || + _controller.selectedGender == null || + _controller.selectedRoleId == null) { showAppSnackbar( title: "Missing Fields", - message: "Please fill all required fields before submitting.", + message: "Please complete all required fields.", type: SnackbarType.warning, ); return; } - // Additional check for dropdowns & joining date - if (_controller.joiningDate == null) { - showAppSnackbar( - title: "Missing Fields", - message: "Please select Joining Date.", - type: SnackbarType.warning, - ); - return; - } - - if (_controller.selectedGender == null) { - showAppSnackbar( - title: "Missing Fields", - message: "Please select Gender.", - type: SnackbarType.warning, - ); - return; - } - - if (_controller.selectedRoleId == null) { - showAppSnackbar( - title: "Missing Fields", - message: "Please select Role.", - type: SnackbarType.warning, - ); - return; - } - - // All validations passed → Call API - final result = await _controller.createEmployees(); + final result = await _controller.createOrUpdateEmployee(); if (result != null && result['success'] == true) { - final employeeData = result['data']; final employeeController = Get.find(); final projectId = employeeController.selectedProjectId; @@ -216,20 +201,10 @@ class _AddEmployeeBottomSheetState extends State employeeController.update(['employee_screen_controller']); - // Reset form - _controller.basicValidator.getController("first_name")?.clear(); - _controller.basicValidator.getController("last_name")?.clear(); - _controller.basicValidator.getController("phone_number")?.clear(); - _controller.selectedGender = null; - _controller.selectedRoleId = null; - _controller.joiningDate = null; - _controller.update(); - - Navigator.pop(context, employeeData); + Navigator.pop(context, result['data']); } } - // --- Section label widget --- Widget _sectionLabel(String title) => Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -239,7 +214,6 @@ class _AddEmployeeBottomSheetState extends State ], ); - // --- Input field with icon --- Widget _inputWithIcon({ required String label, required String hint, @@ -268,7 +242,6 @@ class _AddEmployeeBottomSheetState extends State ); } - // --- Phone input --- Widget _buildPhoneInput(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -322,7 +295,6 @@ class _AddEmployeeBottomSheetState extends State ); } - // --- Dropdown (Gender/Role) --- Widget _buildDropdownField({ required String label, required String value, @@ -356,7 +328,6 @@ class _AddEmployeeBottomSheetState extends State ); } - // --- Common input decoration --- InputDecoration _inputDecoration(String hint) { return InputDecoration( hintText: hint, @@ -379,7 +350,6 @@ class _AddEmployeeBottomSheetState extends State ); } - // --- Gender popup --- void _showGenderPopup(BuildContext context) async { final selected = await showMenu( context: context, @@ -398,7 +368,6 @@ class _AddEmployeeBottomSheetState extends State } } - // --- Role popup --- void _showRolePopup(BuildContext context) async { final selected = await showMenu( context: context, diff --git a/lib/view/employees/employee_detail_screen.dart b/lib/view/employees/employee_detail_screen.dart index ffd1a63..bcf0098 100644 --- a/lib/view/employees/employee_detail_screen.dart +++ b/lib/view/employees/employee_detail_screen.dart @@ -11,6 +11,7 @@ import 'package:marco/helpers/utils/launcher_utils.dart'; import 'package:marco/controller/permission_controller.dart'; import 'package:marco/helpers/utils/permission_constants.dart'; import 'package:marco/helpers/widgets/my_refresh_indicator.dart'; +import 'package:marco/model/employees/add_employee_bottom_sheet.dart'; class EmployeeDetailPage extends StatefulWidget { final String employeeId; @@ -92,8 +93,9 @@ class _EmployeeDetailPageState extends State { fontWeight: FontWeight.normal, color: (isEmail || isPhone) ? Colors.indigo : Colors.black54, fontSize: 14, - decoration: - (isEmail || isPhone) ? TextDecoration.underline : TextDecoration.none, + decoration: (isEmail || isPhone) + ? TextDecoration.underline + : TextDecoration.none, ), ), ); @@ -231,7 +233,7 @@ class _EmployeeDetailPageState extends State { lastName: employee.lastName, size: 45, ), - MySpacing.width(16), + MySpacing.width(12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -248,6 +250,34 @@ class _EmployeeDetailPageState extends State { ], ), ), + IconButton( + icon: const Icon(Icons.edit, + size: 24, color: Colors.red), + onPressed: () async { + final result = + await showModalBottomSheet>( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (_) => AddEmployeeBottomSheet( + employeeData: { + 'id': employee.id, + 'first_name': employee.firstName, + 'last_name': employee.lastName, + 'phone_number': employee.phoneNumber, + 'gender': employee.gender.toLowerCase(), + 'job_role_id': employee.jobRoleId, + 'joining_date': + employee.joiningDate?.toIso8601String(), + }, + ), + ); + + if (result != null) { + controller.fetchEmployeeDetails(widget.employeeId); + } + }, + ), ], ), MySpacing.height(14),