327 lines
9.4 KiB
Dart
327 lines
9.4 KiB
Dart
import 'package:file_picker/file_picker.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:marco/controller/my_controller.dart';
|
|
import 'package:marco/helpers/services/api_service.dart';
|
|
import 'package:marco/helpers/services/app_logger.dart';
|
|
import 'package:marco/helpers/widgets/my_form_validator.dart';
|
|
import 'package:marco/helpers/widgets/my_snackbar.dart';
|
|
import 'package:flutter_contacts/flutter_contacts.dart';
|
|
import 'package:permission_handler/permission_handler.dart';
|
|
|
|
enum Gender {
|
|
male,
|
|
female,
|
|
other;
|
|
|
|
const Gender();
|
|
}
|
|
|
|
class AddEmployeeController extends MyController {
|
|
Map<String, dynamic>? editingEmployeeData;
|
|
|
|
// State
|
|
final MyFormValidator basicValidator = MyFormValidator();
|
|
final List<PlatformFile> files = [];
|
|
final List<String> categories = [];
|
|
|
|
Gender? selectedGender;
|
|
List<Map<String, dynamic>> roles = [];
|
|
String? selectedRoleId;
|
|
String selectedCountryCode = '+91';
|
|
bool showOnline = true;
|
|
DateTime? joiningDate;
|
|
String? selectedOrganizationId;
|
|
RxString selectedOrganizationName = RxString('');
|
|
|
|
@override
|
|
void onInit() {
|
|
super.onInit();
|
|
logSafe('Initializing AddEmployeeController...');
|
|
_initializeFields();
|
|
fetchRoles();
|
|
|
|
if (editingEmployeeData != null) {
|
|
prefillFields();
|
|
}
|
|
}
|
|
|
|
void _initializeFields() {
|
|
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(),
|
|
);
|
|
// Email is optional in controller; UI enforces when application access is checked
|
|
basicValidator.addField(
|
|
'email',
|
|
label: 'Email',
|
|
required: false,
|
|
controller: TextEditingController(),
|
|
);
|
|
|
|
logSafe(
|
|
'Fields initialized for first_name, phone_number, last_name, email.');
|
|
}
|
|
|
|
// Prefill fields in edit mode
|
|
void prefillFields() {
|
|
logSafe('Prefilling data for editing...');
|
|
basicValidator.getController('first_name')?.text =
|
|
editingEmployeeData?['first_name'] ?? '';
|
|
basicValidator.getController('last_name')?.text =
|
|
editingEmployeeData?['last_name'] ?? '';
|
|
basicValidator.getController('phone_number')?.text =
|
|
editingEmployeeData?['phone_number'] ?? '';
|
|
|
|
selectedGender = editingEmployeeData?['gender'] != null
|
|
? Gender.values
|
|
.firstWhereOrNull((g) => g.name == editingEmployeeData!['gender'])
|
|
: null;
|
|
|
|
basicValidator.getController('email')?.text =
|
|
editingEmployeeData?['email'] ?? '';
|
|
|
|
selectedRoleId = editingEmployeeData?['job_role_id'];
|
|
|
|
if (editingEmployeeData?['joining_date'] != null) {
|
|
joiningDate = DateTime.tryParse(editingEmployeeData!['joining_date']);
|
|
}
|
|
|
|
update();
|
|
}
|
|
|
|
void setJoiningDate(DateTime date) {
|
|
joiningDate = date;
|
|
logSafe('Joining date selected: $date');
|
|
update();
|
|
}
|
|
|
|
void onGenderSelected(Gender? gender) {
|
|
selectedGender = gender;
|
|
logSafe('Gender selected: ${gender?.name}');
|
|
update();
|
|
}
|
|
|
|
Future<void> fetchRoles() async {
|
|
logSafe('Fetching roles...');
|
|
try {
|
|
final result = await ApiService.getRoles();
|
|
if (result != null) {
|
|
roles = List<Map<String, dynamic>>.from(result);
|
|
logSafe('Roles fetched successfully.');
|
|
|
|
// ✅ If editing, and role already selected, update the role controller text here
|
|
if (editingEmployeeData != null && selectedRoleId != null) {
|
|
final selectedRole = roles.firstWhereOrNull(
|
|
(r) => r['id'] == selectedRoleId,
|
|
);
|
|
if (selectedRole != null) {
|
|
update();
|
|
}
|
|
}
|
|
|
|
update();
|
|
} else {
|
|
logSafe('Failed to fetch roles: null result', level: LogLevel.error);
|
|
}
|
|
} catch (e, st) {
|
|
logSafe('Error fetching roles',
|
|
level: LogLevel.error, error: e, stackTrace: st);
|
|
}
|
|
}
|
|
|
|
void onRoleSelected(String? roleId) {
|
|
selectedRoleId = roleId;
|
|
logSafe('Role selected: $roleId');
|
|
update();
|
|
}
|
|
|
|
// Create or update employee
|
|
Future<Map<String, dynamic>?> createOrUpdateEmployee({
|
|
String? email,
|
|
bool hasApplicationAccess = false,
|
|
}) async {
|
|
logSafe(editingEmployeeData != null
|
|
? 'Starting employee update...'
|
|
: 'Starting employee creation...');
|
|
|
|
if (selectedGender == null || selectedRoleId == null) {
|
|
showAppSnackbar(
|
|
title: 'Missing Fields',
|
|
message: 'Please select both Gender and Role.',
|
|
type: SnackbarType.warning,
|
|
);
|
|
return null;
|
|
}
|
|
|
|
final firstName = basicValidator.getController('first_name')?.text.trim();
|
|
final lastName = basicValidator.getController('last_name')?.text.trim();
|
|
final phoneNumber =
|
|
basicValidator.getController('phone_number')?.text.trim();
|
|
|
|
try {
|
|
// sanitize orgId before sending
|
|
final String? orgId = (selectedOrganizationId != null &&
|
|
selectedOrganizationId!.trim().isNotEmpty)
|
|
? selectedOrganizationId
|
|
: null;
|
|
|
|
final response = await ApiService.createEmployee(
|
|
id: editingEmployeeData?['id'],
|
|
firstName: firstName!,
|
|
lastName: lastName!,
|
|
phoneNumber: phoneNumber!,
|
|
gender: selectedGender!.name,
|
|
jobRoleId: selectedRoleId!,
|
|
joiningDate: joiningDate?.toIso8601String() ?? '',
|
|
organizationId: orgId,
|
|
email: email,
|
|
hasApplicationAccess: hasApplicationAccess,
|
|
);
|
|
|
|
logSafe('Response: $response');
|
|
|
|
if (response != null && response['success'] == true) {
|
|
showAppSnackbar(
|
|
title: 'Success',
|
|
message: editingEmployeeData != null
|
|
? 'Employee updated successfully!'
|
|
: 'Employee created successfully!',
|
|
type: SnackbarType.success,
|
|
);
|
|
return response;
|
|
} else {
|
|
logSafe('Failed operation', level: LogLevel.error);
|
|
}
|
|
} catch (e, st) {
|
|
logSafe('Error creating/updating employee',
|
|
level: LogLevel.error, error: e, stackTrace: st);
|
|
}
|
|
|
|
showAppSnackbar(
|
|
title: 'Error',
|
|
message: 'Failed to save employee.',
|
|
type: SnackbarType.error,
|
|
);
|
|
return null;
|
|
}
|
|
|
|
Future<bool> _checkAndRequestContactsPermission() async {
|
|
final status = await Permission.contacts.request();
|
|
|
|
if (status.isGranted) return true;
|
|
|
|
if (status.isPermanentlyDenied) {
|
|
await openAppSettings();
|
|
}
|
|
|
|
showAppSnackbar(
|
|
title: 'Permission Required',
|
|
message:
|
|
'Please allow Contacts permission from settings to pick a contact.',
|
|
type: SnackbarType.warning,
|
|
);
|
|
return false;
|
|
}
|
|
|
|
Future<void> pickContact(BuildContext context) async {
|
|
final permissionGranted = await _checkAndRequestContactsPermission();
|
|
if (!permissionGranted) return;
|
|
|
|
try {
|
|
final picked = await FlutterContacts.openExternalPick();
|
|
if (picked == null) return;
|
|
|
|
final contact =
|
|
await FlutterContacts.getContact(picked.id, withProperties: true);
|
|
if (contact == null) {
|
|
showAppSnackbar(
|
|
title: 'Error',
|
|
message: 'Failed to load contact details.',
|
|
type: SnackbarType.error,
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (contact.phones.isEmpty) {
|
|
showAppSnackbar(
|
|
title: 'No Phone Number',
|
|
message: 'Selected contact has no phone number.',
|
|
type: SnackbarType.warning,
|
|
);
|
|
return;
|
|
}
|
|
|
|
final indiaPhones = contact.phones.where((p) {
|
|
final normalized = p.number.replaceAll(RegExp(r'[^0-9+]'), '');
|
|
return normalized.startsWith('+91') ||
|
|
RegExp(r'^\d{10}$').hasMatch(normalized);
|
|
}).toList();
|
|
|
|
if (indiaPhones.isEmpty) {
|
|
showAppSnackbar(
|
|
title: 'No Indian Number',
|
|
message: 'Selected contact has no Indian (+91) phone number.',
|
|
type: SnackbarType.warning,
|
|
);
|
|
return;
|
|
}
|
|
|
|
String? selectedPhone;
|
|
if (indiaPhones.length == 1) {
|
|
selectedPhone = indiaPhones.first.number;
|
|
} else {
|
|
selectedPhone = await showDialog<String>(
|
|
context: context,
|
|
builder: (ctx) => AlertDialog(
|
|
title: const Text('Choose an Indian number'),
|
|
content: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: indiaPhones
|
|
.map(
|
|
(p) => ListTile(
|
|
title: Text(p.number),
|
|
onTap: () => Navigator.of(ctx).pop(p.number),
|
|
),
|
|
)
|
|
.toList(),
|
|
),
|
|
),
|
|
);
|
|
if (selectedPhone == null) return;
|
|
}
|
|
|
|
final normalizedPhone = selectedPhone.replaceAll(RegExp(r'[^0-9]'), '');
|
|
final phoneWithoutCountryCode = normalizedPhone.length > 10
|
|
? normalizedPhone.substring(normalizedPhone.length - 10)
|
|
: normalizedPhone;
|
|
|
|
basicValidator.getController('phone_number')?.text =
|
|
phoneWithoutCountryCode;
|
|
update();
|
|
} catch (e, st) {
|
|
logSafe('Error fetching contacts',
|
|
level: LogLevel.error, error: e, stackTrace: st);
|
|
showAppSnackbar(
|
|
title: 'Error',
|
|
message: 'Failed to fetch contacts.',
|
|
type: SnackbarType.error,
|
|
);
|
|
}
|
|
}
|
|
}
|