From e80b6edf1e6e5e2848e7b9cb283d91c9b28d10f1 Mon Sep 17 00:00:00 2001 From: Sultan khan Date: Sun, 30 Aug 2026 17:19:58 +0300 Subject: [PATCH] vital sign for appointment done. --- lib/core/api/api_client.dart | 2 +- lib/core/app_assets.dart | 1 + .../hmg_services/hmg_services_repo.dart | 7 +- .../hmg_services/hmg_services_view_model.dart | 19 +++- .../appointment_details_page.dart | 76 ++++++++++++++++ .../vital_sign/vital_sign_details_page.dart | 76 +++++++++++----- .../vital_sign/vital_sign_page.dart | 87 +++++++++++-------- 7 files changed, 204 insertions(+), 64 deletions(-) diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 3a5ff281..4db0cf8b 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -210,7 +210,7 @@ class ApiClientImp implements ApiClient { body['TokenID'] = "@dm!n"; } - body['TokenID'] = "@dm!n"; + // body['TokenID'] = "@dm!n"; // body['PatientID'] = 1307867; // body['PatientID'] = 53320; // body['PatientTypeID'] = 1; diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index b4611890..d5f46e82 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -110,6 +110,7 @@ class AppAssets { static const String report_icon = '$svgBasePath/report_icon.svg'; static const String radiology_icon = '$svgBasePath/radiology_icon.svg'; static const String prescription_item_icon = '$svgBasePath/prescription_item_icon.svg'; + static const String vital_sign_item_icon = '$svgBasePath/vital_sign_item_icon.svg'; static const String checkin_location_icon = '$svgBasePath/checkin_location_icon.svg'; static const String checkin_nfc_icon = '$svgBasePath/checkin_nfc_icon.svg'; static const String checkin_qr_icon = '$svgBasePath/checkin_qr_icon.svg'; diff --git a/lib/features/hmg_services/hmg_services_repo.dart b/lib/features/hmg_services/hmg_services_repo.dart index 8f439d2c..6e01a561 100644 --- a/lib/features/hmg_services/hmg_services_repo.dart +++ b/lib/features/hmg_services/hmg_services_repo.dart @@ -67,7 +67,7 @@ abstract class HmgServicesRepo { Future>> getCovidPaymentInfo(String procedureID, int projectID); - Future>>> getPatientVitalSign(); + Future>>> getPatientVitalSign( num? transID, int? projectID); } class HmgServicesRepoImp implements HmgServicesRepo { @@ -917,8 +917,11 @@ class HmgServicesRepoImp implements HmgServicesRepo { } @override - Future>>> getPatientVitalSign() async { + Future>>> getPatientVitalSign(num? transID, int? projectID) async { Map requestBody = { + "TransID": transID, + "TransNo": transID, + "ProjectID": projectID, }; try { diff --git a/lib/features/hmg_services/hmg_services_view_model.dart b/lib/features/hmg_services/hmg_services_view_model.dart index da1ae80d..f462aedd 100644 --- a/lib/features/hmg_services/hmg_services_view_model.dart +++ b/lib/features/hmg_services/hmg_services_view_model.dart @@ -895,12 +895,23 @@ class HmgServicesViewModel extends ChangeNotifier { Future getPatientVitalSign({ Function(dynamic)? onSuccess, Function(String)? onError, + num? transNo, + int? projectID, + bool forceRefresh = false, }) async { - if (hasVitalSignDataLoaded) return; + if (hasVitalSignDataLoaded && transNo == null && projectID == null && !forceRefresh) { + if (onSuccess != null) { + onSuccess(GenericApiModel>( + messageStatus: 1, + data: vitalSignList, + )); + } + return; + } isVitalSignLoading = true; notifyListeners(); - final result = await hmgServicesRepo.getPatientVitalSign(); + final result = await hmgServicesRepo.getPatientVitalSign(transNo, projectID); result.fold( (failure) async { @@ -915,7 +926,9 @@ class HmgServicesViewModel extends ChangeNotifier { isVitalSignLoading = false; if (apiResponse.messageStatus == 1) { vitalSignList = apiResponse.data ?? []; - hasVitalSignDataLoaded = true; + if (transNo == null && projectID == null) { + hasVitalSignDataLoaded = true; + } if (vitalSignList.isNotEmpty) { getIt.get().setSelectedHeight(vitalSignList.first.heightCm); getIt.get().setSelectedWeight(vitalSignList.first.weightKg); diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index 4d181230..5ba715e9 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -22,6 +22,9 @@ import 'package:hmg_patient_app_new/features/book_appointments/models/resp_model import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart'; import 'package:hmg_patient_app_new/features/contact_us/models/feedback_type.dart'; import 'package:hmg_patient_app_new/features/hospital/AppPermission.dart'; +import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/vital_sign_respo_model.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.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'; @@ -40,6 +43,7 @@ import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appoin import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/bell_switch.dart'; import 'package:hmg_patient_app_new/presentation/contact_us/feedback_page.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_detail_page.dart'; +import 'package:hmg_patient_app_new/presentation/vital_sign/vital_sign_page.dart'; import 'package:hmg_patient_app_new/services/clarity_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; @@ -666,6 +670,76 @@ class _AppointmentDetailsPageState extends State { padding: EdgeInsets.zero, shrinkWrap: true, children: [ + MedicalFileCard( + label: LocaleKeys.vitalSigns.tr(context: context), + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.vital_sign_item_icon, + isLargeText: true, + iconSize: 36.w, + ).onPress(() async { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingText.tr(context: context)); + final hmgServicesVM = getIt.get(); + final appt = widget.patientAppointmentHistoryResponseModel; + final targetTransNo = appt.vitalSignAppointmentNo ?? appt.appointmentNo; + + await hmgServicesVM.getPatientVitalSign( + transNo: targetTransNo, + projectID: appt.projectID, + onSuccess: (apiResponse) { + LoaderBottomSheet.hideLoader(); + final List vitalList = (apiResponse is GenericApiModel>) + ? (apiResponse.data ?? []) + : (apiResponse.data is List + ? (apiResponse.data as List) + : hmgServicesVM.vitalSignList); + + VitalSignResModel? matchedVitalSign; + if (vitalList.isNotEmpty) { + // 1. Match by vitalSignAppointmentNo or appointmentNo + if (targetTransNo != null) { + final targetStr = targetTransNo.toString(); + for (final v in vitalList) { + if (v.transNo != null && v.transNo.toString() == targetStr) { + matchedVitalSign = v; + break; + } + } + } + + // 2. If specific API returned records for this TransNo/ProjectID, fallback to first + matchedVitalSign ??= vitalList.first; + + Navigator.of(context).push( + CustomPageRoute( + page: VitalSignPage( + specificVitalSign: matchedVitalSign, + isFromAppointment: true, + ), + ), + ); + } else { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: LocaleKeys.noVitalSignsRecordedYet.tr(context: context)), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }, + ); + }), MedicalFileCard( label: LocaleKeys.labResults.tr(context: context), textColor: AppColors.blackColor, @@ -790,6 +864,7 @@ class _AppointmentDetailsPageState extends State { // ), // ); }), + MedicalFileCard( label: LocaleKeys.doctorResponses.tr(context: context), textColor: AppColors.blackColor, @@ -945,6 +1020,7 @@ class _AppointmentDetailsPageState extends State { ); }) : SizedBox.shrink(), + ], ); }), diff --git a/lib/presentation/vital_sign/vital_sign_details_page.dart b/lib/presentation/vital_sign/vital_sign_details_page.dart index 63b21291..48532645 100644 --- a/lib/presentation/vital_sign/vital_sign_details_page.dart +++ b/lib/presentation/vital_sign/vital_sign_details_page.dart @@ -53,8 +53,15 @@ class VitalSignDetailsArgs { class VitalSignDetailsPage extends StatefulWidget { final VitalSignDetailsArgs args; - - const VitalSignDetailsPage({super.key, required this.args}); + final VitalSignResModel? selectedVitalSign; + final bool isFromAppointment; + + const VitalSignDetailsPage({ + super.key, + required this.args, + this.selectedVitalSign, + this.isFromAppointment = false, + }); @override State createState() => _VitalSignDetailsPageState(); @@ -65,17 +72,28 @@ class _VitalSignDetailsPageState extends State { VitalSignDetailsArgs get args => widget.args; + @override + void initState() { + super.initState(); + if (widget.isFromAppointment) { + WidgetsBinding.instance.addPostFrameCallback((_) { + getIt.get().getPatientVitalSign(forceRefresh: true); + }); + } + } + @override Widget build(BuildContext context) { return CollapsingListView( title: LocaleKeys.vitalSignDetails.tr(context: context), child: Consumer( builder: (context, viewModel, child) { - final latest = viewModel.vitalSignList.isNotEmpty ? viewModel.vitalSignList.first : null; + final targetVitalSign = widget.selectedVitalSign ?? + (viewModel.vitalSignList.isNotEmpty ? viewModel.vitalSignList.first : null); final history = _buildSeries(viewModel.vitalSignList, args); - final latestValueText = _latestValueText(latest); - final status = _statusForLatest(latest); + final latestValueText = _latestValueText(targetVitalSign); + final status = _statusForLatest(targetVitalSign); final scheme = VitalSignUiModel.scheme(status: status, label: args.title); return SingleChildScrollView( @@ -89,7 +107,7 @@ class _VitalSignDetailsPageState extends State { valueText: latestValueText, status: status, scheme: scheme, - latestDate: latest?.vitalSignDate, + latestDate: targetVitalSign?.vitalSignDate, ), _whatIsThisResultCard(context), _historyCard(context, history: history), @@ -531,6 +549,17 @@ class _VitalSignDetailsPageState extends State { return RangeAnnotations(horizontalRangeAnnotations: ranges); } + String _formatValue(dynamic value) { + if (value == null) return '--'; + if (value is num && value == 0) return '--'; + if (value is String) { + if (value.trim().isEmpty) return '--'; + final parsed = double.tryParse(value); + if (parsed == null || parsed == 0) return '--'; + } + return value.toString(); + } + List _buildSeries(List vitals, VitalSignDetailsArgs args) { final List points = []; @@ -578,7 +607,7 @@ class _VitalSignDetailsPageState extends State { DataPoint( value: index, label: label, - actualValue: mv.toStringAsFixed(0), + actualValue: _formatValue(mv), time: dt, displayTime: '${dt.day}/${dt.month}/${dt.year}', unitOfMeasurement: args.unit, @@ -618,7 +647,7 @@ class _VitalSignDetailsPageState extends State { DataPoint( value: index, label: label, - actualValue: diastolic.toStringAsFixed(0), + actualValue: _formatValue(diastolic), time: dt, displayTime: '${dt.day}/${dt.month}/${dt.year}', unitOfMeasurement: args.unit, @@ -646,28 +675,27 @@ class _VitalSignDetailsPageState extends State { switch (args.metric) { case VitalSignMetric.bmi: - final v = _toDouble(latest.bodyMassIndex); - return v == null ? '--' : v.toStringAsFixed(0); + return _formatValue(latest.bodyMassIndex); case VitalSignMetric.height: - final v = _toDouble(latest.heightCm); - return v == null ? '--' : '${v.toStringAsFixed(0)} ${args.unit}'; + final formatted = _formatValue(latest.heightCm); + return formatted == '--' ? '--' : (args.unit.isNotEmpty ? '$formatted ${args.unit}' : formatted); case VitalSignMetric.weight: - final v = _toDouble(latest.weightKg); - return v == null ? '--' : '${v.toStringAsFixed(0)} ${args.unit}'; + final formatted = _formatValue(latest.weightKg); + return formatted == '--' ? '--' : (args.unit.isNotEmpty ? '$formatted ${args.unit}' : formatted); case VitalSignMetric.temperature: - final v = _toDouble(latest.temperatureCelcius); - return v == null ? '--' : '${v.toStringAsFixed(0)} ${args.unit}'; + final formatted = _formatValue(latest.temperatureCelcius); + return formatted == '--' ? '--' : (args.unit.isNotEmpty ? '$formatted ${args.unit}' : formatted); case VitalSignMetric.heartRate: - final v = _toDouble(latest.heartRate ?? latest.pulseBeatPerMinute); - return v == null ? '--' : '${v.toStringAsFixed(0)} ${args.unit}'; + final formatted = _formatValue(latest.heartRate ?? latest.pulseBeatPerMinute); + return formatted == '--' ? '--' : (args.unit.isNotEmpty ? '$formatted ${args.unit}' : formatted); case VitalSignMetric.respiratoryRate: - final v = _toDouble(latest.respirationBeatPerMinute); - return v == null ? '--' : '${v.toStringAsFixed(0)} ${args.unit}'; + final formatted = _formatValue(latest.respirationBeatPerMinute); + return formatted == '--' ? '--' : (args.unit.isNotEmpty ? '$formatted ${args.unit}' : formatted); case VitalSignMetric.bloodPressure: - final s = _toDouble(latest.bloodPressureHigher); - final d = _toDouble(latest.bloodPressureLower); - if (s == null || d == null) return '--'; - return '${s.toStringAsFixed(0)}/${d.toStringAsFixed(0)}'; + final s = _formatValue(latest.bloodPressureHigher); + final d = _formatValue(latest.bloodPressureLower); + if (s == '--' || d == '--') return '--'; + return '$s/$d'; } } diff --git a/lib/presentation/vital_sign/vital_sign_page.dart b/lib/presentation/vital_sign/vital_sign_page.dart index 826c3971..2be990f7 100644 --- a/lib/presentation/vital_sign/vital_sign_page.dart +++ b/lib/presentation/vital_sign/vital_sign_page.dart @@ -21,7 +21,14 @@ import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; class VitalSignPage extends StatefulWidget { - const VitalSignPage({super.key}); + final VitalSignResModel? specificVitalSign; + final bool isFromAppointment; + + const VitalSignPage({ + super.key, + this.specificVitalSign, + this.isFromAppointment = false, + }); @override State createState() => _VitalSignPageState(); @@ -53,10 +60,14 @@ class _VitalSignPageState extends State { return value.toString(); } - void _openDetails(VitalSignDetailsArgs args) { + void _openDetails(VitalSignDetailsArgs args, VitalSignResModel? targetVitalSign) { Navigator.of(context).push( CustomPageRoute( - page: VitalSignDetailsPage(args: args), + page: VitalSignDetailsPage( + args: args, + selectedVitalSign: targetVitalSign, + isFromAppointment: widget.isFromAppointment, + ), ), ); } @@ -75,20 +86,21 @@ class _VitalSignPageState extends State { child: Consumer( builder: (context, viewModel, child) { - // Get the latest vital sign data (first item in the list) - VitalSignResModel? latestVitalSign = viewModel.vitalSignList.isNotEmpty - ? viewModel.vitalSignList.first - : null; + // Get target vital sign: specificVitalSign if passed, otherwise first in list + VitalSignResModel? targetVitalSign = widget.specificVitalSign ?? + (viewModel.vitalSignList.isNotEmpty + ? viewModel.vitalSignList.first + : null); // Debug logging for blood pressure - // if (latestVitalSign != null) { + // if (targetVitalSign != null) { // print('=== Blood Pressure Debug ==='); - // print('bloodPressureHigher: ${latestVitalSign.bloodPressureHigher}'); - // print('bloodPressureLower: ${latestVitalSign.bloodPressureLower}'); - // print('bloodPressureHigher type: ${latestVitalSign.bloodPressureHigher.runtimeType}'); - // print('bloodPressureLower type: ${latestVitalSign.bloodPressureLower.runtimeType}'); - // print('bloodPressureHigher == 0: ${latestVitalSign.bloodPressureHigher == 0}'); - // print('bloodPressureLower == 0: ${latestVitalSign.bloodPressureLower == 0}'); + // print('bloodPressureHigher: ${targetVitalSign.bloodPressureHigher}'); + // print('bloodPressureLower: ${targetVitalSign.bloodPressureLower}'); + // print('bloodPressureHigher type: ${targetVitalSign.bloodPressureHigher.runtimeType}'); + // print('bloodPressureLower type: ${targetVitalSign.bloodPressureLower.runtimeType}'); + // print('bloodPressureHigher == 0: ${targetVitalSign.bloodPressureHigher == 0}'); + // print('bloodPressureLower == 0: ${targetVitalSign.bloodPressureLower == 0}'); // print('========================'); // } @@ -112,9 +124,9 @@ class _VitalSignPageState extends State { _buildVitalSignCard( icon: AppAssets.bmiVital, label: LocaleKeys.bmi.tr(context: context), - value: _formatValue(latestVitalSign?.bodyMassIndex), + value: _formatValue(targetVitalSign?.bodyMassIndex), unit: '', - status: VitalSignUiModel.bmiStatus(latestVitalSign?.bodyMassIndex), + status: VitalSignUiModel.bmiStatus(targetVitalSign?.bodyMassIndex), onTap: () => _openDetails( VitalSignDetailsArgs( metric: VitalSignMetric.bmi, @@ -122,6 +134,7 @@ class _VitalSignPageState extends State { icon: AppAssets.bmiVital, unit: '', ), + targetVitalSign, ), ), SizedBox(height: 16.h), @@ -130,7 +143,7 @@ class _VitalSignPageState extends State { _buildVitalSignCard( icon: AppAssets.heightVital, label: LocaleKeys.height.tr(context: context), - value: _formatValue(latestVitalSign?.heightCm), + value: _formatValue(targetVitalSign?.heightCm), unit: 'cm', status: null, onTap: () => _openDetails( @@ -140,6 +153,7 @@ class _VitalSignPageState extends State { icon: AppAssets.heightVital, unit: 'cm', ), + targetVitalSign, ), ), SizedBox(height: 16.h), @@ -148,10 +162,10 @@ class _VitalSignPageState extends State { _buildVitalSignCard( icon: AppAssets.weightVital, label: LocaleKeys.weight.tr(context: context), - value: _formatValue(latestVitalSign?.weightKg), + value: _formatValue(targetVitalSign?.weightKg), unit: 'kg', - status: (latestVitalSign?.weightKg != null) - ? VitalSignUiModel.weightStatus(latestVitalSign?.bodyMassIndex) + status: (targetVitalSign?.weightKg != null) + ? VitalSignUiModel.weightStatus(targetVitalSign?.bodyMassIndex) : null, onTap: () => _openDetails( VitalSignDetailsArgs( @@ -160,6 +174,7 @@ class _VitalSignPageState extends State { icon: AppAssets.weightVital, unit: 'kg', ), + targetVitalSign, ), ), SizedBox(height: 16.h), @@ -168,18 +183,18 @@ class _VitalSignPageState extends State { _buildVitalSignCard( icon: AppAssets.bloodPressure, label: LocaleKeys.bloodPressure.tr(context: context), - value: (latestVitalSign != null && - _isValidValue(latestVitalSign.bloodPressureHigher) && - _isValidValue(latestVitalSign.bloodPressureLower)) - ? '${latestVitalSign.bloodPressureHigher}/${latestVitalSign.bloodPressureLower}' + value: (targetVitalSign != null && + _isValidValue(targetVitalSign.bloodPressureHigher) && + _isValidValue(targetVitalSign.bloodPressureLower)) + ? '${targetVitalSign.bloodPressureHigher}/${targetVitalSign.bloodPressureLower}' : '--', unit: '', - status: (latestVitalSign != null && - _isValidValue(latestVitalSign.bloodPressureHigher) && - _isValidValue(latestVitalSign.bloodPressureLower)) + status: (targetVitalSign != null && + _isValidValue(targetVitalSign.bloodPressureHigher) && + _isValidValue(targetVitalSign.bloodPressureLower)) ? VitalSignUiModel.bloodPressureStatus( - systolic: latestVitalSign.bloodPressureHigher, - diastolic: latestVitalSign.bloodPressureLower, + systolic: targetVitalSign.bloodPressureHigher, + diastolic: targetVitalSign.bloodPressureLower, ) : null, onTap: () => _openDetails( @@ -191,6 +206,7 @@ class _VitalSignPageState extends State { low: 90, high: 140, ), + targetVitalSign, ), ), SizedBox(height: 16.h), @@ -199,7 +215,7 @@ class _VitalSignPageState extends State { _buildVitalSignCard( icon: AppAssets.temperature, label: LocaleKeys.temperature.tr(context: context), - value: _formatValue(latestVitalSign?.temperatureCelcius), + value: _formatValue(targetVitalSign?.temperatureCelcius), unit: '°C', status: null, onTap: () => _openDetails( @@ -211,6 +227,7 @@ class _VitalSignPageState extends State { low: 36.1, high: 37.2, ), + targetVitalSign, ), ), ], @@ -275,9 +292,9 @@ class _VitalSignPageState extends State { child: _buildVitalSignCard( icon: AppAssets.heart, label: LocaleKeys.heart.tr(context: context), - value: _formatValue(latestVitalSign?.heartRate ?? latestVitalSign?.pulseBeatPerMinute), + value: _formatValue(targetVitalSign?.heartRate ?? targetVitalSign?.pulseBeatPerMinute), unit: 'bpm', - status: _isValidValue(latestVitalSign?.heartRate ?? latestVitalSign?.pulseBeatPerMinute) ? VitalSignStatus.normal : null, + status: _isValidValue(targetVitalSign?.heartRate ?? targetVitalSign?.pulseBeatPerMinute) ? VitalSignStatus.normal : null, onTap: () => _openDetails( VitalSignDetailsArgs( metric: VitalSignMetric.heartRate, @@ -287,6 +304,7 @@ class _VitalSignPageState extends State { low: 60, high: 100, ), + targetVitalSign, ), ), ), @@ -299,9 +317,9 @@ class _VitalSignPageState extends State { _buildVitalSignCard( icon: AppAssets.respRate, label: LocaleKeys.respirationRate.tr(context: context), - value: _formatValue(latestVitalSign?.respirationBeatPerMinute), + value: _formatValue(targetVitalSign?.respirationBeatPerMinute), unit: 'bpm', - status: _isValidValue(latestVitalSign?.respirationBeatPerMinute) ? VitalSignStatus.normal : null, + status: _isValidValue(targetVitalSign?.respirationBeatPerMinute) ? VitalSignStatus.normal : null, onTap: () => _openDetails( VitalSignDetailsArgs( metric: VitalSignMetric.respiratoryRate, @@ -311,6 +329,7 @@ class _VitalSignPageState extends State { low: 12, high: 20, ), + targetVitalSign, ), ), ],