diff --git a/lib/repository/mitra_repository.dart b/lib/repository/mitra_repository.dart index 04d30c7..44fc2f9 100644 --- a/lib/repository/mitra_repository.dart +++ b/lib/repository/mitra_repository.dart @@ -9,6 +9,30 @@ import 'base_repository.dart'; class MitraRepository extends BaseRepository { MitraRepository({required super.dio}); + + Future add({ + required String token, + required String companyID, + required List mouID, + required String doctorID, + required String doctorAddressID, + required String login, + CancelToken? cancelToken, + }) async { + final param = { + "token": token, + "companyID": companyID, + "mouID": mouID, + "doctorID": doctorID, + "doctorAddressID": doctorAddressID, + "login": login + }; + + final service = "${Constants.baseUrl}md/add"; + await post(service: service, jsonParam: param, cancelToken: cancelToken); + return true; + } + Future> search({ required String query, CancelToken? cancelToken, diff --git a/lib/widget/fx_company_lookup.dart b/lib/widget/fx_company_lookup.dart index a6f0368..28d852e 100644 --- a/lib/widget/fx_company_lookup.dart +++ b/lib/widget/fx_company_lookup.dart @@ -13,7 +13,11 @@ import 'provider/selectedCompanyProvider.dart'; // ignore: must_be_immutable class FxAcCompany extends HookConsumerWidget { - FxAcCompany({Key? key}) : super(key: key); + final String? errorValidation; + FxAcCompany({ + Key? key, + this.errorValidation, + }) : super(key: key); CancelToken? cancelToken; @override @@ -23,11 +27,11 @@ class FxAcCompany extends HookConsumerWidget { cancelToken = CancelToken(); final fc = FocusNode(); - final selectedCompany = ref.read(selectdAcCompanyProvider); + final selectedCompany = ref.read(selectedAcCompanyProvider); if (selectedCompany != null) { ctrl.text = selectedCompany.mCompanyName; } - ref.listen(selectdAcCompanyProvider, ((prev, next) { + ref.listen(selectedAcCompanyProvider, ((prev, next) { if (next != null) { ref .read(mitraLookupMouProvider.notifier) @@ -45,6 +49,7 @@ class FxAcCompany extends HookConsumerWidget { fc: fc, hint: "Company", label: "Company", + errorMessage: errorValidation, ); }, optionsBuilder: (tv) async { @@ -71,7 +76,7 @@ class FxAcCompany extends HookConsumerWidget { final model = listModel.elementAt(idx); return InkWell( onTap: () { - ref.read(selectdAcCompanyProvider.notifier).state = + ref.read(selectedAcCompanyProvider.notifier).state = model; onSelect(model); }, diff --git a/lib/widget/fx_data_mitra.dart b/lib/widget/fx_data_mitra.dart index 948c4fa..de4b2ef 100644 --- a/lib/widget/fx_data_mitra.dart +++ b/lib/widget/fx_data_mitra.dart @@ -63,7 +63,7 @@ class FxDataMitra extends HookConsumerWidget { await showDialog( context: context, builder: (context) { - return const FxMitraAddDialog(); + return FxMitraAddDialog(); }); }), ), diff --git a/lib/widget/fx_doctor_address.dart b/lib/widget/fx_doctor_address.dart index 76169bb..37c2b4f 100644 --- a/lib/widget/fx_doctor_address.dart +++ b/lib/widget/fx_doctor_address.dart @@ -4,15 +4,18 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:onemd/model/ac_doctor_model.dart'; import 'fx_data_mitra.dart'; +import 'fx_error_text.dart'; import 'provider/doctor_address_lookup_provider.dart'; -import 'provider/doctor_lookup_provider.dart'; import 'provider/selectedDoctorProvider.dart'; class FxDoctorAddress extends HookConsumerWidget { final double? width; + final String? errorValidation; + const FxDoctorAddress({ Key? key, this.width, + this.errorValidation, }) : super(key: key); @override @@ -35,7 +38,11 @@ class FxDoctorAddress extends HookConsumerWidget { width: double.infinity, decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), - border: Border.all(color: Colors.blue.shade700), + border: Border.all( + color: errorValidation == null + ? Colors.blue.shade700 + : Colors.red.shade700, + ), color: Colors.blue.shade100.withOpacity(0.3), ), child: ConstrainedBox( @@ -46,10 +53,13 @@ class FxDoctorAddress extends HookConsumerWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - FxNormalBlueText( - title: "Alamat dari $doctorName", - isBold: true, - ), + errorValidation == null + ? FxNormalBlueText( + title: "Address of $doctorName", + isBold: true, + ) + : FxErrorText( + title: "Address of $doctorName *) $errorValidation"), const SizedBox(height: 10), if (listAddress.value.isNotEmpty) ConstrainedBox( @@ -61,21 +71,25 @@ class FxDoctorAddress extends HookConsumerWidget { itemBuilder: (context, idx) { final model = listAddress.value[idx]; return Row( + crossAxisAlignment: CrossAxisAlignment.center, children: [ - Checkbox( - value: model.isCheck, + Radio( + groupValue: + ref.watch(selectedAcDoctorAddressProvider), + value: model, onChanged: ((val) { - final List list = - List.empty(growable: true); - list.addAll(listAddress.value); - list[idx].isCheck = val ?? false; - listAddress.value = list; + ref + .read(selectedAcDoctorAddressProvider + .notifier) + .state = val; }), ), const SizedBox(width: 10), Expanded( child: FxNormalBlueText( - title: model.mDoctorAddressDescription), + title: model.mDoctorAddressDescription + .replaceAll("\n", ""), + ), ), ], ); diff --git a/lib/widget/fx_doctor_lookup.dart b/lib/widget/fx_doctor_lookup.dart index c4bd9d8..4b3f729 100644 --- a/lib/widget/fx_doctor_lookup.dart +++ b/lib/widget/fx_doctor_lookup.dart @@ -13,7 +13,8 @@ import 'provider/doctor_address_lookup_provider.dart'; // ignore: must_be_immutable class FxAcDoctor extends HookConsumerWidget { - FxAcDoctor({Key? key}) : super(key: key); + final String? errorValidation; + FxAcDoctor({Key? key, this.errorValidation}) : super(key: key); CancelToken? cancelToken; @override @@ -29,7 +30,6 @@ class FxAcDoctor extends HookConsumerWidget { } ref.listen(selectedAcDoctorProvider, ((prev, next) { if (next != null) { - print("Calling for " + next.fullName); ref .read(doctorAddressLookupProvider.notifier) .lookup(doctorID: next.mDoctorID); @@ -46,6 +46,7 @@ class FxAcDoctor extends HookConsumerWidget { hint: "Doctor", fc: fc, ctrl: ctrl, + errorMessage: errorValidation, ); }, optionsBuilder: (tv) async { diff --git a/lib/widget/fx_mitra_add_dialog.dart b/lib/widget/fx_mitra_add_dialog.dart index d2a7f5b..2f77708 100644 --- a/lib/widget/fx_mitra_add_dialog.dart +++ b/lib/widget/fx_mitra_add_dialog.dart @@ -1,4 +1,7 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:onemd/widget/fx_doctor_address.dart'; @@ -6,15 +9,61 @@ import 'fx_company_lookup.dart'; import 'fx_doctor_lookup.dart'; import 'fx_mitra_mou.dart'; import 'fx_text_field.dart'; +import 'provider/mitra_add_provider.dart'; +import 'provider/selectedCompanyProvider.dart'; +import 'provider/selectedDoctorProvider.dart'; class FxMitraAddDialog extends HookConsumerWidget { const FxMitraAddDialog({Key? key}) : super(key: key); @override Widget build(BuildContext context, WidgetRef ref) { - final ctrlLogin = useTextEditingController(text: ""); final fcLogin = FocusNode(); + final company = ref.watch(selectedAcCompanyProvider); + final doctor = ref.watch(selectedAcDoctorProvider); + final doctorAddress = ref.watch(selectedAcDoctorAddressProvider); + final mou = ref.watch(selectedMouProvider); + + final errorCompany = useState(null); + final errorDoctor = useState(null); + final errorDoctorAddress = useState(null); + final errorMou = useState(null); + final errorLogin = useState(null); + final ctrlLogin = useTextEditingController(text: ""); + + bool Function() validationError; + validationError = () { + bool haveError = false; + if (company == null) { + errorCompany.value = "Company is mandatory"; + haveError = true; + } + if (mou.isEmpty) { + errorMou.value = "Mou is mandatory"; + haveError = true; + } + if (doctor == null) { + errorDoctor.value = "Doctor is mandatory"; + haveError = true; + } + if (doctorAddress == null) { + errorDoctorAddress.value = "Doctor Address is mandatory"; + haveError = true; + } + if (ctrlLogin.text == "") { + errorLogin.value = "Login is mandatory"; + haveError = true; + } + Timer(const Duration(seconds: 3), () { + errorCompany.value = null; + errorMou.value = null; + errorDoctor.value = null; + errorDoctorAddress.value = null; + errorLogin.value = null; + }); + return haveError; + }; return Dialog( shape: RoundedRectangleBorder( side: const BorderSide(), @@ -29,28 +78,75 @@ class FxMitraAddDialog extends HookConsumerWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - FxAcCompany(), + FxAcCompany( + errorValidation: errorCompany.value, + ), const SizedBox(height: 10), - const FxMitraMou(), + FxMitraMou( + errorValidation: errorMou.value, + ), const SizedBox(height: 10), - FxAcDoctor(), + FxAcDoctor( + errorValidation: errorDoctor.value, + ), const SizedBox(height: 10), - FxDoctorAddress(), + FxDoctorAddress( + errorValidation: errorDoctorAddress.value, + ), const SizedBox(height: 10), FxTextField( ctrl: ctrlLogin, fc: fcLogin, hint: "Login", label: "Login", + errorMessage: errorLogin.value, ), const SizedBox(height: 10), - FxTextField( + const FxTextField( hint: "ID", label: "ID", isReadOnly: true, isEnabled: false, suffixText: "Auto Generated"), const SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.max, + children: [ + ElevatedButton( + style: ButtonStyle( + backgroundColor: MaterialStateColor.resolveWith( + (st) => Colors.green, + ), + ), + onPressed: () { + if (!validationError()) { + ref.read(mitraAddProvider.notifier).add( + companyID: company!.mCompanyID, + mouID: mou.map((e) => e.mMouID).toList(), + doctorID: doctor!.mDoctorID, + doctorAddressID: + doctorAddress!.mDoctorAddressID, + login: ctrlLogin.text, + ); + } + }, + child: const Text("Save"), + ), + const SizedBox(width: 20), + ElevatedButton( + style: ButtonStyle( + backgroundColor: MaterialStateColor.resolveWith( + (st) => Colors.red, + ), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text("Cancel"), + ), + ], + ), ], ), ), @@ -58,5 +154,3 @@ class FxMitraAddDialog extends HookConsumerWidget { ); } } - -useTextEditingController({required String text}) {} diff --git a/lib/widget/fx_mitra_mou.dart b/lib/widget/fx_mitra_mou.dart index 41779dc..1e4b678 100644 --- a/lib/widget/fx_mitra_mou.dart +++ b/lib/widget/fx_mitra_mou.dart @@ -5,20 +5,23 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../model/ac_mou_response_model.dart'; import '../screen/md_lab_mitra/mitra_lookup_mou_provider.dart'; import 'fx_data_mitra.dart'; +import 'fx_error_text.dart'; import 'provider/selectedCompanyProvider.dart'; class FxMitraMou extends HookConsumerWidget { final double? width; + final String? errorValidation; const FxMitraMou({ Key? key, this.width, + this.errorValidation, }) : super(key: key); @override Widget build(BuildContext context, WidgetRef ref) { final listMou = useState>(List.empty()); - final companyModel = ref.watch(selectdAcCompanyProvider); + final companyModel = ref.watch(selectedAcCompanyProvider); String companyName = companyModel?.mCompanyName ?? ""; ref.listen(mitraLookupMouProvider, (prev, next) { if (next is MitraLookupMouStateDone) { @@ -28,12 +31,15 @@ class FxMitraMou extends HookConsumerWidget { listMou.value = next.list; } }); - return Container( width: double.infinity, decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), - border: Border.all(color: Colors.blue.shade700), + border: Border.all( + color: errorValidation == null + ? Colors.blue.shade700 + : Colors.red.shade700, + ), color: Colors.blue.shade100.withOpacity(0.3), ), child: ConstrainedBox( @@ -44,10 +50,13 @@ class FxMitraMou extends HookConsumerWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - FxNormalBlueText( - title: "Agreement $companyName", - isBold: true, - ), + errorValidation == null + ? FxNormalBlueText( + title: "Agreement $companyName", + isBold: true, + ) + : FxErrorText( + title: "Agreement $companyName *) $errorValidation"), const SizedBox(height: 10), if (listMou.value.isNotEmpty) ConstrainedBox( @@ -68,6 +77,8 @@ class FxMitraMou extends HookConsumerWidget { list.addAll(listMou.value); list[idx].isCheck = val ?? false; listMou.value = list; + ref.read(selectedMouProvider.notifier).state = + list.where((el) => el.isCheck).toList(); }), ), const SizedBox(width: 10), diff --git a/lib/widget/provider/mitra_add_provider.dart b/lib/widget/provider/mitra_add_provider.dart new file mode 100644 index 0000000..4938506 --- /dev/null +++ b/lib/widget/provider/mitra_add_provider.dart @@ -0,0 +1,79 @@ +import 'package:dio/dio.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../provider/dio_provider.dart'; +import '../../provider/local_auth_provider.dart'; +import '../../repository/base_repository.dart'; +import '../../repository/mitra_repository.dart'; + +final mitraAddProvider = StateNotifierProvider( + (ref) => MitraAddNotifier(ref: ref), +); + +class MitraAddNotifier extends StateNotifier { + final Ref ref; + CancelToken? cancelToken; + MitraAddNotifier({ + required this.ref, + }) : super(MitraAddStateInit()); + + void reset() { + state = MitraAddStateInit(); + } + + void add({ + required String companyID, + required List mouID, + required String doctorID, + required String doctorAddressID, + required String login, + }) async { + try { + state = MitraAddStateLoading(); + final dio = ref.read(dioProvider); + final localAuth = ref.read(localAuthProvider); + if (localAuth?.token == null) { + throw BaseRepositoryException(message: "Invalid Token"); + } + await MitraRepository(dio: dio).add( + token: localAuth!.token!, + mouID: mouID, + companyID: companyID, + doctorID: doctorID, + doctorAddressID: doctorAddressID, + login: login, + ); + state = MitraAddStateDone(); + } catch (e) { + if (e is BaseRepositoryException) { + state = MitraAddStateError(message: e.message); + } else { + state = MitraAddStateError(message: "Unknown Error "); + } + } + } +} + +abstract class MitraAddState extends Equatable { + final DateTime date; + MitraAddState() : date = DateTime.now(); + @override + List get props => throw [date]; +} + +class MitraAddStateInit extends MitraAddState {} + +class MitraAddStateLoading extends MitraAddState {} + +class MitraAddStateError extends MitraAddState { + final String message; + + MitraAddStateError({ + required this.message, + }); +} + +class MitraAddStateDone extends MitraAddState { + MitraAddStateDone(); +} diff --git a/lib/widget/provider/selectedCompanyProvider.dart b/lib/widget/provider/selectedCompanyProvider.dart index 81b90a2..f080c32 100644 --- a/lib/widget/provider/selectedCompanyProvider.dart +++ b/lib/widget/provider/selectedCompanyProvider.dart @@ -1,5 +1,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../model/ac_company_response_model.dart'; +import '../../model/ac_mou_response_model.dart'; -final selectdAcCompanyProvider = StateProvider((ref) => null); +final selectedAcCompanyProvider = StateProvider((ref) => null); +final selectedMouProvider = + StateProvider>((ref) => List.empty()); diff --git a/lib/widget/provider/selectedDoctorProvider.dart b/lib/widget/provider/selectedDoctorProvider.dart index 5c1cfe1..7b09e29 100644 --- a/lib/widget/provider/selectedDoctorProvider.dart +++ b/lib/widget/provider/selectedDoctorProvider.dart @@ -3,3 +3,6 @@ import 'package:onemd/model/ac_doctor_model.dart'; final selectedAcDoctorProvider = StateProvider((ref) => null); + +final selectedAcDoctorAddressProvider = + StateProvider((ref) => null); diff --git a/php-api/mitra/Md.php b/php-api/mitra/Md.php index 0caf7ff..578fcae 100644 --- a/php-api/mitra/Md.php +++ b/php-api/mitra/Md.php @@ -15,6 +15,7 @@ class Md extends MY_Controller function add() { $param = $this->sys_input; + $sql = "insert into "; print_r($param); } @@ -150,6 +151,8 @@ create table mitra( MitraM_CompanyID int, MitraIsActive varchar(1) default 'Y', MitraCommitment text, + MitraM_DoctorID int, + MitraM_DoctorAddressID int, MitraCreated datetime default current_timestamp(), MitraLastUpdated datetime default current_timestamp() on update current_timestamp(), MitraM_UserID int,