Compare commits

...

2 Commits

Author SHA1 Message Date
Syed Taha Alam c09170b506 Wd: Lab result graph completed. 4 months ago
Syed Taha Alam 5461d78a1b Wd: graph added with the threshold 4 months ago

@ -2336,4 +2336,5 @@ const Map localizedValues = {
"liveCareTermsConditions47": {"en": "Our Telehealth Services will be for specific medical specialties or follow-up or medication refill appointments.", "ar": "خدمات الرعاية الصحية عن بُعد الخاصة بنا سوف تكون لتخصصات طبية محددة أو لمواعيد المتابعة أو إعادة صرف الدواء. "},
"liveCareTermsConditions48": {"en": "If you have any complaints or concerns about the Application and or the Website, Our Services, or how we handle your personal information please contact us on: EServices.HMG@drsulaimanalhabib.com or call 011 525 9553", "ar": "إذا كانت لديك أي شكاوى أو مخاوف بشأن التطبيق و/أو موقع الويب أو خدماتنا أو كيفية تعاملنا مع معلوماتك الشخصية، فيرجى التواصل معنا على: EServices.HMG@drsulaimanalhabib.com أو الاتصال على الرقم: 9553 525 011"},
"clickPrivacyPolicy": {"en": "Please click here to view the privacy policy", "ar": "الرجاء الضغط هنا لعرض سياسة الخصوصية"},
"thisResult" : {"en": "This Result", "ar": "هذه النتيجة"},
};

