feat: Update employee creation process and API endpoints for improved functionality

This commit is contained in:
Vaibhav Surve 2025-07-23 11:58:09 +05:30
parent 943c7c7b50
commit 69e64ec789
5 changed files with 99 additions and 52 deletions

View File

@ -110,7 +110,8 @@ class AddEmployeeController extends MyController {
logSafe("Failed to fetch roles: null result", level: LogLevel.error); logSafe("Failed to fetch roles: null result", level: LogLevel.error);
} }
} catch (e, st) { } catch (e, st) {
logSafe("Error fetching roles", level: LogLevel.error, error: e, stackTrace: st); logSafe("Error fetching roles",
level: LogLevel.error, error: e, stackTrace: st);
} }
} }
@ -120,7 +121,7 @@ class AddEmployeeController extends MyController {
update(); update();
} }
Future<bool> createEmployees() async { Future<Map<String, dynamic>?> createEmployees() async {
logSafe("Starting employee creation..."); logSafe("Starting employee creation...");
if (selectedGender == null || selectedRoleId == null) { if (selectedGender == null || selectedRoleId == null) {
logSafe("Missing gender or role.", level: LogLevel.warning); logSafe("Missing gender or role.", level: LogLevel.warning);
@ -129,14 +130,13 @@ class AddEmployeeController extends MyController {
message: "Please select both Gender and Role.", message: "Please select both Gender and Role.",
type: SnackbarType.warning, type: SnackbarType.warning,
); );
return false; return null;
} }
final firstName = basicValidator.getController("first_name")?.text.trim(); final firstName = basicValidator.getController("first_name")?.text.trim();
final lastName = basicValidator.getController("last_name")?.text.trim(); final lastName = basicValidator.getController("last_name")?.text.trim();
final phoneNumber = basicValidator.getController("phone_number")?.text.trim(); final phoneNumber =
basicValidator.getController("phone_number")?.text.trim();
logSafe("Creating employee", level: LogLevel.info);
try { try {
final response = await ApiService.createEmployee( final response = await ApiService.createEmployee(
@ -146,20 +146,24 @@ class AddEmployeeController extends MyController {
gender: selectedGender!.name, gender: selectedGender!.name,
jobRoleId: selectedRoleId!, jobRoleId: selectedRoleId!,
); );
logSafe("Response: $response");
if (response == true) { logSafe("Response: $response");
if (response != null && response['success'] == true) {
logSafe("Employee created successfully."); logSafe("Employee created successfully.");
showAppSnackbar( showAppSnackbar(
title: "Success", title: "Success",
message: "Employee created successfully!", message: "Employee created successfully!",
type: SnackbarType.success, type: SnackbarType.success,
); );
return true; return response;
} else { } else {
logSafe("Failed to create employee (response false)", level: LogLevel.error); logSafe("Failed to create employee (response false)",
level: LogLevel.error);
} }
} catch (e, st) { } catch (e, st) {
logSafe("Error creating employee", level: LogLevel.error, error: e, stackTrace: st); logSafe("Error creating employee",
level: LogLevel.error, error: e, stackTrace: st);
} }
showAppSnackbar( showAppSnackbar(
@ -167,7 +171,7 @@ logSafe("Response: $response");
message: "Failed to create employee.", message: "Failed to create employee.",
type: SnackbarType.error, type: SnackbarType.error,
); );
return false; return null;
} }
Future<bool> _checkAndRequestContactsPermission() async { Future<bool> _checkAndRequestContactsPermission() async {
@ -181,7 +185,8 @@ logSafe("Response: $response");
showAppSnackbar( showAppSnackbar(
title: "Permission Required", title: "Permission Required",
message: "Please allow Contacts permission from settings to pick a contact.", message:
"Please allow Contacts permission from settings to pick a contact.",
type: SnackbarType.warning, type: SnackbarType.warning,
); );
return false; return false;
@ -195,7 +200,8 @@ logSafe("Response: $response");
final picked = await FlutterContacts.openExternalPick(); final picked = await FlutterContacts.openExternalPick();
if (picked == null) return; if (picked == null) return;
final contact = await FlutterContacts.getContact(picked.id, withProperties: true); final contact =
await FlutterContacts.getContact(picked.id, withProperties: true);
if (contact == null) { if (contact == null) {
showAppSnackbar( showAppSnackbar(
title: "Error", title: "Error",
@ -216,7 +222,8 @@ logSafe("Response: $response");
final indiaPhones = contact.phones.where((p) { final indiaPhones = contact.phones.where((p) {
final normalized = p.number.replaceAll(RegExp(r'[^0-9+]'), ''); final normalized = p.number.replaceAll(RegExp(r'[^0-9+]'), '');
return normalized.startsWith('+91') || RegExp(r'^\d{10}$').hasMatch(normalized); return normalized.startsWith('+91') ||
RegExp(r'^\d{10}$').hasMatch(normalized);
}).toList(); }).toList();
if (indiaPhones.isEmpty) { if (indiaPhones.isEmpty) {
@ -256,10 +263,12 @@ logSafe("Response: $response");
? normalizedPhone.substring(normalizedPhone.length - 10) ? normalizedPhone.substring(normalizedPhone.length - 10)
: normalizedPhone; : normalizedPhone;
basicValidator.getController('phone_number')?.text = phoneWithoutCountryCode; basicValidator.getController('phone_number')?.text =
phoneWithoutCountryCode;
update(); update();
} catch (e, st) { } catch (e, st) {
logSafe("Error fetching contacts", level: LogLevel.error, error: e, stackTrace: st); logSafe("Error fetching contacts",
level: LogLevel.error, error: e, stackTrace: st);
showAppSnackbar( showAppSnackbar(
title: "Error", title: "Error",
message: "Failed to fetch contacts.", message: "Failed to fetch contacts.",

View File

@ -18,7 +18,7 @@ class ApiEndpoints {
static const String getAllEmployeesByProject = "/employee/list"; static const String getAllEmployeesByProject = "/employee/list";
static const String getAllEmployees = "/employee/list"; static const String getAllEmployees = "/employee/list";
static const String getRoles = "/roles/jobrole"; static const String getRoles = "/roles/jobrole";
static const String createEmployee = "/employee/manage"; static const String createEmployee = "/employee/manage-mobile";
static const String getEmployeeInfo = "/employee/profile/get"; static const String getEmployeeInfo = "/employee/profile/get";
static const String assignEmployee = "/employee/profile/get"; static const String assignEmployee = "/employee/profile/get";
static const String getAssignedProjects = "/project/assigned-projects"; static const String getAssignedProjects = "/project/assigned-projects";

View File

@ -479,37 +479,37 @@ class ApiService {
/// Get list of assigned projects for a specific employee /// Get list of assigned projects for a specific employee
/// Get list of assigned projects for a specific employee /// Get list of assigned projects for a specific employee
static Future<List<dynamic>?> getAssignedProjects(String employeeId) async { static Future<List<dynamic>?> getAssignedProjects(String employeeId) async {
if (employeeId.isEmpty) { if (employeeId.isEmpty) {
throw ArgumentError("employeeId must not be empty"); throw ArgumentError("employeeId must not be empty");
}
final endpoint = "${ApiEndpoints.getAssignedProjects}/$employeeId";
try {
final response = await _getRequest(endpoint);
if (response == null) {
logSafe("Failed to fetch assigned projects: null response",
level: LogLevel.error);
return null;
} }
final parsed = _parseResponse(response, label: "Assigned Projects"); final endpoint = "${ApiEndpoints.getAssignedProjects}/$employeeId";
if (parsed is List) {
return parsed; try {
} else { final response = await _getRequest(endpoint);
logSafe("Unexpected response format for assigned projects.",
if (response == null) {
logSafe("Failed to fetch assigned projects: null response",
level: LogLevel.error);
return null;
}
final parsed = _parseResponse(response, label: "Assigned Projects");
if (parsed is List) {
return parsed;
} else {
logSafe("Unexpected response format for assigned projects.",
level: LogLevel.error);
return null;
}
} catch (e, stack) {
logSafe("Exception during getAssignedProjects API: $e",
level: LogLevel.error); level: LogLevel.error);
logSafe("StackTrace: $stack", level: LogLevel.debug);
return null; return null;
} }
} catch (e, stack) {
logSafe("Exception during getAssignedProjects API: $e",
level: LogLevel.error);
logSafe("StackTrace: $stack", level: LogLevel.debug);
return null;
} }
}
/// Assign projects to a specific employee /// Assign projects to a specific employee
static Future<bool> assignProjects({ static Future<bool> assignProjects({
@ -839,8 +839,7 @@ static Future<List<dynamic>?> getAssignedProjects(String employeeId) async {
static Future<List<dynamic>?> getRoles() async => static Future<List<dynamic>?> getRoles() async =>
_getRequest(ApiEndpoints.getRoles).then( _getRequest(ApiEndpoints.getRoles).then(
(res) => res != null ? _parseResponse(res, label: 'Roles') : null); (res) => res != null ? _parseResponse(res, label: 'Roles') : null);
static Future<Map<String, dynamic>?> createEmployee({
static Future<bool> createEmployee({
required String firstName, required String firstName,
required String lastName, required String lastName,
required String phoneNumber, required String phoneNumber,
@ -854,15 +853,20 @@ static Future<List<dynamic>?> getAssignedProjects(String employeeId) async {
"gender": gender, "gender": gender,
"jobRoleId": jobRoleId, "jobRoleId": jobRoleId,
}; };
final response = await _postRequest( final response = await _postRequest(
ApiEndpoints.createEmployee, ApiEndpoints.createEmployee,
body, body,
customTimeout: extendedTimeout, customTimeout: extendedTimeout,
); );
if (response == null) return false; if (response == null) return null;
final json = jsonDecode(response.body); final json = jsonDecode(response.body);
return response.statusCode == 200 && json['success'] == true; return {
"success": response.statusCode == 200 && json['success'] == true,
"data": json
};
} }
static Future<Map<String, dynamic>?> getEmployeeDetails( static Future<Map<String, dynamic>?> getEmployeeDetails(

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter/services.dart';
import 'package:marco/controller/dashboard/add_employee_controller.dart'; import 'package:marco/controller/dashboard/add_employee_controller.dart';
import 'package:marco/controller/dashboard/employees_screen_controller.dart'; import 'package:marco/controller/dashboard/employees_screen_controller.dart';
@ -274,6 +275,12 @@ class _AddEmployeeBottomSheetState extends State<AddEmployeeBottomSheet>
return null; return null;
}, },
keyboardType: TextInputType.phone, keyboardType: TextInputType.phone,
inputFormatters: [
// Allow only digits
FilteringTextInputFormatter.digitsOnly,
// Limit to 10 digits
LengthLimitingTextInputFormatter(10),
],
decoration: _inputDecoration("e.g., 9876543210") decoration: _inputDecoration("e.g., 9876543210")
.copyWith( .copyWith(
suffixIcon: IconButton( suffixIcon: IconButton(
@ -355,9 +362,12 @@ class _AddEmployeeBottomSheetState extends State<AddEmployeeBottomSheet>
onPressed: () async { onPressed: () async {
if (_controller.basicValidator if (_controller.basicValidator
.validateForm()) { .validateForm()) {
final success = final result =
await _controller.createEmployees(); await _controller.createEmployees();
if (success) {
if (result != null &&
result['success'] == true) {
final employeeData = result['data'];
final employeeController = final employeeController =
Get.find<EmployeesScreenController>(); Get.find<EmployeesScreenController>();
final projectId = final projectId =
@ -387,7 +397,7 @@ class _AddEmployeeBottomSheetState extends State<AddEmployeeBottomSheet>
_controller.selectedRoleId = null; _controller.selectedRoleId = null;
_controller.update(); _controller.update();
Navigator.pop(context); Navigator.pop(context, employeeData);
} }
} }
}, },

View File

@ -13,6 +13,7 @@ import 'package:marco/helpers/widgets/my_custom_skeleton.dart';
import 'package:marco/view/employees/employee_detail_screen.dart'; import 'package:marco/view/employees/employee_detail_screen.dart';
import 'package:marco/model/employee_model.dart'; import 'package:marco/model/employee_model.dart';
import 'package:marco/helpers/utils/launcher_utils.dart'; import 'package:marco/helpers/utils/launcher_utils.dart';
import 'package:marco/view/employees/assign_employee_bottom_sheet.dart';
class EmployeesScreen extends StatefulWidget { class EmployeesScreen extends StatefulWidget {
const EmployeesScreen({super.key}); const EmployeesScreen({super.key});
@ -162,7 +163,8 @@ class _EmployeesScreenState extends State<EmployeesScreen> with UIMixin {
), ),
floatingActionButton: InkWell( floatingActionButton: InkWell(
onTap: () async { onTap: () async {
final result = await showModalBottomSheet<bool>( // Step 1: Open AddEmployeeBottomSheet
final result = await showModalBottomSheet<Map<String, dynamic>>(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
shape: const RoundedRectangleBorder( shape: const RoundedRectangleBorder(
@ -172,7 +174,29 @@ class _EmployeesScreenState extends State<EmployeesScreen> with UIMixin {
builder: (context) => AddEmployeeBottomSheet(), builder: (context) => AddEmployeeBottomSheet(),
); );
if (result == true) { // Step 2: Check if employee creation was successful
if (result != null && result['success'] == true) {
print("Employee data from bottom sheet: $result");
final employeeData = result['data'];
final employeeId = employeeData['id'] as String;
final jobRoleId = employeeData['jobRoleId'] as String?;
// Step 3: Open AssignProjectBottomSheet for the new employee
await showModalBottomSheet(
context: context,
isScrollControlled: true,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
),
backgroundColor: Colors.transparent,
builder: (context) => AssignProjectBottomSheet(
employeeId: employeeId,
jobRoleId: jobRoleId ?? '',
),
);
// Step 4: Refresh employee list after project assignment
await _refreshEmployees(); await _refreshEmployees();
} }
}, },