From 305b3274c0788e51b6a6087b0e17404dedd816c9 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Tue, 10 Feb 2026 15:15:00 +0300 Subject: [PATCH] Symptoms checker doctor selection implemented --- lib/core/api/api_client.dart | 4 +- lib/core/api_consts.dart | 3 +- .../get_clinic_details_response_model.dart | 25 ++++ .../symptoms_checker_repo.dart | 59 ++++++++ .../symptoms_checker_view_model.dart | 40 ++++++ .../possible_conditions_screen.dart | 132 +++++++++++++++++- 6 files changed, 257 insertions(+), 6 deletions(-) create mode 100644 lib/features/symptoms_checker/models/resp_models/get_clinic_details_response_model.dart diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 231b037..2166b51 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -36,6 +36,7 @@ abstract class ApiClient { required Function(dynamic response, int statusCode) onSuccess, required Function(String error, int statusCode) onFailure, Map? queryParams, + Map apiHeaders, bool isAllowAny, bool isExternal, bool isRCService, @@ -366,6 +367,7 @@ class ApiClientImp implements ApiClient { {required Function(dynamic response, int statusCode) onSuccess, required Function(String error, int statusCode) onFailure, Map? queryParams, + Map? apiHeaders, bool isAllowAny = false, bool isExternal = false, bool isRCService = false}) async { @@ -390,7 +392,7 @@ class ApiClientImp implements ApiClient { if (await Utils.checkConnection(bypassConnectionCheck: true)) { final response = await http.get( Uri.parse(url.trim()), - headers: {'Content-Type': 'application/json', 'Accept': 'application/json'}, + headers: apiHeaders ?? {'Content-Type': 'application/json', 'Accept': 'application/json'}, ); final int statusCode = response.statusCode; // print("statusCode :$statusCode"); diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index ca94cb6..9daf09c 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -683,7 +683,7 @@ const DASHBOARD = 'Services/Patients.svc/REST/PatientDashboard'; class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT @@ -853,6 +853,7 @@ class ApiConsts { static final String getSuggestions = '$symptomsCheckerApi/GetSuggestion'; static final String diagnosis = '$symptomsCheckerApi/GetDiagnosis'; static final String explain = '$symptomsCheckerApi/ExplainDiagnosisResult'; + static final String getClinicFromCondition = '$symptomsCheckerApi/GetClinicsByCondition?condition='; //E-REFERRAL SERVICES static final getAllRelationshipTypes = "Services/Patients.svc/REST/GetAllRelationshipTypes"; diff --git a/lib/features/symptoms_checker/models/resp_models/get_clinic_details_response_model.dart b/lib/features/symptoms_checker/models/resp_models/get_clinic_details_response_model.dart new file mode 100644 index 0000000..2d878c1 --- /dev/null +++ b/lib/features/symptoms_checker/models/resp_models/get_clinic_details_response_model.dart @@ -0,0 +1,25 @@ +class GetClinicDetailsResponseModel { + String? category; + int? clinicID; + bool? isActive; + String? categoryAR; + + GetClinicDetailsResponseModel( + {this.category, this.clinicID, this.isActive, this.categoryAR}); + + GetClinicDetailsResponseModel.fromJson(Map json) { + category = json['Category']; + clinicID = json['ClinicID']; + isActive = json['IsActive']; + categoryAR = json['CategoryAR']; + } + + Map toJson() { + final Map data = new Map(); + data['Category'] = this.category; + data['ClinicID'] = this.clinicID; + data['IsActive'] = this.isActive; + data['CategoryAR'] = this.categoryAR; + return data; + } +} diff --git a/lib/features/symptoms_checker/symptoms_checker_repo.dart b/lib/features/symptoms_checker/symptoms_checker_repo.dart index c0214e1..33bcbd8 100644 --- a/lib/features/symptoms_checker/symptoms_checker_repo.dart +++ b/lib/features/symptoms_checker/symptoms_checker_repo.dart @@ -6,6 +6,7 @@ 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/symptoms_checker/models/resp_models/body_symptom_response_model.dart'; +import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/get_clinic_details_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/risk_and_suggestions_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/symptoms_user_details_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/triage_response_model.dart'; @@ -50,6 +51,11 @@ abstract class SymptomsCheckerRepo { required String userSessionToken, required String sessionId, }); + + Future>>> getClinicDetails({ + required String categoryName, + required String userSessionToken, + }); } class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { @@ -390,4 +396,57 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>>> getClinicDetails({ + required String categoryName, + required String userSessionToken, + }) async { + Map headers = { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $userSessionToken', + }; + Map body = {}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + + await apiClient.get( + "${ApiConsts.getClinicFromCondition}$categoryName", + apiHeaders: headers, + isExternal: true, + isAllowAny: true, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType ?? ServerFailure(error); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + final clinicDetails; + try { + final list = response['dataDetails']; + clinicDetails = list.map((item) => GetClinicDetailsResponseModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus ?? 1, + statusCode: statusCode, + errorMessage: errorMessage, + data: clinicDetails, + ); + } catch (e, stackTrace) { + loggerService.logError("Error parsing GetClinicsByCondition response: $e"); + loggerService.logError("StackTrace: $stackTrace"); + failure = DataParsingFailure(e.toString()); + } + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e, stackTrace) { + loggerService.logError("Exception in GetClinicsByCondition: $e"); + loggerService.logError("StackTrace: $stackTrace"); + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/symptoms_checker/symptoms_checker_view_model.dart b/lib/features/symptoms_checker/symptoms_checker_view_model.dart index 668b302..8d2d5a8 100644 --- a/lib/features/symptoms_checker/symptoms_checker_view_model.dart +++ b/lib/features/symptoms_checker/symptoms_checker_view_model.dart @@ -6,6 +6,7 @@ import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/data/organ_mapping_data.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/organ_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/body_symptom_response_model.dart'; +import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/get_clinic_details_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/risk_and_suggestions_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/symptoms_user_details_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/triage_response_model.dart'; @@ -154,6 +155,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier { /// Get the number of triage questions answered int get triageQuestionCount => _triageQuestionCount; + List clinicDetailsList = []; + /// Get choice index for a specific item int? getTriageChoiceForItem(String itemId) { return _selectedTriageChoicesByItemId[itemId]; @@ -1069,6 +1072,43 @@ class SymptomsCheckerViewModel extends ChangeNotifier { ); } + Future getClinicConditionsFromCategory({ + required String categoryName, + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + clinicDetailsList.clear(); + notifyListeners(); + + final result = await symptomsCheckerRepo.getClinicDetails( + categoryName: categoryName, + userSessionToken: currentSessionAuthToken, + ); + + result.fold( + (failure) async { + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + if (apiResponse.messageStatus == 1 && apiResponse.data != null) { + clinicDetailsList = apiResponse.data!; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse.data!); + } + } else { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? 'Failed to fetch clinic details'); + } + } + }, + ); + } + @override void dispose() { _tooltipTimer?.cancel(); diff --git a/lib/presentation/symptoms_checker/possible_conditions_screen.dart b/lib/presentation/symptoms_checker/possible_conditions_screen.dart index 3d288d7..80c7071 100644 --- a/lib/presentation/symptoms_checker/possible_conditions_screen.dart +++ b/lib/presentation/symptoms_checker/possible_conditions_screen.dart @@ -8,20 +8,32 @@ import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/route_extensions.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/book_appointments/book_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/triage_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/faculity_selection/facility_type_selection_widget.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/condition_card.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.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/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:provider/provider.dart'; import 'package:shimmer/shimmer.dart'; +import '../appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart'; + class PossibleConditionsPage extends StatelessWidget { - const PossibleConditionsPage({super.key}); + PossibleConditionsPage({super.key}); + + late SymptomsCheckerViewModel symptomsCheckerViewModel; + late BookAppointmentsViewModel bookAppointmentsViewModel; + late AppointmentViaRegionViewmodel regionalViewModel; Widget _buildLoadingShimmer() { return ListView.separated( @@ -80,8 +92,20 @@ class PossibleConditionsPage extends StatelessWidget { condition: condition, symptoms: symptoms, onActionPressed: () { - dialogService.showErrorBottomSheet( - message: 'icd10 Code is ${condition.conditionDetails?.icd10Code}. We need to get the clinics mapped against this code.', + LoaderBottomSheet.showLoader(); + symptomsCheckerViewModel.getClinicConditionsFromCategory( + categoryName: (condition.conditionDetails!.category!.name) ?? "Other", + onSuccess: (value) { + LoaderBottomSheet.hideLoader(); + print(symptomsCheckerViewModel.clinicDetailsList.first.clinicID); + initiateBookAppointmentFlow(context); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + dialogService.showErrorBottomSheet( + message: err.toString(), + ); + }, ); }, ); @@ -194,6 +218,9 @@ class PossibleConditionsPage extends StatelessWidget { @override Widget build(BuildContext context) { + symptomsCheckerViewModel = context.read(); + bookAppointmentsViewModel = context.read(); + regionalViewModel = context.read(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( @@ -202,7 +229,6 @@ class PossibleConditionsPage extends StatelessWidget { context: context, onConfirm: () { context.pop(); - final SymptomsCheckerViewModel symptomsCheckerViewModel = context.read(); symptomsCheckerViewModel.reset(); // Clear all symptoms checker data final navigationService = getIt.get(); // navigationService.replaceAllRoutesAndNavigateToLanding(); @@ -230,4 +256,102 @@ class PossibleConditionsPage extends StatelessWidget { ), ); } + + initiateBookAppointmentFlow(BuildContext context) { + bookAppointmentsViewModel.getLocation(); + bookAppointmentsViewModel.setSelectedClinic(GetClinicsListResponseModel( + clinicID: symptomsCheckerViewModel.clinicDetailsList.first.clinicID, + clinicDescription: symptomsCheckerViewModel.clinicDetailsList.first.category, + clinicDescriptionN: symptomsCheckerViewModel.clinicDetailsList.first.categoryAR, + )); + bookAppointmentsViewModel.setIsDoctorsListLoading(true); + + var bottomSheetType = RegionBottomSheetType.FOR_CLINIIC; + openRegionListBottomSheet(context, bottomSheetType); + } + + getTitle(AppointmentViaRegionViewmodel data, BuildContext context) { + if (data.selectedRegionId == null) { + return LocaleKeys.selectRegion.tr(context: context).toText20(weight: FontWeight.w600); + } else { + return Transform.flip( + flipX: data.isArabic ? true : false, + child: Utils.buildSvgWithAssets( + icon: AppAssets.arrow_back, + iconColor: Color(0xff2B353E), + fit: BoxFit.contain, + ), + ).onPress(() { + data.handleBackPress(); + }); + } + } + + void openRegionListBottomSheet(BuildContext context, RegionBottomSheetType type) { + bookAppointmentsViewModel.setProjectID(null); + + regionalViewModel.flush(); + regionalViewModel.setBottomSheetType(type); + // AppointmentViaRegionViewmodel? viewmodel = null; + showCommonBottomSheetWithoutHeight(context, title: "", titleWidget: Consumer(builder: (_, data, __) => getTitle(data, context)), isDismissible: false, + child: Consumer(builder: (context, data, __) { + return getRegionalSelectionWidget(data, context); + }), callBackFunc: () {}); + } + + Widget getRegionalSelectionWidget(AppointmentViaRegionViewmodel data, BuildContext context) { + if (data.bottomSheetState == AppointmentViaRegionState.REGION_SELECTION) { + return RegionBottomSheetBody(); + } + if (data.bottomSheetState == AppointmentViaRegionState.TYPE_SELECTION) { + bookAppointmentsViewModel.resetFilterList(); + return FacilityTypeSelectionWidget( + selectedRegion: data.selectedRegionId ?? "", + ); + } + if (data.bottomSheetState == AppointmentViaRegionState.HOSPITAL_SELECTION) { + return HospitalBottomSheetBody( + searchText: data.searchController, + displayList: data.displayList, + onFacilityClicked: (value) { + data.setSelectedFacility(value); + data.getDisplayList(); + }, + onHospitalClicked: (hospital) { + regionalViewModel.setHospitalModel(hospital); + if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_REGION) { + regionalViewModel.setBottomSheetState(AppointmentViaRegionState.CLINIC_SELECTION); + regionalViewModel.handleLastStepForRegion(); + } else if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_CLINIIC) { + regionalViewModel.setBottomSheetState(AppointmentViaRegionState.DOCTOR_SELECTION); + regionalViewModel.handleLastStepForClinic(); + } else if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER) { + var appointmentsViewModel = Provider.of(context, listen: false); + regionalViewModel.setBottomSheetState(AppointmentViaRegionState.DOCTOR_SELECTION); + regionalViewModel.handleLastStepForClinicForDentalAndLaser(appointmentsViewModel.selectedClinic.clinicID ?? -1); + } + }, + onHospitalSearch: (value) { + data.searchHospitals(value ?? ""); + }, + selectedFacility: data.selectedFacility, + hmcCount: data.hmcCount, + hmgCount: data.hmgCount, + ); + } + if (data.bottomSheetState == AppointmentViaRegionState.DOCTOR_SELECTION) { + //if the region screen is opened for the dental clinic then the project id will be in the hospital list as the list is formed form the get project api + var id = ""; + if (data.regionBottomSheetType == RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER) { + id = regionalViewModel.selectedHospital?.hospitalList.first.iD?.toString() ?? ""; + } else { + id = regionalViewModel.selectedHospital?.patientDoctorAppointmentList?.first.projectID?.toString() ?? ""; + } + bookAppointmentsViewModel.setProjectID(id); + return SizedBox.shrink(); + } else { + return SizedBox.shrink(); + } + return SizedBox.shrink(); + } }