@ -19,6 +19,11 @@ class LabOrderResult {
String? verifiedOn;
String? verifiedOnDateTime;
String? changeResult;
String? calculatedResultFlag;
String? criticalHigh;
String? referenceHigh;
String? criticalLow;
String? referenceLow;
LabOrderResult(
{this.description,
@ -39,7 +44,13 @@ class LabOrderResult {
this.testCode,
this.uOM,
this.verifiedOn,
this.verifiedOnDateTime});
this.calculatedResultFlag,
this.verifiedOnDateTime,
this.criticalHigh,
this.referenceHigh,
this.criticalLow,
this.referenceLow,
});
LabOrderResult.fromJson(Map<String, dynamic> json) {
description = json['Description'];
@ -62,6 +73,11 @@ class LabOrderResult {
verifiedOn = json['VerifiedOn'];
verifiedOnDateTime = json['VerifiedOnDateTime'];
changeResult = json['ChangeResult'];
calculatedResultFlag = json['CalculatedResultFlag'];
criticalHigh = json['CriticalHigh'];
referenceHigh = json['ReferenceHigh'];
criticalLow = json['CriticalLow'];
referenceLow = json['ReferenceLow'];
}
Map<String, dynamic> toJson() {
@ -86,6 +102,15 @@ class LabOrderResult {
data['VerifiedOn'] = this.verifiedOn;
data['VerifiedOnDateTime'] = this.verifiedOnDateTime;
data['ChangeResult'] = this.changeResult;
data['CriticalHigh'] = this.criticalHigh;
data['ReferenceHigh'] = this.referenceHigh;
data['CriticalLow'] = this.criticalLow;
data['ReferenceLow'] = this.referenceLow;
return data;
}
@override
String toString() {
return 'LabOrderResult(flag: $calculatedResultFlag, value: $resultValue, verifiedOn: $verifiedOnDateTime)';
}
}

@ -187,8 +187,8 @@ class BaseAppClient {
// body['IdentificationNo'] = 1023854217;
// body['MobileNo'] = "531940021"; //0560717232
// body['PatientID'] = 5690832; //4609100
// body['TokenID'] = "@dm!n";
body['PatientID'] = 1231755; //4609100
body['TokenID'] = "@dm!n";
// Patient ID: 3027574
// Mobile no.: 0502303285

@ -1,3 +1,5 @@
import 'dart:ui';
import 'package:diplomaticquarterapp/config/config.dart';
import 'package:diplomaticquarterapp/core/model/labs/LabOrderResult.dart';
import 'package:diplomaticquarterapp/core/model/labs/lab_result.dart';
@ -7,6 +9,8 @@ import 'package:diplomaticquarterapp/core/model/labs/request_patient_lab_special
import 'package:diplomaticquarterapp/core/model/labs/request_send_lab_report_email.dart';
import 'package:diplomaticquarterapp/core/service/base_service.dart';
import 'package:diplomaticquarterapp/models/Authentication/authenticated_user.dart';
import 'package:diplomaticquarterapp/uitl/date_uitl.dart';
import 'package:diplomaticquarterapp/widgets/data_display/medical/LabResult/lab_result_graph.dart';
class LabsService extends BaseService {
List<PatientLabOrders> patientLabOrdersList = [];
@ -17,7 +21,8 @@ class LabsService extends BaseService {
hasError = false;
Map<String, dynamic> body = Map();
body['isDentalAllowedBackend'] = false;
await baseAppClient.post(GET_Patient_LAB_ORDERS, onSuccess: (dynamic response, int statusCode) {
await baseAppClient.post(GET_Patient_LAB_ORDERS,
onSuccess: (dynamic response, int statusCode) {
patientLabOrdersList.clear();
response['ListPLO'].forEach((hospital) {
patientLabOrdersList.add(PatientLabOrders.fromJson(hospital));
@ -28,13 +33,21 @@ class LabsService extends BaseService {
}, body: body);
}
RequestPatientLabSpecialResult _requestPatientLabSpecialResult = RequestPatientLabSpecialResult();
RequestPatientLabSpecialResult _requestPatientLabSpecialResult =
RequestPatientLabSpecialResult();
List<PatientLabSpecialResult> patientLabSpecialResult = [];
List<LabResult> labResultList = [];
List<LabOrderResult> labOrdersResultsList = [];
Future getLaboratoryResult({String? projectID, int? clinicID, String? invoiceNo, String? invoiceType, String? orderNo, String? setupID, bool? isVidaPlus}) async {
Future getLaboratoryResult(
{String? projectID,
int? clinicID,
String? invoiceNo,
String? invoiceType,
String? orderNo,
String? setupID,
bool? isVidaPlus}) async {
hasError = false;
_requestPatientLabSpecialResult.projectID = projectID;
_requestPatientLabSpecialResult.clinicID = clinicID;
@ -47,7 +60,8 @@ class LabsService extends BaseService {
_requestPatientLabSpecialResult.orderNo = orderNo;
_requestPatientLabSpecialResult.setupID = setupID;
await baseAppClient.post(GET_Patient_LAB_SPECIAL_RESULT, onSuccess: (dynamic response, int statusCode) {
await baseAppClient.post(GET_Patient_LAB_SPECIAL_RESULT,
onSuccess: (dynamic response, int statusCode) {
patientLabSpecialResult.clear();
response['ListPLSR'].forEach((hospital) {
patientLabSpecialResult.add(PatientLabSpecialResult.fromJson(hospital));
@ -58,7 +72,8 @@ class LabsService extends BaseService {
}, body: _requestPatientLabSpecialResult.toJson());
}
Future getPatientLabResult({PatientLabOrders? patientLabOrder, bool? isVidaPlus}) async {
Future getPatientLabResult(
{PatientLabOrders? patientLabOrder, bool? isVidaPlus}) async {
hasError = false;
Map<String, dynamic> body = Map();
body['InvoiceNo_VP'] = isVidaPlus! ? patientLabOrder!.invoiceNo : "0";
@ -69,7 +84,8 @@ class LabsService extends BaseService {
body['SetupID'] = patientLabOrder.setupID;
body['ProjectID'] = patientLabOrder.projectID;
body['ClinicID'] = patientLabOrder.clinicID;
await baseAppClient.post(GET_Patient_LAB_RESULT, onSuccess: (dynamic response, int statusCode) {
await baseAppClient.post(GET_Patient_LAB_RESULT,
onSuccess: (dynamic response, int statusCode) {
patientLabSpecialResult.clear();
labResultList.clear();
response['ListPLR'].forEach((lab) {
@ -83,7 +99,8 @@ class LabsService extends BaseService {
}, body: body);
}
Future generateCovidLabReport(LabResult covidLabResult, String isOutsideKSA) async {
Future generateCovidLabReport(
LabResult covidLabResult, String isOutsideKSA) async {
hasError = false;
Map<String, dynamic> body = Map();
@ -100,7 +117,8 @@ class LabsService extends BaseService {
dynamic localRes;
await baseAppClient.post(SEND_COVID_LAB_RESULT_EMAIL, onSuccess: (dynamic response, int statusCode) {
await baseAppClient.post(SEND_COVID_LAB_RESULT_EMAIL,
onSuccess: (dynamic response, int statusCode) {
localRes = response;
}, onFailure: (String error, int statusCode) {
hasError = true;
@ -117,7 +135,8 @@ class LabsService extends BaseService {
dynamic localRes;
await baseAppClient.post(COVID_PASSPORT_UPDATE, onSuccess: (dynamic response, int statusCode) {
await baseAppClient.post(COVID_PASSPORT_UPDATE,
onSuccess: (dynamic response, int statusCode) {
localRes = response;
}, onFailure: (String error, int statusCode) {
hasError = true;
@ -126,7 +145,14 @@ class LabsService extends BaseService {
return Future.value(localRes);
}
Future updateWorkplaceName(String workplaceName, String workplaceNameAR, String occupation, String occupationAR, int requestNumber, String setupID, int projectID) async {
Future updateWorkplaceName(
String workplaceName,
String workplaceNameAR,
String occupation,
String occupationAR,
int requestNumber,
String setupID,
int projectID) async {
// Future updateWorkplaceName(String workplaceName, String workplaceNameAR, int requestNumber, String setupID, int projectID) async {
hasError = false;
Map<String, dynamic> body = Map();
@ -143,7 +169,8 @@ class LabsService extends BaseService {
dynamic localRes;
await baseAppClient.post(UPDATE_WORKPLACE_NAME, onSuccess: (dynamic response, int statusCode) {
await baseAppClient.post(UPDATE_WORKPLACE_NAME,
onSuccess: (dynamic response, int statusCode) {
localRes = response;
}, onFailure: (String error, int statusCode) {
hasError = true;
@ -158,7 +185,8 @@ class LabsService extends BaseService {
dynamic localRes;
await baseAppClient.post(GET_PATIENT_PASSPORT_NUMBER, onSuccess: (dynamic response, int statusCode) {
await baseAppClient.post(GET_PATIENT_PASSPORT_NUMBER,
onSuccess: (dynamic response, int statusCode) {
localRes = response;
}, onFailure: (String error, int statusCode) {
hasError = true;
@ -176,7 +204,8 @@ class LabsService extends BaseService {
dynamic localRes;
await baseAppClient.post(GET_SICKLEAVE_STATUS_ADMISSION_NO, onSuccess: (dynamic response, int statusCode) {
await baseAppClient.post(GET_SICKLEAVE_STATUS_ADMISSION_NO,
onSuccess: (dynamic response, int statusCode) {
localRes = response;
}, onFailure: (String error, int statusCode) {
hasError = true;
@ -185,7 +214,10 @@ class LabsService extends BaseService {
return Future.value(localRes);
}
Future getPatientLabOrdersResults({PatientLabOrders? patientLabOrder, String? procedure, bool isVidaPlus = false}) async {
Future getPatientLabOrdersResults(
{PatientLabOrders? patientLabOrder,
String? procedure,
bool isVidaPlus = false}) async {
hasError = false;
Map<String, dynamic> body = Map();
body['InvoiceNo_VP'] = isVidaPlus ? patientLabOrder!.invoiceNo : "0";
@ -198,7 +230,8 @@ class LabsService extends BaseService {
body['Procedure'] = procedure;
body['Procedure'] = procedure;
body['LanguageID'] = 1;
await baseAppClient.post(GET_Patient_LAB_ORDERS_RESULT, onSuccess: (dynamic response, int statusCode) {
await baseAppClient.post(GET_Patient_LAB_ORDERS_RESULT,
onSuccess: (dynamic response, int statusCode) {
labOrdersResultsList.clear();
response['ListPLR'].forEach((lab) {
labOrdersResultsList.add(LabOrderResult.fromJson(lab));
@ -210,20 +243,31 @@ class LabsService extends BaseService {
}, body: body);
}
RequestSendLabReportEmail _requestSendLabReportEmail = RequestSendLabReportEmail();
RequestSendLabReportEmail _requestSendLabReportEmail =
RequestSendLabReportEmail();
Future sendLabReportEmail({PatientLabOrders? patientLabOrder, AuthenticatedUser? userObj, bool isVidaPlus = false, bool isDownload = false, int languageID = 1}) async {
Future sendLabReportEmail(
{PatientLabOrders? patientLabOrder,
AuthenticatedUser? userObj,
bool isVidaPlus = false,
bool isDownload = false,
int languageID = 1}) async {
_requestSendLabReportEmail.projectID = patientLabOrder!.projectID;
_requestSendLabReportEmail.invoiceNo = isVidaPlus ? "0" : patientLabOrder.invoiceNo;
_requestSendLabReportEmail.invoiceNoVP = isVidaPlus ? patientLabOrder.invoiceNo : "0";
_requestSendLabReportEmail.invoiceNo =
isVidaPlus ? "0" : patientLabOrder.invoiceNo;
_requestSendLabReportEmail.invoiceNoVP =
isVidaPlus ? patientLabOrder.invoiceNo : "0";
_requestSendLabReportEmail.invoiceType = patientLabOrder.invoiceType;
_requestSendLabReportEmail.doctorName = patientLabOrder.doctorName;
_requestSendLabReportEmail.clinicName = patientLabOrder.clinicDescription;
_requestSendLabReportEmail.patientName = userObj!.firstName! + " " + userObj.lastName!;
_requestSendLabReportEmail.patientIditificationNum = userObj.patientIdentificationNo;
_requestSendLabReportEmail.patientName =
userObj!.firstName! + " " + userObj.lastName!;
_requestSendLabReportEmail.patientIditificationNum =
userObj.patientIdentificationNo;
_requestSendLabReportEmail.dateofBirth = userObj.dateofBirth;
_requestSendLabReportEmail.to = userObj.emailAddress;
_requestSendLabReportEmail.orderDate = '${patientLabOrder.orderDate!.year}-${patientLabOrder.orderDate!.month}-${patientLabOrder.orderDate!.day}';
_requestSendLabReportEmail.orderDate =
'${patientLabOrder.orderDate!.year}-${patientLabOrder.orderDate!.month}-${patientLabOrder.orderDate!.day}';
_requestSendLabReportEmail.patientMobileNumber = userObj.mobileNumber;
_requestSendLabReportEmail.projectName = patientLabOrder.projectName;
_requestSendLabReportEmail.setupID = patientLabOrder.setupID;
@ -234,13 +278,528 @@ class LabsService extends BaseService {
_requestSendLabReportEmail.languageID = languageID;
// await baseAppClient.post(SEND_LAB_RESULT_EMAIL, onSuccess: (dynamic response, int statusCode) {
await baseAppClient.post(isVidaPlus ? SEND_LAB_RESULT_EMAIL : SEND_LAB_RESULT_EMAIL_NEW, onSuccess: (dynamic response, int statusCode) {
await baseAppClient
.post(isVidaPlus ? SEND_LAB_RESULT_EMAIL : SEND_LAB_RESULT_EMAIL_NEW,
onSuccess: (dynamic response, int statusCode) {
if (isDownload) {
labReportPDF = isVidaPlus ? response['LabReportsPDFContent'] : response['PdfContent'];
labReportPDF = isVidaPlus
? response['LabReportsPDFContent']
: response['PdfContent'];
}
}, onFailure: (String error, int statusCode) {
hasError = true;
super.error = error;
}, body: _requestSendLabReportEmail.toJson());
}
List<LabOrderResult> sortByFlagAndValue(List<LabOrderResult> original) {
const priorityOrder = ['LCL', 'CL', 'L', 'N', 'H', 'CH', 'HCH'];
int getFlagPriority(String? flag) {
if (flag == null) return priorityOrder.length;
final index = priorityOrder.indexOf(flag);
return index == -1 ? priorityOrder.length : index;
}
double parseResultValue(String? value) {
if (value == null) return double.nan;
return double.tryParse(value) ?? double.nan;
}
final copy = List<LabOrderResult>.from(original);
copy.sort((a, b) {
final aFlagPriority = getFlagPriority(a.calculatedResultFlag);
final bFlagPriority = getFlagPriority(b.calculatedResultFlag);
if (aFlagPriority != bFlagPriority) {
return aFlagPriority.compareTo(bFlagPriority);
}
final aValue = parseResultValue(a.resultValue);
final bValue = parseResultValue(b.resultValue);
return aValue.compareTo(bValue);
});
return copy;
}
Map<String, LabOrderResult> mapFirstItemByPriority(
List<LabOrderResult> sortedResults) {
final Map<String, LabOrderResult> priorityMap = {};
const priorityOrder = ['LCL', 'CL', 'L', 'N', 'H', 'CH', 'HCH'];
for (final result in sortedResults) {
final priority = result.calculatedResultFlag?.trim();
if (priority != null &&
priorityOrder.contains(priority) &&
!priorityMap.containsKey(priority)) {
priorityMap[priority] = result;
}
// Early exit if all priorities are found
if (priorityMap.length == priorityOrder.length) break;
}
print("the map of priority is :\n $priorityMap");
return priorityMap;
}
List<LabOrderResult> getMostRecentThree(List<LabOrderResult> original) {
DateTime? parseVerifiedDate(String? raw) {
if (raw == null) return null;
final regex = RegExp(r'\/Date\((\d+)\)\/');
final match = regex.firstMatch(raw);
if (match != null) {
print("the match iss not null");
final millis = int.tryParse(match.group(1)!);
if (millis != null) {
print(
"the data and time is ${DateTime.fromMillisecondsSinceEpoch(millis)}");
return DateTime.fromMillisecondsSinceEpoch(millis);
}
}
return null;
}
final copy = List<LabOrderResult>.from(original);
copy.sort((a, b) {
final aDate = DateUtil.convertStringToDate(a.verifiedOnDateTime);
final bDate = DateUtil.convertStringToDate(b.verifiedOnDateTime);
if (aDate == null && bDate == null) return 0;
if (aDate == null) return 1;
if (bDate == null) return -1;
return bDate.compareTo(aDate); // descending
});
print("the copied item are $copy");
return copy.take(3).toList();
}
List<ThresholdRange> buildThresholdList(List<LabOrderResult> topResults) {
var mapOfPriority = mapFirstItemByPriority(topResults);
return mapResultToThreshold(topResults, mapOfPriority);
}
List<ThresholdRange> mapResultToThreshold(
List<LabOrderResult> results, Map<String, LabOrderResult> mapOfPriority) {
// Extract valid numeric results
List<double> actualValues = results
.map((e) => double.tryParse(e.resultValue ?? ''))
.where((v) => v != null)
.cast<double>()
.toList();
if (actualValues.isEmpty) return [];
actualValues.sort();
double min = actualValues.first;
double max = actualValues.last;
double baseRange = max - min;
// Handle single value or equal values
if (baseRange == 0) {
baseRange = max * 0.2; // ±10%
min = max - baseRange / 2;
max = max + baseRange / 2;
}
// Adjust range scale based on number of actual values
// >5 = compress thresholds; <5 = expand thresholds
int valueCount = actualValues.length;
double scalingFactor = valueCount >= 5 ? 1.0 : (5 / valueCount);
double adjustedRange = baseRange * scalingFactor;
// Recalculate thresholds based on adjusted range
// double criticalLow = min;
// double low = min + 0.25 * adjustedRange;
// double normal = min + 0.5 * adjustedRange;
// double high = min + 0.75 * adjustedRange;
// double criticalHigh = min + adjustedRange;
const priorityOrder = ['LCL', 'CL', 'L', 'N', 'H', 'CH', 'HCH'];
var mapOfValues = inferThresholds(mapOfPriority);
var item = results.first;
String? realCriticalLow =
(item.criticalLow == "0") ? null : item.criticalLow;
String? realReferenceHigh =
(item.referenceHigh == "0") ? null : item.referenceHigh;
String? realCriticalHigh =
(item.criticalHigh == "0") ? null : item.criticalHigh;
String? realReferenceLow =
(item.referenceLow == "0") ? null : item.referenceLow;
final adjustedValues = adjustValues(
criticalLow: mapOfValues['criticalLow'],
low: mapOfValues['low'],
normal: mapOfValues['normal'],
high: mapOfValues['high'],
criticalHigh: mapOfValues['criticalHigh'],
);
return [
ThresholdRange(
label: 'Critical Low',
value: adjustedValues["criticalLow"]!,
color: Color(0xffffffff),
lineColor: Color(0xFFe9a2a4),
actualValue: realCriticalLow),
ThresholdRange(
label: 'Low',
value: adjustedValues['low']!,
color: Color(0xFFf2fbf5),
lineColor: Color(0xFFefc481),
actualValue: realReferenceLow),
ThresholdRange(
label: 'Normal',
value: adjustedValues['normal']!,
color: Color(0xFFf2fbf5),
lineColor: Color(0xFF5dc36b)),
ThresholdRange(
label: 'High',
value: adjustedValues['high']!,
color: Color(0xffffffff),
lineColor: Color(0xFFefc481),
actualValue: realReferenceHigh),
ThresholdRange(
label: 'Critical High',
value: adjustedValues['criticalHigh']!,
color: Color(0xffffffff),
lineColor: Color(0xFFe9a2a4),
actualValue: realCriticalHigh),
];
}
Map<String, double> inferThresholds(
Map<String, LabOrderResult> mapOfPriority) {
double? parse(String? v) {
final parsed = double.tryParse(v ?? '');
return (parsed == null || parsed < 0) ? null : parsed;
}
// Parse inputs
double? criticalLow = parse(
mapOfPriority['LCL']?.resultValue ?? mapOfPriority['CL']?.resultValue);
double? low = parse(mapOfPriority['L']?.resultValue);
double? normal = parse(mapOfPriority['N']?.resultValue);
double? high = parse(mapOfPriority['H']?.resultValue);
double? criticalHigh = parse(
mapOfPriority['CH']?.resultValue ?? mapOfPriority['HCH']?.resultValue);
const step = 5.0;
List<double?> values = [criticalLow, low, normal, high, criticalHigh];
// Find the index of the known value (priority: central -> left -> right)
int anchorIndex = values.indexWhere((v) => v != null);
double anchorValue = values[anchorIndex] ?? 50;
// Infer all values around the anchor
List<double> inferred = List.generate(5, (i) {
double v = anchorValue + (i - anchorIndex) * step;
return v < 0 ? 0 : v;
});
var mapresult = {
'criticalLow': values[0]??-1,
'low': values[1]??-1,
'normal': values[2]??-1,
'high': values[3]??-1,
'criticalHigh': values[4]??-1,
};
print("the result is $mapresult");
return mapresult;
}
Map<String, double> adjustValues({
double? criticalLow,
double? low,
double? normal,
double? high,
double? criticalHigh,
double step = 10,
}) {
bool criticalLowHasValue = true;
bool lowHasValue = true;
bool normaHasValue = true;
bool highHasValue = true;
bool criticalHighHasValue = true;
if (criticalLow == null || criticalLow == -1) criticalLowHasValue = false;
if (low == null || low == -1) lowHasValue = false;
if (normal == null ||normal ==-1) normaHasValue = false;
if (high == null ||high ==-1) highHasValue = false;
if (criticalHigh == null ||criticalHigh ==-1) criticalHighHasValue = false;
print("the values arre $criticalLowHasValue $lowHasValue $normaHasValue $highHasValue $criticalHighHasValue");
if (!criticalLowHasValue) {
criticalLow = 0;
if (lowHasValue) {
low = low! - step;
} if (normaHasValue && (criticalLow!= 0 || criticalLow != -1)) {
low = normal! - step * 2;
} if (highHasValue && (criticalLow!= 0 || criticalLow != -1)) {
low = high! - step * 3;
} if (criticalHighHasValue && (criticalLow!= 0 || criticalLow != -1)) {
low = criticalHigh! - step * 4;
}
}
if (!lowHasValue) {
print("the low value is not set");
low = 0;
if (criticalLowHasValue && (low != 0 || low != -1)) {
low = criticalLow! + step;
} if (normaHasValue && (low != 0 || low != -1)) {
low = normal! - step;
} if (highHasValue && (low != 0 || low != -1)) {
low = high! - step * 2;
} if (criticalHighHasValue && (low != 0 || low != -1)) {
low = criticalHigh! - step * 3;
}
}
if (!normaHasValue) {
normal = 0;
if (criticalLowHasValue && (normal != 0 || normal != -1)) {
normal = criticalLow! + step * 2;
} if (lowHasValue) {
normal = low! + step;
} if (highHasValue) {
normal = high! - step;
} if (criticalHighHasValue) {
normal = criticalHigh! - step * 2;
}
}
if (!highHasValue) {
high = 0;
if (criticalLowHasValue) {
high = criticalLow! + step * 3;
} if (lowHasValue) {
high = low! + step * 2;
} if (normaHasValue) {
high = normal! + step;
} if (criticalHighHasValue) {
high = criticalHigh! - step;
}
}
if (!criticalHighHasValue) {
criticalHigh = 0;
if (criticalLowHasValue) {
criticalHigh = criticalLow! + step * 4;
} if (lowHasValue) {
criticalHigh = low! + step * 3;
} if (normaHasValue) {
criticalHigh = normal! + step * 2;
} if (highHasValue) {
criticalHigh = high! + step;
}
}
if(((criticalLow??0)<0) == true){
var mod = ((low ?? 0) + (normal??0) )/2;
criticalLow = mod;
}
if(((low??0)<0) == true){
var mod = ((criticalLow ?? 0) + (normal??0) )/2;
low = mod;
}
if(((normal??0)<0) == true){
var mod = ((low ?? 0) + (high??0) )/2;
normal = mod;
}
if(((high??0)<0) == true){
var mod = ((normal ?? 0) + (criticalHigh??0) )/2;
high = mod;
}
if(((criticalHigh??0)<0) == true){
criticalHigh = (high??0)+step;
}
Map<String, double?> values = {
'criticalLow':criticalLow ,
'low': low,
'normal': normal ,
'high': high ,
'criticalHigh': criticalHigh,
};
print("thee adjusted values `are $values");
// // Find the first known value
// int firstKnownIndex = values.values.toList().indexWhere((v) => v != null);
//
// if (firstKnownIndex == -1) {
// // No values at all, start from 0
// values = {
// 'criticalLow': 0,
// 'low': step,
// 'normal': step * 2,
// 'high': step * 3,
// 'criticalHigh': step * 4,
// };
// } else {
// // Fill backward
// for (int i = firstKnownIndex - 1; i >= 0; i--) {
// values[values.keys.elementAt(i)] =
// (values.values.elementAt(i + 1)! - step).clamp(0, double.infinity);
// }
//
// // Fill forward
// for (int i = firstKnownIndex + 1; i < values.length; i++) {
// values[values.keys.elementAt(i)] =
// values.values.elementAt(i - 1)! + step;
// }
// }
//
// // Ensure strictly increasing sequence
// double prev = -double.infinity;
// values.forEach((key, val) {
// if (val! <= prev) {
// values[key] = prev + step;
// }
// prev = values[key]!;
// });
return values.map((k, v) => MapEntry(k, v!));
}
void adjustLabOrderResults(List<LabOrderResult> results) {
const priorityOrder = ['LCL', 'CL', 'L', 'N', 'H', 'CH', 'HCH'];
// Sort results by priority order
results.sort((a, b) => priorityOrder
.indexOf(a.calculatedResultFlag ?? '')
.compareTo(priorityOrder.indexOf(b.calculatedResultFlag ?? '')));
// Extract values
List<double?> values = results.map((r) {
double? v = double.tryParse(r.resultValue ?? '');
return (v != null && v >= 0) ? v : null;
}).toList();
// Find first non-null index
int? firstIdx = values.indexWhere((v) => v != null);
if (firstIdx == -1) return; // All values missing
// Fill before first known value (decreasing with step 5, not less than 0)
for (int i = firstIdx - 1; i >= 0; i--) {
values[i] = (values[i + 1]! - 5).clamp(0, double.infinity);
}
// Fill after first known value (increasing with step 5)
for (int i = firstIdx + 1; i < values.length; i++) {
if (values[i] == null) {
values[i] = values[i - 1]! + 5;
}
}
// For gaps in the middle, interpolate
for (int i = 0; i < values.length; i++) {
if (values[i] == null) {
// find next non-null
int j = i + 1;
while (j < values.length && values[j] == null) j++;
if (j < values.length) {
double start = values[i - 1]!;
double end = values[j]!;
double step = (end - start) / (j - i + 1);
for (int k = i; k < j; k++) {
values[k] = start + step * (k - i + 1);
}
}
}
}
// Update results list directly
for (int i = 0; i < results.length; i++) {
results[i].resultValue = values[i]?.toStringAsFixed(2) ?? '0.00';
}
}
double transformValueInRange(double inputValue, String flag) {
// Define range boundaries
double rangeStart, rangeEnd;
switch (flag) {
case 'CL':
rangeStart = 0.0;
rangeEnd = 19.0;
break;
case 'L':
rangeStart = 20.0;
rangeEnd = 39.0;
break;
case 'N':
rangeStart = 40.0;
rangeEnd = 59.0;
break;
case 'H':
rangeStart = 60.0;
rangeEnd = 79.0;
break;
case 'CH':
rangeStart = 80.0;
rangeEnd = 100.0;
break;
default:
throw ArgumentError('Invalid flag: $flag');
}
// Clamp input value to 0-100 and map it to the range bounds
final clampedValue = inputValue.clamp(0.0, 100.0);
final normalizedValue = clampedValue / 100.0; // Normalize input to 0-1
// Map the normalized value to the target range bounds
final transformedValue = rangeStart + (normalizedValue * (rangeEnd - rangeStart));
return transformedValue;
}
List<ThresholdRange> getThresholdValue() {
return [
ThresholdRange(
label: 'criticalLow',
value: 0,
color: Color(0xffffffff),
lineColor: Color(0xFFe9a2a4)),
ThresholdRange(
label: 'low',
value: 20,
color: Color(0xFFf2fbf5),
lineColor: Color(0xFFeecd94)),
ThresholdRange(
label: 'normal',
value: 40,
color: Color(0xFFf2fbf5),
lineColor: Color(0xFF5dc36b)),
ThresholdRange(
label: 'high',
value: 60,
color: Color(0xffffffff),
lineColor: Color(0xFFeecd94)),
ThresholdRange(
label: 'criticalHigh',
value: 80,
color: Color(0xffffffff),
lineColor: Color(0xFFe9a2a4)),
];
}
}

@ -13,6 +13,7 @@ import 'package:diplomaticquarterapp/models/Authentication/authenticated_user.da
import 'package:diplomaticquarterapp/uitl/app_toast.dart';
import 'package:diplomaticquarterapp/uitl/date_uitl.dart';
import 'package:diplomaticquarterapp/widgets/charts/app_time_series_chart.dart';
import 'package:diplomaticquarterapp/widgets/data_display/medical/LabResult/lab_result_graph.dart';
import 'package:open_filex/open_filex.dart';
import 'package:path_provider/path_provider.dart';
@ -24,7 +25,8 @@ class LabsViewModel extends BaseViewModel {
LabsService _labsService = locator<LabsService>();
List<LabOrderResult> get labOrdersResultsList => _labsService.labOrdersResultsList;
List<TimeSeriesSales2> timeSeries = [];
List<DataPoint> timeSeries = [];
List<ThresholdRange> threshold = [];
String get labReportPDF => _labsService.labReportPDF;
@ -158,6 +160,7 @@ class LabsViewModel extends BaseViewModel {
error = _labsService.error!;
setState(ViewState.Error);
} else {
/// commented out based on PAP-304
// bool isShouldClear = false;
// if (_labsService.labOrdersResultsList.length == 1) {
@ -170,11 +173,27 @@ class LabsViewModel extends BaseViewModel {
//
// _labsService.labOrdersResultsList.clear();
_labsService.labOrdersResultsList.forEach((element) {
var sortedResponse = _labsService.sortByFlagAndValue(_labsService.labOrdersResultsList);
print("the sorted list is ${sortedResponse}");
var recentThree = _labsService.getMostRecentThree(sortedResponse);
print("the recentThree list is ${recentThree}");
var mappedToThresholdValue = _labsService.getThresholdValue();
// var mappedToThresholdValue = _labsService.buildThresholdList(recentThree);
// _labsService.adjustLabOrderResults(sortedResponse);
print("the recentThree list is ${mappedToThresholdValue}");
threshold = mappedToThresholdValue;
List months = ['Jan','Feb','Mar','April','May','Jun','July','Aug','Sep','Oct','Nov','Dec'];
double counter = 1;
recentThree.reversed.forEach((element) {
try {
timeSeries.add(new TimeSeriesSales2(DateUtil.convertStringToDate(element.verifiedOnDateTime!), double.parse(element.resultValue!)));
var dateTime = DateUtil.convertStringToDate(element.verifiedOnDateTime!);
timeSeries.add(DataPoint( labelValue: counter,value : _labsService.transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??""), label: "${months[dateTime.month]} ${dateTime.year}", date: dateTime));
counter++;
} catch (e) {}
});
timeSeries.last.label = "thisResult";
timeSeries.last.isStringResource = true;
setState(ViewState.Idle);
}
}

@ -168,7 +168,7 @@ class PayfortService extends BaseService {
/// Step 4: Processing Payment [Don't multiply with 100]
/// Amount value send always round ex. [100] not [100.00, 100.21]
FortRequest request = FortRequest(
command: FortCommand.purchase,
// command: FortCommand.purchase,
amount: orderAmount!,
customerName: customerName!,
customerEmail: customerEmail!,

@ -13,6 +13,7 @@ import 'package:diplomaticquarterapp/uitl/LocalNotification.dart';
import 'package:diplomaticquarterapp/uitl/app_shared_preferences.dart';
import 'package:diplomaticquarterapp/uitl/push-notification-handler.dart';
import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart';
import 'package:diplomaticquarterapp/widgets/data_display/medical/LabResult/lab_result_graph.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

@ -70,7 +70,7 @@ class AppToast {
child: toast,
gravity: ToastGravity.TOP,
toastDuration: Duration(seconds: timeInSeconds),
positionedToastBuilder: (context, child) {
positionedToastBuilder: (context, child,_) {
return Positioned(top: 50, left: 10, right: 10, child: child);
});
// Fluttertoast.showToast(msg: message, toastLength: toastLength, gravity: toastGravity, timeInSecForIosWeb: timeInSeconds, backgroundColor: Colors.red, textColor: textColor, fontSize: fontSize);

File diff suppressed because it is too large Load Diff

@ -7,6 +7,7 @@ import 'package:diplomaticquarterapp/uitl/utils.dart';
import 'package:diplomaticquarterapp/uitl/utils_new.dart';
import 'package:diplomaticquarterapp/widgets/Loader/gif_loader_container.dart';
import 'package:diplomaticquarterapp/widgets/charts/show_chart.dart';
import 'package:diplomaticquarterapp/widgets/data_display/medical/LabResult/lab_result_graph.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
@ -25,7 +26,10 @@ class FlowChartPage extends StatelessWidget {
projectViewModel = Provider.of(context);
return BaseView<LabsViewModel>(
onModelReady: (model) =>
model.getPatientLabOrdersResults(patientLabOrder: patientLabOrder, procedure: filterName, isVidaPlus: Utils.isVidaPlusProject(projectViewModel!, int.parse(patientLabOrder!.projectID!))),
model.getPatientLabOrdersResults(patientLabOrder: patientLabOrder,
procedure: filterName,
isVidaPlus: Utils.isVidaPlusProject(
projectViewModel!, int.parse(patientLabOrder!.projectID!))),
builder: (context, model, w) =>
SizedBox(
height: MediaQuery
@ -54,52 +58,56 @@ class FlowChartPage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.only(
top: 16.0, right: 16.0, bottom: 10),
child: GestureDetector(
onTap: () => Navigator.pop(context),
child: Align(
alignment: Alignment.centerRight,
child: Icon(
Icons.close,
color: Colors.black,
size: 28,
),
),
),
),
Container(
width: double.infinity,
padding: EdgeInsets.only(
left: 17, top: 12, right: 13, bottom: 12),
margin:
EdgeInsets.only(left: 21, right: 21, top: 21),
decoration: BoxDecoration(
shape: BoxShape.rectangle,
color: Colors.white,
borderRadius: BorderRadius.all(
Radius.circular(10.0),
),
boxShadow: [
BoxShadow(
color: Color(0xff000000).withOpacity(.05),
//spreadRadius: 5,
blurRadius: 27,
offset: Offset(0, -3),
Padding(
padding: const EdgeInsets.only(
top: 16.0, right: 16.0, bottom: 10),
child: GestureDetector(
onTap: () => Navigator.pop(context),
child: Align(
alignment: Alignment.centerRight,
child: Icon(
Icons.close,
color: Colors.black,
size: 28,
),
],
),
// child: LineChartCurved(
// title: filterName,
// labResult: model.labOrdersResultsList,
// ),
child: ShowChart(
title: filterName!,
timeSeries: model.timeSeries,
indexes: model.timeSeries.length ~/ 5.5 ?? 0,
),
),
),
Container(
width: double.infinity,
padding: EdgeInsets.only(
left: 17, top: 12, right: 13, bottom: 12),
margin:
EdgeInsets.only(left: 21, right: 21, top: 21),
decoration: BoxDecoration(
shape: BoxShape.rectangle,
color: Colors.white,
borderRadius: BorderRadius.all(
Radius.circular(10.0),
),
boxShadow: [
BoxShadow(
color: Color(0xff000000).withOpacity(.05),
//spreadRadius: 5,
blurRadius: 27,
offset: Offset(0, -3),
),
],
),
// child: LineChartCurved(
// title: filterName,
// labResult: model.labOrdersResultsList,
// ),
// child: ShowChart(
// title: filterName!,
// timeSeries: model.timeSeries,
// indexes: model.timeSeries.length ~/ 5.5 ?? 0,
// ),
child: DynamicResultChart(
dataPoints: model.timeSeries,
thresholds: model.threshold,
),
),
LabResultChartAndDetails(
name: filterName!,
labResult: model.labOrdersResultsList,
@ -119,7 +127,7 @@ class FlowChartPage extends StatelessWidget {
),
),
),
),
),
);
}
}

@ -0,0 +1,297 @@
import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart';
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:intl/intl.dart';
class DynamicResultChart extends StatelessWidget {
final List<DataPoint> dataPoints;
final List<ThresholdRange> thresholds;
const DynamicResultChart(
{super.key, required this.dataPoints, required this.thresholds});
@override
Widget build(BuildContext context) {
final minY = thresholds.first.value;
final maxY = thresholds.last.value + 1;
final spots = dataPoints.asMap().entries.map((entry) {
return FlSpot(entry.key.toDouble(), entry.value.value);
}).toList();
final widthPerPoint = 30.0; // Customize as needed
final chartWidth = (dataPoints.length + 1) * widthPerPoint;
return Material(
color: Colors.white,
child: SizedBox(
height: MediaQuery.sizeOf(context).height / 4,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SizedBox(
width: MediaQuery.sizeOf(context).width - 77,
child: Padding(
padding: const EdgeInsets.only(top: 8.0, bottom: 8),
child: LineChart(
LineChartData(
minY: minY,
maxY: maxY,
minX: dataPoints.first.labelValue - 1,
maxX: dataPoints.first.labelValue + 1,
lineTouchData:
LineTouchData(touchTooltipData: LineTouchTooltipData(
getTooltipItems: (touchedSpots) {
if (touchedSpots.isEmpty) return [];
// Only show tooltip for the first touched spot, hide others
return touchedSpots.map((spot) {
if (spot == touchedSpots.first) {
return LineTooltipItem(
'${spot.y}',
const TextStyle(color: Colors.white),
);
}
return null; // hides the rest
}).toList();
}
)),
titlesData: FlTitlesData(
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 77,
interval: .1, // Let fl_chart handle it
getTitlesWidget: (value, _) {
// print("the value is ======== ${value}");
// Compare with 2-decimal precision to avoid close duplicates
final matchingThreshold = thresholds.firstWhere(
(t) =>
t.value.toStringAsFixed(1) ==
value.toStringAsFixed(1),
orElse: () => ThresholdRange(
label: '',
value: 0,
color: Colors.transparent,
lineColor: Colors.transparent),
);
var actualValue = (matchingThreshold.actualValue !=
null)
? "${TranslationBase.of(context).getTranslation(matchingThreshold.label)} (${matchingThreshold.actualValue})"
: '${TranslationBase.of(context).getTranslation(matchingThreshold.label)}';
if (matchingThreshold.label.isNotEmpty) {
return Text(
actualValue,
style: const TextStyle(fontSize: 10),
);
}
return const SizedBox.shrink();
},
),
),
bottomTitles: AxisTitles(
axisNameSize: 60,
sideTitles: SideTitles(
showTitles: true,
reservedSize: 50,
getTitlesWidget: (value, _) {
if (value.toInt() >= 0 &&
value.toInt() < dataPoints.length) {
var label = dataPoints[value.toInt()].label;
if (dataPoints[value.toInt()].isStringResource) {
label = TranslationBase.of(context)
.getTranslation(
dataPoints[value.toInt()].label);
}
return Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
label,
style: const TextStyle(fontSize: 12),
),
);
}
return const SizedBox.shrink();
},
interval: 1, // ensures 1:1 mapping with spots
),
),
topTitles: AxisTitles(),
rightTitles: AxisTitles(),
),
borderData: FlBorderData(show: false),
lineBarsData:
_buildColoredLineSegments(dataPoints, thresholds),
gridData: FlGridData(show: false),
extraLinesData: ExtraLinesData(
horizontalLines: thresholds.map((t) {
return HorizontalLine(
y: t.value,
color: t.lineColor,
strokeWidth: 2,
// label: HorizontalLineLabel(
// show: true,
// alignment: Alignment.centerLeft,
// style: const TextStyle(fontSize: 10),
// labelResolver: (_) => '${t.label} (${t.value.toStringAsFixed(2)})',
// ),
);
}).toList(),
),
rangeAnnotations: RangeAnnotations(
horizontalRangeAnnotations: _buildRangeShades(thresholds),
),
),
),
),
),
),
));
}
List<LineChartBarData> _buildColoredLineSegments(
List<DataPoint> dataPoints, List<ThresholdRange> thresholds) {
List<LineChartBarData> segments = [];
Color getColor(double value) {
for (int i = thresholds.length - 1; i >= 0; i--) {
if (value >= thresholds[i].value) {
return thresholds[i].lineColor;
}
}
return Colors.grey;
}
for (int i = 0; i < dataPoints.length - 1; i++) {
final dp1 = dataPoints[i];
final dp2 = dataPoints[i + 1];
final spot1 = FlSpot(i.toDouble(), dp1.value);
final spot2 = FlSpot((i + 1).toDouble(), dp2.value);
final color1 = getColor(dp1.value);
final color2 = getColor(dp2.value);
segments.add(LineChartBarData(
spots: [spot1, spot2],
isCurved: false,
barWidth: 2,
gradient: LinearGradient(
colors: [color1, color2],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
),
dotData: FlDotData(show: false), // We'll handle dots separately
));
}
// Add dot markers separately so they are on top
final List<FlSpot> allSpots = dataPoints.asMap().entries.map((entry) {
return FlSpot(entry.key.toDouble(), entry.value.value);
}).toList();
print("the spots are $allSpots");
return [
...segments,
LineChartBarData(
spots: allSpots,
isCurved: false,
barWidth: 0, // invisible line
dotData: FlDotData(
show: true,
getDotPainter: (spot, _, __, index) {
final value = spot.y;
final color = getColor(value);
return FlDotCirclePainter(
radius: 4,
color: color,
strokeWidth: 1,
strokeColor: Colors.white,
);
},
),
)
];
}
List<HorizontalRangeAnnotation> _buildRangeShades(
List<ThresholdRange> thresholds) {
List<HorizontalRangeAnnotation> ranges = [];
for (int i = 0; i < thresholds.length - 1; i++) {
ranges.add(HorizontalRangeAnnotation(
y1: thresholds[i].value,
y2: thresholds[i + 1].value,
color: thresholds[i].color,
));
}
return ranges;
}
}
final List<DataPoint> sampleData = [
DataPoint(value: 1.4, label: 'Jan 2024', date: DateTime(2024, 1, 1)),
DataPoint(value: 3.6, label: 'Feb 2024', date: DateTime(2024, 2, 1)),
DataPoint(value: 1.96, label: 'This result', date: DateTime(2024, 3, 1)),
];
final List<ThresholdRange> thresholdLevels = [
ThresholdRange(
label: 'Critical Low',
value: 1.4,
color: Color(0xfff6e9e9),
lineColor: Color(0xFFe9a2a4)),
ThresholdRange(
label: 'Low',
value: 3.6,
color: Color(0xFFf2fbf5),
lineColor: Color(0xFFeecd94)),
ThresholdRange(
label: 'Normal',
value: 5.96,
color: Color(0xFFf2fbf5),
lineColor: Color(0xFF5dc36b)),
ThresholdRange(
label: 'High',
value: 7.15,
color: Color(0xfff6e9e9),
lineColor: Color(0xFFeecd94)),
ThresholdRange(
label: 'Critical High',
value: 10.15,
color: Color(0xfff6e9e9),
lineColor: Color(0xFFe9a2a4)),
];
class DataPoint {
final double value;
double labelValue;
String label; // e.g., "This Result"
final DateTime date;
bool isStringResource;
DataPoint(
{required this.value,
required this.label,
required this.date,
this.isStringResource = false,
this.labelValue = 0.0});
}
class ThresholdRange {
final String label;
final double value;
final Color color;
final Color lineColor;
final String? actualValue;
ThresholdRange(
{required this.label,
required this.value,
required this.color,
required this.lineColor,
this.actualValue});
@override
String toString() {
return 'ThresholdRange(label: $label, value: $value, color: ${color.value.toRadixString(16)}, lineColor: ${lineColor.value.toRadixString(16)})';
}
}

@ -66,7 +66,7 @@ dependencies:
shared_preferences: ^2.0.0
# flutter_flexible_toast: ^0.1.4
fluttertoast: ^8.0.8
firebase_messaging: 14.1.0
firebase_messaging: 14.7.0
firebase_analytics: ^10.0.5
# Progress bar
flutter_progress_hud: ^2.0.2

Loading…
Cancel
Save