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.
2416 lines
100 KiB
Dart
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),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|