Merge branch 'dev_v3.13.6_CR_7211_Lab_report_changes' into merge-update-with-lab-changes

# Conflicts:
#	lib/config/localized_values.dart
#	lib/core/viewModels/medical/labs_view_model.dart
#	lib/uitl/translations_delegate_base.dart
#	lib/widgets/data_display/medical/LabResult/FlowChartPage.dart
merge-update-with-lab-changes
haroon amjad 4 months ago
commit 7b6395cc49

@ -2371,4 +2371,6 @@ const Map localizedValues = {
"thisResult" : {"en": "This Result", "ar": "هذه النتيجة"},
"seeAllGraphValues" : {"en": "See all values", "ar": "شاهد جميع القيم"},
};

@ -323,8 +323,9 @@ class LabsService extends BaseService {
if (bDate == null) return -1;
return bDate.compareTo(aDate); // descending
});
print("the copied item are $copy");
print("the copied item are $copy");
labOrdersResultsList = copy;
return copy.take(3).toList();
}

@ -1,6 +1,7 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui';
import 'package:hmg_patient_app/core/enum/filter_type.dart';
import 'package:hmg_patient_app/core/enum/viewstate.dart';
@ -25,8 +26,11 @@ class LabsViewModel extends BaseViewModel {
LabsService _labsService = locator<LabsService>();
List<LabOrderResult> get labOrdersResultsList => _labsService.labOrdersResultsList;
List<DataPoint> timeSeries = [];
List<DataPoint> threePointGraphValue = [];
List<DataPoint> completeeGraphValues = [];
List<ThresholdRange> threshold = [];
List months = ['Jan','Feb','Mar','April','May','Jun','July','Aug','Sep','Oct','Nov','Dec'];
String get labReportPDF => _labsService.labReportPDF;
@ -188,16 +192,46 @@ class LabsViewModel extends BaseViewModel {
recentThree.reversed.forEach((element) {
try {
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));
threePointGraphValue.add(DataPoint( labelValue: counter,value : _labsService.transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??""), label: "${months[dateTime.month-1]} ${dateTime.year}", date: dateTime));
counter++;
} catch (e) {}
});
timeSeries.last.label = "thisResult";
timeSeries.last.isStringResource = true;
threePointGraphValue.last.label = "thisResult";
threePointGraphValue.last.isStringResource = true;
setState(ViewState.Idle);
}
}
/// This function is used to create a full graph data points for the lab results.
/// @param onComplete: A callback function that is called when the data points are created.
createFullGraphDatPoints(
VoidCallback onComplete
){
completeeGraphValues.clear();
setState(ViewState.Busy);
double counter = 1;
threshold = _labsService.getThresholdValue();
_labsService.labOrdersResultsList.reversed.forEach((element) {
try {
print("the mapping is being done");
var dateTime = DateUtil.convertStringToDate(element.verifiedOnDateTime!);
completeeGraphValues.add(DataPoint( labelValue: counter,value : _labsService.transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??""), label: "${months[dateTime.month-1]} ${dateTime.year}", date: dateTime));
} catch (e) {
print("the mapping is having exception $e");
}
++counter;
});
print("the label values are $counter");
print("the completeeGraphValues length are ${completeeGraphValues.length}");
print("the completeeGraphValues last value of label ias ${completeeGraphValues.last.labelValue}");
completeeGraphValues.last.label = "thisResult";
completeeGraphValues.last.isStringResource = true;
setState(ViewState.Idle);
onComplete();
}
sendLabReportEmail({PatientLabOrders? patientLabOrder, String? mes, AuthenticatedUser? userObj, required bool isVidaPlus, bool isDownload = false}) async {
setState(ViewState.Busy);

@ -3526,6 +3526,7 @@ class TranslationBase {
String get criticalLow => localizedValues["criticalLow"][locale.languageCode];
String get high => localizedValues["high"][locale.languageCode];
String get thisResult => localizedValues["thisResult"][locale.languageCode];
String get seeAllGraphValues => localizedValues["seeAllGraphValues"][locale.languageCode];
String get normal => localizedValues["normal"][locale.languageCode];
String get low => localizedValues["low"][locale.languageCode];

@ -29,109 +29,126 @@ class FlowChartPage extends StatelessWidget {
Widget build(BuildContext context) {
projectViewModel = Provider.of(context);
return BaseView<LabsViewModel>(
onModelReady: (model) =>
model.getPatientLabOrdersResults(patientLabOrder: patientLabOrder,
procedure: filterName,
isVidaPlus: Utils.isVidaPlusProject(
projectViewModel!, int.parse(patientLabOrder!.projectID!))),
builder: (context, model, w) =>
SizedBox(
height: MediaQuery
.sizeOf(context)
.height * .75,
child: (model.state == ViewState.Busy)
? Material(
color: Colors.grey.withOpacity(0.6),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20))),
child: Center(
child: Container(
child: GifLoaderContainer(),
margin: EdgeInsets.only(
bottom: MediaQuery
.of(context)
.size
.height * 0.09)),
),
)
: SingleChildScrollView(
child: model.labOrdersResultsList.isNotEmpty
? Column(
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),
onModelReady: (model) => model.getPatientLabOrdersResults(
patientLabOrder: patientLabOrder,
procedure: filterName,
isVidaPlus: Utils.isVidaPlusProject(
projectViewModel!, int.parse(patientLabOrder!.projectID!))),
builder: (context, model, w) => SizedBox(
height: MediaQuery.sizeOf(context).height * .75,
child: (model.state == ViewState.Busy)
? Material(
color: Colors.grey.withOpacity(0.6),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20))),
child: Center(
child: Container(
child: GifLoaderContainer(),
margin: EdgeInsets.only(
bottom: MediaQuery.of(context).size.height * 0.09)),
),
)
: SingleChildScrollView(
child: model.labOrdersResultsList.isNotEmpty
? Column(
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),
),
],
),
// child: LineChartCurved(
// title: filterName,
// labResult: model.labOrdersResultsList,
// ),
// child: ShowChart(
// title: filterName!,
// timeSeries: model.timeSeries,
// indexes: model.timeSeries.length ~/ 5.5 ?? 0,
// ),
child: Column(
children: [
SizedBox(
height: MediaQuery.sizeOf(context).height / 4,
child: DynamicResultChart(
dataPoints: model.threePointGraphValue,
thresholds: model.threshold,
width: MediaQuery.sizeOf(context).width - 77,
scrollDirection: Axis.horizontal,
height: MediaQuery.sizeOf(context).height / 4,
),
),
InkWell(
onTap: () {
model.createFullGraphDatPoints(() {
Navigator.push(context,
MaterialPageRoute(builder: (_) {
return FullScreenGraph(
completeeGraphValues: model.completeeGraphValues,
threshold: model.threshold,
);
}));
});
},
child: Text(TranslationBase.of(context)
.seeAllGraphValues),
)
],
),
),
LabResultChartAndDetails(
name: filterName!,
labResult: model.labOrdersResultsList,
),
],
)
: Center(
child: Container(
padding: EdgeInsets.only(
top: MediaQuery.of(context).size.height * 0.32),
child: Center(
child: getNoDataWidget(context),
),
),
),
// 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,
),
],
)
: Center(
child: Container(
padding: EdgeInsets.only(
top: MediaQuery
.of(context)
.size
.height * 0.32),
child: Center(
child: getNoDataWidget(context),
),
),
),
),
),
),
);
}
}

