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:30:24 +05:30
commit 30405f7de4
3 changed files with 52 additions and 72 deletions

View File

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

View File

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

View File

@ -49,18 +49,18 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
} }
String _getDisplayValue(dynamic value) { String _getDisplayValue(dynamic value) {
if (value == null || value.toString().trim().isEmpty || value == 'null') { if (value == null || value.toString().trim().isEmpty || value == "null") {
return 'NA'; return "NA";
} }
return value.toString(); return value.toString();
} }
String _formatDate(DateTime? date) { String _formatDate(DateTime? date) {
if (date == null || date == DateTime(1)) return 'NA'; if (date == null || date == DateTime(1)) return "NA";
try { try {
return DateFormat('d/M/yyyy').format(date); return DateFormat('d/M/yyyy').format(date);
} catch (_) { } catch (_) {
return 'NA'; return "NA";
} }
} }
@ -75,18 +75,15 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
return Padding( return Padding(
padding: const EdgeInsets.symmetric(vertical: 12), padding: const EdgeInsets.symmetric(vertical: 12),
child: InkWell( child: InkWell(
onTap: isActionable && value != 'NA' ? onTap : null, onTap: isActionable && value != "NA" ? onTap : null,
onLongPress: isActionable && value != 'NA' ? onLongPress : null, onLongPress: isActionable && value != "NA" ? onLongPress : null,
borderRadius: BorderRadius.circular(5), borderRadius: BorderRadius.circular(5),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Container( Container(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: Icon( child: Icon(icon, size: 20),
icon,
size: 20,
),
), ),
MySpacing.width(16), MySpacing.width(16),
Expanded( Expanded(
@ -101,23 +98,19 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
MySpacing.height(4), MySpacing.height(4),
MyText( MyText(
value, value,
color: isActionable && value != 'NA' color: isActionable && value != "NA"
? Colors.blueAccent ? Colors.blueAccent
: Colors.black87, : Colors.black87,
fontWeight: 500, fontWeight: 500,
decoration: isActionable && value != 'NA' decoration: isActionable && value != "NA"
? TextDecoration.underline ? TextDecoration.underline
: TextDecoration.none, : TextDecoration.none,
), ),
], ],
), ),
), ),
if (isActionable && value != 'NA') if (isActionable && value != "NA")
Icon( Icon(Icons.chevron_right, color: Colors.grey[400], size: 20),
Icons.chevron_right,
color: Colors.grey[400],
size: 20,
),
], ],
), ),
), ),
@ -140,10 +133,7 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
children: [ children: [
Row( Row(
children: [ children: [
Icon( Icon(titleIcon, size: 20),
titleIcon,
size: 20,
),
MySpacing.width(8), MySpacing.width(8),
MyText( MyText(
title, title,
@ -170,7 +160,7 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
backgroundColor: const Color(0xFFF1F1F1), backgroundColor: const Color(0xFFF1F1F1),
appBar: showAppBar appBar: showAppBar
? CustomAppBar( ? CustomAppBar(
title: 'Employee Details', title: "Employee Details",
onBackPressed: () { onBackPressed: () {
if (widget.fromProfile) { if (widget.fromProfile) {
Get.back(); Get.back();
@ -187,7 +177,7 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
final employee = controller.selectedEmployeeDetails.value; final employee = controller.selectedEmployeeDetails.value;
if (employee == null) { if (employee == null) {
return Center(child: MyText('No employee details found.')); return const Center(child: MyText("No employee details found."));
} }
return SafeArea( return SafeArea(
@ -202,7 +192,7 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
// Header Section /// ------------------ HEADER CARD ------------------
Card( Card(
elevation: 2, elevation: 2,
shadowColor: Colors.black12, shadowColor: Colors.black12,
@ -255,8 +245,8 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
employee.hasApplicationAccess, employee.hasApplicationAccess,
'gender': employee.gender.toLowerCase(), 'gender': employee.gender.toLowerCase(),
'job_role_id': employee.jobRoleId, 'job_role_id': employee.jobRoleId,
'joining_date': 'joining_date': employee.joiningDate
employee.joiningDate?.toIso8601String(), ?.toIso8601String(),
}, },
), ),
); );
@ -271,8 +261,10 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
), ),
), ),
), ),
MySpacing.height(16), MySpacing.height(16),
/// ------------------ MANAGE REPORTING ------------------
_buildSectionCard( _buildSectionCard(
title: 'Manage Reporting', title: 'Manage Reporting',
titleIcon: Icons.people_outline, titleIcon: Icons.people_outline,
@ -306,7 +298,6 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
), ),
); );
// 🔄 Refresh reporting managers after editing
await controller.fetchReportingManagers(employee.id); await controller.fetchReportingManagers(employee.id);
}, },
child: Padding( child: Padding(
@ -353,41 +344,32 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
} }
return Padding( return Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.fromLTRB(8, 8, 8, 8),
top: 8.0, left: 8, right: 8, bottom: 8),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Padding( Text(
padding: 'Primary → ${_getManagerNames(primary)}',
const EdgeInsets.symmetric(vertical: 4.0), style: const TextStyle(
child: Text(
'Primary → ${_getManagerNames(primary)}',
style: const TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600),
),
),
), ),
Padding( Text(
padding: 'Secondary → ${_getManagerNames(secondary)}',
const EdgeInsets.symmetric(vertical: 4.0), style: const TextStyle(
child: Text(
'Secondary → ${_getManagerNames(secondary)}',
style: const TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600),
),
),
), ),
], ],
), ),
); );
}) }),
], ],
), ),
// Contact Information Section MySpacing.height(16),
/// ------------------ CONTACT INFO ------------------
_buildSectionCard( _buildSectionCard(
title: 'Contact Information', title: 'Contact Information',
titleIcon: Icons.contact_phone, titleIcon: Icons.contact_phone,
@ -406,7 +388,8 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
onLongPress: () { onLongPress: () {
if (employee.email != null && if (employee.email != null &&
employee.email.toString().trim().isNotEmpty) { employee.email.toString().trim().isNotEmpty) {
LauncherUtils.copyToClipboard(employee.email!, LauncherUtils.copyToClipboard(
employee.email!,
typeLabel: 'Email'); typeLabel: 'Email');
} }
}, },
@ -432,9 +415,10 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
), ),
], ],
), ),
MySpacing.height(16), MySpacing.height(16),
// Emergency Contact Section /// ------------------ EMERGENCY CONTACT ------------------
_buildSectionCard( _buildSectionCard(
title: 'Emergency Contact', title: 'Emergency Contact',
titleIcon: Icons.emergency, titleIcon: Icons.emergency,
@ -444,17 +428,16 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
label: 'Contact Person', label: 'Contact Person',
value: value:
_getDisplayValue(employee.emergencyContactPerson), _getDisplayValue(employee.emergencyContactPerson),
isActionable: false,
), ),
_buildDetailRow( _buildDetailRow(
icon: Icons.phone_in_talk_outlined, icon: Icons.phone_in_talk_outlined,
label: 'Emergency Phone', label: 'Emergency Phone',
value: _getDisplayValue(employee.emergencyPhoneNumber), value:
_getDisplayValue(employee.emergencyPhoneNumber),
isActionable: true, isActionable: true,
onTap: () { onTap: () {
if (employee.emergencyPhoneNumber != null && if (employee.emergencyPhoneNumber != null &&
employee.emergencyPhoneNumber employee.emergencyPhoneNumber!
.toString()
.trim() .trim()
.isNotEmpty) { .isNotEmpty) {
LauncherUtils.launchPhone( LauncherUtils.launchPhone(
@ -463,8 +446,7 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
}, },
onLongPress: () { onLongPress: () {
if (employee.emergencyPhoneNumber != null && if (employee.emergencyPhoneNumber != null &&
employee.emergencyPhoneNumber employee.emergencyPhoneNumber!
.toString()
.trim() .trim()
.isNotEmpty) { .isNotEmpty) {
LauncherUtils.copyToClipboard( LauncherUtils.copyToClipboard(
@ -475,9 +457,10 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
), ),
], ],
), ),
MySpacing.height(16), MySpacing.height(16),
// Personal Information Section /// ------------------ PERSONAL INFO ------------------
_buildSectionCard( _buildSectionCard(
title: 'Personal Information', title: 'Personal Information',
titleIcon: Icons.person, titleIcon: Icons.person,
@ -486,25 +469,23 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
icon: Icons.wc_outlined, icon: Icons.wc_outlined,
label: 'Gender', label: 'Gender',
value: _getDisplayValue(employee.gender), value: _getDisplayValue(employee.gender),
isActionable: false,
), ),
_buildDetailRow( _buildDetailRow(
icon: Icons.cake_outlined, icon: Icons.cake_outlined,
label: 'Birth Date', label: 'Birth Date',
value: _formatDate(employee.birthDate), value: _formatDate(employee.birthDate),
isActionable: false,
), ),
_buildDetailRow( _buildDetailRow(
icon: Icons.work_outline, icon: Icons.work_outline,
label: 'Joining Date', label: 'Joining Date',
value: _formatDate(employee.joiningDate), value: _formatDate(employee.joiningDate),
isActionable: false,
), ),
], ],
), ),
MySpacing.height(16), MySpacing.height(16),
// Address Information Section /// ------------------ ADDRESS INFO ------------------
_buildSectionCard( _buildSectionCard(
title: 'Address Information', title: 'Address Information',
titleIcon: Icons.location_on, titleIcon: Icons.location_on,
@ -513,13 +494,11 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
icon: Icons.home_outlined, icon: Icons.home_outlined,
label: 'Current Address', label: 'Current Address',
value: _getDisplayValue(employee.currentAddress), value: _getDisplayValue(employee.currentAddress),
isActionable: false,
), ),
_buildDetailRow( _buildDetailRow(
icon: Icons.home_work_outlined, icon: Icons.home_work_outlined,
label: 'Permanent Address', label: 'Permanent Address',
value: _getDisplayValue(employee.permanentAddress), value: _getDisplayValue(employee.permanentAddress),
isActionable: false,
), ),
], ],
), ),
@ -530,7 +509,7 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
); );
}), }),
// Floating Assign to Project FAB /// ------------------ FLOATING BUTTON ------------------
floatingActionButton: Obx(() { floatingActionButton: Obx(() {
final employee = controller.selectedEmployeeDetails.value; final employee = controller.selectedEmployeeDetails.value;
if (employee == null) return const SizedBox.shrink(); if (employee == null) return const SizedBox.shrink();
@ -548,17 +527,18 @@ class _EmployeeDetailPageState extends State<EmployeeDetailPage> with UIMixin {
); );
}, },
backgroundColor: contentTheme.primary, backgroundColor: contentTheme.primary,
label: MyText( icon: const Icon(Icons.add),
label: MyText(
'Assign to Project', 'Assign to Project',
fontSize: 14, fontSize: 14,
fontWeight: 500, fontWeight: 500,
), ),
icon: const Icon(Icons.add),
); );
}), }),
); );
} }
/// ------------------ UTIL ------------------
String _getManagerNames(List<EmployeeModel> managers) { String _getManagerNames(List<EmployeeModel> managers) {
if (managers.isEmpty) return ''; if (managers.isEmpty) return '';
return managers return managers