Merge branch 'master' into haroon_dev

pull/368/head
haroon amjad 1 month ago
commit 2bbf5d22d5

@ -211,7 +211,7 @@ class ApiClientImp implements ApiClient {
}
// body['TokenID'] = "@dm!n";
// body['PatientID'] = 945786;
// body['PatientID'] = 1307867;
// body['PatientID'] = 53320;
// body['PatientTypeID'] = 1;
// body['PatientOutSA'] = 0;

@ -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';

@ -67,7 +67,7 @@ abstract class HmgServicesRepo {
Future<Either<Failure, GenericApiModel<Covid19GetPaymentInfo>>> getCovidPaymentInfo(String procedureID, int projectID);
Future<Either<Failure, GenericApiModel<List<VitalSignResModel>>>> getPatientVitalSign();
Future<Either<Failure, GenericApiModel<List<VitalSignResModel>>>> getPatientVitalSign( num? transID, int? projectID);
}
class HmgServicesRepoImp implements HmgServicesRepo {
@ -917,8 +917,11 @@ class HmgServicesRepoImp implements HmgServicesRepo {
}
@override
Future<Either<Failure, GenericApiModel<List<VitalSignResModel>>>> getPatientVitalSign() async {
Future<Either<Failure, GenericApiModel<List<VitalSignResModel>>>> getPatientVitalSign(num? transID, int? projectID) async {
Map<String, dynamic> requestBody = {
"TransID": transID,
"TransNo": transID,
"ProjectID": projectID,
};
try {

@ -895,12 +895,23 @@ class HmgServicesViewModel extends ChangeNotifier {
Future<void> 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<List<VitalSignResModel>>(
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<SymptomsCheckerViewModel>().setSelectedHeight(vitalSignList.first.heightCm);
getIt.get<SymptomsCheckerViewModel>().setSelectedWeight(vitalSignList.first.weightKg);

@ -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<AppointmentDetailsPage> {
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<HmgServicesViewModel>();
final appt = widget.patientAppointmentHistoryResponseModel;
final targetTransNo = appt.vitalSignAppointmentNo ?? appt.appointmentNo;
await hmgServicesVM.getPatientVitalSign(
transNo: targetTransNo,
projectID: appt.projectID,
onSuccess: (apiResponse) {
LoaderBottomSheet.hideLoader();
final List<VitalSignResModel> vitalList = (apiResponse is GenericApiModel<List<VitalSignResModel>>)
? (apiResponse.data ?? [])
: (apiResponse.data is List<VitalSignResModel>
? (apiResponse.data as List<VitalSignResModel>)
: 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<AppointmentDetailsPage> {
// ),
// );
}),
MedicalFileCard(
label: LocaleKeys.doctorResponses.tr(context: context),
textColor: AppColors.blackColor,
@ -945,6 +1020,7 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
);
})
: SizedBox.shrink(),
],
);
}),

@ -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<VitalSignDetailsPage> createState() => _VitalSignDetailsPageState();
@ -65,17 +72,28 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
VitalSignDetailsArgs get args => widget.args;
@override
void initState() {
super.initState();
if (widget.isFromAppointment) {
WidgetsBinding.instance.addPostFrameCallback((_) {
getIt.get<HmgServicesViewModel>().getPatientVitalSign(forceRefresh: true);
});
}
}
@override
Widget build(BuildContext context) {
return CollapsingListView(
title: LocaleKeys.vitalSignDetails.tr(context: context),
child: Consumer<HmgServicesViewModel>(
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<VitalSignDetailsPage> {
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<VitalSignDetailsPage> {
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<DataPoint> _buildSeries(List<VitalSignResModel> vitals, VitalSignDetailsArgs args) {
final List<DataPoint> points = [];
@ -578,7 +607,7 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
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<VitalSignDetailsPage> {
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<VitalSignDetailsPage> {
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';
}
}

@ -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<VitalSignPage> createState() => _VitalSignPageState();
@ -53,10 +60,14 @@ class _VitalSignPageState extends State<VitalSignPage> {
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<VitalSignPage> {
child: Consumer<HmgServicesViewModel>(
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<VitalSignPage> {
_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<VitalSignPage> {
icon: AppAssets.bmiVital,
unit: '',
),
targetVitalSign,
),
),
SizedBox(height: 16.h),
@ -130,7 +143,7 @@ class _VitalSignPageState extends State<VitalSignPage> {
_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<VitalSignPage> {
icon: AppAssets.heightVital,
unit: 'cm',
),
targetVitalSign,
),
),
SizedBox(height: 16.h),
@ -148,10 +162,10 @@ class _VitalSignPageState extends State<VitalSignPage> {
_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<VitalSignPage> {
icon: AppAssets.weightVital,
unit: 'kg',
),
targetVitalSign,
),
),
SizedBox(height: 16.h),
@ -168,18 +183,18 @@ class _VitalSignPageState extends State<VitalSignPage> {
_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<VitalSignPage> {
low: 90,
high: 140,
),
targetVitalSign,
),
),
SizedBox(height: 16.h),
@ -199,7 +215,7 @@ class _VitalSignPageState extends State<VitalSignPage> {
_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<VitalSignPage> {
low: 36.1,
high: 37.2,
),
targetVitalSign,
),
),
],
@ -275,9 +292,9 @@ class _VitalSignPageState extends State<VitalSignPage> {
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<VitalSignPage> {
low: 60,
high: 100,
),
targetVitalSign,
),
),
),
@ -299,9 +317,9 @@ class _VitalSignPageState extends State<VitalSignPage> {
_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<VitalSignPage> {
low: 12,
high: 20,
),
targetVitalSign,
),
),
],

Loading…
Cancel
Save