@ -0,0 +1,59 @@
import 'package:diplomaticquarterapp/core/viewModels/medical/labs_view_model.dart';
import 'package:diplomaticquarterapp/pages/base/base_view.dart';
import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart';
import 'package:diplomaticquarterapp/widgets/data_display/medical/LabResult/lab_result_graph.dart';
import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart';
import 'package:flutter/material.dart';
class FullScreenGraph extends StatelessWidget {
final List<DataPoint> completeeGraphValues ;
final List<ThresholdRange> threshold ;
const FullScreenGraph({super.key, required this.completeeGraphValues, required this.threshold});
@override
Widget build(BuildContext context) {
return AppScaffold(
isShowAppBar: true,
appBarTitle: TranslationBase.of(context).labResult,
showNewAppBar: true,
showNewAppBarTitle: true,
backgroundColor: Color(0xffF8F8F8),
body: RotatedBox(
quarterTurns: 1,
child: SizedBox(
// width: MediaQuery.sizeOf(context).height,
height: MediaQuery.sizeOf(context).width,
child: Material(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 18),
child: DynamicResultChart(
dataPoints: completeeGraphValues,
thresholds: threshold,
// width:((completeeGraphValues.length <=2)? MediaQuery.sizeOf(context).height : (MediaQuery.sizeOf(context).height* (
// completeeGraphValues.length <= 3
// ? 1
// : (completeeGraphValues.length/3))
// ) )-77,
width: MediaQuery.sizeOf(context).height-100,
maxX: completeeGraphValues.length.toDouble(),
scrollDirection: Axis.horizontal,
height: MediaQuery.sizeOf(context).width,
isFullScreenGraph: true,
),
),
),
),
),
);
}
// double getMax(List<DataPoint> dataPoints) {
// double max = double.negativeInfinity;
// for (var point in dataPoints) {
// if (point.value > max) {
// max = point.y;
// }
// }
// return max;
// }
}

