1895 lines
68 KiB
Dart
1895 lines
68 KiB
Dart
import 'dart:convert';
|
||
|
||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter/services.dart';
|
||
import 'package:google_fonts/google_fonts.dart';
|
||
import 'package:intl/intl.dart';
|
||
|
||
import '../config/environment.dart';
|
||
import '../customAppBar/toastHelper.dart';
|
||
import '../responsive.dart';
|
||
import '../service/api_service.dart';
|
||
import '../service/file_upload_service.dart';
|
||
import 'package:http/http.dart' as http;
|
||
|
||
import 'package:file_picker/file_picker.dart';
|
||
|
||
import '../service/multi_file_upload_widget.dart';
|
||
import '../service/token_storage_service.dart';
|
||
import 'package:pdf/widgets.dart' as pw;
|
||
import 'package:nhancepolicy/logger.dart';
|
||
|
||
class RaiseClaimDialog extends StatefulWidget {
|
||
final BuildContext parentContext;
|
||
final VoidCallback onSuccess;
|
||
|
||
const RaiseClaimDialog({
|
||
Key? key,
|
||
required this.parentContext,
|
||
required this.onSuccess,
|
||
}) : super(key: key);
|
||
|
||
@override
|
||
State<RaiseClaimDialog> createState() => _RaiseClaimDialogState();
|
||
}
|
||
|
||
class _RaiseClaimDialogState extends State<RaiseClaimDialog> {
|
||
// const RaiseClaimDialog({super.key});
|
||
bool hasSubmitted = false;
|
||
late ApiService apiService;
|
||
bool isLoading = false;
|
||
dynamic empPrimaryId;
|
||
dynamic empClientId;
|
||
dynamic empClientBranchId;
|
||
dynamic empHrId;
|
||
final tokenService = TokenStorageService();
|
||
String? _postPreToken = '';
|
||
List<Map<String, dynamic>> policyList = [];
|
||
dynamic policyDataIsEmpty = 1;
|
||
dynamic policyHeading;
|
||
dynamic policyName;
|
||
dynamic EmployeePolicy;
|
||
List<Map<String, String>> employeeDetails = [];
|
||
dynamic argumentsData;
|
||
dynamic claimsDetails;
|
||
int? fromClaimsPage;
|
||
dynamic decodedToken;
|
||
List<Map<String, dynamic>> departmentList = [];
|
||
List<Map<String, dynamic>> policyNumberList = [];
|
||
int? serviceId;
|
||
String? serviceName;
|
||
int? policyNumberId;
|
||
int? selectedMemberId;
|
||
String? selectedMemberName;
|
||
String? selectedServiceName;
|
||
String? selectedPolicyNo;
|
||
dynamic claimsDepartmentId;
|
||
dynamic claimSubject;
|
||
dynamic rawPolicyStartDate;
|
||
dynamic rawPolicyEndDate;
|
||
dynamic claimsPolicyNo;
|
||
dynamic client_branch_id;
|
||
dynamic mobileNo;
|
||
dynamic client_policy_id;
|
||
dynamic selfDetails;
|
||
dynamic emailId;
|
||
List<Map<String, dynamic>> employeePolicyList = [];
|
||
String? selectedFileNames;
|
||
// html.File? uploadedFile;
|
||
// List<html.File> uploadedFiles = [];
|
||
List<PlatformFile> uploadedFiles = [];
|
||
final fileService = FileUploadService();
|
||
bool isSubmitting = false;
|
||
dynamic getPostCardArrays = [];
|
||
List<Map<String, dynamic>> activePoliciesList = [];
|
||
int stausVal = 1;
|
||
Map<String, dynamic> getClaimPoliciesApi = {};
|
||
int? selectedPolicyTypeId;
|
||
int? selectedClientPolicyId;
|
||
|
||
Map<String, dynamic>? selectedMemberObject;
|
||
TextEditingController searchController = TextEditingController();
|
||
|
||
Map<String, dynamic> claimTypeMap = {};
|
||
List<Map<String, dynamic>> claimTypeList = [];
|
||
int? claimTypeId;
|
||
bool isClaimTypeValid = true;
|
||
|
||
// Declare subjectController and bodyController as instance variables
|
||
late TextEditingController subjectController;
|
||
late TextEditingController messageController;
|
||
late TextEditingController accidentDetailsController;
|
||
late TextEditingController hospitalNameController;
|
||
late TextEditingController hospitalAddressController;
|
||
late TextEditingController hospitalCityController;
|
||
late TextEditingController hospitalStateController;
|
||
late TextEditingController hospitalPinCodeController;
|
||
late TextEditingController hospitalPhoneNoController;
|
||
late TextEditingController claimAmountController;
|
||
late TextEditingController sumInsuredController;
|
||
late TextEditingController admitDateController;
|
||
late TextEditingController dischargeDateController;
|
||
|
||
DateTime? accidentDate;
|
||
DateTime? deathDate;
|
||
DateTime? intimationDate;
|
||
DateTime? birthDate;
|
||
DateTime? admitDate;
|
||
DateTime? dischargeDate;
|
||
|
||
DateTime? parsedPolicyStartDate;
|
||
DateTime? parsedPolicyEndDate;
|
||
|
||
Map<String, DateTime?> selectedDates = {
|
||
"Accident Date": null,
|
||
"Date of Death": null,
|
||
};
|
||
|
||
final Map<int, List<int>> servicePolicyTypeMap = {
|
||
1: [2, 3, 4, 5],
|
||
2: [1],
|
||
3: [6],
|
||
4: [7],
|
||
};
|
||
|
||
List<Map<String, dynamic>> filteredPoliciesList = [];
|
||
|
||
// Create a unique form key for each accordion section
|
||
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
|
||
|
||
bool isServiceValid = true;
|
||
bool isPolicyValid = true;
|
||
bool isMemberValid = true;
|
||
bool isSubjectValid = true;
|
||
bool isHospitalNameValid = true;
|
||
bool isHospitalAddressValid = true;
|
||
bool isHospitalCityValid = true;
|
||
bool isHospitalStateValid = true;
|
||
bool isHospitalPincodeValid = true;
|
||
bool isHospitalPhoneNoValid = true;
|
||
bool isAdmitDischargeValid = true;
|
||
bool isClaimAmountValid = true;
|
||
bool isAccidentDateValid = true;
|
||
bool isIntimationDateValid = true;
|
||
bool isAdmitDateValid = true;
|
||
bool isDischargeDateValid = true;
|
||
bool isAccidentService = false;
|
||
bool isDeathDateValid = true;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
fileService.clearAll();
|
||
subjectController = TextEditingController();
|
||
messageController = TextEditingController();
|
||
accidentDetailsController = TextEditingController();
|
||
hospitalNameController = TextEditingController();
|
||
hospitalAddressController = TextEditingController();
|
||
hospitalCityController = TextEditingController();
|
||
hospitalStateController = TextEditingController();
|
||
hospitalPinCodeController = TextEditingController();
|
||
hospitalPhoneNoController = TextEditingController();
|
||
claimAmountController = TextEditingController();
|
||
sumInsuredController = TextEditingController();
|
||
admitDateController = TextEditingController();
|
||
dischargeDateController = TextEditingController();
|
||
apiService = ApiService(context); // Initialize ApiService here
|
||
_loadToken();
|
||
// Check if fromClaimsPage is 0 and set serviceId to 'Claims' (id is 2)
|
||
// if (fromClaimsPage == 0) {
|
||
// setInitialServiceId();
|
||
// }
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
// Dispose the controllers to avoid memory leaks
|
||
subjectController.dispose();
|
||
messageController.dispose();
|
||
accidentDetailsController.dispose();
|
||
hospitalNameController.dispose();
|
||
hospitalAddressController.dispose();
|
||
hospitalCityController.dispose();
|
||
hospitalStateController.dispose();
|
||
hospitalPinCodeController.dispose();
|
||
hospitalPhoneNoController.dispose();
|
||
claimAmountController.dispose();
|
||
sumInsuredController.dispose();
|
||
admitDateController.dispose();
|
||
dischargeDateController.dispose();
|
||
serviceId = null;
|
||
claimTypeId = null;
|
||
departmentList.clear();
|
||
super.dispose();
|
||
fileService.clearAll();
|
||
}
|
||
|
||
Future<void> _loadToken() async {
|
||
_postPreToken = tokenService.getCurrentToken();
|
||
empClientId = await tokenService.readValue('empClientId');
|
||
empClientBranchId = await tokenService.readValue('empClientBranchId');
|
||
empHrId = await tokenService.readValue('empHrId');
|
||
getClaimsPoliciesDetails();
|
||
await getPostCashDepositDetails(
|
||
empClientBranchId, empClientId, empHrId, _postPreToken);
|
||
}
|
||
|
||
Future<void> getClaimsPoliciesDetails() async {
|
||
logDebug('9');
|
||
setState(() {
|
||
isLoading = true;
|
||
});
|
||
try {
|
||
logDebug('10');
|
||
final response =
|
||
await apiService.getClaimPoliciesToApi(_postPreToken!, empClientId);
|
||
if (response['status'] == 'success') {
|
||
setState(() {
|
||
isLoading = false;
|
||
});
|
||
setState(() {
|
||
getClaimPoliciesApi = Map<String, dynamic>.from(response['data']);
|
||
departmentList = (getClaimPoliciesApi['ticket_type'] as List)
|
||
.map<Map<String, dynamic>>((item) => {
|
||
'id': int.parse(item['ticket_type'].toString()),
|
||
'name': item['type_name'].toString(),
|
||
})
|
||
.toList();
|
||
|
||
claimTypeMap =
|
||
Map<String, dynamic>.from(getClaimPoliciesApi['claim_type']);
|
||
logDebug('claimTypeMap $claimTypeMap');
|
||
});
|
||
} else {
|
||
setState(() {
|
||
isLoading = false;
|
||
});
|
||
|
||
// ToastHelper.showWarningToast(
|
||
// context, 'Request failed with status: ${response.statusCode}');
|
||
logDebug('Request failed with status: ${response['code']}');
|
||
}
|
||
} catch (e) {
|
||
setState(() {
|
||
// isLoading = false;
|
||
});
|
||
logDebug('Exception occurred: $e');
|
||
} finally {
|
||
setState(() {
|
||
// _isLoading = false;
|
||
});
|
||
}
|
||
}
|
||
|
||
Future<void> getPostCashDepositDetails(
|
||
empClientBranchId, empClientId, empHrId, _postPreToken) async {
|
||
logDebug('IN');
|
||
logDebug("clintBranchId -$empClientBranchId");
|
||
logDebug("clintID -$empClientId");
|
||
logDebug("hr_id -$empHrId");
|
||
logDebug("token -$_postPreToken");
|
||
|
||
isLoading = true;
|
||
// setState(() {
|
||
// _isLoading = true;
|
||
// });
|
||
try {
|
||
if (empClientBranchId == null || empClientId == null) {
|
||
return;
|
||
}
|
||
final response = await apiService.getActiveCashDepositDetailsToApi(
|
||
empClientId!, empClientBranchId!, empHrId, _postPreToken, stausVal);
|
||
|
||
// final response = await apiService.getCashDepositDetailsToApi(
|
||
// clintID!, clintBranchId!, hr_id, token);
|
||
logDebug('IN1');
|
||
if (response['status'] == 'success') {
|
||
isLoading = false;
|
||
setState(() {
|
||
logDebug('response');
|
||
logDebug(response['data']);
|
||
getPostCardArrays = List<Map<String, dynamic>>.from(response['data']);
|
||
logDebug('getPostCardArrays');
|
||
logDebug(getPostCardArrays);
|
||
|
||
activePoliciesList = getPostCardArrays;
|
||
});
|
||
|
||
logDebug('IN2');
|
||
} else {
|
||
isLoading = false;
|
||
logDebug('API request failed with status');
|
||
setState(() {
|
||
activePoliciesList = [];
|
||
});
|
||
logDebug('API request failed with status');
|
||
}
|
||
} catch (e) {
|
||
logDebug('Exception occurred: $e');
|
||
}
|
||
}
|
||
|
||
void filterPoliciesByService(int serviceTicketType) {
|
||
final allowedPolicyTypes = servicePolicyTypeMap[serviceTicketType] ?? [];
|
||
|
||
final filtered = activePoliciesList.where((policy) {
|
||
final policyTypeId = int.tryParse(
|
||
policy['policy_type_id'].toString(),
|
||
);
|
||
return allowedPolicyTypes.contains(policyTypeId);
|
||
}).toList();
|
||
|
||
setState(() {
|
||
policyNumberId = null;
|
||
|
||
policyNumberList = filtered.map<Map<String, dynamic>>((policy) {
|
||
return {
|
||
// ✅ VALUE (what gets sent)
|
||
'id': int.parse(policy['client_policy_id'].toString()),
|
||
|
||
// ✅ DISPLAY (what user sees)
|
||
'label': '${policy['type']} - ${policy['policy_no']}',
|
||
|
||
// keep extras if needed
|
||
'policy_type_id': int.parse(policy['policy_type_id'].toString()),
|
||
};
|
||
}).toList();
|
||
|
||
isPolicyValid = true;
|
||
});
|
||
}
|
||
|
||
void loadClaimTypes() {
|
||
logDebug('serviceId $serviceId');
|
||
if (serviceId == null) return;
|
||
|
||
String key = serviceId.toString();
|
||
|
||
if (claimTypeMap.containsKey(key)) {
|
||
Map<String, dynamic> types = Map<String, dynamic>.from(claimTypeMap[key]);
|
||
|
||
claimTypeList = types.entries.map((entry) {
|
||
return {
|
||
'id': int.parse(entry.key),
|
||
'name': entry.value,
|
||
};
|
||
}).toList();
|
||
|
||
logDebug('claimTypeList $claimTypeList');
|
||
|
||
// // ✅ AUTO SELECT FIRST CLAIM TYPE
|
||
// if (claimTypeList.isNotEmpty) {
|
||
// claimTypeId = claimTypeList.first['id'];
|
||
// }
|
||
} else {
|
||
claimTypeList = [];
|
||
claimTypeId = null;
|
||
setState(() {});
|
||
}
|
||
}
|
||
|
||
Future<void> getCDPoliciesDetails() async {
|
||
logDebug('9');
|
||
setState(() {
|
||
isLoading = true;
|
||
});
|
||
try {
|
||
logDebug('10');
|
||
|
||
final response = await apiService.getEmployeeAndDependenceToApi(
|
||
empClientId,
|
||
selectedClientPolicyId,
|
||
empClientBranchId,
|
||
_postPreToken!);
|
||
|
||
if (response['status'] == 'success') {
|
||
final List<Map<String, dynamic>> members =
|
||
List<Map<String, dynamic>>.from(response['data']);
|
||
|
||
setState(() {
|
||
employeePolicyList = members.map((m) {
|
||
return {
|
||
'id': int.parse(m['id'].toString()), // ✅ UNIQUE
|
||
'name': m['name'], // ✅ DISPLAY
|
||
'client_policy_id': m['client_policy_id'] ?? '',
|
||
'emp_id': m['self_employee_id'] ?? '',
|
||
'insured_emp_id': m['employee_id'] ?? '',
|
||
'emp_code': m['emp_code'] ?? '',
|
||
'mobile': m['mobile'] ?? ' ',
|
||
'empEmailCorporate': m['email_corporate'] ?? '',
|
||
'relationship': m['relationship'],
|
||
'gender': m['gender'],
|
||
'dob': m['dob'],
|
||
'policy_name': serviceId == 1 ? null : m['policy_name'],
|
||
};
|
||
}).toList();
|
||
});
|
||
}
|
||
} catch (e) {
|
||
setState(() {
|
||
isLoading = false;
|
||
});
|
||
logDebug('Exception occurred: $e');
|
||
} finally {
|
||
setState(() {
|
||
isLoading = false;
|
||
});
|
||
}
|
||
}
|
||
|
||
Future<void> sendFormDataToApi() async {
|
||
setState(() {
|
||
hasSubmitted = serviceId != null ? true : false;
|
||
isServiceValid = serviceId != null;
|
||
isPolicyValid = selectedClientPolicyId != null;
|
||
isMemberValid = selectedMemberId != null;
|
||
MultiFileUploadWidget.showValidation =
|
||
serviceId != null && FileUploadService().files.isEmpty;
|
||
// MultiFileUploadWidget.showValidation = FileUploadService().files.isEmpty;
|
||
// isClaimTypeValid = claimTypeId != null;
|
||
// isSubjectValid = subjectController.text.trim().isNotEmpty;
|
||
// 🏥 GMC / Topup
|
||
final isGmc = serviceId == 1 || serviceId == 72;
|
||
|
||
isHospitalNameValid =
|
||
!isGmc || hospitalNameController.text.trim().isNotEmpty;
|
||
isHospitalAddressValid =
|
||
!isGmc || hospitalAddressController.text.trim().isNotEmpty;
|
||
isHospitalCityValid =
|
||
!isGmc || hospitalCityController.text.trim().isNotEmpty;
|
||
isHospitalStateValid =
|
||
!isGmc || hospitalStateController.text.trim().isNotEmpty;
|
||
isHospitalPincodeValid =
|
||
!isGmc || hospitalPinCodeController.text.trim().isNotEmpty;
|
||
// isHospitalPhoneNoValid = !isGmc || hospitalPhoneNoController.text.trim().isNotEmpty;
|
||
// isHospitalPhoneNoValid = !isGmc || (hospitalPhoneNoController.text.trim().length == 10);
|
||
String phone = hospitalPhoneNoController.text.trim();
|
||
// isHospitalPhoneNoValid = !isGmc || (RegExp(r'^\d{10}$').hasMatch(phone));
|
||
isHospitalPhoneNoValid = !isGmc || phone.isNotEmpty;
|
||
|
||
isAdmitDateValid = !isGmc || admitDate != null;
|
||
isDischargeDateValid = !isGmc || dischargeDate != null;
|
||
isClaimTypeValid = !isGmc || claimTypeId != null;
|
||
|
||
isClaimAmountValid =
|
||
!isGmc || claimAmountController.text.trim().isNotEmpty;
|
||
|
||
// ☠ Accident / Death
|
||
// final isAccident = [2, 3, 4].contains(serviceId);
|
||
//
|
||
// isAccidentService = isAccident ? true : false;
|
||
//
|
||
// isAccidentDateValid = !isAccident || accidentDate != null;
|
||
// isIntimationDateValid = !isAccident || intimationDate != null;
|
||
// 🎯 GPA / GTLI / EDLI validation
|
||
// 🎯 GPA / GTLI / EDLI dynamic validation
|
||
// if (serviceId == 2) {
|
||
// logDebug('serviceId == 2');
|
||
// isAccidentDateValid = accidentDate != null;
|
||
// isDeathDateValid = true;
|
||
// }
|
||
// else if (serviceId == 3 || serviceId == 4) {
|
||
// logDebug('serviceId == 3 || serviceId == 4');
|
||
// isDeathDateValid = deathDate != null;
|
||
// isAccidentDateValid = true;
|
||
// }
|
||
// else {
|
||
// logDebug('ELSE');
|
||
// isAccidentDateValid = true;
|
||
// isDeathDateValid = true;
|
||
// }
|
||
if (serviceId == 2) {
|
||
// GPA
|
||
isAccidentDateValid = accidentDate != null;
|
||
isDeathDateValid = true;
|
||
isIntimationDateValid = true;
|
||
} else if (serviceId == 3) {
|
||
// GTLI
|
||
isDeathDateValid = deathDate != null;
|
||
isAccidentDateValid = true;
|
||
isIntimationDateValid = true;
|
||
} else if (serviceId == 4) {
|
||
// EDLI
|
||
isDeathDateValid = deathDate != null;
|
||
isAccidentDateValid = true;
|
||
isIntimationDateValid = true;
|
||
} else {
|
||
isAccidentDateValid = true;
|
||
isDeathDateValid = true;
|
||
isIntimationDateValid = true;
|
||
}
|
||
});
|
||
|
||
// if (!isDeathDateValid) {
|
||
// ToastHelper.showErrorToast(
|
||
// context, 'Date of Death is required');
|
||
// return;
|
||
// }
|
||
//
|
||
// if (!isAccidentDateValid) {
|
||
// ToastHelper.showErrorToast(
|
||
// context, 'Accident Date is required');
|
||
// return;
|
||
// }
|
||
|
||
if (isServiceValid &&
|
||
isPolicyValid &&
|
||
isMemberValid &&
|
||
isHospitalNameValid &&
|
||
isHospitalAddressValid &&
|
||
isHospitalStateValid &&
|
||
isHospitalCityValid &&
|
||
isHospitalPincodeValid &&
|
||
isAdmitDateValid &&
|
||
isHospitalPhoneNoValid &&
|
||
isDischargeDateValid &&
|
||
isClaimAmountValid &&
|
||
isAccidentDateValid &&
|
||
isDeathDateValid &&
|
||
isIntimationDateValid) {
|
||
// ✅ Phone validation AFTER all required fields pass
|
||
// ✅ Exact 10 digit validation AFTER all required fields filled
|
||
if ((serviceId == 1 || serviceId == 72) &&
|
||
hospitalPhoneNoController.text.trim().isNotEmpty &&
|
||
!RegExp(r'^\d{10}$')
|
||
.hasMatch(hospitalPhoneNoController.text.trim())) {
|
||
ToastHelper.showErrorToast(
|
||
context,
|
||
'Hospital phone number must be exactly 10 digits',
|
||
);
|
||
return;
|
||
}
|
||
|
||
if (FileUploadService().files.isEmpty) {
|
||
ToastHelper.showErrorToast(
|
||
context,
|
||
'Please upload at least one document',
|
||
);
|
||
return;
|
||
}
|
||
} else {
|
||
ToastHelper.showErrorToast(
|
||
context,
|
||
'Please Fill Required Fields',
|
||
);
|
||
return;
|
||
}
|
||
setState(() => isSubmitting = true); // 🔥 start loader
|
||
setState(() {
|
||
isLoading = true;
|
||
});
|
||
|
||
try {
|
||
logDebug('Check One');
|
||
Map<String, dynamic> fields = {
|
||
'client_policy_id': selectedClientPolicyId,
|
||
// 'policy_type_id': selectedPolicyTypeId,
|
||
|
||
'emp_id': selectedMemberObject?['emp_id'],
|
||
'ticket_type_id': serviceId,
|
||
'subject': serviceName,
|
||
'message': messageController.text,
|
||
|
||
'emp_code': selectedMemberObject?['emp_code'] ?? '',
|
||
'mobile_number': selectedMemberObject?['mobile'] ?? ' ',
|
||
'email': selectedMemberObject?['empEmailCorporate'] ?? '',
|
||
'fullname': selectedMemberObject?['name'] ?? '',
|
||
'client_id': empClientId,
|
||
'relationship': selectedMemberObject?['relationship'],
|
||
'gender': selectedMemberObject?['gender'],
|
||
'age': selectedMemberObject?['dob'],
|
||
'policy_name':
|
||
serviceId == 1 ? null : selectedMemberObject?['policy_name'],
|
||
};
|
||
|
||
if (serviceId == 1 || serviceId == 72) {
|
||
String formattedAdmitDate = DateFormat('yyyy-MM-dd').format(admitDate!);
|
||
String formattedDischargeDate =
|
||
DateFormat('yyyy-MM-dd').format(dischargeDate!);
|
||
fields['claim_type'] = claimTypeId;
|
||
fields['hospital_name'] = hospitalNameController.text;
|
||
fields['hospital_address'] = hospitalAddressController.text;
|
||
fields['hospital_city'] = hospitalCityController.text;
|
||
fields['hospital_state'] = hospitalStateController.text;
|
||
fields['hospital_pin_code'] = hospitalPinCodeController.text;
|
||
fields['hospital_phone_no'] = hospitalPhoneNoController.text;
|
||
fields['doa'] = formattedAdmitDate;
|
||
fields['dod'] = formattedDischargeDate;
|
||
fields['claim_amount'] = claimAmountController.text;
|
||
fields['policyholder_name'] = employeePolicyList[0]['name'];
|
||
fields['member_name'] = selectedMemberObject?['name'];
|
||
fields['insured_emp_id'] = selectedMemberObject?['insured_emp_id'];
|
||
} else {
|
||
if (accidentDate != null) {
|
||
fields['date_of_accident'] =
|
||
DateFormat('yyyy-MM-dd').format(accidentDate!);
|
||
}
|
||
|
||
if (deathDate != null) {
|
||
fields['date_of_death'] = DateFormat('yyyy-MM-dd').format(deathDate!);
|
||
}
|
||
|
||
if (birthDate != null) {
|
||
fields['dob'] = DateFormat('yyyy-MM-dd').format(birthDate!);
|
||
}
|
||
|
||
if (intimationDate != null) {
|
||
fields['date_of_intimat'] =
|
||
DateFormat('yyyy-MM-dd').format(intimationDate!);
|
||
}
|
||
fields['si_amt'] = sumInsuredController.text;
|
||
fields['policyholder_name'] = employeePolicyList[0]['name'];
|
||
fields['member_name'] = selectedMemberName;
|
||
fields['insured_emp_id'] = selectedMemberObject?['insured_emp_id'];
|
||
}
|
||
|
||
final request = http.MultipartRequest(
|
||
'POST', Uri.parse('${Environment.apiUrlPost}initiateClaim'));
|
||
request.headers['Authorization'] = 'Bearer $_postPreToken';
|
||
request.headers['APP-SIGNATURE'] =
|
||
'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y';
|
||
final stringFields =
|
||
fields.map((key, value) => MapEntry(key, value?.toString() ?? ''));
|
||
logDebug("📁 stringFields: ${stringFields}");
|
||
request.fields.addAll(stringFields);
|
||
|
||
// get uploaded files
|
||
final uploadedFiles = FileUploadService().files;
|
||
logDebug("📁 Total uploaded files: ${uploadedFiles.length}");
|
||
logDebug(
|
||
"📂 File list (original names): ${uploadedFiles.map((f) => f.file.name).toList()}");
|
||
|
||
// Validate all labels present (optional but recommended)
|
||
for (final uf in uploadedFiles) {
|
||
logDebug(
|
||
"📝 Checking label for file: ${uf.file.name}, current label: '${uf.label}'");
|
||
if ((uf.label ?? '').trim().isEmpty) {
|
||
ToastHelper.showErrorToast(
|
||
context, 'Please enter name for all uploaded documents');
|
||
setState(() => isLoading = false);
|
||
return;
|
||
}
|
||
}
|
||
|
||
// ✅ NEW VALIDATION: At least one PDF must be uploaded
|
||
// bool hasPdf = uploadedFiles.any((uf) {
|
||
// final ext = uf.file.extension?.toLowerCase() ?? '';
|
||
// return ext == 'pdf';
|
||
// });
|
||
//
|
||
// if (!hasPdf) {
|
||
// ToastHelper.showErrorToast(context, 'Please upload at least one PDF document');
|
||
// setState(() => isLoading = false);
|
||
// return;
|
||
// }
|
||
|
||
// Add files
|
||
// for (var uf in uploadedFiles) {
|
||
// final pf = uf.file;
|
||
// if (pf.bytes != null) {
|
||
// request.files.add(http.MultipartFile.fromBytes(
|
||
// 'claim_docs[]',
|
||
// pf.bytes!,
|
||
// filename: pf.name,
|
||
// ));
|
||
// }
|
||
// }
|
||
|
||
// Add files — convert images to PDF if needed
|
||
for (var uf in uploadedFiles) {
|
||
final pf = uf.file;
|
||
final ext = pf.extension?.toLowerCase() ?? '';
|
||
|
||
if (pf.bytes != null) {
|
||
Uint8List fileBytes = pf.bytes!;
|
||
|
||
if (['jpg', 'jpeg', 'png', 'heic'].contains(ext)) {
|
||
// ✅ Convert image → PDF
|
||
final pdf = pw.Document();
|
||
final image = pw.MemoryImage(fileBytes);
|
||
|
||
pdf.addPage(
|
||
pw.Page(
|
||
build: (pw.Context context) => pw.Center(
|
||
child: pw.Image(image, fit: pw.BoxFit.contain),
|
||
),
|
||
),
|
||
);
|
||
|
||
fileBytes = await pdf.save(); // converted PDF bytes
|
||
|
||
// replace file name with .pdf extension
|
||
final pdfFileName = pf.name.replaceAll(RegExp(r'\.\w+$'), '.pdf');
|
||
|
||
logDebug('📄 Converted image ${pf.name} → PDF ($pdfFileName)');
|
||
request.files.add(http.MultipartFile.fromBytes(
|
||
'claim_docs[]',
|
||
fileBytes,
|
||
filename: pdfFileName,
|
||
));
|
||
} else {
|
||
// ✅ Already a PDF
|
||
request.files.add(http.MultipartFile.fromBytes(
|
||
'claim_docs[]',
|
||
fileBytes,
|
||
filename: pf.name,
|
||
));
|
||
}
|
||
}
|
||
}
|
||
|
||
// ✅ Combine all names into a JSON array string
|
||
final claimDocNames = uploadedFiles.map((uf) => uf.label.trim()).toList();
|
||
final encodedNames = jsonEncode(claimDocNames);
|
||
request.fields['claim_doc_names'] = encodedNames;
|
||
|
||
// ✅ Debug print
|
||
logDebug("Payload being sent:");
|
||
logDebug("Files: ${uploadedFiles.map((f) => f.file.name).toList()}");
|
||
logDebug("Names (JSON): $encodedNames");
|
||
|
||
final response = await request.send();
|
||
final responseBody = await response.stream.bytesToString();
|
||
|
||
final decoded = jsonDecode(responseBody);
|
||
if (decoded['status'] == true) {
|
||
resetFormOnServiceChange();
|
||
// 1️⃣ Close dialog first
|
||
Navigator.pop(context);
|
||
|
||
// 2️⃣ Call parent page function
|
||
widget.onSuccess();
|
||
|
||
ToastHelper.showSuccessToast(context, decoded['message']);
|
||
serviceId = null;
|
||
departmentList.clear();
|
||
setState(() {
|
||
isLoading = false;
|
||
});
|
||
// context.go('/claims', extra: 2);
|
||
logDebug('Form data submitted successfully');
|
||
fileService.clearAll();
|
||
} else {
|
||
setState(() {
|
||
isLoading = false;
|
||
});
|
||
Navigator.pop(context);
|
||
ToastHelper.showErrorToast(context, "Failed: ${decoded['message']}");
|
||
}
|
||
} catch (e) {
|
||
setState(() {
|
||
isLoading = false;
|
||
});
|
||
logDebug('Error submitting form data: $e');
|
||
} finally {
|
||
setState(() => isSubmitting = false); // 🔥 stop loader
|
||
}
|
||
}
|
||
|
||
Future<void> _pickDate(BuildContext context, String label) async {
|
||
final DateTime? pickedDate = await showDatePicker(
|
||
context: context,
|
||
initialDate: selectedDates[label] ?? DateTime.now(),
|
||
firstDate: DateTime(2000),
|
||
lastDate: DateTime.now(),
|
||
);
|
||
|
||
if (pickedDate != null && pickedDate != selectedDates[label]) {
|
||
setState(() {
|
||
selectedDates[label] = pickedDate;
|
||
});
|
||
}
|
||
}
|
||
|
||
Future<void> _pickFromToDate(BuildContext context) async {
|
||
// Define the policy date range
|
||
final DateTime policyStartDate =
|
||
DateFormat('dd-MMM-yyyy').parse(rawPolicyStartDate);
|
||
final DateTime policyEndDate =
|
||
DateFormat('dd-MMM-yyyy').parse(rawPolicyEndDate);
|
||
|
||
// Show the date range picker within the policy date limits
|
||
final DateTimeRange? pickedDateRange = await showDateRangePicker(
|
||
context: context,
|
||
firstDate: policyStartDate,
|
||
lastDate: policyEndDate,
|
||
initialDateRange: admitDate != null && dischargeDate != null
|
||
? DateTimeRange(start: admitDate!, end: dischargeDate!)
|
||
: null,
|
||
);
|
||
|
||
// Update values if a valid date range is picked
|
||
if (pickedDateRange != null) {
|
||
setState(() {
|
||
admitDate = pickedDateRange.start;
|
||
dischargeDate = pickedDateRange.end;
|
||
admitDateController.text = DateFormat('dd-MM-yyyy').format(admitDate!);
|
||
dischargeDateController.text =
|
||
DateFormat('dd-MM-yyyy').format(dischargeDate!);
|
||
});
|
||
}
|
||
}
|
||
|
||
// Future<void> _pickFromToDate(BuildContext context) async {
|
||
// final DateTimeRange? pickedDateRange = await showDateRangePicker(
|
||
// context: context,
|
||
// firstDate: DateTime(2000),
|
||
// lastDate: DateTime(2100),
|
||
// initialDateRange: admitDate != null && dischargeDate != null
|
||
// ? DateTimeRange(start: admitDate!, end: dischargeDate!)
|
||
// : null,
|
||
// );
|
||
//
|
||
// if (pickedDateRange != null) {
|
||
// setState(() {
|
||
// admitDate = pickedDateRange.start;
|
||
// dischargeDate = pickedDateRange.end;
|
||
// admitDateController.text = DateFormat('yyyy-MM-dd').format(admitDate!);
|
||
// dischargeDateController.text =
|
||
// DateFormat('yyyy-MM-dd').format(dischargeDate!);
|
||
// });
|
||
// }
|
||
// }
|
||
|
||
int calculateAge(String dob) {
|
||
DateTime birthDate = DateTime.parse(dob); // Parse the dob string
|
||
DateTime currentDate = DateTime.now();
|
||
|
||
int age = currentDate.year - birthDate.year;
|
||
|
||
// Adjust age if the birthday has not occurred yet this year
|
||
if (currentDate.month < birthDate.month ||
|
||
(currentDate.month == birthDate.month &&
|
||
currentDate.day < birthDate.day)) {
|
||
age--;
|
||
}
|
||
|
||
return age;
|
||
}
|
||
|
||
void resetFormOnServiceChange() {
|
||
hasSubmitted = false;
|
||
MultiFileUploadWidget.showValidation = false;
|
||
// Reset dropdown selections
|
||
selectedClientPolicyId = null;
|
||
selectedPolicyTypeId = null;
|
||
policyNumberId = null;
|
||
selectedMemberId = null;
|
||
selectedMemberName = null;
|
||
selectedMemberObject = null;
|
||
claimTypeId = null;
|
||
|
||
// Clear lists
|
||
employeePolicyList.clear();
|
||
policyNumberList.clear();
|
||
|
||
// Clear all text controllers
|
||
messageController.clear();
|
||
accidentDetailsController.clear();
|
||
hospitalNameController.clear();
|
||
hospitalAddressController.clear();
|
||
hospitalCityController.clear();
|
||
hospitalStateController.clear();
|
||
hospitalPinCodeController.clear();
|
||
hospitalPhoneNoController.clear();
|
||
claimAmountController.clear();
|
||
sumInsuredController.clear();
|
||
admitDateController.clear();
|
||
dischargeDateController.clear();
|
||
|
||
// Reset dates
|
||
accidentDate = null;
|
||
deathDate = null;
|
||
intimationDate = null;
|
||
birthDate = null;
|
||
admitDate = null;
|
||
dischargeDate = null;
|
||
|
||
// Reset validation flags
|
||
isPolicyValid = true;
|
||
isMemberValid = true;
|
||
isHospitalNameValid = true;
|
||
isHospitalAddressValid = true;
|
||
isHospitalCityValid = true;
|
||
isHospitalStateValid = true;
|
||
isHospitalPincodeValid = true;
|
||
isHospitalPhoneNoValid = true;
|
||
isAdmitDateValid = true;
|
||
isDischargeDateValid = true;
|
||
isClaimAmountValid = true;
|
||
isAccidentDateValid = true;
|
||
isIntimationDateValid = true;
|
||
isDeathDateValid = true;
|
||
|
||
// Clear uploaded files
|
||
fileService.clearAll();
|
||
MultiFileUploadWidget.hasFiles = false;
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return WillPopScope(
|
||
onWillPop: () async {
|
||
resetFormOnServiceChange();
|
||
return true;
|
||
},
|
||
child: Dialog(
|
||
backgroundColor: Colors.white, // ✅ PURE WHITE popup
|
||
insetPadding: const EdgeInsets.all(20),
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(16),
|
||
),
|
||
child: ClipRRect(
|
||
borderRadius: BorderRadius.circular(16),
|
||
child: SizedBox(
|
||
width: MediaQuery.of(context).size.width *
|
||
0.75, // Desktop popup width
|
||
// height: MediaQuery.of(context).size.height * 0.85,
|
||
child: Stack(
|
||
children: [
|
||
/// MAIN CONTENT (YOUR EXISTING UI)
|
||
SingleChildScrollView(
|
||
child: Container(
|
||
padding: EdgeInsets.all(20),
|
||
color: Colors.white,
|
||
child: Column(
|
||
children: [
|
||
/// 🔹 HEADER ROW (Title + Close)
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Text(
|
||
"Raise Insurance Claim",
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 20,
|
||
fontWeight: FontWeight.w600,
|
||
),
|
||
),
|
||
IconButton(
|
||
icon: const Icon(Icons.close),
|
||
onPressed: () => {
|
||
setState(() {
|
||
resetFormOnServiceChange();
|
||
}),
|
||
Navigator.pop(context)
|
||
},
|
||
),
|
||
],
|
||
),
|
||
|
||
const SizedBox(height: 16),
|
||
|
||
Form(
|
||
key: formKey,
|
||
child: Column(
|
||
children: [
|
||
_row([
|
||
buildDropdownField(
|
||
'Service',
|
||
(value) {
|
||
final selectedItem =
|
||
departmentList.firstWhere(
|
||
(item) => item['id'] == value,
|
||
orElse: () => {},
|
||
);
|
||
setState(() {
|
||
resetFormOnServiceChange();
|
||
serviceId = value;
|
||
serviceName = selectedItem['name'];
|
||
policyNumberId = null;
|
||
isServiceValid = true;
|
||
});
|
||
logDebug(
|
||
'🔥 serviceId set to $serviceId');
|
||
filterPoliciesByService(value!);
|
||
loadClaimTypes();
|
||
},
|
||
departmentList,
|
||
'name',
|
||
serviceId,
|
||
required: true,
|
||
isValid: isServiceValid,
|
||
),
|
||
buildDropdownField(
|
||
'Select Policy',
|
||
(value) {
|
||
final selectedPolicy =
|
||
policyNumberList.firstWhere(
|
||
(p) => p['id'] == value);
|
||
|
||
setState(() {
|
||
// ✅ THIS is what you send to API
|
||
selectedClientPolicyId =
|
||
selectedPolicy['id'];
|
||
|
||
// optional
|
||
selectedPolicyTypeId =
|
||
selectedPolicy['policy_type_id'];
|
||
policyNumberId = value;
|
||
|
||
isPolicyValid = true;
|
||
});
|
||
|
||
getCDPoliciesDetails();
|
||
},
|
||
policyNumberList,
|
||
'label', // 👈 DISPLAY FIELD
|
||
policyNumberId,
|
||
required: true,
|
||
isValid: isPolicyValid,
|
||
),
|
||
if (serviceId == 1 || serviceId == 72)
|
||
buildDropdownField(
|
||
'Select Claim Type',
|
||
(value) {
|
||
setState(() {
|
||
claimTypeId = value;
|
||
isClaimTypeValid = true;
|
||
});
|
||
},
|
||
claimTypeList,
|
||
'name',
|
||
claimTypeId,
|
||
required: true,
|
||
isValid: isClaimTypeValid,
|
||
),
|
||
buildDropdownFieldSearch(
|
||
'Member Name',
|
||
(value) {
|
||
final member =
|
||
employeePolicyList.firstWhere(
|
||
(m) => m['id'] == value,
|
||
);
|
||
|
||
setState(() {
|
||
selectedMemberId = value;
|
||
selectedMemberObject =
|
||
member; // ✅ FULL OBJECT
|
||
selectedMemberName = member['name'];
|
||
isMemberValid = true;
|
||
logDebug(
|
||
'selectedMemberObject $selectedMemberObject');
|
||
});
|
||
},
|
||
employeePolicyList,
|
||
'name',
|
||
selectedMemberId,
|
||
required: true,
|
||
isValid: isMemberValid,
|
||
),
|
||
]),
|
||
_row([
|
||
buildTextField(
|
||
'Message', messageController),
|
||
if (serviceId == 1 || serviceId == 72) ...[
|
||
buildTextField('Hospital Name',
|
||
hospitalNameController,
|
||
required: true,
|
||
isValid: isHospitalNameValid),
|
||
buildTextField('Hospital Address',
|
||
hospitalAddressController,
|
||
required: true,
|
||
isValid: isHospitalAddressValid),
|
||
]
|
||
]),
|
||
if (serviceId == 1 || serviceId == 72)
|
||
_row([
|
||
buildTextField('Hospital City',
|
||
hospitalCityController,
|
||
required: true,
|
||
isValid: isHospitalCityValid,
|
||
inputFormatters: [
|
||
FilteringTextInputFormatter.allow(
|
||
RegExp(r"[a-zA-Z\s]")),
|
||
]),
|
||
buildTextField('Hospital State',
|
||
hospitalStateController,
|
||
required: true,
|
||
isValid: isHospitalStateValid,
|
||
inputFormatters: [
|
||
FilteringTextInputFormatter.allow(
|
||
RegExp(r"[a-zA-Z\s]")),
|
||
]),
|
||
buildTextField('Hospital Pincode',
|
||
hospitalPinCodeController,
|
||
keyboardType: TextInputType.number,
|
||
inputFormatters: [
|
||
FilteringTextInputFormatter
|
||
.digitsOnly,
|
||
LengthLimitingTextInputFormatter(6),
|
||
],
|
||
required: true,
|
||
isValid: isHospitalPincodeValid),
|
||
]),
|
||
if (serviceId == 1 || serviceId == 72)
|
||
_row([
|
||
buildTextField(
|
||
'Hospital Phone No',
|
||
hospitalPhoneNoController,
|
||
keyboardType: TextInputType.number,
|
||
inputFormatters: [
|
||
FilteringTextInputFormatter
|
||
.digitsOnly,
|
||
LengthLimitingTextInputFormatter(10),
|
||
],
|
||
required: true,
|
||
isValid: !hasSubmitted ||
|
||
serviceId == null ||
|
||
(serviceId != 1 &&
|
||
serviceId != 72) ||
|
||
RegExp(r'^\d{10}$').hasMatch(
|
||
hospitalPhoneNoController.text
|
||
.trim()),
|
||
),
|
||
buildDatePickerField(
|
||
label: 'Admit Date',
|
||
selectedDate: admitDate,
|
||
allowFuture: false,
|
||
onDateSelected: (d) {
|
||
setState(() {
|
||
admitDate = d;
|
||
dischargeDate = null;
|
||
});
|
||
},
|
||
required: true,
|
||
isValid: isAdmitDateValid),
|
||
buildDatePickerField(
|
||
label: 'Discharge Date',
|
||
selectedDate: dischargeDate,
|
||
allowFuture: true,
|
||
minDate: admitDate
|
||
?.add(const Duration(days: 1)),
|
||
onDateSelected: (d) =>
|
||
setState(() => dischargeDate = d),
|
||
required: true,
|
||
isValid: isDischargeDateValid),
|
||
buildTextField('Claims Amount',
|
||
claimAmountController,
|
||
keyboardType: TextInputType.number,
|
||
inputFormatters: [
|
||
FilteringTextInputFormatter
|
||
.digitsOnly
|
||
],
|
||
required: true,
|
||
isValid: isClaimAmountValid),
|
||
]),
|
||
if ([2, 3, 4].contains(serviceId))
|
||
_row([
|
||
buildDatePickerField(
|
||
label: 'Date of Birth',
|
||
selectedDate: birthDate,
|
||
allowFuture: false,
|
||
onDateSelected: (d) =>
|
||
setState(() => birthDate = d),
|
||
),
|
||
buildDatePickerField(
|
||
label: 'Accident Date',
|
||
selectedDate: accidentDate,
|
||
allowFuture: false,
|
||
onDateSelected: (d) {
|
||
setState(() {
|
||
accidentDate = d;
|
||
deathDate = null;
|
||
intimationDate = null;
|
||
});
|
||
},
|
||
required: serviceId == 2,
|
||
isValid: isAccidentDateValid),
|
||
buildDatePickerField(
|
||
label: 'Date of Death',
|
||
selectedDate: deathDate,
|
||
allowFuture: false,
|
||
onDateSelected: (d) =>
|
||
setState(() => deathDate = d),
|
||
required:
|
||
(serviceId == 3 || serviceId == 4)
|
||
? true
|
||
: false, // 🔥 GTLI & EDLI
|
||
isValid: isDeathDateValid,
|
||
),
|
||
]),
|
||
if ([2, 3, 4].contains(serviceId))
|
||
_row([
|
||
buildDatePickerField(
|
||
label: 'Date of Intimation',
|
||
selectedDate: intimationDate,
|
||
allowFuture: false,
|
||
onDateSelected: (d) => setState(
|
||
() => intimationDate = d)),
|
||
buildTextField(
|
||
'Sum Insured',
|
||
sumInsuredController,
|
||
keyboardType: TextInputType.number,
|
||
inputFormatters: [
|
||
FilteringTextInputFormatter.digitsOnly
|
||
],
|
||
),
|
||
const SizedBox(),
|
||
]),
|
||
_row([
|
||
MultiFileUploadWidget(),
|
||
]),
|
||
Align(
|
||
alignment: Alignment.centerRight,
|
||
child: SizedBox(
|
||
width: 120,
|
||
height: 42,
|
||
child: ElevatedButton(
|
||
onPressed:
|
||
(isSubmitting || serviceId == null)
|
||
? null
|
||
: sendFormDataToApi,
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor:
|
||
const Color(0xFFE26728),
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius:
|
||
BorderRadius.circular(10),
|
||
),
|
||
),
|
||
child: isSubmitting
|
||
? const SizedBox(
|
||
height: 18,
|
||
width: 18,
|
||
child:
|
||
CircularProgressIndicator(
|
||
strokeWidth: 2,
|
||
color: Colors.white,
|
||
),
|
||
)
|
||
: const Text(
|
||
'Send',
|
||
style: TextStyle(
|
||
color: Colors.white,
|
||
fontWeight:
|
||
FontWeight.w600),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
|
||
/// 🔥 LOADER OVERLAY
|
||
if (isLoading)
|
||
Positioned.fill(
|
||
child: Container(
|
||
decoration: BoxDecoration(
|
||
color: Colors.white.withOpacity(0.6),
|
||
borderRadius: BorderRadius.circular(20),
|
||
),
|
||
child: Center(
|
||
child: Image.asset(
|
||
'assets/nhance-loader.gif',
|
||
height: 60,
|
||
width: 60,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
)));
|
||
}
|
||
|
||
/// ---------- HELPERS ----------
|
||
|
||
Widget _row(List<Widget> children) {
|
||
return Padding(
|
||
padding: const EdgeInsets.only(bottom: 14),
|
||
child: Row(
|
||
children: children
|
||
.map((e) => Expanded(
|
||
child: Padding(
|
||
padding: const EdgeInsets.only(right: 12),
|
||
child: e,
|
||
)))
|
||
.toList(),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget fieldLabel(String text, {bool required = false}) {
|
||
return Padding(
|
||
padding: const EdgeInsets.only(bottom: 6),
|
||
child: RichText(
|
||
text: TextSpan(
|
||
text: text,
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.w500,
|
||
color: Colors.black,
|
||
),
|
||
children: required
|
||
? const [
|
||
TextSpan(
|
||
text: ' *',
|
||
style: TextStyle(color: Colors.red),
|
||
)
|
||
]
|
||
: [],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget errorText(bool isValid) {
|
||
return SizedBox(
|
||
height: 16, // fixed height for alignment
|
||
child: isValid
|
||
? null
|
||
: const Text(
|
||
"Required",
|
||
style: TextStyle(
|
||
color: Colors.red,
|
||
fontSize: 11,
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget buildTextField(
|
||
String label,
|
||
TextEditingController controller, {
|
||
bool required = false,
|
||
bool isValid = true,
|
||
TextInputType keyboardType = TextInputType.text,
|
||
List<TextInputFormatter>? inputFormatters,
|
||
}) {
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
fieldLabel(label, required: required),
|
||
formBox(
|
||
child: TextField(
|
||
controller: controller,
|
||
keyboardType: keyboardType,
|
||
inputFormatters: inputFormatters,
|
||
decoration: const InputDecoration(
|
||
isDense: true,
|
||
border: InputBorder.none,
|
||
),
|
||
),
|
||
),
|
||
errorText(isValid),
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget buildTextAreaField(String label, TextEditingController controller) {
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
fieldLabel(label),
|
||
Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFFF1F1F1),
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: TextField(
|
||
controller: controller,
|
||
maxLines: 3,
|
||
decoration: const InputDecoration(
|
||
isDense: true,
|
||
border: InputBorder.none,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget buildDropdownField(
|
||
String label,
|
||
void Function(int?) onChanged,
|
||
List<Map<String, dynamic>> itemsList,
|
||
String displayField,
|
||
int? selectedValue, {
|
||
bool required = false,
|
||
bool isValid = true,
|
||
}) {
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
fieldLabel(label, required: required),
|
||
formBox(
|
||
child: DropdownButtonHideUnderline(
|
||
child: DropdownButton<int>(
|
||
isExpanded: true,
|
||
value: selectedValue,
|
||
hint: const Text('Select'),
|
||
icon: const Icon(Icons.keyboard_arrow_down),
|
||
|
||
/// ✅ This controls selected value (closed state)
|
||
selectedItemBuilder: (context) {
|
||
return itemsList.map<Widget>((item) {
|
||
return Align(
|
||
alignment: Alignment.centerLeft,
|
||
child: Text(
|
||
item[displayField] ?? '',
|
||
maxLines: 1,
|
||
overflow: TextOverflow.ellipsis,
|
||
softWrap: false,
|
||
),
|
||
);
|
||
}).toList();
|
||
},
|
||
|
||
/// ✅ This controls dropdown list (open state)
|
||
items: itemsList.map<DropdownMenuItem<int>>((item) {
|
||
return DropdownMenuItem<int>(
|
||
value: item['id'],
|
||
child: Text(
|
||
item[displayField] ?? '',
|
||
style: const TextStyle(fontSize: 13),
|
||
), // FULL TEXT here
|
||
);
|
||
}).toList(),
|
||
|
||
onChanged: onChanged,
|
||
),
|
||
),
|
||
),
|
||
if (required) errorText(isValid),
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget buildDropdownFieldSearch(
|
||
String label,
|
||
void Function(int?) onChanged,
|
||
List<Map<String, dynamic>> itemsList,
|
||
String displayField,
|
||
int? selectedValue, {
|
||
bool required = false,
|
||
bool isValid = true,
|
||
}) {
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
fieldLabel(label, required: required),
|
||
const SizedBox(height: 4),
|
||
Container(
|
||
height: 40,
|
||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||
decoration: BoxDecoration(
|
||
color: Color(0xFFF1F1F1),
|
||
borderRadius: BorderRadius.circular(8),
|
||
// boxShadow: [
|
||
// BoxShadow(
|
||
// color: Colors.black.withOpacity(0.08),
|
||
// blurRadius: 8,
|
||
// offset: const Offset(0, 2),
|
||
// ),
|
||
// ],
|
||
),
|
||
child: DropdownButtonHideUnderline(
|
||
child: DropdownButton2<int>(
|
||
isExpanded: true,
|
||
value: selectedValue,
|
||
hint: const Text(
|
||
'Select',
|
||
style: TextStyle(fontSize: 12),
|
||
),
|
||
|
||
iconStyleData: const IconStyleData(
|
||
icon: Icon(Icons.keyboard_arrow_down),
|
||
),
|
||
|
||
dropdownStyleData: DropdownStyleData(
|
||
maxHeight: 260,
|
||
decoration: BoxDecoration(
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
),
|
||
|
||
// 🔍 SEARCH SUPPORT
|
||
dropdownSearchData: DropdownSearchData(
|
||
searchController: searchController,
|
||
searchInnerWidgetHeight: 50,
|
||
searchInnerWidget: Padding(
|
||
padding: const EdgeInsets.all(8),
|
||
child: TextField(
|
||
controller: searchController,
|
||
style: const TextStyle(fontSize: 12),
|
||
decoration: InputDecoration(
|
||
hintText: 'Search...',
|
||
hintStyle: const TextStyle(fontSize: 12),
|
||
isDense: true,
|
||
border: OutlineInputBorder(
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
searchMatchFn: (item, searchValue) {
|
||
final matchedItem = itemsList.firstWhere(
|
||
(e) => e['id'] == item.value,
|
||
orElse: () => {},
|
||
);
|
||
|
||
final text =
|
||
matchedItem[displayField]?.toString().toLowerCase() ?? '';
|
||
|
||
return text.contains(searchValue.toLowerCase());
|
||
},
|
||
),
|
||
|
||
// 🔹 CLEAR SEARCH WHEN CLOSED
|
||
onMenuStateChange: (isOpen) {
|
||
if (!isOpen) searchController.clear();
|
||
},
|
||
|
||
items: itemsList.map((item) {
|
||
return DropdownMenuItem<int>(
|
||
value: item['id'],
|
||
child: Text(
|
||
item[displayField] ?? '',
|
||
style: const TextStyle(fontSize: 12),
|
||
overflow: TextOverflow.ellipsis,
|
||
),
|
||
);
|
||
}).toList(),
|
||
|
||
onChanged: onChanged,
|
||
),
|
||
),
|
||
),
|
||
if (required) errorText(isValid),
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget buildDatePickerField({
|
||
required String label,
|
||
required DateTime? selectedDate,
|
||
required bool allowFuture,
|
||
required ValueChanged<DateTime?> onDateSelected,
|
||
DateTime? minDate,
|
||
DateTime? maxDate,
|
||
bool required = false,
|
||
bool isValid = true,
|
||
}) {
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
fieldLabel(label, required: required),
|
||
Container(
|
||
height: 42,
|
||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFFF1F1F1),
|
||
borderRadius: BorderRadius.circular(8),
|
||
// border: required && !isValid
|
||
// ? Border.all(color: Colors.red)
|
||
// : null,
|
||
),
|
||
child: InkWell(
|
||
onTap: () async {
|
||
final DateTime now = DateTime.now();
|
||
final DateTime first = minDate ?? DateTime(1980);
|
||
final DateTime last =
|
||
allowFuture ? (maxDate ?? DateTime(2100)) : now;
|
||
|
||
final DateTime initialDate =
|
||
selectedDate ?? (first.isAfter(now) ? first : now);
|
||
|
||
final picked = await showDatePicker(
|
||
context: context,
|
||
initialDate: initialDate,
|
||
firstDate: first,
|
||
lastDate: last,
|
||
initialEntryMode: DatePickerEntryMode.calendarOnly,
|
||
);
|
||
|
||
if (picked != null) {
|
||
onDateSelected(picked);
|
||
}
|
||
},
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Text(
|
||
selectedDate != null
|
||
? DateFormat('dd-MM-yyyy').format(selectedDate)
|
||
: 'Select',
|
||
style: const TextStyle(color: Colors.black),
|
||
),
|
||
// ✅ Show clear button only if date selected
|
||
if (selectedDate != null)
|
||
GestureDetector(
|
||
onTap: () {
|
||
onDateSelected(null); // 🔥 Clear date
|
||
},
|
||
child: const Icon(
|
||
Icons.close,
|
||
size: 18,
|
||
color: Colors.grey,
|
||
),
|
||
)
|
||
else
|
||
const Icon(Icons.calendar_today, size: 18),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
errorText(isValid),
|
||
],
|
||
);
|
||
}
|
||
|
||
// Widget uploadBox() {
|
||
// return Column(
|
||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||
// children: [
|
||
// fieldLabel('Upload Documents'),
|
||
// Container(
|
||
// height: 120,
|
||
// width: double.infinity,
|
||
// decoration: BoxDecoration(
|
||
// color: const Color(0xFFF9F9F9),
|
||
// borderRadius: BorderRadius.circular(10),
|
||
// border: Border.all(
|
||
// color: const Color(0xFF00A6A6),
|
||
// width: 1,
|
||
// ),
|
||
// ),
|
||
// child: const MultiFileUploadWidget(),
|
||
// ),
|
||
// ],
|
||
// );
|
||
// }
|
||
|
||
Widget formBox({required Widget child}) {
|
||
return Container(
|
||
height: 42,
|
||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFFF1F1F1), // 👈 light grey
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: Center(child: child),
|
||
);
|
||
}
|
||
}
|
||
|
||
// class RaiseClaimDialog extends StatelessWidget {
|
||
// const RaiseClaimDialog({super.key});
|
||
//
|
||
// @override
|
||
// Widget build(BuildContext context) {
|
||
// return Dialog(
|
||
// backgroundColor: Colors.white, // 👈 THIS
|
||
// insetPadding: const EdgeInsets.all(24),
|
||
// shape: RoundedRectangleBorder(
|
||
// borderRadius: BorderRadius.circular(16),
|
||
// ),
|
||
// child: Container(
|
||
// width: MediaQuery.of(context).size.width * 0.75,
|
||
// padding: const EdgeInsets.all(24),
|
||
// child: Column(
|
||
// mainAxisSize: MainAxisSize.min,
|
||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||
// children: [
|
||
// /// HEADER
|
||
// Row(
|
||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
// children: [
|
||
// Text(
|
||
// 'Raise Insurance Claim',
|
||
// style: GoogleFonts.poppins(
|
||
// fontSize: 20,
|
||
// fontWeight: FontWeight.w600,
|
||
// ),
|
||
// ),
|
||
// IconButton(
|
||
// icon: const Icon(Icons.close),
|
||
// onPressed: () => Navigator.pop(context),
|
||
// ),
|
||
// ],
|
||
// ),
|
||
//
|
||
// const SizedBox(height: 20),
|
||
//
|
||
// /// FORM
|
||
// Expanded(
|
||
// child: SingleChildScrollView(
|
||
// child: Column(
|
||
// children: [
|
||
// _row([
|
||
// _dropdown('Select Policy*'),
|
||
// _dropdown('Member Name'),
|
||
// _input('Hospital Name'),
|
||
// ]),
|
||
// _row([
|
||
// _input('Hospital Address'),
|
||
// _input('Hospital City'),
|
||
// _input('Hospital State'),
|
||
// ]),
|
||
// _row([
|
||
// _input('Hospital Pincode'),
|
||
// _input('Hospital Phone Number'),
|
||
// _date('Admit Date'),
|
||
// ]),
|
||
// _row([
|
||
// _date('Discharge Date'),
|
||
// _input('Claim Amount'),
|
||
// _input('Message'),
|
||
// ]),
|
||
//
|
||
// const SizedBox(height: 20),
|
||
//
|
||
// /// UPLOAD
|
||
// _uploadBox(),
|
||
// ],
|
||
// ),
|
||
// ),
|
||
// ),
|
||
//
|
||
// const SizedBox(height: 20),
|
||
//
|
||
// /// FOOTER
|
||
// Align(
|
||
// alignment: Alignment.centerRight,
|
||
// child: SizedBox(
|
||
// width: 120,
|
||
// height: 42,
|
||
// child: ElevatedButton(
|
||
// onPressed: () {},
|
||
// style: ElevatedButton.styleFrom(
|
||
// backgroundColor: const Color(0xFFE26728),
|
||
// shape: RoundedRectangleBorder(
|
||
// borderRadius: BorderRadius.circular(10),
|
||
// ),
|
||
// ),
|
||
// child: const Text(
|
||
// 'Send',
|
||
// style: TextStyle(fontWeight: FontWeight.w600),
|
||
// ),
|
||
// ),
|
||
// ),
|
||
// ),
|
||
// ],
|
||
// ),
|
||
// ),
|
||
// );
|
||
// }
|
||
//
|
||
// /// ---------- HELPERS ----------
|
||
//
|
||
// Widget _row(List<Widget> children) {
|
||
// return Padding(
|
||
// padding: const EdgeInsets.only(bottom: 14),
|
||
// child: Row(
|
||
// children: children
|
||
// .map((e) => Expanded(child: Padding(
|
||
// padding: const EdgeInsets.only(right: 12),
|
||
// child: e,
|
||
// )))
|
||
// .toList(),
|
||
// ),
|
||
// );
|
||
// }
|
||
//
|
||
// Widget _input(String label) {
|
||
// return Column(
|
||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||
// children: [
|
||
// Text(label, style: _labelStyle),
|
||
// const SizedBox(height: 6),
|
||
// _box(
|
||
// TextField(
|
||
// decoration: const InputDecoration(
|
||
// border: InputBorder.none,
|
||
// ),
|
||
// ),
|
||
// ),
|
||
// ],
|
||
// );
|
||
// }
|
||
//
|
||
// Widget _dropdown(String label) {
|
||
// return Column(
|
||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||
// children: [
|
||
// Text(label, style: _labelStyle),
|
||
// const SizedBox(height: 6),
|
||
// _box(
|
||
// DropdownButtonHideUnderline(
|
||
// child: DropdownButton<String>(
|
||
// isExpanded: true,
|
||
// hint: const Text('Select'),
|
||
// items: const [],
|
||
// onChanged: (_) {},
|
||
// ),
|
||
// ),
|
||
// ),
|
||
// ],
|
||
// );
|
||
// }
|
||
//
|
||
// Widget _date(String label) {
|
||
// return Column(
|
||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||
// children: [
|
||
// Text(label, style: _labelStyle),
|
||
// const SizedBox(height: 6),
|
||
// _box(
|
||
// TextField(
|
||
// readOnly: true,
|
||
// decoration: const InputDecoration(
|
||
// suffixIcon: Icon(Icons.calendar_today, size: 18),
|
||
// border: InputBorder.none,
|
||
// ),
|
||
// ),
|
||
// ),
|
||
// ],
|
||
// );
|
||
// }
|
||
//
|
||
// Widget _box(Widget child) {
|
||
// return Container(
|
||
// height: 42,
|
||
// padding: const EdgeInsets.symmetric(horizontal: 12),
|
||
// decoration: BoxDecoration(
|
||
// color: const Color(0xFFF1F1F1),
|
||
// borderRadius: BorderRadius.circular(8),
|
||
// ),
|
||
// child: Center(child: child),
|
||
// );
|
||
// }
|
||
//
|
||
// Widget _uploadBox() {
|
||
// return Column(
|
||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||
// children: [
|
||
// Text('Upload Documents', style: _labelStyle),
|
||
// const SizedBox(height: 8),
|
||
// Container(
|
||
// height: 120,
|
||
// width: double.infinity,
|
||
// decoration: BoxDecoration(
|
||
// borderRadius: BorderRadius.circular(10),
|
||
// border: Border.all(
|
||
// color: const Color(0xFF00A6A6),
|
||
// style: BorderStyle.solid,
|
||
// ),
|
||
// color: const Color(0xFFF9F9F9),
|
||
// ),
|
||
// child: Column(
|
||
// mainAxisAlignment: MainAxisAlignment.center,
|
||
// children: const [
|
||
// Icon(Icons.insert_drive_file,
|
||
// size: 36, color: Color(0xFF00A6A6)),
|
||
// SizedBox(height: 6),
|
||
// Text(
|
||
// 'Sample_file.PDF',
|
||
// style: TextStyle(fontWeight: FontWeight.w500),
|
||
// ),
|
||
// SizedBox(height: 4),
|
||
// Text(
|
||
// '(Supported formats: PDF, PNG, JPG, JPEG, HEIC)',
|
||
// style: TextStyle(fontSize: 11, color: Colors.grey),
|
||
// ),
|
||
// ],
|
||
// ),
|
||
// ),
|
||
// ],
|
||
// );
|
||
// }
|
||
// }
|
||
|
||
/// ---------- STYLES ----------
|
||
final _labelStyle = GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.w500,
|
||
);
|