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";
|
||||
|
||||
// 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")
|
||||
.replace(queryParameters: queryParams);
|
||||
_log('GET request: $uri');
|
||||
_log("GET $uri");
|
||||
|
||||
try {
|
||||
http.Response response =
|
||||
@ -69,7 +69,7 @@ class ApiService {
|
||||
queryParams: queryParams, hasRetried: true);
|
||||
}
|
||||
}
|
||||
_log("Refresh failed.");
|
||||
_log("Token refresh failed.");
|
||||
}
|
||||
return response;
|
||||
} 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();
|
||||
if (token == null) return null;
|
||||
|
||||
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 {
|
||||
final response = await http
|
||||
.post(uri, headers: _headers(token), body: jsonEncode(body))
|
||||
.timeout(timeout);
|
||||
|
||||
_log("Response Status: ${response.statusCode}");
|
||||
return response;
|
||||
} catch (e) {
|
||||
_log("HTTP POST Exception: $e");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== API Calls =====
|
||||
// ===== Attendence Screen API Calls =====
|
||||
|
||||
static Future<List<dynamic>?> getProjects() async {
|
||||
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 {
|
||||
final response = await _getRequest(ApiEndpoints.getEmployeesByProject,
|
||||
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,
|
||||
@ -115,24 +124,30 @@ class ApiService {
|
||||
"projectId": projectId,
|
||||
if (dateFrom != null)
|
||||
"dateFrom": DateFormat('yyyy-MM-dd').format(dateFrom),
|
||||
if (dateTo != null)
|
||||
"dateTo": DateFormat('yyyy-MM-dd').format(dateTo),
|
||||
if (dateTo != null) "dateTo": DateFormat('yyyy-MM-dd').format(dateTo),
|
||||
};
|
||||
|
||||
final response =
|
||||
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 {
|
||||
final response = await _getRequest("${ApiEndpoints.getAttendanceLogView}/$id");
|
||||
return response != null ? _parseResponse(response, label: 'Log Details') : null;
|
||||
final response =
|
||||
await _getRequest("${ApiEndpoints.getAttendanceLogView}/$id");
|
||||
return response != null
|
||||
? _parseResponse(response, label: 'Log Details')
|
||||
: null;
|
||||
}
|
||||
|
||||
static Future<List<dynamic>?> getRegularizationLogs(String projectId) async {
|
||||
final response = await _getRequest(ApiEndpoints.getRegularizationLogs,
|
||||
queryParams: {"projectId": projectId});
|
||||
return response != null ? _parseResponse(response, label: 'Regularization Logs') : null;
|
||||
return response != null
|
||||
? _parseResponse(response, label: 'Regularization Logs')
|
||||
: null;
|
||||
}
|
||||
|
||||
// ===== Upload Attendance Image =====
|
||||
@ -204,4 +219,74 @@ class ApiService {
|
||||
final imageNumber = count.toString().padLeft(3, '0');
|
||||
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';
|
||||
|
||||
class MyRefreshWrapper extends StatelessWidget {
|
||||
class MyRefreshableContent extends StatelessWidget {
|
||||
final Future<void> Function() onRefresh;
|
||||
final Widget child;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
const MyRefreshWrapper({
|
||||
const MyRefreshableContent({
|
||||
Key? key,
|
||||
required this.onRefresh,
|
||||
required this.child,
|
||||
this.padding,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RefreshIndicator(
|
||||
onRefresh: onRefresh,
|
||||
backgroundColor: Colors.red, // Set background color to red
|
||||
color: Colors.white, // Set spinner color to white
|
||||
backgroundColor: Colors.red,
|
||||
color: Colors.white,
|
||||
child: SingleChildScrollView(
|
||||
padding: padding,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
|
@ -7,6 +7,9 @@ class EmployeeModel {
|
||||
final String checkOut;
|
||||
final int activity;
|
||||
int action;
|
||||
final String jobRole;
|
||||
final String email;
|
||||
final String phoneNumber;
|
||||
|
||||
EmployeeModel({
|
||||
required this.id,
|
||||
@ -17,6 +20,9 @@ class EmployeeModel {
|
||||
required this.checkOut,
|
||||
required this.activity,
|
||||
required this.action,
|
||||
required this.jobRole,
|
||||
required this.email,
|
||||
required this.phoneNumber,
|
||||
});
|
||||
|
||||
factory EmployeeModel.fromJson(Map<String, dynamic> json) {
|
||||
@ -29,6 +35,9 @@ class EmployeeModel {
|
||||
checkOut: json['checkOut']?.toString() ?? '-',
|
||||
action: json['action'] ?? 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,
|
||||
'action': action,
|
||||
'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/attendanceScreen.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 {
|
||||
@override
|
||||
RouteSettings? redirect(String? route) {
|
||||
@ -21,20 +24,58 @@ class AuthMiddleware extends GetMiddleware {
|
||||
|
||||
getPageRoute() {
|
||||
var routes = [
|
||||
GetPage(name: '/', page: () => const AttendanceScreen(), middlewares: [AuthMiddleware()]),
|
||||
GetPage(
|
||||
name: '/',
|
||||
page: () => const AttendanceScreen(),
|
||||
middlewares: [AuthMiddleware()]),
|
||||
|
||||
// Dashboard
|
||||
GetPage(name: '/dashboard/attendance', page: () => AttendanceScreen(), middlewares: [AuthMiddleware()]),
|
||||
GetPage(name: '/dashboard', page: () => DashboardScreen(), middlewares: [AuthMiddleware()]),
|
||||
GetPage(
|
||||
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
|
||||
GetPage(name: '/auth/login', page: () => LoginScreen()),
|
||||
GetPage(name: '/auth/register_account', page: () => const RegisterAccountScreen()),
|
||||
GetPage(name: '/auth/forgot_password', page: () => const ForgotPasswordScreen()),
|
||||
GetPage(name: '/auth/reset_password', page: () => const ResetPasswordScreen()),
|
||||
GetPage(
|
||||
name: '/auth/register_account',
|
||||
page: () => const RegisterAccountScreen()),
|
||||
GetPage(
|
||||
name: '/auth/forgot_password',
|
||||
page: () => const ForgotPasswordScreen()),
|
||||
GetPage(
|
||||
name: '/auth/reset_password', page: () => const ResetPasswordScreen()),
|
||||
// Error
|
||||
GetPage(name: '/error/coming_soon', page: () => ComingSoonScreen(), middlewares: [AuthMiddleware()]),
|
||||
GetPage(name: '/error/500', page: () => Error500Screen(), middlewares: [AuthMiddleware()]),
|
||||
GetPage(name: '/error/404', page: () => Error404Screen(), middlewares: [AuthMiddleware()]),
|
||||
GetPage(
|
||||
name: '/error/coming_soon',
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
return Layout(
|
||||
child: MyRefreshWrapper(
|
||||
child: MyRefreshableContent(
|
||||
onRefresh: () async {
|
||||
if (attendanceController.selectedProjectId != null) {
|
||||
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",
|
||||
isCondensed: isCondensed,
|
||||
route: '/dashboard/attendance'),
|
||||
NavigationItem(
|
||||
iconData: LucideIcons.users,
|
||||
title: "Employees",
|
||||
isCondensed: isCondensed,
|
||||
route: '/dashboard/employees'),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
Loading…
x
Reference in New Issue
Block a user