@ -6,14 +6,26 @@ import 'package:intl/intl.dart';
class DynamicResultChart extends StatelessWidget {
final List<DataPoint> dataPoints;
final List<ThresholdRange> thresholds;
final double? width;
final double height;
final double? maxX;
final Axis scrollDirection;
final bool isFullScreenGraph;
const DynamicResultChart(
{super.key, required this.dataPoints, required this.thresholds});
{super.key, required this.dataPoints, required this.thresholds,required this.width,required this.scrollDirection, required this.height, this.maxX, this.isFullScreenGraph = false});
@override
Widget build(BuildContext context) {
final minY = thresholds.first.value;
final maxY = thresholds.last.value + 1;
var minY = 0.0;
var maxY = 0.0;
if(thresholds.isNotEmpty) {
print("the thresholds are $thresholds");
minY = thresholds.first.value;
maxY = thresholds.last.value + 1;
}else{
return SizedBox.shrink();
}
final spots = dataPoints.asMap().entries.map((entry) {
return FlSpot(entry.key.toDouble(), entry.value.value);
@ -24,130 +36,127 @@ class DynamicResultChart extends StatelessWidget {
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 [];
width: width,
height: height,
child: Padding(
padding: const EdgeInsets.only(top: 8.0, bottom: 8),
child: LineChart(
LineChartData(
minY: minY,
maxY: maxY,
minX: dataPoints.first.labelValue-1,
maxX: maxX,
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}");
// Only show tooltip for the first touched spot, hide others
return touchedSpots.map((spot) {
if (spot == touchedSpots.first) {
final dataPoint = dataPoints[spot.x.toInt()];
return LineTooltipItem(
'${dataPoint.label} ${spot.y.toStringAsFixed(2)}',
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),
);
// 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);
}
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: !isFullScreenGraph,
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
return Padding(
padding: EdgeInsetsDirectional.only(top: 8.0,),
child: Text(
label,
style: const TextStyle(fontSize: 12),
),
),
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),
),
),
);
}
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(
@ -186,12 +195,13 @@ class DynamicResultChart extends StatelessWidget {
}
// 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 [
var data = [
...segments,
LineChartBarData(
spots: allSpots,
@ -212,6 +222,10 @@ class DynamicResultChart extends StatelessWidget {
),
)
];
var max = LineChartHelper.calculateMaxAxisValues(data).maxX;
print("the maxX is the -------> $max");
return data;
}
List<HorizontalRangeAnnotation> _buildRangeShades(
@ -226,6 +240,8 @@ class DynamicResultChart extends StatelessWidget {
}
return ranges;
}
}
final List<DataPoint> sampleData = [

Loading…
Cancel
Save