diff --git a/lib/model/mitra_response_model.dart b/lib/model/mitra_response_model.dart index 9256495..77b1449 100644 --- a/lib/model/mitra_response_model.dart +++ b/lib/model/mitra_response_model.dart @@ -12,9 +12,13 @@ class MitraResponseModel { late String mitraLastUpdated; late String mitraMCompanyID; late String mitraMUserID; + late String mitraMDoctorID; + late String mitraMDoctorAddressID; late String mitraPassword; late String mitraUsername; late String aggrement; + late String mDoctorName; + late List aggrementID; MitraResponseModel({ required this.mCompanyAddress, @@ -33,6 +37,10 @@ class MitraResponseModel { required this.mitraPassword, required this.mitraUsername, required this.aggrement, + this.mitraMDoctorID = "0", + this.mitraMDoctorAddressID = "0", + this.mDoctorName = "", + this.aggrementID = const [], }); MitraResponseModel.fromJson(Map json) { @@ -43,6 +51,8 @@ class MitraResponseModel { mitraHoldDate = json['MitraHoldDate']; mitraHoldMUserID = json['MitraHoldM_UserID']?.toString() ?? ""; mitraID = json['MitraID'].toString(); + mitraMDoctorID = json['MitraM_DoctorID'].toString(); + mitraMDoctorAddressID = json['MitraM_DoctorAddressID'].toString(); mitraIDNo = json['MitraIDNo']; mitraIsActive = json['MitraIsActive']; mitraIsHold = json['MitraIsHold']; @@ -55,22 +65,22 @@ class MitraResponseModel { } Map toJson() { - final Map data = new Map(); - data['M_CompanyAddress'] = this.mCompanyAddress; - data['M_CompanyName'] = this.mCompanyName; - data['MitraCommitment'] = this.mitraCommitment; - data['MitraCreated'] = this.mitraCreated; - data['MitraHoldDate'] = this.mitraHoldDate; - data['MitraHoldM_UserID'] = this.mitraHoldMUserID; - data['MitraID'] = this.mitraID; - data['MitraIDNo'] = this.mitraIDNo; - data['MitraIsActive'] = this.mitraIsActive; - data['MitraIsHold'] = this.mitraIsHold; - data['MitraLastUpdated'] = this.mitraLastUpdated; - data['MitraM_CompanyID'] = this.mitraMCompanyID; - data['MitraM_UserID'] = this.mitraMUserID; - data['MitraPassword'] = this.mitraPassword; - data['MitraUsername'] = this.mitraUsername; + final Map data = {}; + data['M_CompanyAddress'] = mCompanyAddress; + data['M_CompanyName'] = mCompanyName; + data['MitraCommitment'] = mitraCommitment; + data['MitraCreated'] = mitraCreated; + data['MitraHoldDate'] = mitraHoldDate; + data['MitraHoldM_UserID'] = mitraHoldMUserID; + data['MitraID'] = mitraID; + data['MitraIDNo'] = mitraIDNo; + data['MitraIsActive'] = mitraIsActive; + data['MitraIsHold'] = mitraIsHold; + data['MitraLastUpdated'] = mitraLastUpdated; + data['MitraM_CompanyID'] = mitraMCompanyID; + data['MitraM_UserID'] = mitraMUserID; + data['MitraPassword'] = mitraPassword; + data['MitraUsername'] = mitraUsername; return data; } } diff --git a/lib/repository/mitra_repository.dart b/lib/repository/mitra_repository.dart index b734004..8c1c583 100644 --- a/lib/repository/mitra_repository.dart +++ b/lib/repository/mitra_repository.dart @@ -35,6 +35,33 @@ class MitraRepository extends BaseRepository { return true; } + Future edit({ + required String mitraID, + required String token, + required String companyID, + required List mouID, + required String doctorID, + required String doctorAddressID, + required String login, + required String password, + CancelToken? cancelToken, + }) async { + final param = { + "token": token, + "mitraID": mitraID, + "companyID": companyID, + "mouID": mouID, + "doctorID": doctorID, + "doctorAddressID": doctorAddressID, + "login": login, + "password": password + }; + + final service = "${Constants.baseUrl}md/edit"; + await post(service: service, jsonParam: param, cancelToken: cancelToken); + return true; + } + Future> search({ required String query, CancelToken? cancelToken, @@ -46,6 +73,9 @@ class MitraRepository extends BaseRepository { final List result = List.empty(growable: true); for (final el in resp["data"]) { final model = MitraResponseModel.fromJson(el); + List aggrementID = (el["aggrementID"].toString()).split(","); + model.aggrementID = aggrementID; + model.mDoctorName = el["M_DoctorName"]; result.add(model); } return result; diff --git a/lib/widget/fx_company_lookup.dart b/lib/widget/fx_company_lookup.dart index 28d852e..576a474 100644 --- a/lib/widget/fx_company_lookup.dart +++ b/lib/widget/fx_company_lookup.dart @@ -14,10 +14,9 @@ import 'provider/selectedCompanyProvider.dart'; // ignore: must_be_immutable class FxAcCompany extends HookConsumerWidget { final String? errorValidation; - FxAcCompany({ - Key? key, - this.errorValidation, - }) : super(key: key); + final bool readOnly; + FxAcCompany({Key? key, this.errorValidation, this.readOnly = false}) + : super(key: key); CancelToken? cancelToken; @override @@ -45,6 +44,8 @@ class FxAcCompany extends HookConsumerWidget { focusNode: fc, fieldViewBuilder: (context, ctrl, fc, onChange) { return FxTextField( + isReadOnly: readOnly, + isEnabled: !readOnly, ctrl: ctrl, fc: fc, hint: "Company", diff --git a/lib/widget/fx_data_mitra.dart b/lib/widget/fx_data_mitra.dart index 948c4fa..4208f37 100644 --- a/lib/widget/fx_data_mitra.dart +++ b/lib/widget/fx_data_mitra.dart @@ -3,11 +3,21 @@ 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/model/ac_company_response_model.dart'; +import 'package:onemd/model/ac_doctor_model.dart'; +import '../model/ac_mou_response_model.dart'; import '../model/mitra_response_model.dart'; +import '../provider/dio_provider.dart'; +import '../repository/mitra_repository.dart'; +import '../screen/md_lab_mitra/mitra_lookup_mou_provider.dart'; import '../screen/md_lab_mitra/mitra_search_provider.dart'; import 'fx_error_text.dart'; import 'fx_mitra_add_dialog.dart'; +import 'fx_mitra_edit_dialog.dart'; +import 'provider/doctor_address_lookup_provider.dart'; +import 'provider/selectedCompanyProvider.dart'; +import 'provider/selectedDoctorProvider.dart'; class FxDataMitra extends HookConsumerWidget { final int rowsPerPage; @@ -76,10 +86,61 @@ class FxDataMitra extends HookConsumerWidget { titleColumn(""), ], source: _MitraDataSource( - totalRow: list.value.length, - list: list.value, - pageWidth: pageWidth, - ), + totalRow: list.value.length, + list: list.value, + pageWidth: pageWidth, + onEdit: (model) async { + ref.read(selectedMouProvider.notifier).state = model.aggrementID + .map((id) => AcMouResponseModel( + mMouID: id, + mMouName: "", + mMouNote: "", + mMouIsMcu: "", + mMouNumber: "", + mMouEndDate: "", + mMouIsActive: "Y", + mMouStartDate: "", + )) + .toList(); + ref.read(selectedAcCompanyProvider.notifier).state = AcCompanyModel( + mCompanyID: model.mitraMCompanyID, + mCompanyAddress: model.mCompanyAddress, + mCompanyAddressLocation: "", + mCompanyEmail: "", + mCompanyHp: "", + mCompanyName: model.mCompanyName, + mCompanyNumber: "", + mCompanyPhone: "", + ); + ref + .read(mitraLookupMouProvider.notifier) + .lookup(companyID: model.mitraMCompanyID); + ref.read(selectedAcDoctorProvider.notifier).state = + AcDoctorResponseModel( + fullName: model.mDoctorName, + mDoctorID: model.mitraMDoctorID, + ); + ref + .read(doctorAddressLookupProvider.notifier) + .lookup(doctorID: model.mitraMDoctorID); + + ref.read(selectedAcDoctorAddressProvider.notifier).state = + AcDoctorAddressResponseModel( + mDoctorAddressID: model.mitraMDoctorAddressID, + mDoctorAddressDescription: "", + ); + + await showDialog( + context: context, + builder: (context) { + return FxMitraEditDialog( + login: model.mitraUsername, + idNo: model.mitraIDNo, + mitraID: model.mitraID, + ); + }, + ); + }), ); } @@ -117,11 +178,12 @@ class _MitraDataSource extends DataTableSource { final List list; final int totalRow; final double pageWidth; - + final void Function(MitraResponseModel)? onEdit; _MitraDataSource({ required this.list, required this.totalRow, required this.pageWidth, + this.onEdit, }); @override DataRow? getRow(int index) { @@ -151,7 +213,9 @@ class _MitraDataSource extends DataTableSource { mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ InkWell( - onTap: () {}, + onTap: () { + if (onEdit != null) onEdit!(model); + }, child: Icon( Icons.edit_rounded, size: 24, diff --git a/lib/widget/fx_doctor_address.dart b/lib/widget/fx_doctor_address.dart index 37c2b4f..e7a278d 100644 --- a/lib/widget/fx_doctor_address.dart +++ b/lib/widget/fx_doctor_address.dart @@ -11,11 +11,12 @@ import 'provider/selectedDoctorProvider.dart'; class FxDoctorAddress extends HookConsumerWidget { final double? width; final String? errorValidation; - - const FxDoctorAddress({ + bool isInit; + FxDoctorAddress({ Key? key, this.width, this.errorValidation, + this.isInit = true, }) : super(key: key); @override @@ -25,15 +26,22 @@ class FxDoctorAddress extends HookConsumerWidget { final doctorModel = ref.watch(selectedAcDoctorProvider); String doctorName = doctorModel?.fullName ?? ""; + final initState = useState(isInit); + ref.listen(doctorAddressLookupProvider, (prev, next) { if (next is DoctorAddressLookupStateDone) { for (int idx = 0; idx < next.list.length; idx++) { - next.list[idx].isCheck = false; + if (ref.read(selectedAcDoctorAddressProvider)?.mDoctorAddressID != + null && + ref.read(selectedAcDoctorAddressProvider)!.mDoctorAddressID == + next.list[idx].mDoctorAddressID) { + ref.read(selectedAcDoctorAddressProvider.notifier).state = + next.list[idx]; + } } listAddress.value = next.list; } }); - return Container( width: double.infinity, decoration: BoxDecoration( diff --git a/lib/widget/fx_mitra_edit_dialog.dart b/lib/widget/fx_mitra_edit_dialog.dart new file mode 100644 index 0000000..33fe465 --- /dev/null +++ b/lib/widget/fx_mitra_edit_dialog.dart @@ -0,0 +1,202 @@ +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'; + +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/mitra_edit_provider.dart'; +import 'provider/selectedCompanyProvider.dart'; +import 'provider/selectedDoctorProvider.dart'; + +class FxMitraEditDialog extends HookConsumerWidget { + final String login; + final String idNo; + final String mitraID; + + const FxMitraEditDialog({ + Key? key, + required this.login, + required this.idNo, + required this.mitraID, + }) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + 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 fcPassword = FocusNode(); + final fcRePassword = FocusNode(); + final errorPassword = useState(null); + final errorRePassword = useState(null); + + final ctrlLogin = useTextEditingController(text: login); + final ctrlPassword = useTextEditingController(text: ""); + final ctrlRePassword = useTextEditingController(text: ""); + + bool Function() validationError; + validationError = () { + bool haveError = false; + if (ctrlRePassword.text != ctrlPassword.text) { + errorRePassword.value = "Password confirmation error"; + haveError = true; + } + 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(), + borderRadius: BorderRadius.circular(10), + ), + child: SizedBox( + width: 800, + height: 600, + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FxAcCompany( + readOnly: true, + errorValidation: errorCompany.value, + ), + const SizedBox(height: 10), + FxMitraMou( + errorValidation: errorMou.value, + ), + const SizedBox(height: 10), + FxAcDoctor( + errorValidation: errorDoctor.value, + ), + const SizedBox(height: 10), + 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( + ctrl: ctrlPassword, + fc: fcPassword, + hint: "Password", + label: "Password", + obscureText: true, + errorMessage: errorPassword.value, + ), + const SizedBox(height: 10), + FxTextField( + ctrl: ctrlRePassword, + fc: fcRePassword, + hint: "Retype Password", + label: "Retype Password", + obscureText: true, + errorMessage: errorRePassword.value, + ), + const SizedBox(height: 10), + FxTextField( + ctrl: useTextEditingController(text: idNo), + 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(mitraEditProvider.notifier).edit( + mitraID: mitraID, + companyID: company!.mCompanyID, + mouID: mou.map((e) => e.mMouID).toList(), + doctorID: doctor!.mDoctorID, + doctorAddressID: + doctorAddress!.mDoctorAddressID, + login: ctrlLogin.text, + password: ctrlPassword.text, + query: "", + ); + Navigator.of(context).pop(); + } + }, + 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"), + ), + ], + ), + ], + ), + ), + )), + ); + } +} diff --git a/lib/widget/fx_mitra_mou.dart b/lib/widget/fx_mitra_mou.dart index 1e4b678..50bb080 100644 --- a/lib/widget/fx_mitra_mou.dart +++ b/lib/widget/fx_mitra_mou.dart @@ -26,7 +26,16 @@ class FxMitraMou extends HookConsumerWidget { ref.listen(mitraLookupMouProvider, (prev, next) { if (next is MitraLookupMouStateDone) { for (int idx = 0; idx < next.list.length; idx++) { - next.list[idx].isCheck = false; + final mouID = next.list[idx].mMouID; + + if (ref + .read(selectedMouProvider) + .indexWhere((m) => m.mMouID == mouID) == + -1) { + next.list[idx].isCheck = false; + } else { + next.list[idx].isCheck = true; + } } listMou.value = next.list; } diff --git a/lib/widget/provider/mitra_edit_provider.dart b/lib/widget/provider/mitra_edit_provider.dart new file mode 100644 index 0000000..dc1b08f --- /dev/null +++ b/lib/widget/provider/mitra_edit_provider.dart @@ -0,0 +1,87 @@ +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'; +import '../../screen/md_lab_mitra/mitra_search_provider.dart'; + +final mitraEditProvider = + StateNotifierProvider( + (ref) => MitraEditNotifier(ref: ref), +); + +class MitraEditNotifier extends StateNotifier { + final Ref ref; + CancelToken? cancelToken; + MitraEditNotifier({ + required this.ref, + }) : super(MitraEditStateInit()); + + void reset() { + state = MitraEditStateInit(); + } + + void edit({ + required String mitraID, + required String companyID, + required List mouID, + required String doctorID, + required String doctorAddressID, + required String login, + required String password, + required String query, + }) async { + try { + state = MitraEditStateLoading(); + final dio = ref.read(dioProvider); + final localAuth = ref.read(localAuthProvider); + if (localAuth?.token == null) { + throw BaseRepositoryException(message: "Invalid Token"); + } + await MitraRepository(dio: dio).edit( + mitraID: mitraID, + token: localAuth!.token!, + mouID: mouID, + companyID: companyID, + doctorID: doctorID, + doctorAddressID: doctorAddressID, + login: login, + password: password, + ); + state = MitraEditStateDone(); + ref.read(mitraSearchProvider.notifier).search(query: query); + } catch (e) { + if (e is BaseRepositoryException) { + state = MitraEditStateError(message: e.message); + } else { + state = MitraEditStateError(message: "Unknown Error "); + } + } + } +} + +abstract class MitraEditState extends Equatable { + final DateTime date; + MitraEditState() : date = DateTime.now(); + @override + List get props => throw [date]; +} + +class MitraEditStateInit extends MitraEditState {} + +class MitraEditStateLoading extends MitraEditState {} + +class MitraEditStateError extends MitraEditState { + final String message; + + MitraEditStateError({ + required this.message, + }); +} + +class MitraEditStateDone extends MitraEditState { + MitraEditStateDone(); +} diff --git a/php-api/mitra/Md.php b/php-api/mitra/Md.php index 13846ac..891ec41 100644 --- a/php-api/mitra/Md.php +++ b/php-api/mitra/Md.php @@ -12,6 +12,97 @@ class Md extends MY_Controller { echo "Mitra:MD:API"; } + function edit() + { + $param = $this->sys_input; + $user = $this->sys_user; + $userID = $user["M_UserID"]; + + $this->db->trans_begin(); + + if ($param["password"] == "") { + $sql = "update mitra + set MitraM_CompanyID=?, MitraM_DoctorID=?, + MitraM_DoctorAddressID=?,MitraUsername=?, + MitraM_UserID=? + where MitraID = ?"; + $qry = $this->db->query($sql, [ + $param["companyID"], $param["doctorID"], + $param["doctorAddressID"], $param["login"], + $userID, $param["mitraID"] + ]); + } else { + $sql = "update mitra + set MitraM_CompanyID=?, MitraM_DoctorID=?, + MitraM_DoctorAddressID=?,MitraUsername=?, + MitraM_UserID=?, MitraPassword = md5(?) + where MitraID = ?"; + $qry = $this->db->query($sql, [ + $param["companyID"], $param["doctorID"], + $param["doctorAddressID"], $param["login"], + $userID, $param["password"], $param["mitraID"] + ]); + } + if (!$qry) { + echo json_encode([ + "status" => "ERR", + "message" => $this->db->error()["message"], + ]); + exit(); + } + $mitraID = $param["mitraID"]; + $s_mouID = implode(",", $param["mouID"]); + if ($s_mouID == "") { + $s_mouID = "0"; + } + $sql = "update mitra_mou set MitraMouIsActive ='N' where + MitraMouMitraID = ? and MitraMouM_MouID not in ($s_mouID)"; + $qry = $this->db->query($sql, [$param["mitraID"]]); + + if (!$qry) { + echo json_encode([ + "status" => "ERR", + "message" => $this->db->error()["message"], + ]); + $this->db->trans_rollback(); + exit(); + } + $sql = "select * from mitra_mou where MitraMouMitraID =? and MitraMouIsActive ='Y'"; + $qry = $this->db->query($sql, [$param["mitraID"]]); + if (!$qry) { + echo json_encode([ + "status" => "ERR", + "message" => $this->db->error()["message"], + ]); + $this->db->trans_rollback(); + exit(); + } + $rows_mouid = []; + foreach ($qry->result_array() as $r) { + $rows_mouid[] = $r["MitraMouM_MouID"]; + } + + $sql = "insert into mitra_mou(MitraMouMitraID,MitraMouM_MouID, + MitraMouM_UserID) + values(?,?,?)"; + + foreach ($param["mouID"] as $mouID) { + if (in_array($mouID, $rows_mouid)) { + continue; + } + $qry = $this->db->query($sql, [$mitraID, $mouID, $userID]); + if (!$qry) { + echo json_encode([ + "status" => "ERR", + "message" => $this->db->error()["message"], + ]); + $this->db->trans_rollback(); + exit(); + } + } + $this->db->trans_commit(); + echo json_encode(["status" => "OK"]); + } function add() { $param = $this->sys_input; @@ -82,7 +173,9 @@ class Md extends MY_Controller $this->corss(); $sql = "select mitra.*, M_CompanyName, M_CompanyAddress, - group_concat(concat(M_MouName,' [', date_format(M_MouEndDate,'%d/%m/%Y'),'] ') separator '^') aggrement + group_concat(concat(M_MouName,' [', date_format(M_MouEndDate,'%d/%m/%Y'),'] ') separator '^') aggrement, + group_concat(M_MouID separator ',') aggrementID, + M_DoctorName from mitra join m_company on MitraM_CompanyID = M_CompanyID @@ -90,6 +183,7 @@ class Md extends MY_Controller and ( MitraUsername like ? or M_CompanyName like ?) + join m_doctor on MitraM_DoctorID = M_DoctorID join mitra_mou on MitraID = MitraMouMitraID and MitraMouIsActive ='Y'