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 { 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 _basicDetDffStructureList = []; List _contactColsStructureList = []; List _contactDffStructureList = []; bool _ebsLoaded = false; // Cache for dynamically generated text field controllers final Map _fieldControllers = {}; @override void initState() { super.initState(); _fetchEbsStructures(); } @override void dispose() { _nationalIdController.dispose(); for (var c in _fieldControllers.values) { c.dispose(); } _fieldControllers.clear(); super.dispose(); } TextEditingController _getControllerForField(String key, String? initialValue) { if (!_fieldControllers.containsKey(key)) { _fieldControllers[key] = TextEditingController(text: initialValue ?? ''); } return _fieldControllers[key]!; } void _syncController(String key, String? value) { if (_fieldControllers.containsKey(key)) { _fieldControllers[key]!.text = value ?? ''; } else { _fieldControllers[key] = TextEditingController(text: value ?? ''); } } 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 _fetchEbsStructures() async { try { List results = await Future.wait([ ProfileApiClient().getBasicDetDffStructure().catchError((_) => []), ProfileApiClient().getContactColsStructureList("ADD").catchError((_) => []), ProfileApiClient().getContactDffStructureList().catchError((_) => []), ]); if (mounted) { setState(() { _basicDetDffStructureList = results[0] as List; _contactColsStructureList = results[1] as List; _contactDffStructureList = results[2] as List; _initStructureDetails(); _ebsLoaded = true; }); } } catch (_) {} } // --- Step 1 Date of Birth Picker --- Future _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); } // 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); } } } _contactColsStructureList.forEach(matchStep1); _basicDetDffStructureList.forEach(matchStep1); _contactDffStructureList.forEach(matchStep1); } // --- Map GetPatientInfo to Dynamic Structures --- void _applyPrefilledPatientInfo(PatientInfoModel patient, bool isAr, String nationalId, String dob) { 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 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 ?? ''; // 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); } // 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); } } // 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); } } // 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); } } // 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); } } // 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); } } // 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); } } List? objValues; List? 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'; } } // 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; 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; 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; break; } else if (isMale && (code == 'SON' || (!meaning.contains('daughter') && (meaning.contains('son') || meaning.contains('ابن') || meaning.contains('ولد'))))) { details.sEGMENTVALUEDSP = opt.mEANING; details.vARCHAR2VALUE = opt.cODE; 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 _selectDate(BuildContext context, [DateTime? initial]) async { DateTime time = initial ?? DateTime.now(); if (!Platform.isIOS) { DateTime tempDate = time; bool? confirmed = 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, 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; } } // --- 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 label = (model.sEGMENTPROMPT ?? "") + (isRequired ? "*" : ""); model.getContactDetailsList ??= GetContactDetailsList(); String colKey = model.aPPLICATIONCOLUMNNAME ?? "COL_$index"; if (model.dATATYPE == "VARCHAR2") { if (model.objectValuesList != null && model.objectValuesList!.isNotEmpty) { String colName = (model.aPPLICATIONCOLUMNNAME ?? "").toUpperCase(); String prompt = (model.sEGMENTPROMPT ?? "").toLowerCase(); 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 ?? ""; return Padding( padding: const EdgeInsets.only(bottom: 12), child: PopupMenuButton( child: DynamicTextFieldWidget( label, displayVal.isNotEmpty ? displayVal : LocaleKeys.select.tr(), isEnable: false, isPopup: true, backgroundColor: Colors.white, ), itemBuilder: (_) => >[ for (int i = 0; i < model.objectValuesList!.length; i++) PopupMenuItem( value: i, child: (model.objectValuesList![i].mEANING ?? "").toText14(), ), ], onSelected: (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); return Padding( padding: const EdgeInsets.only(bottom: 12), child: DynamicTextFieldWidget( label, LocaleKeys.enterFieldPlaceholder.tr(args: [label]), controller: controller, backgroundColor: Colors.white, onChange: (String text) { model.getContactDetailsList!.sEGMENTVALUEDSP = text; model.getContactDetailsList!.vARCHAR2VALUE = text; }, ), ); } } else if (model.dATATYPE == "NUMBER") { var controller = _getControllerForField(colKey, model.getContactDetailsList!.sEGMENTVALUEDSP); return Padding( padding: const EdgeInsets.only(bottom: 12), child: DynamicTextFieldWidget( label, LocaleKeys.enterFieldPlaceholder.tr(args: [label]), controller: controller, isInputTypeNum: true, backgroundColor: Colors.white, onChange: (String text) { model.getContactDetailsList!.sEGMENTVALUEDSP = text; model.getContactDetailsList!.vARCHAR2VALUE = text; }, ), ); } else if (model.dATATYPE == "DATE") { String displayVal = model.getContactDetailsList!.sEGMENTVALUEDSP ?? ""; return Padding( padding: const EdgeInsets.only(bottom: 12), child: DynamicTextFieldWidget( label, displayVal.isNotEmpty ? displayVal : LocaleKeys.dateFormatPlaceholder.tr(), suffixIconData: Icons.calendar_today_outlined, isReadOnly: true, backgroundColor: Colors.white, onTap: () 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 label = (model.sEGMENTPROMPT ?? "") + (isRequired ? "*" : ""); model.getContactDetailsList ??= GetContactDetailsList(); String colKey = model.aPPLICATIONCOLUMNNAME ?? "BASIC_DFF_$index"; if (model.fORMATTYPE == "C") { if (model.eSERVICESVS != null && model.eSERVICESVS!.isNotEmpty) { String colName = (model.aPPLICATIONCOLUMNNAME ?? "").toUpperCase(); String prompt = (model.sEGMENTPROMPT ?? "").toLowerCase(); 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 ?? ""; return Padding( padding: const EdgeInsets.only(bottom: 12), child: PopupMenuButton( child: DynamicTextFieldWidget( label, displayVal.isNotEmpty ? displayVal : LocaleKeys.select.tr(), isEnable: false, isPopup: true, backgroundColor: Colors.white, ), itemBuilder: (_) => >[ for (int i = 0; i < model.eSERVICESVS!.length; i++) PopupMenuItem( value: i, child: (model.eSERVICESVS![i].vALUECOLUMNNAME ?? "").toText14(), ), ], onSelected: (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); return Padding( padding: const EdgeInsets.only(bottom: 12), child: DynamicTextFieldWidget( label, LocaleKeys.enterFieldPlaceholder.tr(args: [label]), controller: controller, backgroundColor: Colors.white, onChange: (String text) { model.getContactDetailsList!.sEGMENTVALUEDSP = text; model.getContactDetailsList!.vARCHAR2VALUE = text; }, ), ); } } else if (model.fORMATTYPE == "N") { var controller = _getControllerForField(colKey, model.getContactDetailsList!.sEGMENTVALUEDSP); return Padding( padding: const EdgeInsets.only(bottom: 12), child: DynamicTextFieldWidget( label, LocaleKeys.enterFieldPlaceholder.tr(args: [label]), controller: controller, isInputTypeNum: true, backgroundColor: Colors.white, onChange: (String text) { model.getContactDetailsList!.sEGMENTVALUEDSP = text; model.getContactDetailsList!.vARCHAR2VALUE = text; }, ), ); } else if (model.fORMATTYPE == "X") { String displayVal = model.getContactDetailsList!.sEGMENTVALUEDSP ?? ""; return Padding( padding: const EdgeInsets.only(bottom: 12), child: DynamicTextFieldWidget( label, displayVal.isNotEmpty ? displayVal : LocaleKeys.dateFormatPlaceholder.tr(), suffixIconData: Icons.calendar_today_outlined, isReadOnly: true, backgroundColor: Colors.white, onTap: () 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 label = (model.sEGMENTPROMPT ?? "") + (isRequired ? "*" : ""); model.getContactDetailsList ??= GetContactDetailsList(); String colKey = model.aPPLICATIONCOLUMNNAME ?? "CONTACT_DFF_$index"; if (model.fORMATTYPE == "C") { if (model.eSERVICESVS != null && model.eSERVICESVS!.isNotEmpty) { String colName = (model.aPPLICATIONCOLUMNNAME ?? "").toUpperCase(); String prompt = (model.sEGMENTPROMPT ?? "").toLowerCase(); 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 ?? ""; return Padding( padding: const EdgeInsets.only(bottom: 12), child: PopupMenuButton( child: DynamicTextFieldWidget( label, displayVal.isNotEmpty ? displayVal : LocaleKeys.select.tr(), isEnable: false, isPopup: true, backgroundColor: Colors.white, ), itemBuilder: (_) => >[ for (int i = 0; i < model.eSERVICESVS!.length; i++) PopupMenuItem( value: i, child: (model.eSERVICESVS![i].vALUECOLUMNNAME ?? "").toText14(), ), ], onSelected: (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); return Padding( padding: const EdgeInsets.only(bottom: 12), child: DynamicTextFieldWidget( label, LocaleKeys.enterFieldPlaceholder.tr(args: [label]), controller: controller, backgroundColor: Colors.white, onChange: (String text) { model.getContactDetailsList!.sEGMENTVALUEDSP = text; model.getContactDetailsList!.vARCHAR2VALUE = text; }, ), ); } } else if (model.fORMATTYPE == "N") { var controller = _getControllerForField(colKey, model.getContactDetailsList!.sEGMENTVALUEDSP); return Padding( padding: const EdgeInsets.only(bottom: 12), child: DynamicTextFieldWidget( label, LocaleKeys.enterFieldPlaceholder.tr(args: [label]), controller: controller, isInputTypeNum: true, backgroundColor: Colors.white, onChange: (String text) { model.getContactDetailsList!.sEGMENTVALUEDSP = text; model.getContactDetailsList!.vARCHAR2VALUE = text; }, ), ); } else if (model.fORMATTYPE == "X") { String displayVal = model.getContactDetailsList?.dATEVALUE ?? model.getContactDetailsList?.sEGMENTVALUEDSP ?? ""; return Padding( padding: const EdgeInsets.only(bottom: 12), child: DynamicTextFieldWidget( label, displayVal.isNotEmpty ? displayVal : LocaleKeys.dateFormatPlaceholder.tr(), suffixIconData: Icons.calendar_today_outlined, isReadOnly: true, backgroundColor: Colors.white, onTap: () 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> 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> 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> 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> 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 route) => false, ); }, ), body: SafeArea( child: Column( children: [ // 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> steps = >[ {'num': '1', 'title': LocaleKeys.stepVerify.tr()}, {'num': '2', 'title': LocaleKeys.stepBasic.tr()}, {'num': '3', 'title': LocaleKeys.stepAdditional.tr()}, {'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: [ for (int i = 0; i < steps.length; i++) ...[ if (i < steps.length - 1) SizedBox( width: stepSpacing, child: Stack( clipBehavior: Clip.none, children: [ // 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: [ _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: [ _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> 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: [ 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 DynamicTextFieldWidget( "${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, inputFormatters: _isLegislationSA ? [ FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(10), ] : [ LengthLimitingTextInputFormatter(25), ], ), const SizedBox(height: 16), // Date of Birth DynamicTextFieldWidget( "${LocaleKeys.dateOfBirth.tr()}*", dobDisplay, isReadOnly: true, backgroundColor: Colors.white, 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: [ 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: [ 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 keywords, [List? 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: [ 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: [ _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) { return DefaultButton( _isLegislationSA ? LocaleKeys.searchUser.tr() : LocaleKeys.next.tr(), _isLoading ? null : _onSearchUser, iconData: _isLegislationSA ? Icons.search : null, isTextExpanded: false, height: 48, borderRadius: 12, ).insideContainer; } VoidCallback onBack = () { setState(() { _currentStep--; }); }; VoidCallback onNext; String nextLabel; if (_currentStep == 2) { onNext = _onNextFromBasicInfo; nextLabel = LocaleKeys.next.tr(); } else if (_currentStep == 3) { onNext = _onNextFromAdditionalInfo; nextLabel = LocaleKeys.next.tr(); } else { 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: [ // 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, onNext, height: 48, borderRadius: 12, ), ), ], ), ); } }