From b03cbf518c7b37539725a3e9c71f56ff67fa2fb2 Mon Sep 17 00:00:00 2001 From: faizatflutter Date: Tue, 28 Apr 2026 18:16:29 +0300 Subject: [PATCH] updated scheduleApi Fix --- lib/core/api_consts.dart | 2 +- .../symptoms_checker_repo.dart | 2 +- .../symptoms_checker_view_model.dart | 23 ++++++++ .../review_appointment_page.dart | 55 ++++++++++++++----- .../possible_conditions_screen.dart | 33 +++++++---- .../symptoms_checker/triage_screen.dart | 48 ++++++++-------- 6 files changed, 112 insertions(+), 51 deletions(-) diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index cc2288be..c9c4aaff 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -15,6 +15,7 @@ class ApiConsts { static String symptomsCheckerApiUAT = '${hmgPharmacyApiBaseUrl}symptomsapi/api/SymptomChecker'; // dRC API URL PROD static String symptomsCheckerApiLive = '${hmgPharmacyApiBaseUrl}symptomsapi_live/api/SymptomChecker'; // dRC API URL PROD static String symptomsCheckerApi = '${hmgPharmacyApiBaseUrl}symptomsapi_live/api/SymptomChecker'; // dRC API URL PROD + static String symptomsCheckerScheduleAppointment = '$symptomsCheckerApi/ScheduleAppointment'; // Symptoms Checker Credentials static String symptomsCheckerUsername = 'mobile_user'; @@ -212,7 +213,6 @@ class ApiConsts { static final String diagnosis = '$symptomsCheckerApi/GetDiagnosis'; static final String explain = '$symptomsCheckerApi/ExplainDiagnosisResult'; static final String getClinicFromCondition = '$symptomsCheckerApi/GetClinicsByCondition?condition='; - static final String scheduleAppointment = '$symptomsCheckerApi/ScheduleAppointment'; //E-REFERRAL SERVICES static final getAllRelationshipTypes = "Services/Patients.svc/REST/GetAllRelationshipTypes"; diff --git a/lib/features/symptoms_checker/symptoms_checker_repo.dart b/lib/features/symptoms_checker/symptoms_checker_repo.dart index 824a01c4..732b8553 100644 --- a/lib/features/symptoms_checker/symptoms_checker_repo.dart +++ b/lib/features/symptoms_checker/symptoms_checker_repo.dart @@ -490,7 +490,7 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { Failure? failure; await apiClient.post( - ApiConsts.scheduleAppointment, + ApiConsts.symptomsCheckerScheduleAppointment, apiHeaders: headers, body: body, isExternal: true, diff --git a/lib/features/symptoms_checker/symptoms_checker_view_model.dart b/lib/features/symptoms_checker/symptoms_checker_view_model.dart index b6154f35..b0f7879d 100644 --- a/lib/features/symptoms_checker/symptoms_checker_view_model.dart +++ b/lib/features/symptoms_checker/symptoms_checker_view_model.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; @@ -204,6 +205,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier { /// Set flag for booking from symptoms checker void setBookingFromSymptomsChecker(bool value) { isBookingFromSymptomsChecker = value; + + log("isBookingFromSymptomsChecker: $isBookingFromSymptomsChecker"); notifyListeners(); } @@ -1043,6 +1046,26 @@ class SymptomsCheckerViewModel extends ChangeNotifier { notifyListeners(); } + /// Clear user selections (organs, symptoms, risk factors, questions) but keep results + /// This is useful when user reaches results page and we want to prevent going back to edit selections + void clearSelectionsKeepResults() { + _selectedOrganIds.clear(); + _selectedSymptomsByOrgan.clear(); + _selectedRiskFactorIds.clear(); + _selectedSuggestionsIds.clear(); + _triageEvidenceList.clear(); + _selectedTriageChoicesByItemId.clear(); + _selectedSingleItemId = null; + _selectedSingleChoiceIndex = null; + _symptomSearchQuery = ''; + _filteredOrganSymptomsResults.clear(); + _isBottomSheetExpanded = false; + _tooltipTimer?.cancel(); + _tooltipOrganId = null; + // Keep: bodySymptomResponse, riskFactorsResponse, suggestionsResponse, triageDataDetails, user info, booking flag + notifyListeners(); + } + // User Info Flow Methods /// Set current page in user info flow diff --git a/lib/presentation/book_appointment/review_appointment_page.dart b/lib/presentation/book_appointment/review_appointment_page.dart index a8954548..1a40c21b 100644 --- a/lib/presentation/book_appointment/review_appointment_page.dart +++ b/lib/presentation/book_appointment/review_appointment_page.dart @@ -1,10 +1,11 @@ +import 'dart:developer'; + 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_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; -import 'package:hmg_patient_app_new/core/utils/loading_utils.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; @@ -17,8 +18,8 @@ import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_v import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/waiting_appointment/waiting_appointment_payment_page.dart'; import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; -import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.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'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; @@ -77,7 +78,8 @@ class _ReviewAppointmentPageState extends State { Row( children: [ Image.network( - bookAppointmentsViewModel.selectedDoctor.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown.png", + bookAppointmentsViewModel.selectedDoctor.doctorImageURL ?? + "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown.png", width: 50.h, height: 50.h, fit: BoxFit.cover, @@ -93,9 +95,11 @@ class _ReviewAppointmentPageState extends State { .toString() .toText16(isBold: true, maxlines: 1), SizedBox(width: 12.w), - (bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL != null && bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL!.isNotEmpty) + (bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL != null && + bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL!.isNotEmpty) ? Image.network( - bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SAU.png", + bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL ?? + "https://hmgwebservices.com/Images/flag/SAU.png", width: 20.h, height: 15.h, fit: BoxFit.cover, @@ -104,7 +108,9 @@ class _ReviewAppointmentPageState extends State { ], ), SizedBox(height: 2.h), - (bookAppointmentsViewModel.selectedDoctor.speciality!.isNotEmpty ? bookAppointmentsViewModel.selectedDoctor.speciality!.first : "") + (bookAppointmentsViewModel.selectedDoctor.speciality!.isNotEmpty + ? bookAppointmentsViewModel.selectedDoctor.speciality!.first + : "") .toString() .toText12(isBold: true, color: AppColors.greyTextColor, maxLine: 1), ], @@ -166,7 +172,8 @@ class _ReviewAppointmentPageState extends State { spacing: 4.h, runSpacing: 4.h, children: [ - AppCustomChipWidget(labelText: "${appState.getAuthenticatedUser()!.age} ${LocaleKeys.yearsOld.tr(context: context)}"), + AppCustomChipWidget( + labelText: "${appState.getAuthenticatedUser()!.age} ${LocaleKeys.yearsOld.tr(context: context)}"), AppCustomChipWidget( labelText: "${LocaleKeys.gender.tr(context: context)}: ${appState.getAuthenticatedUser()?.gender == 1 ? LocaleKeys.malE.tr(context: context) : LocaleKeys.femaleGender.tr(context: context)}"), @@ -303,7 +310,9 @@ class _ReviewAppointmentPageState extends State { LoaderBottomSheet.hideLoader(); myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); myAppointmentsViewModel.getPatientAppointments(true, false); - showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.success.tr(context: context), child: Utils.getSuccessWidget(loadingText: apiResponse.data["SuccessMsg"]), callBackFunc: () { + showCommonBottomSheetWithoutHeight(context, + title: LocaleKeys.success.tr(context: context), + child: Utils.getSuccessWidget(loadingText: apiResponse.data["SuccessMsg"]), callBackFunc: () { Navigator.of(context).pop(); Navigator.pushAndRemoveUntil( context, @@ -315,7 +324,8 @@ class _ReviewAppointmentPageState extends State { }, onError: (error) { LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.error.tr(context: context), child: Utils.getErrorWidget(loadingText: error), callBackFunc: () { + showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.error.tr(context: context), child: Utils.getErrorWidget(loadingText: error), + callBackFunc: () { Navigator.of(context).pop(); }, isFullScreen: false); }, @@ -325,7 +335,9 @@ class _ReviewAppointmentPageState extends State { void initiateBookAppointment() async { // LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: bookAppointmentsViewModel.isPatientRescheduleAppointment ? LocaleKeys.reschedulingAppo.tr(context: context) : LocaleKeys.bookingYourAppointment.tr(context: context)); LoaderBottomSheet.showLoader( - loadingText: bookAppointmentsViewModel.isPatientRescheduleAppointment ? LocaleKeys.reschedulingAppo.tr(context: context) : LocaleKeys.bookingYourAppointment.tr(context: context)); + loadingText: bookAppointmentsViewModel.isPatientRescheduleAppointment + ? LocaleKeys.reschedulingAppo.tr(context: context) + : LocaleKeys.bookingYourAppointment.tr(context: context)); myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); if (bookAppointmentsViewModel.isLiveCareSchedule) { @@ -336,7 +348,8 @@ class _ReviewAppointmentPageState extends State { LoaderBottomSheet.hideLoader(); await Future.delayed(Duration(milliseconds: 50)).then((value) async { // LoaderBottomSheet.showLoader(loadingText: LocaleKeys.appointmentSuccess.tr()); - showCommonBottomSheetWithoutHeight(context, child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()), callBackFunc: () { + showCommonBottomSheetWithoutHeight(context, child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()), + callBackFunc: () { bookAppointmentsViewModel.setIsPatientRescheduleAppointment(false); bookAppointmentsViewModel.setIsLiveCareSchedule(false); Navigator.pushAndRemoveUntil( @@ -366,12 +379,24 @@ class _ReviewAppointmentPageState extends State { isCloseButtonVisible: true, ); }, onSuccess: (apiResp) async { + log("AppointmentNo: ${symptomsCheckerViewModel.isBookingFromSymptomsChecker}"); // Check if booking is from symptoms checker and call the API if (symptomsCheckerViewModel.isBookingFromSymptomsChecker) { final appointmentNo = apiResp.data['AppointmentNo']?.toString() ?? ''; final doctorId = bookAppointmentsViewModel.selectedDoctor.doctorID?.toString() ?? ''; - // Combine date and time for ISO string format - final appointmentDate = '${bookAppointmentsViewModel.selectedAppointmentDate} ${bookAppointmentsViewModel.selectedAppointmentTime}'; + + // Convert date and time to ISO 8601 format with timezone + DateTime? appointmentDateTime; + try { + // Combine date (YYYY-MM-DD) and time (HH:MM) into ISO format string + final dateTimeString = '${bookAppointmentsViewModel.selectedAppointmentDate}T${bookAppointmentsViewModel.selectedAppointmentTime}:00'; + appointmentDateTime = DateTime.parse(dateTimeString); + } catch (e) { + log("Error parsing appointment date/time: $e"); + } + + // Convert to ISO 8601 format with UTC timezone (e.g., "2026-02-11T13:15:37.652Z") + final appointmentDate = appointmentDateTime != null ? DateUtil.getISODateFormat(appointmentDateTime.toUtc()) : ''; final mobileNumber = appState.getAuthenticatedUser()?.mobileNumber ?? ''; final fileNo = appState.getAuthenticatedUser()?.patientId?.toString() ?? ''; final projectId = bookAppointmentsViewModel.selectedDoctor.projectID ?? 0; @@ -388,7 +413,6 @@ class _ReviewAppointmentPageState extends State { onSuccess: (response) { // Success - continue with normal flow debugPrint("onSuccess called for saveAppointmentDetailsForSymptomsChecker: ${response.data}"); - }, onError: (error) { // Log error but don't block the user flow @@ -399,7 +423,8 @@ class _ReviewAppointmentPageState extends State { LoaderBottomSheet.hideLoader(); await Future.delayed(Duration(milliseconds: 50)).then((value) async { - showCommonBottomSheetWithoutHeight(context, child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()).paddingSymmetrical(0.h, 24.h), callBackFunc: () { + showCommonBottomSheetWithoutHeight(context, + child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()).paddingSymmetrical(0.h, 24.h), callBackFunc: () { bookAppointmentsViewModel.setIsLiveCareSchedule(false); bookAppointmentsViewModel.setIsPatientRescheduleAppointment(false); Navigator.pushAndRemoveUntil(context, CustomPageRoute(page: LandingNavigation()), (r) => false); diff --git a/lib/presentation/symptoms_checker/possible_conditions_screen.dart b/lib/presentation/symptoms_checker/possible_conditions_screen.dart index 6cb1ccd1..3a113b1e 100644 --- a/lib/presentation/symptoms_checker/possible_conditions_screen.dart +++ b/lib/presentation/symptoms_checker/possible_conditions_screen.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -17,6 +19,7 @@ import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models 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/hospital_bottom_sheet/hospital_bottom_sheet_body.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/nearest_er_page.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/condition_card.dart'; @@ -29,17 +32,28 @@ 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 StatefulWidget { + const PossibleConditionsPage({super.key}); -class PossibleConditionsPage extends StatelessWidget { - PossibleConditionsPage({super.key}); + @override + State createState() => _PossibleConditionsPageState(); +} +class _PossibleConditionsPageState extends State { late SymptomsCheckerViewModel symptomsCheckerViewModel; late BookAppointmentsViewModel bookAppointmentsViewModel; late AppointmentViaRegionViewmodel regionalViewModel; - late AppState appState; + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + // Clear selections once user reaches results page + symptomsCheckerViewModel.clearSelectionsKeepResults(); + }); + } + Widget _buildLoadingShimmer() { return ListView.separated( shrinkWrap: true, @@ -114,12 +128,15 @@ class PossibleConditionsPage extends StatelessWidget { return; } + log("isBookingFromSymptomsChecker: ${symptomsCheckerViewModel.isBookingFromSymptomsChecker}"); + // For non-emergency cases, continue with normal booking flow LoaderBottomSheet.showLoader(); symptomsCheckerViewModel.getClinicConditionsFromCategory( categoryName: (condition.conditionDetails!.category!.name) ?? "Other", onSuccess: (value) { LoaderBottomSheet.hideLoader(); + debugPrint("isBookingFromSymptomsChecker:: ${symptomsCheckerViewModel.isBookingFromSymptomsChecker}"); debugPrint(symptomsCheckerViewModel.clinicDetailsList.first.clinicID.toString()); initiateBookAppointmentFlow(context); }, @@ -329,10 +346,7 @@ class PossibleConditionsPage extends StatelessWidget { titleWidget: Consumer(builder: (_, data, __) => getTitle(data, context)), isDismissible: false, child: Consumer(builder: (context, data, __) { return getRegionalSelectionWidget(data, context); - }), callBackFunc: () { - // Reset flag when bottom sheet is closed/cancelled - symptomsCheckerViewModel.setBookingFromSymptomsChecker(false); - }); + }), callBackFunc: () {}); } Widget getRegionalSelectionWidget(AppointmentViaRegionViewmodel data, BuildContext context) { @@ -368,7 +382,7 @@ class PossibleConditionsPage extends StatelessWidget { } }, onHospitalSearch: (value) { - data.searchHospitals(value ?? ""); + data.searchHospitals(value); }, selectedFacility: data.selectedFacility, hmcCount: data.hmcCount, @@ -390,7 +404,6 @@ class PossibleConditionsPage extends StatelessWidget { } else { return SizedBox.shrink(); } - return SizedBox.shrink(); } void _handleSortByLocationToggle(bool value, AppointmentViaRegionViewmodel regionVM) { diff --git a/lib/presentation/symptoms_checker/triage_screen.dart b/lib/presentation/symptoms_checker/triage_screen.dart index 442b9691..79c47d09 100644 --- a/lib/presentation/symptoms_checker/triage_screen.dart +++ b/lib/presentation/symptoms_checker/triage_screen.dart @@ -442,31 +442,31 @@ class _TriagePageState extends State { ] else ...[ // Type 2: Show all items with their choices ...List.generate(question.items!.length, (itemIndex) { - final item = question.items![itemIndex]; - final itemId = item.id ?? ""; - final choices = item.choices ?? []; - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Item name (sub-question) - (item.name ?? "").toText14(isBold: true, color: AppColors.textColor), - SizedBox(height: 8.h), - // Choices for this item - ...List.generate(choices.length, (choiceIndex) { - // Check selection based on question type - bool selected = viewModel.isTriageQuestionSingleSelection - ? viewModel.isTriageSingleOptionSelected(itemId, choiceIndex) - : viewModel.getTriageChoiceForItem(itemId) == choiceIndex; - return _buildOptionItem(itemId, choiceIndex, selected, choices[choiceIndex].label ?? ""); - }), + final item = question.items![itemIndex]; + final itemId = item.id ?? ""; + final choices = item.choices ?? []; - // Add divider between items (but not after the last one) - if (itemIndex < question.items!.length - 1) ...[ + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Item name (sub-question) + (item.name ?? "").toText14(isBold: true, color: AppColors.textColor), SizedBox(height: 8.h), - Divider(color: AppColors.bottomNAVBorder, thickness: 1), - SizedBox(height: 10.h), - ], + // Choices for this item + ...List.generate(choices.length, (choiceIndex) { + // Check selection based on question type + bool selected = viewModel.isTriageQuestionSingleSelection + ? viewModel.isTriageSingleOptionSelected(itemId, choiceIndex) + : viewModel.getTriageChoiceForItem(itemId) == choiceIndex; + return _buildOptionItem(itemId, choiceIndex, selected, choices[choiceIndex].label ?? ""); + }), + + // Add divider between items (but not after the last one) + if (itemIndex < question.items!.length - 1) ...[ + SizedBox(height: 8.h), + Divider(color: AppColors.bottomNAVBorder, thickness: 1), + SizedBox(height: 10.h), + ], ], ); }), @@ -605,7 +605,7 @@ class _TriagePageState extends State { ), ), ], - ), + ), ], ), SizedBox(height: 24.h),