From ce488e69546d0953b7fce3d8bb1d7b7413ea954c Mon Sep 17 00:00:00 2001 From: taha Date: Tue, 8 Sep 2026 15:02:22 +0300 Subject: [PATCH] Refactor laboratory result handling to support multiple special results and improve attachment display --- .../labs/patient_lab_special_result.dart | 6 + .../laboratory_result_page.dart | 2 +- .../laboratory_result_widget.dart | 197 +++++++++++++++++- 3 files changed, 194 insertions(+), 11 deletions(-) diff --git a/lib/core/model/labs/patient_lab_special_result.dart b/lib/core/model/labs/patient_lab_special_result.dart index 5920bf7e..809f26b7 100644 --- a/lib/core/model/labs/patient_lab_special_result.dart +++ b/lib/core/model/labs/patient_lab_special_result.dart @@ -1,6 +1,8 @@ import 'lab_result.dart'; class PatientLabSpecialResult { + String? SetupID; + String? ProjectID; String? invoiceNo; String? moduleID; String? resultData; @@ -9,6 +11,7 @@ class PatientLabSpecialResult { List? FileNameWithExtensionList; List? UploadedDocuments; bool? IsLabTestAttachment; // for the attachment icon in the lab result list + dynamic LabAccessionNo; PatientLabSpecialResult( {this.invoiceNo, @@ -21,7 +24,10 @@ class PatientLabSpecialResult { this.IsLabTestAttachment}); PatientLabSpecialResult.fromJson(Map json) { + SetupID = json['SetupID']; + ProjectID = json['ProjectID'].toString(); invoiceNo = json['InvoiceNo']; + LabAccessionNo = json['LabAccessionNo']; moduleID = json['ModuleID']; resultData = json['ResultData']; resultDataHTML = json['ResultDataHTML']; diff --git a/lib/screens/patients/profile/lab_result/laboratory_result/laboratory_result_page.dart b/lib/screens/patients/profile/lab_result/laboratory_result/laboratory_result_page.dart index 03ca0b6f..c26feab9 100644 --- a/lib/screens/patients/profile/lab_result/laboratory_result/laboratory_result_page.dart +++ b/lib/screens/patients/profile/lab_result/laboratory_result/laboratory_result_page.dart @@ -47,7 +47,7 @@ class _LaboratoryResultPageState extends State { LaboratoryResultWidget( onTap: () async {}, billNo: widget.patientLabOrders.invoiceNo!, - details: model.patientLabSpecialResult.length > 0 ? model.patientLabSpecialResult[0].resultDataHTML! : "", + details: model.patientLabSpecialResult, orderNo: widget.patientLabOrders.orderNo!, patientLabOrder: widget.patientLabOrders, patient: widget.patient, diff --git a/lib/screens/patients/profile/lab_result/laboratory_result/laboratory_result_widget.dart b/lib/screens/patients/profile/lab_result/laboratory_result/laboratory_result_widget.dart index 27dbd7ab..c5643fcc 100644 --- a/lib/screens/patients/profile/lab_result/laboratory_result/laboratory_result_widget.dart +++ b/lib/screens/patients/profile/lab_result/laboratory_result/laboratory_result_widget.dart @@ -1,5 +1,7 @@ import 'package:doctor_app_flutter/config/size_config.dart'; +import 'package:doctor_app_flutter/core/model/labs/lab_result.dart'; import 'package:doctor_app_flutter/core/model/labs/patient_lab_orders.dart'; +import 'package:doctor_app_flutter/core/model/labs/patient_lab_special_result.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/core/viewModel/project_view_model.dart'; @@ -17,7 +19,7 @@ import 'package:provider/provider.dart'; class LaboratoryResultWidget extends StatefulWidget { final GestureTapCallback? onTap; final String? billNo; - final String? details; + final List? details; final String? orderNo; final PatientLabOrders? patientLabOrder; final PatiantInformtion? patient; @@ -147,7 +149,7 @@ class _LaboratoryResultWidgetState extends State { SizedBox( height: 15, ), - if (widget.details != null && widget.details!.isNotEmpty) + if (widget.details?.isNotEmpty == true) Column( children: [ InkWell( @@ -200,15 +202,81 @@ class _LaboratoryResultWidgetState extends State { bottomRight: Radius.circular(5.0), )), duration: Duration(milliseconds: 7000), - child: Container( - width: double.infinity, - child: !Utils.isTextHtml(widget.details!) - ? AppText( - widget.details ?? TranslationBase.of(context).noDataAvailable, - ) - : Html( - data: widget.details ?? TranslationBase.of(context).noDataAvailable, + child: ListView.separated( + separatorBuilder: (context, index) => Divider( + color: Colors.grey[300], + thickness: 1, + ), + itemCount: widget.details?.length ?? 0, + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + String? resultData; + try { + resultData = (widget.details?[index])?.resultDataHTML?.toString(); + } catch (_) { + resultData = null; + } + final displayData = (resultData == null || resultData.trim().isEmpty) + ? TranslationBase.of(context).noDataAvailable + : resultData; + + return Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + !Utils.isTextHtml(displayData) + ? AppText(displayData) + : Html(data: displayData), + ], + ), ), + Visibility( + visible: widget.details?[index].IsLabTestAttachment ?? false, + child: InkWell( + onTap: () async { + var labAccessNumber = widget.details?[index].LabAccessionNo; + // if (widget.details?[index].FileNameWithExtensionList?.isNotEmpty == true || widget.details?[index].FileNameWithExtensionList?.length == 1) { + // await model.getLabAttachments(patient, labAccessNumber: labAccessNumber, FileName: widget.details?[index].FileNameWithExtensionList?.first); + // model.openPDF(); + // return; + // } + // + // if (widget.details?[index].UploadedDocuments?.isNotEmpty == true || widget.details?[index].UploadedDocuments?.length == 1) { + // await model.getLabAttachments(patient, labAccessNumber: labAccessNumber, uploadedDocument: widget.details?[index].UploadedDocuments?.first); + // model.openPDF(); + // return; + // } + + showUploadedDocumentsBottomSheet( + context, + model, + documents: widget.details?[index].UploadedDocuments, + fileName: widget.details?[index].FileNameWithExtensionList, + labAccessNumber: labAccessNumber, + projectID: int.parse(widget.details?[index].ProjectID??"0"), + setupID: widget.details?[index].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, + ), + ], + ), + ), + ), + + ], + ); + }, ), ), SizedBox( @@ -226,4 +294,113 @@ class _LaboratoryResultWidgetState extends State { ), ); } + + void showUploadedDocumentsBottomSheet( + BuildContext context, LabsViewModel model, { + 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(widget.patient!, uploadedDocument: documents![index], projectID: projectID, setupID: setupID); + model.openPDF(); + } + if (fileName?.isNotEmpty == true) { + await model.getLabAttachments(widget.patient!, labAccessNumber: labAccessNumber, FileName: fileName![index], projectID: projectID, setupID: setupID); + model.openPDF(); + } + }, + ); + }, + ), + ), + ), + ), + const SizedBox(height: 8), + ], + ), + ), + ); + }, + ); + } }