From 162337b31740b9a66d515b19df641b9661ce3d8d Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Wed, 31 Dec 2025 11:53:06 +0300 Subject: [PATCH 1/2] Added appointment nearest gate & floor --- lib/core/api_consts.dart | 2 + .../book_appointments_repo.dart | 39 +++ .../book_appointments_view_model.dart | 32 ++ ...pointment_nearest_gate_response_model.dart | 64 ++++ .../appointment_details_page.dart | 188 ++++++------ .../widgets/appointment_card.dart | 1 + .../laser/laser_appointment.dart | 3 +- .../review_appointment_page.dart | 280 ++++++++++-------- ...ting_appointment_online_checkin_sheet.dart | 1 + .../widgets/appointment_calendar.dart | 1 + 10 files changed, 397 insertions(+), 214 deletions(-) create mode 100644 lib/features/book_appointments/models/resp_models/appointment_nearest_gate_response_model.dart diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index a3cf213..309e9df 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -334,6 +334,8 @@ var GET_PATIENT_SHARE_FOR_WALKIN_APPOINTMENT = 'Services/Doctors.svc/REST/GetChe var CAN_PAY_FOR_FOR_WALKIN_APPOINTMENT = 'Services/Doctors.svc/REST/CanPayForWalkinAppointment'; +var GET_APPOINTMENT_NEAREST_GATE = 'Services/OUTPs.svc/REST/getGateByProjectIDandClinicID'; + //URL to get medicine and pharmacies list var CHANNEL = 3; var GENERAL_ID = 'Cs2020@2016\$2958'; diff --git a/lib/features/book_appointments/book_appointments_repo.dart b/lib/features/book_appointments/book_appointments_repo.dart index f6e83ff..cfd473e 100644 --- a/lib/features/book_appointments/book_appointments_repo.dart +++ b/lib/features/book_appointments/book_appointments_repo.dart @@ -5,6 +5,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/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/appointment_nearest_gate_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/dental_chief_complaints_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctor_profile_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart'; @@ -102,6 +103,8 @@ abstract class BookAppointmentsRepo { required int userAge, Function(dynamic)? onSuccess, Function(String)? onError}); + + Future>> getAppointmentNearestGate({required int projectID, required int clinicID}); } class BookAppointmentsRepoImp implements BookAppointmentsRepo { @@ -1046,4 +1049,40 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>> getAppointmentNearestGate({required int projectID, required int clinicID}) async { + Map mapRequest = {"ProjectID": projectID, "ClinicID": clinicID}; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + GET_APPOINTMENT_NEAREST_GATE, + body: mapRequest, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final nearestGateResponse = AppointmentNearestGateResponseModel.fromJson(response['getGateByProjectIDandClinicIDList'][0]); + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: nearestGateResponse, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index ae74ffb..cbed940 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -14,6 +14,7 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_repo.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/LaserCategoryType.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/free_slot.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/appointment_nearest_gate_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/dental_chief_complaints_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctor_profile_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart'; @@ -44,6 +45,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { bool isDoctorsListLoading = false; bool isDoctorProfileLoading = false; bool isDoctorSearchByNameStarted = false; + bool isAppointmentNearestGateLoading = false; bool isLiveCareSchedule = false; bool isGetDocForHealthCal = false; @@ -132,6 +134,8 @@ class BookAppointmentsViewModel extends ChangeNotifier { PatientAppointmentShareResponseModel? patientWalkInAppointmentShareResponseModel; + AppointmentNearestGateResponseModel? appointmentNearestGateResponseModel; + ///variables for laser clinic List femaleLaserCategory = [ LaserCategoryType(1, 'bodyString'), @@ -1343,4 +1347,32 @@ class BookAppointmentsViewModel extends ChangeNotifier { }, ); } + + Future getAppointmentNearestGate({required int projectID, required int clinicID, Function(dynamic)? onSuccess, Function(String)? onError}) async { + isAppointmentNearestGateLoading = true; + notifyListeners(); + + final result = await bookAppointmentsRepo.getAppointmentNearestGate(projectID: projectID, clinicID: clinicID); + + result.fold( + (failure) async { + if (onError != null) { + onError(failure.message); + } + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError!(apiResponse.errorMessage!); + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + appointmentNearestGateResponseModel = apiResponse.data!; + isAppointmentNearestGateLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } } diff --git a/lib/features/book_appointments/models/resp_models/appointment_nearest_gate_response_model.dart b/lib/features/book_appointments/models/resp_models/appointment_nearest_gate_response_model.dart new file mode 100644 index 0000000..bdaa4e2 --- /dev/null +++ b/lib/features/book_appointments/models/resp_models/appointment_nearest_gate_response_model.dart @@ -0,0 +1,64 @@ +class AppointmentNearestGateResponseModel { + String? clinicDescription; + String? clinicDescriptionN; + int? clinicID; + String? clinicLocation; + String? clinicLocationN; + int? gender; + int? iD; + String? nearestGateNumber; + String? nearestGateNumberN; + int? projectID; + String? projectName; + String? projectNameN; + int? rowID; + + AppointmentNearestGateResponseModel( + {this.clinicDescription, + this.clinicDescriptionN, + this.clinicID, + this.clinicLocation, + this.clinicLocationN, + this.gender, + this.iD, + this.nearestGateNumber, + this.nearestGateNumberN, + this.projectID, + this.projectName, + this.projectNameN, + this.rowID}); + + AppointmentNearestGateResponseModel.fromJson(Map json) { + clinicDescription = json['ClinicDescription']; + clinicDescriptionN = json['ClinicDescriptionN']; + clinicID = json['ClinicID']; + clinicLocation = json['ClinicLocation']; + clinicLocationN = json['ClinicLocationN']; + gender = json['Gender']; + iD = json['ID']; + nearestGateNumber = json['NearestGateNumber']; + nearestGateNumberN = json['NearestGateNumberN']; + projectID = json['ProjectID']; + projectName = json['ProjectName']; + projectNameN = json['ProjectNameN']; + rowID = json['RowID']; + } + + Map toJson() { + final Map data = Map(); + data['ClinicDescription'] = clinicDescription; + data['ClinicDescriptionN'] = clinicDescriptionN; + data['ClinicID'] = clinicID; + data['ClinicLocation'] = clinicLocation; + data['ClinicLocationN'] = clinicLocationN; + data['Gender'] = gender; + data['ID'] = iD; + data['NearestGateNumber'] = nearestGateNumber; + data['NearestGateNumberN'] = nearestGateNumberN; + data['ProjectID'] = projectID; + data['ProjectName'] = projectName; + data['ProjectNameN'] = projectNameN; + data['RowID'] = rowID; + return data; + } +} diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index 7b605b9..7b2439c 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -4,6 +4,8 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/api_consts.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/calender_utils_new.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; @@ -31,6 +33,7 @@ import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_deta 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'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; @@ -132,10 +135,7 @@ class _AppointmentDetailsPageState extends State { }, onCancelTap: () async { myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); - var isEventAddedOrRemoved = await CalenderUtilsNew.instance.checkAndRemove( id:"${widget.patientAppointmentHistoryResponseModel.appointmentNo}", ); - setState(() { - myAppointmentsViewModel.setAppointmentReminder(isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel); - }); + LoaderBottomSheet.showLoader(loadingText: "Cancelling Appointment, Please Wait...".needTranslation); await myAppointmentsViewModel.cancelAppointment( patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, @@ -155,6 +155,11 @@ class _AppointmentDetailsPageState extends State { isFullScreen: false, ); }); + + var isEventAddedOrRemoved = await CalenderUtilsNew.instance.checkAndRemove( id:"${widget.patientAppointmentHistoryResponseModel.appointmentNo}", ); + setState(() { + myAppointmentsViewModel.setAppointmentReminder(isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel); + }); }, onRescheduleTap: () async { openDoctorScheduleCalendar(); @@ -164,90 +169,105 @@ class _AppointmentDetailsPageState extends State { !AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) ? Column( children: [ - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.r, - hasShadow: false, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - "Appointment Status".needTranslation.toText16(isBold: true), - ], - ), - SizedBox(height: 4.h), - (!AppointmentType.isConfirmed(widget.patientAppointmentHistoryResponseModel) - ? "Not Confirmed".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500) - : "Confirmed".needTranslation.toText12(color: AppColors.successColor, fontWeight: FontWeight.w500)), - SizedBox(height: 16.h), - //TODO Add countdown timer in case of LiveCare Appointment - widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false - ? Row( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.livecare_clinic_icon, width: 40.h, height: 40.h), - SizedBox(width: 12.w), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "The doctor will call you once the appointment time approaches." - .needTranslation - .toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), - ], + Consumer(builder: (context, bookAppointmentsVM, child) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.r, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + "Appointment Status".needTranslation.toText16(isBold: true), + ], + ), + SizedBox(height: 4.h), + (!AppointmentType.isConfirmed(widget.patientAppointmentHistoryResponseModel) + ? "Not Confirmed".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500) + : "Confirmed".needTranslation.toText12(color: AppColors.successColor, fontWeight: FontWeight.w500)), + SizedBox(height: 16.h), + //TODO Add countdown timer in case of LiveCare Appointment + widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false + ? Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.livecare_clinic_icon, width: 40.h, height: 40.h), + SizedBox(width: 12.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "The doctor will call you once the appointment time approaches." + .needTranslation + .toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + ], + ), ), - ), - ], - ) - : Stack( - children: [ - ClipRRect( - clipBehavior: Clip.hardEdge, - borderRadius: BorderRadius.circular(24.r), - // Todo: what is this???? Api Key??? 😲 - child: Image.network( - "https://maps.googleapis.com/maps/api/staticmap?center=${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&zoom=14&size=350x165&maptype=roadmap&markers=color:red%7C${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&key=${ApiKeyConstants.googleMapsApiKey}", - fit: BoxFit.contain, + ], + ) + : Stack( + children: [ + ClipRRect( + clipBehavior: Clip.hardEdge, + borderRadius: BorderRadius.circular(24.r), + // Todo: what is this???? Api Key??? 😲 + child: Image.network( + "https://maps.googleapis.com/maps/api/staticmap?center=${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&zoom=14&size=350x165&maptype=roadmap&markers=color:red%7C${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&key=${ApiKeyConstants.googleMapsApiKey}", + fit: BoxFit.contain, + ), ), - ), - Positioned( - bottom: 0, - child: SizedBox( - width: MediaQuery.of(context).size.width * 0.785, - child: CustomButton( - text: "Get Directions".needTranslation, - onPressed: () { - MapsLauncher.launchCoordinates( - double.parse(widget.patientAppointmentHistoryResponseModel.latitude!), - double.parse(widget.patientAppointmentHistoryResponseModel.longitude!), - widget.patientAppointmentHistoryResponseModel.projectName); - }, - backgroundColor: AppColors.textColor.withValues(alpha: 0.8), - borderColor: AppointmentType.getNextActionButtonColor( - widget.patientAppointmentHistoryResponseModel.nextAction) - .withValues(alpha: 0.01), - textColor: AppColors.whiteColor, - fontSize: 14.f, - fontWeight: FontWeight.w500, - borderRadius: 12.r, - padding: EdgeInsets.symmetric(horizontal: 10.w), - height: 40.h, - icon: AppAssets.directions_icon, - iconColor: AppColors.whiteColor, - iconSize: 14.h, - ).paddingAll(12.h), + Positioned( + bottom: 0, + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.785, + child: CustomButton( + text: "Get Directions".needTranslation, + onPressed: () { + MapsLauncher.launchCoordinates(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!), + double.parse(widget.patientAppointmentHistoryResponseModel.longitude!), widget.patientAppointmentHistoryResponseModel.projectName); + }, + backgroundColor: AppColors.textColor.withValues(alpha: 0.8), + borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.01), + textColor: AppColors.whiteColor, + fontSize: 14.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: 40.h, + icon: AppAssets.directions_icon, + iconColor: AppColors.whiteColor, + iconSize: 14.h, + ).paddingAll(12.h), + ), ), - ), - ], - ), - ], + ], + ), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + spacing: 8.w, + runSpacing: 8.h, + children: [ + AppCustomChipWidget( + labelText: bookAppointmentsVM.isAppointmentNearestGateLoading + ? "Floor: Ground Floor" + : "Floor: ${getIt.get().isArabic() ? bookAppointmentsViewModel.appointmentNearestGateResponseModel!.clinicLocationN : bookAppointmentsViewModel.appointmentNearestGateResponseModel!.clinicLocation}", + ).toShimmer2(isShow: bookAppointmentsVM.isAppointmentNearestGateLoading), + AppCustomChipWidget( + labelText: + "Nearest Gate: ${getIt.get().isArabic() ? bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumberN : bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumber}") + .toShimmer2(isShow: bookAppointmentsVM.isAppointmentNearestGateLoading), + ], + ), + ], + ), ), - ), - ), + ); + }), SizedBox(height: 16.h), Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index 57e19ca..6849253 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -372,6 +372,7 @@ class AppointmentCard extends StatelessWidget { ), ); } else { + bookAppointmentsViewModel.getAppointmentNearestGate(projectID: patientAppointmentHistoryResponseModel.projectID, clinicID: patientAppointmentHistoryResponseModel.clinicID); Navigator.of(context) .push( CustomPageRoute( diff --git a/lib/presentation/book_appointment/laser/laser_appointment.dart b/lib/presentation/book_appointment/laser/laser_appointment.dart index 28f0f2d..144c261 100644 --- a/lib/presentation/book_appointment/laser/laser_appointment.dart +++ b/lib/presentation/book_appointment/laser/laser_appointment.dart @@ -1,6 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.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/laser_body_parts.dart'; @@ -81,7 +82,7 @@ class LaserAppointment extends StatelessWidget { activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), tabs: [ CustomTabBarModel(null,LocaleKeys.malE.tr()), - CustomTabBarModel(null,"Female".needTranslation.tr()), + CustomTabBarModel(null, "Female".needTranslation), ], onTabChange: (index) { var viewmodel = context.read(); diff --git a/lib/presentation/book_appointment/review_appointment_page.dart b/lib/presentation/book_appointment/review_appointment_page.dart index 40abd40..0ee7a33 100644 --- a/lib/presentation/book_appointment/review_appointment_page.dart +++ b/lib/presentation/book_appointment/review_appointment_page.dart @@ -50,146 +50,168 @@ class _ReviewAppointmentPageState extends State { Expanded( child: CollapsingListView( title: LocaleKeys.reviewAppointment.tr(context: context), - child: SingleChildScrollView( - padding: EdgeInsets.symmetric(horizontal: 24.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 24.h), - LocaleKeys.docInfo.tr(context: context).toText16(isBold: true), - SizedBox(height: 16.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.h, - hasShadow: false, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Image.network( - bookAppointmentsViewModel.selectedDoctor.doctorImageURL!, - width: 50.h, - height: 50.h, - fit: BoxFit.cover, - ).circle(100), - SizedBox(width: 8.h), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - SizedBox( - width: MediaQuery.of(context).size.width * 0.49, - child: - "${bookAppointmentsViewModel.selectedDoctor.doctorTitle} ${bookAppointmentsViewModel.selectedDoctor.name}".toString().toText16(isBold: true, maxlines: 1), - ), - Image.network( - bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL!, - width: 20.h, - height: 15.h, - fit: BoxFit.cover, - ), - ], - ), - SizedBox(height: 2.h), - (bookAppointmentsViewModel.selectedDoctor.speciality!.isNotEmpty ? bookAppointmentsViewModel.selectedDoctor.speciality!.first : "") - .toString() - .toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor, maxLine: 1), - ], - ), - ], - ), - SizedBox(height: 12.h), - Wrap( - direction: Axis.horizontal, - spacing: 8.h, - runSpacing: 8.h, - children: [ - AppCustomChipWidget( - labelText: "${LocaleKeys.clinic.tr(context: context)}: ${bookAppointmentsViewModel.selectedDoctor.clinicName}".needTranslation, - ), - AppCustomChipWidget( - labelText: "${LocaleKeys.branch.tr(context: context)} ${bookAppointmentsViewModel.selectedDoctor.projectName}".needTranslation, - ), - AppCustomChipWidget( - labelText: - "${LocaleKeys.date.tr(context: context)}: ${bookAppointmentsViewModel.isWaitingAppointmentSelected ? DateUtil.formatDateToDate(DateTime.now(), false) : bookAppointmentsViewModel.selectedAppointmentDate}" - .needTranslation, - ), - AppCustomChipWidget( - labelText: - "${LocaleKeys.time.tr(context: context)}: ${bookAppointmentsViewModel.isWaitingAppointmentSelected ? "Waiting Appointment".needTranslation : bookAppointmentsViewModel.selectedAppointmentTime}" - .needTranslation, - ), - ], - ), - ], + child: Consumer(builder: (context, bookAppointmentsVM, child) { + return SingleChildScrollView( + padding: EdgeInsets.symmetric(horizontal: 24.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 24.h), + LocaleKeys.docInfo.tr(context: context).toText16(isBold: true), + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: false, ), - ), - ), - SizedBox(height: 24.h), - LocaleKeys.patientInfo.tr(context: context).toText16(isBold: true), - SizedBox(height: 16.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.h, - hasShadow: false, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Row( - children: [ - Image.asset( - appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.maleImg : AppAssets.femaleImg, - width: 52.h, - height: 52.h, - ), - SizedBox(width: 8.h), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}".toText16(isBold: true), - SizedBox(height: 8.h), - AppCustomChipWidget(labelText: "${appState.getAuthenticatedUser()!.age} Years Old"), - ], - ), - ], + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Image.network( + bookAppointmentsViewModel.selectedDoctor.doctorImageURL!, + width: 50.h, + height: 50.h, + fit: BoxFit.cover, + ).circle(100), + SizedBox(width: 8.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + SizedBox( + width: MediaQuery.of(context).size.width * 0.49, + child: "${bookAppointmentsViewModel.selectedDoctor.doctorTitle} ${bookAppointmentsViewModel.selectedDoctor.name}" + .toString() + .toText16(isBold: true, maxlines: 1), + ), + Image.network( + bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL!, + width: 20.h, + height: 15.h, + fit: BoxFit.cover, + ), + ], + ), + SizedBox(height: 2.h), + (bookAppointmentsViewModel.selectedDoctor.speciality!.isNotEmpty ? bookAppointmentsViewModel.selectedDoctor.speciality!.first : "") + .toString() + .toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor, maxLine: 1), + ], + ), + ], + ), + SizedBox(height: 12.h), + Wrap( + direction: Axis.horizontal, + spacing: 8.h, + runSpacing: 8.h, + children: [ + AppCustomChipWidget( + labelText: "${LocaleKeys.clinic.tr(context: context)}: ${bookAppointmentsViewModel.selectedDoctor.clinicName}".needTranslation, + ), + AppCustomChipWidget( + labelText: "${LocaleKeys.branch.tr(context: context)} ${bookAppointmentsViewModel.selectedDoctor.projectName}".needTranslation, + ), + AppCustomChipWidget( + labelText: + "${LocaleKeys.date.tr(context: context)}: ${bookAppointmentsViewModel.isWaitingAppointmentSelected ? DateUtil.formatDateToDate(DateTime.now(), false) : bookAppointmentsViewModel.selectedAppointmentDate}" + .needTranslation, + ), + AppCustomChipWidget( + labelText: + "${LocaleKeys.time.tr(context: context)}: ${bookAppointmentsViewModel.isWaitingAppointmentSelected ? "Waiting Appointment".needTranslation : bookAppointmentsViewModel.selectedAppointmentTime}" + .needTranslation, + ), + ], + ), + ], + ), ), ), - ), - SizedBox(height: 24.h), - "Hospital Information".needTranslation.toText16(isBold: true), - SizedBox(height: 16.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 12.h, - hasShadow: false, + SizedBox(height: 24.h), + LocaleKeys.patientInfo.tr(context: context).toText16(isBold: true), + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Row( + children: [ + Image.asset( + appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.maleImg : AppAssets.femaleImg, + width: 52.h, + height: 52.h, + ), + SizedBox(width: 8.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}".toText16(isBold: true), + SizedBox(height: 8.h), + AppCustomChipWidget(labelText: "${appState.getAuthenticatedUser()!.age} Years Old"), + ], + ), + ], + ), + ), ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Row( - children: [ - bookAppointmentsViewModel.selectedDoctor.projectName!.toText16(isBold: true), - ], + SizedBox(height: 24.h), + "Hospital Information".needTranslation.toText16(isBold: true), + SizedBox(height: 16.h), + Container( + width: double.infinity, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + bookAppointmentsViewModel.selectedDoctor.projectName!.toText16(isBold: true), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + spacing: 8.w, + runSpacing: 8.h, + children: [ + AppCustomChipWidget( + labelText: bookAppointmentsVM.isAppointmentNearestGateLoading + ? "Floor: Ground Floor" + : "Floor: ${getIt.get().isArabic() ? bookAppointmentsViewModel.appointmentNearestGateResponseModel!.clinicLocationN : bookAppointmentsViewModel.appointmentNearestGateResponseModel!.clinicLocation}", + ).toShimmer2(isShow: bookAppointmentsVM.isAppointmentNearestGateLoading), + AppCustomChipWidget( + labelText: + "Nearest Gate: ${getIt.get().isArabic() ? bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumberN : bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumber}") + .toShimmer2(isShow: bookAppointmentsVM.isAppointmentNearestGateLoading), + ], + ), + ], + ), ), ), - ), - ], - ), - ), + ], + ), + ); + }), ), ), Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, - borderRadius: 24.h, + borderRadius: 24.r, hasShadow: true, ), child: CustomButton( diff --git a/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_online_checkin_sheet.dart b/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_online_checkin_sheet.dart index 8a04843..4a2a304 100644 --- a/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_online_checkin_sheet.dart +++ b/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_online_checkin_sheet.dart @@ -142,6 +142,7 @@ class WaitingAppointmentOnlineCheckinSheet extends StatelessWidget { bookAppointmentsViewModel.waitingAppointmentProjectID, onSuccess: (value) { LoaderBottomSheet.hideLoader(); + bookAppointmentsViewModel.getAppointmentNearestGate(projectID: bookAppointmentsViewModel.waitingAppointmentProjectID, clinicID: bookAppointmentsViewModel.waitingAppointmentDoctor!.clinicID!); bookAppointmentsViewModel.setIsWaitingAppointmentSelected(true); Navigator.of(context).push( CustomPageRoute( diff --git a/lib/presentation/book_appointment/widgets/appointment_calendar.dart b/lib/presentation/book_appointment/widgets/appointment_calendar.dart index b2666b6..3a9f57d 100644 --- a/lib/presentation/book_appointment/widgets/appointment_calendar.dart +++ b/lib/presentation/book_appointment/widgets/appointment_calendar.dart @@ -185,6 +185,7 @@ class _AppointmentCalendarState extends State { ), ); } else { + bookAppointmentsViewModel.getAppointmentNearestGate(projectID: bookAppointmentsViewModel.selectedDoctor.projectID!, clinicID: bookAppointmentsViewModel.selectedDoctor.clinicID!); bookAppointmentsViewModel.setSelectedAppointmentDateTime(selectedDate, selectedTime); Navigator.of(context).pop(); Navigator.of(context).push( From e9590a15d384ebf448424e87511285d95967e2c0 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Wed, 31 Dec 2025 12:39:15 +0300 Subject: [PATCH 2/2] updates & fixes --- .../appointment_details_page.dart | 1 - .../appointments/my_appointments_page.dart | 81 ++----------------- lib/presentation/lab/lab_orders_page.dart | 2 +- 3 files changed, 9 insertions(+), 75 deletions(-) diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index 7b2439c..dae8d25 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -155,7 +155,6 @@ class _AppointmentDetailsPageState extends State { isFullScreen: false, ); }); - var isEventAddedOrRemoved = await CalenderUtilsNew.instance.checkAndRemove( id:"${widget.patientAppointmentHistoryResponseModel.appointmentNo}", ); setState(() { myAppointmentsViewModel.setAppointmentReminder(isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel); diff --git a/lib/presentation/appointments/my_appointments_page.dart b/lib/presentation/appointments/my_appointments_page.dart index abf2464..eb72bc6 100644 --- a/lib/presentation/appointments/my_appointments_page.dart +++ b/lib/presentation/appointments/my_appointments_page.dart @@ -22,6 +22,7 @@ import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointme 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/custom_tab_bar.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/date_range_calender.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart'; @@ -187,18 +188,9 @@ class _MyAppointmentsPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - CustomButton( - text: "${myAppointmentsVM.patientAppointmentsViewList[index].patientDoctorAppointmentList!.length} Appointments", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), + AppCustomChipWidget( + labelText: + "${myAppointmentsVM.patientAppointmentsViewList[index].patientDoctorAppointmentList!.length} Appointments"), Icon(isExpanded ? Icons.expand_less : Icons.expand_more), ], ), @@ -224,7 +216,7 @@ class _MyAppointmentsPageState extends State { child: isExpanded ? Container( key: ValueKey(index), - padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h), + padding: EdgeInsets.symmetric(horizontal: 0.w, vertical: 0.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -232,63 +224,6 @@ class _MyAppointmentsPageState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Image.network( - appointment.doctorImageURL!, - width: 24.h, - height: 24.h, - fit: BoxFit.fill, - ).circle(100), - SizedBox(width: 8.h), - Expanded(child: appointment.doctorNameObj!.toText14(weight: FontWeight.w500)), - ], - ), - SizedBox(height: 8.h), - Row( - children: [ - CustomButton( - text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(appointment.appointmentDate), false), - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 24.h, - ), - SizedBox(width: 8.h), - CustomButton( - text: myAppointmentsVM.isAppointmentsSortByClinic ? appointment.projectName! : appointment.clinicName!, - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 24.h, - ), - SizedBox(width: 8.h), - CustomButton( - text: appointment.statusDesc ?? "", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 24.h, - ), - ], - ), - SizedBox(height: 8.h), AppointmentCard( patientAppointmentHistoryResponseModel: appointment, myAppointmentsViewModel: myAppointmentsViewModel, @@ -296,9 +231,9 @@ class _MyAppointmentsPageState extends State { isLoading: false, isFromHomePage: false, ), - SizedBox(height: 12.h), - Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), - SizedBox(height: 12.h), + SizedBox(height: 8.h), + Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h).paddingSymmetrical(16.w, 0.h), + SizedBox(height: 8.h), ], ); }), diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index c131275..90651f1 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -1 +1 @@ -import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.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/enums.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'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_toolbar.dart'; import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; import 'alphabeticScroll.dart'; class LabOrdersPage extends StatefulWidget { const LabOrdersPage({super.key}); @override State createState() => _LabOrdersPageState(); } class _LabOrdersPageState extends State { late LabViewModel labProvider; late DateRangeSelectorRangeViewModel rangeViewModel; late AppState _appState; List?> labSuggestions = []; int? expandedIndex; String? selectedFilterText = ''; int activeIndex = 0; @override void initState() { scheduleMicrotask(() { labProvider.initLabProvider(); }); super.initState(); } @override Widget build(BuildContext context) { labProvider = Provider.of(context, listen: false); rangeViewModel = Provider.of(context); _appState = getIt(); return CollapsingToolbar( title: LocaleKeys.labResults.tr(), search: () async { final lavVM = Provider.of(context, listen: false); if (lavVM.isLabOrdersLoading) { return; } else { String? value = await Navigator.of(context).push( CustomPageRoute( page: SearchLabResultsContent(labSuggestionsList: lavVM.labSuggestions), fullScreenDialog: true, direction: AxisDirection.down, ), ); if (value != null) { selectedFilterText = value; lavVM.filterLabReports(value); } } }, child: Consumer( builder: (context, model, child) { return SingleChildScrollView( physics: AlwaysScrollableScrollPhysics(), padding: EdgeInsets.all(24.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: CustomTabBar( activeTextColor: Color(0xffED1C2B), activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), tabs: [ CustomTabBarModel(null, "By Visit".needTranslation), CustomTabBarModel(null, "By Test".needTranslation), // CustomTabBarModel(null, "Completed".needTranslation), ], onTabChange: (index) { activeIndex = index; setState(() {}); }, ), ), ], ), if (activeIndex == 0) Padding( padding: EdgeInsets.symmetric(vertical: 10.h), child: Row( children: [ CustomButton( text: LocaleKeys.byClinic.tr(context: context), onPressed: () { model.setIsSortByClinic(true); }, backgroundColor: model.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, borderColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), textColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, fontSize: 12, fontWeight: FontWeight.w500, borderRadius: 10, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, ), SizedBox(width: 8.h), CustomButton( text: LocaleKeys.byHospital.tr(context: context), onPressed: () { model.setIsSortByClinic(false); }, backgroundColor: model.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, borderColor: model.isSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor, textColor: model.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, fontSize: 12, fontWeight: FontWeight.w500, borderRadius: 10, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, ), ], ), ), SizedBox(height: 8.h), selectedFilterText!.isNotEmpty ? CustomChipWidget( chipText: selectedFilterText!, chipType: ChipTypeEnum.alert, isSelected: true, ) : SizedBox(), activeIndex == 0 ? // By Visit - show grouped view when available model.isLabOrdersLoading ? ListView.builder( shrinkWrap: true, physics: AlwaysScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: 5, itemBuilder: (context, index) => LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ), ) : (model.patientLabOrdersViewList.isNotEmpty ? ListView.builder( shrinkWrap: true, physics: AlwaysScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.patientLabOrdersViewList.length, itemBuilder: (context, index) { final group = model.patientLabOrdersViewList[index]; final isExpanded = expandedIndex == index; return AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: AnimatedContainer( duration: Duration(milliseconds: 300), curve: Curves.easeInOut, margin: EdgeInsets.symmetric(vertical: 8.h), decoration: RoundedRectangleBorder() .toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), child: InkWell( onTap: () { setState(() { expandedIndex = isExpanded ? null : index; }); }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: EdgeInsets.all(16.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ CustomButton( text: "${group.length} ${'results'.needTranslation}", onPressed: () {}, backgroundColor: AppColors.greyColor, borderColor: AppColors.greyColor, textColor: AppColors.blackColor, fontSize: 10, fontWeight: FontWeight.w500, borderRadius: 8, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 30.h, ), Icon(isExpanded ? Icons.expand_less : Icons.expand_more), ], ), SizedBox(height: 8.h), Text( model.isSortByClinic ? (group.first.clinicDescription ?? 'Unknown') : (group.first.projectName ?? 'Unknown'), style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600), overflow: TextOverflow.ellipsis, ), ], ), ), AnimatedSwitcher( duration: Duration(milliseconds: 500), switchInCurve: Curves.easeIn, switchOutCurve: Curves.easeOut, transitionBuilder: (Widget child, Animation animation) { return FadeTransition( opacity: animation, child: SizeTransition( sizeFactor: animation, axisAlignment: 0.0, child: child, ), ); }, child: isExpanded ? Container( key: ValueKey(index), padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ...group.map((order) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisSize: MainAxisSize.min, children: [ Image.network( order.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", width: 24.h, height: 24.h, fit: BoxFit.cover, ).circle(100), SizedBox(width: 8.h), Expanded(child: (order.doctorName ?? order.doctorNameEnglish ?? "").toString().toText14(weight: FontWeight.w500)), ], ), SizedBox(height: 8.h), Row( children: [ CustomButton( text: ("Order No: ".needTranslation + order.orderNo!), onPressed: () {}, backgroundColor: AppColors.greyColor, borderColor: AppColors.greyColor, textColor: AppColors.blackColor, fontSize: 10, fontWeight: FontWeight.w500, borderRadius: 8, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 24.h, ), SizedBox(width: 8.h), CustomButton( text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(order.orderDate ?? ""), false), onPressed: () {}, backgroundColor: AppColors.greyColor, borderColor: AppColors.greyColor, textColor: AppColors.blackColor, fontSize: 10, fontWeight: FontWeight.w500, borderRadius: 8, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 24.h, ), ], ), SizedBox(height: 8.h), Row( children: [ CustomButton( text: model.isSortByClinic ? (order.clinicDescription ?? "") : (order.projectName ?? ""), onPressed: () {}, backgroundColor: AppColors.greyColor, borderColor: AppColors.greyColor, textColor: AppColors.blackColor, fontSize: 10, fontWeight: FontWeight.w500, borderRadius: 8, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 24.h, ), ], ), // SizedBox(height: 8.h), Row( children: [ Expanded(flex: 2, child: SizedBox()), // Expanded( // flex: 1, // child: Container( // height: 40.h, // width: 40.w, // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( // color: AppColors.textColor, // borderRadius: 12, // ), // child: Padding( // padding: EdgeInsets.all(12.h), // child: Transform.flip( // flipX: _appState.isArabic(), // child: Utils.buildSvgWithAssets( // icon: AppAssets.forward_arrow_icon_small, // iconColor: AppColors.whiteColor, // fit: BoxFit.contain, // ), // ), // ), // ).onPress(() { // model.currentlySelectedPatientOrder = order; // labProvider.getPatientLabResultByHospital(order); // labProvider.getPatientSpecialResult(order); // Navigator.of(context).push( // CustomPageRoute(page: LabResultByClinic(labOrder: order)), // ); // }), // ) Expanded( flex:2, child: CustomButton( icon: AppAssets.view_report_icon, iconColor: AppColors.primaryRedColor, iconSize: 16.h, text: "View Results".needTranslation, onPressed: () { model.currentlySelectedPatientOrder = order; labProvider.getPatientLabResultByHospital(order); labProvider.getPatientSpecialResult(order); Navigator.of(context).push( CustomPageRoute(page: LabResultByClinic(labOrder: order)), ); }, backgroundColor: AppColors.secondaryLightRedColor, borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, fontSize: 14, fontWeight: FontWeight.w500, borderRadius: 12, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, ), ) ], ), SizedBox(height: 12.h), Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), SizedBox(height: 12.h), ], ); }).toList(), ], ), ) : SizedBox.shrink(), ), ], ), ), ), ), )); }, ) : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation)) : // By Test or other tabs keep existing behavior (model.isLabOrdersLoading) ? Column( children: List.generate( 5, (index) => LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, )), ) : AlphabeticScroll( alpahbetsAvailable: model.indexedCharacterForUniqueTest, details: model.uniqueTestsList, labViewModel: model, rangeViewModel: rangeViewModel, appState: _appState, ) ], ) ); }, ), ); } Color getLabOrderStatusColor(num status) { switch (status) { case 44: return AppColors.warningColorYellow; case 45: return AppColors.warningColorYellow; case 16: return AppColors.successColor; case 17: return AppColors.successColor; default: return AppColors.greyColor; } } String getLabOrderStatusText(num status) { switch (status) { case 44: return LocaleKeys.resultsPending.tr(context: context); case 45: return LocaleKeys.resultsPending.tr(context: context); case 16: return LocaleKeys.resultsAvailable.tr(context: context); case 17: return LocaleKeys.resultsAvailable.tr(context: context); default: return ""; } } getLabSuggestions(LabViewModel model) { if (model.patientLabOrders.isEmpty) { return []; } return model.patientLabOrders.map((m) => m.testDetails).toList(); } } \ No newline at end of file +import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.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/enums.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'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_toolbar.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; import 'alphabeticScroll.dart'; class LabOrdersPage extends StatefulWidget { const LabOrdersPage({super.key}); @override State createState() => _LabOrdersPageState(); } class _LabOrdersPageState extends State { late LabViewModel labProvider; late DateRangeSelectorRangeViewModel rangeViewModel; late AppState _appState; List?> labSuggestions = []; int? expandedIndex; String? selectedFilterText = ''; int activeIndex = 0; @override void initState() { scheduleMicrotask(() { labProvider.initLabProvider(); }); super.initState(); } @override Widget build(BuildContext context) { labProvider = Provider.of(context, listen: false); rangeViewModel = Provider.of(context); _appState = getIt(); return CollapsingToolbar( title: LocaleKeys.labResults.tr(), search: () async { final lavVM = Provider.of(context, listen: false); if (lavVM.isLabOrdersLoading) { return; } else { String? value = await Navigator.of(context).push( CustomPageRoute( page: SearchLabResultsContent(labSuggestionsList: lavVM.labSuggestions), fullScreenDialog: true, direction: AxisDirection.down, ), ); if (value != null) { selectedFilterText = value; lavVM.filterLabReports(value); } } }, child: Consumer( builder: (context, model, child) { return SingleChildScrollView( physics: AlwaysScrollableScrollPhysics(), padding: EdgeInsets.all(24.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: CustomTabBar( activeTextColor: Color(0xffED1C2B), activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), tabs: [ CustomTabBarModel(null, "By Visit".needTranslation), CustomTabBarModel(null, "By Test".needTranslation), // CustomTabBarModel(null, "Completed".needTranslation), ], onTabChange: (index) { activeIndex = index; setState(() {}); }, ), ), ], ), if (activeIndex == 0) Padding( padding: EdgeInsets.symmetric(vertical: 10.h), child: Row( children: [ CustomButton( text: LocaleKeys.byClinic.tr(context: context), onPressed: () { model.setIsSortByClinic(true); }, backgroundColor: model.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, borderColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), textColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, fontSize: 12, fontWeight: FontWeight.w500, borderRadius: 10, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, ), SizedBox(width: 8.h), CustomButton( text: LocaleKeys.byHospital.tr(context: context), onPressed: () { model.setIsSortByClinic(false); }, backgroundColor: model.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, borderColor: model.isSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor, textColor: model.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, fontSize: 12, fontWeight: FontWeight.w500, borderRadius: 10, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, ), ], ), ), SizedBox(height: 8.h), selectedFilterText!.isNotEmpty ? CustomChipWidget( chipText: selectedFilterText!, chipType: ChipTypeEnum.alert, isSelected: true, ) : SizedBox(), activeIndex == 0 ? // By Visit - show grouped view when available model.isLabOrdersLoading ? ListView.builder( shrinkWrap: true, physics: AlwaysScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: 5, itemBuilder: (context, index) => LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ), ) : (model.patientLabOrdersViewList.isNotEmpty ? ListView.builder( shrinkWrap: true, physics: AlwaysScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.patientLabOrdersViewList.length, itemBuilder: (context, index) { final group = model.patientLabOrdersViewList[index]; final isExpanded = expandedIndex == index; return AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: AnimatedContainer( duration: Duration(milliseconds: 300), curve: Curves.easeInOut, margin: EdgeInsets.symmetric(vertical: 8.h), decoration: RoundedRectangleBorder() .toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), child: InkWell( onTap: () { setState(() { expandedIndex = isExpanded ? null : index; }); }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: EdgeInsets.all(16.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ AppCustomChipWidget(labelText: "${group.length} ${'results'.needTranslation}"), Icon(isExpanded ? Icons.expand_less : Icons.expand_more), ], ), SizedBox(height: 8.h), Text( model.isSortByClinic ? (group.first.clinicDescription ?? 'Unknown') : (group.first.projectName ?? 'Unknown'), style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600), overflow: TextOverflow.ellipsis, ), ], ), ), AnimatedSwitcher( duration: Duration(milliseconds: 500), switchInCurve: Curves.easeIn, switchOutCurve: Curves.easeOut, transitionBuilder: (Widget child, Animation animation) { return FadeTransition( opacity: animation, child: SizeTransition( sizeFactor: animation, axisAlignment: 0.0, child: child, ), ); }, child: isExpanded ? Container( key: ValueKey(index), padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 0.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ...group.map((order) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisSize: MainAxisSize.min, children: [ Image.network( order.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", width: 24.w, height: 24.h, fit: BoxFit.cover, ).circle(100), SizedBox(width: 8.h), Expanded(child: (order.doctorName ?? order.doctorNameEnglish ?? "").toString().toText14(weight: FontWeight.w500)), ], ), SizedBox(height: 8.h), Wrap( direction: Axis.horizontal, spacing: 4.h, runSpacing: 4.h, children: [ AppCustomChipWidget( labelText: ("Order No: ".needTranslation + order.orderNo!), ), AppCustomChipWidget( labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(order.orderDate ?? ""), false), ), AppCustomChipWidget( labelText: model.isSortByClinic ? (order.clinicDescription ?? "") : (order.projectName ?? ""), ), ], ), // Row( // children: [ // CustomButton( // text: ("Order No: ".needTranslation + order.orderNo!), // onPressed: () {}, // backgroundColor: AppColors.greyColor, // borderColor: AppColors.greyColor, // textColor: AppColors.blackColor, // fontSize: 10, // fontWeight: FontWeight.w500, // borderRadius: 8, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 24.h, // ), // SizedBox(width: 8.h), // CustomButton( // text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(order.orderDate ?? ""), false), // onPressed: () {}, // backgroundColor: AppColors.greyColor, // borderColor: AppColors.greyColor, // textColor: AppColors.blackColor, // fontSize: 10, // fontWeight: FontWeight.w500, // borderRadius: 8, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 24.h, // ), // ], // ), // SizedBox(height: 8.h), // Row( // children: [ // CustomButton( // text: model.isSortByClinic ? (order.clinicDescription ?? "") : (order.projectName ?? ""), // onPressed: () {}, // backgroundColor: AppColors.greyColor, // borderColor: AppColors.greyColor, // textColor: AppColors.blackColor, // fontSize: 10, // fontWeight: FontWeight.w500, // borderRadius: 8, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 24.h, // ), // ], // ), SizedBox(height: 12.h), Row( children: [ Expanded(flex: 2, child: SizedBox()), // Expanded( // flex: 1, // child: Container( // height: 40.h, // width: 40.w, // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( // color: AppColors.textColor, // borderRadius: 12, // ), // child: Padding( // padding: EdgeInsets.all(12.h), // child: Transform.flip( // flipX: _appState.isArabic(), // child: Utils.buildSvgWithAssets( // icon: AppAssets.forward_arrow_icon_small, // iconColor: AppColors.whiteColor, // fit: BoxFit.contain, // ), // ), // ), // ).onPress(() { // model.currentlySelectedPatientOrder = order; // labProvider.getPatientLabResultByHospital(order); // labProvider.getPatientSpecialResult(order); // Navigator.of(context).push( // CustomPageRoute(page: LabResultByClinic(labOrder: order)), // ); // }), // ) Expanded( flex:2, child: CustomButton( icon: AppAssets.view_report_icon, iconColor: AppColors.primaryRedColor, iconSize: 16.h, text: "View Results".needTranslation, onPressed: () { model.currentlySelectedPatientOrder = order; labProvider.getPatientLabResultByHospital(order); labProvider.getPatientSpecialResult(order); Navigator.of(context).push( CustomPageRoute(page: LabResultByClinic(labOrder: order)), ); }, backgroundColor: AppColors.secondaryLightRedColor, borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, fontSize: 14, fontWeight: FontWeight.w500, borderRadius: 12, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, ), ) ], ), SizedBox(height: 12.h), Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), SizedBox(height: 12.h), ], ); }).toList(), ], ), ) : SizedBox.shrink(), ), ], ), ), ), ), )); }, ) : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation)) : // By Test or other tabs keep existing behavior (model.isLabOrdersLoading) ? Column( children: List.generate( 5, (index) => LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, )), ) : AlphabeticScroll( alpahbetsAvailable: model.indexedCharacterForUniqueTest, details: model.uniqueTestsList, labViewModel: model, rangeViewModel: rangeViewModel, appState: _appState, ) ], ) ); }, ), ); } Color getLabOrderStatusColor(num status) { switch (status) { case 44: return AppColors.warningColorYellow; case 45: return AppColors.warningColorYellow; case 16: return AppColors.successColor; case 17: return AppColors.successColor; default: return AppColors.greyColor; } } String getLabOrderStatusText(num status) { switch (status) { case 44: return LocaleKeys.resultsPending.tr(context: context); case 45: return LocaleKeys.resultsPending.tr(context: context); case 16: return LocaleKeys.resultsAvailable.tr(context: context); case 17: return LocaleKeys.resultsAvailable.tr(context: context); default: return ""; } } getLabSuggestions(LabViewModel model) { if (model.patientLabOrders.isEmpty) { return []; } return model.patientLabOrders.map((m) => m.testDetails).toList(); } } \ No newline at end of file