- Introduced a new `logSafe` function for consistent logging with sensitivity handling. - Replaced direct logger calls with `logSafe` in `api_service.dart`, `app_initializer.dart`, `auth_service.dart`, `permission_service.dart`, and `my_image_compressor.dart`. - Enhanced error handling and logging in various service methods to capture exceptions and provide more context. - Updated image compression logging to include quality and size metrics. - Improved app initialization logging to capture success and error states. - Ensured sensitive information is not logged directly.
271 lines
7.6 KiB
Dart
271 lines
7.6 KiB
Dart
import 'package:file_picker/file_picker.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:marco/controller/my_controller.dart';
|
|
import 'package:marco/helpers/widgets/my_form_validator.dart';
|
|
import 'package:marco/helpers/services/api_service.dart';
|
|
import 'package:marco/helpers/widgets/my_snackbar.dart';
|
|
import 'package:flutter_contacts/flutter_contacts.dart';
|
|
import 'package:permission_handler/permission_handler.dart';
|
|
import 'package:marco/helpers/services/app_logger.dart';
|
|
|
|
enum Gender {
|
|
male,
|
|
female,
|
|
other;
|
|
|
|
const Gender();
|
|
}
|
|
|
|
class AddEmployeeController extends MyController {
|
|
List<PlatformFile> files = [];
|
|
final MyFormValidator basicValidator = MyFormValidator();
|
|
Gender? selectedGender;
|
|
List<Map<String, dynamic>> roles = [];
|
|
String? selectedRoleId;
|
|
final List<Map<String, String>> countries = [
|
|
{"code": "+91", "name": "India"},
|
|
{"code": "+1", "name": "USA"},
|
|
{"code": "+971", "name": "UAE"},
|
|
{"code": "+44", "name": "UK"},
|
|
{"code": "+81", "name": "Japan"},
|
|
{"code": "+61", "name": "Australia"},
|
|
{"code": "+49", "name": "Germany"},
|
|
{"code": "+33", "name": "France"},
|
|
{"code": "+86", "name": "China"},
|
|
];
|
|
|
|
final Map<String, int> minDigitsPerCountry = {
|
|
"+91": 10,
|
|
"+1": 10,
|
|
"+971": 9,
|
|
"+44": 10,
|
|
"+81": 10,
|
|
"+61": 9,
|
|
"+49": 10,
|
|
"+33": 9,
|
|
"+86": 11,
|
|
};
|
|
|
|
final Map<String, int> maxDigitsPerCountry = {
|
|
"+91": 10,
|
|
"+1": 10,
|
|
"+971": 9,
|
|
"+44": 11,
|
|
"+81": 10,
|
|
"+61": 9,
|
|
"+49": 11,
|
|
"+33": 9,
|
|
"+86": 11,
|
|
};
|
|
|
|
String selectedCountryCode = "+91";
|
|
bool showOnline = true;
|
|
final List<String> categories = [];
|
|
|
|
@override
|
|
void onInit() {
|
|
super.onInit();
|
|
logSafe("Initializing AddEmployeeController...");
|
|
_initializeFields();
|
|
fetchRoles();
|
|
}
|
|
|
|
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(),
|
|
);
|
|
logSafe("Fields initialized for first_name, phone_number, last_name.");
|
|
}
|
|
|
|
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.");
|
|
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();
|
|
}
|
|
|
|
Future<bool> createEmployees() async {
|
|
logSafe("Starting employee creation...");
|
|
if (selectedGender == null || selectedRoleId == null) {
|
|
logSafe("Missing gender or role.", level: LogLevel.warning);
|
|
showAppSnackbar(
|
|
title: "Missing Fields",
|
|
message: "Please select both Gender and Role.",
|
|
type: SnackbarType.warning,
|
|
);
|
|
return false;
|
|
}
|
|
|
|
final firstName = basicValidator.getController("first_name")?.text.trim();
|
|
final lastName = basicValidator.getController("last_name")?.text.trim();
|
|
final phoneNumber = basicValidator.getController("phone_number")?.text.trim();
|
|
|
|
logSafe("Creating employee", level: LogLevel.info);
|
|
|
|
try {
|
|
final response = await ApiService.createEmployee(
|
|
firstName: firstName!,
|
|
lastName: lastName!,
|
|
phoneNumber: phoneNumber!,
|
|
gender: selectedGender!.name,
|
|
jobRoleId: selectedRoleId!,
|
|
);
|
|
|
|
if (response == true) {
|
|
logSafe("Employee created successfully.");
|
|
showAppSnackbar(
|
|
title: "Success",
|
|
message: "Employee created successfully!",
|
|
type: SnackbarType.success,
|
|
);
|
|
return true;
|
|
} else {
|
|
logSafe("Failed to create employee (response false)", level: LogLevel.error);
|
|
}
|
|
} catch (e, st) {
|
|
logSafe("Error creating employee", level: LogLevel.error, error: e, stackTrace: st);
|
|
}
|
|
|
|
showAppSnackbar(
|
|
title: "Error",
|
|
message: "Failed to create employee.",
|
|
type: SnackbarType.error,
|
|
);
|
|
return false;
|
|
}
|
|
|
|
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: 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,
|
|
);
|
|
}
|
|
}
|
|
}
|