Add employee management features including controller, models, and UI
- Implemented AddEmployeeController for handling employee creation logic. - Created EmployeeScreen and AddEmployeeScreen for employee management UI. - Added API endpoints for fetching and creating employees. - Updated EmployeeModel to include additional fields: jobRole, email, and phoneNumber. - Refactored MyRefreshWrapper to MyRefreshableContent for consistency. - Enhanced navigation to include employee management routes.
This commit is contained in:
parent
1ea8447d6c
commit
1ea960b0ec
119
lib/controller/dashboard/add_employee_controller.dart
Normal file
119
lib/controller/dashboard/add_employee_controller.dart
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
import 'package:file_picker/file_picker.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:marco/controller/my_controller.dart';
|
||||||
|
import 'package:marco/helpers/widgets/my_form_validator.dart';
|
||||||
|
import 'package:marco/helpers/services/api_service.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:logger/logger.dart';
|
||||||
|
|
||||||
|
enum Gender {
|
||||||
|
male,
|
||||||
|
female,
|
||||||
|
other;
|
||||||
|
|
||||||
|
const Gender();
|
||||||
|
}
|
||||||
|
|
||||||
|
final Logger logger = Logger();
|
||||||
|
|
||||||
|
class AddEmployeeController extends MyController {
|
||||||
|
List<PlatformFile> files = [];
|
||||||
|
MyFormValidator basicValidator = MyFormValidator();
|
||||||
|
Gender? selectedGender;
|
||||||
|
List<Map<String, dynamic>> roles = [];
|
||||||
|
String? selectedRoleId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
logger.i("Initializing AddEmployeeController...");
|
||||||
|
fetchRoles();
|
||||||
|
basicValidator.addField(
|
||||||
|
'first_name',
|
||||||
|
label: "First Name",
|
||||||
|
required: true,
|
||||||
|
controller: TextEditingController(),
|
||||||
|
);
|
||||||
|
basicValidator.addField(
|
||||||
|
'phone_number',
|
||||||
|
label: "Phone Number",
|
||||||
|
required: true,
|
||||||
|
controller: TextEditingController(),
|
||||||
|
);
|
||||||
|
basicValidator.addField(
|
||||||
|
'last_name',
|
||||||
|
label: "Last Name",
|
||||||
|
required: true,
|
||||||
|
controller: TextEditingController(),
|
||||||
|
);
|
||||||
|
logger.i("Fields initialized for first_name, phone_number, last_name.");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool showOnline = true;
|
||||||
|
|
||||||
|
final List<String> categories = [];
|
||||||
|
|
||||||
|
void onGenderSelected(Gender? gender) {
|
||||||
|
selectedGender = gender;
|
||||||
|
logger.i("Gender selected: ${gender?.name}");
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> fetchRoles() async {
|
||||||
|
logger.i("Fetching roles...");
|
||||||
|
final result = await ApiService.getRoles();
|
||||||
|
if (result != null) {
|
||||||
|
roles = List<Map<String, dynamic>>.from(result);
|
||||||
|
logger.i("Roles fetched successfully.");
|
||||||
|
update();
|
||||||
|
} else {
|
||||||
|
logger.e("Failed to fetch roles.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onRoleSelected(String? roleId) {
|
||||||
|
selectedRoleId = roleId;
|
||||||
|
logger.i("Role selected: $roleId");
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> createEmployees() async {
|
||||||
|
logger.i("Starting employee creation...");
|
||||||
|
if (selectedGender == null || selectedRoleId == null) {
|
||||||
|
logger.w("Missing gender or role.");
|
||||||
|
Get.snackbar(
|
||||||
|
"Missing Fields",
|
||||||
|
"Please select both Gender and Role.",
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final firstName = basicValidator.getController("first_name")?.text.trim();
|
||||||
|
final lastName = basicValidator.getController("last_name")?.text.trim();
|
||||||
|
final phoneNumber =
|
||||||
|
basicValidator.getController("phone_number")?.text.trim();
|
||||||
|
|
||||||
|
logger.i(
|
||||||
|
"Creating employee with Name: $firstName $lastName, Phone: $phoneNumber, Gender: ${selectedGender!.name}");
|
||||||
|
|
||||||
|
final response = await ApiService.createEmployee(
|
||||||
|
firstName: firstName!,
|
||||||
|
lastName: lastName!,
|
||||||
|
phoneNumber: phoneNumber!,
|
||||||
|
gender: selectedGender!.name,
|
||||||
|
jobRoleId: selectedRoleId!,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response == true) {
|
||||||
|
logger.i("Employee created successfully.");
|
||||||
|
Get.back(); // Or navigate as needed
|
||||||
|
Get.snackbar("Success", "Employee created successfully!",
|
||||||
|
snackPosition: SnackPosition.BOTTOM);
|
||||||
|
} else {
|
||||||
|
logger.e("Failed to create employee.");
|
||||||
|
Get.snackbar("Error", "Failed to create employee.",
|
||||||
|
snackPosition: SnackPosition.BOTTOM);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
102
lib/controller/dashboard/employees_screen_controller.dart
Normal file
102
lib/controller/dashboard/employees_screen_controller.dart
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:logger/logger.dart';
|
||||||
|
import 'package:marco/helpers/services/api_service.dart';
|
||||||
|
import 'package:marco/model/attendance_model.dart';
|
||||||
|
import 'package:marco/model/project_model.dart';
|
||||||
|
import 'package:marco/model/employee_model.dart';
|
||||||
|
|
||||||
|
final Logger log = Logger();
|
||||||
|
|
||||||
|
class EmployeesScreenController extends GetxController {
|
||||||
|
List<AttendanceModel> attendances = [];
|
||||||
|
List<ProjectModel> projects = [];
|
||||||
|
String? selectedProjectId;
|
||||||
|
List<EmployeeModel> employees = [];
|
||||||
|
|
||||||
|
RxBool isLoading = false.obs;
|
||||||
|
RxMap<String, RxBool> uploadingStates = <String, RxBool>{}.obs;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
fetchAllProjects();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> fetchAllProjects() async {
|
||||||
|
isLoading.value = true;
|
||||||
|
await _handleApiCall(
|
||||||
|
ApiService.getProjects,
|
||||||
|
onSuccess: (data) {
|
||||||
|
projects = data.map((json) => ProjectModel.fromJson(json)).toList();
|
||||||
|
log.i("Projects fetched: ${projects.length} projects loaded.");
|
||||||
|
},
|
||||||
|
onEmpty: () => log.w("No project data found or API call failed."),
|
||||||
|
);
|
||||||
|
isLoading.value = false;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> fetchAllEmployees() async {
|
||||||
|
isLoading.value = true;
|
||||||
|
await _handleApiCall(
|
||||||
|
ApiService.getAllEmployees,
|
||||||
|
onSuccess: (data) {
|
||||||
|
employees = data.map((json) => EmployeeModel.fromJson(json)).toList();
|
||||||
|
log.i("All Employees fetched: ${employees.length} employees loaded.");
|
||||||
|
},
|
||||||
|
onEmpty: () => log.w("No Employee data found or API call failed."),
|
||||||
|
);
|
||||||
|
isLoading.value = false;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> fetchEmployeesByProject(String? projectId) async {
|
||||||
|
if (projectId == null || projectId.isEmpty) {
|
||||||
|
log.e("Project ID is required but was null or empty.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = true;
|
||||||
|
await _handleApiCall(
|
||||||
|
() => ApiService.getAllEmployeesByProject(projectId),
|
||||||
|
onSuccess: (data) {
|
||||||
|
employees = data.map((json) => EmployeeModel.fromJson(json)).toList();
|
||||||
|
for (var emp in employees) {
|
||||||
|
uploadingStates[emp.id] = false.obs;
|
||||||
|
}
|
||||||
|
log.i("Employees fetched: ${employees.length} for project $projectId");
|
||||||
|
update();
|
||||||
|
},
|
||||||
|
onEmpty: () {
|
||||||
|
log.w("No employees found for project $projectId.");
|
||||||
|
employees = [];
|
||||||
|
update();
|
||||||
|
},
|
||||||
|
onError: (e) =>
|
||||||
|
log.e("Error fetching employees for project $projectId: $e"),
|
||||||
|
);
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleApiCall(
|
||||||
|
Future<List<dynamic>?> Function() apiCall, {
|
||||||
|
required Function(List<dynamic>) onSuccess,
|
||||||
|
required Function() onEmpty,
|
||||||
|
Function(dynamic error)? onError,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final response = await apiCall();
|
||||||
|
if (response != null && response.isNotEmpty) {
|
||||||
|
onSuccess(response);
|
||||||
|
} else {
|
||||||
|
onEmpty();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (onError != null) {
|
||||||
|
onError(e);
|
||||||
|
} else {
|
||||||
|
log.e("API call error: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,5 +10,8 @@ class ApiEndpoints {
|
|||||||
static const String uploadAttendanceImage = "/attendance/record-image";
|
static const String uploadAttendanceImage = "/attendance/record-image";
|
||||||
|
|
||||||
// Employee Screen API Endpoints
|
// Employee Screen API Endpoints
|
||||||
|
static const String getAllEmployeesByProject = "/Project/employees/get";
|
||||||
|
static const String getAllEmployees = "/employee/list";
|
||||||
|
static const String getRoles = "/roles/jobrole";
|
||||||
|
static const String createEmployee = "/employee/manage-mobile";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,7 +53,7 @@ class ApiService {
|
|||||||
|
|
||||||
Uri uri = Uri.parse("${ApiEndpoints.baseUrl}$endpoint")
|
Uri uri = Uri.parse("${ApiEndpoints.baseUrl}$endpoint")
|
||||||
.replace(queryParameters: queryParams);
|
.replace(queryParameters: queryParams);
|
||||||
_log('GET request: $uri');
|
_log("GET $uri");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
http.Response response =
|
http.Response response =
|
||||||
@ -69,7 +69,7 @@ class ApiService {
|
|||||||
queryParams: queryParams, hasRetried: true);
|
queryParams: queryParams, hasRetried: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_log("Refresh failed.");
|
_log("Token refresh failed.");
|
||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -78,35 +78,44 @@ class ApiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<http.Response?> _postRequest(String endpoint, dynamic body) async {
|
static Future<http.Response?> _postRequest(
|
||||||
|
String endpoint, dynamic body) async {
|
||||||
String? token = await _getToken();
|
String? token = await _getToken();
|
||||||
if (token == null) return null;
|
if (token == null) return null;
|
||||||
|
|
||||||
final uri = Uri.parse("${ApiEndpoints.baseUrl}$endpoint");
|
final uri = Uri.parse("${ApiEndpoints.baseUrl}$endpoint");
|
||||||
_log("POST request to $uri with body: $body");
|
|
||||||
|
_log("POST $uri");
|
||||||
|
_log("Headers: ${_headers(token)}");
|
||||||
|
_log("Body: $body");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final response = await http
|
final response = await http
|
||||||
.post(uri, headers: _headers(token), body: jsonEncode(body))
|
.post(uri, headers: _headers(token), body: jsonEncode(body))
|
||||||
.timeout(timeout);
|
.timeout(timeout);
|
||||||
|
|
||||||
|
_log("Response Status: ${response.statusCode}");
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_log("HTTP POST Exception: $e");
|
_log("HTTP POST Exception: $e");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// ===== Attendence Screen API Calls =====
|
||||||
// ===== API Calls =====
|
|
||||||
|
|
||||||
static Future<List<dynamic>?> getProjects() async {
|
static Future<List<dynamic>?> getProjects() async {
|
||||||
final response = await _getRequest(ApiEndpoints.getProjects);
|
final response = await _getRequest(ApiEndpoints.getProjects);
|
||||||
return response != null ? _parseResponse(response, label: 'Projects') : null;
|
return response != null
|
||||||
|
? _parseResponse(response, label: 'Projects')
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<List<dynamic>?> getEmployeesByProject(String projectId) async {
|
static Future<List<dynamic>?> getEmployeesByProject(String projectId) async {
|
||||||
final response = await _getRequest(ApiEndpoints.getEmployeesByProject,
|
final response = await _getRequest(ApiEndpoints.getEmployeesByProject,
|
||||||
queryParams: {"projectId": projectId});
|
queryParams: {"projectId": projectId});
|
||||||
return response != null ? _parseResponse(response, label: 'Employees') : null;
|
return response != null
|
||||||
|
? _parseResponse(response, label: 'Employees')
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<List<dynamic>?> getAttendanceLogs(String projectId,
|
static Future<List<dynamic>?> getAttendanceLogs(String projectId,
|
||||||
@ -115,24 +124,30 @@ class ApiService {
|
|||||||
"projectId": projectId,
|
"projectId": projectId,
|
||||||
if (dateFrom != null)
|
if (dateFrom != null)
|
||||||
"dateFrom": DateFormat('yyyy-MM-dd').format(dateFrom),
|
"dateFrom": DateFormat('yyyy-MM-dd').format(dateFrom),
|
||||||
if (dateTo != null)
|
if (dateTo != null) "dateTo": DateFormat('yyyy-MM-dd').format(dateTo),
|
||||||
"dateTo": DateFormat('yyyy-MM-dd').format(dateTo),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
final response =
|
final response =
|
||||||
await _getRequest(ApiEndpoints.getAttendanceLogs, queryParams: query);
|
await _getRequest(ApiEndpoints.getAttendanceLogs, queryParams: query);
|
||||||
return response != null ? _parseResponse(response, label: 'Attendance Logs') : null;
|
return response != null
|
||||||
|
? _parseResponse(response, label: 'Attendance Logs')
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<List<dynamic>?> getAttendanceLogView(String id) async {
|
static Future<List<dynamic>?> getAttendanceLogView(String id) async {
|
||||||
final response = await _getRequest("${ApiEndpoints.getAttendanceLogView}/$id");
|
final response =
|
||||||
return response != null ? _parseResponse(response, label: 'Log Details') : null;
|
await _getRequest("${ApiEndpoints.getAttendanceLogView}/$id");
|
||||||
|
return response != null
|
||||||
|
? _parseResponse(response, label: 'Log Details')
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<List<dynamic>?> getRegularizationLogs(String projectId) async {
|
static Future<List<dynamic>?> getRegularizationLogs(String projectId) async {
|
||||||
final response = await _getRequest(ApiEndpoints.getRegularizationLogs,
|
final response = await _getRequest(ApiEndpoints.getRegularizationLogs,
|
||||||
queryParams: {"projectId": projectId});
|
queryParams: {"projectId": projectId});
|
||||||
return response != null ? _parseResponse(response, label: 'Regularization Logs') : null;
|
return response != null
|
||||||
|
? _parseResponse(response, label: 'Regularization Logs')
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== Upload Attendance Image =====
|
// ===== Upload Attendance Image =====
|
||||||
@ -204,4 +219,74 @@ class ApiService {
|
|||||||
final imageNumber = count.toString().padLeft(3, '0');
|
final imageNumber = count.toString().padLeft(3, '0');
|
||||||
return "${employeeId}_${dateStr}_$imageNumber.jpg";
|
return "${employeeId}_${dateStr}_$imageNumber.jpg";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== Employee Screen API Calls =====
|
||||||
|
static Future<List<dynamic>?> getAllEmployeesByProject(
|
||||||
|
String projectId) async {
|
||||||
|
if (projectId.isEmpty) {
|
||||||
|
throw ArgumentError('projectId must not be empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
final String endpoint =
|
||||||
|
"${ApiEndpoints.getAllEmployeesByProject}/$projectId";
|
||||||
|
final response = await _getRequest(endpoint);
|
||||||
|
|
||||||
|
return response != null
|
||||||
|
? _parseResponse(response, label: 'Employees by Project')
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<List<dynamic>?> getAllEmployees() async {
|
||||||
|
final response = await _getRequest(ApiEndpoints.getAllEmployees);
|
||||||
|
return response != null
|
||||||
|
? _parseResponse(response, label: 'All Employees')
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<List<dynamic>?> getRoles() async {
|
||||||
|
final response = await _getRequest(ApiEndpoints.getRoles);
|
||||||
|
return response != null
|
||||||
|
? _parseResponse(response, label: 'All Employees')
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<bool> createEmployee({
|
||||||
|
required String firstName,
|
||||||
|
required String lastName,
|
||||||
|
required String phoneNumber,
|
||||||
|
required String gender,
|
||||||
|
required String jobRoleId,
|
||||||
|
}) async {
|
||||||
|
final body = {
|
||||||
|
"firstName": firstName,
|
||||||
|
"lastName": lastName,
|
||||||
|
"phoneNumber": phoneNumber,
|
||||||
|
"gender": gender,
|
||||||
|
"jobRoleId": jobRoleId,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make the API request
|
||||||
|
final response = await _postRequest(ApiEndpoints.createEmployee, body);
|
||||||
|
|
||||||
|
if (response == null) {
|
||||||
|
_log("Error: No response from server.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final json = jsonDecode(response.body);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
if (json['success'] == true) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
_log(
|
||||||
|
"Failed to create employee: ${json['message'] ?? 'Unknown error'}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_log(
|
||||||
|
"Failed to create employee. Status code: ${response.statusCode}, Response: ${json['message'] ?? 'No message'}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,26 +1,23 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class MyRefreshWrapper extends StatelessWidget {
|
class MyRefreshableContent extends StatelessWidget {
|
||||||
final Future<void> Function() onRefresh;
|
final Future<void> Function() onRefresh;
|
||||||
final Widget child;
|
final Widget child;
|
||||||
final EdgeInsetsGeometry? padding;
|
|
||||||
|
|
||||||
const MyRefreshWrapper({
|
const MyRefreshableContent({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.onRefresh,
|
required this.onRefresh,
|
||||||
required this.child,
|
required this.child,
|
||||||
this.padding,
|
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: onRefresh,
|
onRefresh: onRefresh,
|
||||||
backgroundColor: Colors.red, // Set background color to red
|
backgroundColor: Colors.red,
|
||||||
color: Colors.white, // Set spinner color to white
|
color: Colors.white,
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
padding: padding,
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
|
||||||
child: child,
|
child: child,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -7,6 +7,9 @@ class EmployeeModel {
|
|||||||
final String checkOut;
|
final String checkOut;
|
||||||
final int activity;
|
final int activity;
|
||||||
int action;
|
int action;
|
||||||
|
final String jobRole;
|
||||||
|
final String email;
|
||||||
|
final String phoneNumber;
|
||||||
|
|
||||||
EmployeeModel({
|
EmployeeModel({
|
||||||
required this.id,
|
required this.id,
|
||||||
@ -17,6 +20,9 @@ class EmployeeModel {
|
|||||||
required this.checkOut,
|
required this.checkOut,
|
||||||
required this.activity,
|
required this.activity,
|
||||||
required this.action,
|
required this.action,
|
||||||
|
required this.jobRole,
|
||||||
|
required this.email,
|
||||||
|
required this.phoneNumber,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory EmployeeModel.fromJson(Map<String, dynamic> json) {
|
factory EmployeeModel.fromJson(Map<String, dynamic> json) {
|
||||||
@ -29,6 +35,9 @@ class EmployeeModel {
|
|||||||
checkOut: json['checkOut']?.toString() ?? '-',
|
checkOut: json['checkOut']?.toString() ?? '-',
|
||||||
action: json['action'] ?? 0,
|
action: json['action'] ?? 0,
|
||||||
activity: json['activity'] ?? 0,
|
activity: json['activity'] ?? 0,
|
||||||
|
jobRole: json['jobRole']?.toString() ?? '-',
|
||||||
|
email: json['email']?.toString() ?? '-',
|
||||||
|
phoneNumber: json['phoneNumber']?.toString() ?? '-',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,6 +52,9 @@ class EmployeeModel {
|
|||||||
'checkOut': checkOut,
|
'checkOut': checkOut,
|
||||||
'action': action,
|
'action': action,
|
||||||
'activity': activity,
|
'activity': activity,
|
||||||
|
'jobRole': jobRole.isEmpty ? '-' : jobRole,
|
||||||
|
'email': email.isEmpty ? '-' : email,
|
||||||
|
'phoneNumber': phoneNumber.isEmpty ? '-' : phoneNumber,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,9 @@ import 'package:marco/view/error_pages/error_500_screen.dart';
|
|||||||
// import 'package:marco/view/dashboard/attendance_screen.dart';
|
// import 'package:marco/view/dashboard/attendance_screen.dart';
|
||||||
import 'package:marco/view/dashboard/attendanceScreen.dart';
|
import 'package:marco/view/dashboard/attendanceScreen.dart';
|
||||||
import 'package:marco/view/dashboard/dashboard_screen.dart';
|
import 'package:marco/view/dashboard/dashboard_screen.dart';
|
||||||
|
import 'package:marco/view/dashboard/add_employee_screen.dart';
|
||||||
|
import 'package:marco/view/dashboard/employee_screen.dart';
|
||||||
|
|
||||||
class AuthMiddleware extends GetMiddleware {
|
class AuthMiddleware extends GetMiddleware {
|
||||||
@override
|
@override
|
||||||
RouteSettings? redirect(String? route) {
|
RouteSettings? redirect(String? route) {
|
||||||
@ -21,20 +24,58 @@ class AuthMiddleware extends GetMiddleware {
|
|||||||
|
|
||||||
getPageRoute() {
|
getPageRoute() {
|
||||||
var routes = [
|
var routes = [
|
||||||
GetPage(name: '/', page: () => const AttendanceScreen(), middlewares: [AuthMiddleware()]),
|
GetPage(
|
||||||
|
name: '/',
|
||||||
|
page: () => const AttendanceScreen(),
|
||||||
|
middlewares: [AuthMiddleware()]),
|
||||||
|
|
||||||
// Dashboard
|
// Dashboard
|
||||||
GetPage(name: '/dashboard/attendance', page: () => AttendanceScreen(), middlewares: [AuthMiddleware()]),
|
GetPage(
|
||||||
GetPage(name: '/dashboard', page: () => DashboardScreen(), middlewares: [AuthMiddleware()]),
|
name: '/dashboard/attendance',
|
||||||
|
page: () => AttendanceScreen(),
|
||||||
|
middlewares: [AuthMiddleware()]),
|
||||||
|
GetPage(
|
||||||
|
name: '/dashboard',
|
||||||
|
page: () => DashboardScreen(),
|
||||||
|
middlewares: [AuthMiddleware()]),
|
||||||
|
GetPage(
|
||||||
|
name: '/dashboard/employees',
|
||||||
|
page: () => EmployeeScreen(),
|
||||||
|
middlewares: [AuthMiddleware()]),
|
||||||
|
// Employees Creation
|
||||||
|
GetPage(
|
||||||
|
name: '/employees/addEmployee',
|
||||||
|
page: () => AddEmployeeScreen(),
|
||||||
|
middlewares: [AuthMiddleware()]),
|
||||||
// Authentication
|
// Authentication
|
||||||
GetPage(name: '/auth/login', page: () => LoginScreen()),
|
GetPage(name: '/auth/login', page: () => LoginScreen()),
|
||||||
GetPage(name: '/auth/register_account', page: () => const RegisterAccountScreen()),
|
GetPage(
|
||||||
GetPage(name: '/auth/forgot_password', page: () => const ForgotPasswordScreen()),
|
name: '/auth/register_account',
|
||||||
GetPage(name: '/auth/reset_password', page: () => const ResetPasswordScreen()),
|
page: () => const RegisterAccountScreen()),
|
||||||
|
GetPage(
|
||||||
|
name: '/auth/forgot_password',
|
||||||
|
page: () => const ForgotPasswordScreen()),
|
||||||
|
GetPage(
|
||||||
|
name: '/auth/reset_password', page: () => const ResetPasswordScreen()),
|
||||||
// Error
|
// Error
|
||||||
GetPage(name: '/error/coming_soon', page: () => ComingSoonScreen(), middlewares: [AuthMiddleware()]),
|
GetPage(
|
||||||
GetPage(name: '/error/500', page: () => Error500Screen(), middlewares: [AuthMiddleware()]),
|
name: '/error/coming_soon',
|
||||||
GetPage(name: '/error/404', page: () => Error404Screen(), middlewares: [AuthMiddleware()]),
|
page: () => ComingSoonScreen(),
|
||||||
|
middlewares: [AuthMiddleware()]),
|
||||||
|
GetPage(
|
||||||
|
name: '/error/500',
|
||||||
|
page: () => Error500Screen(),
|
||||||
|
middlewares: [AuthMiddleware()]),
|
||||||
|
GetPage(
|
||||||
|
name: '/error/404',
|
||||||
|
page: () => Error404Screen(),
|
||||||
|
middlewares: [AuthMiddleware()]),
|
||||||
];
|
];
|
||||||
return routes.map((e) => GetPage(name: e.name, page: e.page, middlewares: e.middlewares, transition: Transition.noTransition)).toList();
|
return routes
|
||||||
|
.map((e) => GetPage(
|
||||||
|
name: e.name,
|
||||||
|
page: e.page,
|
||||||
|
middlewares: e.middlewares,
|
||||||
|
transition: Transition.noTransition))
|
||||||
|
.toList();
|
||||||
}
|
}
|
||||||
|
|||||||
270
lib/view/dashboard/add_employee_screen.dart
Normal file
270
lib/view/dashboard/add_employee_screen.dart
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_lucide/flutter_lucide.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:marco/controller/dashboard/add_employee_controller.dart';
|
||||||
|
import 'package:marco/helpers/theme/app_theme.dart';
|
||||||
|
import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
|
||||||
|
import 'package:marco/helpers/utils/my_shadow.dart';
|
||||||
|
import 'package:marco/helpers/widgets/my_breadcrumb.dart';
|
||||||
|
import 'package:marco/helpers/widgets/my_breadcrumb_item.dart';
|
||||||
|
import 'package:marco/helpers/widgets/my_button.dart';
|
||||||
|
import 'package:marco/helpers/widgets/my_card.dart';
|
||||||
|
import 'package:marco/helpers/widgets/my_flex.dart';
|
||||||
|
import 'package:marco/helpers/widgets/my_flex_item.dart';
|
||||||
|
import 'package:marco/helpers/widgets/my_spacing.dart';
|
||||||
|
import 'package:marco/helpers/widgets/my_text.dart';
|
||||||
|
import 'package:marco/helpers/widgets/my_text_style.dart';
|
||||||
|
import 'package:marco/view/layouts/layout.dart';
|
||||||
|
|
||||||
|
class AddEmployeeScreen extends StatefulWidget {
|
||||||
|
const AddEmployeeScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AddEmployeeScreen> createState() => _AddEmployeeScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AddEmployeeScreenState extends State<AddEmployeeScreen> with UIMixin {
|
||||||
|
AddEmployeeController controller = Get.put(AddEmployeeController());
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Layout(
|
||||||
|
child: GetBuilder(
|
||||||
|
init: controller,
|
||||||
|
tag: 'add_employee_controller',
|
||||||
|
builder: (controller) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: MySpacing.x(flexSpacing),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
MyText.titleMedium(
|
||||||
|
"Add Employee",
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: 600,
|
||||||
|
),
|
||||||
|
MyBreadcrumb(
|
||||||
|
children: [
|
||||||
|
MyBreadcrumbItem(name: 'Employee'),
|
||||||
|
MyBreadcrumbItem(name: 'Add Employee'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
MySpacing.height(flexSpacing),
|
||||||
|
Padding(
|
||||||
|
padding: MySpacing.x(flexSpacing / 2),
|
||||||
|
child: MyFlex(
|
||||||
|
children: [
|
||||||
|
MyFlexItem(sizes: "lg-8 md-12", child: detail()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget detail() {
|
||||||
|
return Form(
|
||||||
|
key: controller
|
||||||
|
.basicValidator.formKey, // Ensure the key is correctly assigned
|
||||||
|
child: MyCard.bordered(
|
||||||
|
borderRadiusAll: 4,
|
||||||
|
border: Border.all(color: Colors.grey.withOpacity(0.2)),
|
||||||
|
shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom),
|
||||||
|
paddingAll: 24,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(LucideIcons.server, size: 16),
|
||||||
|
MySpacing.width(12),
|
||||||
|
MyText.titleMedium("General", fontWeight: 600),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
MySpacing.height(24),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
MyText.labelMedium("First Name"),
|
||||||
|
MySpacing.height(8),
|
||||||
|
TextFormField(
|
||||||
|
validator:
|
||||||
|
controller.basicValidator.getValidation('first_name'),
|
||||||
|
controller:
|
||||||
|
controller.basicValidator.getController('first_name'),
|
||||||
|
keyboardType: TextInputType.name,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "eg: Jhon",
|
||||||
|
hintStyle: MyTextStyle.bodySmall(xMuted: true),
|
||||||
|
border: outlineInputBorder,
|
||||||
|
enabledBorder: outlineInputBorder,
|
||||||
|
focusedBorder: focusedInputBorder,
|
||||||
|
contentPadding: MySpacing.all(16),
|
||||||
|
isCollapsed: true,
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
MySpacing.height(24),
|
||||||
|
MyText.labelMedium("Last Name"),
|
||||||
|
MySpacing.height(8),
|
||||||
|
TextFormField(
|
||||||
|
validator:
|
||||||
|
controller.basicValidator.getValidation('last_name'),
|
||||||
|
controller:
|
||||||
|
controller.basicValidator.getController('last_name'),
|
||||||
|
keyboardType: TextInputType.name,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "eg: Doe",
|
||||||
|
hintStyle: MyTextStyle.bodySmall(xMuted: true),
|
||||||
|
border: outlineInputBorder,
|
||||||
|
enabledBorder: outlineInputBorder,
|
||||||
|
focusedBorder: focusedInputBorder,
|
||||||
|
contentPadding: MySpacing.all(16),
|
||||||
|
isCollapsed: true,
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
MySpacing.height(24),
|
||||||
|
MyText.labelMedium("Phone Number"),
|
||||||
|
MySpacing.height(8),
|
||||||
|
TextFormField(
|
||||||
|
validator:
|
||||||
|
controller.basicValidator.getValidation('phone_number'),
|
||||||
|
controller:
|
||||||
|
controller.basicValidator.getController('phone_number'),
|
||||||
|
keyboardType: TextInputType.phone,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "eg: +91 9876543210",
|
||||||
|
hintStyle: MyTextStyle.bodySmall(xMuted: true),
|
||||||
|
border: outlineInputBorder,
|
||||||
|
enabledBorder: outlineInputBorder,
|
||||||
|
focusedBorder: focusedInputBorder,
|
||||||
|
contentPadding: MySpacing.all(16),
|
||||||
|
isCollapsed: true,
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
MySpacing.height(24),
|
||||||
|
MyFlex(contentPadding: false, children: [
|
||||||
|
MyFlexItem(
|
||||||
|
sizes: 'lg-6 md-12',
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
MyText.labelMedium("Select Gender"),
|
||||||
|
MySpacing.height(8),
|
||||||
|
DropdownButtonFormField<Gender>(
|
||||||
|
value: controller.selectedGender,
|
||||||
|
dropdownColor: contentTheme.background,
|
||||||
|
menuMaxHeight: 200,
|
||||||
|
isDense: true,
|
||||||
|
items: Gender.values.map((gender) {
|
||||||
|
return DropdownMenuItem<Gender>(
|
||||||
|
value: gender,
|
||||||
|
child: MyText.labelMedium(
|
||||||
|
gender.name[0].toUpperCase() +
|
||||||
|
gender.name.substring(1),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
icon: const Icon(Icons.expand_more, size: 20),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "Select Gender",
|
||||||
|
hintStyle: MyTextStyle.bodySmall(xMuted: true),
|
||||||
|
border: outlineInputBorder,
|
||||||
|
enabledBorder: outlineInputBorder,
|
||||||
|
focusedBorder: focusedInputBorder,
|
||||||
|
contentPadding: MySpacing.all(14),
|
||||||
|
isCollapsed: true,
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
),
|
||||||
|
onChanged: controller.onGenderSelected,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
MySpacing.height(24),
|
||||||
|
MyFlex(contentPadding: false, children: [
|
||||||
|
MyFlexItem(
|
||||||
|
sizes: 'lg-6 md-12',
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
MyText.labelMedium("Select Role"),
|
||||||
|
MySpacing.height(8),
|
||||||
|
DropdownButtonFormField<String>(
|
||||||
|
value: controller.selectedRoleId,
|
||||||
|
dropdownColor: contentTheme.background,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "Select Role",
|
||||||
|
hintStyle: MyTextStyle.bodySmall(xMuted: true),
|
||||||
|
border: outlineInputBorder,
|
||||||
|
enabledBorder: outlineInputBorder,
|
||||||
|
focusedBorder: focusedInputBorder,
|
||||||
|
contentPadding: MySpacing.all(14),
|
||||||
|
isCollapsed: true,
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
),
|
||||||
|
icon: const Icon(Icons.expand_more, size: 20),
|
||||||
|
isDense: true,
|
||||||
|
items: controller.roles.map((role) {
|
||||||
|
return DropdownMenuItem<String>(
|
||||||
|
value: role['id'],
|
||||||
|
child: Text(role['name']),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
onChanged: controller.onRoleSelected,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
MySpacing.height(24),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
MyButton.text(
|
||||||
|
onPressed: () {
|
||||||
|
Get.toNamed('/dashboard/employees');
|
||||||
|
},
|
||||||
|
padding: MySpacing.xy(20, 16),
|
||||||
|
splashColor:
|
||||||
|
contentTheme.secondary.withValues(alpha: 0.1),
|
||||||
|
child: MyText.bodySmall('Cancel'),
|
||||||
|
),
|
||||||
|
MySpacing.width(12),
|
||||||
|
MyButton(
|
||||||
|
onPressed: () async {
|
||||||
|
if (controller.basicValidator.validateForm()) {
|
||||||
|
await controller.createEmployees();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
elevation: 0,
|
||||||
|
padding: MySpacing.xy(20, 16),
|
||||||
|
backgroundColor: contentTheme.primary,
|
||||||
|
borderRadiusAll: AppStyle.buttonRadius.medium,
|
||||||
|
child: MyText.bodySmall(
|
||||||
|
'Save',
|
||||||
|
color: contentTheme.onPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -40,7 +40,7 @@ class _AttendanceScreenState extends State<AttendanceScreen> with UIMixin {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Layout(
|
return Layout(
|
||||||
child: MyRefreshWrapper(
|
child: MyRefreshableContent(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
if (attendanceController.selectedProjectId != null) {
|
if (attendanceController.selectedProjectId != null) {
|
||||||
await attendanceController.fetchEmployeesByProject(
|
await attendanceController.fetchEmployeesByProject(
|
||||||
|
|||||||
260
lib/view/dashboard/employee_screen.dart
Normal file
260
lib/view/dashboard/employee_screen.dart
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:marco/helpers/theme/app_theme.dart';
|
||||||
|
import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
|
||||||
|
import 'package:marco/helpers/widgets/my_breadcrumb.dart';
|
||||||
|
import 'package:marco/helpers/widgets/my_breadcrumb_item.dart';
|
||||||
|
import 'package:marco/helpers/widgets/my_flex.dart';
|
||||||
|
import 'package:marco/helpers/widgets/my_flex_item.dart';
|
||||||
|
import 'package:marco/helpers/widgets/my_spacing.dart';
|
||||||
|
import 'package:marco/helpers/widgets/my_text.dart';
|
||||||
|
import 'package:marco/view/layouts/layout.dart';
|
||||||
|
import 'package:marco/controller/permission_controller.dart';
|
||||||
|
import 'package:marco/controller/dashboard/employees_screen_controller.dart';
|
||||||
|
import 'package:marco/helpers/widgets/my_loading_component.dart';
|
||||||
|
import 'package:marco/helpers/widgets/my_refresh_wrapper.dart';
|
||||||
|
import 'package:marco/model/my_paginated_table.dart';
|
||||||
|
|
||||||
|
class EmployeeScreen extends StatefulWidget {
|
||||||
|
const EmployeeScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<EmployeeScreen> createState() => _EmployeeScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EmployeeScreenState extends State<EmployeeScreen> with UIMixin {
|
||||||
|
final EmployeesScreenController employeesScreenController =
|
||||||
|
Get.put(EmployeesScreenController());
|
||||||
|
final PermissionController permissionController =
|
||||||
|
Get.put(PermissionController());
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
employeesScreenController.selectedProjectId = null;
|
||||||
|
await employeesScreenController.fetchAllEmployees();
|
||||||
|
employeesScreenController.update();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Layout(
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
GetBuilder<EmployeesScreenController>(
|
||||||
|
init: employeesScreenController,
|
||||||
|
builder: (controller) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: MySpacing.x(flexSpacing),
|
||||||
|
child: MyText.titleMedium("Employee",
|
||||||
|
fontSize: 18, fontWeight: 600),
|
||||||
|
),
|
||||||
|
MySpacing.height(flexSpacing),
|
||||||
|
Padding(
|
||||||
|
padding: MySpacing.x(flexSpacing),
|
||||||
|
child: MyBreadcrumb(
|
||||||
|
children: [
|
||||||
|
MyBreadcrumbItem(name: 'Dashboard'),
|
||||||
|
MyBreadcrumbItem(name: 'Employee', active: true),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
MySpacing.height(flexSpacing),
|
||||||
|
Padding(
|
||||||
|
padding: MySpacing.x(flexSpacing),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: Colors.black,
|
||||||
|
width: 1.5,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
child: PopupMenuButton<String>(
|
||||||
|
onSelected: (String value) async {
|
||||||
|
if (value.isEmpty) {
|
||||||
|
employeesScreenController.selectedProjectId =
|
||||||
|
null;
|
||||||
|
await employeesScreenController
|
||||||
|
.fetchAllEmployees();
|
||||||
|
} else {
|
||||||
|
employeesScreenController.selectedProjectId =
|
||||||
|
value;
|
||||||
|
await employeesScreenController
|
||||||
|
.fetchEmployeesByProject(value);
|
||||||
|
}
|
||||||
|
employeesScreenController.update();
|
||||||
|
},
|
||||||
|
itemBuilder: (BuildContext context) {
|
||||||
|
List<PopupMenuItem<String>> items = [
|
||||||
|
PopupMenuItem<String>(
|
||||||
|
value: '',
|
||||||
|
child: MyText.bodySmall('All Employees',
|
||||||
|
fontWeight: 600),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
items.addAll(
|
||||||
|
employeesScreenController.projects
|
||||||
|
.map<PopupMenuItem<String>>((project) {
|
||||||
|
return PopupMenuItem<String>(
|
||||||
|
value: project.id,
|
||||||
|
child: MyText.bodySmall(project.name),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
);
|
||||||
|
|
||||||
|
return items;
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12.0, vertical: 8.0),
|
||||||
|
child: MyText.bodySmall(
|
||||||
|
employeesScreenController.selectedProjectId ==
|
||||||
|
null
|
||||||
|
? 'All Employees'
|
||||||
|
: employeesScreenController.projects
|
||||||
|
.firstWhere((project) =>
|
||||||
|
project.id ==
|
||||||
|
employeesScreenController
|
||||||
|
.selectedProjectId)
|
||||||
|
.name,
|
||||||
|
fontWeight: 600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Get.toNamed('/employees/addEmployee');
|
||||||
|
},
|
||||||
|
child: Text('Add New Employee'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
MySpacing.height(flexSpacing),
|
||||||
|
Padding(
|
||||||
|
padding: MySpacing.x(flexSpacing / 2),
|
||||||
|
child: MyFlex(
|
||||||
|
children: [
|
||||||
|
MyFlexItem(sizes: 'lg-6 ', child: employeeListTab()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Obx(() {
|
||||||
|
return employeesScreenController.isLoading.value
|
||||||
|
? Container(
|
||||||
|
color: Colors.black.withOpacity(0.05),
|
||||||
|
child: const Center(
|
||||||
|
child: LoadingComponent(
|
||||||
|
isLoading: true,
|
||||||
|
loadingText: 'Loading Employees...',
|
||||||
|
child: SizedBox.shrink(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink();
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget employeeListTab() {
|
||||||
|
if (employeesScreenController.employees.isEmpty) {
|
||||||
|
return Center(
|
||||||
|
child: MyText.bodySmall("No Employees Assigned to This Project",
|
||||||
|
fontWeight: 600),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final columns = <DataColumn>[
|
||||||
|
DataColumn(label: MyText.labelLarge('Name', color: contentTheme.primary)),
|
||||||
|
DataColumn(
|
||||||
|
label: MyText.labelLarge('Contact', color: contentTheme.primary)),
|
||||||
|
DataColumn(
|
||||||
|
label: MyText.labelLarge('Actions', color: contentTheme.primary)),
|
||||||
|
];
|
||||||
|
|
||||||
|
final rows =
|
||||||
|
employeesScreenController.employees.asMap().entries.map((entry) {
|
||||||
|
var employee = entry.value;
|
||||||
|
return DataRow(
|
||||||
|
cells: [
|
||||||
|
DataCell(
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
MyText.bodyMedium(employee.name, fontWeight: 600),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
MyText.bodySmall(employee.jobRole, color: Colors.grey),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DataCell(
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
MyText.bodyMedium(employee.email, fontWeight: 600),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
MyText.bodySmall(employee.phoneNumber, color: Colors.grey),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DataCell(
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.visibility),
|
||||||
|
tooltip: 'View',
|
||||||
|
onPressed: () {
|
||||||
|
// View employee action
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.edit),
|
||||||
|
tooltip: 'Edit',
|
||||||
|
onPressed: () {
|
||||||
|
// Edit employee action
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
return MyRefreshableContent(
|
||||||
|
onRefresh: () async {
|
||||||
|
if (employeesScreenController.selectedProjectId == null) {
|
||||||
|
await employeesScreenController.fetchAllEmployees();
|
||||||
|
} else {
|
||||||
|
await employeesScreenController.fetchEmployeesByProject(
|
||||||
|
employeesScreenController.selectedProjectId!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: MyPaginatedTable(
|
||||||
|
columns: columns,
|
||||||
|
rows: rows,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -119,6 +119,11 @@ class _LeftBarState extends State<LeftBar>
|
|||||||
title: "Attendance",
|
title: "Attendance",
|
||||||
isCondensed: isCondensed,
|
isCondensed: isCondensed,
|
||||||
route: '/dashboard/attendance'),
|
route: '/dashboard/attendance'),
|
||||||
|
NavigationItem(
|
||||||
|
iconData: LucideIcons.users,
|
||||||
|
title: "Employees",
|
||||||
|
isCondensed: isCondensed,
|
||||||
|
route: '/dashboard/employees'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user