From 75573358b92861d2572511fce38de0ba439022c1 Mon Sep 17 00:00:00 2001 From: taha Date: Thu, 3 Sep 2026 17:10:45 +0300 Subject: [PATCH] Add lab result attachment handling and improve model structure --- .../labs/all_special_lab_result_model.dart | 36 ++- .../labs/patient_lab_special_result.dart | 24 +- .../request_patient_lab_special_result.dart | 3 + .../all_lab_special_result_page.dart | 2 + .../special_lab_result_details_page.dart | 219 +++++++++++++++--- 5 files changed, 251 insertions(+), 33 deletions(-) diff --git a/lib/core/model/labs/all_special_lab_result_model.dart b/lib/core/model/labs/all_special_lab_result_model.dart index 70b4d2cf..04da6d86 100644 --- a/lib/core/model/labs/all_special_lab_result_model.dart +++ b/lib/core/model/labs/all_special_lab_result_model.dart @@ -1,3 +1,5 @@ +import 'lab_result.dart'; + class AllSpecialLabResultModel { dynamic actualDoctorRate; dynamic admissionDate; @@ -10,7 +12,7 @@ class AllSpecialLabResultModel { dynamic clinicDescriptionN; dynamic clinicID; dynamic createdOn; - double? decimalDoctorRate; + dynamic? decimalDoctorRate; dynamic doctorID; String? doctorImageURL; String? doctorName; @@ -51,6 +53,11 @@ class AllSpecialLabResultModel { dynamic status; dynamic statusDesc; String? strOrderDate; + List? FileNameWithExtensionList; + List? UploadedDocuments; + bool? IsLabTestAttachment; // for the attachment icon in the lab result list + dynamic LabAccessionNo; + dynamic FileHash; AllSpecialLabResultModel( {this.actualDoctorRate, @@ -112,9 +119,9 @@ class AllSpecialLabResultModel { appointmentDate = json['AppointmentDate']; appointmentNo = json['AppointmentNo']; appointmentTime = json['AppointmentTime']; - clinicDescription = json['ClinicDescription']; - clinicDescriptionEnglish = json['ClinicDescriptionEnglish']; - clinicDescriptionN = json['ClinicDescriptionN']; + clinicDescription = json['ClinicDescription']??""; + clinicDescriptionEnglish = json['ClinicDescriptionEnglish']??""; + clinicDescriptionN = json['ClinicDescriptionN']??""; clinicID = json['ClinicID']; createdOn = json['CreatedOn']; decimalDoctorRate = json['DecimalDoctorRate']; @@ -146,8 +153,8 @@ class AllSpecialLabResultModel { orderNo = json['OrderNo']; patientID = json['PatientID']; projectID = json['ProjectID']; - projectName = json['ProjectName']; - projectNameN = json['ProjectNameN']; + projectName = json['ProjectName']??""; + projectNameN = json['ProjectNameN']??""; qR = json['QR']; resultData = json['ResultData']; resultDataHTML = json['ResultDataHTML']; @@ -157,6 +164,23 @@ class AllSpecialLabResultModel { status = json['Status']; statusDesc = json['StatusDesc']; strOrderDate = json['StrOrderDate']; + IsLabTestAttachment = json['IsLabTestAttachment'] ?? false; + + LabAccessionNo = json['LabAccessionNo'] ?? json['AccessionNo']; + + if (json['FileNameWithExtensionList'] != null) { + FileNameWithExtensionList = []; + json['FileNameWithExtensionList'].forEach((v) { + if (v["FileNameWithExtension"] != null) FileNameWithExtensionList?.add(v["FileNameWithExtension"]); + }); + } + FileHash = json['FileHash']; + if (json['UploadedDocuments'] != null) { + UploadedDocuments = []; + json['UploadedDocuments'].forEach((v) { + UploadedDocuments!.add(new UploadedDocument.fromJson(v)); + }); + } } Map toJson() { diff --git a/lib/core/model/labs/patient_lab_special_result.dart b/lib/core/model/labs/patient_lab_special_result.dart index 1d224c05..5920bf7e 100644 --- a/lib/core/model/labs/patient_lab_special_result.dart +++ b/lib/core/model/labs/patient_lab_special_result.dart @@ -1,16 +1,24 @@ +import 'lab_result.dart'; + class PatientLabSpecialResult { String? invoiceNo; String? moduleID; String? resultData; String? resultDataHTML; dynamic resultDataTxt; + List? FileNameWithExtensionList; + List? UploadedDocuments; + bool? IsLabTestAttachment; // for the attachment icon in the lab result list PatientLabSpecialResult( {this.invoiceNo, this.moduleID, this.resultData, this.resultDataHTML, - this.resultDataTxt}); + this.resultDataTxt, + this.FileNameWithExtensionList, + this.UploadedDocuments, + this.IsLabTestAttachment}); PatientLabSpecialResult.fromJson(Map json) { invoiceNo = json['InvoiceNo']; @@ -18,6 +26,20 @@ class PatientLabSpecialResult { resultData = json['ResultData']; resultDataHTML = json['ResultDataHTML']; resultDataTxt = json['ResultDataTxt']; + if (json['FileNameWithExtensionList'] != null) { + FileNameWithExtensionList = []; + json['FileNameWithExtensionList'].forEach((v) { + if (v["FileNameWithExtension"] != null) FileNameWithExtensionList?.add(v["FileNameWithExtension"]); + }); + } + if (json['UploadedDocuments'] != null) { + UploadedDocuments = []; + json['UploadedDocuments'].forEach((v) { + UploadedDocuments!.add(new UploadedDocument.fromJson(v)); + }); + } + + IsLabTestAttachment = json['IsLabTestAttachment'] ?? false; } Map toJson() { diff --git a/lib/core/model/labs/request_patient_lab_special_result.dart b/lib/core/model/labs/request_patient_lab_special_result.dart index bac33689..3529e2af 100644 --- a/lib/core/model/labs/request_patient_lab_special_result.dart +++ b/lib/core/model/labs/request_patient_lab_special_result.dart @@ -1,5 +1,6 @@ class RequestPatientLabSpecialResult { String? invoiceNo; + String? invoiceNoVP; String? orderNo; String? setupID; String? projectID; @@ -58,11 +59,13 @@ class RequestPatientLabSpecialResult { tokenID = json['TokenID']; patientTypeID = json['PatientTypeID']; patientType = json['PatientType']; + invoiceNoVP = json['invoiceNoVP']; } Map toJson() { final Map data = new Map(); data['InvoiceNo'] = this.invoiceNo; + data['InvoiceNo_VP'] = this.invoiceNoVP; data['OrderNo'] = this.orderNo; data['SetupID'] = this.setupID; data['ProjectID'] = this.projectID; diff --git a/lib/screens/patients/profile/lab_result/special_result/all_lab_special_result_page.dart b/lib/screens/patients/profile/lab_result/special_result/all_lab_special_result_page.dart index dc7886a1..6600e4e5 100644 --- a/lib/screens/patients/profile/lab_result/special_result/all_lab_special_result_page.dart +++ b/lib/screens/patients/profile/lab_result/special_result/all_lab_special_result_page.dart @@ -153,6 +153,8 @@ class _AllLabSpecialResultState extends State { resultData: model.allSpecialLabList[index] .resultDataHTML!, patient: patient!, + labResult: model.allSpecialLabList[index], + model: model ), ), ), diff --git a/lib/screens/patients/profile/lab_result/special_result/special_lab_result_details_page.dart b/lib/screens/patients/profile/lab_result/special_result/special_lab_result_details_page.dart index c3c47010..2afc0d4c 100644 --- a/lib/screens/patients/profile/lab_result/special_result/special_lab_result_details_page.dart +++ b/lib/screens/patients/profile/lab_result/special_result/special_lab_result_details_page.dart @@ -1,5 +1,7 @@ +import 'package:doctor_app_flutter/core/model/labs/all_special_lab_result_model.dart'; import 'package:doctor_app_flutter/core/viewModel/PatientMedicalReportViewModel.dart'; import 'package:doctor_app_flutter/core/model/patient/patiant_info_model.dart'; +import 'package:doctor_app_flutter/core/viewModel/labs_view_model.dart'; import 'package:doctor_app_flutter/screens/base/base_view.dart'; import 'package:doctor_app_flutter/utils/translations_delegate_base_utils.dart'; import 'package:doctor_app_flutter/widgets/patients/profile/app_bar/patient-profile-app-bar.dart'; @@ -9,11 +11,19 @@ import 'package:doctor_app_flutter/widgets/shared/errors/error_message.dart'; import 'package:flutter/material.dart'; import 'package:flutter_html/flutter_html.dart'; +import '../../../../../config/size_config.dart'; +import '../../../../../core/model/labs/lab_result.dart'; +import '../../../../../widgets/shared/app_texts_widget.dart'; + class SpecialLabResultDetailsPage extends StatelessWidget { final String resultData; final PatiantInformtion patient; - const SpecialLabResultDetailsPage({Key? key, required this.resultData, required this.patient}) + final AllSpecialLabResultModel labResult; + + final LabsViewModel model; + + const SpecialLabResultDetailsPage({Key? key, required this.resultData, required this.patient, required this.labResult, required this.model}) : super(key: key); @override @@ -37,31 +47,80 @@ class SpecialLabResultDetailsPage extends StatelessWidget { TranslationBase.of(context).lab, subTitle: TranslationBase.of(context).result, ), - resultData != null - ? Container( - width: double.infinity, - margin: - EdgeInsets.symmetric(horizontal: 16, vertical: 16), - padding: - EdgeInsets.symmetric(horizontal: 16, vertical: 16), - decoration: BoxDecoration( - color: Colors.white, - shape: BoxShape.rectangle, - borderRadius: BorderRadius.all(Radius.circular(8)), - border: Border.fromBorderSide( - BorderSide( - color: Colors.white, - width: 1.0, - ), - ), - ), - child: Html(data: resultData ?? ""), - ) - : Container( - child: ErrorMessage( - error: TranslationBase.of(context).noItem, - ), - ), + Row( + children: [ + Expanded( + child: Column( + children: [ + ( resultData != null && resultData.isNotEmpty) + ? Container( + width: double.infinity, + margin: + EdgeInsets.symmetric(horizontal: 16, vertical: 16), + padding: + EdgeInsets.symmetric(horizontal: 16, vertical: 16), + decoration: BoxDecoration( + color: Colors.white, + shape: BoxShape.rectangle, + borderRadius: BorderRadius.all(Radius.circular(8)), + border: Border.fromBorderSide( + BorderSide( + color: Colors.white, + width: 1.0, + ), + ), + ), + child: Html(data: resultData ?? ""), + ) + : Container( + child: ErrorMessage( + error: TranslationBase.of(context).noItem, + ), + ), + ], + ), + ), + Visibility( + visible: labResult.IsLabTestAttachment ?? false, + child: InkWell( + onTap: () async { + var labAccessNumber = labResult.LabAccessionNo; + // if (patientLabResultList[index].FileNameWithExtensionList?.isNotEmpty == true || patientLabResultList[index].FileNameWithExtensionList?.length == 1) { + // await model.getLabAttachments(patient, labAccessNumber: labAccessNumber, FileName: patientLabResultList[index].FileNameWithExtensionList?.first); + // model.openPDF(); + // return; + // } + // + // if (patientLabResultList[index].UploadedDocuments?.isNotEmpty == true || patientLabResultList[index].UploadedDocuments?.length == 1) { + // await model.getLabAttachments(patient, labAccessNumber: labAccessNumber, uploadedDocument: patientLabResultList[index].UploadedDocuments?.first); + // model.openPDF(); + // return; + // } + + showUploadedDocumentsBottomSheet( + context, + documents: labResult.UploadedDocuments, + fileName: labResult.FileNameWithExtensionList, + labAccessNumber: labAccessNumber, + projectID: int.parse(labResult.projectID!), + setupID: labResult.setupID); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + AppText( + TranslationBase.of(context).showAttachment, + textDecoration: TextDecoration.underline, + color: Colors.red.shade700, + fontSize: SizeConfig.textMultiplier! * 1.2, + fontWeight: FontWeight.w700, + ), + ], + ), + ), + ), + ], + ), ], ), ), @@ -69,4 +128,112 @@ class SpecialLabResultDetailsPage extends StatelessWidget { ), ); } + void showUploadedDocumentsBottomSheet( + BuildContext context, { + List? documents, + List? fileName, + String? labAccessNumber, + int? projectID, + String? setupID, + }) { + const double kItemHeight = 66.0; // ListTile with contentPadding: 10 + final double screenHeight = MediaQuery.of(context).size.height; + final double maxSheetHeight = screenHeight * 0.5; + final double minSheetHeight = (kItemHeight + 100).clamp(0, maxSheetHeight); + + showModalBottomSheet( + context: context, + isScrollControlled: true, // lets the sheet respect custom height constraints + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(16)), + ), + builder: (BuildContext context) { + return ConstrainedBox( + constraints: BoxConstraints( + minHeight: minSheetHeight, + maxHeight: maxSheetHeight, + ), + child: SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Drag handle + Container( + margin: const EdgeInsets.symmetric(vertical: 8), + width: 40, + height: 4, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(2), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Align( + alignment: AlignmentDirectional.centerStart, + child: Text( + TranslationBase.of(context).selectAnAttachment, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + Flexible( + child: Padding( + padding: const EdgeInsets.all(16), + child: Material( + color: const Color(0X55F2F2F2), + child: ListView.separated( + shrinkWrap: true, + padding: EdgeInsets.zero, + itemCount: documents?.length ?? fileName?.length ?? 0, + separatorBuilder: (context, index) => Padding( + padding: const EdgeInsets.all(8.0), + child: const Divider(height: 1, color: Colors.grey), + ), + itemBuilder: (context, index) { + final docName = documents?[index]?.fileName ?? fileName?[index]; + + return ListTile( + contentPadding: const EdgeInsets.all(10), + title: Text( + docName ?? "Unnamed Document", + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.w500, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + trailing: const Icon( + Icons.chevron_right, + color: Colors.grey, + ), + onTap: () async { + Navigator.pop(context); + if (documents?.isNotEmpty == true) { + await model.getLabAttachments(patient, uploadedDocument: documents![index], projectID: projectID, setupID: setupID); + model.openPDF(); + } + if (fileName?.isNotEmpty == true) { + await model.getLabAttachments(patient, labAccessNumber: labAccessNumber, FileName: fileName![index], projectID: projectID, setupID: setupID); + model.openPDF(); + } + }, + ); + }, + ), + ), + ), + ), + const SizedBox(height: 8), + ], + ), + ), + ); + }, + ); + } }