You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
mohemm-flutter-app/lib/ui/profile/add_dependent_verify_screen...

2416 lines
100 KiB
Dart

import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:mohem_flutter_app/api/leave_balance_api_client.dart';
import 'package:mohem_flutter_app/api/profile_api_client.dart';
import 'package:mohem_flutter_app/app_state/app_state.dart';
import 'package:mohem_flutter_app/classes/colors.dart';
import 'package:mohem_flutter_app/classes/consts.dart';
import 'package:mohem_flutter_app/classes/utils.dart';
import 'package:mohem_flutter_app/config/routes.dart';
import 'package:mohem_flutter_app/extensions/string_extensions.dart';
import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
import 'package:mohem_flutter_app/models/dyanmic_forms/validate_eit_transaction_model.dart';
import 'package:mohem_flutter_app/models/get_contact_dff_structure_list.dart';
import 'package:mohem_flutter_app/models/get_eit_dff_structure_list_model.dart';
import 'package:mohem_flutter_app/models/profile/basic_details_dff_structure.dart';
import 'package:mohem_flutter_app/models/post_params_model.dart';
import 'package:mohem_flutter_app/models/profile/get_contact_clos_structure_list.dart';
import 'package:mohem_flutter_app/models/profile/get_contact_details_list.dart';
import 'package:mohem_flutter_app/models/profile/patient_info_model.dart';
import 'package:mohem_flutter_app/models/profile/submit_contact_transaction_list_model.dart';
import 'package:mohem_flutter_app/ui/misc/request_submit_screen.dart';
import 'package:mohem_flutter_app/widgets/app_bar_widget.dart';
import 'package:mohem_flutter_app/widgets/button/default_button.dart';
import 'package:mohem_flutter_app/widgets/dynamic_forms/dynamic_textfield_widget.dart';
class AddDependentVerifyScreen extends StatefulWidget {
const AddDependentVerifyScreen({Key? key}) : super(key: key);
@override
_AddDependentVerifyScreenState createState() => _AddDependentVerifyScreenState();
}
class _AddDependentVerifyScreenState extends State<AddDependentVerifyScreen> {
PostParamsModel get _postParamsInitConfig => AppState().postParamsInitConfig;
bool get _isLegislationSA => _postParamsInitConfig.pLegislationCode?.trim().toUpperCase() == 'SA';
int _currentStep = 1; // 1: Verify, 2: Basic Info, 3: Additional Info, 4: Submit
bool _isLoading = false;
// Step 1 Controllers & State
final TextEditingController _nationalIdController = TextEditingController();
DateTime? _selectedDob;
PatientInfoModel? _patientInfo;
// Dynamic EBS Structures
List<GetBasicDetDffStructureList> _basicDetDffStructureList = [];
List<GetContactColsStructureList> _contactColsStructureList = [];
List<GetContactDffStructureList> _contactDffStructureList = [];
bool _ebsLoaded = false;
// Cache for dynamically generated text field controllers
final Map<String, TextEditingController> _fieldControllers = {};
// Track field keys/prompts that were prefilled from patientInfo
final Set<String> _prefilledFieldKeys = <String>{};
bool _isFieldPrefilledFromPatientInfo(String? colName, String? prompt, String? val) {
if (_patientInfo == null) return false;
if (val == null || val.trim().isEmpty) return false;
if (colName != null && _prefilledFieldKeys.contains(colName.toUpperCase().trim())) {
return true;
}
if (prompt != null && _prefilledFieldKeys.contains(prompt.toLowerCase().trim())) {
return true;
}
return false;
}
IconData? _getSuffixIconForField(String colName, String prompt, String dataType) {
String col = colName.toUpperCase();
String p = prompt.toLowerCase();
if (col.contains('EMAIL') || p.contains('email') || p.contains('بريد')) {
return Icons.mail_outline_rounded;
}
if (col.contains('PHONE') || col.contains('MOBILE') || col.contains('TEL') || p.contains('phone') || p.contains('mobile') || p.contains('جوال') || p.contains('هاتف')) {
return Icons.phone_outlined;
}
if (dataType == 'DATE' || col.contains('DATE') || p.contains('date') || p.contains('تاريخ')) {
return Icons.calendar_today_outlined;
}
return null;
}
@override
void initState() {
super.initState();
_nationalIdController.addListener(_onNationalIdChanged);
_fetchEbsStructures();
}
void _onNationalIdChanged() {
if (mounted) setState(() {});
}
void _onFieldControllerChanged() {
if (mounted) setState(() {});
}
@override
void dispose() {
_nationalIdController.removeListener(_onNationalIdChanged);
_nationalIdController.dispose();
for (var c in _fieldControllers.values) {
c.removeListener(_onFieldControllerChanged);
c.dispose();
}
_fieldControllers.clear();
super.dispose();
}
bool _isStep1Valid() {
String natId = _nationalIdController.text.trim();
if (natId.isEmpty) return false;
if (_isLegislationSA && natId.length != 10) return false;
if (_selectedDob == null) return false;
return true;
}
bool _isStep2Valid() {
for (int i = 0; i < _contactColsStructureList.length; i++) {
var col = _contactColsStructureList[i];
if (col.dISPLAYFLAG != "N" && col.rEQUIREDFLAG == "Y") {
String colKey = col.aPPLICATIONCOLUMNNAME ?? "COL_$i";
String textVal = _fieldControllers[colKey]?.text.trim() ?? "";
String dspVal = col.getContactDetailsList?.sEGMENTVALUEDSP?.trim() ?? "";
String codeVal = col.getContactDetailsList?.vARCHAR2VALUE?.trim() ?? "";
String dateVal = col.getContactDetailsList?.dATEVALUE?.trim() ?? "";
if (textVal.isEmpty && dspVal.isEmpty && codeVal.isEmpty && dateVal.isEmpty) {
return false;
}
}
}
for (int i = 0; i < _basicDetDffStructureList.length; i++) {
var basic = _basicDetDffStructureList[i];
if (basic.dISPLAYFLAG != "N" && basic.rEQUIREDFLAG == "Y") {
String colKey = basic.aPPLICATIONCOLUMNNAME ?? "BASIC_DFF_$i";
String textVal = _fieldControllers[colKey]?.text.trim() ?? "";
String dspVal = basic.getContactDetailsList?.sEGMENTVALUEDSP?.trim() ?? "";
String codeVal = basic.getContactDetailsList?.vARCHAR2VALUE?.trim() ?? "";
String dateVal = basic.getContactDetailsList?.dATEVALUE?.trim() ?? "";
if (textVal.isEmpty && dspVal.isEmpty && codeVal.isEmpty && dateVal.isEmpty) {
return false;
}
}
}
return true;
}
bool _isStep3Valid() {
for (int i = 0; i < _contactDffStructureList.length; i++) {
var dff = _contactDffStructureList[i];
if (dff.dISPLAYFLAG != "N" && dff.rEQUIREDFLAG == "Y") {
String colKey = dff.aPPLICATIONCOLUMNNAME ?? "CONTACT_DFF_$i";
String textVal = _fieldControllers[colKey]?.text.trim() ?? "";
String dspVal = dff.getContactDetailsList?.sEGMENTVALUEDSP?.trim() ?? "";
String codeVal = dff.getContactDetailsList?.vARCHAR2VALUE?.trim() ?? "";
String dateVal = dff.getContactDetailsList?.dATEVALUE?.trim() ?? "";
if (textVal.isEmpty && dspVal.isEmpty && codeVal.isEmpty && dateVal.isEmpty) {
return false;
}
}
}
return true;
}
TextEditingController _getControllerForField(String key, String? initialValue) {
if (!_fieldControllers.containsKey(key)) {
TextEditingController ctrl = TextEditingController(text: initialValue ?? '');
ctrl.addListener(_onFieldControllerChanged);
_fieldControllers[key] = ctrl;
}
return _fieldControllers[key]!;
}
void _syncController(String key, String? value) {
if (_fieldControllers.containsKey(key)) {
if (_fieldControllers[key]!.text != (value ?? '')) {
_fieldControllers[key]!.text = value ?? '';
}
} else {
TextEditingController ctrl = TextEditingController(text: value ?? '');
ctrl.addListener(_onFieldControllerChanged);
_fieldControllers[key] = ctrl;
}
}
void _initStructureDetails() {
for (var element in _basicDetDffStructureList) {
element.getContactDetailsList ??= GetContactDetailsList();
String colName = (element.aPPLICATIONCOLUMNNAME ?? "").toUpperCase();
String prompt = (element.sEGMENTPROMPT ?? "").toLowerCase();
if ((colName.contains("SPONSOR") || prompt.contains("sponsor") || prompt.contains("كفيل")) &&
element.eSERVICESVS != null && element.eSERVICESVS!.isNotEmpty) {
element.getContactDetailsList!.sEGMENTVALUEDSP ??= element.eSERVICESVS!.first.vALUECOLUMNNAME;
element.getContactDetailsList!.vARCHAR2VALUE ??= element.eSERVICESVS!.first.iDCOLUMNNAME;
}
}
for (var element in _contactColsStructureList) {
element.getContactDetailsList ??= GetContactDetailsList();
String colName = (element.aPPLICATIONCOLUMNNAME ?? "").toUpperCase();
String prompt = (element.sEGMENTPROMPT ?? "").toLowerCase();
if ((colName.contains("SPONSOR") || prompt.contains("sponsor") || prompt.contains("كفيل")) &&
element.objectValuesList != null && element.objectValuesList!.isNotEmpty) {
element.getContactDetailsList!.sEGMENTVALUEDSP ??= element.objectValuesList!.first.mEANING;
element.getContactDetailsList!.vARCHAR2VALUE ??= element.objectValuesList!.first.cODE;
}
}
for (var element in _contactDffStructureList) {
element.getContactDetailsList ??= GetContactDetailsList();
String colName = (element.aPPLICATIONCOLUMNNAME ?? "").toUpperCase();
String prompt = (element.sEGMENTPROMPT ?? "").toLowerCase();
if ((colName.contains("SPONSOR") || prompt.contains("sponsor") || prompt.contains("كفيل")) &&
element.eSERVICESVS != null && element.eSERVICESVS!.isNotEmpty) {
element.getContactDetailsList!.sEGMENTVALUEDSP ??= element.eSERVICESVS!.first.vALUECOLUMNNAME;
element.getContactDetailsList!.vARCHAR2VALUE ??= element.eSERVICESVS!.first.iDCOLUMNNAME;
}
}
}
Future<void> _fetchEbsStructures() async {
try {
List<dynamic> results = await Future.wait([
ProfileApiClient().getBasicDetDffStructure().catchError((_) => <GetBasicDetDffStructureList>[]),
ProfileApiClient().getContactColsStructureList("ADD").catchError((_) => <GetContactColsStructureList>[]),
ProfileApiClient().getContactDffStructureList().catchError((_) => <GetContactDffStructureList>[]),
]);
if (mounted) {
setState(() {
_basicDetDffStructureList = results[0] as List<GetBasicDetDffStructureList>;
_contactColsStructureList = results[1] as List<GetContactColsStructureList>;
_contactDffStructureList = results[2] as List<GetContactDffStructureList>;
_initStructureDetails();
_ebsLoaded = true;
});
}
} catch (_) {}
}
// --- Step 1 Date of Birth Picker ---
Future<void> _pickDateOfBirth(BuildContext context) async {
DateTime initial = _selectedDob ?? DateTime(DateTime.now().year - 5, 1, 1);
if (!Platform.isIOS) {
DateTime tempDate = initial;
await showCupertinoModalPopup(
context: context,
builder: (BuildContext cxt) => Container(
height: 260,
color: Colors.white,
child: Column(
children: [
Container(
height: 44,
color: Colors.grey.shade100,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
CupertinoButton(
padding: EdgeInsets.zero,
child: LocaleKeys.cancel.tr().toText14(color: Colors.grey),
onPressed: () => Navigator.pop(cxt),
),
CupertinoButton(
padding: EdgeInsets.zero,
child: LocaleKeys.done
.tr()
.toText14(isBold: true, color: const Color(0xFF168A89)),
onPressed: () {
setState(() {
_selectedDob = tempDate;
});
Navigator.pop(cxt);
},
),
],
),
),
Expanded(
child: CupertinoDatePicker(
mode: CupertinoDatePickerMode.date,
initialDateTime: initial,
maximumDate: DateTime.now(),
minimumYear: 1920,
maximumYear: DateTime.now().year,
onDateTimeChanged: (DateTime value) {
tempDate = value;
},
),
),
],
),
),
);
} else {
DateTime? picked = await showDatePicker(
context: context,
initialDate: initial,
firstDate: DateTime(1920, 1),
lastDate: DateTime.now(),
initialEntryMode: DatePickerEntryMode.calendarOnly,
);
if (picked != null) {
setState(() {
_selectedDob = picked;
});
}
}
}
// --- Step 1 Action: Search User / Next ---
void _onSearchUser() async {
bool isAr = AppState().isArabic(context);
String nationalId = _nationalIdController.text.trim();
if (nationalId.isEmpty) {
Utils.showToast(LocaleKeys.pleaseEnterNationalId.tr());
return;
}
if (_isLegislationSA && nationalId.length != 10) {
Utils.showToast(LocaleKeys.nationalIdMustBe10Digits.tr());
return;
}
if (_selectedDob == null) {
Utils.showToast(LocaleKeys.pleaseSelectDob.tr());
return;
}
String formattedDob = DateFormat('dd/MM/yyyy').format(_selectedDob!);
String ymdDob = DateFormat('yyyy-MM-dd').format(_selectedDob!);
// If legislation code is not 'SA', skip NHIC API call and prefill, proceed to manual entry
if (_postParamsInitConfig.pLegislationCode != 'SA') {
Utils.showLoading(context);
setState(() {
_isLoading = true;
});
try {
if (!_ebsLoaded) {
await _fetchEbsStructures();
}
_initStructureDetails();
_syncStep1ValuesToStructures(nationalId, ymdDob);
} finally {
Utils.hideLoading(context);
setState(() {
_isLoading = false;
_currentStep = 2; // Move to Step 2 for manual entry
});
}
return;
}
// SA legislation flow: Call NHIC getPatientInfo API
Utils.showLoading(context);
setState(() {
_isLoading = true;
});
try {
PatientInfoModel? patientInfo = await ProfileApiClient().getPatientInfo(
nationalId: nationalId,
dob: formattedDob,
);
// Ensure EBS structures are ready
if (!_ebsLoaded) {
await _fetchEbsStructures();
}
Utils.hideLoading(context);
setState(() {
_isLoading = false;
});
if (patientInfo != null && (patientInfo.messageStatus == 1 || (patientInfo.idNumber != null && patientInfo.idNumber!.isNotEmpty))) {
_patientInfo = patientInfo;
if (_patientInfo!.dateOfBirth == null || _patientInfo!.dateOfBirth!.isEmpty) {
_patientInfo!.dateOfBirth = formattedDob;
}
// Apply prefilled patient info across dynamic structures
_initStructureDetails();
_applyPrefilledPatientInfo(patientInfo, isAr, nationalId, formattedDob);
setState(() {
_currentStep = 2; // Move to Step 2: Basic Info
});
} else {
String msg = patientInfo?.errorEndUserMessage ??
patientInfo?.errorMessage ??
LocaleKeys.dependentNotFoundNhic.tr();
Utils.showToast(msg);
}
} catch (ex) {
Utils.hideLoading(context);
setState(() {
_isLoading = false;
});
Utils.handleException(ex, context, null);
}
}
// --- Sync Step 1 National ID & DOB for Non-SA Dynamic Structures ---
void _syncStep1ValuesToStructures(String nationalId, String? ymdDob) {
void matchStep1(colItem) {
if (colItem == null) return;
colItem.getContactDetailsList ??= GetContactDetailsList();
GetContactDetailsList details = colItem.getContactDetailsList!;
String colName = (colItem.aPPLICATIONCOLUMNNAME ?? '').toString().toUpperCase().trim();
String prompt = (colItem.sEGMENTPROMPT ?? '').toString().toLowerCase().trim();
String colKey = colItem.aPPLICATIONCOLUMNNAME ?? '';
// 1. Civil ID / National ID / Iqama / Identification
if (colName.contains('NATIONAL_IDENTIFIER') ||
colName.contains('CIVIL_ID') ||
colName.contains('IQAMA') ||
colName.contains('IDENTIFIER') ||
colName.contains('ID_NUMBER') ||
colName.contains('ID_NO') ||
colName.contains('NATIONAL_ID') ||
prompt.contains('civil') ||
prompt.contains('iqama') ||
prompt.contains('national id') ||
prompt.contains('هوية') ||
prompt.contains('إقامة') ||
prompt.contains('هويه')) {
details.sEGMENTVALUEDSP = nationalId;
details.vARCHAR2VALUE = nationalId;
_syncController(colKey, nationalId);
if (_patientInfo != null) {
if (colName.isNotEmpty) _prefilledFieldKeys.add(colName);
if (prompt.isNotEmpty) {
_prefilledFieldKeys.add(prompt);
_prefilledFieldKeys.add(prompt.replaceAll('*', '').trim());
}
if (colKey.isNotEmpty) _prefilledFieldKeys.add(colKey);
}
}
// 2. Date of birth
else if (colName.contains('DATE_OF_BIRTH') ||
colName.contains('DOB') ||
colName.contains('BIRTH') ||
prompt.contains('birth') ||
prompt.contains('ميلاد') ||
prompt.contains('dob')) {
if (ymdDob != null && ymdDob.isNotEmpty) {
details.sEGMENTVALUEDSP = ymdDob;
details.dATEVALUE = ymdDob;
details.vARCHAR2VALUE = ymdDob;
_syncController(colKey, ymdDob);
if (_patientInfo != null) {
if (colName.isNotEmpty) _prefilledFieldKeys.add(colName);
if (prompt.isNotEmpty) {
_prefilledFieldKeys.add(prompt);
_prefilledFieldKeys.add(prompt.replaceAll('*', '').trim());
}
if (colKey.isNotEmpty) _prefilledFieldKeys.add(colKey);
}
}
}
}
_contactColsStructureList.forEach(matchStep1);
_basicDetDffStructureList.forEach(matchStep1);
_contactDffStructureList.forEach(matchStep1);
}
// --- Map GetPatientInfo to Dynamic Structures ---
void _applyPrefilledPatientInfo(PatientInfoModel patient, bool isAr, String nationalId, String dob) {
_prefilledFieldKeys.clear();
String fullNameAr = patient.fullNameAr;
String fullNameEn = patient.fullNameEn;
String effectiveName = isAr
? (fullNameAr.isNotEmpty ? fullNameAr : fullNameEn)
: (fullNameEn.isNotEmpty ? fullNameEn : fullNameAr);
DateTime? parsedDob;
if (patient.dateOfBirth != null && patient.dateOfBirth!.isNotEmpty) {
String cleanDob = patient.dateOfBirth!.replaceAll(r'\', '').trim();
List<String> formats = ['dd/MM/yyyy', 'MM/dd/yyyy', 'd/M/yyyy', 'M/d/yyyy', 'yyyy-MM-dd'];
for (String f in formats) {
try {
parsedDob = DateFormat(f).parseStrict(cleanDob);
break;
} catch (_) {}
}
}
if (parsedDob == null && _selectedDob != null) {
parsedDob = _selectedDob;
}
String ymdDob = parsedDob != null ? DateFormat('yyyy-MM-dd').format(parsedDob) : (patient.dateOfBirth ?? dob);
void matchAndFill(colItem) {
if (colItem == null) return;
colItem.getContactDetailsList ??= GetContactDetailsList();
GetContactDetailsList details = colItem.getContactDetailsList!;
String colName = (colItem.aPPLICATIONCOLUMNNAME ?? '').toString().toUpperCase().trim();
String prompt = (colItem.sEGMENTPROMPT ?? '').toString().toLowerCase().trim();
String colKey = colItem.aPPLICATIONCOLUMNNAME ?? '';
void markPrefilled() {
if (colName.isNotEmpty) _prefilledFieldKeys.add(colName);
if (prompt.isNotEmpty) {
_prefilledFieldKeys.add(prompt);
_prefilledFieldKeys.add(prompt.replaceAll('*', '').trim());
}
if (colKey.isNotEmpty) _prefilledFieldKeys.add(colKey);
}
// 1. Civil ID / National ID / Iqama
if (colName.contains('NATIONAL_IDENTIFIER') ||
colName.contains('CIVIL_ID') ||
colName.contains('IQAMA') ||
colName.contains('IDENTIFIER') ||
colName.contains('ID_NUMBER') ||
colName.contains('ID_NO') ||
colName.contains('NATIONAL_ID') ||
prompt.contains('civil') ||
prompt.contains('iqama') ||
prompt.contains('national id') ||
prompt.contains('هوية') ||
prompt.contains('إقامة') ||
prompt.contains('هويه')) {
String idVal = (patient.idNumber != null && patient.idNumber!.isNotEmpty)
? patient.idNumber!
: nationalId;
details.sEGMENTVALUEDSP = idVal;
details.vARCHAR2VALUE = idVal;
_syncController(colKey, idVal);
markPrefilled();
}
// 2. Date of birth
else if (colName.contains('DATE_OF_BIRTH') ||
colName.contains('DOB') ||
colName.contains('BIRTH') ||
prompt.contains('birth') ||
prompt.contains('ميلاد') ||
prompt.contains('dob')) {
String dobVal = ymdDob.isNotEmpty ? ymdDob : (patient.dateOfBirth ?? dob);
if (dobVal.isNotEmpty) {
details.sEGMENTVALUEDSP = dobVal;
details.dATEVALUE = dobVal;
details.vARCHAR2VALUE = dobVal;
_syncController(colKey, dobVal);
markPrefilled();
}
}
// 3. First Name
else if (colName.contains('FIRST_NAME') ||
prompt.contains('first name') ||
prompt.contains('الاسم الأول') ||
prompt.contains('اسم الأول')) {
bool wantsAr = prompt.contains('عربي') || prompt.contains('arabic') || colName.contains('_AR') || colName.contains('ARABIC');
String val = wantsAr
? (patient.firstNameAr ?? patient.firstNameEn ?? '')
: (patient.firstNameEn ?? patient.firstNameAr ?? '');
if (val.isNotEmpty) {
details.sEGMENTVALUEDSP = val;
details.vARCHAR2VALUE = val;
_syncController(colKey, val);
markPrefilled();
}
}
// 4. Second / Father Name
else if (colName.contains('SECOND_NAME') ||
colName.contains('FATHER_NAME') ||
prompt.contains('father') ||
prompt.contains('الأب') ||
prompt.contains('اسم الأب') ||
prompt.contains('second name')) {
bool wantsAr = prompt.contains('عربي') || prompt.contains('arabic') || colName.contains('_AR') || colName.contains('ARABIC');
String val = wantsAr
? (patient.secondNameAr ?? patient.secondNameEn ?? '')
: (patient.secondNameEn ?? patient.secondNameAr ?? '');
if (val != '-' && val.isNotEmpty) {
details.sEGMENTVALUEDSP = val;
details.vARCHAR2VALUE = val;
_syncController(colKey, val);
markPrefilled();
}
}
// 5. Third / Grandfather Name
else if (colName.contains('THIRD_NAME') ||
colName.contains('GRAND_FATHER') ||
colName.contains('GRAND_NAME') ||
prompt.contains('grand') ||
prompt.contains('الجد') ||
prompt.contains('third name')) {
bool wantsAr = prompt.contains('عربي') || prompt.contains('arabic') || colName.contains('_AR') || colName.contains('ARABIC');
String val = wantsAr
? ((patient.thirdNameAr != '-' && patient.thirdNameAr != null) ? patient.thirdNameAr! : '')
: ((patient.thirdNameEn != '-' && patient.thirdNameEn != null) ? patient.thirdNameEn! : '');
if (val.isNotEmpty) {
details.sEGMENTVALUEDSP = val;
details.vARCHAR2VALUE = val;
_syncController(colKey, val);
markPrefilled();
}
}
// 6. Last / Family Name
else if (colName.contains('LAST_NAME') ||
colName.contains('FAMILY_NAME') ||
prompt.contains('last name') ||
prompt.contains('family name') ||
prompt.contains('العائلة') ||
prompt.contains('القبيلة') ||
prompt.contains('الأخير')) {
bool wantsAr = prompt.contains('عربي') || prompt.contains('arabic') || colName.contains('_AR') || colName.contains('ARABIC');
String val = wantsAr
? (patient.lastNameAr ?? patient.lastNameEn ?? '')
: (patient.lastNameEn ?? patient.lastNameAr ?? '');
if (val.isNotEmpty) {
details.sEGMENTVALUEDSP = val;
details.vARCHAR2VALUE = val;
_syncController(colKey, val);
markPrefilled();
}
}
// 7. Full Name / Contact Name
else if (colName.contains('CONTACT_NAME') ||
colName.contains('FULL_NAME') ||
prompt.contains('contact name') ||
prompt.contains('اسم التابع') ||
prompt.contains('الاسم الكامل') ||
prompt.contains('اسم جهة الاتصال')) {
bool wantsAr = prompt.contains('عربي') || prompt.contains('arabic') || colName.contains('_AR') || colName.contains('ARABIC');
String val = wantsAr ? fullNameAr : (fullNameEn.isNotEmpty ? fullNameEn : fullNameAr);
if (val.isEmpty) val = effectiveName;
if (val.isNotEmpty) {
details.sEGMENTVALUEDSP = val;
details.vARCHAR2VALUE = val;
_syncController(colKey, val);
markPrefilled();
}
}
List<ObjectValuesList>? objValues;
List<ESERVICESVS>? eServices;
if (colItem is GetContactColsStructureList) {
objValues = colItem.objectValuesList;
} else if (colItem is GetBasicDetDffStructureList) {
eServices = colItem.eSERVICESVS;
} else if (colItem is GetContactDffStructureList) {
eServices = colItem.eSERVICESVS;
}
// 8. Gender / Sex
if (colName.contains('SEX') ||
colName.contains('GENDER') ||
prompt.contains('gender') ||
prompt.contains('sex') ||
prompt.contains('الجنس')) {
String g = (patient.gender ?? '').trim().toUpperCase();
bool isFemale = g == 'F' || g.startsWith('FEM');
bool isMale = g == 'M' || g.startsWith('MAL') || (!isFemale && g.isNotEmpty);
bool matched = false;
if (objValues != null && objValues.isNotEmpty) {
// Priority 1: Match exact code ('F' or 'M')
for (var opt in objValues) {
String code = (opt.cODE ?? '').trim().toUpperCase();
if ((isFemale && (code == 'F' || code == 'FEMALE')) ||
(isMale && (code == 'M' || code == 'MALE'))) {
details.sEGMENTVALUEDSP = opt.mEANING;
details.vARCHAR2VALUE = opt.cODE;
matched = true;
break;
}
}
// Priority 2: Match meaning safely without substring collision
if (!matched) {
for (var opt in objValues) {
String meaning = (opt.mEANING ?? '').trim().toLowerCase();
if (isFemale && (meaning == 'female' || meaning.contains('female') || meaning.contains('أنثى'))) {
details.sEGMENTVALUEDSP = opt.mEANING;
details.vARCHAR2VALUE = opt.cODE;
matched = true;
break;
} else if (isMale && !meaning.contains('female') && (meaning == 'male' || meaning.contains('male') || meaning.contains('ذكر'))) {
details.sEGMENTVALUEDSP = opt.mEANING;
details.vARCHAR2VALUE = opt.cODE;
matched = true;
break;
}
}
}
} else if (eServices != null && eServices.isNotEmpty) {
// Priority 1: Match exact ID/Code
for (var opt in eServices) {
String idCol = (opt.iDCOLUMNNAME ?? '').trim().toUpperCase();
if ((isFemale && (idCol == 'F' || idCol == 'FEMALE')) ||
(isMale && (idCol == 'M' || idCol == 'MALE'))) {
details.sEGMENTVALUEDSP = opt.vALUECOLUMNNAME;
details.vARCHAR2VALUE = opt.iDCOLUMNNAME;
matched = true;
break;
}
}
// Priority 2: Match value
if (!matched) {
for (var opt in eServices) {
String valCol = (opt.vALUECOLUMNNAME ?? '').trim().toLowerCase();
if (isFemale && (valCol.contains('female') || valCol.contains('أنثى'))) {
details.sEGMENTVALUEDSP = opt.vALUECOLUMNNAME;
details.vARCHAR2VALUE = opt.iDCOLUMNNAME;
matched = true;
break;
} else if (isMale && !valCol.contains('female') && (valCol.contains('male') || valCol.contains('ذكر'))) {
details.sEGMENTVALUEDSP = opt.vALUECOLUMNNAME;
details.vARCHAR2VALUE = opt.iDCOLUMNNAME;
matched = true;
break;
}
}
}
}
if (!matched) {
details.sEGMENTVALUEDSP = isFemale ? LocaleKeys.genderFemale.tr() : LocaleKeys.genderMale.tr();
details.vARCHAR2VALUE = isFemale ? 'F' : 'M';
}
markPrefilled();
}
// 10. Title
else if (colName.contains('TITLE') || prompt.contains('title') || prompt.contains('اللقب')) {
String g = (patient.gender ?? '').trim().toUpperCase();
bool isFemale = g == 'F' || g.startsWith('FEM');
bool isMale = g == 'M' || g.startsWith('MAL') || (!isFemale && g.isNotEmpty);
if (objValues != null && objValues.isNotEmpty) {
for (var opt in objValues) {
String code = (opt.cODE ?? '').trim().toUpperCase();
String meaning = (opt.mEANING ?? '').trim().toLowerCase();
if (isFemale && (code == 'MS' || code == 'MRS' || code == 'MISS' || meaning.contains('ms') || meaning.contains('mrs') || meaning.contains('miss') || meaning.contains('سيدة') || meaning.contains('آنسة'))) {
details.sEGMENTVALUEDSP = opt.mEANING;
details.vARCHAR2VALUE = opt.cODE;
markPrefilled();
break;
} else if (isMale && (code == 'MR' || (!meaning.contains('mrs') && !meaning.contains('سيدة') && (meaning.startsWith('mr') || meaning.contains('mr.') || meaning.contains('سيد'))))) {
details.sEGMENTVALUEDSP = opt.mEANING;
details.vARCHAR2VALUE = opt.cODE;
markPrefilled();
break;
}
}
} else if (eServices != null && eServices.isNotEmpty) {
for (var opt in eServices) {
String code = (opt.iDCOLUMNNAME ?? '').trim().toUpperCase();
String meaning = (opt.vALUECOLUMNNAME ?? '').trim().toLowerCase();
if (isFemale && (code == 'MS' || code == 'MRS' || code == 'MISS' || meaning.contains('ms') || meaning.contains('mrs') || meaning.contains('miss') || meaning.contains('سيدة') || meaning.contains('آنسة'))) {
details.sEGMENTVALUEDSP = opt.vALUECOLUMNNAME;
details.vARCHAR2VALUE = opt.iDCOLUMNNAME;
markPrefilled();
break;
} else if (isMale && (code == 'MR' || (!meaning.contains('mrs') && !meaning.contains('سيدة') && (meaning.startsWith('mr') || meaning.contains('mr.') || meaning.contains('سيد'))))) {
details.sEGMENTVALUEDSP = opt.vALUECOLUMNNAME;
details.vARCHAR2VALUE = opt.iDCOLUMNNAME;
markPrefilled();
break;
}
}
}
}
// 11. Contact Type / Relationship
else if (colName.contains('CONTACT_TYPE') ||
colName.contains('RELATIONSHIP') ||
prompt.contains('relationship') ||
prompt.contains('قرابة')) {
String g = (patient.gender ?? '').trim().toUpperCase();
bool isFemale = g == 'F' || g.startsWith('FEM');
bool isMale = g == 'M' || g.startsWith('MAL') || (!isFemale && g.isNotEmpty);
if (objValues != null && objValues.isNotEmpty) {
for (var opt in objValues) {
String code = (opt.cODE ?? '').trim().toUpperCase();
String meaning = (opt.mEANING ?? '').trim().toLowerCase();
if (isFemale && (code == 'DAUGHTER' || meaning.contains('daughter') || meaning.contains('ابنة') || meaning.contains('بنت'))) {
details.sEGMENTVALUEDSP = opt.mEANING;
details.vARCHAR2VALUE = opt.cODE;
markPrefilled();
break;
} else if (isMale && (code == 'SON' || (!meaning.contains('daughter') && (meaning.contains('son') || meaning.contains('ابن') || meaning.contains('ولد'))))) {
details.sEGMENTVALUEDSP = opt.mEANING;
details.vARCHAR2VALUE = opt.cODE;
markPrefilled();
break;
}
}
} else if (eServices != null && eServices.isNotEmpty) {
for (var opt in eServices) {
String code = (opt.iDCOLUMNNAME ?? '').trim().toUpperCase();
String meaning = (opt.vALUECOLUMNNAME ?? '').trim().toLowerCase();
if (isFemale && (code == 'DAUGHTER' || meaning.contains('daughter') || meaning.contains('ابنة') || meaning.contains('بنت'))) {
details.sEGMENTVALUEDSP = opt.vALUECOLUMNNAME;
details.vARCHAR2VALUE = opt.iDCOLUMNNAME;
markPrefilled();
break;
} else if (isMale && (code == 'SON' || (!meaning.contains('daughter') && (meaning.contains('son') || meaning.contains('ابن') || meaning.contains('ولد'))))) {
details.sEGMENTVALUEDSP = opt.vALUECOLUMNNAME;
details.vARCHAR2VALUE = opt.iDCOLUMNNAME;
markPrefilled();
break;
}
}
}
}
// 12. Religion
else if (colName.contains('RELIGION') || prompt.contains('religion') || prompt.contains('الديانة')) {
if (eServices != null && eServices.isNotEmpty) {
for (var opt in eServices) {
String valCol = (opt.vALUECOLUMNNAME ?? '').toLowerCase();
if (valCol.contains('islam') || valCol.contains('إسلام') || valCol.contains('مسلم')) {
details.sEGMENTVALUEDSP = opt.vALUECOLUMNNAME;
details.vARCHAR2VALUE = opt.iDCOLUMNNAME;
break;
}
}
} else {
details.sEGMENTVALUEDSP = LocaleKeys.religionIslam.tr();
details.vARCHAR2VALUE = 'ISLAM';
}
}
// 13. Sponsor & Dependent Sponsor Detail
else if (colName.contains('SPONSOR') || prompt.contains('sponsor') || prompt.contains('كفيل')) {
bool isSponsorDetail = prompt.contains('sponsor detail') ||
prompt.contains('detail') ||
prompt.contains('تفاصيل') ||
colName.contains('SPONSOR_DETAIL') ||
colName.contains('DEP_SPONSOR') ||
colName.contains('DETAIL');
if (isSponsorDetail) {
// Dependent Sponsor Detail: do NOT fill with name, default select first option from dropdown
if (eServices != null && eServices.isNotEmpty) {
details.sEGMENTVALUEDSP = eServices.first.vALUECOLUMNNAME;
details.vARCHAR2VALUE = eServices.first.iDCOLUMNNAME;
} else if (objValues != null && objValues.isNotEmpty) {
details.sEGMENTVALUEDSP = objValues.first.mEANING;
details.vARCHAR2VALUE = objValues.first.cODE;
}
} else {
// Sponsor field: also default select first option from dropdown
if (eServices != null && eServices.isNotEmpty) {
details.sEGMENTVALUEDSP = eServices.first.vALUECOLUMNNAME;
details.vARCHAR2VALUE = eServices.first.iDCOLUMNNAME;
} else if (objValues != null && objValues.isNotEmpty) {
details.sEGMENTVALUEDSP = objValues.first.mEANING;
details.vARCHAR2VALUE = objValues.first.cODE;
} else {
var emp = AppState().memberInformationList;
String empName = isAr
? (emp?.eMPLOYEENAMEAr?.isNotEmpty == true ? emp!.eMPLOYEENAMEAr! : (emp?.eMPLOYEENAME ?? ''))
: (emp?.eMPLOYEENAMEEn?.isNotEmpty == true ? emp!.eMPLOYEENAMEEn! : (emp?.eMPLOYEENAME ?? ''));
if (empName.isNotEmpty) {
details.sEGMENTVALUEDSP = empName;
details.vARCHAR2VALUE = empName;
_syncController(colKey, empName);
}
}
}
}
// 14. Ticket / Education / Medical Insurance Eligibility
else if (prompt.contains('ticket') || colName.contains('TICKET') ||
prompt.contains('education') || colName.contains('EDU') ||
prompt.contains('insurance') || colName.contains('MED')) {
if (eServices != null && eServices.isNotEmpty) {
for (var opt in eServices) {
String valCol = (opt.vALUECOLUMNNAME ?? '').toLowerCase();
String idCol = (opt.iDCOLUMNNAME ?? '').toUpperCase();
if (idCol == 'Y' || valCol.contains('eligible') || valCol.contains('مؤهل') || valCol.contains('نعم') || valCol.contains('yes')) {
details.sEGMENTVALUEDSP = opt.vALUECOLUMNNAME;
details.vARCHAR2VALUE = opt.iDCOLUMNNAME;
break;
}
}
}
}
// 15. Relationship Start Date
else if (prompt.contains('start') || colName.contains('DATE_START') || colName.contains('START_DATE')) {
String startVal = ymdDob.isNotEmpty ? ymdDob : DateFormat('yyyy-MM-dd').format(DateTime.now());
details.sEGMENTVALUEDSP = startVal;
details.dATEVALUE = startVal;
details.vARCHAR2VALUE = startVal;
}
}
_contactColsStructureList.forEach(matchAndFill);
_basicDetDffStructureList.forEach(matchAndFill);
_contactDffStructureList.forEach(matchAndFill);
}
// --- Dynamic Date Selector ---
Future<DateTime?> _selectDate(BuildContext context, [DateTime? initial]) async {
DateTime time = initial ?? DateTime.now();
if (!Platform.isIOS) {
DateTime tempDate = time;
bool? confirmed = await showCupertinoModalPopup<bool>(
context: context,
builder: (BuildContext cxt) => Container(
height: 260,
color: Colors.white,
child: Column(
children: [
Container(
height: 44,
color: Colors.grey.shade100,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
CupertinoButton(
padding: EdgeInsets.zero,
child: LocaleKeys.cancel.tr().toText14(color: Colors.grey),
onPressed: () => Navigator.pop(cxt, false),
),
CupertinoButton(
padding: EdgeInsets.zero,
child: LocaleKeys.done
.tr()
.toText14(isBold: true, color: const Color(0xFF168A89)),
onPressed: () => Navigator.pop(cxt, true),
),
],
),
),
Expanded(
child: CupertinoDatePicker(
backgroundColor: Colors.white,
mode: CupertinoDatePickerMode.date,
initialDateTime: time,
minimumYear: 1920,
maximumYear: DateTime.now().year + 10,
onDateTimeChanged: (DateTime value) {
tempDate = value;
},
),
),
],
),
),
);
if (confirmed == true) {
return tempDate;
}
return null;
} else {
DateTime? picked = await showDatePicker(
context: context,
initialDate: time,
firstDate: DateTime(1920, 1),
lastDate: DateTime.now().add(const Duration(days: 365 * 10)),
initialEntryMode: DatePickerEntryMode.calendarOnly,
);
return picked;
}
}
// --- Helper to build text fields styled specifically for Add Dependent Screen ---
Widget _buildFigmaTextField(
String labelText,
String hintText, {
TextEditingController? controller,
VoidCallback? onTap,
IconData? suffixIconData,
bool isEnable = true,
bool isReadOnly = false,
bool isPopup = false,
int? lines = 1,
TextInputAction? inputAction,
void Function(String)? onChange,
bool isInputTypeNum = false,
int? maxLength,
List<TextInputFormatter>? inputFormatters,
Color? backgroundColor,
Color? borderColor,
double? borderWidth,
Color? labelColor,
Color? textColor,
Color? suffixIconColor,
}) {
return DynamicTextFieldWidget(
labelText,
hintText,
controller: controller,
onTap: onTap,
suffixIconData: suffixIconData,
isEnable: isEnable,
isReadOnly: isReadOnly,
isPopup: isPopup,
lines: lines,
inputAction: inputAction,
onChange: onChange,
isInputTypeNum: isInputTypeNum,
maxLength: maxLength,
inputFormatters: inputFormatters,
backgroundColor: backgroundColor ?? Colors.white,
borderColor: borderColor,
borderWidth: borderWidth,
labelColor: labelColor,
textColor: textColor,
suffixIconColor: suffixIconColor,
borderRadius: BorderRadius.circular(14),
constraints: const BoxConstraints(minHeight: 66),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
boxShadow: const <BoxShadow>[
BoxShadow(
color: Color.fromRGBO(0, 0, 0, 0.0314),
offset: Offset(0, 2),
blurRadius: 5,
spreadRadius: 0,
),
],
);
}
// --- Build Dynamic Fields for Contact Columns ---
Widget _buildDynamicColField(GetContactColsStructureList model, int index) {
if (model.dISPLAYFLAG == "N") {
return const SizedBox.shrink();
}
bool isRequired = model.rEQUIREDFLAG == "Y";
String rawPrompt = (model.sEGMENTPROMPT ?? "").trim();
String label = rawPrompt.endsWith("*")
? rawPrompt
: (isRequired ? "$rawPrompt*" : rawPrompt);
model.getContactDetailsList ??= GetContactDetailsList();
String colName = (model.aPPLICATIONCOLUMNNAME ?? "").toUpperCase().trim();
String prompt = rawPrompt.toLowerCase();
String colKey = model.aPPLICATIONCOLUMNNAME ?? "COL_$index";
if (model.dATATYPE == "VARCHAR2") {
if (model.objectValuesList != null && model.objectValuesList!.isNotEmpty) {
bool isSponsorRelated = colName.contains("SPONSOR") || prompt.contains("sponsor") || prompt.contains("كفيل");
bool valueInOptions = model.objectValuesList!.any((opt) => opt.mEANING == model.getContactDetailsList!.sEGMENTVALUEDSP);
if (isSponsorRelated && (!valueInOptions || model.getContactDetailsList!.sEGMENTVALUEDSP == null || model.getContactDetailsList!.sEGMENTVALUEDSP!.isEmpty)) {
model.getContactDetailsList!.sEGMENTVALUEDSP = model.objectValuesList!.first.mEANING;
model.getContactDetailsList!.vARCHAR2VALUE = model.objectValuesList!.first.cODE;
}
String displayVal = model.getContactDetailsList!.sEGMENTVALUEDSP ?? "";
bool isFieldDisabled = _isFieldPrefilledFromPatientInfo(colName, prompt, displayVal);
bool hasSelectedValue = displayVal.trim().isNotEmpty;
bool showRedBorder = isRequired && !isFieldDisabled && !hasSelectedValue;
Color effectiveBorderColor = showRedBorder
? const Color(0xFFED1C2B)
: const Color(0xFFE6E9E8);
double effectiveBorderWidth = showRedBorder ? 1.4 : 1.0;
Color effectiveLabelColor = isFieldDisabled ? const Color(0xFF8E95A2) : const Color(0xff2B353E);
Color effectiveTextColor = isFieldDisabled
? const Color(0xFF8E95A2)
: (displayVal.isNotEmpty ? const Color(0xff2B353E) : const Color(0xFF94A3B8));
Color effectiveSuffixColor = isFieldDisabled ? const Color(0xFF8E95A2) : const Color(0xFF64748B);
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: PopupMenuButton<int>(
enabled: !isFieldDisabled,
child: _buildFigmaTextField(
label,
displayVal.isNotEmpty ? displayVal : LocaleKeys.select.tr(),
isEnable: false,
isPopup: true,
backgroundColor: Colors.white,
borderColor: effectiveBorderColor,
borderWidth: effectiveBorderWidth,
labelColor: effectiveLabelColor,
textColor: effectiveTextColor,
suffixIconColor: effectiveSuffixColor,
),
itemBuilder: (_) => isFieldDisabled
? <PopupMenuItem<int>>[]
: <PopupMenuItem<int>>[
for (int i = 0; i < model.objectValuesList!.length; i++)
PopupMenuItem<int>(
value: i,
child: (model.objectValuesList![i].mEANING ?? "").toText14(),
),
],
onSelected: isFieldDisabled
? null
: (int selectedIdx) {
setState(() {
model.getContactDetailsList!.sEGMENTVALUEDSP = model.objectValuesList![selectedIdx].mEANING;
model.getContactDetailsList!.vARCHAR2VALUE = model.objectValuesList![selectedIdx].cODE;
});
},
),
);
} else {
var controller = _getControllerForField(colKey, model.getContactDetailsList!.sEGMENTVALUEDSP);
bool isFieldDisabled = _isFieldPrefilledFromPatientInfo(colName, prompt, controller.text);
bool hasValue = controller.text.trim().isNotEmpty || (model.getContactDetailsList?.sEGMENTVALUEDSP?.trim().isNotEmpty ?? false);
bool showRedBorder = isRequired && !isFieldDisabled && !hasValue;
Color effectiveBorderColor = showRedBorder
? const Color(0xFFED1C2B)
: const Color(0xFFE6E9E8);
double effectiveBorderWidth = showRedBorder ? 1.4 : 1.0;
Color effectiveLabelColor = isFieldDisabled ? const Color(0xFF8E95A2) : const Color(0xff2B353E);
Color effectiveTextColor = isFieldDisabled ? const Color(0xFF8E95A2) : const Color(0xff2B353E);
Color effectiveSuffixColor = isFieldDisabled ? const Color(0xFF8E95A2) : const Color(0xFF64748B);
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: _buildFigmaTextField(
label,
LocaleKeys.enterFieldPlaceholder.tr(args: [label]),
controller: controller,
isEnable: !isFieldDisabled,
isReadOnly: isFieldDisabled,
backgroundColor: Colors.white,
borderColor: effectiveBorderColor,
borderWidth: effectiveBorderWidth,
labelColor: effectiveLabelColor,
textColor: effectiveTextColor,
suffixIconData: _getSuffixIconForField(colName, prompt, "VARCHAR2"),
suffixIconColor: effectiveSuffixColor,
onChange: isFieldDisabled
? null
: (String text) {
model.getContactDetailsList!.sEGMENTVALUEDSP = text;
model.getContactDetailsList!.vARCHAR2VALUE = text;
},
),
);
}
} else if (model.dATATYPE == "NUMBER") {
var controller = _getControllerForField(colKey, model.getContactDetailsList!.sEGMENTVALUEDSP);
bool isFieldDisabled = _isFieldPrefilledFromPatientInfo(colName, prompt, controller.text);
bool hasValue = controller.text.trim().isNotEmpty || (model.getContactDetailsList?.sEGMENTVALUEDSP?.trim().isNotEmpty ?? false);
bool showRedBorder = isRequired && !isFieldDisabled && !hasValue;
Color effectiveBorderColor = showRedBorder
? const Color(0xFFED1C2B)
: const Color(0xFFE6E9E8);
double effectiveBorderWidth = showRedBorder ? 1.4 : 1.0;
Color effectiveLabelColor = isFieldDisabled ? const Color(0xFF8E95A2) : const Color(0xff2B353E);
Color effectiveTextColor = isFieldDisabled ? const Color(0xFF8E95A2) : const Color(0xff2B353E);
Color effectiveSuffixColor = isFieldDisabled ? const Color(0xFF8E95A2) : const Color(0xFF64748B);
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: _buildFigmaTextField(
label,
LocaleKeys.enterFieldPlaceholder.tr(args: [label]),
controller: controller,
isEnable: !isFieldDisabled,
isReadOnly: isFieldDisabled,
isInputTypeNum: true,
backgroundColor: Colors.white,
borderColor: effectiveBorderColor,
borderWidth: effectiveBorderWidth,
labelColor: effectiveLabelColor,
textColor: effectiveTextColor,
suffixIconData: _getSuffixIconForField(colName, prompt, "NUMBER"),
suffixIconColor: effectiveSuffixColor,
onChange: isFieldDisabled
? null
: (String text) {
model.getContactDetailsList!.sEGMENTVALUEDSP = text;
model.getContactDetailsList!.vARCHAR2VALUE = text;
},
),
);
} else if (model.dATATYPE == "DATE") {
String displayVal = model.getContactDetailsList!.sEGMENTVALUEDSP ?? "";
bool isFieldDisabled = _isFieldPrefilledFromPatientInfo(colName, prompt, displayVal);
bool hasSelectedValue = displayVal.trim().isNotEmpty;
bool showRedBorder = isRequired && !isFieldDisabled && !hasSelectedValue;
Color effectiveBorderColor = showRedBorder
? const Color(0xFFED1C2B)
: const Color(0xFFE6E9E8);
double effectiveBorderWidth = showRedBorder ? 1.4 : 1.0;
Color effectiveLabelColor = isFieldDisabled ? const Color(0xFF8E95A2) : const Color(0xff2B353E);
Color effectiveTextColor = isFieldDisabled
? const Color(0xFF8E95A2)
: (displayVal.isNotEmpty ? const Color(0xff2B353E) : const Color(0xFF94A3B8));
Color effectiveSuffixColor = isFieldDisabled ? const Color(0xFF8E95A2) : const Color(0xFF64748B);
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: _buildFigmaTextField(
label,
displayVal.isNotEmpty ? displayVal : LocaleKeys.dateFormatPlaceholder.tr(),
suffixIconData: Icons.calendar_today_outlined,
isEnable: !isFieldDisabled,
isReadOnly: true,
backgroundColor: Colors.white,
borderColor: effectiveBorderColor,
borderWidth: effectiveBorderWidth,
labelColor: effectiveLabelColor,
textColor: effectiveTextColor,
suffixIconColor: effectiveSuffixColor,
onTap: isFieldDisabled
? null
: () async {
DateTime initial = DateTime.now();
if (displayVal.isNotEmpty) {
try {
initial = DateFormat('yyyy-MM-dd').parse(displayVal);
} catch (_) {
try {
initial = DateFormat('dd/MM/yyyy').parse(displayVal);
} catch (_) {}
}
}
DateTime? picked = await _selectDate(context, initial);
if (picked != null) {
String formatted = DateFormat('yyyy-MM-dd', "en_US").format(picked);
setState(() {
model.getContactDetailsList!.sEGMENTVALUEDSP = formatted;
model.getContactDetailsList!.dATEVALUE = formatted;
model.getContactDetailsList!.vARCHAR2VALUE = formatted;
});
}
},
),
);
}
return const SizedBox.shrink();
}
// --- Build Dynamic Fields for Basic Det DFF ---
Widget _buildDynamicBasicDffField(GetBasicDetDffStructureList model, int index) {
if (model.dISPLAYFLAG == "N") {
return const SizedBox.shrink();
}
bool isRequired = model.rEQUIREDFLAG == "Y";
String rawPrompt = (model.sEGMENTPROMPT ?? "").trim();
String label = rawPrompt.endsWith("*")
? rawPrompt
: (isRequired ? "$rawPrompt*" : rawPrompt);
model.getContactDetailsList ??= GetContactDetailsList();
String colName = (model.aPPLICATIONCOLUMNNAME ?? "").toUpperCase().trim();
String prompt = rawPrompt.toLowerCase();
String colKey = model.aPPLICATIONCOLUMNNAME ?? "BASIC_DFF_$index";
if (model.fORMATTYPE == "C") {
if (model.eSERVICESVS != null && model.eSERVICESVS!.isNotEmpty) {
bool isSponsorRelated = colName.contains("SPONSOR") || prompt.contains("sponsor") || prompt.contains("كفيل");
bool valueInOptions = model.eSERVICESVS!.any((opt) => opt.vALUECOLUMNNAME == model.getContactDetailsList!.sEGMENTVALUEDSP);
if (isSponsorRelated && (!valueInOptions || model.getContactDetailsList!.sEGMENTVALUEDSP == null || model.getContactDetailsList!.sEGMENTVALUEDSP!.isEmpty)) {
model.getContactDetailsList!.sEGMENTVALUEDSP = model.eSERVICESVS!.first.vALUECOLUMNNAME;
model.getContactDetailsList!.vARCHAR2VALUE = model.eSERVICESVS!.first.iDCOLUMNNAME;
}
String displayVal = model.getContactDetailsList!.sEGMENTVALUEDSP ?? "";
bool isFieldDisabled = _isFieldPrefilledFromPatientInfo(colName, prompt, displayVal);
bool hasSelectedValue = displayVal.trim().isNotEmpty;
bool showRedBorder = isRequired && !isFieldDisabled && !hasSelectedValue;
Color effectiveBorderColor = showRedBorder
? const Color(0xFFED1C2B)
: const Color(0xFFE6E9E8);
double effectiveBorderWidth = showRedBorder ? 1.4 : 1.0;
Color effectiveLabelColor = isFieldDisabled ? const Color(0xFF8E95A2) : const Color(0xff2B353E);
Color effectiveTextColor = isFieldDisabled
? const Color(0xFF8E95A2)
: (displayVal.isNotEmpty ? const Color(0xff2B353E) : const Color(0xFF94A3B8));
Color effectiveSuffixColor = isFieldDisabled ? const Color(0xFF8E95A2) : const Color(0xFF64748B);
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: PopupMenuButton<int>(
enabled: !isFieldDisabled,
child: _buildFigmaTextField(
label,
displayVal.isNotEmpty ? displayVal : LocaleKeys.select.tr(),
isEnable: false,
isPopup: true,
backgroundColor: Colors.white,
borderColor: effectiveBorderColor,
borderWidth: effectiveBorderWidth,
labelColor: effectiveLabelColor,
textColor: effectiveTextColor,
suffixIconColor: effectiveSuffixColor,
),
itemBuilder: (_) => isFieldDisabled
? <PopupMenuItem<int>>[]
: <PopupMenuItem<int>>[
for (int i = 0; i < model.eSERVICESVS!.length; i++)
PopupMenuItem<int>(
value: i,
child: (model.eSERVICESVS![i].vALUECOLUMNNAME ?? "").toText14(),
),
],
onSelected: isFieldDisabled
? null
: (int selectedIdx) {
setState(() {
model.getContactDetailsList!.sEGMENTVALUEDSP = model.eSERVICESVS![selectedIdx].vALUECOLUMNNAME;
model.getContactDetailsList!.vARCHAR2VALUE = model.eSERVICESVS![selectedIdx].iDCOLUMNNAME;
});
},
),
);
} else {
var controller = _getControllerForField(colKey, model.getContactDetailsList!.sEGMENTVALUEDSP);
bool isFieldDisabled = _isFieldPrefilledFromPatientInfo(colName, prompt, controller.text);
bool hasValue = controller.text.trim().isNotEmpty || (model.getContactDetailsList?.sEGMENTVALUEDSP?.trim().isNotEmpty ?? false);
bool showRedBorder = isRequired && !isFieldDisabled && !hasValue;
Color effectiveBorderColor = showRedBorder
? const Color(0xFFED1C2B)
: const Color(0xFFE6E9E8);
double effectiveBorderWidth = showRedBorder ? 1.4 : 1.0;
Color effectiveLabelColor = isFieldDisabled ? const Color(0xFF8E95A2) : const Color(0xff2B353E);
Color effectiveTextColor = isFieldDisabled ? const Color(0xFF8E95A2) : const Color(0xff2B353E);
Color effectiveSuffixColor = isFieldDisabled ? const Color(0xFF8E95A2) : const Color(0xFF64748B);
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: _buildFigmaTextField(
label,
LocaleKeys.enterFieldPlaceholder.tr(args: [label]),
controller: controller,
isEnable: !isFieldDisabled,
isReadOnly: isFieldDisabled,
backgroundColor: Colors.white,
borderColor: effectiveBorderColor,
borderWidth: effectiveBorderWidth,
labelColor: effectiveLabelColor,
textColor: effectiveTextColor,
suffixIconData: _getSuffixIconForField(colName, prompt, "VARCHAR2"),
suffixIconColor: effectiveSuffixColor,
onChange: isFieldDisabled
? null
: (String text) {
model.getContactDetailsList!.sEGMENTVALUEDSP = text;
model.getContactDetailsList!.vARCHAR2VALUE = text;
},
),
);
}
} else if (model.fORMATTYPE == "N") {
var controller = _getControllerForField(colKey, model.getContactDetailsList!.sEGMENTVALUEDSP);
bool isFieldDisabled = _isFieldPrefilledFromPatientInfo(colName, prompt, controller.text);
bool hasValue = controller.text.trim().isNotEmpty || (model.getContactDetailsList?.sEGMENTVALUEDSP?.trim().isNotEmpty ?? false);
bool showRedBorder = isRequired && !isFieldDisabled && !hasValue;
Color effectiveBorderColor = showRedBorder
? const Color(0xFFED1C2B)
: const Color(0xFFE6E9E8);
double effectiveBorderWidth = showRedBorder ? 1.4 : 1.0;
Color effectiveLabelColor = isFieldDisabled ? const Color(0xFF8E95A2) : const Color(0xff2B353E);
Color effectiveTextColor = isFieldDisabled ? const Color(0xFF8E95A2) : const Color(0xff2B353E);
Color effectiveSuffixColor = isFieldDisabled ? const Color(0xFF8E95A2) : const Color(0xFF64748B);
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: _buildFigmaTextField(
label,
LocaleKeys.enterFieldPlaceholder.tr(args: [label]),
controller: controller,
isEnable: !isFieldDisabled,
isReadOnly: isFieldDisabled,
isInputTypeNum: true,
backgroundColor: Colors.white,
borderColor: effectiveBorderColor,
borderWidth: effectiveBorderWidth,
labelColor: effectiveLabelColor,
textColor: effectiveTextColor,
suffixIconData: _getSuffixIconForField(colName, prompt, "NUMBER"),
suffixIconColor: effectiveSuffixColor,
onChange: isFieldDisabled
? null
: (String text) {
model.getContactDetailsList!.sEGMENTVALUEDSP = text;
model.getContactDetailsList!.vARCHAR2VALUE = text;
},
),
);
} else if (model.fORMATTYPE == "X") {
String displayVal = model.getContactDetailsList!.sEGMENTVALUEDSP ?? "";
bool isFieldDisabled = _isFieldPrefilledFromPatientInfo(colName, prompt, displayVal);
bool hasSelectedValue = displayVal.trim().isNotEmpty;
bool showRedBorder = isRequired && !isFieldDisabled && !hasSelectedValue;
Color effectiveBorderColor = showRedBorder
? const Color(0xFFED1C2B)
: const Color(0xFFE6E9E8);
double effectiveBorderWidth = showRedBorder ? 1.4 : 1.0;
Color effectiveLabelColor = isFieldDisabled ? const Color(0xFF8E95A2) : const Color(0xff2B353E);
Color effectiveTextColor = isFieldDisabled
? const Color(0xFF8E95A2)
: (displayVal.isNotEmpty ? const Color(0xff2B353E) : const Color(0xFF94A3B8));
Color effectiveSuffixColor = isFieldDisabled ? const Color(0xFF8E95A2) : const Color(0xFF64748B);
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: _buildFigmaTextField(
label,
displayVal.isNotEmpty ? displayVal : LocaleKeys.dateFormatPlaceholder.tr(),
suffixIconData: Icons.calendar_today_outlined,
isEnable: !isFieldDisabled,
isReadOnly: true,
backgroundColor: Colors.white,
borderColor: effectiveBorderColor,
borderWidth: effectiveBorderWidth,
labelColor: effectiveLabelColor,
textColor: effectiveTextColor,
suffixIconColor: effectiveSuffixColor,
onTap: isFieldDisabled
? null
: () async {
DateTime initial = DateTime.now();
if (displayVal.isNotEmpty) {
try {
initial = DateFormat('yyyy-MM-dd').parse(displayVal);
} catch (_) {
try {
initial = DateFormat('dd/MM/yyyy').parse(displayVal);
} catch (_) {}
}
}
DateTime? picked = await _selectDate(context, initial);
if (picked != null) {
String formatted = DateFormat('yyyy-MM-dd', "en_US").format(picked);
setState(() {
model.getContactDetailsList!.sEGMENTVALUEDSP = formatted;
model.getContactDetailsList!.dATEVALUE = formatted;
model.getContactDetailsList!.vARCHAR2VALUE = formatted;
});
}
},
),
);
}
return const SizedBox.shrink();
}
// --- Build Dynamic Fields for Contact DFF ---
Widget _buildDynamicContactDffField(GetContactDffStructureList model, int index) {
if (model.dISPLAYFLAG == "N") {
return const SizedBox.shrink();
}
bool isRequired = model.rEQUIREDFLAG == "Y";
String rawPrompt = (model.sEGMENTPROMPT ?? "").trim();
String label = rawPrompt.endsWith("*")
? rawPrompt
: (isRequired ? "$rawPrompt*" : rawPrompt);
model.getContactDetailsList ??= GetContactDetailsList();
String colName = (model.aPPLICATIONCOLUMNNAME ?? "").toUpperCase().trim();
String prompt = rawPrompt.toLowerCase();
String colKey = model.aPPLICATIONCOLUMNNAME ?? "CONTACT_DFF_$index";
if (model.fORMATTYPE == "C") {
if (model.eSERVICESVS != null && model.eSERVICESVS!.isNotEmpty) {
bool isSponsorRelated = colName.contains("SPONSOR") || prompt.contains("sponsor") || prompt.contains("كفيل");
bool valueInOptions = model.eSERVICESVS!.any((opt) => opt.vALUECOLUMNNAME == model.getContactDetailsList!.sEGMENTVALUEDSP);
if (isSponsorRelated && (!valueInOptions || model.getContactDetailsList!.sEGMENTVALUEDSP == null || model.getContactDetailsList!.sEGMENTVALUEDSP!.isEmpty)) {
model.getContactDetailsList!.sEGMENTVALUEDSP = model.eSERVICESVS!.first.vALUECOLUMNNAME;
model.getContactDetailsList!.vARCHAR2VALUE = model.eSERVICESVS!.first.iDCOLUMNNAME;
}
String displayVal = model.getContactDetailsList!.sEGMENTVALUEDSP ?? "";
bool isFieldDisabled = _isFieldPrefilledFromPatientInfo(colName, prompt, displayVal);
bool hasSelectedValue = displayVal.trim().isNotEmpty;
bool showRedBorder = isRequired && !isFieldDisabled && !hasSelectedValue;
Color effectiveBorderColor = showRedBorder
? const Color(0xFFED1C2B)
: const Color(0xFFE6E9E8);
double effectiveBorderWidth = showRedBorder ? 1.4 : 1.0;
Color effectiveLabelColor = isFieldDisabled ? const Color(0xFF8E95A2) : const Color(0xff2B353E);
Color effectiveTextColor = isFieldDisabled
? const Color(0xFF8E95A2)
: (displayVal.isNotEmpty ? const Color(0xff2B353E) : const Color(0xFF94A3B8));
Color effectiveSuffixColor = isFieldDisabled ? const Color(0xFF8E95A2) : const Color(0xFF64748B);
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: PopupMenuButton<int>(
enabled: !isFieldDisabled,
child: _buildFigmaTextField(
label,
displayVal.isNotEmpty ? displayVal : LocaleKeys.select.tr(),
isEnable: false,
isPopup: true,
backgroundColor: Colors.white,
borderColor: effectiveBorderColor,
borderWidth: effectiveBorderWidth,
labelColor: effectiveLabelColor,
textColor: effectiveTextColor,
suffixIconColor: effectiveSuffixColor,
),
itemBuilder: (_) => isFieldDisabled
? <PopupMenuItem<int>>[]
: <PopupMenuItem<int>>[
for (int i = 0; i < model.eSERVICESVS!.length; i++)
PopupMenuItem<int>(
value: i,
child: (model.eSERVICESVS![i].vALUECOLUMNNAME ?? "").toText14(),
),
],
onSelected: isFieldDisabled
? null
: (int selectedIdx) {
setState(() {
model.getContactDetailsList!.sEGMENTVALUEDSP = model.eSERVICESVS![selectedIdx].vALUECOLUMNNAME;
model.getContactDetailsList!.vARCHAR2VALUE = model.eSERVICESVS![selectedIdx].iDCOLUMNNAME;
});
},
),
);
} else {
var controller = _getControllerForField(colKey, model.getContactDetailsList!.sEGMENTVALUEDSP);
bool isFieldDisabled = _isFieldPrefilledFromPatientInfo(colName, prompt, controller.text);
bool hasValue = controller.text.trim().isNotEmpty || (model.getContactDetailsList?.sEGMENTVALUEDSP?.trim().isNotEmpty ?? false);
bool showRedBorder = isRequired && !isFieldDisabled && !hasValue;
Color effectiveBorderColor = showRedBorder
? const Color(0xFFED1C2B)
: const Color(0xFFE6E9E8);
double effectiveBorderWidth = showRedBorder ? 1.4 : 1.0;
Color effectiveLabelColor = isFieldDisabled ? const Color(0xFF8E95A2) : const Color(0xff2B353E);
Color effectiveTextColor = isFieldDisabled ? const Color(0xFF8E95A2) : const Color(0xff2B353E);
Color effectiveSuffixColor = isFieldDisabled ? const Color(0xFF8E95A2) : const Color(0xFF64748B);
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: _buildFigmaTextField(
label,
LocaleKeys.enterFieldPlaceholder.tr(args: [label]),
controller: controller,
isEnable: !isFieldDisabled,
isReadOnly: isFieldDisabled,
backgroundColor: Colors.white,
borderColor: effectiveBorderColor,
borderWidth: effectiveBorderWidth,
labelColor: effectiveLabelColor,
textColor: effectiveTextColor,
suffixIconData: _getSuffixIconForField(colName, prompt, "VARCHAR2"),
suffixIconColor: effectiveSuffixColor,
onChange: isFieldDisabled
? null
: (String text) {
model.getContactDetailsList!.sEGMENTVALUEDSP = text;
model.getContactDetailsList!.vARCHAR2VALUE = text;
},
),
);
}
} else if (model.fORMATTYPE == "N") {
var controller = _getControllerForField(colKey, model.getContactDetailsList!.sEGMENTVALUEDSP);
bool isFieldDisabled = _isFieldPrefilledFromPatientInfo(colName, prompt, controller.text);
bool hasValue = controller.text.trim().isNotEmpty || (model.getContactDetailsList?.sEGMENTVALUEDSP?.trim().isNotEmpty ?? false);
bool showRedBorder = isRequired && !isFieldDisabled && !hasValue;
Color effectiveBorderColor = showRedBorder
? const Color(0xFFED1C2B)
: const Color(0xFFE6E9E8);
double effectiveBorderWidth = showRedBorder ? 1.4 : 1.0;
Color effectiveLabelColor = isFieldDisabled ? const Color(0xFF8E95A2) : const Color(0xff2B353E);
Color effectiveTextColor = isFieldDisabled ? const Color(0xFF8E95A2) : const Color(0xff2B353E);
Color effectiveSuffixColor = isFieldDisabled ? const Color(0xFF8E95A2) : const Color(0xFF64748B);
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: _buildFigmaTextField(
label,
LocaleKeys.enterFieldPlaceholder.tr(args: [label]),
controller: controller,
isEnable: !isFieldDisabled,
isReadOnly: isFieldDisabled,
isInputTypeNum: true,
backgroundColor: Colors.white,
borderColor: effectiveBorderColor,
borderWidth: effectiveBorderWidth,
labelColor: effectiveLabelColor,
textColor: effectiveTextColor,
suffixIconData: _getSuffixIconForField(colName, prompt, "NUMBER"),
suffixIconColor: effectiveSuffixColor,
onChange: isFieldDisabled
? null
: (String text) {
model.getContactDetailsList!.sEGMENTVALUEDSP = text;
model.getContactDetailsList!.vARCHAR2VALUE = text;
},
),
);
} else if (model.fORMATTYPE == "X") {
String displayVal = model.getContactDetailsList?.dATEVALUE ?? model.getContactDetailsList?.sEGMENTVALUEDSP ?? "";
bool isFieldDisabled = _isFieldPrefilledFromPatientInfo(colName, prompt, displayVal);
bool hasSelectedValue = displayVal.trim().isNotEmpty;
bool showRedBorder = isRequired && !isFieldDisabled && !hasSelectedValue;
Color effectiveBorderColor = showRedBorder
? const Color(0xFFED1C2B)
: const Color(0xFFE6E9E8);
double effectiveBorderWidth = showRedBorder ? 1.4 : 1.0;
Color effectiveLabelColor = isFieldDisabled ? const Color(0xFF8E95A2) : const Color(0xff2B353E);
Color effectiveTextColor = isFieldDisabled
? const Color(0xFF8E95A2)
: (displayVal.isNotEmpty ? const Color(0xff2B353E) : const Color(0xFF94A3B8));
Color effectiveSuffixColor = isFieldDisabled ? const Color(0xFF8E95A2) : const Color(0xFF64748B);
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: _buildFigmaTextField(
label,
displayVal.isNotEmpty ? displayVal : LocaleKeys.dateFormatPlaceholder.tr(),
suffixIconData: Icons.calendar_today_outlined,
isEnable: !isFieldDisabled,
isReadOnly: true,
backgroundColor: Colors.white,
borderColor: effectiveBorderColor,
borderWidth: effectiveBorderWidth,
labelColor: effectiveLabelColor,
textColor: effectiveTextColor,
suffixIconColor: effectiveSuffixColor,
onTap: isFieldDisabled
? null
: () async {
DateTime initial = DateTime.now();
if (displayVal.isNotEmpty) {
try {
initial = DateFormat('yyyy-MM-dd').parse(displayVal);
} catch (_) {
try {
initial = DateFormat('dd/MM/yyyy').parse(displayVal);
} catch (_) {}
}
}
DateTime? picked = await _selectDate(context, initial);
if (picked != null) {
String formatted = DateFormat('yyyy-MM-dd', "en_US").format(picked);
setState(() {
model.getContactDetailsList!.sEGMENTVALUEDSP = formatted;
model.getContactDetailsList!.dATEVALUE = formatted;
model.getContactDetailsList!.vARCHAR2VALUE = formatted;
});
}
},
),
);
}
return const SizedBox.shrink();
}
// --- Step 2 Validation & Next ---
void _onNextFromBasicInfo() {
for (var col in _contactColsStructureList) {
if (col.dISPLAYFLAG != "N" && col.rEQUIREDFLAG == "Y") {
String? val = col.getContactDetailsList?.sEGMENTVALUEDSP;
String? varch = col.getContactDetailsList?.vARCHAR2VALUE;
if ((val == null || val.trim().isEmpty) && (varch == null || varch.trim().isEmpty)) {
String name = col.sEGMENTPROMPT ?? LocaleKeys.requiredField.tr();
Utils.showToast(LocaleKeys.pleaseFillField.tr(args: [name]));
return;
}
}
}
for (var basic in _basicDetDffStructureList) {
if (basic.dISPLAYFLAG != "N" && basic.rEQUIREDFLAG == "Y") {
String? val = basic.getContactDetailsList?.sEGMENTVALUEDSP;
String? varch = basic.getContactDetailsList?.vARCHAR2VALUE;
if ((val == null || val.trim().isEmpty) && (varch == null || varch.trim().isEmpty)) {
String name = basic.sEGMENTPROMPT ?? LocaleKeys.requiredField.tr();
Utils.showToast(LocaleKeys.pleaseFillField.tr(args: [name]));
return;
}
}
}
setState(() {
_currentStep = 3; // Move to Step 3: Additional Info
});
}
// --- Step 3 Validation & Next ---
void _onNextFromAdditionalInfo() {
for (var dff in _contactDffStructureList) {
if (dff.dISPLAYFLAG != "N" && dff.rEQUIREDFLAG == "Y") {
String? val = dff.getContactDetailsList?.sEGMENTVALUEDSP;
String? varch = dff.getContactDetailsList?.vARCHAR2VALUE;
if ((val == null || val.trim().isEmpty) && (varch == null || varch.trim().isEmpty)) {
String name = dff.sEGMENTPROMPT ?? LocaleKeys.requiredField.tr();
Utils.showToast(LocaleKeys.pleaseFillField.tr(args: [name]));
return;
}
}
}
setState(() {
_currentStep = 4; // Move to Step 4: Submit / Review
});
}
// --- Step 4 Submit Transaction to EBS ---
void _onSubmitTransaction() async {
Utils.showLoading(context);
try {
List<Map<String, dynamic>> values1 = _basicDetDffStructureList.map((e) {
String? dateVal;
String? varChar;
int? numberVal;
if (e.fORMATTYPE == 'N') {
dateVal = null;
varChar = null;
numberVal = int.tryParse(e.getContactDetailsList?.sEGMENTVALUEDSP ?? "0");
} else if (e.fORMATTYPE == 'X') {
dateVal = e.getContactDetailsList?.sEGMENTVALUEDSP?.toString();
varChar = null;
numberVal = null;
} else if (e.eSERVICESVS != null && e.eSERVICESVS!.isNotEmpty) {
dateVal = null;
varChar = e.getContactDetailsList?.vARCHAR2VALUE;
numberVal = null;
} else {
dateVal = null;
varChar = e.getContactDetailsList?.sEGMENTVALUEDSP;
numberVal = null;
}
return ValidateEitTransactionModel(
dATEVALUE: dateVal,
nAME: e.aPPLICATIONCOLUMNNAME,
nUMBERVALUE: numberVal,
tRANSACTIONNUMBER: 1,
vARCHAR2VALUE: varChar,
).toJson();
}).toList();
List<Map<String, dynamic>> values2 = _contactDffStructureList.map((e) {
String? dateVal;
String? varChar;
int? numberVal;
if (e.fORMATTYPE == 'N') {
dateVal = null;
varChar = null;
numberVal = int.tryParse(e.getContactDetailsList?.sEGMENTVALUEDSP ?? "0");
} else if (e.fORMATTYPE == 'X') {
dateVal = e.getContactDetailsList?.dATEVALUE ?? e.getContactDetailsList?.sEGMENTVALUEDSP;
varChar = null;
numberVal = null;
} else if (e.eSERVICESVS != null && e.eSERVICESVS!.isNotEmpty) {
dateVal = null;
varChar = e.getContactDetailsList?.vARCHAR2VALUE;
numberVal = null;
} else {
dateVal = null;
numberVal = null;
varChar = e.getContactDetailsList?.sEGMENTVALUEDSP;
}
return ValidateEitTransactionModel(
dATEVALUE: dateVal,
nAME: e.aPPLICATIONCOLUMNNAME,
nUMBERVALUE: numberVal,
tRANSACTIONNUMBER: 1,
vARCHAR2VALUE: varChar,
).toJson();
}).toList();
List<Map<String, dynamic>> values3 = _contactColsStructureList.map((e) {
String? dateVal;
String? varChar1;
int? numberVal;
if (e.dATATYPE == 'VARCHAR2') {
dateVal = null;
numberVal = null;
if (e.objectValuesList != null && e.objectValuesList!.isNotEmpty) {
varChar1 = e.getContactDetailsList?.vARCHAR2VALUE ?? "";
} else {
varChar1 = e.getContactDetailsList?.sEGMENTVALUEDSP ?? "";
}
} else if (e.dATATYPE == 'DATE') {
dateVal = e.getContactDetailsList?.sEGMENTVALUEDSP?.toString();
varChar1 = null;
numberVal = null;
} else if (e.dATATYPE == 'NUMBER') {
dateVal = null;
varChar1 = null;
numberVal = int.tryParse(e.getContactDetailsList?.sEGMENTVALUEDSP ?? "0");
}
// Format National ID for Oracle EBS (X-XXXX-XXXX-X) e.g. 2-5446-0227-5
String colNameUpper = (e.aPPLICATIONCOLUMNNAME ?? '').toUpperCase().trim();
if (colNameUpper.contains('NATIONAL_IDENTIFIER') ||
colNameUpper.contains('CIVIL_ID') ||
colNameUpper.contains('IQAMA')) {
if (varChar1 != null && varChar1.isNotEmpty) {
String clean = varChar1.replaceAll('-', '').trim();
if (clean.length == 10) {
varChar1 = "${clean.substring(0, 1)}-${clean.substring(1, 5)}-${clean.substring(5, 9)}-${clean.substring(9, 10)}";
}
}
}
return ValidateEitTransactionModel(
dATEVALUE: dateVal,
nAME: e.aPPLICATIONCOLUMNNAME,
nUMBERVALUE: numberVal,
tRANSACTIONNUMBER: 1,
vARCHAR2VALUE: varChar1,
).toJson();
}).toList();
List<Map<String, dynamic>> valuesFinal = [...values1, ...values2, ...values3];
SubmitContactTransactionList? submitResult = await ProfileApiClient().submitContactTransactionAddAndUpdate("ADD", null, valuesFinal);
Utils.hideLoading(context);
if (submitResult != null && submitResult.pTRANSACTIONID != null) {
await Navigator.pushNamed(
context,
AppRoutes.requestSubmitScreen,
arguments: RequestSubmitScreenParams(
LocaleKeys.profile_familyDetails.tr(),
submitResult.pTRANSACTIONID!,
submitResult.pITEMKEY ?? '',
'family_member',
),
);
if (!AppState().cancelRequestTrancsection) {
Navigator.pop(context);
return;
}
Utils.showLoading(context);
await LeaveBalanceApiClient().cancelHrTransaction(submitResult.pTRANSACTIONID!);
Utils.hideLoading(context);
Navigator.pop(context);
} else {
Utils.showToast(LocaleKeys.dependentAddedSuccessfully.tr());
Navigator.pop(context);
}
} catch (ex) {
Utils.hideLoading(context);
Utils.handleException(ex, context, null);
}
}
@override
Widget build(BuildContext context) {
bool isAr = AppState().isArabic(context);
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBarWidget(
context,
title: LocaleKeys.addDependent.tr(),
showHomeButton: true,
onBackTapped: () {
if (_currentStep > 1) {
setState(() {
_currentStep--;
});
} else {
Navigator.maybePop(context);
}
},
onHomeTapped: () {
Navigator.pushNamedAndRemoveUntil(
context,
ApiConsts.ISDependetLogin ? AppRoutes.dashboardScreenForDependet : AppRoutes.dashboard,
(Route<dynamic> route) => false,
);
},
),
body: SafeArea(
child: Column(
children: <Widget>[
// Stepper: 1 Verify -> 2 Basic -> 3 Additional -> 4 Submit
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
child: _buildStepper(isAr),
),
// Step Content
Expanded(
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
child: _buildCurrentStepContent(isAr),
),
),
// Pinned Bottom Button(s) - Reusing DefaultButton with gradient
_buildBottomButtons(isAr),
],
),
),
);
}
// --- Stepper Widget ---
Widget _buildStepper(bool isAr) {
List<Map<String, String>> steps = <Map<String, String>>[
<String, String>{'num': '1', 'title': LocaleKeys.stepVerify.tr()},
<String, String>{'num': '2', 'title': LocaleKeys.stepBasic.tr()},
<String, String>{'num': '3', 'title': LocaleKeys.stepAdditional.tr()},
<String, String>{'num': '4', 'title': LocaleKeys.submit.tr()},
];
Color completedColor = const Color(0xFF199F6A); // completed colors as per figma
Color activeColor = const Color(0xFFFF8A00);
Color inactiveBorderColor = const Color(0xFFE2E8F0);
Color inactiveTextColor = const Color(0xFF64748B);
const double circleSize = 32.0;
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
double availableWidth = constraints.maxWidth;
// The stepper is left-aligned and spans ~75-80% of width on mobile
double stepSpacing = ((availableWidth * 0.78 - circleSize) / (steps.length - 1)).clamp(68.0, 84.0);
return Align(
alignment: AlignmentDirectional.centerStart,
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
for (int i = 0; i < steps.length; i++) ...<Widget>[
if (i < steps.length - 1)
SizedBox(
width: stepSpacing,
child: Stack(
clipBehavior: Clip.none,
children: <Widget>[
// Connecting line from right edge of circle to next circle
Positioned(
top: (circleSize - 2) / 2,
left: isAr ? null : circleSize,
right: isAr ? circleSize : null,
width: stepSpacing - circleSize,
child: Container(
height: 2,
color: (i + 1) < _currentStep ? completedColor : inactiveBorderColor,
),
),
// Circle + Title (left-aligned with circle)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
_buildStepCircle(
i: i,
steps: steps,
completedColor: completedColor,
activeColor: activeColor,
inactiveBorderColor: inactiveBorderColor,
inactiveTextColor: inactiveTextColor,
circleSize: circleSize,
),
const SizedBox(height: 6),
steps[i]['title']!.toText12(
isBold: (i + 1) == _currentStep,
color: (i + 1) < _currentStep
? completedColor
: ((i + 1) == _currentStep ? activeColor : inactiveTextColor),
),
],
),
],
),
)
else
// Last step (Submit)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
_buildStepCircle(
i: i,
steps: steps,
completedColor: completedColor,
activeColor: activeColor,
inactiveBorderColor: inactiveBorderColor,
inactiveTextColor: inactiveTextColor,
circleSize: circleSize,
),
const SizedBox(height: 6),
steps[i]['title']!.toText12(
isBold: (i + 1) == _currentStep,
color: (i + 1) < _currentStep
? completedColor
: ((i + 1) == _currentStep ? activeColor : inactiveTextColor),
),
],
),
],
],
),
);
},
);
}
Widget _buildStepCircle({
required int i,
required List<Map<String, String>> steps,
required Color completedColor,
required Color activeColor,
required Color inactiveBorderColor,
required Color inactiveTextColor,
required double circleSize,
}) {
bool isCompleted = (i + 1) < _currentStep;
bool isActive = (i + 1) == _currentStep;
return Container(
width: circleSize,
height: circleSize,
decoration: BoxDecoration(
color: isCompleted
? completedColor
: (isActive ? activeColor : Colors.white),
shape: BoxShape.circle,
border: Border.all(
color: isCompleted
? completedColor
: (isActive ? activeColor : inactiveBorderColor),
width: 1.5,
),
),
alignment: Alignment.center,
child: steps[i]['num']!.toText13(
isBold: true,
color: (isCompleted || isActive) ? Colors.white : inactiveTextColor,
),
);
}
// --- Step Content Switcher ---
Widget _buildCurrentStepContent(bool isAr) {
switch (_currentStep) {
case 1:
return _buildStep1Verify(isAr);
case 2:
return _buildStep2BasicInfo(isAr);
case 3:
return _buildStep3AdditionalInfo(isAr);
case 4:
return _buildStep4Submit(isAr);
default:
return _buildStep1Verify(isAr);
}
}
// --- STEP 1: VERIFY ---
Widget _buildStep1Verify(bool isAr) {
String dobDisplay = _selectedDob != null
? DateFormat('dd / MM / yyyy').format(_selectedDob!)
: LocaleKeys.dateFormatPlaceholder.tr();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const SizedBox(height: 12),
LocaleKeys.findYourDependent.tr().toText20(
isBold: true,
color: const Color(0xFF1E293B),
),
const SizedBox(height: 8),
(_isLegislationSA
? LocaleKeys.findYourDependentDesc.tr()
: LocaleKeys.enterDependentDetailsToContinue.tr())
.toText14(
color: const Color(0xFF64748B),
),
const SizedBox(height: 24),
// National ID
_buildFigmaTextField(
"${LocaleKeys.nationalId.tr()}*",
_isLegislationSA
? LocaleKeys.enter10DigitNationalId.tr()
: LocaleKeys.enterFieldPlaceholder.tr(args: [LocaleKeys.nationalId.tr()]),
controller: _nationalIdController,
suffixIconData: Icons.badge_outlined,
isInputTypeNum: _isLegislationSA,
maxLength: _isLegislationSA ? 10 : 25,
backgroundColor: Colors.white,
borderColor: const Color(0xFFE6E9E8),
labelColor: const Color(0xff2B353E),
textColor: const Color(0xff2B353E),
suffixIconColor: const Color(0xFF64748B),
inputFormatters: _isLegislationSA
? [
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(10),
]
: [
LengthLimitingTextInputFormatter(25),
],
),
const SizedBox(height: 16),
// Date of Birth
_buildFigmaTextField(
"${LocaleKeys.dateOfBirth.tr()}*",
dobDisplay,
isReadOnly: true,
backgroundColor: Colors.white,
borderColor: const Color(0xFFE6E9E8),
labelColor: const Color(0xff2B353E),
textColor: _selectedDob != null ? const Color(0xff2B353E) : const Color(0xFF94A3B8),
suffixIconColor: const Color(0xFF64748B),
suffixIconData: Icons.calendar_today_outlined,
onTap: () => _pickDateOfBirth(context),
),
const SizedBox(height: 24),
],
);
}
// --- STEP 2: BASIC INFO (DYNAMICALLY GENERATED FROM CONTACT COLS & BASIC DET DFF) ---
Widget _buildStep2BasicInfo(bool isAr) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const SizedBox(height: 12),
LocaleKeys.basicInfo.tr().toText20(
isBold: true,
color: const Color(0xFF1E293B),
),
const SizedBox(height: 6),
(_isLegislationSA ? LocaleKeys.basicInfoDesc.tr() : LocaleKeys.enterBasicDetailsDesc.tr()).toText14(
color: const Color(0xFF64748B),
),
const SizedBox(height: 20),
// Dynamic fields from Contact Cols Structure
for (int i = 0; i < _contactColsStructureList.length; i++)
_buildDynamicColField(_contactColsStructureList[i], i),
// Dynamic fields from Basic Det DFF Structure
for (int i = 0; i < _basicDetDffStructureList.length; i++)
_buildDynamicBasicDffField(_basicDetDffStructureList[i], i),
const SizedBox(height: 12),
],
);
}
// --- STEP 3: ADDITIONAL INFO (DYNAMICALLY GENERATED FROM CONTACT DFF) ---
Widget _buildStep3AdditionalInfo(bool isAr) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const SizedBox(height: 12),
LocaleKeys.additionalInfo.tr().toText20(
isBold: true,
color: const Color(0xFF1E293B),
),
const SizedBox(height: 6),
LocaleKeys.additionalInfoDesc.tr().toText14(
color: const Color(0xFF64748B),
),
const SizedBox(height: 20),
// Dynamic fields from Contact DFF Structure
for (int i = 0; i < _contactDffStructureList.length; i++)
_buildDynamicContactDffField(_contactDffStructureList[i], i),
const SizedBox(height: 12),
],
);
}
// Helper to extract field value dynamically for Review summary
String _findFieldValue(List<String> keywords, [List<String>? excludeKeywords]) {
for (var item in _contactColsStructureList) {
String colName = (item.aPPLICATIONCOLUMNNAME ?? '').toUpperCase();
String prompt = (item.sEGMENTPROMPT ?? '').toUpperCase();
if (excludeKeywords != null && excludeKeywords.any((ex) => colName.contains(ex.toUpperCase()) || prompt.contains(ex.toUpperCase()))) {
continue;
}
for (var k in keywords) {
String key = k.toUpperCase();
if (colName.contains(key) || prompt.contains(key)) {
String? val = item.getContactDetailsList?.sEGMENTVALUEDSP;
if (val != null && val.trim().isNotEmpty) return val.trim();
String? v2 = item.getContactDetailsList?.vARCHAR2VALUE;
if (v2 != null && v2.trim().isNotEmpty) return v2.trim();
}
}
}
for (var item in _basicDetDffStructureList) {
String colName = (item.aPPLICATIONCOLUMNNAME ?? '').toUpperCase();
String prompt = (item.sEGMENTPROMPT ?? '').toUpperCase();
if (excludeKeywords != null && excludeKeywords.any((ex) => colName.contains(ex.toUpperCase()) || prompt.contains(ex.toUpperCase()))) {
continue;
}
for (var k in keywords) {
String key = k.toUpperCase();
if (colName.contains(key) || prompt.contains(key)) {
String? val = item.getContactDetailsList?.sEGMENTVALUEDSP;
if (val != null && val.trim().isNotEmpty) return val.trim();
String? v2 = item.getContactDetailsList?.vARCHAR2VALUE;
if (v2 != null && v2.trim().isNotEmpty) return v2.trim();
}
}
}
for (var item in _contactDffStructureList) {
String colName = (item.aPPLICATIONCOLUMNNAME ?? '').toUpperCase();
String prompt = (item.sEGMENTPROMPT ?? '').toUpperCase();
if (excludeKeywords != null && excludeKeywords.any((ex) => colName.contains(ex.toUpperCase()) || prompt.contains(ex.toUpperCase()))) {
continue;
}
for (var k in keywords) {
String key = k.toUpperCase();
if (colName.contains(key) || prompt.contains(key)) {
String? val = item.getContactDetailsList?.sEGMENTVALUEDSP;
if (val != null && val.trim().isNotEmpty) return val.trim();
String? v2 = item.getContactDetailsList?.vARCHAR2VALUE;
if (v2 != null && v2.trim().isNotEmpty) return v2.trim();
}
}
}
return '';
}
// --- STEP 4: SUBMIT / REVIEW ---
Widget _buildStep4Submit(bool isAr) {
String natId = _findFieldValue(['NATIONAL_IDENTIFIER', 'CIVIL_ID', 'IQAMA']);
if (natId.isEmpty) natId = _nationalIdController.text;
String fn = _findFieldValue(['FIRST_NAME']);
String ln = _findFieldValue(['LAST_NAME', 'FAMILY_NAME']);
String fullName = "$fn $ln".trim();
if (fullName.isEmpty) {
fullName = _findFieldValue(['CONTACT_NAME', 'FULL_NAME']);
}
if (fullName.isEmpty && _patientInfo != null) {
fullName = isAr ? _patientInfo!.fullNameAr : _patientInfo!.fullNameEn;
}
String arFn = _findFieldValue(['ARABIC_FIRST', 'FIRST_NAME_AR']);
String arLn = _findFieldValue(['ARABIC_FAMILY', 'LAST_NAME_AR', 'ARABIC_LAST']);
String arFullName = "$arFn $arLn".trim();
if (arFullName.isEmpty && _patientInfo != null) {
arFullName = _patientInfo!.fullNameAr;
}
String dobDisplay = _findFieldValue(['DATE_OF_BIRTH', 'DOB']);
if (dobDisplay.isEmpty && _selectedDob != null) {
dobDisplay = DateFormat('dd / MM / yyyy').format(_selectedDob!);
}
String rel = _findFieldValue(['CONTACT_TYPE', 'RELATIONSHIP']);
String sponsor = _findFieldValue(['SPONSOR'], ['DETAIL', 'تفاصيل']);
String sponsorDetail = _findFieldValue(['SPONSOR_DETAIL', 'DEP_SPONSOR', 'تفاصيل كفيل', 'DETAIL']);
String insurance = _findFieldValue(['INSURANCE', 'MED']);
String startDate = _findFieldValue(['START', 'DATE_START']);
if (startDate.isEmpty) startDate = dobDisplay;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const SizedBox(height: 12),
LocaleKeys.reviewAndSubmit.tr().toText20(
isBold: true,
color: const Color(0xFF1E293B),
),
const SizedBox(height: 6),
LocaleKeys.reviewAndSubmitDesc.tr().toText14(
color: const Color(0xFF64748B),
),
const SizedBox(height: 20),
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: const Color(0xFFE2E8F0)),
boxShadow: const [
BoxShadow(
color: Color(0x08000000),
blurRadius: 8,
offset: Offset(0, 2),
),
],
),
child: Column(
children: <Widget>[
_buildReviewRow(LocaleKeys.nationalId.tr(), natId),
const Divider(height: 20),
_buildReviewRow(LocaleKeys.fullName.tr(), fullName),
if (arFullName.isNotEmpty) ...[
const Divider(height: 20),
_buildReviewRow(LocaleKeys.arabicName.tr(), arFullName),
],
const Divider(height: 20),
_buildReviewRow(LocaleKeys.dateOfBirth.tr(), dobDisplay),
if (rel.isNotEmpty) ...[
const Divider(height: 20),
_buildReviewRow(LocaleKeys.relationship.tr(), rel),
],
if (sponsor.isNotEmpty) ...[
const Divider(height: 20),
_buildReviewRow(LocaleKeys.sponsor.tr(), sponsor),
],
if (sponsorDetail.isNotEmpty) ...[
const Divider(height: 20),
_buildReviewRow(LocaleKeys.dependentSponsorDetail.tr(), sponsorDetail),
],
if (insurance.isNotEmpty) ...[
const Divider(height: 20),
_buildReviewRow(LocaleKeys.insuranceEligibility.tr(), insurance),
],
if (startDate.isNotEmpty) ...[
const Divider(height: 20),
_buildReviewRow(LocaleKeys.relationshipStartDate.tr(), startDate),
],
],
),
),
const SizedBox(height: 24),
],
);
}
Widget _buildReviewRow(String title, String value) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
flex: 4,
child: title.toText13(
color: const Color(0xFF64748B),
),
),
Expanded(
flex: 6,
child: (value.isNotEmpty ? value : "-").toText14(
isBold: true,
textAlign: TextAlign.end,
color: const Color(0xFF1E293B),
),
),
],
);
}
// --- Bottom Buttons (Always reusing DefaultButton with app gradient!) ---
Widget _buildBottomButtons(bool isAr) {
if (_currentStep == 1) {
bool canProceed = _isStep1Valid() && !_isLoading;
return DefaultButton(
_isLegislationSA ? LocaleKeys.searchUser.tr() : LocaleKeys.next.tr(),
canProceed ? _onSearchUser : null,
iconData: _isLegislationSA ? Icons.search : null,
isTextExpanded: false,
height: 48,
borderRadius: 12,
textColor: canProceed ? Colors.white : const Color(0xFF8E95A2),
).insideContainer;
}
VoidCallback onBack = () {
setState(() {
_currentStep--;
});
};
bool canProceed = false;
VoidCallback onNext;
String nextLabel;
if (_currentStep == 2) {
canProceed = _isStep2Valid();
onNext = _onNextFromBasicInfo;
nextLabel = LocaleKeys.next.tr();
} else if (_currentStep == 3) {
canProceed = _isStep3Valid();
onNext = _onNextFromAdditionalInfo;
nextLabel = LocaleKeys.next.tr();
} else {
canProceed = true;
onNext = _onSubmitTransaction;
nextLabel = LocaleKeys.submitRequest.tr();
}
return Container(
color: Colors.white,
padding: const EdgeInsets.only(top: 16, bottom: 16, right: 20, left: 20),
child: Row(
children: <Widget>[
// Back Button: reuses DefaultButton with outline styling
Expanded(
child: DefaultButton(
LocaleKeys.back.tr(),
onBack,
colors: const [Colors.white, Colors.white],
textColor: MyColors.gradiantEndColor,
border: Border.all(color: MyColors.gradiantEndColor, width: 1.5),
height: 48,
borderRadius: 12,
),
),
const SizedBox(width: 16),
// Next / Submit Button: reuses DefaultButton with canonical gradient
Expanded(
child: DefaultButton(
nextLabel,
canProceed ? onNext : null,
height: 48,
borderRadius: 12,
textColor: canProceed ? Colors.white : const Color(0xFF8E95A2),
),
),
],
),
);
}
}