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? editingEmployeeData; // State final MyFormValidator basicValidator = MyFormValidator(); final List files = []; final List categories = []; Gender? selectedGender; List> 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 fetchRoles() async { logSafe('Fetching roles...'); try { final result = await ApiService.getRoles(); if (result != null) { roles = List>.from(result); logSafe('Roles fetched successfully.'); 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?> 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 _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 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( 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, ); } } }