323 lines
11 KiB
Dart
323 lines
11 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:intl/intl.dart';
|
|
import 'package:marco/controller/employee/employees_screen_controller.dart';
|
|
import 'package:marco/helpers/widgets/custom_app_bar.dart';
|
|
import 'package:marco/helpers/widgets/avatar.dart';
|
|
import 'package:marco/helpers/widgets/my_spacing.dart';
|
|
import 'package:marco/helpers/widgets/my_text.dart';
|
|
import 'package:marco/view/employees/assign_employee_bottom_sheet.dart';
|
|
import 'package:marco/helpers/utils/launcher_utils.dart';
|
|
import 'package:marco/controller/permission_controller.dart';
|
|
import 'package:marco/helpers/utils/permission_constants.dart';
|
|
import 'package:marco/helpers/widgets/my_refresh_indicator.dart';
|
|
import 'package:marco/model/employees/add_employee_bottom_sheet.dart';
|
|
|
|
class EmployeeDetailPage extends StatefulWidget {
|
|
final String employeeId;
|
|
final bool fromProfile;
|
|
|
|
const EmployeeDetailPage({
|
|
super.key,
|
|
required this.employeeId,
|
|
this.fromProfile = false,
|
|
});
|
|
|
|
@override
|
|
State<EmployeeDetailPage> createState() => _EmployeeDetailPageState();
|
|
}
|
|
|
|
class _EmployeeDetailPageState extends State<EmployeeDetailPage> {
|
|
final EmployeesScreenController controller =
|
|
Get.put(EmployeesScreenController());
|
|
final PermissionController _permissionController =
|
|
Get.find<PermissionController>();
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
controller.fetchEmployeeDetails(widget.employeeId);
|
|
});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
controller.selectedEmployeeDetails.value = null;
|
|
super.dispose();
|
|
}
|
|
|
|
String _getDisplayValue(dynamic value) {
|
|
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';
|
|
try {
|
|
return DateFormat('d/M/yyyy').format(date);
|
|
} catch (_) {
|
|
return 'NA';
|
|
}
|
|
}
|
|
|
|
Widget _buildLabelValueRow(String label, String value,
|
|
{bool isMultiLine = false}) {
|
|
final lowerLabel = label.toLowerCase();
|
|
final isEmail = lowerLabel == 'email';
|
|
final isPhone =
|
|
lowerLabel == 'phone number' || lowerLabel == 'emergency phone number';
|
|
|
|
void handleTap() {
|
|
if (value == 'NA') return;
|
|
if (isEmail) {
|
|
LauncherUtils.launchEmail(value);
|
|
} else if (isPhone) {
|
|
LauncherUtils.launchPhone(value);
|
|
}
|
|
}
|
|
|
|
void handleLongPress() {
|
|
if (value == 'NA') return;
|
|
LauncherUtils.copyToClipboard(value, typeLabel: label);
|
|
}
|
|
|
|
final valueWidget = GestureDetector(
|
|
onTap: (isEmail || isPhone) ? handleTap : null,
|
|
onLongPress: (isEmail || isPhone) ? handleLongPress : null,
|
|
child: Text(
|
|
value,
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.normal,
|
|
color: (isEmail || isPhone) ? Colors.indigo : Colors.black54,
|
|
fontSize: 14,
|
|
decoration: (isEmail || isPhone)
|
|
? TextDecoration.underline
|
|
: TextDecoration.none,
|
|
),
|
|
),
|
|
);
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
if (isMultiLine) ...[
|
|
Text(
|
|
label,
|
|
style: const TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.black87,
|
|
fontSize: 14,
|
|
),
|
|
),
|
|
MySpacing.height(4),
|
|
valueWidget,
|
|
] else
|
|
GestureDetector(
|
|
onTap: (isEmail || isPhone) ? handleTap : null,
|
|
onLongPress: (isEmail || isPhone) ? handleLongPress : null,
|
|
child: RichText(
|
|
text: TextSpan(
|
|
text: "$label: ",
|
|
style: const TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.black87,
|
|
fontSize: 14,
|
|
),
|
|
children: [
|
|
TextSpan(
|
|
text: value,
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.normal,
|
|
color:
|
|
(isEmail || isPhone) ? Colors.indigo : Colors.black54,
|
|
decoration: (isEmail || isPhone)
|
|
? TextDecoration.underline
|
|
: TextDecoration.none,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
MySpacing.height(10),
|
|
Divider(color: Colors.grey[300], height: 1),
|
|
MySpacing.height(10),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildInfoCard(employee) {
|
|
return Card(
|
|
elevation: 3,
|
|
shadowColor: Colors.black12,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
|
child: Padding(
|
|
padding: const EdgeInsets.fromLTRB(12, 16, 12, 16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
MySpacing.height(12),
|
|
_buildLabelValueRow('Email', _getDisplayValue(employee.email)),
|
|
_buildLabelValueRow(
|
|
'Phone Number', _getDisplayValue(employee.phoneNumber)),
|
|
_buildLabelValueRow('Emergency Contact Person',
|
|
_getDisplayValue(employee.emergencyContactPerson)),
|
|
_buildLabelValueRow('Emergency Phone Number',
|
|
_getDisplayValue(employee.emergencyPhoneNumber)),
|
|
_buildLabelValueRow('Gender', _getDisplayValue(employee.gender)),
|
|
_buildLabelValueRow('Birth Date', _formatDate(employee.birthDate)),
|
|
_buildLabelValueRow(
|
|
'Joining Date', _formatDate(employee.joiningDate)),
|
|
_buildLabelValueRow(
|
|
'Current Address',
|
|
_getDisplayValue(employee.currentAddress),
|
|
isMultiLine: true,
|
|
),
|
|
_buildLabelValueRow(
|
|
'Permanent Address',
|
|
_getDisplayValue(employee.permanentAddress),
|
|
isMultiLine: true,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final bool showAppBar = !widget.fromProfile;
|
|
|
|
return Scaffold(
|
|
backgroundColor: const Color(0xFFF1F1F1),
|
|
appBar: showAppBar
|
|
? CustomAppBar(
|
|
title: 'Employee Details',
|
|
onBackPressed: () {
|
|
if (widget.fromProfile) {
|
|
Get.back();
|
|
} else {
|
|
Get.offNamed('/dashboard/employees');
|
|
}
|
|
},
|
|
)
|
|
: null,
|
|
body: Obx(() {
|
|
if (controller.isLoadingEmployeeDetails.value) {
|
|
return const Center(child: CircularProgressIndicator());
|
|
}
|
|
|
|
final employee = controller.selectedEmployeeDetails.value;
|
|
if (employee == null) {
|
|
return const Center(child: Text('No employee details found.'));
|
|
}
|
|
|
|
return SafeArea(
|
|
child: MyRefreshIndicator(
|
|
onRefresh: () async {
|
|
await controller.fetchEmployeeDetails(widget.employeeId);
|
|
},
|
|
child: SingleChildScrollView(
|
|
physics: const AlwaysScrollableScrollPhysics(),
|
|
padding: const EdgeInsets.fromLTRB(12, 20, 12, 80),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Avatar(
|
|
firstName: employee.firstName,
|
|
lastName: employee.lastName,
|
|
size: 45,
|
|
),
|
|
MySpacing.width(12),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
MyText.titleMedium(
|
|
'${employee.firstName} ${employee.lastName}',
|
|
fontWeight: 700,
|
|
),
|
|
MySpacing.height(6),
|
|
MyText.bodySmall(
|
|
_getDisplayValue(employee.jobRole),
|
|
fontWeight: 500,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.edit,
|
|
size: 24, color: Colors.red),
|
|
onPressed: () async {
|
|
final result =
|
|
await showModalBottomSheet<Map<String, dynamic>>(
|
|
context: context,
|
|
isScrollControlled: true,
|
|
backgroundColor: Colors.transparent,
|
|
builder: (_) => AddEmployeeBottomSheet(
|
|
employeeData: {
|
|
'id': employee.id,
|
|
'first_name': employee.firstName,
|
|
'last_name': employee.lastName,
|
|
'phone_number': employee.phoneNumber,
|
|
'gender': employee.gender.toLowerCase(),
|
|
'job_role_id': employee.jobRoleId,
|
|
'joining_date':
|
|
employee.joiningDate?.toIso8601String(),
|
|
},
|
|
),
|
|
);
|
|
|
|
if (result != null) {
|
|
controller.fetchEmployeeDetails(widget.employeeId);
|
|
}
|
|
},
|
|
),
|
|
],
|
|
),
|
|
MySpacing.height(14),
|
|
_buildInfoCard(employee),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}),
|
|
floatingActionButton: Obx(() {
|
|
if (!_permissionController.hasPermission(Permissions.assignToProject)) {
|
|
return const SizedBox.shrink();
|
|
}
|
|
if (controller.isLoadingEmployeeDetails.value ||
|
|
controller.selectedEmployeeDetails.value == null) {
|
|
return const SizedBox.shrink();
|
|
}
|
|
final employee = controller.selectedEmployeeDetails.value!;
|
|
return FloatingActionButton.extended(
|
|
onPressed: () {
|
|
showModalBottomSheet(
|
|
context: context,
|
|
isScrollControlled: true,
|
|
backgroundColor: Colors.transparent,
|
|
builder: (context) => AssignProjectBottomSheet(
|
|
employeeId: widget.employeeId,
|
|
jobRoleId: employee.jobRoleId,
|
|
),
|
|
);
|
|
},
|
|
backgroundColor: Colors.red,
|
|
icon: const Icon(Icons.assignment),
|
|
label: const Text(
|
|
'Assign to Project',
|
|
style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
|
|
),
|
|
);
|
|
}),
|
|
);
|
|
}
|
|
}
|