Monthly Reports implemented

pull/143/head
haroon amjad 1 week ago
parent 255353dd21
commit ede8844f19

@ -1,3 +1,3 @@
add_<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.1905 5.77665C20.59 6.15799 20.6047 6.79098 20.2234 7.19048L9.72336 18.1905C9.53745 18.3852 9.28086 18.4968 9.01163 18.4999C8.7424 18.5031 8.48328 18.3975 8.29289 18.2071L4.79289 14.7071C4.40237 14.3166 4.40237 13.6834 4.79289 13.2929C5.18342 12.9024 5.81658 12.9024 6.20711 13.2929L8.98336 16.0692L18.7766 5.80953C19.158 5.41003 19.791 5.39531 20.1905 5.77665Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 537 B

After

Width:  |  Height:  |  Size: 533 B

@ -75,6 +75,7 @@ class CacheConst {
static const String patientOccupationList = 'patient-occupation-list';
static const String hasEnabledQuickLogin = 'has-enabled-quick-login';
static const String quickLoginEnabled = 'quick-login-enabled';
static const String isMonthlyReportEnabled = 'is-monthly-report-enabled';
static const String zoomRoomID = 'zoom-room-id';
static String isAppOpenedFromCall = "is_app_opened_from_call";

@ -30,6 +30,8 @@ import 'package:hmg_patient_app_new/features/location/location_repo.dart';
import 'package:hmg_patient_app_new/features/location/location_view_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_repo.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart';
import 'package:hmg_patient_app_new/features/monthly_report/monthly_report_repo.dart';
import 'package:hmg_patient_app_new/features/monthly_report/monthly_report_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart';
@ -141,6 +143,7 @@ class AppDependencies {
getIt.registerLazySingleton<BloodDonationRepo>(() => BloodDonationRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<WaterMonitorRepo>(() => WaterMonitorRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<MyInvoicesRepo>(() => MyInvoicesRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<MonthlyReportRepo>(() => MonthlyReportRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
// ViewModels
// Global/shared VMs LazySingleton
@ -276,5 +279,7 @@ class AppDependencies {
getIt.registerLazySingleton<WaterMonitorViewModel>(() => WaterMonitorViewModel(waterMonitorRepo: getIt()));
getIt.registerLazySingleton<MyInvoicesViewModel>(() => MyInvoicesViewModel(myInvoicesRepo: getIt(), errorHandlerService: getIt(), navServices: getIt()));
getIt.registerLazySingleton<MonthlyReportViewModel>(() => MonthlyReportViewModel(errorHandlerService: getIt(), monthlyReportRepo: getIt()));
}
}

@ -326,7 +326,7 @@ class Utils {
children: [
SizedBox(height: isSmallWidget ? 0.h : 48.h),
Lottie.asset(AppAnimations.noData,
repeat: false, reverse: false, frameRate: FrameRate(60), width: width.h, height: height.h, fit: BoxFit.fill),
repeat: false, reverse: false, frameRate: FrameRate(60), width: width.w, height: height.h, fit: BoxFit.fill),
SizedBox(height: 16.h),
(noDataText ?? LocaleKeys.noDataAvailable.tr())
.toText14(weight: FontWeight.w500, color: AppColors.greyTextColor, isCenter: true)

@ -192,7 +192,8 @@ extension EmailValidator on String {
letterSpacing: letterSpacing,
height: height,
fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal),
decoration: isUnderLine ? TextDecoration.underline : null),
decoration: isUnderLine ? TextDecoration.underline : null,
decorationColor: color ?? AppColors.blackColor),
);
Widget toText15(

@ -0,0 +1,53 @@
import 'package:dartz/dartz.dart';
import 'package:hmg_patient_app_new/core/api/api_client.dart';
import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
abstract class MonthlyReportRepo {
Future<Either<Failure, GenericApiModel<dynamic>>> updatePatientHealthSummaryReport({required bool rSummaryReport});
}
class MonthlyReportRepoImp implements MonthlyReportRepo {
final ApiClient apiClient;
final LoggerService loggerService;
MonthlyReportRepoImp({required this.loggerService, required this.apiClient});
@override
Future<Either<Failure, GenericApiModel<dynamic>>> updatePatientHealthSummaryReport({required bool rSummaryReport}) async {
Map<String, dynamic> mapDevice = {
"RSummaryReport": rSummaryReport,
};
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(
UPDATE_HEALTH_TERMS,
body: mapDevice,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: response,
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
}

@ -0,0 +1,66 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/features/monthly_report/monthly_report_repo.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
class MonthlyReportViewModel extends ChangeNotifier {
MonthlyReportRepo monthlyReportRepo;
ErrorHandlerService errorHandlerService;
bool isUpdateHealthSummaryLoading = false;
bool isHealthSummaryEnabled = false;
MonthlyReportViewModel({
required this.monthlyReportRepo,
required this.errorHandlerService,
});
setHealthSummaryEnabled(bool value) {
isHealthSummaryEnabled = value;
notifyListeners();
}
Future<void> updatePatientHealthSummaryReport({
required bool rSummaryReport,
Function(dynamic)? onSuccess,
Function(String)? onError,
}) async {
isUpdateHealthSummaryLoading = true;
notifyListeners();
final result = await monthlyReportRepo.updatePatientHealthSummaryReport(
rSummaryReport: rSummaryReport,
);
result.fold(
(failure) async {
isUpdateHealthSummaryLoading = false;
notifyListeners();
await errorHandlerService.handleError(failure: failure);
if (onError != null) {
onError(failure.toString());
}
},
(apiResponse) {
isUpdateHealthSummaryLoading = false;
if (apiResponse.messageStatus == 2) {
notifyListeners();
if (onError != null) {
onError(apiResponse.errorMessage ?? "Unknown error");
}
} else if (apiResponse.messageStatus == 1) {
// Update the local state on success
isHealthSummaryEnabled = rSummaryReport;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
}
@override
void dispose() {
super.dispose();
}
}

@ -22,6 +22,7 @@ import 'package:hmg_patient_app_new/features/lab/history/lab_history_viewmodel.d
import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart';
import 'package:hmg_patient_app_new/features/location/location_view_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart';
import 'package:hmg_patient_app_new/features/monthly_report/monthly_report_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
@ -173,6 +174,9 @@ void main() async {
),
ChangeNotifierProvider<MyInvoicesViewModel>(
create: (_) => getIt.get<MyInvoicesViewModel>(),
),
ChangeNotifierProvider<MonthlyReportViewModel>(
create: (_) => getIt.get<MonthlyReportViewModel>(),
)
], child: MyApp()),
),

@ -48,7 +48,7 @@ class SelectLivecareClinicPage extends StatelessWidget {
SizedBox(height: 40.h),
Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.immediate_service_icon, width: 58.h, height: 58.h),
Utils.buildSvgWithAssets(icon: AppAssets.immediate_service_icon, width: 58.w, height: 58.h),
SizedBox(width: 18.h),
Expanded(
child: Column(

@ -3,9 +3,11 @@ import 'dart:async';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:get_it/get_it.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/cache_consts.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/size_config.dart';
@ -23,6 +25,7 @@ import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/models/family_file_response_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/models/patient_sickleave_response_model.dart';
import 'package:hmg_patient_app_new/features/monthly_report/monthly_report_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';
import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_view_model.dart';
@ -49,12 +52,14 @@ import 'package:hmg_patient_app_new/presentation/medical_file/widgets/lab_rad_ca
import 'package:hmg_patient_app_new/presentation/medical_file/widgets/medical_file_card.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/widgets/patient_sick_leave_card.dart';
import 'package:hmg_patient_app_new/presentation/medical_report/medical_reports_page.dart';
import 'package:hmg_patient_app_new/presentation/monthly_report/monthly_report.dart';
import 'package:hmg_patient_app_new/presentation/my_family/my_family.dart';
import 'package:hmg_patient_app_new/presentation/my_invoices/my_invoices_list.dart';
import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart';
import 'package:hmg_patient_app_new/presentation/radiology/radiology_orders_page.dart';
import 'package:hmg_patient_app_new/presentation/todo_section/todo_page.dart';
import 'package:hmg_patient_app_new/presentation/vital_sign/vital_sign_page.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
@ -89,6 +94,9 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
late MyInvoicesViewModel myInvoicesViewModel;
late HmgServicesViewModel hmgServicesViewModel;
late PrescriptionsViewModel prescriptionsViewModel;
late MonthlyReportViewModel monthlyReportViewModel;
final CacheService cacheService = GetIt.instance<CacheService>();
int currentIndex = 0;
@ -140,6 +148,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
myInvoicesViewModel = Provider.of<MyInvoicesViewModel>(context, listen: false);
hmgServicesViewModel = Provider.of<HmgServicesViewModel>(context, listen: false);
prescriptionsViewModel = Provider.of<PrescriptionsViewModel>(context, listen: false);
monthlyReportViewModel = Provider.of<MonthlyReportViewModel>(context, listen: false);
NavigationService navigationService = getIt.get<NavigationService>();
return CollapsingListView(
// title: "Medical File".needTranslation,
@ -1217,7 +1226,14 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
svgIcon: AppAssets.monthly_reports_icon,
isLargeText: true,
iconSize: 36.h,
),
).onPress(() {
monthlyReportViewModel.setHealthSummaryEnabled(cacheService.getBool(key: CacheConst.isMonthlyReportEnabled) ?? false);
Navigator.of(context).push(
CustomPageRoute(
page: MonthlyReport(),
),
);
}),
MedicalFileCard(
label: "Medical Reports".needTranslation,
textColor: AppColors.blackColor,

@ -0,0 +1,189 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/cache_consts.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/monthly_report/monthly_report_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:provider/provider.dart';
class MonthlyReport extends StatelessWidget {
MonthlyReport({super.key});
late AppState appState;
final CacheService _cacheService = GetIt.instance<CacheService>();
bool isTermsAccepted = true;
@override
Widget build(BuildContext context) {
appState = getIt.get<AppState>();
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: Consumer<MonthlyReportViewModel>(builder: (context, monthlyReportVM, child) {
return Column(
children: [
Expanded(
child: CollapsingListView(
title: LocaleKeys.monthlyReports.tr(),
child: SingleChildScrollView(
child: Column(
children: [
SizedBox(height: 24.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.r,
hasShadow: false,
),
child: Row(
mainAxisSize: MainAxisSize.max,
children: [
LocaleKeys.patientHealthSummaryReport.tr(context: context).toText14(isBold: true),
const Spacer(),
Switch(
activeTrackColor: AppColors.successColor,
value: monthlyReportVM.isHealthSummaryEnabled,
onChanged: (newValue) async {
monthlyReportVM.setHealthSummaryEnabled(newValue);
},
),
],
).paddingSymmetrical(16.h, 16.h),
),
SizedBox(height: 16.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.r,
hasShadow: false,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.email_icon, width: 40.h, height: 40.h),
SizedBox(width: 8.h),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.email.tr(context: context).toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500),
"${appState.getAuthenticatedUser()!.emailAddress}".toText16(color: AppColors.textColor, weight: FontWeight.w500),
],
),
],
),
],
).paddingSymmetrical(16.h, 16.h),
),
SizedBox(height: 16.h),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Utils.buildSvgWithAssets(icon: AppAssets.prescription_remarks_icon, width: 18.w, height: 18.h),
SizedBox(width: 9.h),
Expanded(
child:
"This monthly health summary report reflects the health indicators and analysis results of the latest visits. Please note that this will be sent automatically from the system and its not considered as a official report so no medical decision should be taken based on it"
.needTranslation
.toText10(weight: FontWeight.w500, color: AppColors.greyTextColorLight),
),
],
),
],
).paddingSymmetrical(24.w, 0.h),
),
),
),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: true,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.only(top: 32.h, left: 24.w),
child: Row(
children: [
SizedBox(
height: 24.0,
width: 24.0,
child: Checkbox(
value: isTermsAccepted,
onChanged: (v) {
isTermsAccepted = v ?? true;
},
activeColor: AppColors.primaryRedColor,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
visualDensity: VisualDensity.compact,
),
),
SizedBox(width: 10.w),
"I agree to the ".toText14(isBold: true, letterSpacing: -1.0),
"terms and conditions".toText14(isBold: true, letterSpacing: -1.0, color: AppColors.primaryRedColor, isUnderLine: true).onPress(() {
Utils.openWebView(
url: 'https://hmg.com/en/Pages/Terms.aspx',
);
})
],
),
),
CustomButton(
text: LocaleKeys.save.tr(),
onPressed: () async {
LoaderBottomSheet.showLoader(loadingText: "Updating Monthly Report Status...".needTranslation);
await monthlyReportVM.updatePatientHealthSummaryReport(
rSummaryReport: monthlyReportVM.isHealthSummaryEnabled,
onSuccess: (response) async {
LoaderBottomSheet.hideLoader();
await _cacheService.saveBool(
key: CacheConst.isMonthlyReportEnabled,
value: monthlyReportVM.isHealthSummaryEnabled,
);
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getSuccessWidget(loadingText: "Monthly Report Status Updated Successfully".needTranslation),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
},
onError: (error) {
// Error is already handled by errorHandlerService in view model
},
);
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: AppColors.whiteColor,
fontSize: 16.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
height: 46.h,
iconColor: AppColors.whiteColor,
iconSize: 20.h,
).paddingSymmetrical(24.h, 24.h),
],
),
),
],
);
}),
);
}
}

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_swiper_view/flutter_swiper_view.dart';
@ -28,6 +30,7 @@ import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
class ProfileSettings extends StatefulWidget {
const ProfileSettings({super.key});
@ -195,8 +198,6 @@ class ProfileSettingsState extends State<ProfileSettings> {
title: "Application Language".needTranslation, child: AppLanguageChange(), callBackFunc: () {}, isFullScreen: false);
}, trailingLabel: Utils.appState.isArabic() ? "العربية".needTranslation : "English".needTranslation),
1.divider,
actionItem(AppAssets.accessibility, "Symptoms Checker".needTranslation, () {}),
1.divider,
actionItem(AppAssets.accessibility, "Accessibility".needTranslation, () {}),
1.divider,
actionItem(AppAssets.bell, "Notifications Settings".needTranslation, () {}),
@ -235,15 +236,35 @@ class ProfileSettingsState extends State<ProfileSettings> {
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true),
child: Column(
children: [
actionItem(AppAssets.call_fill, "Contact Us".needTranslation, () {}, trailingLabel: "9200666666"),
actionItem(AppAssets.call_fill, "Contact Us".needTranslation, () {
launchUrl(Uri.parse("tel://" + "+966 11 525 9999"));
}, trailingLabel: "011 525 9999"),
1.divider,
actionItem(AppAssets.permission, "Permissions".needTranslation, () {}, trailingLabel: "Location, Camera"),
1.divider,
actionItem(AppAssets.rate, "Rate Our App".needTranslation, () {}, isExternalLink: true),
actionItem(AppAssets.rate, "Rate Our App".needTranslation, () {
if (Platform.isAndroid) {
Utils.openWebView(
url: 'https://play.google.com/store/apps/details?id=com.ejada.hmg',
);
} else {
Utils.openWebView(
url: 'https://itunes.apple.com/app/id733503978',
);
}
}, isExternalLink: true),
1.divider,
actionItem(AppAssets.privacy_terms, "Privacy Policy".needTranslation, () {}, isExternalLink: true),
actionItem(AppAssets.privacy_terms, "Privacy Policy".needTranslation, () {
Utils.openWebView(
url: 'https://hmg.com/en/Pages/Privacy.aspx',
);
}, isExternalLink: true),
1.divider,
actionItem(AppAssets.privacy_terms, "Terms & Conditions".needTranslation, () {}, isExternalLink: true),
actionItem(AppAssets.privacy_terms, "Terms & Conditions".needTranslation, () {
Utils.openWebView(
url: 'https://hmg.com/en/Pages/Terms.aspx',
);
}, isExternalLink: true),
],
),
),

@ -89,7 +89,7 @@ class CollapsingListView extends StatelessWidget {
? Transform.flip(
flipX: appState.isArabic(),
child: IconButton(
icon: Utils.buildSvgWithAssets(icon: isClose ? AppAssets.closeBottomNav : AppAssets.arrow_back, width: 32.h, height: 32.h),
icon: Utils.buildSvgWithAssets(icon: isClose ? AppAssets.closeBottomNav : AppAssets.arrow_back, width: 24.h, height: 24.h),
padding: EdgeInsets.only(left: 12),
onPressed: () {
if (leadingCallback != null) {
@ -265,6 +265,7 @@ class _ScrollAnimatedTitleState extends State<ScrollAnimatedTitle> {
style: TextStyle(
fontSize: _fontSize,
fontWeight: FontWeight.bold,
letterSpacing: -1.0,
),
).expanded,
...[

Loading…
Cancel
Save