From d8a9d8443a732213643808c7c258dbb8a85589ec Mon Sep 17 00:00:00 2001 From: Sultan khan Date: Sun, 23 Nov 2025 11:56:53 +0300 Subject: [PATCH] e-referral work done search-referral in progress. --- lib/core/api_consts.dart | 24 +- lib/core/utils/validation_utils.dart | 14 +- .../hmg_services/hmg_services_repo.dart | 238 ++++++++++ .../hmg_services/hmg_services_view_model.dart | 189 ++++++++ ...check_activation_e_referral_req_model.dart | 57 +++ .../req_models/create_e_referral_model.dart | 155 +++++++ .../create_e_referral_req_model.dart} | 0 ...d_activation_code_ereferral_req_model.dart | 56 +++ .../get_all_cities_resp_model.dart | 21 + .../relationship_type_resp_mode.dart | 25 + .../ui_models/e_referral_form_model.dart | 42 ++ .../e_referral/e-referral_validator.dart | 91 ++++ .../e_referral/e_referral_form_manager.dart | 206 +++++++++ .../e_referral/e_referral_page_home.dart | 55 ++- .../e_referral/new_e_referral.dart | 183 ++++++++ lib/presentation/e_referral/new_referral.dart | 432 ------------------ .../e_referral/search_e_referral.dart | 98 ++++ .../e_referral/widget/e-referral_otp.dart | 105 +++++ .../widget/e_referral_other_details.dart | 308 +++++++++++++ .../widget/e_referral_patient_info.dart | 289 ++++++++++++ .../widget/e_referral_requester_form.dart | 228 +++++++++ .../e_referral/widget/e_referral_stepper.dart | 72 +++ .../widget/search_e_referral_form.dart | 184 ++++++++ lib/widgets/attachment_options.dart | 2 +- lib/widgets/dropdown/dropdown_widget.dart | 30 +- 25 files changed, 2635 insertions(+), 469 deletions(-) create mode 100644 lib/features/hmg_services/models/req_models/check_activation_e_referral_req_model.dart create mode 100644 lib/features/hmg_services/models/req_models/create_e_referral_model.dart rename lib/features/hmg_services/models/{create_e_referral_model.dart => req_models/create_e_referral_req_model.dart} (100%) create mode 100644 lib/features/hmg_services/models/req_models/send_activation_code_ereferral_req_model.dart create mode 100644 lib/features/hmg_services/models/resq_models/get_all_cities_resp_model.dart create mode 100644 lib/features/hmg_services/models/resq_models/relationship_type_resp_mode.dart create mode 100644 lib/features/hmg_services/models/ui_models/e_referral_form_model.dart create mode 100644 lib/presentation/e_referral/e-referral_validator.dart create mode 100644 lib/presentation/e_referral/e_referral_form_manager.dart create mode 100644 lib/presentation/e_referral/new_e_referral.dart delete mode 100644 lib/presentation/e_referral/new_referral.dart create mode 100644 lib/presentation/e_referral/search_e_referral.dart create mode 100644 lib/presentation/e_referral/widget/e-referral_otp.dart create mode 100644 lib/presentation/e_referral/widget/e_referral_other_details.dart create mode 100644 lib/presentation/e_referral/widget/e_referral_patient_info.dart create mode 100644 lib/presentation/e_referral/widget/e_referral_requester_form.dart create mode 100644 lib/presentation/e_referral/widget/e_referral_stepper.dart create mode 100644 lib/presentation/e_referral/widget/search_e_referral_form.dart diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index a9e95ae..4cedda1 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -178,8 +178,12 @@ var DELETE_CHILD_REQUEST = 'Services/Community.svc/REST/DeleteBaby'; var GET_TABLE_REQUEST = 'Services/Community.svc/REST/CreateVaccinationTable'; ///BloodDenote +/// +/// use get all cities from the e-referral, already calling this api there don't use multiple same api calls var GET_CITIES_REQUEST = 'Services/Lists.svc/REST/GetAllCities'; - +/// +/// +/// ///BloodDetails var GET_BLOOD_REQUEST = 'services/PatientVarification.svc/REST/BloodDonation_GetBloodGroupDetails'; @@ -440,12 +444,7 @@ var H2O_UPDATE_USER_DETAIL = "Services/H2ORemainder.svc/REST/H2O_UpdateUserDetai var H2O_UNDO_USER_ACTIVITY = "Services/H2ORemainder.svc/REST/H2o_UndoUserActivity"; //E_Referral Services -var GET_ALL_RELATIONSHIP_TYPES = "Services/Patients.svc/REST/GetAllRelationshipTypes"; -var SEND_ACTIVATION_CODE_FOR_E_REFERRAL = 'Services/Authentication.svc/REST/SendActivationCodeForEReferral'; -var CHECK_ACTIVATION_CODE_FOR_E_REFERRAL = 'Services/Authentication.svc/REST/CheckActivationCodeForEReferral'; -var GET_ALL_CITIES = 'services/Lists.svc/rest/GetAllCities'; -var CREATE_E_REFERRAL = "Services/Patients.svc/REST/CreateEReferral"; -var GET_E_REFERRALS = "Services/Patients.svc/REST/GetEReferrals"; + // Encillary Orders @@ -849,6 +848,17 @@ class ApiConsts { var GET_ALL_CMC_ORDERS_RC = 'api/cmc/list'; var UPDATE_CMC_ORDER_RC = 'api/cmc/update'; + + //E-REFERRAL SERVICES + + static final getAllRelationshipTypes = "Services/Patients.svc/REST/GetAllRelationshipTypes"; + static final sendActivationCodeForEReferral = 'Services/Authentication.svc/REST/SendActivationCodeForEReferral'; + static final checkActivationCodeForEReferral = 'Services/Authentication.svc/REST/CheckActivationCodeForEReferral'; + static final getAllCities = 'services/Lists.svc/rest/GetAllCities'; + static final createEReferral = "Services/Patients.svc/REST/CreateEReferral"; + static final getEReferrals = "Services/Patients.svc/REST/GetEReferrals"; + + // ************ static values for Api **************** static final double appVersionID = 20.0; static final int appChannelId = 3; diff --git a/lib/core/utils/validation_utils.dart b/lib/core/utils/validation_utils.dart index 7e38300..abba447 100644 --- a/lib/core/utils/validation_utils.dart +++ b/lib/core/utils/validation_utils.dart @@ -101,8 +101,7 @@ class ValidationUtils { return regex.hasMatch(id); } - static bool validateUaeRegistration( - {String? name, GenderTypeEnum? gender, NationalityCountries? country, MaritalStatusTypeEnum? maritalStatus, required Function() onOkPress}) { + static bool validateUaeRegistration({String? name, GenderTypeEnum? gender, NationalityCountries? country, MaritalStatusTypeEnum? maritalStatus, required Function() onOkPress}) { if (name == null || name.isEmpty) { _dialogService.showExceptionBottomSheet(message: LocaleKeys.pleaseEnterAValidName.tr(), onOkPressed: onOkPress); return false; @@ -141,8 +140,7 @@ class ValidationUtils { return true; } - static bool isValidatedIdAndPhoneWithCountryValidation( - {String? nationalId, String? phoneNumber, required Function() onOkPress, CountryEnum? selectedCountry}) { + static bool isValidatedIdAndPhoneWithCountryValidation({String? nationalId, String? phoneNumber, required Function() onOkPress, CountryEnum? selectedCountry}) { bool isCorrectID = true; if (nationalId == null || nationalId.isEmpty) { _dialogService.showExceptionBottomSheet(message: LocaleKeys.pleaseEnterAnationalID.tr(), onOkPressed: onOkPress); @@ -171,4 +169,10 @@ class ValidationUtils { } return isCorrectID; } -} + + static bool isNullOrEmpty(String? value) { + return value == null || value + .trim() + .isEmpty; + } +} \ No newline at end of file diff --git a/lib/features/hmg_services/hmg_services_repo.dart b/lib/features/hmg_services/hmg_services_repo.dart index 39a6142..1115f18 100644 --- a/lib/features/hmg_services/hmg_services_repo.dart +++ b/lib/features/hmg_services/hmg_services_repo.dart @@ -5,10 +5,17 @@ import 'package:hmg_patient_app_new/core/api/api_client.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/check_activation_e_referral_req_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/order_update_req_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_all_cities_resp_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_services_resp_model.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; +import 'package:provider/provider.dart'; + +import 'models/req_models/create_e_referral_model.dart'; +import 'models/req_models/send_activation_code_ereferral_req_model.dart'; +import 'models/resq_models/relationship_type_resp_mode.dart'; abstract class HmgServicesRepo { Future>>> getAllCmcOrders(); @@ -16,6 +23,17 @@ abstract class HmgServicesRepo { Future>> updateCmcPresOrder(OrderUpdateRequestModel requestModel); Future>>> getAllCmcServices({required int patientID}); + + Future>>> getRelationshipTypes(); + + Future>>> getAllCities(); + + Future>> sendEReferralActivationCode(SendActivationCodeForEReferralRequestModel requestModel); + + Future>> checkEReferralActivationCode(CheckActivationCodeForEReferralRequestModel requestModel); + + Future>> createEReferral(CreateEReferralRequestModel requestModel); + } class HmgServicesRepoImp implements HmgServicesRepo { @@ -175,4 +193,224 @@ class HmgServicesRepoImp implements HmgServicesRepo { return Left(UnknownFailure(e.toString())); } } + + + /*e-referral functions*/ + + + @override + Future>>> getRelationshipTypes() async { + Map requestBody = {}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + + await apiClient.post( + ApiConsts.getAllRelationshipTypes, + body: requestBody, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + loggerService.logError("EReferral Services API Failed: $error, Status: $statusCode"); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + List relationshipList = []; + + if (response['List_EReferralResult'] != null && response['List_EReferralResult'] is List) { + final servicesList = response['List_EReferralResult'] as List; + + for (var serviceJson in servicesList) { + if (serviceJson is Map) { + relationshipList.add(GetAllRelationshipTypeResponseModel.fromJson(serviceJson)); + } + } + } + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: relationshipList, + ); + } catch (e) { + loggerService.logError("Error parsing E-Referral services: ${e.toString()}"); + failure = DataParsingFailure(e.toString()); + } + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + log("Unknown error in getAllCmcServices: ${e.toString()}"); + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>>> getAllCities() async { + Map requestBody = {}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + + await apiClient.post( + ApiConsts.getAllCities, + body: requestBody, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + loggerService.logError("EReferral Services API Failed: $error, Status: $statusCode"); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + List citiesList = []; + + if (response['ListCities'] != null && response['ListCities'] is List) { + final servicesList = response['ListCities'] as List; + + for (var serviceJson in servicesList) { + if (serviceJson is Map) { + citiesList.add(GetAllCitiesResponseModel.fromJson(serviceJson)); + } + } + } + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: citiesList, + ); + } catch (e) { + loggerService.logError("Error parsing E-Referral services: ${e.toString()}"); + failure = DataParsingFailure(e.toString()); + } + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + log("Unknown error in getAllCmcServices: ${e.toString()}"); + return Left(UnknownFailure(e.toString())); + } + } + + + @override + Future>> sendEReferralActivationCode(SendActivationCodeForEReferralRequestModel requestModel) async { + GenericApiModel? apiResponse; + try { + + Failure? failure; + await apiClient.post( + ApiConsts.sendActivationCodeForEReferral, + body: requestModel.toJson(), + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + loggerService.logError("EReferral Services API Failed: $error, Status: $statusCode"); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + + + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: response['LogInTokenID'] as String, + ); + } catch (e) { + loggerService.logError("Error parsing E-Referral services: ${e.toString()}"); + failure = DataParsingFailure(e.toString()); + } + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + log("Unknown error in getAllCmcServices: ${e.toString()}"); + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>> createEReferral(CreateEReferralRequestModel requestModel) async { + GenericApiModel? apiResponse; + try { + Failure? failure; + await apiClient.post( + ApiConsts.createEReferral, + body: requestModel.toJson(), + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + loggerService.logError("EReferral Services API Failed: $error, Status: $statusCode"); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: response['ReferralNumber'].toString(), + ); + } catch (e) { + loggerService.logError("Error parsing E-Referral services: ${e.toString()}"); + failure = DataParsingFailure(e.toString()); + } + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + log("Unknown error in getAllCmcServices: ${e.toString()}"); + return Left(UnknownFailure(e.toString())); + } + } + + + @override + Future>> checkEReferralActivationCode(CheckActivationCodeForEReferralRequestModel requestModel) async { + GenericApiModel? apiResponse; + try { + Failure? failure; + await apiClient.post( + ApiConsts.checkActivationCodeForEReferral, + body: requestModel.toJson(), + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + loggerService.logError("EReferral Services API Failed: $error, Status: $statusCode"); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: response['IsAuthenticated'].toString(), + ); + } catch (e) { + loggerService.logError("Error parsing E-Referral services: ${e.toString()}"); + failure = DataParsingFailure(e.toString()); + } + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + log("Unknown error in getAllCmcServices: ${e.toString()}"); + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/hmg_services/hmg_services_view_model.dart b/lib/features/hmg_services/hmg_services_view_model.dart index 84e3bb4..5dd5482 100644 --- a/lib/features/hmg_services/hmg_services_view_model.dart +++ b/lib/features/hmg_services/hmg_services_view_model.dart @@ -1,10 +1,17 @@ import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_repo.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/create_e_referral_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/order_update_req_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/send_activation_code_ereferral_req_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_all_cities_resp_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_services_resp_model.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; +import 'models/req_models/check_activation_e_referral_req_model.dart'; +import 'models/resq_models/relationship_type_resp_mode.dart'; + class HmgServicesViewModel extends ChangeNotifier { final HmgServicesRepo hmgServicesRepo; final ErrorHandlerService errorHandlerService; @@ -18,6 +25,10 @@ class HmgServicesViewModel extends ChangeNotifier { List cmcOrdersList = []; List cmcServicesList = []; + + List relationTypes =[]; + List getAllCitiesList =[]; + Future getOrdersList() async { cmcOrdersList.clear(); isCmcOrdersLoading = true; @@ -134,4 +145,182 @@ class HmgServicesViewModel extends ChangeNotifier { return success; } + + /*e-referral functions*/ + + Future getRelationshipType({ + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + notifyListeners(); + final result = await hmgServicesRepo.getRelationshipTypes(); + + result.fold( + (failure) async { + + notifyListeners(); + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + + if (apiResponse.messageStatus == 1) { + relationTypes = apiResponse.data ?? []; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } else { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? 'Unknown error'); + } + } + }, + ); + } + + + Future getAllCities({ + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + notifyListeners(); + final result = await hmgServicesRepo.getAllCities(); + + result.fold( + (failure) async { + + notifyListeners(); + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + + if (apiResponse.messageStatus == 1) { + getAllCitiesList = apiResponse.data ?? []; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } else { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? 'Unknown error'); + } + } + }, + ); + } + + Future eReferralSendActivationCode({ + required SendActivationCodeForEReferralRequestModel requestModel, + Function(GenericApiModel)? onSuccess, + Function(String)? onError, + }) async { + + notifyListeners(); + + final result = await hmgServicesRepo.sendEReferralActivationCode(requestModel); + + result.fold( + (failure) async { + notifyListeners(); + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + if (apiResponse.messageStatus == 1) { + + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } else { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? 'Unknown error'); + } + } + }, + ); + } + + Future checkEReferralActivationCode({ + required CheckActivationCodeForEReferralRequestModel requestModel, + Function(GenericApiModel)? onSuccess, + Function(String)? onError, + }) async { + + notifyListeners(); + + final result = await hmgServicesRepo.checkEReferralActivationCode(requestModel); + + result.fold( + (failure) async { + notifyListeners(); + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + if (apiResponse.messageStatus == 1) { + + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } else { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? 'Unknown error'); + } + } + }, + ); + } + + + Future createEReferral({ + required CreateEReferralRequestModel requestModel, + Function(GenericApiModel)? onSuccess, + Function(String)? onError, + }) async { + + notifyListeners(); + + final result = await hmgServicesRepo.createEReferral(requestModel); + + result.fold( + (failure) async { + notifyListeners(); + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + if (apiResponse.messageStatus == 1) { + + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } else { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? 'Unknown error'); + } + } + }, + ); + } + } diff --git a/lib/features/hmg_services/models/req_models/check_activation_e_referral_req_model.dart b/lib/features/hmg_services/models/req_models/check_activation_e_referral_req_model.dart new file mode 100644 index 0000000..a356616 --- /dev/null +++ b/lib/features/hmg_services/models/req_models/check_activation_e_referral_req_model.dart @@ -0,0 +1,57 @@ +class CheckActivationCodeForEReferralRequestModel { + String? logInTokenID; + String? activationCode; + double? versionID; + int? channel; + int? languageID; + String? iPAdress; + String? generalid; + int? patientOutSA; + dynamic sessionID; + bool? isDentalAllowedBackend; + int? deviceTypeID; + + CheckActivationCodeForEReferralRequestModel( + {this.logInTokenID, + this.activationCode, + this.versionID, + this.channel, + this.languageID, + this.iPAdress, + this.generalid, + this.patientOutSA, + this.sessionID, + this.isDentalAllowedBackend, + this.deviceTypeID}); + + CheckActivationCodeForEReferralRequestModel.fromJson( + Map json) { + logInTokenID = json['LogInTokenID']; + activationCode = json['activationCode']; + versionID = json['VersionID']; + channel = json['Channel']; + languageID = json['LanguageID']; + iPAdress = json['IPAdress']; + generalid = json['generalid']; + patientOutSA = json['PatientOutSA']; + sessionID = json['SessionID']; + isDentalAllowedBackend = json['isDentalAllowedBackend']; + deviceTypeID = json['DeviceTypeID']; + } + + Map toJson() { + final Map data = new Map(); + data['LogInTokenID'] = this.logInTokenID; + data['activationCode'] = this.activationCode; + data['VersionID'] = this.versionID; + data['Channel'] = this.channel; + data['LanguageID'] = this.languageID; + data['IPAdress'] = this.iPAdress; + data['generalid'] = this.generalid; + data['PatientOutSA'] = this.patientOutSA; + data['SessionID'] = this.sessionID; + data['isDentalAllowedBackend'] = this.isDentalAllowedBackend; + data['DeviceTypeID'] = this.deviceTypeID; + return data; + } +} diff --git a/lib/features/hmg_services/models/req_models/create_e_referral_model.dart b/lib/features/hmg_services/models/req_models/create_e_referral_model.dart new file mode 100644 index 0000000..1b351fd --- /dev/null +++ b/lib/features/hmg_services/models/req_models/create_e_referral_model.dart @@ -0,0 +1,155 @@ +class CreateEReferralRequestModel { + bool? isInsuredPatient; + String? cityCode; + String? cityName; + String? requesterName; + String? requesterContactNo; + int? requesterRelationship; + String? otherRelationship; + String? fullName; + int? identificationNo; + String? patientMobileNumber; + int? preferredBranchCode; + String? preferredBranchName; + List? medicalReportAttachment; + dynamic insuranceCardAttachment; + double? versionID; + int? channel; + int? languageID; + String? iPAdress; + String? generalid; + int? patientOutSA; + String? sessionID; + bool? isDentalAllowedBackend; + int? deviceTypeID; + int? patientID; + String? tokenID; + int? patientTypeID; + int? patientType; + + CreateEReferralRequestModel( + {this.isInsuredPatient, + this.cityCode, + this.cityName, + this.requesterName, + this.requesterContactNo, + this.requesterRelationship, + this.otherRelationship, + this.fullName, + this.identificationNo, + this.patientMobileNumber, + this.preferredBranchCode, + this.preferredBranchName, + this.medicalReportAttachment, + this.insuranceCardAttachment, + this.versionID, + this.channel, + this.languageID, + this.iPAdress, + this.generalid, + this.patientOutSA, + this.sessionID, + this.isDentalAllowedBackend, + this.deviceTypeID, + this.patientID, + this.tokenID, + this.patientTypeID, + this.patientType}); + + CreateEReferralRequestModel.fromJson(Map json) { + isInsuredPatient = json['IsInsuredPatient']; + cityCode = json['CityCode']; + cityName = json['CityName']; + requesterName = json['RequesterName']; + requesterContactNo = json['RequesterContactNo']; + requesterRelationship = json['RequesterRelationship']; + otherRelationship = json['OtherRelationship']; + fullName = json['FullName']; + identificationNo = json['IdentificationNo']; + patientMobileNumber = json['PatientMobileNumber']; + preferredBranchCode = json['PreferredBranchCode']; + preferredBranchName = json['PreferredBranchName']; + if (json['MedicalReportAttachment'] != null) { + medicalReportAttachment = []; + json['MedicalReportAttachment'].forEach((v) { + medicalReportAttachment!.add(EReferralAttachment.fromJson(v)); + }); + } + insuranceCardAttachment = json['InsuranceCardAttachment'] != null ? EReferralAttachment.fromJson(json['InsuranceCardAttachment']) : null; + versionID = json['VersionID']; + channel = json['Channel']; + languageID = json['LanguageID']; + iPAdress = json['IPAdress']; + generalid = json['generalid']; + patientOutSA = json['PatientOutSA']; + sessionID = json['SessionID']; + isDentalAllowedBackend = json['isDentalAllowedBackend']; + deviceTypeID = json['DeviceTypeID']; + patientID = json['PatientID']; + tokenID = json['TokenID']; + patientTypeID = json['PatientTypeID']; + patientType = json['PatientType']; + } + + Map toJson() { + final Map data = new Map(); + data['IsInsuredPatient'] = this.isInsuredPatient; + data['CityCode'] = this.cityCode; + data['CityName'] = this.cityName; + data['RequesterName'] = this.requesterName; + data['RequesterContactNo'] = this.requesterContactNo; + data['RequesterRelationship'] = this.requesterRelationship; + data['OtherRelationship'] = this.otherRelationship; + data['FullName'] = this.fullName; + data['IdentificationNo'] = this.identificationNo; + data['PatientMobileNumber'] = this.patientMobileNumber; + data['PreferredBranchCode'] = this.preferredBranchCode; + data['PreferredBranchName'] = this.preferredBranchName; + if (this.medicalReportAttachment != null) { + // FIXED: Use map() to convert each item to JSON, then convert to list + data['MedicalReportAttachment'] = this.medicalReportAttachment!.map((v) => v.toJson()).toList(); + } + if (this.insuranceCardAttachment == null) { + data['InsuranceCardAttachment'] = {}; + } else if (this.insuranceCardAttachment is EReferralAttachment) { + // FIXED: Check if it's an EReferralAttachment before calling toJson() + data['InsuranceCardAttachment'] = (this.insuranceCardAttachment as EReferralAttachment).toJson(); + } else { + // If it's something else, assign directly + data['InsuranceCardAttachment'] = this.insuranceCardAttachment; + } + data['VersionID'] = this.versionID; + data['Channel'] = this.channel; + data['LanguageID'] = this.languageID; + data['IPAdress'] = this.iPAdress; + data['generalid'] = this.generalid; + data['PatientOutSA'] = this.patientOutSA; + data['SessionID'] = this.sessionID; + data['isDentalAllowedBackend'] = this.isDentalAllowedBackend; + data['DeviceTypeID'] = this.deviceTypeID; + data['PatientID'] = this.patientID; + data['TokenID'] = this.tokenID; + data['PatientTypeID'] = this.patientTypeID; + data['PatientType'] = this.patientType; + return data; + } +} + +class EReferralAttachment { + String? fileName; + String? base64String; + + EReferralAttachment({this.fileName, this.base64String}); + + EReferralAttachment.fromJson(Map json) { + fileName = json['FileName']; + base64String = json['Base64String']; + } + + Map toJson() { + final Map data = new Map(); + data['FileName'] = this.fileName; + data['Base64String'] = this.base64String; + return data; + } +} \ No newline at end of file diff --git a/lib/features/hmg_services/models/create_e_referral_model.dart b/lib/features/hmg_services/models/req_models/create_e_referral_req_model.dart similarity index 100% rename from lib/features/hmg_services/models/create_e_referral_model.dart rename to lib/features/hmg_services/models/req_models/create_e_referral_req_model.dart diff --git a/lib/features/hmg_services/models/req_models/send_activation_code_ereferral_req_model.dart b/lib/features/hmg_services/models/req_models/send_activation_code_ereferral_req_model.dart new file mode 100644 index 0000000..0380de8 --- /dev/null +++ b/lib/features/hmg_services/models/req_models/send_activation_code_ereferral_req_model.dart @@ -0,0 +1,56 @@ +class SendActivationCodeForEReferralRequestModel { + int? patientMobileNumber; + String? zipCode; + double? versionID; + int? channel; + int? languageID; + String? iPAdress; + String? generalid; + int? patientOutSA; + dynamic sessionID; + bool? isDentalAllowedBackend; + int? deviceTypeID; + + SendActivationCodeForEReferralRequestModel( + {this.patientMobileNumber, + this.zipCode, + this.versionID, + this.channel, + this.languageID, + this.iPAdress, + this.generalid, + this.patientOutSA, + this.sessionID, + this.isDentalAllowedBackend, + this.deviceTypeID}); + + SendActivationCodeForEReferralRequestModel.fromJson(Map json) { + patientMobileNumber = json['PatientMobileNumber']; + zipCode = json['ZipCode']; + versionID = json['VersionID']; + channel = json['Channel']; + languageID = json['LanguageID']; + iPAdress = json['IPAdress']; + generalid = json['generalid']; + patientOutSA = json['PatientOutSA']; + sessionID = json['SessionID']; + isDentalAllowedBackend = json['isDentalAllowedBackend']; + deviceTypeID = json['DeviceTypeID']; + } + + Map toJson() { + final Map data = new Map(); + data['PatientMobileNumber'] = this.patientMobileNumber; + data['ZipCode'] = this.zipCode; + data['VersionID'] = this.versionID; + data['Channel'] = this.channel; + data['LanguageID'] = this.languageID; + data['IPAdress'] = this.iPAdress; + data['generalid'] = this.generalid; + data['PatientOutSA'] = this.patientOutSA; + data['SessionID'] = this.sessionID; + data['isDentalAllowedBackend'] = this.isDentalAllowedBackend; + data['DeviceTypeID'] = this.deviceTypeID; + return data; + } +} diff --git a/lib/features/hmg_services/models/resq_models/get_all_cities_resp_model.dart b/lib/features/hmg_services/models/resq_models/get_all_cities_resp_model.dart new file mode 100644 index 0000000..efc3d58 --- /dev/null +++ b/lib/features/hmg_services/models/resq_models/get_all_cities_resp_model.dart @@ -0,0 +1,21 @@ +class GetAllCitiesResponseModel { + int? iD; + String? description; + String? descriptionN; + + GetAllCitiesResponseModel({this.iD, this.description, this.descriptionN}); + + GetAllCitiesResponseModel.fromJson(Map json) { + iD = json['ID']; + description = json['Description']; + descriptionN = json['DescriptionN']; + } + + Map toJson() { + final Map data = new Map(); + data['ID'] = this.iD; + data['Description'] = this.description; + data['DescriptionN'] = this.descriptionN; + return data; + } +} diff --git a/lib/features/hmg_services/models/resq_models/relationship_type_resp_mode.dart b/lib/features/hmg_services/models/resq_models/relationship_type_resp_mode.dart new file mode 100644 index 0000000..237017d --- /dev/null +++ b/lib/features/hmg_services/models/resq_models/relationship_type_resp_mode.dart @@ -0,0 +1,25 @@ +class GetAllRelationshipTypeResponseModel { + int? iD; + String? text; + String? textAr; + String? textEn; + + GetAllRelationshipTypeResponseModel( + {this.iD, this.text, this.textAr, this.textEn}); + + GetAllRelationshipTypeResponseModel.fromJson(Map json) { + iD = json['ID']; + text = json['Text']; + textAr = json['Text_Ar']; + textEn = json['Text_En']; + } + + Map toJson() { + final Map data = new Map(); + data['ID'] = this.iD; + data['Text'] = this.text; + data['Text_Ar'] = this.textAr; + data['Text_En'] = this.textEn; + return data; + } +} diff --git a/lib/features/hmg_services/models/ui_models/e_referral_form_model.dart b/lib/features/hmg_services/models/ui_models/e_referral_form_model.dart new file mode 100644 index 0000000..fded059 --- /dev/null +++ b/lib/features/hmg_services/models/ui_models/e_referral_form_model.dart @@ -0,0 +1,42 @@ +// models/referral_models.dart +import 'package:hmg_patient_app_new/core/common_models/nationality_country_model.dart'; +import 'package:hmg_patient_app_new/core/enums.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/create_e_referral_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_all_cities_resp_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/relationship_type_resp_mode.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; + +class ReferralFormData { + String requesterName = ''; + String requesterPhone = ''; + CountryEnum countryEnum = CountryEnum.saudiArabia; + GetAllRelationshipTypeResponseModel? relationship; + String otherRelationshipName = ''; + + String patientIdentification = ''; + String patientName = ''; + String patientPhone = ''; + GetAllCitiesResponseModel? patientCity; + + List medicalReportImages = []; + HospitalsModel? branch; + bool isPatientInsured = false; + List insuredPatientImages = []; +} + +class FormValidationErrors { + String? requesterName; + String? requesterPhone; + String? relationship; + String? otherRelationshipName; + + String? patientIdentification; + String? patientName; + String? patientCity; + String? patientPhone; + + + String? medicalReport; + String? branch; + String? insuredDocument; +} \ No newline at end of file diff --git a/lib/presentation/e_referral/e-referral_validator.dart b/lib/presentation/e_referral/e-referral_validator.dart new file mode 100644 index 0000000..78efb49 --- /dev/null +++ b/lib/presentation/e_referral/e-referral_validator.dart @@ -0,0 +1,91 @@ +// utils/referral_validator.dart +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/e_referral_form_model.dart'; + +class ReferralValidator { + static FormValidationErrors validateStep1(ReferralFormData formData) { + final errors = FormValidationErrors(); + + if (formData.requesterName.trim().isEmpty) { + errors.requesterName = 'Referral requester name is required'; + } + + if (formData.requesterPhone.trim().isEmpty) { + errors.requesterPhone = 'Phone number is required'; + } else if (formData.countryEnum.countryCode == '966' && + !_isValidSaudiPhone(formData.requesterPhone)) { + errors.requesterPhone = 'Please enter a valid Saudi phone number (5xxxxxxxx)'; + } + + if (formData.relationship == null) { + errors.relationship = 'Please select a relationship'; + } + + if (formData.relationship != null && + formData.relationship?.iD == 5 && + formData.otherRelationshipName.trim().isEmpty) { + errors.otherRelationshipName = 'Other relationship name is required'; + } + + return errors; + } + + static FormValidationErrors validateStep2(ReferralFormData formData) { + final errors = FormValidationErrors(); + + if (formData.patientIdentification.trim().isEmpty) { + errors.patientIdentification = 'Identification number is required'; + } + + if (formData.patientName.trim().isEmpty) { + errors.patientName = 'Patient name is required'; + } + + if (formData.patientPhone == null) { + errors.patientPhone = 'Please Enter patient phone number'; + } + + if (formData.patientCity == null) { + errors.patientCity = 'Please select patient city'; + } + + return errors; + } + + static FormValidationErrors validateStep3(ReferralFormData formData) { + final errors = FormValidationErrors(); + + if (formData.medicalReportImages.isEmpty) { + errors.medicalReport = 'At least one medical report is required'; + } + + if (formData.branch == null) { + errors.branch = 'Please select a branch'; + } + + if (formData.isPatientInsured && formData.insuredPatientImages.isEmpty) { + errors.insuredDocument = 'Insurance document is required for insured patients'; + } + + return errors; + } + + static bool _isValidSaudiPhone(String phone) { + final regex = RegExp(r'^5\d{8}$'); + return regex.hasMatch(phone); + } + + static bool hasErrors(FormValidationErrors errors) { + return errors.requesterName != null || + errors.requesterPhone != null || + errors.relationship != null || + errors.otherRelationshipName != null || + errors.patientIdentification != null || + errors.patientName != null || + errors.patientPhone != null || + errors.patientCity != null || + errors.medicalReport != null || + errors.branch != null || + errors.insuredDocument != null; + } +} \ No newline at end of file diff --git a/lib/presentation/e_referral/e_referral_form_manager.dart b/lib/presentation/e_referral/e_referral_form_manager.dart new file mode 100644 index 0000000..a69a2fe --- /dev/null +++ b/lib/presentation/e_referral/e_referral_form_manager.dart @@ -0,0 +1,206 @@ +// managers/referral_form_manager.dart +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/common_models/nationality_country_model.dart'; +import 'package:hmg_patient_app_new/core/enums.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/create_e_referral_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_all_cities_resp_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/relationship_type_resp_mode.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/e_referral_form_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; + + +class ReferralFormManager extends ChangeNotifier { + final ReferralFormData _formData = ReferralFormData(); + final FormValidationErrors _errors = FormValidationErrors(); + + ReferralFormData get formData => _formData; + FormValidationErrors get errors => _errors; + + // Field-specific update methods that don't notify listeners immediately + void updateRequesterName(String value) { + _formData.requesterName = value; + _clearError('requesterName'); + } + + void updateRequesterPhone(String value) { + _formData.requesterPhone = value; + _clearError('requesterPhone'); + } + + void updateCountryEnum(CountryEnum value) { + _formData.countryEnum = value; + } + + void updateRelationship(GetAllRelationshipTypeResponseModel? value) { + _formData.relationship = value; + _clearError('relationship'); + } + + void updateOtherRelationshipName(String value) { + _formData.otherRelationshipName = value; + _clearError('otherRelationshipName'); + } + + void updatePatientIdentification(String value) { + _formData.patientIdentification = value; + _clearError('patientIdentification'); + } + + void updatePatientName(String value) { + _formData.patientName = value; + _clearError('patientName'); + } + + void updatePatientPhone(String? value) { + _formData.patientPhone = value!; + _clearError('patientPhone'); + } + + void updatePatientCity(GetAllCitiesResponseModel? value) { + _formData.patientCity = value; + _clearError('patientCity'); + } + + void updateBranch(HospitalsModel? value) { + _formData.branch = value; + _clearError('branch'); + } + + void updateIsPatientInsured(bool value) { + _formData.isPatientInsured = value; + if (!value) { + _formData.insuredPatientImages.clear(); + } + _clearError('insuredDocument'); + } + + void addMedicalReport(EReferralAttachment attachment) { + _formData.medicalReportImages.add(attachment); + _clearError('medicalReport'); + } + + void removeMedicalReport(int index) { + if (index >= 0 && index < _formData.medicalReportImages.length) { + _formData.medicalReportImages.removeAt(index); + } + } + + void addInsuranceDocument(EReferralAttachment attachment) { + _formData.insuredPatientImages.add(attachment); + _clearError('insuredDocument'); + } + + void removeInsuranceDocument(int index) { + if (index >= 0 && index < _formData.insuredPatientImages.length) { + _formData.insuredPatientImages.removeAt(index); + } + } + + // Error management + void setErrors(FormValidationErrors newErrors) { + _errors.requesterName = newErrors.requesterName; + _errors.requesterPhone = newErrors.requesterPhone; + _errors.relationship = newErrors.relationship; + _errors.otherRelationshipName = newErrors.otherRelationshipName; + _errors.patientIdentification = newErrors.patientIdentification; + _errors.patientName = newErrors.patientName; + _errors.patientPhone = newErrors.patientPhone; + _errors.patientCity = newErrors.patientCity; + _errors.medicalReport = newErrors.medicalReport; + _errors.branch = newErrors.branch; + _errors.insuredDocument = newErrors.insuredDocument; + notifyListeners(); // Only notify when errors change + } + + void clearAllErrors() { + _errors.requesterName = null; + _errors.requesterPhone = null; + _errors.relationship = null; + _errors.otherRelationshipName = null; + _errors.patientIdentification = null; + _errors.patientName = null; + _errors.patientPhone = null; + _errors.patientCity = null; + _errors.medicalReport = null; + _errors.branch = null; + _errors.insuredDocument = null; + notifyListeners(); + } + + void _clearError(String field) { + bool shouldNotify = false; + + switch (field) { + case 'requesterName': + if (_errors.requesterName != null) { + _errors.requesterName = null; + shouldNotify = true; + } + break; + case 'requesterPhone': + if (_errors.requesterPhone != null) { + _errors.requesterPhone = null; + shouldNotify = true; + } + break; + case 'relationship': + if (_errors.relationship != null) { + _errors.relationship = null; + shouldNotify = true; + } + break; + case 'otherRelationshipName': + if (_errors.otherRelationshipName != null) { + _errors.otherRelationshipName = null; + shouldNotify = true; + } + break; + case 'patientIdentification': + if (_errors.patientIdentification != null) { + _errors.patientIdentification = null; + shouldNotify = true; + } + break; + case 'patientName': + if (_errors.patientName != null) { + _errors.patientName = null; + shouldNotify = true; + } + break; + case 'patientPhone': + if (_errors.patientPhone != null) { + _errors.patientPhone = null; + shouldNotify = true; + } + break; + case 'patientCity': + if (_errors.patientCity != null) { + _errors.patientCity = null; + shouldNotify = true; + } + break; + case 'medicalReport': + if (_errors.medicalReport != null) { + _errors.medicalReport = null; + shouldNotify = true; + } + break; + case 'branch': + if (_errors.branch != null) { + _errors.branch = null; + shouldNotify = true; + } + break; + case 'insuredDocument': + if (_errors.insuredDocument != null) { + _errors.insuredDocument = null; + shouldNotify = true; + } + break; + } + + if (shouldNotify) { + notifyListeners(); + } + } +} \ No newline at end of file diff --git a/lib/presentation/e_referral/e_referral_page_home.dart b/lib/presentation/e_referral/e_referral_page_home.dart index 3eb1255..d41defb 100644 --- a/lib/presentation/e_referral/e_referral_page_home.dart +++ b/lib/presentation/e_referral/e_referral_page_home.dart @@ -1,15 +1,13 @@ import 'dart:ui'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart'; -import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; -import 'package:hmg_patient_app_new/presentation/e_referral/new_referral.dart'; +import 'package:hmg_patient_app_new/presentation/e_referral/new_e_referral.dart'; +import 'package:hmg_patient_app_new/presentation/e_referral/search_e_referral.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -35,7 +33,9 @@ class _EReferralPageState extends State { } bool isNewReferral = true; - VoidCallback? onNextStep; + VoidCallback? onNextStep; + int _currentPageIndex = 0; + int activeTabIndex = 0; @override Widget build(BuildContext context) { return Scaffold( @@ -47,34 +47,53 @@ class _EReferralPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 16.h), - contactUsVM.isHMGLocationsListLoading - ? SizedBox.shrink() - : CustomTabBar( + CustomTabBar( activeTextColor: AppColors.primaryRedColor, activeBackgroundColor: AppColors.primaryRedColor.withValues(alpha: .1), tabs: [ CustomTabBarModel(null, "New Referral".needTranslation), CustomTabBarModel(null, "Search Referral".needTranslation), ], - onTabChange: (index) {}, + onTabChange: (index) { + activeTabIndex =index; + setState(() { + + }); + }, ).paddingSymmetrical(24.h, 0.h), SizedBox(height: 24.h), - NewReferralPage(onNextStep: (nextStep) { - WidgetsBinding.instance.addPostFrameCallback((_) { + activeTabIndex ==0 ? NewReferralPage( + onNextStep: (nextStep) { + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + onNextStep = nextStep; + }); + }); + }, + onStepChanged: (int value) { setState(() { - onNextStep = nextStep; - }); - }); - }) + _currentPageIndex = value; + }); + }, + ) : + SearchEReferralPage( + onNextStep: (onNextStep) { + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + onNextStep = onNextStep; + }); + }); + }, + ) ], ); }), ), bottomNavigationBar: Padding( - padding: EdgeInsets.all(16.h), + padding: EdgeInsets.all(20.h), child: CustomButton( - text: LocaleKeys.next.tr(), - onPressed:onNextStep ?? () {}, + text:activeTabIndex == 0 ? _currentPageIndex >= 2 ? LocaleKeys.submit.tr() : LocaleKeys.next.tr() : LocaleKeys.search.tr(), + onPressed: onNextStep ?? () {}, backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, diff --git a/lib/presentation/e_referral/new_e_referral.dart b/lib/presentation/e_referral/new_e_referral.dart new file mode 100644 index 0000000..ca96153 --- /dev/null +++ b/lib/presentation/e_referral/new_e_referral.dart @@ -0,0 +1,183 @@ + +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; +import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/create_e_referral_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/e_referral_form_model.dart'; +import 'package:hmg_patient_app_new/presentation/e_referral/widget/e-referral_otp.dart'; +import 'package:hmg_patient_app_new/presentation/e_referral/widget/e_referral_other_details.dart'; +import 'package:hmg_patient_app_new/presentation/e_referral/widget/e_referral_patient_info.dart'; +import 'package:hmg_patient_app_new/presentation/e_referral/widget/e_referral_requester_form.dart'; +import 'package:hmg_patient_app_new/presentation/e_referral/widget/e_referral_stepper.dart'; +import 'package:provider/provider.dart'; +import 'e-referral_validator.dart'; +import 'e_referral_form_manager.dart'; + +class NewReferralPage extends StatefulWidget { + final Function(VoidCallback) onNextStep; + final Function(int) onStepChanged; + + const NewReferralPage({ + super.key, + required this.onNextStep, + required this.onStepChanged, + }); + + @override + State createState() => _NewReferralPageState(); +} + +class _NewReferralPageState extends State { + final PageController _pageController = PageController(); + final ReferralFormManager _formManager = ReferralFormManager(); // Use manager + int _currentStep = 0; + + final List _steps = [ + 'Requester Info', + 'Patient Information', + 'Other details' + ]; + + @override + void initState() { + super.initState(); + _loadData(); + widget.onNextStep(_handleNextStep); + } + + void _handleNextStep() { + switch (_currentStep) { + case 0: + if (_validateCurrentStep()) { + OTPService.openOTPScreen( + context: context, + phoneNumber: _formManager.formData.requesterPhone ?? '', + countryEnum: _formManager.formData.countryEnum, + onSuccess: _proceedToNextStep, + ); + } + break; + case 1: + if (_validateCurrentStep()) { + _proceedToNextStep(); + } + break; + case 2: + if (_validateCurrentStep()) { + _submitReferral(); + } + break; + } + } + + bool _validateCurrentStep() { + FormValidationErrors stepErrors; + + switch (_currentStep) { + case 0: + stepErrors = ReferralValidator.validateStep1(_formManager.formData); + break; + case 1: + stepErrors = ReferralValidator.validateStep2(_formManager.formData); + break; + case 2: + stepErrors = ReferralValidator.validateStep3(_formManager.formData); + break; + default: + stepErrors = FormValidationErrors(); + } + + // Update errors through manager + _formManager.setErrors(stepErrors); + + return !ReferralValidator.hasErrors(stepErrors); + } + + void _proceedToNextStep() { + _pageController.nextPage( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + setState(() { + _currentStep++; + }); + widget.onStepChanged(_currentStep); + } + + void _submitReferral() { + + CreateEReferralRequestModel createReferrralRequestModel = + CreateEReferralRequestModel( + isInsuredPatient: _formManager.formData.isPatientInsured, + cityCode: _formManager.formData.patientCity!.iD!.toString(), + cityName: _formManager.formData.patientCity!.description, + requesterName: _formManager.formData.requesterName, + requesterContactNo: _formManager.formData.requesterPhone, + requesterRelationship: _formManager.formData.relationship?.iD, + otherRelationship: _formManager.formData.relationship!.iD.toString(), + fullName: _formManager.formData.patientName, + identificationNo: int.tryParse(_formManager.formData!.patientIdentification ?? '0'), + patientMobileNumber: _formManager.formData.patientPhone, + preferredBranchCode: _formManager.formData.branch!.iD, + medicalReportAttachment: _formManager.formData.medicalReportImages, + insuranceCardAttachment: _formManager.formData.insuredPatientImages, + preferredBranchName: _formManager.formData.branch!.desciption + ); + + final hmgServicesVM = context.read(); + hmgServicesVM.createEReferral( + requestModel: createReferrralRequestModel, + onSuccess: (response) { + print("E-Referral submitted successfully"); + }, + onError: (errorMessage) { + // Handle error (e.g., show error message) + print(errorMessage); + }, + ); + + } + + void _loadData() { + final authVM = context.read(); + final habibWalletVM = context.read(); + final hmgServicesVM = context.read(); + + hmgServicesVM.getRelationshipType(); + authVM.loadCountriesData(); + hmgServicesVM.getAllCities(); + habibWalletVM.getProjectsList(); + } + + @override + Widget build(BuildContext context) { + return ChangeNotifierProvider.value( + value: _formManager, + child: SizedBox( + height: MediaQuery.of(context).size.height, + child: Column( + children: [ + const SizedBox(height: 8), + ProgressStepperWidget( + currentStep: _currentStep, + steps: _steps, + ), + Expanded( + child: PageView( + controller: _pageController, + physics: const NeverScrollableScrollPhysics(), + onPageChanged: (index) => setState(() => _currentStep = index), + children: [ + RequesterFormStep(), + PatientInformationStep(), + OtherDetailsStep(), + ], + ), + ), + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/presentation/e_referral/new_referral.dart b/lib/presentation/e_referral/new_referral.dart deleted file mode 100644 index 73c4f5c..0000000 --- a/lib/presentation/e_referral/new_referral.dart +++ /dev/null @@ -1,432 +0,0 @@ -// dart -// File: lib/presentation/e_referral/new_referral_page.dart - -import 'dart:io'; - -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; -import 'package:hmg_patient_app_new/core/app_assets.dart'; -import 'package:hmg_patient_app_new/core/app_export.dart'; -import 'package:hmg_patient_app_new/core/app_state.dart'; -import 'package:hmg_patient_app_new/core/common_models/nationality_country_model.dart'; -import 'package:hmg_patient_app_new/core/dependencies.dart'; -import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; -import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; -import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; -import 'package:hmg_patient_app_new/features/hmg_services/models/create_e_referral_model.dart'; -import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; -import 'package:hmg_patient_app_new/theme/colors.dart'; -import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; -import 'package:hmg_patient_app_new/widgets/dropdown/dropdown_widget.dart'; -import 'package:hmg_patient_app_new/widgets/image_picker.dart'; -import 'package:hmg_patient_app_new/widgets/input_widget.dart'; -import 'package:provider/provider.dart'; - -class NewReferralPage extends StatefulWidget { - final Function(VoidCallback) onNextStep; - const NewReferralPage({super.key, required this.onNextStep}); - - @override - State createState() => NewReferralPageState(); -} - -class NewReferralPageState extends State { - final PageController _pageController = PageController(); - int pageIndex = 0; - int _tabIndex = 0; - bool isPatientInsured =false; - final TextEditingController _nameController = TextEditingController(); - final TextEditingController _phoneController = TextEditingController(); - String _country = 'Saudi Arabia'; - String? _relationship; - List medicalReportImages = []; - List insuredPatientImages = []; - void nextPressed() { - if (pageIndex < 2) { - _pageController.nextPage(duration: const Duration(milliseconds: 300), curve: Curves.easeInOut); - } else { - // submit logic - } - } - - @override - initState() { - super.initState(); - widget.onNextStep((){ - nextPressed(); - }); - - } - - Widget _progressStep({required String title, required bool active, bool showDivider = true}) { - final Color activeColor = active ? AppColors.primaryRedColor : Colors.grey.shade400; - return Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - - children: [ - CircleAvatar( - radius: 13, - backgroundColor: active ? activeColor : Colors.grey.shade300, - child: Icon(Icons.check, size: 14, color: Colors.white), - ), - if (showDivider) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: Divider(thickness: 1), - ), - - ], - ), - const SizedBox(height: 6), - Text(title, style: const TextStyle(fontSize: 11, fontWeight: FontWeight.w600)), - const SizedBox(height: 6), - Container( - padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4), - decoration: BoxDecoration( - color: active ? activeColor.withOpacity(0.15) : Colors.grey.shade100, - borderRadius: BorderRadius.circular(6), - ), - child: Text(active ? 'Active' : 'Inactive', - style: TextStyle(fontSize: 9, color: active ? activeColor : Colors.grey)), - ), - ], - ), - ); - } - - Widget _requesterForm() { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 24.0), - child: ListView( - physics: const BouncingScrollPhysics(), - children: [ - const SizedBox(height: 12), - const Text('Referral requester information', - style: TextStyle(fontWeight: FontWeight.w700, fontSize: 16)).paddingSymmetrical(4.h, 0.h), - const SizedBox(height: 12), - TextInputWidget( - controller: _nameController, - padding: const EdgeInsets.symmetric(horizontal: 16.0), - hintText: 'Enter Referral Requester Name*', labelText: 'Requester Name', - ).paddingSymmetrical(0.h, 4.h), - - Selector? countriesList, NationalityCountries? selectedCountry, bool isArabic})>( - selector: (context, authViewModel) { - final appState = getIt.get(); - return ( - countriesList: authViewModel.countriesList, - selectedCountry: authViewModel.pickedCountryByUAEUser, - isArabic: appState.isArabic(), - ); - }, - shouldRebuild: (previous, next) => previous.countriesList != next.countriesList || previous.selectedCountry != next.selectedCountry || previous.isArabic != next.isArabic, - builder: (context, data, child) { - final authVM = context.read(); - return DropdownWidget( - labelText: LocaleKeys.country.tr(), - hintText:_country, - isEnable: true, - dropdownItems: (data.countriesList ?? []).map((e) => data.isArabic ? e.nameN ?? "" : e.name ?? "").toList(), - selectedValue: data.selectedCountry != null - ? data.isArabic - ? data.selectedCountry!.nameN ?? "" - : data.selectedCountry!.name ?? "" - : "", - onChange: authVM.onUAEUserCountrySelection, - hasSelectionCustomIcon: true, - labelColor: AppColors.textColor, - padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), - selectionCustomIcon: AppAssets.arrow_down, - leadingIcon: AppAssets.globe, - ).withVerticalPadding(8); - }, - ).paddingSymmetrical(0.h, 4.h), - - TextInputWidget( - labelText: LocaleKeys.mobileNumber.tr(), - hintText: LocaleKeys.mobileNumber.tr(), - controller: null, - isEnable: true, - prefix: null, - isAllowLeadingIcon: true, - labelColor: AppColors.textColor, padding: const EdgeInsets.symmetric(horizontal: 16.0), - isReadOnly: true, - leadingIcon: AppAssets.call).paddingSymmetrical(0.h, 4.h), - - Selector? countriesList, NationalityCountries? selectedCountry, bool isArabic})>( - selector: (context, authViewModel) { - final appState = getIt.get(); - return ( - countriesList: authViewModel.countriesList, - selectedCountry: authViewModel.pickedCountryByUAEUser, - isArabic: appState.isArabic(), - ); - }, - shouldRebuild: (previous, next) => previous.countriesList != next.countriesList || previous.selectedCountry != next.selectedCountry || previous.isArabic != next.isArabic, - builder: (context, data, child) { - final authVM = context.read(); - return DropdownWidget( - labelText: "Relationship", - hintText: "Relationship*".needTranslation, - isEnable: true, - dropdownItems: (data.countriesList ?? []).map((e) => data.isArabic ? e.nameN ?? "" : e.name ?? "").toList(), - selectedValue: data.selectedCountry != null - ? data.isArabic - ? data.selectedCountry!.nameN ?? "" - : data.selectedCountry!.name ?? "" - : "", - onChange: authVM.onUAEUserCountrySelection, - hasSelectionCustomIcon: true, - labelColor: AppColors.textColor, - padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), - selectionCustomIcon: AppAssets.arrow_down, - leadingIcon: AppAssets.globe, - ).withVerticalPadding(8); - }, - ).paddingSymmetrical(0.h, 4.h), - - const SizedBox(height: 120), - ], - ), - ); - } - - Widget _patientInformation(){ - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: ListView( - physics: const BouncingScrollPhysics(), - children: [ - const SizedBox(height: 12), - 'Patient information'.toText16(weight: FontWeight.bold).paddingSymmetrical(4.h, 0.h), - const SizedBox(height: 12), - TextInputWidget( - controller: _nameController, - padding: const EdgeInsets.symmetric(horizontal: 16.0), - hintText: 'Enter Identification Number*', labelText: 'Identification Number ', - ).paddingSymmetrical(0.h, 4.h), - - TextInputWidget( - controller: _nameController, - padding: const EdgeInsets.symmetric(horizontal: 16.0), - hintText: 'Patient Name*', labelText: 'Name', - ).paddingSymmetrical(0.h, 4.h), - - Selector? countriesList, NationalityCountries? selectedCountry, bool isArabic})>( - selector: (context, authViewModel) { - final appState = getIt.get(); - return ( - countriesList: authViewModel.countriesList, - selectedCountry: authViewModel.pickedCountryByUAEUser, - isArabic: appState.isArabic(), - ); - }, - shouldRebuild: (previous, next) => previous.countriesList != next.countriesList || previous.selectedCountry != next.selectedCountry || previous.isArabic != next.isArabic, - builder: (context, data, child) { - final authVM = context.read(); - return DropdownWidget( - labelText: LocaleKeys.country.tr(), - hintText:_country, - isEnable: true, - dropdownItems: (data.countriesList ?? []).map((e) => data.isArabic ? e.nameN ?? "" : e.name ?? "").toList(), - selectedValue: data.selectedCountry != null - ? data.isArabic - ? data.selectedCountry!.nameN ?? "" - : data.selectedCountry!.name ?? "" - : "", - onChange: authVM.onUAEUserCountrySelection, - hasSelectionCustomIcon: true, - labelColor: AppColors.textColor, - padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), - selectionCustomIcon: AppAssets.arrow_down, - leadingIcon: AppAssets.globe, - ).withVerticalPadding(8); - }, - ).paddingSymmetrical(0.h, 4.h), - - - 'Where the patient located'.needTranslation.toText16(weight: FontWeight.bold).paddingSymmetrical(4.h, 0.h), - - Selector? countriesList, NationalityCountries? selectedCountry, bool isArabic})>( - selector: (context, authViewModel) { - final appState = getIt.get(); - return ( - countriesList: authViewModel.countriesList, - selectedCountry: authViewModel.pickedCountryByUAEUser, - isArabic: appState.isArabic(), - ); - }, - shouldRebuild: (previous, next) => previous.countriesList != next.countriesList || previous.selectedCountry != next.selectedCountry || previous.isArabic != next.isArabic, - builder: (context, data, child) { - final authVM = context.read(); - return DropdownWidget( - labelText: LocaleKeys.country.tr(), - hintText:_country, - isEnable: true, - dropdownItems: (data.countriesList ?? []).map((e) => data.isArabic ? e.nameN ?? "" : e.name ?? "").toList(), - selectedValue: data.selectedCountry != null - ? data.isArabic - ? data.selectedCountry!.nameN ?? "" - : data.selectedCountry!.name ?? "" - : "", - onChange: authVM.onUAEUserCountrySelection, - hasSelectionCustomIcon: true, - labelColor: AppColors.textColor, - padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), - selectionCustomIcon: AppAssets.arrow_down, - leadingIcon: AppAssets.globe, - ).withVerticalPadding(8); - }, - ).paddingSymmetrical(0.h, 4.h), - - ])); - } - - Widget _otherDetails() { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: ListView( - physics: const BouncingScrollPhysics(), - children: [ - const SizedBox(height: 12), - 'Other Details'.toText16(weight: FontWeight.bold).paddingSymmetrical(4.h, 0.h), - const SizedBox(height: 12), - - InkWell(child: TextInputWidget( - controller: _nameController, - padding: const EdgeInsets.symmetric(horizontal: 16.0), - hintText: 'Medical Report', labelText: 'Select Attachment', - suffix: Icon(Icons.attachment), - isReadOnly: true, - - ), - onTap: (){ - ImageOptions.showImageOptionsNew( - context, - true, - (String image, File file) { - setState(() { - EReferralAttachment eReferralAttachment = new EReferralAttachment(fileName: 'image ${medicalReportImages.length + 1}.png', base64String: image); - medicalReportImages.add(eReferralAttachment); - }); - }, - ); - }, - ).paddingSymmetrical(0.h, 4.h), - - Selector? countriesList, NationalityCountries? selectedCountry, bool isArabic})>( - selector: (context, authViewModel) { - final appState = getIt.get(); - return ( - countriesList: authViewModel.countriesList, - selectedCountry: authViewModel.pickedCountryByUAEUser, - isArabic: appState.isArabic(), - ); - }, - shouldRebuild: (previous, next) => previous.countriesList != next.countriesList || previous.selectedCountry != next.selectedCountry || previous.isArabic != next.isArabic, - builder: (context, data, child) { - final authVM = context.read(); - return DropdownWidget( - labelText: LocaleKeys.branch.tr(), - hintText:_country, - isEnable: true, - dropdownItems: (data.countriesList ?? []).map((e) => data.isArabic ? e.nameN ?? "" : e.name ?? "").toList(), - selectedValue: data.selectedCountry != null - ? data.isArabic - ? data.selectedCountry!.nameN ?? "" - : data.selectedCountry!.name ?? "" - : "", - onChange: authVM.onUAEUserCountrySelection, - hasSelectionCustomIcon: true, - labelColor: AppColors.textColor, - padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), - selectionCustomIcon: AppAssets.arrow_down, - leadingIcon: AppAssets.hospital, - ).withVerticalPadding(8); - }, - ).paddingSymmetrical(0.h, 4.h), - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Row( - children: [ - Checkbox( - value: isPatientInsured, - activeColor: AppColors.primaryRedColor, - onChanged: (bool? newValue) { - setState(() { - isPatientInsured = newValue!; - }); - }), - Padding( - padding: const EdgeInsets.all(5.0), - child: Text( - "Patient is Insured".needTranslation, - style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600), - ), - ), - ], - ), - ], - ).paddingSymmetrical(0.h, 4.h), - - isPatientInsured? InkWell(child: TextInputWidget( - controller: _nameController, - padding: const EdgeInsets.symmetric(horizontal: 16.0), - hintText: 'Insurance Document', labelText: 'Select Attachment', - suffix: Icon(Icons.attachment), - isReadOnly: true, - - )) : SizedBox(), - ])); - } - @override - Widget build(BuildContext context) { - final bool step0Active = pageIndex == 0; - final bool step1Active = pageIndex == 1; - final bool step2Active = pageIndex == 2; - - return SizedBox( - height: MediaQuery - .of(context) - .size - .height, // constrain height - child: Column( - children: [ - const SizedBox(height: 8), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - _progressStep(title: 'Requester Info', active: step0Active), - _progressStep(title: 'Patient Information', active: step1Active), - _progressStep( - title: 'Other details', active: step2Active, showDivider: false), - - ], - ), - ), - Expanded( - child: PageView( - controller: _pageController, - physics: const NeverScrollableScrollPhysics(), - onPageChanged: (i) => setState(() => pageIndex = i), - children: [ - _requesterForm(), - _patientInformation(), - // const Center(child: Text('Patient Info - step 2 (placeholder)')), - _otherDetails(), - ], - ), - ), - - - ], - ), - ); - } - } diff --git a/lib/presentation/e_referral/search_e_referral.dart b/lib/presentation/e_referral/search_e_referral.dart new file mode 100644 index 0000000..fabf115 --- /dev/null +++ b/lib/presentation/e_referral/search_e_referral.dart @@ -0,0 +1,98 @@ + +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/create_e_referral_model.dart'; +import 'package:hmg_patient_app_new/presentation/e_referral/widget/search_e_referral_form.dart'; +import 'package:provider/provider.dart'; +import 'e-referral_validator.dart'; +import 'e_referral_form_manager.dart'; + +class SearchEReferralPage extends StatefulWidget { + final Function(VoidCallback) onNextStep; + + + const SearchEReferralPage({ + super.key, + required this.onNextStep, + }); + + @override + State createState() => _SearchEReferralPageState(); +} + +class _SearchEReferralPageState extends State { + final PageController _pageController = PageController(); + final ReferralFormManager _formManager = ReferralFormManager(); // Use manager + int _currentStep = 0; + + + + @override + void initState() { + super.initState(); + _loadData(); + widget.onNextStep(_handleNextStep); + } + + void _handleNextStep() { + _searchReferral(); + } + + void _searchReferral() { + + CreateEReferralRequestModel createReferrralRequestModel = + CreateEReferralRequestModel( + isInsuredPatient: _formManager.formData.isPatientInsured, + cityCode: _formManager.formData.patientCity!.iD!.toString(), + cityName: _formManager.formData.patientCity!.description, + requesterName: _formManager.formData.requesterName, + requesterContactNo: _formManager.formData.requesterPhone, + requesterRelationship: _formManager.formData.relationship?.iD, + otherRelationship: _formManager.formData.relationship!.iD.toString(), + fullName: _formManager.formData.patientName, + identificationNo: int.tryParse(_formManager.formData!.patientIdentification ?? '0'), + patientMobileNumber: _formManager.formData.patientPhone, + preferredBranchCode: _formManager.formData.branch!.iD, + medicalReportAttachment: _formManager.formData.medicalReportImages, + insuranceCardAttachment: _formManager.formData.insuredPatientImages, + preferredBranchName: _formManager.formData.branch!.desciption + ); + + final hmgServicesVM = context.read(); + hmgServicesVM.createEReferral( + requestModel: createReferrralRequestModel, + onSuccess: (response) { + print("E-Referral submitted successfully"); + }, + onError: (errorMessage) { + // Handle error (e.g., show error message) + print(errorMessage); + }, + ); + + } + + void _loadData() { + + } + + @override + Widget build(BuildContext context) { + return ChangeNotifierProvider.value( + value: _formManager, + child: SizedBox( + height: MediaQuery.of(context).size.height, + child: Column( + children: [ + + Expanded( + child: + SearchEReferralFormForm(), + + ), + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/presentation/e_referral/widget/e-referral_otp.dart b/lib/presentation/e_referral/widget/e-referral_otp.dart new file mode 100644 index 0000000..3fcac6c --- /dev/null +++ b/lib/presentation/e_referral/widget/e-referral_otp.dart @@ -0,0 +1,105 @@ +// services/otp_service.dart +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/enums.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:provider/provider.dart'; +import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/check_activation_e_referral_req_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/send_activation_code_ereferral_req_model.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/features/authentication/widgets/otp_verification_screen.dart'; + +class OTPService { + static void openOTPScreen({ + required BuildContext context, + required String phoneNumber, + required CountryEnum countryEnum, + required Function onSuccess, + }) { + final hmgServicesViewModel = context.read(); + + hmgServicesViewModel.eReferralSendActivationCode( + requestModel: SendActivationCodeForEReferralRequestModel( + patientMobileNumber: int.parse(phoneNumber), + zipCode: countryEnum.countryCode, + patientOutSA: countryEnum.countryCode == '966' ? 0 : 1, + ), + onSuccess: (GenericApiModel response) { + _showOTPVerificationSheet( + context: context, + phoneNumber: phoneNumber, + loginTokenID: response.data, + onSuccess: onSuccess, + ); + }, + onError: (String errorMessage) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(errorMessage)) + ); + }, + ); + } + + static void _showOTPVerificationSheet({ + required BuildContext context, + required String phoneNumber, + required String loginTokenID, + required Function onSuccess, + }) { + showCommonBottomSheet( + context, + isFullScreen: true, + title: "OTP Verification", + isCloseButtonVisible: false, + height:ResponsiveExtension.screenHeight * 0.75, + child: OTPVerificationScreen( + phoneNumber: phoneNumber, + checkActivationCode: (int code) { + _verifyOTP( + context: context, + loginTokenID: loginTokenID, + code: code, + onSuccess: onSuccess, + ); + }, + onResendOTPPressed: (String phoneNumber) { + Navigator.pop(context); + openOTPScreen( + context: context, + phoneNumber: phoneNumber, + countryEnum: CountryEnum.saudiArabia, + onSuccess: onSuccess, + ); + }, + isFormFamilyFile: false, + ), + ); + } + + static void _verifyOTP({ + required BuildContext context, + required String loginTokenID, + required int code, + required Function onSuccess, + }) { + final hmgServicesViewModel = context.read(); + + hmgServicesViewModel.checkEReferralActivationCode( + requestModel: CheckActivationCodeForEReferralRequestModel( + logInTokenID: loginTokenID, + activationCode: code.toString(), + ), + onSuccess: (GenericApiModel response) { + Navigator.pop(context); + onSuccess(); + }, + onError: (String errorMessage) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(errorMessage)) + ); + }, + ); + } +} \ No newline at end of file diff --git a/lib/presentation/e_referral/widget/e_referral_other_details.dart b/lib/presentation/e_referral/widget/e_referral_other_details.dart new file mode 100644 index 0000000..d473dfe --- /dev/null +++ b/lib/presentation/e_referral/widget/e_referral_other_details.dart @@ -0,0 +1,308 @@ +// widgets/other_details_step.dart +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/utils/validation_utils.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/create_e_referral_model.dart'; +import 'package:hmg_patient_app_new/presentation/e_referral/e_referral_form_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; +import 'package:hmg_patient_app_new/widgets/dropdown/dropdown_widget.dart'; +import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/image_picker.dart'; + + +class OtherDetailsStep extends StatefulWidget { + const OtherDetailsStep({super.key}); + + @override + State createState() => _OtherDetailsStepState(); +} + +class _OtherDetailsStepState extends State { + final TextEditingController _medicalReportController = TextEditingController(); + final TextEditingController _insuranceController = TextEditingController(); + + late ReferralFormManager _formManager; + + @override + void initState() { + super.initState(); + _formManager = context.read(); + _updateMedicalReportText(); + _updateInsuranceText(); + } + + @override + void didUpdateWidget(OtherDetailsStep oldWidget) { + super.didUpdateWidget(oldWidget); + _updateMedicalReportText(); + _updateInsuranceText(); + } + + void _updateMedicalReportText() { + final hasMedicalReports = _formManager.formData.medicalReportImages.isNotEmpty; + _medicalReportController.text = hasMedicalReports + ? '${_formManager.formData.medicalReportImages.length} file(s) selected' + : ''; + } + + void _updateInsuranceText() { + final hasInsuranceDocs = _formManager.formData.insuredPatientImages.isNotEmpty; + _insuranceController.text = hasInsuranceDocs + ? '${_formManager.formData.insuredPatientImages.length} file(s) selected' + : ''; + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, formManager, child) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: ListView( + physics: const BouncingScrollPhysics(), + children: [ + const SizedBox(height: 12), + _buildSectionTitle('Other Details'), + const SizedBox(height: 12), + _buildMedicalReportField(formManager), + _buildBranchField(context, formManager), + _buildInsuranceCheckbox(formManager), + if (formManager.formData.isPatientInsured) _buildInsuranceField(formManager), + ], + ), + ); + }, + ); + } + + Widget _buildSectionTitle(String title) { + return Text( + title, + style: const TextStyle(fontWeight: FontWeight.w700, fontSize: 16) + ).paddingSymmetrical(4, 0); + } + + Widget _buildMedicalReportField(ReferralFormManager formManager) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + InkWell( + child: TextInputWidget( + controller: _medicalReportController, + padding: const EdgeInsets.symmetric(horizontal: 16.0), + hintText: 'Medical Report', + labelText: 'Select Attachment', + suffix: const Icon(Icons.attachment), + isReadOnly: true, + errorMessage: formManager.errors.medicalReport, + hasError: !ValidationUtils.isNullOrEmpty(formManager.errors.medicalReport), + ), + onTap: () { + ImageOptions.showImageOptionsNew( + context, + true, + (String image, File file) { + _addMedicalReport(image, file, formManager); + }, + ); + }, + ), + if (formManager.formData.medicalReportImages.isNotEmpty) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + child: Wrap( + spacing: 8.0, + children: formManager.formData.medicalReportImages.asMap().entries.map((entry) { + final index = entry.key; + return Chip( + label: Text('Medical Report ${index + 1}'), + deleteIcon: const Icon(Icons.close, size: 16), + onDeleted: () { + _removeMedicalReport(index, formManager); + }, + ); + }).toList(), + ), + ), + ], + ).paddingSymmetrical(0, 4); + } + + Widget _buildBranchField(BuildContext context, ReferralFormManager formManager) { + return DropdownWidget( + labelText: 'Branch', + hintText: formManager.formData.branch?.desciption ?? "Select Branch", + isEnable: false, + hasSelectionCustomIcon: true, + labelColor: Colors.black, + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), + selectionCustomIcon: AppAssets.arrow_down, + leadingIcon: AppAssets.hospital, + dropdownItems: [], + errorMessage: formManager.errors.branch, + hasError: !ValidationUtils.isNullOrEmpty(formManager.errors.branch), + ).paddingSymmetrical(0, 4).onPress(() { + _showBranchBottomSheet(context, formManager); + }); + } + + Widget _buildInsuranceCheckbox(ReferralFormManager formManager) { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + children: [ + Checkbox( + value: formManager.formData.isPatientInsured, + activeColor: Colors.red, + onChanged: (bool? newValue) { + final value = newValue ?? false; + formManager.updateIsPatientInsured(value); + if (!value) { + _updateInsuranceText(); + } + }, + ), + const Padding( + padding: EdgeInsets.all(5.0), + child: Text( + "Patient is Insured", + style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600), + ), + ), + ], + ), + ], + ).paddingSymmetrical(0, 4); + } + + Widget _buildInsuranceField(ReferralFormManager formManager) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + InkWell( + child: TextInputWidget( + controller: _insuranceController, + padding: const EdgeInsets.symmetric(horizontal: 16.0), + hintText: 'Insurance Document', + labelText: 'Select Attachment', + suffix: const Icon(Icons.attachment), + isReadOnly: true, + errorMessage: formManager.errors.insuredDocument, + hasError: !ValidationUtils.isNullOrEmpty(formManager.errors.insuredDocument), + ), + onTap: () { + ImageOptions.showImageOptionsNew( + context, + true, + (String image, File file) { + _addInsuranceDocument(image, file, formManager); + }, + ); + }, + ), + if (formManager.formData.insuredPatientImages.isNotEmpty) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + child: Wrap( + spacing: 8.0, + children: formManager.formData.insuredPatientImages.asMap().entries.map((entry) { + final index = entry.key; + return Chip( + label: Text('Insurance ${index + 1}'), + deleteIcon: const Icon(Icons.close, size: 16), + onDeleted: () { + _removeInsuranceDocument(index, formManager); + }, + ); + }).toList(), + ), + ), + ], + ); + } + + void _showBranchBottomSheet(BuildContext context, ReferralFormManager formManager) { + final habibWalletVM = context.read(); + + showCommonBottomSheetWithoutHeight( + context, + title: "Select Branch", + child: Consumer( + builder: (context, habibWalletVM, child) { + final hospitals = habibWalletVM.advancePaymentHospitals; + if (hospitals == null || hospitals.isEmpty) { + return const Center( + child: Padding( + padding: EdgeInsets.all(16.0), + child: Text('No branches available'), + ), + ); + } + + return ListView.separated( + shrinkWrap: true, + physics: const BouncingScrollPhysics(), + itemBuilder: (context, index) { + final branch = hospitals[index]; + return ListTile( + title: Text(branch.desciption ?? 'Unknown'), + onTap: () { + formManager.updateBranch(branch); + Navigator.pop(context); + }, + ); + }, + separatorBuilder: (context, index) => const Divider(), + itemCount: hospitals.length, + ); + }, + ), + useSafeArea: true, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + + void _addMedicalReport(String image, File file, ReferralFormManager formManager) { + final newAttachment = EReferralAttachment( + fileName: 'medical_report_${formManager.formData.medicalReportImages.length + 1}.png', + base64String: image + ); + + formManager.addMedicalReport(newAttachment); + _updateMedicalReportText(); + } + + void _removeMedicalReport(int index, ReferralFormManager formManager) { + formManager.removeMedicalReport(index); + _updateMedicalReportText(); + } + + void _addInsuranceDocument(String image, File file, ReferralFormManager formManager) { + final newAttachment = EReferralAttachment( + fileName: 'insurance_${formManager.formData.insuredPatientImages.length + 1}.png', + base64String: image + ); + + formManager.addInsuranceDocument(newAttachment); + _updateInsuranceText(); + } + + void _removeInsuranceDocument(int index, ReferralFormManager formManager) { + formManager.removeInsuranceDocument(index); + _updateInsuranceText(); + } + + @override + void dispose() { + _medicalReportController.dispose(); + _insuranceController.dispose(); + super.dispose(); + } +} \ No newline at end of file diff --git a/lib/presentation/e_referral/widget/e_referral_patient_info.dart b/lib/presentation/e_referral/widget/e_referral_patient_info.dart new file mode 100644 index 0000000..779804e --- /dev/null +++ b/lib/presentation/e_referral/widget/e_referral_patient_info.dart @@ -0,0 +1,289 @@ +// widgets/patient_information_step.dart +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/utils/validation_utils.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/presentation/e_referral/e_referral_form_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; +import 'package:hmg_patient_app_new/widgets/dropdown/dropdown_widget.dart'; +import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; + +class PatientInformationStep extends StatefulWidget { + const PatientInformationStep({super.key}); + + @override + State createState() => PatientInformationStepState(); +} + +class PatientInformationStepState extends State { + late TextEditingController _identificationController; + late TextEditingController _nameController; + late TextEditingController _phoneController; + + late FocusNode _identificationFocusNode; + late FocusNode _nameFocusNode; + late FocusNode _phoneFocusNode; + + late ReferralFormManager _formManager; + + @override + void initState() { + super.initState(); + _formManager = context.read(); + + _identificationController = TextEditingController(); + _nameController = TextEditingController(); + _phoneController = TextEditingController(); + + _identificationFocusNode = FocusNode(); + _nameFocusNode = FocusNode(); + _phoneFocusNode = FocusNode(); + + // Initialize controllers with current values + _identificationController.text = _formManager.formData.patientIdentification ?? ''; + _nameController.text = _formManager.formData.patientName ?? ''; + _phoneController.text = _formManager.formData.patientPhone ?? ''; + + // Auto-focus the identification field when the step loads + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + _identificationFocusNode.requestFocus(); + } + }); + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, formManager, child) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: ListView( + physics: const BouncingScrollPhysics(), + children: [ + const SizedBox(height: 12), + _buildSectionTitle('Patient information'), + const SizedBox(height: 12), + _buildIdentificationField(formManager), + _buildPatientNameField(formManager), + // _buildPatientCountryField(context, formManager), + _buildPatientPhoneField(formManager), + const SizedBox(height: 20), + _buildSectionTitle('Where the patient located'), + _buildPatientCityField(context, formManager), + ], + ), + ); + }, + ); + } + + Widget _buildSectionTitle(String title) { + return Text( + title, + style: const TextStyle(fontWeight: FontWeight.w700, fontSize: 16) + ).paddingSymmetrical(4, 0); + } + + Widget _buildIdentificationField(ReferralFormManager formManager) { + return Focus( + focusNode: _identificationFocusNode, + child: TextInputWidget( + controller: _identificationController, + padding: const EdgeInsets.symmetric(horizontal: 16.0), + hintText: 'Enter Identification Number*', + labelText: 'Identification Number', + errorMessage: formManager.errors.patientIdentification, + hasError: !ValidationUtils.isNullOrEmpty(formManager.errors.patientIdentification), + onChange: (value) { + formManager.updatePatientIdentification(value ?? ''); + }, + onSubmitted: (value) { + _nameFocusNode.requestFocus(); + }, + ).paddingSymmetrical(0, 4), + ); + } + + Widget _buildPatientNameField(ReferralFormManager formManager) { + return Focus( + focusNode: _nameFocusNode, + child: TextInputWidget( + controller: _nameController, + padding: const EdgeInsets.symmetric(horizontal: 16.0), + hintText: 'Patient Name*', + labelText: 'Name', + keyboardType: TextInputType.text, + errorMessage: formManager.errors.patientName, + hasError: !ValidationUtils.isNullOrEmpty(formManager.errors.patientName), + onChange: (value) { + formManager.updatePatientName(value ?? ''); + }, + onSubmitted: (value) { + // Optionally move to next field or keep focus + }, + ).paddingSymmetrical(0, 4), + ); + } + + // Widget _buildPatientCountryField(BuildContext context, ReferralFormManager formManager) { + // return DropdownWidget( + // labelText: 'Country', + // hintText: formManager.formData.patientCountry?.name ?? "Select Country", + // isEnable: false, + // hasSelectionCustomIcon: true, + // labelColor: Colors.black, + // padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), + // selectionCustomIcon: AppAssets.arrow_down, + // leadingIcon: AppAssets.globe, + // dropdownItems: [], + // errorMessage: formManager.errors.patientCountry, + // hasError: !ValidationUtils.isNullOrEmpty(formManager.errors.patientCountry), + // ).paddingSymmetrical(0, 4).onPress(() { + // _showCountryBottomSheet(context, formManager); + // }); + // } + + Widget _buildPatientPhoneField(ReferralFormManager formManager) { + return Focus( + focusNode: _phoneFocusNode, + child: TextInputWidget( + labelText: 'Phone Number', + hintText: "5xxxxxxxx", + controller: _phoneController, + padding: const EdgeInsets.all(8), + keyboardType: TextInputType.number, + onChange: (value) { + formManager.updatePatientPhone(value ?? ''); + }, + onCountryChange: (value) { + formManager.updateCountryEnum(value); + }, + prefix: '966', + isBorderAllowed: false, + isAllowLeadingIcon: true, + fontSize: 13, + isCountryDropDown: true, + leadingIcon: AppAssets.smart_phone, + errorMessage: formManager.errors.patientPhone, + hasError: !ValidationUtils.isNullOrEmpty(formManager.errors.patientPhone) + ).paddingSymmetrical(0, 8), + ); + } + + Widget _buildPatientCityField(BuildContext context, ReferralFormManager formManager) { + return DropdownWidget( + labelText: 'City', + hintText: formManager.formData.patientCity?.description ?? "Select City", + isEnable: false, + hasSelectionCustomIcon: true, + labelColor: Colors.black, + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), + selectionCustomIcon: AppAssets.arrow_down, + leadingIcon: AppAssets.globe, + dropdownItems: [], + errorMessage: formManager.errors.patientCity, + hasError: !ValidationUtils.isNullOrEmpty(formManager.errors.patientCity), + ).paddingSymmetrical(0, 4).onPress(() { + _showCityBottomSheet(context, formManager); + }); + } + + // void _showCountryBottomSheet(BuildContext context, ReferralFormManager formManager) { + // final authVM = context.read(); + // + // showCommonBottomSheetWithoutHeight( + // context, + // title: "Select Country", + // child: Consumer( + // builder: (context, authVM, child) { + // final countries = authVM.countriesList; + // if (countries == null || countries.isEmpty) { + // return const Center( + // child: Padding( + // padding: EdgeInsets.all(16.0), + // child: Text('No countries available'), + // ), + // ); + // } + // + // return ListView.separated( + // shrinkWrap: true, + // physics: const BouncingScrollPhysics(), + // itemBuilder: (context, index) { + // final country = countries[index]; + // return ListTile( + // title: Text(country.name ?? 'Unknown'), + // onTap: () { + // formManager.updatePatientCountry(country); + // Navigator.pop(context); + // }, + // ); + // }, + // separatorBuilder: (context, index) => const Divider(), + // itemCount: countries.length, + // ); + // }, + // ), + // useSafeArea: true, + // isFullScreen: false, + // isCloseButtonVisible: true, + // ); + // } + + void _showCityBottomSheet(BuildContext context, ReferralFormManager formManager) { + final hmgServicesVM = context.read(); + + showCommonBottomSheetWithoutHeight( + context, + title: "Select City", + child: Consumer( + builder: (context, hmgServicesVM, child) { + final cities = hmgServicesVM.getAllCitiesList; + if (cities == null || cities.isEmpty) { + return const Center( + child: Padding( + padding: EdgeInsets.all(16.0), + child: Text('No cities available'), + ), + ); + } + + return ListView.separated( + shrinkWrap: true, + physics: const BouncingScrollPhysics(), + itemBuilder: (context, index) { + final city = cities[index]; + return ListTile( + title: Text(city.description ?? 'Unknown'), + onTap: () { + formManager.updatePatientCity(city); + Navigator.pop(context); + }, + ); + }, + separatorBuilder: (context, index) => const Divider(), + itemCount: cities.length, + ); + }, + ), + useSafeArea: true, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + + @override + void dispose() { + _identificationController.dispose(); + _nameController.dispose(); + _phoneController.dispose(); + _identificationFocusNode.dispose(); + _nameFocusNode.dispose(); + _phoneFocusNode.dispose(); + super.dispose(); + } +} \ No newline at end of file diff --git a/lib/presentation/e_referral/widget/e_referral_requester_form.dart b/lib/presentation/e_referral/widget/e_referral_requester_form.dart new file mode 100644 index 0000000..b6952a5 --- /dev/null +++ b/lib/presentation/e_referral/widget/e_referral_requester_form.dart @@ -0,0 +1,228 @@ +// widgets/requester_form_step.dart +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/utils/validation_utils.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/e_referral_form_model.dart'; +import 'package:hmg_patient_app_new/presentation/e_referral/e-referral_validator.dart'; +import 'package:hmg_patient_app_new/presentation/e_referral/e_referral_form_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; +import 'package:hmg_patient_app_new/widgets/dropdown/dropdown_widget.dart'; +import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +// widgets/requester_form_step.dart +// widgets/requester_form_step.dart +class RequesterFormStep extends StatefulWidget { + const RequesterFormStep({super.key}); + + @override + State createState() => RequesterFormStepState(); +} + +class RequesterFormStepState extends State { + late TextEditingController _nameController; + late TextEditingController _phoneController; + late TextEditingController _otherNameController; + + late FocusNode _nameFocusNode; + late FocusNode _phoneFocusNode; + late FocusNode _otherNameFocusNode; + + late ReferralFormManager _formManager; + + @override + void initState() { + super.initState(); + _formManager = context.read(); + + _nameController = TextEditingController(); + _phoneController = TextEditingController(); + _otherNameController = TextEditingController(); + + _nameFocusNode = FocusNode(); + _phoneFocusNode = FocusNode(); + _otherNameFocusNode = FocusNode(); + + // Initialize controllers with current values + _nameController.text = _formManager.formData.requesterName ?? ''; + _phoneController.text = _formManager.formData.requesterPhone ?? ''; + _otherNameController.text = _formManager.formData.otherRelationshipName ?? ''; + + // Auto-focus the name field when the step loads + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + _nameFocusNode.requestFocus(); + } + }); + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, formManager, child) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 24.0), + child: ListView( + physics: const BouncingScrollPhysics(), + children: [ + const SizedBox(height: 12), + _buildSectionTitle('Referral requester information'), + const SizedBox(height: 12), + _buildNameField(formManager), + _buildPhoneField(formManager), + _buildRelationshipField(context, formManager), + if (_showOtherNameField(formManager)) _buildOtherNameField(formManager), + ], + ), + ); + }, + ); + } + + Widget _buildSectionTitle(String title) { + return Text( + title, + style: const TextStyle(fontWeight: FontWeight.w700, fontSize: 16) + ).paddingSymmetrical(4, 0); + } + + Widget _buildNameField(ReferralFormManager formManager) { + return Focus( + focusNode: _nameFocusNode, + child: TextInputWidget( + controller: _nameController, + padding: const EdgeInsets.symmetric(horizontal: 16.0), + hintText: 'Enter Referral Requester Name*', + labelText: 'Requester Name', + keyboardType: TextInputType.text, + errorMessage: formManager.errors.requesterName, + hasError: !ValidationUtils.isNullOrEmpty(formManager.errors.requesterName), + onChange: (value) { + formManager.updateRequesterName(value ?? ''); + }, + onSubmitted: (value) { + _phoneFocusNode.requestFocus(); + }, + ).paddingSymmetrical(0, 8), + ); + } + + Widget _buildPhoneField(ReferralFormManager formManager) { + return Focus( + focusNode: _phoneFocusNode, + child: TextInputWidget( + labelText: 'Phone Number', + hintText: "5xxxxxxxx", + controller: _phoneController, + padding: const EdgeInsets.all(8), + keyboardType: TextInputType.number, + onChange: (value) { + formManager.updateRequesterPhone(value ?? ''); + }, + onCountryChange: (value) { + formManager.updateCountryEnum(value); + }, + prefix: '966', + isBorderAllowed: false, + isAllowLeadingIcon: true, + fontSize: 13, + isCountryDropDown: true, + leadingIcon: AppAssets.smart_phone, + errorMessage: formManager.errors.requesterPhone, + hasError: !ValidationUtils.isNullOrEmpty(formManager.errors.requesterPhone) + ).paddingSymmetrical(0, 8), + ); + } + + Widget _buildRelationshipField(BuildContext context, ReferralFormManager formManager) { + return DropdownWidget( + labelText: "Relationship", + hintText: formManager.formData.relationship?.textEn ?? "Select Relation", + isEnable: false, + selectedValue: formManager.formData.relationship?.textEn ?? "Select Relation", + errorMessage: formManager.errors.relationship, + hasError: !ValidationUtils.isNullOrEmpty(formManager.errors.relationship), + hasSelectionCustomIcon: false, + labelColor: Colors.black, + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), + leadingIcon: AppAssets.user_circle, + dropdownItems: [], + ).paddingSymmetrical(0, 8).onPress(() { + _showRelationshipBottomSheet(context, formManager); + }); + } + + Widget _buildOtherNameField(ReferralFormManager formManager) { + return Focus( + focusNode: _otherNameFocusNode, + child: TextInputWidget( + controller: _otherNameController, + padding: const EdgeInsets.symmetric(horizontal: 16.0), + hintText: 'Other Name*', + labelText: 'Other Name', + errorMessage: formManager.errors.otherRelationshipName, + onChange: (value) { + formManager.updateOtherRelationshipName(value ?? ''); + }, + ).paddingSymmetrical(0, 4), + ); + } + + bool _showOtherNameField(ReferralFormManager formManager) { + return formManager.formData.relationship != null && + formManager.formData.relationship?.iD == 5; + } + + void _showRelationshipBottomSheet(BuildContext context, ReferralFormManager formManager) { + final hmgServicesVM = context.read(); + + showCommonBottomSheetWithoutHeight( + context, + title: "Select Relation", + child: Consumer( + builder: (context, hmgServicesVM, child) { + if (hmgServicesVM.relationTypes.isEmpty) { + return const Center( + child: Padding( + padding: EdgeInsets.all(16.0), + child: Text('No relationships available'), + ), + ); + } + + return ListView.separated( + shrinkWrap: true, + physics: const BouncingScrollPhysics(), + itemBuilder: (context, index) { + final relationship = hmgServicesVM.relationTypes[index]; + return ListTile( + title: Text(relationship.textEn ?? 'Unknown'), + onTap: () { + formManager.updateRelationship(relationship); + Navigator.pop(context); + }, + ); + }, + separatorBuilder: (context, index) => const Divider(), + itemCount: hmgServicesVM.relationTypes.length, + ); + }, + ), + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + + @override + void dispose() { + _nameController.dispose(); + _phoneController.dispose(); + _otherNameController.dispose(); + _nameFocusNode.dispose(); + _phoneFocusNode.dispose(); + _otherNameFocusNode.dispose(); + super.dispose(); + } +} \ No newline at end of file diff --git a/lib/presentation/e_referral/widget/e_referral_stepper.dart b/lib/presentation/e_referral/widget/e_referral_stepper.dart new file mode 100644 index 0000000..a6cd771 --- /dev/null +++ b/lib/presentation/e_referral/widget/e_referral_stepper.dart @@ -0,0 +1,72 @@ +// widgets/progress_stepper_widget.dart +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +class ProgressStepperWidget extends StatelessWidget { + final int currentStep; + final List steps; + + const ProgressStepperWidget({ + super.key, + required this.currentStep, + required this.steps, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 24.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + for (int i = 0; i < steps.length; i++) + _buildStep( + title: steps[i], + active: i == currentStep, + showDivider: i < steps.length - 1, + ), + ], + ), + ); + } + + Widget _buildStep({required String title, required bool active, bool showDivider = true}) { + final Color activeColor = active ? AppColors.primaryRedColor : Colors.grey.shade400; + + return Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + CircleAvatar( + radius: 13, + backgroundColor: active ? activeColor : Colors.grey.shade300, + child: Icon(Icons.check, size: 14, color: Colors.white), + ), + if (showDivider) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Divider(thickness: 1), + ), + ], + ), + const SizedBox(height: 6), + Text(title, style: const TextStyle(fontSize: 11, fontWeight: FontWeight.w600)), + const SizedBox(height: 6), + Container( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4), + decoration: BoxDecoration( + color: active ? activeColor.withOpacity(0.15) : Colors.grey.shade100, + borderRadius: BorderRadius.circular(6), + ), + child: Text( + active ? 'Active' : 'Inactive', + style: TextStyle(fontSize: 9, color: active ? activeColor : Colors.grey) + ), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/presentation/e_referral/widget/search_e_referral_form.dart b/lib/presentation/e_referral/widget/search_e_referral_form.dart new file mode 100644 index 0000000..37d4025 --- /dev/null +++ b/lib/presentation/e_referral/widget/search_e_referral_form.dart @@ -0,0 +1,184 @@ + +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/utils/validation_utils.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/e_referral_form_model.dart'; +import 'package:hmg_patient_app_new/presentation/e_referral/e-referral_validator.dart'; +import 'package:hmg_patient_app_new/presentation/e_referral/e_referral_form_manager.dart'; +import 'package:provider/provider.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; +import 'package:hmg_patient_app_new/widgets/dropdown/dropdown_widget.dart'; +import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +class SearchEReferralFormForm extends StatefulWidget { + const SearchEReferralFormForm({super.key}); + + @override + State createState() => SearchEReferralFormFormState(); +} + +class SearchEReferralFormFormState extends State { + late TextEditingController _searchController; + late TextEditingController _phoneController; + late FocusNode _searchFocusNode; + late FocusNode _phoneFocusNode; + + int criteria =0; + + List> criteriaList = [ + {0: 'Identification Number'}, + {1: 'Referral Number'}, + ]; + late ReferralFormManager _formManager; + + @override + void initState() { + super.initState(); + _formManager = context.read(); + + _searchController = TextEditingController(); + _phoneController = TextEditingController(); + _searchFocusNode = FocusNode(); + _phoneFocusNode = FocusNode(); + + // Initialize controllers with current values + _searchController.text = ''; + _phoneController.text = ''; + + // Auto-focus the name field when the step loads + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + _searchFocusNode.requestFocus(); + } + }); + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, formManager, child) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 24.0), + child: ListView( + physics: const BouncingScrollPhysics(), + children: [ + + const SizedBox(height: 12), + _buildSelectionField(context, formManager), + _buildNameField(formManager), + _buildPhoneField(formManager), + + ], + ), + ); + }, + ); + } + + + + Widget _buildNameField(ReferralFormManager formManager) { + return Focus( + focusNode: _searchFocusNode, + child: TextInputWidget( + controller: _searchController, + padding: const EdgeInsets.symmetric(horizontal: 16.0), + hintText:criteria ==0 ? "Enter Identification Number" : "Enter Referral Number", + labelText: criteria ==0 ? "Identification Number" : "Referral Number", + keyboardType: TextInputType.text, + // errorMessage: formManager.errors.requesterName, + hasError: !ValidationUtils.isNullOrEmpty(_searchController.text), + onChange: (value) { + + }, + onSubmitted: (value) { + + }, + ).paddingSymmetrical(0, 8), + ); + } + + Widget _buildPhoneField(ReferralFormManager formManager) { + return Focus( + focusNode: _phoneFocusNode, + child: TextInputWidget( + labelText: 'Phone Number', + hintText: "5xxxxxxxx", + controller: _phoneController, + padding: const EdgeInsets.all(8), + keyboardType: TextInputType.number, + onChange: (value) { + formManager.updateRequesterPhone(value ?? ''); + }, + onCountryChange: (value) { + formManager.updateCountryEnum(value); + }, + prefix: '966', + isBorderAllowed: false, + isAllowLeadingIcon: true, + fontSize: 13, + isCountryDropDown: true, + leadingIcon: AppAssets.smart_phone, + errorMessage: formManager.errors.requesterPhone, + hasError: !ValidationUtils.isNullOrEmpty(formManager.errors.requesterPhone) + ).paddingSymmetrical(0, 8), + ); + } + + Widget _buildSelectionField(BuildContext context, ReferralFormManager formManager) { + return DropdownWidget( + labelText: "Select the Search Criteria", + hintText: criteria ==0 ? "Identification Number" : "Referral Number", + isEnable: false, + hasSelectionCustomIcon: false, + labelColor: Colors.black, + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), + leadingIcon: AppAssets.search_icon, + dropdownItems: [], + ).paddingSymmetrical(0, 8).onPress(() { + _showCriteriaBottomSheet(context); + }); + } + + + + void _showCriteriaBottomSheet(BuildContext context,) { + + + showCommonBottomSheetWithoutHeight( + context, + title: "Select Criteria", + child: ListView.separated( + shrinkWrap: true, + physics: const BouncingScrollPhysics(), + itemBuilder: (context, index) { + return ListTile( + title: Text(criteriaList[index].values.first), + onTap: () { + setState(() { + criteria = index; + }); + Navigator.pop(context); + }, + ); + }, + separatorBuilder: (context, index) => const Divider(), + itemCount: criteriaList.length, + ), + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + + @override + void dispose() { + _searchController.dispose(); + _phoneController.dispose(); + _searchFocusNode.dispose(); + _phoneFocusNode.dispose(); + + super.dispose(); + } +} \ No newline at end of file diff --git a/lib/widgets/attachment_options.dart b/lib/widgets/attachment_options.dart index 64c2485..be443f8 100644 --- a/lib/widgets/attachment_options.dart +++ b/lib/widgets/attachment_options.dart @@ -45,7 +45,7 @@ class AttachmentOptions extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ SvgPicture.asset( - "assets/images/$icon", + "assets/images/svg/$icon", ), title.toText11(isBold: true), ], diff --git a/lib/widgets/dropdown/dropdown_widget.dart b/lib/widgets/dropdown/dropdown_widget.dart index b4062e4..5ef5e58 100644 --- a/lib/widgets/dropdown/dropdown_widget.dart +++ b/lib/widgets/dropdown/dropdown_widget.dart @@ -19,7 +19,8 @@ class DropdownWidget extends StatelessWidget { final String? selectionCustomIcon; final String? leadingIcon; final Color? labelColor; - + final String? errorMessage; + final bool? hasError; const DropdownWidget( {Key? key, required this.labelText, @@ -34,7 +35,10 @@ class DropdownWidget extends StatelessWidget { this.hasSelectionCustomIcon = false, this.selectionCustomIcon, this.leadingIcon, - this.labelColor}) + this.labelColor, + this.errorMessage, + this.hasError =false + }) : super(key: key); @override @@ -42,16 +46,18 @@ class DropdownWidget extends StatelessWidget { Widget content = Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, - children: [_buildLabelText(labelColor), _buildDropdown(context)], + children: [_buildLabelText(labelColor), _buildDropdown(context),], ); - return Container( + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [Container( padding: padding, alignment: Alignment.center, // This might need adjustment based on layout decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: Colors.white, borderRadius: isAllowRadius ? 15.h : null, - side: isBorderAllowed ? BorderSide(color: const Color(0xffefefef), width: 1) : null, + side: isBorderAllowed ? BorderSide(color: hasError! ? Colors.red: const Color(0xffefefef), width: 1) : null, ), child: Row( // Wrap with a Row @@ -62,9 +68,21 @@ class DropdownWidget extends StatelessWidget { SizedBox(width: 3.h), ], Expanded(child: content), + ], ), - ); + ), + if (hasError! && errorMessage != null) + Padding( + padding: EdgeInsets.only(top: 4.h, left: 12.h), // Adjust padding as needed + child: Text( + errorMessage!, + style: TextStyle( + color: Colors.red, + fontSize: 12.f, + ), + ), + )]); } Widget _buildLeadingIcon() {