diff --git a/lib/core/model/patient_muse/PatientSearchRequestModel.dart b/lib/core/model/patient_muse/PatientSearchRequestModel.dart index d779cf80..04dc18ea 100644 --- a/lib/core/model/patient_muse/PatientSearchRequestModel.dart +++ b/lib/core/model/patient_muse/PatientSearchRequestModel.dart @@ -91,5 +91,8 @@ class PatientSearchRequestModel { this.lastName = ""; this.patientMobileNumber = ""; this.patientIdentificationID = ""; + this.pageIndex = 0 ; + this.pageSize = 10; + // this.clinicID = null; } } diff --git a/lib/core/viewModel/PatientSearchViewModel.dart b/lib/core/viewModel/PatientSearchViewModel.dart index bc9ca991..e30d31c5 100644 --- a/lib/core/viewModel/PatientSearchViewModel.dart +++ b/lib/core/viewModel/PatientSearchViewModel.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:doctor_app_flutter/core/enum/filter_type.dart'; import 'package:doctor_app_flutter/core/enum/patient_type.dart'; import 'package:doctor_app_flutter/core/enum/view_state.dart'; @@ -129,11 +131,40 @@ class PatientSearchViewModel extends BaseViewModel { List get inPatientList => _inPatientService.inPatientList; List get myIinPatientList => _inPatientService.myInPatientList; - + List mainList = []; List filteredInPatientItems = []; List filteredMyInPatientItems = []; + PatientSearchRequestModel? requestModel; + bool isVidaPlusProject = false; + bool paginationLoading = false; + + /// Resets all state variables in the ViewModel to their default values. + void resetViewModel() { + // 1. Clear all lists that hold patient data. + mainList.clear(); + filteredInPatientItems.clear(); + filteredMyInPatientItems.clear(); + _inPatientService.inPatientList.clear(); + _inPatientService.myInPatientList.clear(); + + // 2. Reset the pagination request model and flags. + requestModel = null; + isVidaPlusProject = false; + paginationLoading = false; + // 3. Reset pagination counters (if you have them). + // Based on your code, you have a `resetInPatientPagination` function that does this. + resetInPatientPagination(); + + // 4. Clear any existing error messages. + error = null; + + // 5. Set the view state back to Idle. + // It's good practice to notify listeners after a major state change. + setState(ViewState.Idle); + } Future getInPatientList(PatientSearchRequestModel requestModel, {bool isMyInpatient = false, bool isLocalBusy = false, bool isVidaPlusProject = false, bool isForceFullRefresh = false}) async { + resetViewModel(); await getDoctorProfile(); if (isLocalBusy) { setState(ViewState.BusyLocal); @@ -141,6 +172,10 @@ class PatientSearchViewModel extends BaseViewModel { setState(ViewState.Busy); } if (inPatientList.length == 0 || isForceFullRefresh) await _inPatientService.getInPatientList(requestModel, false, isVidaPlusProject: isVidaPlusProject); + if(isVidaPlusProject) { + this.requestModel = requestModel; + this.isVidaPlusProject = isVidaPlusProject; + } if (_inPatientService.hasError) { error = _inPatientService.error; if (isLocalBusy) { @@ -154,17 +189,42 @@ class PatientSearchViewModel extends BaseViewModel { setState(ViewState.Idle); } } + FutureOr paginatedInPatientCall(String? selectedClinicName, String? query, bool? isSortDes,{bool isMyInPatient = false,bool isAllClinic = false }) async { + + if(isVidaPlusProject = false) return; + + if(this.requestModel == null) return; + PatientSearchRequestModel requestModel = this.requestModel!; + requestModel.pageIndex = (requestModel.pageIndex??0)+1; + paginationLoading = true; + notifyListeners(); + await _inPatientService.getInPatientList(requestModel, false, isVidaPlusProject: isVidaPlusProject); + paginationLoading = false; + notifyListeners(); + if (_inPatientService.hasError) { + error = _inPatientService.error; + + } else { + this.requestModel = requestModel; + await setDefaultInPatientList(isFromPagination: true); + generateInpatientClinicList(isFromPagination: true); + sortInPatient(isDes:isSortDes == true, isAllClinic: isAllClinic, isMyInPatient: isMyInPatient ); + filterSearchResults(query!, isAllClinic: isAllClinic, isMyInPatient: isMyInPatient); + filterByClinic(clinicName: selectedClinicName!); + } + + } sortInPatient({bool isDes = false, bool isAllClinic = false, bool isMyInPatient = false}) { if (isMyInPatient ? myIinPatientList.length > 0 : isAllClinic - ? inPatientList.length > 0 + ? mainList.length > 0 : filteredInPatientItems.length > 0) { List localInPatient = isMyInPatient ? [...filteredMyInPatientItems] : isAllClinic - ? [...inPatientList] + ? [...mainList] : [...filteredInPatientItems]; if (isDes) localInPatient.sort((PatiantInformtion a, PatiantInformtion b) => b.admissionDateWithDateTimeForm!.compareTo(a.admissionDateWithDateTimeForm!)); @@ -188,16 +248,19 @@ class PatientSearchViewModel extends BaseViewModel { filteredInPatientItems.clear(); firstSubsetIndex = 0; lastSubsetIndex = inPatientPageSize - 1; + requestModel?.pageIndex = 0; } - Future setDefaultInPatientList() async { + Future setDefaultInPatientList({isFromPagination = false}) async { setState(ViewState.BusyLocal); await getDoctorProfile(); - resetInPatientPagination(); + if(isFromPagination == false) { + resetInPatientPagination(); + } if (inPatientList.length > 0) { - lastSubsetIndex = (inPatientList.length < inPatientPageSize - 1 ? inPatientList.length : inPatientPageSize - 1); - - filteredInPatientItems.addAll(inPatientList.sublist(firstSubsetIndex, lastSubsetIndex)); + // lastSubsetIndex = (inPatientList.length < inPatientPageSize - 1 ? inPatientList.length : inPatientPageSize - 1); + mainList.addAll(inPatientList); + filteredInPatientItems.addAll(inPatientList); } if (myIinPatientList.length > 0) { @@ -206,8 +269,10 @@ class PatientSearchViewModel extends BaseViewModel { setState(ViewState.Idle); } - generateInpatientClinicList() { - InpatientClinicList.clear(); + generateInpatientClinicList({bool isFromPagination = false}) { + if(isFromPagination == false) { + InpatientClinicList.clear(); + } inPatientList.forEach((element) { if (!InpatientClinicList.contains(element.clinicDescription)) { InpatientClinicList.add(element.clinicDescription!); @@ -242,15 +307,20 @@ class PatientSearchViewModel extends BaseViewModel { } filterByClinic({required String clinicName}) { + if(clinicName.isEmpty) return; filteredInPatientItems = []; - for (var i = 0; i < inPatientList.length; i++) { - if (inPatientList[i].clinicDescription == clinicName) { - filteredInPatientItems.add(inPatientList[i]); + for (var i = 0; i < mainList.length; i++) { + if (mainList[i].clinicDescription == clinicName) { + filteredInPatientItems.add(mainList[i]); } } notifyListeners(); } + void resetFilters(){ + filteredInPatientItems = mainList; + } + void clearPatientList() { _inPatientService.inPatientList = []; _inPatientService.myInPatientList = []; @@ -260,7 +330,7 @@ class PatientSearchViewModel extends BaseViewModel { var strExist = query.length > 0 ? true : false; if (isMyInPatient) { - List localFilteredMyInPatientItems = [...myIinPatientList]; + List localFilteredMyInPatientItems = [...filteredMyInPatientItems]; if (strExist) { filteredMyInPatientItems.clear(); @@ -276,28 +346,28 @@ class PatientSearchViewModel extends BaseViewModel { } notifyListeners(); } else { - if (myIinPatientList.length > 0) filteredMyInPatientItems.clear(); - filteredMyInPatientItems.addAll(myIinPatientList); + if (filteredMyInPatientItems.length > 0) filteredMyInPatientItems.clear(); + filteredMyInPatientItems.addAll(localFilteredMyInPatientItems); notifyListeners(); } } else { if (isAllClinic) { if (strExist) { filteredInPatientItems = []; - for (var i = 0; i < inPatientList.length; i++) { - String firstName = inPatientList[i].firstName!.toUpperCase(); - String lastName = inPatientList[i].lastName!.toUpperCase(); - String mobile = inPatientList[i].mobileNumber!.toUpperCase(); - String patientID = inPatientList[i].patientId.toString(); + for (var i = 0; i < mainList.length; i++) { + String firstName = mainList[i].firstName!.toUpperCase(); + String lastName = mainList[i].lastName!.toUpperCase(); + String mobile = mainList[i].mobileNumber!.toUpperCase(); + String patientID = mainList[i].patientId.toString(); if (firstName.contains(query.toUpperCase()) || lastName.contains(query.toUpperCase()) || mobile.contains(query) || patientID.contains(query)) { - filteredInPatientItems.add(inPatientList[i]); + filteredInPatientItems.add(mainList[i]); } } notifyListeners(); } else { - if (inPatientList.length > 0) filteredInPatientItems.clear(); - filteredInPatientItems.addAll(inPatientList); + if (mainList.length > 0) filteredInPatientItems.clear(); + filteredInPatientItems.addAll(mainList); notifyListeners(); } } else { diff --git a/lib/screens/patients/In_patient/filter_date_page.dart b/lib/screens/patients/In_patient/filter_date_page.dart index 39cb25f8..b4147f25 100644 --- a/lib/screens/patients/In_patient/filter_date_page.dart +++ b/lib/screens/patients/In_patient/filter_date_page.dart @@ -154,7 +154,6 @@ class _FilterDatePageState extends State { to: "${AppDateUtils.convertDateToFormat(widget.patientSearchViewModel.selectedToDate!, "yyyy-MM-dd")} 00:00:00", searchType: null)..transformDataForVidaPlus(), isForceFullRefresh: true); - Navigator.of(context).pop(); } }, ), diff --git a/lib/screens/patients/In_patient/in_patient_list_page.dart b/lib/screens/patients/In_patient/in_patient_list_page.dart index 17d0b718..3d5cdc5f 100644 --- a/lib/screens/patients/In_patient/in_patient_list_page.dart +++ b/lib/screens/patients/In_patient/in_patient_list_page.dart @@ -1,3 +1,4 @@ +import 'package:doctor_app_flutter/config/config.dart'; import 'package:doctor_app_flutter/config/size_config.dart'; import 'package:doctor_app_flutter/core/enum/view_state.dart'; import 'package:doctor_app_flutter/core/viewModel/PatientSearchViewModel.dart'; @@ -52,7 +53,7 @@ class _InPatientListPageState extends State { body: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (!widget.isMyInPatient && widget.patientSearchViewModel!.inPatientList.isNotEmpty) + if (!widget.isMyInPatient && widget.patientSearchViewModel!.mainList.isNotEmpty) Container( margin: EdgeInsets.only(left: 10.0, right: 10, top: 10), child: Row( @@ -68,7 +69,7 @@ class _InPatientListPageState extends State { setState(() { widget.onChangeValue!(isAllClinic: true, showBottomSheet: false, selectedClinicName: null); - widget.patientSearchViewModel!.setDefaultInPatientList(); + widget.patientSearchViewModel!.resetFilters(); }); }, activeColor: Colors.red, @@ -82,7 +83,7 @@ class _InPatientListPageState extends State { onTap: () { widget.onChangeValue!(isAllClinic: true, showBottomSheet: false, selectedClinicName: null); - widget.patientSearchViewModel!.setDefaultInPatientList(); + widget.patientSearchViewModel!.resetFilters(); }, ), if (widget.patientSearchViewModel!.InpatientClinicList.isNotEmpty) @@ -162,7 +163,8 @@ class _InPatientListPageState extends State { : widget.patientSearchViewModel!.filteredInPatientItems.length > 0 ? (widget.isMyInPatient && widget.patientSearchViewModel!.myIinPatientList.length == 0) ? NoData() - : ListOfAllInPatient(isAllClinic: widget.isAllClinic, hasQuery: hasQuery, patientSearchViewModel: widget.patientSearchViewModel!, isVidaPlusProject: widget.isVidaPlusProject,) + : ListOfAllInPatient(isAllClinic: widget.isAllClinic, hasQuery: hasQuery, patientSearchViewModel: widget.patientSearchViewModel!, isVidaPlusProject: widget + .isVidaPlusProject,query: _searchController.text, selectedClinicName: widget.selectedClinicName,isSortDes: isSortDes) : NoData() : Center( child: Container( @@ -170,6 +172,19 @@ class _InPatientListPageState extends State { width: 300, ), ), + SizedBox(height: 16,), + Visibility( + visible: widget.patientSearchViewModel!.paginationLoading, + child: Center( + child: SizedBox( + height: 25, + width: 25, + child: CircularProgressIndicator( + color: AppGlobal.appRedColor, + ), + ), + ), + ) ], ), bottomSheet: !widget.showBottomSheet diff --git a/lib/screens/patients/In_patient/in_patient_screen.dart b/lib/screens/patients/In_patient/in_patient_screen.dart index b4eb1145..0971142f 100644 --- a/lib/screens/patients/In_patient/in_patient_screen.dart +++ b/lib/screens/patients/In_patient/in_patient_screen.dart @@ -95,6 +95,7 @@ class _InPatientScreenState extends State with SingleTickerProv requestModel.pageSize = 10; requestModel.pageIndex = 0; requestModel.searchType = null; + requestModel.transformDataForVidaPlus(); } await model.getInPatientList(requestModel, isVidaPlusProject: isVidaPlusProject); @@ -159,12 +160,12 @@ class _InPatientScreenState extends State with SingleTickerProv labelPadding: EdgeInsets.only(top: 0, left: 0, right: 0, bottom: 0), // unselectedLabelColor: Colors.grey[800], tabs: [ - tabWidget(screenSize, _activeTab == 0, TranslationBase.of(context).inPatientAll, context: context, counter: model.inPatientList.length, isFirst: true), + tabWidget(screenSize, _activeTab == 0, TranslationBase.of(context).inPatientAll, context: context, counter: model.mainList.length, isFirst: true), tabWidget( screenSize, _activeTab == 1, TranslationBase.of(context).myInPatientTitle, - counter: model.myIinPatientList.length, + counter: model.filteredMyInPatientItems.length, isMiddle: true, context: context, ), @@ -225,7 +226,7 @@ class _InPatientScreenState extends State with SingleTickerProv setState(() { widget.isAllClinic = isAllClinic; widget.showBottomSheet = showBottomSheet; - widget.selectedClinicName = selectedClinicName; + widget.selectedClinicName = selectedClinicName??""; }); } diff --git a/lib/screens/patients/In_patient/list_of_all_in_patient.dart b/lib/screens/patients/In_patient/list_of_all_in_patient.dart index 1458020a..1797edfa 100644 --- a/lib/screens/patients/In_patient/list_of_all_in_patient.dart +++ b/lib/screens/patients/In_patient/list_of_all_in_patient.dart @@ -1,3 +1,4 @@ +import 'package:doctor_app_flutter/config/config.dart'; import 'package:doctor_app_flutter/core/viewModel/PatientSearchViewModel.dart'; import 'package:doctor_app_flutter/widgets/patients/patient_card/PatientCard.dart'; import 'package:flutter/material.dart'; @@ -6,40 +7,67 @@ import 'package:flutter/scheduler.dart'; import '../../../routes.dart'; import 'NoData.dart'; -class ListOfAllInPatient extends StatelessWidget { +class ListOfAllInPatient extends StatefulWidget { const ListOfAllInPatient({ Key? key, required this.isAllClinic, required this.hasQuery, required this.patientSearchViewModel, - required this.isVidaPlusProject, + required this.isVidaPlusProject, this.selectedClinicName,this.query, this.isSortDes }) : super(key: key); final bool isAllClinic; final bool hasQuery; final bool isVidaPlusProject; + final bool? isSortDes; + final String? selectedClinicName; + final String? query; final PatientSearchViewModel patientSearchViewModel; + @override + State createState() => _ListOfAllInPatientState(); +} + +class _ListOfAllInPatientState extends State { + final ScrollController _scrollController = ScrollController(); + + @override + void initState() { + + // 2. Add a listener to the controller to trigger pagination. + _scrollController.addListener(() { + // Check if we are at the end of the list and not currently loading. + if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 50 && + !widget.patientSearchViewModel.paginationLoading) { + // Trigger the data fetch only if pagination is allowed. + + widget.patientSearchViewModel.paginatedInPatientCall(widget.selectedClinicName,widget.query, widget.isSortDes); + + } + }); + super.initState(); + } @override Widget build(BuildContext context) { return Expanded( child: Container( margin: EdgeInsets.symmetric(horizontal: 16.0), - child: patientSearchViewModel.filteredInPatientItems.length == 0 + child: widget.patientSearchViewModel.filteredInPatientItems.length == 0 ? NoData() : NotificationListener( child: ListView.builder( - itemCount: patientSearchViewModel.filteredInPatientItems.length, + itemCount: widget.patientSearchViewModel.filteredInPatientItems.length, scrollDirection: Axis.vertical, shrinkWrap: true, + controller : _scrollController, physics: const AlwaysScrollableScrollPhysics(), itemBuilder: (context, index) { return PatientCard( - patientInfo: patientSearchViewModel.filteredInPatientItems[index], + patientInfo: widget.patientSearchViewModel.filteredInPatientItems[index], patientType: "1", arrivalType: "1", isInpatient: true, - isMyPatient: patientSearchViewModel.filteredInPatientItems[index].doctorId == patientSearchViewModel.doctorProfile!.doctorID, + isMyPatient: widget.patientSearchViewModel.filteredInPatientItems[index].doctorId == widget.patientSearchViewModel.doctorProfile!.doctorID, onTap: () { FocusScopeNode currentFocus = FocusScope.of(context); if (!currentFocus.hasPrimaryFocus) { @@ -48,28 +76,28 @@ class ListOfAllInPatient extends StatelessWidget { SchedulerBinding.instance.addPostFrameCallback((_) { Navigator.of(context).pushNamed(PATIENTS_PROFILE, arguments: { - "patient": patientSearchViewModel.filteredInPatientItems[index], + "patient": widget.patientSearchViewModel.filteredInPatientItems[index], "patientType": "1", "from": "0", "to": "0", "isSearch": false, "isInpatient": true, "arrivalType": "1", - "isMyPatient": patientSearchViewModel.filteredInPatientItems[index].doctorId == patientSearchViewModel.doctorProfile!.doctorID, - "isVidaPlusProject": isVidaPlusProject, + "isMyPatient": widget.patientSearchViewModel.filteredInPatientItems[index].doctorId == widget.patientSearchViewModel.doctorProfile!.doctorID, + "isVidaPlusProject": widget.isVidaPlusProject, }); }); }, ); }), onNotification: (notification) { - if (isAllClinic && !hasQuery) if (notification is ScrollUpdateNotification) { + if (widget.isAllClinic && !widget.hasQuery) if (notification is ScrollUpdateNotification) { if (notification.metrics.pixels >= notification.metrics.maxScrollExtent - 50) { - patientSearchViewModel.addOnFilteredList(); + widget.patientSearchViewModel.addOnFilteredList(); } if (notification.metrics.pixels <= notification.metrics.minScrollExtent - 50) { - patientSearchViewModel.removeOnFilteredList(); + widget.patientSearchViewModel.removeOnFilteredList(); } } return false;