Merge branch 'OH_Dev_Manish' of https://git.marcoaiot.com/admin/marco.pms.mobileapp into OH_Dev_Manish

This commit is contained in:
Manish 2025-11-17 14:47:42 +05:30
commit 5ce37379cb
3 changed files with 51 additions and 71 deletions

View File

@ -1,7 +1,8 @@
class ApiEndpoints {
// static const String baseUrl = "https://stageapi.marcoaiot.com/api";
// static const String baseUrl = "https://stageapi.marcoaiot.com/api";
// static const String baseUrl = "https://api.marcoaiot.com/api";
//static const String baseUrl = "https://devapi.marcoaiot.com/api";
static const String baseUrl = "https://devapi.marcoaiot.com/api";
static const String getMasterCurrencies = "/Master/currencies/list";
static const String getMasterExpensesCategories =
@ -134,6 +135,7 @@ class ApiEndpoints {
static const String manageOrganizationHierarchy =
"/organization/hierarchy/manage";
// Service Project Module API Endpoints
static const String getServiceProjectsList = "/serviceproject/list";
static const String getServiceProjectDetail = "/serviceproject/details";

View File

@ -305,7 +305,6 @@ class ApiService {
}
}
/// Create a new Service Project Job
static Future<bool> createServiceProjectJobApi({
required String title,
@ -425,7 +424,6 @@ class ApiService {
return null;
}
/// Get details of a single service project
/// Get details of a single service project
static Future<ServiceProjectDetailModel?> getServiceProjectDetailApi(
String projectId) async {

View File

@ -51,18 +51,18 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
}
String _getDisplayValue(dynamic value) {
if (value == null || value.toString().trim().isEmpty || value == 'null') {
return 'NA';
if (value == null || value.toString().trim().isEmpty || value == "null") {
return "NA";
}
return value.toString();
}
String _formatDate(DateTime? date) {
if (date == null || date == DateTime(1)) return 'NA';
if (date == null || date == DateTime(1)) return "NA";
try {
return DateFormat('d/M/yyyy').format(date);
} catch (_) {
return 'NA';
return "NA";
}
}
@ -77,18 +77,15 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: InkWell(
onTap: isActionable && value != 'NA' ? onTap : null,
onLongPress: isActionable && value != 'NA' ? onLongPress : null,
onTap: isActionable && value != "NA" ? onTap : null,
onLongPress: isActionable && value != "NA" ? onLongPress : null,
borderRadius: BorderRadius.circular(5),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.all(8),
child: Icon(
icon,
size: 20,
),
child: Icon(icon, size: 20),
),
MySpacing.width(16),
Expanded(
@ -103,23 +100,19 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
MySpacing.height(4),
MyText(
value,
color: isActionable && value != 'NA'
color: isActionable && value != "NA"
? Colors.blueAccent
: Colors.black87,
fontWeight: 500,
decoration: isActionable && value != 'NA'
decoration: isActionable && value != "NA"
? TextDecoration.underline
: TextDecoration.none,
),
],
),
),
if (isActionable && value != 'NA')
Icon(
Icons.chevron_right,
color: Colors.grey[400],
size: 20,
),
if (isActionable && value != "NA")
Icon(Icons.chevron_right, color: Colors.grey[400], size: 20),
],
),
),
@ -142,10 +135,7 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
children: [
Row(
children: [
Icon(
titleIcon,
size: 20,
),
Icon(titleIcon, size: 20),
MySpacing.width(8),
MyText(
title,
@ -172,7 +162,7 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
backgroundColor: const Color(0xFFF1F1F1),
appBar: showAppBar
? CustomAppBar(
title: 'Employee Details',
title: "Employee Details",
onBackPressed: () {
if (widget.fromProfile) {
Get.back();
@ -189,7 +179,7 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
final employee = controller.selectedEmployeeDetails.value;
if (employee == null) {
return Center(child: MyText('No employee details found.'));
return const Center(child: MyText("No employee details found."));
}
return SafeArea(
@ -204,7 +194,7 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Header Section
/// ------------------ HEADER CARD ------------------
Card(
elevation: 2,
shadowColor: Colors.black12,
@ -257,8 +247,8 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
employee.hasApplicationAccess,
'gender': employee.gender.toLowerCase(),
'job_role_id': employee.jobRoleId,
'joining_date':
employee.joiningDate?.toIso8601String(),
'joining_date': employee.joiningDate
?.toIso8601String(),
},
),
);
@ -273,8 +263,10 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
),
),
),
MySpacing.height(16),
/// ------------------ MANAGE REPORTING ------------------
_buildSectionCard(
title: 'Manage Reporting',
titleIcon: Icons.people_outline,
@ -308,7 +300,6 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
),
);
// 🔄 Refresh reporting managers after editing
await controller.fetchReportingManagers(employee.id);
},
child: Padding(
@ -355,41 +346,32 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
}
return Padding(
padding: const EdgeInsets.only(
top: 8.0, left: 8, right: 8, bottom: 8),
padding: const EdgeInsets.fromLTRB(8, 8, 8, 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding:
const EdgeInsets.symmetric(vertical: 4.0),
child: Text(
'Primary → ${_getManagerNames(primary)}',
style: const TextStyle(
Text(
'Primary → ${_getManagerNames(primary)}',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
fontWeight: FontWeight.w600),
),
Padding(
padding:
const EdgeInsets.symmetric(vertical: 4.0),
child: Text(
'Secondary → ${_getManagerNames(secondary)}',
style: const TextStyle(
Text(
'Secondary → ${_getManagerNames(secondary)}',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
fontWeight: FontWeight.w600),
),
],
),
);
})
}),
],
),
// Contact Information Section
MySpacing.height(16),
/// ------------------ CONTACT INFO ------------------
_buildSectionCard(
title: 'Contact Information',
titleIcon: Icons.contact_phone,
@ -408,7 +390,8 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
onLongPress: () {
if (employee.email != null &&
employee.email.toString().trim().isNotEmpty) {
LauncherUtils.copyToClipboard(employee.email!,
LauncherUtils.copyToClipboard(
employee.email!,
typeLabel: 'Email');
}
},
@ -434,9 +417,10 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
),
],
),
MySpacing.height(16),
// Emergency Contact Section
/// ------------------ EMERGENCY CONTACT ------------------
_buildSectionCard(
title: 'Emergency Contact',
titleIcon: Icons.emergency,
@ -446,17 +430,16 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
label: 'Contact Person',
value:
_getDisplayValue(employee.emergencyContactPerson),
isActionable: false,
),
_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
.toString()
employee.emergencyPhoneNumber!
.trim()
.isNotEmpty) {
LauncherUtils.launchPhone(
@ -465,8 +448,7 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
},
onLongPress: () {
if (employee.emergencyPhoneNumber != null &&
employee.emergencyPhoneNumber
.toString()
employee.emergencyPhoneNumber!
.trim()
.isNotEmpty) {
LauncherUtils.copyToClipboard(
@ -477,9 +459,10 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
),
],
),
MySpacing.height(16),
// Personal Information Section
/// ------------------ PERSONAL INFO ------------------
_buildSectionCard(
title: 'Personal Information',
titleIcon: Icons.person,
@ -488,25 +471,23 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
icon: Icons.wc_outlined,
label: 'Gender',
value: _getDisplayValue(employee.gender),
isActionable: false,
),
_buildDetailRow(
icon: Icons.cake_outlined,
label: 'Birth Date',
value: _formatDate(employee.birthDate),
isActionable: false,
),
_buildDetailRow(
icon: Icons.work_outline,
label: 'Joining Date',
value: _formatDate(employee.joiningDate),
isActionable: false,
),
],
),
MySpacing.height(16),
// Address Information Section
/// ------------------ ADDRESS INFO ------------------
_buildSectionCard(
title: 'Address Information',
titleIcon: Icons.location_on,
@ -515,13 +496,11 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
icon: Icons.home_outlined,
label: 'Current Address',
value: _getDisplayValue(employee.currentAddress),
isActionable: false,
),
_buildDetailRow(
icon: Icons.home_work_outlined,
label: 'Permanent Address',
value: _getDisplayValue(employee.permanentAddress),
isActionable: false,
),
],
),
@ -532,7 +511,7 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
);
}),
// Floating Assign to Project FAB
/// ------------------ FLOATING BUTTON ------------------
floatingActionButton: Obx(() {
final employee = controller.selectedEmployeeDetails.value;
if (employee == null) return const SizedBox.shrink();
@ -550,17 +529,18 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
);
},
backgroundColor: contentTheme.primary,
label: MyText(
icon: const Icon(Icons.add),
label: MyText(
'Assign to Project',
fontSize: 14,
fontWeight: 500,
),
icon: const Icon(Icons.add),
);
}),
);
}
/// ------------------ UTIL ------------------
String _getManagerNames(List<EmployeeModel> managers) {
if (managers.isEmpty) return '';
return managers