From 6cf08c04d29106410510435a1cfaf827b2f33040 Mon Sep 17 00:00:00 2001 From: tahaalam Date: Mon, 6 Oct 2025 09:57:12 +0300 Subject: [PATCH] appointment filters added. --- assets/langs/ar-SA.json | 3 +- assets/langs/en-US.json | 4 +- .../models/appointemnet_filters.dart | 17 + .../my_appointments_view_model.dart | 157 +++++++- lib/generated/locale_keys.g.dart | 1 + lib/main.dart | 6 +- .../appointments/my_appointments_page.dart | 350 +++++++----------- .../widgets/AppointmentFilter.dart | 48 +++ lib/presentation/home/landing_page.dart | 1 - lib/presentation/lab/lab_orders_page.dart | 6 +- .../lab/lab_results/lab_result_details.dart | 12 +- lib/theme/colors.dart | 1 + lib/widgets/chip/app_custom_chip_widget.dart | 4 +- .../date_range_calender.dart} | 40 +- .../viewmodel/date_range_view_model.dart} | 12 +- 15 files changed, 420 insertions(+), 242 deletions(-) create mode 100644 lib/features/my_appointments/models/appointemnet_filters.dart create mode 100644 lib/presentation/appointments/widgets/AppointmentFilter.dart rename lib/{presentation/lab/lab_results/lab_result_calender.dart => widgets/date_range_selector/date_range_calender.dart} (90%) rename lib/{features/lab/lab_range_view_model.dart => widgets/date_range_selector/viewmodel/date_range_view_model.dart} (94%) diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index b08e783..cc114c8 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -864,5 +864,6 @@ "regionAndLocation": "المنطقة والمواقع", "clearAllFilters": "مسح جميع الفلاتر", "filters": "فلاتر", - "searchClinic": "بحث عن عيادة" + "searchClinic": "بحث عن عيادة", + "walkin": "زيارة بدون موعد" } \ No newline at end of file diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index adab1d5..d0069a3 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -860,5 +860,7 @@ "regionAndLocation": "Region And Locations", "clearAllFilters": "Clear all filters", "filters": "Filters", - "searchClinic": "Search Clinic" + "searchClinic": "Search Clinic", + "walkin": "Walk In" + } \ No newline at end of file diff --git a/lib/features/my_appointments/models/appointemnet_filters.dart b/lib/features/my_appointments/models/appointemnet_filters.dart new file mode 100644 index 0000000..e157af4 --- /dev/null +++ b/lib/features/my_appointments/models/appointemnet_filters.dart @@ -0,0 +1,17 @@ +import 'package:hmg_patient_app_new/core/app_assets.dart'; + +enum AppointmentListingFilters{ + WALKIN("walkin", AppAssets.walkin_appointment_icon), + BOOKED("booked", AppAssets.calendar), + CONFIRMED("confirmed", AppAssets.calendar), + ARRIVED("arrived", AppAssets.calendar), + LIVECARE("livecare", AppAssets.small_livecare_icon), + DATESELECTION("",AppAssets.calendar, trailingIcon: AppAssets.arrow_down); + + final String labelText; + final String leadingIcon; + final String trailingIcon; + + const AppointmentListingFilters(this.labelText, this.leadingIcon, + {this.trailingIcon = ""}); +} \ No newline at end of file diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index c553f0e..99fbf60 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -1,14 +1,18 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/appointemnet_filters.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_share_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/utils/appointment_type.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import '../../core/utils/doctor_response_mapper.dart' show DoctorMapper; class MyAppointmentsViewModel extends ChangeNotifier { int selectedTabIndex = 0; + int previouslySelectedTab = -1; MyAppointmentsRepo myAppointmentsRepo; ErrorHandlerService errorHandlerService; @@ -21,7 +25,14 @@ class MyAppointmentsViewModel extends ChangeNotifier { bool isAppointmentDataToBeLoaded = true; + List availableFilters = []; + List? selectedFilter = []; + bool isDateFilterSelected = false; + DateTime? start =null; + DateTime? end =null; + List patientAppointmentsHistoryList = []; + List filteredAppointmentList = []; List patientUpcomingAppointmentsHistoryList = []; List patientArrivedAppointmentsHistoryList = []; @@ -35,6 +46,7 @@ class MyAppointmentsViewModel extends ChangeNotifier { MyAppointmentsViewModel({required this.myAppointmentsRepo, required this.errorHandlerService, required this.appState}); void onTabChange(int index) { + previouslySelectedTab = selectedTabIndex; selectedTabIndex = index; notifyListeners(); } @@ -132,10 +144,43 @@ class MyAppointmentsViewModel extends ChangeNotifier { patientAppointmentsHistoryList.addAll(patientUpcomingAppointmentsHistoryList); patientAppointmentsHistoryList.addAll(patientArrivedAppointmentsHistoryList); - + filteredAppointmentList.addAll(patientAppointmentsHistoryList); print('Upcoming Appointments: ${patientUpcomingAppointmentsHistoryList.length}'); print('Arrived Appointments: ${patientArrivedAppointmentsHistoryList.length}'); print('All Appointments: ${patientAppointmentsHistoryList.length}'); + getFiltersForSelectedAppointmentList(filteredAppointmentList); + } + + void getFiltersForSelectedAppointmentList( + List filteredAppointmentList) { + availableFilters.clear(); + if (filteredAppointmentList.isEmpty == true) return; + availableFilters.add(AppointmentListingFilters.DATESELECTION); + if (filteredAppointmentList + .any((element) => element.isLiveCareAppointment == true)) { + availableFilters.add(AppointmentListingFilters.LIVECARE); + } + + if (filteredAppointmentList + .any((element) => element.isLiveCareAppointment == false)) { + availableFilters.add(AppointmentListingFilters.WALKIN); + } + + if (filteredAppointmentList + .any((element) => AppointmentType.isArrived(element) == true)) { + availableFilters.add(AppointmentListingFilters.ARRIVED); + } + + if (filteredAppointmentList + .any((element) => AppointmentType.isBooked(element) == true)) { + availableFilters.add(AppointmentListingFilters.BOOKED); + } + + if (filteredAppointmentList + .any((element) => AppointmentType.isConfirmed(element) == true)) { + availableFilters.add(AppointmentListingFilters.CONFIRMED); + } + notifyListeners(); } Future getPatientShareAppointment(int projectID, int clinicID, String appointmentNo, bool isLiveCareAppointment, {Function(dynamic)? onSuccess, Function(String)? onError}) async { @@ -337,4 +382,114 @@ class MyAppointmentsViewModel extends ChangeNotifier { }, ); } + + void updateListWRTTab(int index) { + isDateFilterSelected = false; + selectedFilter = []; + // if(previouslySelectedTab == selectedTabIndex ) return; + switch (index) { + case 0: + filteredAppointmentList.clear(); + filteredAppointmentList.addAll(patientAppointmentsHistoryList); + break; + case 1: + filteredAppointmentList.clear(); + filteredAppointmentList.addAll(patientUpcomingAppointmentsHistoryList); + break; + case 2: + filteredAppointmentList.clear(); + filteredAppointmentList.addAll(patientArrivedAppointmentsHistoryList); + break; + } + getFiltersForSelectedAppointmentList(filteredAppointmentList); + notifyListeners(); + } + + void setSelectedFilter(AppointmentListingFilters availableFilter) { + if (selectedFilter?.contains(availableFilter) == true) { + selectedFilter?.remove(availableFilter); + notifyListeners(); + + return; + } + selectedFilter?.add(availableFilter) ; + notifyListeners(); + } + + void getSelectedDateRange(DateTime? start, DateTime? end) { + this.start = start; + this.end = end; + isDateFilterSelected = true; + List sourceList = []; + if (selectedTabIndex == 0) { + sourceList = patientAppointmentsHistoryList; + } else if (selectedTabIndex == 1) { + sourceList = patientUpcomingAppointmentsHistoryList; + } else if (selectedTabIndex == 2) { + sourceList = patientArrivedAppointmentsHistoryList; + } + // if (isDateFilterSelected) sourceList = filteredAppointmentList; + if (start == null && end == null) { + isDateFilterSelected = false; + filteredAppointmentList.clear(); + sourceList.forEach((element) { + if (isUnderFilter(element)) { + filteredAppointmentList.add(element); + } + }); + filteredAppointmentList.addAll(sourceList); + } else { + filteredAppointmentList.clear(); + sourceList.forEach((element) { + try { + var dateTime = DateUtil.convertStringToDate(element.appointmentDate); + if (start != null && end == null) { + if (dateTime.isAtSameMomentAs(start)) { + if (isUnderFilter(element)) { + filteredAppointmentList.add(element); + } + } + } else if (start != null && end != null) { + if ((dateTime.isAfter(start)) && (dateTime.isBefore(end))) { + if (isUnderFilter(element)) { + filteredAppointmentList.add(element); + } + } + } + } catch (e) {} + }); + } + notifyListeners(); + } + + void filterTheListAsPerSelection() { + getSelectedDateRange(start, end); + } + + bool isUnderFilter(PatientAppointmentHistoryResponseModel element) { + bool isUnderTheFilter = false; + if (selectedFilter == null || selectedFilter!.isEmpty) return true; + int count = 0; + for (var filter in selectedFilter ?? []) { + switch (filter) { + case AppointmentListingFilters.WALKIN: + if (element.isLiveCareAppointment == false) return true; + case AppointmentListingFilters.BOOKED: + if (AppointmentType.isBooked(element))return true; + + case AppointmentListingFilters.CONFIRMED: + if (AppointmentType.isConfirmed(element))return true; + + case AppointmentListingFilters.ARRIVED: + if (AppointmentType.isArrived(element))return true; + + case AppointmentListingFilters.LIVECARE: + if (element.isLiveCareAppointment == true) return true; + + case AppointmentListingFilters.DATESELECTION: + + } + } + return false; + } } diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 9447bd1..724ae2f 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -863,5 +863,6 @@ abstract class LocaleKeys { static const clearAllFilters = 'clearAllFilters'; static const filters = 'filters'; static const searchClinic = 'searchClinic'; + static const walkin = 'walkin'; } diff --git a/lib/main.dart b/lib/main.dart index cdfcc13..ac11f8f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -15,7 +15,6 @@ import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_mode import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/history/lab_history_viewmodel.dart'; -import 'package:hmg_patient_app_new/features/lab/lab_range_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart'; @@ -28,6 +27,7 @@ import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/app_theme.dart'; +import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart' show DateRangeSelectorRangeViewModel; import 'package:provider/provider.dart'; import 'package:provider/single_child_widget.dart'; @@ -163,8 +163,8 @@ void main() async { navigationService: getIt(), appState: getIt())), ChangeNotifierProvider( create: (_) => LabHistoryViewModel()), - ChangeNotifierProvider( - create: (_) => LabRangeViewModel()) , + ChangeNotifierProvider( + create: (_) => DateRangeSelectorRangeViewModel()) , ChangeNotifierProvider( create: (_) => DoctorFilterViewModel()) ], child: MyApp()), diff --git a/lib/presentation/appointments/my_appointments_page.dart b/lib/presentation/appointments/my_appointments_page.dart index 2518c88..c939234 100644 --- a/lib/presentation/appointments/my_appointments_page.dart +++ b/lib/presentation/appointments/my_appointments_page.dart @@ -8,19 +8,26 @@ 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/my_appointments/models/appointemnet_filters.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/AppointmentFilter.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_card.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart'; +import 'package:hmg_patient_app_new/widgets/date_range_selector/date_range_calender.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/buttons/custom_button.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:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; import 'package:provider/provider.dart'; +import '../../widgets/common_bottom_sheet.dart' + show showCommonBottomSheetWithoutHeight; + class MyAppointmentsPage extends StatefulWidget { const MyAppointmentsPage({super.key}); @@ -61,6 +68,8 @@ class _MyAppointmentsPageState extends State { ], onTabChange: (index) { myAppointmentsViewModel.onTabChange(index); + myAppointmentsViewModel.updateListWRTTab(index); + context.read().flush(); }, ).paddingSymmetrical(24.h, 0.h), Consumer(builder: (context, myAppointmentsVM, child) { @@ -74,218 +83,147 @@ class _MyAppointmentsPageState extends State { } Widget getSelectedTabData(int index, MyAppointmentsViewModel myAppointmentsVM) { - switch (index) { - case 0: - //All Appointments Tab Data - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Expandable list - ListView.separated( - padding: EdgeInsets.only(top: 24.h), - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: myAppointmentsVM.isMyAppointmentsLoading - ? 5 - : myAppointmentsVM.patientAppointmentsHistoryList.isNotEmpty - ? myAppointmentsVM.patientAppointmentsHistoryList.length - : 1, - itemBuilder: (context, index) { - return myAppointmentsVM.isMyAppointmentsLoading - ? Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), - child: AppointmentCard( - patientAppointmentHistoryResponseModel: PatientAppointmentHistoryResponseModel(), - myAppointmentsViewModel: myAppointmentsViewModel, - isLoading: true, - isFromHomePage: false, - ), - ).paddingSymmetrical(24.h, 0.h) - : myAppointmentsVM.patientAppointmentsHistoryList.isNotEmpty - ? 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, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), - child: AppointmentCard( - patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index], - myAppointmentsViewModel: myAppointmentsViewModel, - isLoading: false, - isFromHomePage: false, - ), - ).paddingSymmetrical(24.h, 0.h), + return getAppointList( + myAppointmentsVM, myAppointmentsVM.filteredAppointmentList); + } + + Widget getAppointList(MyAppointmentsViewModel myAppointmentsVM, + List filteredAppointmentList) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + + Visibility( + visible: myAppointmentsVM.availableFilters.isNotEmpty, + child: getAppointmentFilters(myAppointmentsVM)), + ListView.separated( + padding: EdgeInsets.only(top: 24.h), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: myAppointmentsVM.isMyAppointmentsLoading + ? 5 + : filteredAppointmentList.isNotEmpty + ? filteredAppointmentList.length + : 1, + itemBuilder: (context, index) { + return myAppointmentsVM.isMyAppointmentsLoading + ? Container( + decoration: RoundedRectangleBorder() + .toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: + PatientAppointmentHistoryResponseModel(), + myAppointmentsViewModel: myAppointmentsViewModel, + isLoading: true, + isFromHomePage: false, + ), + ).paddingSymmetrical(24.h, 0.h) + : filteredAppointmentList.isNotEmpty + ? 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, + decoration: RoundedRectangleBorder() + .toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: + filteredAppointmentList[index], + myAppointmentsViewModel: + myAppointmentsViewModel, + isLoading: false, + isFromHomePage: false, ), - ), - ) - : Utils.getNoDataWidget( - context, - noDataText: "You don't have any appointments yet.".needTranslation, - callToActionButton: CustomButton( - text: LocaleKeys.bookAppo.tr(context: context), - onPressed: () { - Navigator.of(context).push( - CustomPageRoute( - page: BookAppointmentPage(), - ), - ); - }, - backgroundColor: Color(0xffFEE9EA), - borderColor: Color(0xffFEE9EA), - textColor: Color(0xffED1C2B), - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40, - icon: AppAssets.add_icon, - iconColor: AppColors.primaryRedColor, - ).paddingSymmetrical(48.h, 0.h), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), - ), - ], - ); - case 1: - //Upcoming Appointments Tab Data - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Expandable list - ListView.separated( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: myAppointmentsVM.isMyAppointmentsLoading - ? 5 - : myAppointmentsVM.patientUpcomingAppointmentsHistoryList.isNotEmpty - ? myAppointmentsVM.patientUpcomingAppointmentsHistoryList.length - : 1, - itemBuilder: (context, index) { - return myAppointmentsVM.isMyAppointmentsLoading - ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0.h) - : myAppointmentsVM.patientUpcomingAppointmentsHistoryList.isNotEmpty - ? 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: 24.h, hasShadow: true), - child: AppointmentCard( - patientAppointmentHistoryResponseModel: myAppointmentsVM.patientUpcomingAppointmentsHistoryList[index], - myAppointmentsViewModel: myAppointmentsViewModel, - ), - ).paddingSymmetrical(24.h, 0.h), + ).paddingSymmetrical(24.h, 0.h), + ), + ), + ) + : Utils.getNoDataWidget( + context, + noDataText: "You don't have any appointments yet." + .needTranslation, + callToActionButton: CustomButton( + text: LocaleKeys.bookAppo.tr(context: context), + onPressed: () { + Navigator.of(context).push( + CustomPageRoute( + page: BookAppointmentPage(), ), - ), - ) - : Utils.getNoDataWidget( - context, - noDataText: "You don't have any appointments yet.".needTranslation, - callToActionButton: CustomButton( - text: LocaleKeys.bookAppo.tr(context: context), - onPressed: () { - Navigator.of(context).push( - CustomPageRoute( - page: BookAppointmentPage(), - ), - ); - }, - backgroundColor: Color(0xffFEE9EA), - borderColor: Color(0xffFEE9EA), - textColor: Color(0xffED1C2B), - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40, - icon: AppAssets.add_icon, - iconColor: AppColors.primaryRedColor, - ).paddingSymmetrical(48.h, 0.h), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), - ), - ], - ); - case 2: - //Completed Appointments Tab Data - return Column( - crossAxisAlignment: CrossAxisAlignment.start, + ); + }, + backgroundColor: Color(0xffFEE9EA), + borderColor: Color(0xffFEE9EA), + textColor: Color(0xffED1C2B), + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40, + icon: AppAssets.add_icon, + iconColor: AppColors.primaryRedColor, + ).paddingSymmetrical(48.h, 0.h), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => + SizedBox(height: 16.h), + ), + ], + ); + } + + Widget getAppointmentFilters(MyAppointmentsViewModel myAppointmentsVM) { + return SizedBox( + height: 56.h, + child: Row( children: [ - // Expandable list - ListView.separated( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: myAppointmentsVM.isMyAppointmentsLoading - ? 5 - : myAppointmentsVM.patientArrivedAppointmentsHistoryList.isNotEmpty - ? myAppointmentsVM.patientArrivedAppointmentsHistoryList.length - : 1, - itemBuilder: (context, index) { - return myAppointmentsVM.isMyAppointmentsLoading - ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0.h) - : myAppointmentsVM.patientArrivedAppointmentsHistoryList.isNotEmpty - ? 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: 24.h, hasShadow: true), - child: AppointmentCard( - patientAppointmentHistoryResponseModel: myAppointmentsVM.patientArrivedAppointmentsHistoryList[index], - myAppointmentsViewModel: myAppointmentsViewModel, - ), - ).paddingSymmetrical(24.h, 0.h), + Expanded( + child: ListView.separated( + separatorBuilder: (_, index) => SizedBox( + width: 8.h, + ), + scrollDirection: Axis.horizontal, + itemCount: myAppointmentsVM.availableFilters.length, + itemBuilder: (_, index) => AppointmentFilters( + selectedFilter: myAppointmentsVM.selectedFilter, + item: myAppointmentsVM.availableFilters[index], + onClicked: () { + if (myAppointmentsVM.availableFilters[index] == + AppointmentListingFilters.DATESELECTION) { + showCommonBottomSheetWithoutHeight( + title: "Set The Date Range".needTranslation, + context, + child: DateRangeSelector( + onRangeSelected: (start, end) { + // if (start != null) { + myAppointmentsVM.getSelectedDateRange( + start, end); + // } + }, ), - ), - ) - : Utils.getNoDataWidget( - context, - noDataText: "You don't have any appointments yet.".needTranslation, - callToActionButton: CustomButton( - text: LocaleKeys.bookAppo.tr(context: context), - onPressed: () { - Navigator.of(context).push( - CustomPageRoute( - page: BookAppointmentPage(), - ), - ); - }, - backgroundColor: Color(0xffFEE9EA), - borderColor: Color(0xffFEE9EA), - textColor: Color(0xffED1C2B), - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40, - icon: AppAssets.add_icon, - iconColor: AppColors.primaryRedColor, - ).paddingSymmetrical(48.h, 0.h), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + isFullScreen: false, + isCloseButtonVisible: true, + callBackFunc: () {}, + ); + } else { + myAppointmentsVM.setSelectedFilter( + myAppointmentsVM.availableFilters[index]); + myAppointmentsVM.filterTheListAsPerSelection(); + } + }, + )), ), ], - ); - default: - return Container(); - } + )).paddingOnly(top: 24.h, left: 24.h, right: 24.h); } } diff --git a/lib/presentation/appointments/widgets/AppointmentFilter.dart b/lib/presentation/appointments/widgets/AppointmentFilter.dart new file mode 100644 index 0000000..fd2a9fe --- /dev/null +++ b/lib/presentation/appointments/widgets/AppointmentFilter.dart @@ -0,0 +1,48 @@ +import 'package:easy_localization/easy_localization.dart' show tr, StringTranslateExtension; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/appointemnet_filters.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:smooth_corner/smooth_corner.dart'; + +class AppointmentFilters extends StatelessWidget { + final AppointmentListingFilters item; + final List? selectedFilter; + final VoidCallback onClicked; + + const AppointmentFilters( + {super.key, + required this.item, + required this.onClicked, + required this.selectedFilter}); + + @override + Widget build(BuildContext context) { + return AppCustomChipWidget( + backgroundColor: selectedFilter?.contains(item) == true?AppColors.chipSecondaryLightRedColor:AppColors.whiteColor, + icon: item.leadingIcon, + textColor: selectedFilter?.contains(item) == true + ? AppColors.chipPrimaryRedBorderColor: AppColors.blackColor, + labelText: item.labelText.isNotEmpty?item.labelText.tr():"", + iconHasColor: true, + iconColor: selectedFilter?.contains(item) == true + ? AppColors.chipPrimaryRedBorderColor:AppColors.blackColor, + iconSize: 16, + deleteIcon: item.trailingIcon, + labelPadding: EdgeInsetsDirectional.only(start: 8.h, end: 0.h), + padding: EdgeInsets.symmetric(vertical: 12.h, horizontal: 8.h), + deleteIconSize: Size(18.h, 18.h), + shape: SmoothRectangleBorder( + borderRadius: BorderRadius.circular(10 ), + smoothness: 10, + side: BorderSide( + color: selectedFilter?.contains(item) == true + ? AppColors.chipPrimaryRedBorderColor + : AppColors.borderGrayColor, + width: 1), + )).onPress(onClicked); + } +} diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 9443ee2..abdaac5 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -32,7 +32,6 @@ import 'package:hmg_patient_app_new/presentation/home/widgets/habib_wallet_card. import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/small_service_card.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/welcome_widget.dart'; -import 'package:hmg_patient_app_new/presentation/lab/lab_results/lab_result_calender.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; import 'package:hmg_patient_app_new/presentation/profile_settings/profile_settings.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index 6fd8234..3f6ab99 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -9,7 +9,6 @@ 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_range_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/features/lab/lab_view_model.dart'; @@ -18,6 +17,7 @@ import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.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/widgets/chip/custom_chip_widget.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 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; @@ -32,7 +32,7 @@ class LabOrdersPage extends StatefulWidget { class _LabOrdersPageState extends State { late LabViewModel labProvider; - late LabRangeViewModel rangeViewModel; + late DateRangeSelectorRangeViewModel rangeViewModel; List?> labSuggestions = []; int? expandedIndex; @@ -50,7 +50,7 @@ class _LabOrdersPageState extends State { @override Widget build(BuildContext context) { labProvider = Provider.of(context); - rangeViewModel = Provider.of(context); + rangeViewModel = Provider.of(context); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, diff --git a/lib/presentation/lab/lab_results/lab_result_details.dart b/lib/presentation/lab/lab_results/lab_result_details.dart index 3e9558c..5030d37 100644 --- a/lib/presentation/lab/lab_results/lab_result_details.dart +++ b/lib/presentation/lab/lab_results/lab_result_details.dart @@ -6,10 +6,10 @@ 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_range_view_model.dart' show LabRangeViewModel; +import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/lab_result.dart'; -import 'package:hmg_patient_app_new/presentation/lab/lab_results/lab_result_calender.dart'; +import 'package:hmg_patient_app_new/widgets/date_range_selector/date_range_calender.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_results/lab_result_list_item.dart'; import 'package:hmg_patient_app_new/theme/colors.dart' show AppColors; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; @@ -133,7 +133,7 @@ class LabResultDetails extends StatelessWidget { ], )); - Widget LabGraph(BuildContext context) => Consumer( + Widget LabGraph(BuildContext context) => Consumer( builder: (_, model, ___) => Consumer( builder: (_, labmodel, ___) => Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( @@ -179,7 +179,7 @@ class LabResultDetails extends StatelessWidget { showCommonBottomSheetWithoutHeight( title: "Set The Date Range".needTranslation, context, - child: LabResultCalender( + child: DateRangeSelector( onRangeSelected: (start, end) { // if (start != null) { @@ -227,7 +227,7 @@ class LabResultDetails extends StatelessWidget { ); } - Widget historyBody(LabRangeViewModel model, LabViewModel labmodel) { + Widget historyBody(DateRangeSelectorRangeViewModel model, LabViewModel labmodel) { if(model.isGraphVisible){ var graphColor = labmodel.getColor(recentLabResult.calculatedResultFlag??"N"); return CustomGraph( @@ -280,7 +280,7 @@ class LabResultDetails extends StatelessWidget { } } - Widget labHistoryList(LabRangeViewModel model, LabViewModel labmodel) { + Widget labHistoryList(DateRangeSelectorRangeViewModel model, LabViewModel labmodel) { return SizedBox( height: 180.h, child: ListView.separated( diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index 291f81d..55f2b2a 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -29,6 +29,7 @@ class AppColors { static const Color bgRedLightColor = Color(0xFFFEE9EA); static const Color bgGreenColor = Color(0xFF18C273); static const Color textColor = Color(0xFF2E3039); + static const Color borderGrayColor = Color(0x332E3039); static const Color textColorLight = Color(0xFF5E5E5E); static const Color borderOnlyColor = Color(0xFF2E3039); static const Color chipBorderColorOpacity20 = Color(0x332E3039); diff --git a/lib/widgets/chip/app_custom_chip_widget.dart b/lib/widgets/chip/app_custom_chip_widget.dart index d11b8f4..751b01c 100644 --- a/lib/widgets/chip/app_custom_chip_widget.dart +++ b/lib/widgets/chip/app_custom_chip_widget.dart @@ -24,6 +24,7 @@ class AppCustomChipWidget extends StatelessWidget { this.deleteIconColor = AppColors.textColor, this.deleteIconHasColor = false, this.padding = EdgeInsets.zero, + this.labelPadding , }); final String? labelText; @@ -40,6 +41,7 @@ class AppCustomChipWidget extends StatelessWidget { final bool deleteIconHasColor; final OutlinedBorder? shape; final EdgeInsets? padding; + final EdgeInsetsDirectional? labelPadding; @override Widget build(BuildContext context) { @@ -62,7 +64,7 @@ class AppCustomChipWidget extends StatelessWidget { // padding: EdgeInsets.all(0.0), padding: padding, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - labelPadding: EdgeInsetsDirectional.only(start: 0.h, end: deleteIcon?.isNotEmpty == true ? 2.h : 8.h), + labelPadding: labelPadding??EdgeInsetsDirectional.only(start: 0.h, end: deleteIcon?.isNotEmpty == true ? 2.h : 8.h), backgroundColor: backgroundColor, shape: shape ?? SmoothRectangleBorder( diff --git a/lib/presentation/lab/lab_results/lab_result_calender.dart b/lib/widgets/date_range_selector/date_range_calender.dart similarity index 90% rename from lib/presentation/lab/lab_results/lab_result_calender.dart rename to lib/widgets/date_range_selector/date_range_calender.dart index b118293..1a551f3 100644 --- a/lib/presentation/lab/lab_results/lab_result_calender.dart +++ b/lib/widgets/date_range_selector/date_range_calender.dart @@ -7,32 +7,32 @@ import 'package:hmg_patient_app_new/core/app_export.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_range_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/models/Range.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart' show DateRangeSelectorRangeViewModel; import 'package:provider/provider.dart'; import 'package:syncfusion_flutter_calendar/calendar.dart'; import 'package:syncfusion_flutter_datepicker/datepicker.dart'; typedef OnRangeSelected = void Function(DateTime? start, DateTime? end); -class LabResultCalender extends StatefulWidget { +class DateRangeSelector extends StatefulWidget { final OnRangeSelected onRangeSelected; - const LabResultCalender({super.key, required this.onRangeSelected}); + const DateRangeSelector({super.key, required this.onRangeSelected}); @override - State createState() => _LabResultCalenderState(); + State createState() => _DateRangeSelectorState(); } -class _LabResultCalenderState extends State { +class _DateRangeSelectorState extends State { late DateRangePickerController _calendarController; DateTime? start; DateTime? end; - late LabRangeViewModel model; + late DateRangeSelectorRangeViewModel model; @override void initState() { _calendarController = DateRangePickerController(); @@ -44,13 +44,17 @@ class _LabResultCalenderState extends State { @override Widget build(BuildContext context) { - model = Provider.of(context); + model = Provider.of(context); + + + _calendarController.selectedRange = PickerDateRange(model.fromDate,model.toDate); + return Padding( padding: EdgeInsets.symmetric(horizontal: 0.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Consumer( + Consumer( builder: (_, model, __) => selectionChip(model), ).paddingOnly(bottom: 16.h), Container( @@ -135,13 +139,19 @@ class _LabResultCalenderState extends State { ), onSelectionChanged: (DateRangePickerSelectionChangedArgs args) { + print("the value is ${args.value}"); if (args.value is PickerDateRange) { final PickerDateRange range = args.value; start = range.startDate; end = range.endDate; model.fromDate = start; model.toDate = end; - model.resetCurrentlySelectedRange(); + if(end == null) { + scheduleMicrotask((){ + model.resetCurrentlySelectedRange(); + }); + + } } }, ), @@ -151,7 +161,7 @@ class _LabResultCalenderState extends State { ), Row( children: [ - Consumer( + Consumer( builder: (_, model, __) => Visibility( visible: (model.fromDate != null || model.toDate != null), child: Expanded( @@ -164,6 +174,8 @@ class _LabResultCalenderState extends State { _calendarController.selectedRange = null; _calendarController.selectedDate = null; model.flush(); + Navigator.of(context).pop(); + widget.onRangeSelected(null, null); }, backgroundColor: AppColors.secondaryLightRedColor, borderColor: AppColors.secondaryLightRedColor, @@ -203,7 +215,7 @@ class _LabResultCalenderState extends State { } fromDateComponent() { - return Consumer( + return Consumer( builder: (_, model, __) { return displayDate("Start Date".needTranslation, model.getDateString(model.fromDate), model.fromDate == null); @@ -212,7 +224,7 @@ class _LabResultCalenderState extends State { } toDateComponent() { - return Consumer( + return Consumer( builder: (_, model, __) { return displayDate("End Date".needTranslation, model.getDateString(model.toDate), model.toDate == null); @@ -254,7 +266,7 @@ class _LabResultCalenderState extends State { ), ); - selectionChip(LabRangeViewModel model) { + selectionChip(DateRangeSelectorRangeViewModel model) { return Row( spacing: 8.h, children: [ @@ -311,6 +323,7 @@ class _LabResultCalenderState extends State { _calendarController.selectedRange = null; model.currentlySelectedRange = Range.LAST_6MONTH; model.calculateDatesFromRange(); + }), AppCustomChipWidget( labelText: "Year ${model.getCurrentYear}", @@ -329,6 +342,7 @@ class _LabResultCalenderState extends State { _calendarController.selectedRange = null; model.currentlySelectedRange = Range.THIS_YEAR; model.calculateDatesFromRange(); + }), ], ); diff --git a/lib/features/lab/lab_range_view_model.dart b/lib/widgets/date_range_selector/viewmodel/date_range_view_model.dart similarity index 94% rename from lib/features/lab/lab_range_view_model.dart rename to lib/widgets/date_range_selector/viewmodel/date_range_view_model.dart index aa91964..42f222b 100644 --- a/lib/features/lab/lab_range_view_model.dart +++ b/lib/widgets/date_range_selector/viewmodel/date_range_view_model.dart @@ -2,7 +2,7 @@ import 'package:dartz/dartz.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/features/lab/models/Range.dart'; -class LabRangeViewModel extends ChangeNotifier { +class DateRangeSelectorRangeViewModel extends ChangeNotifier { List months = [ 'Jan', 'Feb', @@ -24,8 +24,6 @@ class LabRangeViewModel extends ChangeNotifier { set currentlySelectedRange(Range? value) { _currentlySelectedRange = value; - notifyListeners(); - } DateTime? _toDate; @@ -34,7 +32,7 @@ class LabRangeViewModel extends ChangeNotifier { set toDate(DateTime? value) { _toDate = value; - notifyListeners(); + } DateTime? _fromDate; @@ -43,11 +41,11 @@ class LabRangeViewModel extends ChangeNotifier { set fromDate(DateTime? value) { _fromDate = value; - notifyListeners(); + } - LabRangeViewModel(); + DateRangeSelectorRangeViewModel(); get getCurrentYear => DateTime.now().year; @@ -65,6 +63,7 @@ class LabRangeViewModel extends ChangeNotifier { _fromDate = DateTime(_toDate!.year, DateTime.january, 01); default: } + notifyListeners(); } getDateString(DateTime? date){ @@ -84,6 +83,7 @@ class LabRangeViewModel extends ChangeNotifier { resetCurrentlySelectedRange(){ currentlySelectedRange = null; + notifyListeners(); } alterGraphVisibility(){