refactor: improve null safety in employee details and enhance UI handling
This commit is contained in:
parent
dc4ea7979c
commit
474ecac53c
@ -123,8 +123,8 @@ class _EmployeeDetailBottomSheetState extends State<EmployeeDetailBottomSheet> {
|
||||
radius: 40,
|
||||
backgroundColor: Colors.blueGrey[200],
|
||||
child: Avatar(
|
||||
firstName: employee.firstName,
|
||||
lastName: employee.lastName,
|
||||
firstName: employee.firstName ?? '',
|
||||
lastName: employee.lastName ?? '',
|
||||
size: 60,
|
||||
),
|
||||
),
|
||||
@ -172,7 +172,7 @@ class _EmployeeDetailBottomSheetState extends State<EmployeeDetailBottomSheet> {
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (context) => AssignProjectBottomSheet(
|
||||
employeeId: widget.employeeId,
|
||||
jobRoleId: employee.jobRoleId,
|
||||
jobRoleId: employee.jobRoleId ?? '',
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@ -1,52 +1,51 @@
|
||||
class EmployeeDetailsModel {
|
||||
final String id;
|
||||
final String firstName;
|
||||
final String lastName;
|
||||
final String? id;
|
||||
final String? firstName;
|
||||
final String? lastName;
|
||||
final String? middleName;
|
||||
final String? email;
|
||||
final String gender;
|
||||
final String? gender;
|
||||
final DateTime? birthDate;
|
||||
final DateTime? joiningDate;
|
||||
final String? permanentAddress;
|
||||
final String? currentAddress;
|
||||
final String phoneNumber;
|
||||
final String? phoneNumber;
|
||||
final String? emergencyPhoneNumber;
|
||||
final String? emergencyContactPerson;
|
||||
final bool isActive;
|
||||
final bool isRootUser;
|
||||
final bool isSystem;
|
||||
final String jobRole;
|
||||
final String jobRoleId;
|
||||
final bool? isActive;
|
||||
final bool? isRootUser;
|
||||
final bool? isSystem;
|
||||
final String? jobRole;
|
||||
final String? jobRoleId;
|
||||
final String? photo;
|
||||
final String? applicationUserId;
|
||||
final bool hasApplicationAccess;
|
||||
final bool? hasApplicationAccess;
|
||||
final String? organizationId;
|
||||
final String? aadharNumber;
|
||||
final String? panNumber;
|
||||
|
||||
|
||||
|
||||
EmployeeDetailsModel({
|
||||
required this.id,
|
||||
required this.firstName,
|
||||
required this.lastName,
|
||||
this.id,
|
||||
this.firstName,
|
||||
this.lastName,
|
||||
this.middleName,
|
||||
this.email,
|
||||
required this.gender,
|
||||
this.gender,
|
||||
this.birthDate,
|
||||
this.joiningDate,
|
||||
this.permanentAddress,
|
||||
this.currentAddress,
|
||||
required this.phoneNumber,
|
||||
this.phoneNumber,
|
||||
this.emergencyPhoneNumber,
|
||||
this.emergencyContactPerson,
|
||||
required this.isActive,
|
||||
required this.isRootUser,
|
||||
required this.isSystem,
|
||||
required this.jobRole,
|
||||
required this.jobRoleId,
|
||||
this.isActive,
|
||||
this.isRootUser,
|
||||
this.isSystem,
|
||||
this.jobRole,
|
||||
this.jobRoleId,
|
||||
this.photo,
|
||||
this.applicationUserId,
|
||||
required this.hasApplicationAccess,
|
||||
this.hasApplicationAccess,
|
||||
this.organizationId,
|
||||
this.aadharNumber,
|
||||
this.panNumber,
|
||||
@ -54,30 +53,30 @@ class EmployeeDetailsModel {
|
||||
|
||||
factory EmployeeDetailsModel.fromJson(Map<String, dynamic> json) {
|
||||
return EmployeeDetailsModel(
|
||||
id: json['id'],
|
||||
firstName: json['firstName'],
|
||||
lastName: json['lastName'],
|
||||
middleName: json['middleName'],
|
||||
email: json['email'],
|
||||
gender: json['gender'],
|
||||
birthDate: _parseDate(json['birthDate']),
|
||||
joiningDate: _parseDate(json['joiningDate']),
|
||||
permanentAddress: json['permanentAddress'],
|
||||
currentAddress: json['currentAddress'],
|
||||
phoneNumber: json['phoneNumber'],
|
||||
emergencyPhoneNumber: json['emergencyPhoneNumber'],
|
||||
emergencyContactPerson: json['emergencyContactPerson'],
|
||||
isActive: json['isActive'],
|
||||
isRootUser: json['isRootUser'],
|
||||
isSystem: json['isSystem'],
|
||||
jobRole: json['jobRole'],
|
||||
jobRoleId: json['jobRoleId'],
|
||||
photo: json['photo'],
|
||||
applicationUserId: json['applicationUserId'],
|
||||
hasApplicationAccess: json['hasApplicationAccess'],
|
||||
organizationId: json['organizationId'],
|
||||
aadharNumber: json['aadharNumber'],
|
||||
panNumber: json['panNumber'],
|
||||
id: json['id'] as String?,
|
||||
firstName: json['firstName'] as String?,
|
||||
lastName: json['lastName'] as String?,
|
||||
middleName: json['middleName'] as String?,
|
||||
email: json['email'] as String?,
|
||||
gender: json['gender'] as String?,
|
||||
birthDate: _parseDate(json['birthDate'] as String?),
|
||||
joiningDate: _parseDate(json['joiningDate'] as String?),
|
||||
permanentAddress: json['permanentAddress'] as String?,
|
||||
currentAddress: json['currentAddress'] as String?,
|
||||
phoneNumber: json['phoneNumber'] as String?,
|
||||
emergencyPhoneNumber: json['emergencyPhoneNumber'] as String?,
|
||||
emergencyContactPerson: json['emergencyContactPerson'] as String?,
|
||||
isActive: json['isActive'] as bool?,
|
||||
isRootUser: json['isRootUser'] as bool?,
|
||||
isSystem: json['isSystem'] as bool?,
|
||||
jobRole: json['jobRole'] as String?,
|
||||
jobRoleId: json['jobRoleId'] as String?,
|
||||
photo: json['photo'] as String?,
|
||||
applicationUserId: json['applicationUserId'] as String?,
|
||||
hasApplicationAccess: json['hasApplicationAccess'] as bool?,
|
||||
organizationId: json['organizationId'] as String?,
|
||||
aadharNumber: json['aadharNumber'] as String?,
|
||||
panNumber: json['panNumber'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
@ -116,4 +115,4 @@ class EmployeeDetailsModel {
|
||||
}
|
||||
return DateTime.tryParse(dateStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,9 +13,8 @@ import 'package:marco/helpers/utils/mixins/ui_mixin.dart';
|
||||
import 'package:marco/helpers/widgets/my_custom_skeleton.dart';
|
||||
import 'package:marco/view/employees/manage_reporting_bottom_sheet.dart';
|
||||
import 'package:marco/model/employees/employee_model.dart';
|
||||
import 'package:marco/view/employees/manage_reporting_bottom_sheet.dart';
|
||||
import 'package:marco/model/employees/employee_model.dart';
|
||||
import 'package:marco/view/employees/assign_employee_bottom_sheet.dart';
|
||||
import 'package:marco/model/employees/employee_details_model.dart';
|
||||
|
||||
class EmployeeDetailPage extends StatefulWidget {
|
||||
final String employeeId;
|
||||
@ -177,16 +176,19 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
|
||||
return SkeletonLoaders.employeeDetailSkeletonLoader();
|
||||
}
|
||||
|
||||
final employee = controller.selectedEmployeeDetails.value;
|
||||
final EmployeeDetailsModel? employee =
|
||||
controller.selectedEmployeeDetails.value;
|
||||
if (employee == null) {
|
||||
return Center(child: MyText("No employee details found."));
|
||||
return Center(child: MyText("No employee details found."));
|
||||
}
|
||||
|
||||
return SafeArea(
|
||||
child: MyRefreshIndicator(
|
||||
onRefresh: () async {
|
||||
await controller.fetchEmployeeDetails(widget.employeeId);
|
||||
await controller.fetchReportingManagers(employee.id);
|
||||
if (employee.id != null) {
|
||||
await controller.fetchReportingManagers(employee.id!);
|
||||
}
|
||||
},
|
||||
child: SingleChildScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
@ -206,8 +208,8 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
|
||||
child: Row(
|
||||
children: [
|
||||
Avatar(
|
||||
firstName: employee.firstName,
|
||||
lastName: employee.lastName,
|
||||
firstName: employee.firstName ?? "",
|
||||
lastName: employee.lastName ?? "",
|
||||
size: 35,
|
||||
),
|
||||
MySpacing.width(16),
|
||||
@ -216,7 +218,7 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
MyText.titleMedium(
|
||||
'${employee.firstName} ${employee.lastName}',
|
||||
'${employee.firstName ?? ""} ${employee.lastName ?? ""}',
|
||||
fontWeight: 700,
|
||||
),
|
||||
MySpacing.height(6),
|
||||
@ -231,8 +233,8 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
|
||||
icon: Icon(Icons.edit,
|
||||
size: 24, color: contentTheme.primary),
|
||||
onPressed: () async {
|
||||
final result = await showModalBottomSheet<
|
||||
Map<String, dynamic>>(
|
||||
final result =
|
||||
await showModalBottomSheet<Map<String, dynamic>>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
@ -244,11 +246,11 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
|
||||
'phone_number': employee.phoneNumber,
|
||||
'email': employee.email,
|
||||
'hasApplicationAccess':
|
||||
employee.hasApplicationAccess,
|
||||
'gender': employee.gender.toLowerCase(),
|
||||
employee.hasApplicationAccess ?? false,
|
||||
'gender': employee.gender?.toLowerCase() ?? '',
|
||||
'job_role_id': employee.jobRoleId,
|
||||
'joining_date': employee.joiningDate
|
||||
?.toIso8601String(),
|
||||
'joining_date':
|
||||
employee.joiningDate?.toIso8601String(),
|
||||
},
|
||||
),
|
||||
);
|
||||
@ -279,28 +281,29 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (_) => ManageReportingBottomSheet(
|
||||
initialEmployee: EmployeeModel(
|
||||
id: employee.id,
|
||||
employeeId: employee.id.toString(),
|
||||
firstName: employee.firstName ?? "",
|
||||
lastName: employee.lastName ?? "",
|
||||
id: employee.id ?? '',
|
||||
employeeId: employee.id ?? '',
|
||||
firstName: employee.firstName ?? '',
|
||||
lastName: employee.lastName ?? '',
|
||||
name:
|
||||
"${employee.firstName} ${employee.lastName}",
|
||||
email: employee.email ?? "",
|
||||
jobRole: employee.jobRole ?? "",
|
||||
"${employee.firstName ?? ''} ${employee.lastName ?? ''}",
|
||||
email: employee.email ?? '',
|
||||
jobRole: employee.jobRole ?? '',
|
||||
jobRoleID: "0",
|
||||
designation: employee.jobRole ?? "",
|
||||
phoneNumber: employee.phoneNumber ?? "",
|
||||
designation: employee.jobRole ?? '',
|
||||
phoneNumber: employee.phoneNumber ?? '',
|
||||
activity: 0,
|
||||
action: 0,
|
||||
),
|
||||
hideMainSelector: true,
|
||||
hideLoggedUserFromSelection: true,
|
||||
loggedUserId:
|
||||
controller.selectedEmployeeDetails.value?.id,
|
||||
loggedUserId: controller.selectedEmployeeDetails.value?.id,
|
||||
),
|
||||
);
|
||||
|
||||
await controller.fetchReportingManagers(employee.id);
|
||||
if (employee.id != null) {
|
||||
await controller.fetchReportingManagers(employee.id!);
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
@ -353,14 +356,12 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
|
||||
Text(
|
||||
'Primary → ${_getManagerNames(primary)}',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600),
|
||||
fontSize: 14, fontWeight: FontWeight.w600),
|
||||
),
|
||||
Text(
|
||||
'Secondary → ${_getManagerNames(secondary)}',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600),
|
||||
fontSize: 14, fontWeight: FontWeight.w600),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -383,16 +384,15 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
|
||||
isActionable: true,
|
||||
onTap: () {
|
||||
if (employee.email != null &&
|
||||
employee.email.toString().trim().isNotEmpty) {
|
||||
employee.email!.trim().isNotEmpty) {
|
||||
LauncherUtils.launchEmail(employee.email!);
|
||||
}
|
||||
},
|
||||
onLongPress: () {
|
||||
if (employee.email != null &&
|
||||
employee.email.toString().trim().isNotEmpty) {
|
||||
employee.email!.trim().isNotEmpty) {
|
||||
LauncherUtils.copyToClipboard(
|
||||
employee.email!,
|
||||
typeLabel: 'Email');
|
||||
employee.email!, typeLabel: 'Email');
|
||||
}
|
||||
},
|
||||
),
|
||||
@ -402,16 +402,16 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
|
||||
value: _getDisplayValue(employee.phoneNumber),
|
||||
isActionable: true,
|
||||
onTap: () {
|
||||
if (employee.phoneNumber.trim().isNotEmpty) {
|
||||
LauncherUtils.launchPhone(employee.phoneNumber);
|
||||
if (employee.phoneNumber != null &&
|
||||
employee.phoneNumber!.trim().isNotEmpty) {
|
||||
LauncherUtils.launchPhone(employee.phoneNumber!);
|
||||
}
|
||||
},
|
||||
onLongPress: () {
|
||||
if (employee.phoneNumber.trim().isNotEmpty) {
|
||||
if (employee.phoneNumber != null &&
|
||||
employee.phoneNumber!.trim().isNotEmpty) {
|
||||
LauncherUtils.copyToClipboard(
|
||||
employee.phoneNumber,
|
||||
typeLabel: 'Phone Number',
|
||||
);
|
||||
employee.phoneNumber!, typeLabel: 'Phone Number');
|
||||
}
|
||||
},
|
||||
),
|
||||
@ -428,29 +428,22 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
|
||||
_buildDetailRow(
|
||||
icon: Icons.person_outline,
|
||||
label: 'Contact Person',
|
||||
value:
|
||||
_getDisplayValue(employee.emergencyContactPerson),
|
||||
value: _getDisplayValue(employee.emergencyContactPerson),
|
||||
),
|
||||
_buildDetailRow(
|
||||
icon: Icons.phone_in_talk_outlined,
|
||||
label: 'Emergency Phone',
|
||||
value:
|
||||
_getDisplayValue(employee.emergencyPhoneNumber),
|
||||
value: _getDisplayValue(employee.emergencyPhoneNumber),
|
||||
isActionable: true,
|
||||
onTap: () {
|
||||
if (employee.emergencyPhoneNumber != null &&
|
||||
employee.emergencyPhoneNumber!
|
||||
.trim()
|
||||
.isNotEmpty) {
|
||||
LauncherUtils.launchPhone(
|
||||
employee.emergencyPhoneNumber!);
|
||||
employee.emergencyPhoneNumber!.trim().isNotEmpty) {
|
||||
LauncherUtils.launchPhone(employee.emergencyPhoneNumber!);
|
||||
}
|
||||
},
|
||||
onLongPress: () {
|
||||
if (employee.emergencyPhoneNumber != null &&
|
||||
employee.emergencyPhoneNumber!
|
||||
.trim()
|
||||
.isNotEmpty) {
|
||||
employee.emergencyPhoneNumber!.trim().isNotEmpty) {
|
||||
LauncherUtils.copyToClipboard(
|
||||
employee.emergencyPhoneNumber!,
|
||||
typeLabel: 'Emergency Phone');
|
||||
@ -513,7 +506,7 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
|
||||
|
||||
/// ------------------ FLOATING BUTTON ------------------
|
||||
floatingActionButton: Obx(() {
|
||||
final employee = controller.selectedEmployeeDetails.value;
|
||||
final EmployeeDetailsModel? employee = controller.selectedEmployeeDetails.value;
|
||||
if (employee == null) return const SizedBox.shrink();
|
||||
|
||||
return FloatingActionButton.extended(
|
||||
@ -524,7 +517,7 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (context) => AssignProjectBottomSheet(
|
||||
employeeId: widget.employeeId,
|
||||
jobRoleId: employee.jobRoleId,
|
||||
jobRoleId: employee.jobRoleId ?? '',
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user