From cf987b68f8c7875dfa77fcd6f8702067f73c250f Mon Sep 17 00:00:00 2001 From: padmanto Date: Mon, 6 Jun 2022 07:41:55 +0700 Subject: [PATCH] Up to Mou Lookup --- helper_func.fish | 28 ++- lib/app/constants.dart | 1 + lib/model/ac_company_response_model.dart | 48 +++++ lib/model/ac_mou_response_model.dart | 45 +++++ lib/model/mitra_response_model.dart | 76 ++++++++ lib/repository/base_repository.dart | 20 +- lib/repository/mitra_repository.dart | 56 ++++++ lib/screen/md_lab_mitra/md_lab_mitra_screen.dart | 76 ++------ .../md_lab_mitra/mitra_lookup_mou_provider.dart | 70 +++++++ lib/screen/md_lab_mitra/mitra_search_provider.dart | 70 +++++++ .../md_lab_mitra/selectedCompanyProvider.dart | 5 + lib/widget/fx_company_lookup.dart | 92 +++++++++ lib/widget/fx_data_mitra.dart | 207 +++++++++++++++++++++ lib/widget/fx_debouncer.dart | 20 ++ lib/widget/fx_error_text.dart | 20 ++ lib/widget/fx_mitra_add_dialog.dart | 37 ++++ lib/widget/fx_mitra_mou.dart | 68 +++++++ lib/widget/loading_page_widget.dart | 86 +++++++++ php-api/mitra/Md-curl.md | 12 ++ php-api/mitra/Md.php | 172 +++++++++++++++++ 20 files changed, 1133 insertions(+), 76 deletions(-) create mode 100644 lib/model/ac_company_response_model.dart create mode 100644 lib/model/ac_mou_response_model.dart create mode 100644 lib/model/mitra_response_model.dart create mode 100644 lib/repository/mitra_repository.dart create mode 100644 lib/screen/md_lab_mitra/mitra_lookup_mou_provider.dart create mode 100644 lib/screen/md_lab_mitra/mitra_search_provider.dart create mode 100644 lib/screen/md_lab_mitra/selectedCompanyProvider.dart create mode 100644 lib/widget/fx_company_lookup.dart create mode 100644 lib/widget/fx_data_mitra.dart create mode 100644 lib/widget/fx_debouncer.dart create mode 100644 lib/widget/fx_error_text.dart create mode 100644 lib/widget/fx_mitra_add_dialog.dart create mode 100644 lib/widget/fx_mitra_mou.dart create mode 100644 lib/widget/loading_page_widget.dart create mode 100644 php-api/mitra/Md-curl.md create mode 100644 php-api/mitra/Md.php diff --git a/helper_func.fish b/helper_func.fish index 763e5cd..e4c7488 100644 --- a/helper_func.fish +++ b/helper_func.fish @@ -1,11 +1,21 @@ function ul_flutter_ui -set cabang $argv[1] -echo upload to $cabang -cd build/web -rsync -avzr --progress *.js one@$cabang:/home/one/project/one/one-ui-flutter/md-mitra/ -rsync -avzr --progress *.html one@$cabang:/home/one/project/one/one-ui-flutter/md-mitra/ -rsync -avzr --progress assets one@$cabang:/home/one/project/one/one-ui-flutter/md-mitra/ -rsync -avzr --progress canvaskit one@$cabang:/home/one/project/one/one-ui-flutter/md-mitra/ -rsync -avzr --progress icons one@$cabang:/home/one/project/one/one-ui-flutter/md-mitra/ -cd ../.. + set cabang $argv[1] + echo upload to $cabang + cd build/web + rsync -avzr --progress *.js one@$cabang:/home/one/project/one/one-ui-flutter/md-mitra/ + rsync -avzr --progress *.html one@$cabang:/home/one/project/one/one-ui-flutter/md-mitra/ + rsync -avzr --progress assets one@$cabang:/home/one/project/one/one-ui-flutter/md-mitra/ + rsync -avzr --progress canvaskit one@$cabang:/home/one/project/one/one-ui-flutter/md-mitra/ + rsync -avzr --progress icons one@$cabang:/home/one/project/one/one-ui-flutter/md-mitra/ + cd ../.. +end + +function ul_api_to + set cabang $argv[2] + set target (string replace php-api/ "" $argv[1]) + + echo upload to $cabang + cd php-api + rsync -avzr --progress $target one@devone.aplikasi.web.id:/home/one/project/one/one-api/application/controllers/$target + cd .. end diff --git a/lib/app/constants.dart b/lib/app/constants.dart index 883f7c3..c688e49 100644 --- a/lib/app/constants.dart +++ b/lib/app/constants.dart @@ -20,6 +20,7 @@ class Constants { static const fontMediumSize = 16.0; static const fontSmallSize = 14.0; static String appVersion = "v0.1"; + static String baseUrl = "http://devone.aplikasi.web.id/one-api/mitra/"; static const Color colorNegative = Color(0xff95424E); } diff --git a/lib/model/ac_company_response_model.dart b/lib/model/ac_company_response_model.dart new file mode 100644 index 0000000..9c1f393 --- /dev/null +++ b/lib/model/ac_company_response_model.dart @@ -0,0 +1,48 @@ +class AcCompanyModel { + late String mCompanyAddress; + late String mCompanyAddressLocation; + late String mCompanyEmail; + late String mCompanyHp; + late String mCompanyID; + late String mCompanyName; + late String mCompanyNumber; + late String mCompanyPIC; + late String mCompanyPhone; + + AcCompanyModel({ + required this.mCompanyAddress, + required this.mCompanyAddressLocation, + required this.mCompanyEmail, + required this.mCompanyHp, + required this.mCompanyID, + required this.mCompanyName, + required this.mCompanyNumber, + required this.mCompanyPhone, + }); + + AcCompanyModel.fromJson(Map json) { + mCompanyAddress = json['M_CompanyAddress'] ?? ""; + mCompanyAddressLocation = json['M_CompanyAddressLocation'] ?? ""; + mCompanyEmail = json['M_CompanyEmail'] ?? ""; + mCompanyHp = json['M_CompanyHp'] ?? ""; + mCompanyID = json['M_CompanyID'] ?? ""; + mCompanyName = json['M_CompanyName'] ?? ""; + mCompanyNumber = json['M_CompanyNumber'] ?? ""; + mCompanyPIC = json['M_CompanyPIC'] ?? ""; + mCompanyPhone = json['M_CompanyPhone'] ?? ""; + } + + Map toJson() { + final Map data = {}; + data['M_CompanyAddress'] = mCompanyAddress; + data['M_CompanyAddressLocation'] = mCompanyAddressLocation; + data['M_CompanyEmail'] = mCompanyEmail; + data['M_CompanyHp'] = mCompanyHp; + data['M_CompanyID'] = mCompanyID; + data['M_CompanyName'] = mCompanyName; + data['M_CompanyNumber'] = mCompanyNumber; + data['M_CompanyPIC'] = mCompanyPIC; + data['M_CompanyPhone'] = mCompanyPhone; + return data; + } +} diff --git a/lib/model/ac_mou_response_model.dart b/lib/model/ac_mou_response_model.dart new file mode 100644 index 0000000..4a71976 --- /dev/null +++ b/lib/model/ac_mou_response_model.dart @@ -0,0 +1,45 @@ +class AcMouResponseModel { + late String mMouEndDate; + late String mMouID; + late String mMouIsActive; + late String mMouIsMcu; + late String mMouName; + late String mMouNote; + late String mMouNumber; + late String mMouStartDate; + + AcMouResponseModel({ + required this.mMouEndDate, + required this.mMouID, + required this.mMouIsActive, + required this.mMouIsMcu, + required this.mMouName, + required this.mMouNote, + required this.mMouNumber, + required this.mMouStartDate, + }); + + AcMouResponseModel.fromJson(Map json) { + mMouEndDate = json['M_MouEndDate'] ?? ""; + mMouID = json['M_MouID'].toString(); + mMouIsActive = json['M_MouIsActive'] ?? ""; + mMouIsMcu = json['M_MouIsMcu'] ?? ""; + mMouName = json['M_MouName'] ?? ""; + mMouNote = json['M_MouNote'] ?? ""; + mMouNumber = json['M_MouNumber'] ?? ""; + mMouStartDate = json['M_MouStartDate'] ?? ""; + } + + Map toJson() { + final Map data = {}; + data['M_MouEndDate'] = mMouEndDate; + data['M_MouID'] = mMouID; + data['M_MouIsActive'] = mMouIsActive; + data['M_MouIsMcu'] = mMouIsMcu; + data['M_MouName'] = mMouName; + data['M_MouNote'] = mMouNote; + data['M_MouNumber'] = mMouNumber; + data['M_MouStartDate'] = mMouStartDate; + return data; + } +} diff --git a/lib/model/mitra_response_model.dart b/lib/model/mitra_response_model.dart new file mode 100644 index 0000000..9256495 --- /dev/null +++ b/lib/model/mitra_response_model.dart @@ -0,0 +1,76 @@ +class MitraResponseModel { + late String mCompanyAddress; + late String mCompanyName; + late String mitraCommitment; + late String mitraCreated; + late String mitraHoldDate; + late String mitraHoldMUserID; + late String mitraID; + late String mitraIDNo; + late String mitraIsActive; + late String mitraIsHold; + late String mitraLastUpdated; + late String mitraMCompanyID; + late String mitraMUserID; + late String mitraPassword; + late String mitraUsername; + late String aggrement; + + MitraResponseModel({ + required this.mCompanyAddress, + required this.mCompanyName, + required this.mitraCommitment, + required this.mitraCreated, + required this.mitraHoldDate, + required this.mitraHoldMUserID, + required this.mitraID, + required this.mitraIDNo, + required this.mitraIsActive, + required this.mitraIsHold, + required this.mitraLastUpdated, + required this.mitraMCompanyID, + required this.mitraMUserID, + required this.mitraPassword, + required this.mitraUsername, + required this.aggrement, + }); + + MitraResponseModel.fromJson(Map json) { + mCompanyAddress = json['M_CompanyAddress']; + mCompanyName = json['M_CompanyName']; + mitraCommitment = json['MitraCommitment']; + mitraCreated = json['MitraCreated']; + mitraHoldDate = json['MitraHoldDate']; + mitraHoldMUserID = json['MitraHoldM_UserID']?.toString() ?? ""; + mitraID = json['MitraID'].toString(); + mitraIDNo = json['MitraIDNo']; + mitraIsActive = json['MitraIsActive']; + mitraIsHold = json['MitraIsHold']; + mitraLastUpdated = json['MitraLastUpdated']; + mitraMCompanyID = json['MitraM_CompanyID'].toString(); + mitraMUserID = json['MitraM_UserID']?.toString() ?? ""; + mitraPassword = json['MitraPassword']; + mitraUsername = json['MitraUsername']; + aggrement = json['aggrement']; + } + + 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; + return data; + } +} diff --git a/lib/repository/base_repository.dart b/lib/repository/base_repository.dart index 813a7d4..7953c8b 100644 --- a/lib/repository/base_repository.dart +++ b/lib/repository/base_repository.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:io'; import 'package:dio/dio.dart'; @@ -6,7 +7,7 @@ abstract class BaseRepository { final Dio dio; BaseRepository({required this.dio}); - Future get({ + Future> get({ required String service, CancelToken? cancelToken, }) async { @@ -26,7 +27,11 @@ abstract class BaseRepository { message: "Invalid Http Response ${response.statusCode}", ); } - return response.data; + final jData = jsonDecode(response.data); + if (jData["status"] == "ERR") { + throw BaseRepositoryException(message: jData["message"]); + } + return jData; } on DioError catch (e) { throw BaseRepositoryException(message: e.message); } on SocketException catch (e) { @@ -36,7 +41,7 @@ abstract class BaseRepository { } } - Future post({ + Future> post({ required String service, required Map jsonParam, CancelToken? cancelToken, @@ -55,10 +60,15 @@ abstract class BaseRepository { ); if (response.statusCode != 200) { throw BaseRepositoryException( - message: "Invalid Http Response ${response.statusCode}", + message: + "Invalid Http Response ${response.statusCode} |${response.statusMessage}", ); } - return response.data; + final jData = jsonDecode(response.data); + if (jData["status"] == "ERR") { + throw BaseRepositoryException(message: jData["message"]); + } + return jData; } on DioError catch (e) { throw BaseRepositoryException(message: e.message); } on SocketException catch (e) { diff --git a/lib/repository/mitra_repository.dart b/lib/repository/mitra_repository.dart new file mode 100644 index 0000000..d996d2c --- /dev/null +++ b/lib/repository/mitra_repository.dart @@ -0,0 +1,56 @@ +import 'package:dio/dio.dart'; + +import '../app/constants.dart'; +import '../model/ac_company_response_model.dart'; +import '../model/ac_mou_response_model.dart'; +import '../model/mitra_response_model.dart'; +import 'base_repository.dart'; + +class MitraRepository extends BaseRepository { + MitraRepository({required super.dio}); + Future> search({ + required String query, + CancelToken? cancelToken, + }) async { + final param = {"query": query}; + final service = "${Constants.baseUrl}md/search"; + final resp = await post( + service: service, jsonParam: param, cancelToken: cancelToken); + final List result = List.empty(growable: true); + for (final el in resp["data"]) { + final model = MitraResponseModel.fromJson(el); + result.add(model); + } + return result; + } + + Future> lookupCompany({ + required String query, + CancelToken? cancelToken, + }) async { + final param = {"query": query}; + final service = "${Constants.baseUrl}md/lookup_company"; + final resp = await post( + service: service, jsonParam: param, cancelToken: cancelToken); + final List result = List.empty(growable: true); + for (final el in resp["data"]) { + final model = AcCompanyModel.fromJson(el); + result.add(model); + } + return result; + } + + Future> lookupMou({ + required String companyID, + CancelToken? cancelToken, + }) async { + final service = "${Constants.baseUrl}md/lookup_mou/$companyID"; + final resp = await get(service: service, cancelToken: cancelToken); + final List result = List.empty(growable: true); + for (final el in resp["data"]) { + final model = AcMouResponseModel.fromJson(el); + result.add(model); + } + return result; + } +} diff --git a/lib/screen/md_lab_mitra/md_lab_mitra_screen.dart b/lib/screen/md_lab_mitra/md_lab_mitra_screen.dart index f907b92..9737863 100644 --- a/lib/screen/md_lab_mitra/md_lab_mitra_screen.dart +++ b/lib/screen/md_lab_mitra/md_lab_mitra_screen.dart @@ -3,6 +3,9 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../../provider/local_auth_provider.dart'; +import '../../provider/title_provider.dart'; +import '../../widget/fx_data_mitra.dart'; +import 'mitra_search_provider.dart'; class MdLabMitraScreen extends HookConsumerWidget { const MdLabMitraScreen({Key? key}) : super(key: key); @@ -10,79 +13,28 @@ class MdLabMitraScreen extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final size = MediaQuery.of(context).size; - final errorMessage = useState(""); final oneUser = ref.read(localAuthProvider); - + final isInit = useState(true); if (oneUser == null) { redirectToHome(); } + if (isInit.value) { + isInit.value = false; + WidgetsBinding.instance.addPostFrameCallback((_) { + ref.read(webTitleProvider.notifier).state = "Master data::Lab Mitra"; + ref.read(mitraSearchProvider.notifier).search(query: ""); + }); + } return Material( child: Container( height: size.height, width: size.width, color: Colors.white, - child: Padding( - padding: const EdgeInsets.all(20.0), - child: Row( - children: [ - Expanded( - flex: 3, - child: Container(color: Colors.green), - ), - const Expanded( - flex: 7, - child: Align( - alignment: Alignment.topLeft, - child: Text("MD Lab Mitra"), - ), - ), - ], - ), + child: const Padding( + padding: EdgeInsets.all(20.0), + child: Expanded(child: FxDataMitra()), ), ), ); } } - -class FxLoadingWidget extends StatelessWidget { - final String title; - final Color color; - final double size; - final double fontSize; - const FxLoadingWidget({ - Key? key, - required this.title, - this.color = Colors.blue, - this.fontSize = 16.0, - this.size = 48.0, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Material( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.max, - children: [ - SizedBox( - width: size, - height: size, - child: CircularProgressIndicator( - color: color, - ), - ), - const SizedBox( - height: 10, - ), - Text( - title, - style: TextStyle( - fontSize: fontSize, - color: color, - ), - ), - ], - ), - ); - } -} diff --git a/lib/screen/md_lab_mitra/mitra_lookup_mou_provider.dart b/lib/screen/md_lab_mitra/mitra_lookup_mou_provider.dart new file mode 100644 index 0000000..b23ab41 --- /dev/null +++ b/lib/screen/md_lab_mitra/mitra_lookup_mou_provider.dart @@ -0,0 +1,70 @@ +import 'package:dio/dio.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../model/ac_mou_response_model.dart'; +import '../../provider/dio_provider.dart'; +import '../../repository/base_repository.dart'; +import '../../repository/mitra_repository.dart'; + +final mitraLookupMouProvider = + StateNotifierProvider( + (ref) => MitraLookupMouNotifier(ref: ref), +); + +class MitraLookupMouNotifier extends StateNotifier { + final Ref ref; + CancelToken? cancelToken; + MitraLookupMouNotifier({ + required this.ref, + }) : super(MitraLookupMouStateInit()); + + void lookup({required String companyID}) async { + if (cancelToken == null) { + cancelToken = CancelToken(); + } else { + cancelToken!.cancel(); + cancelToken = CancelToken(); + } + try { + state = MitraLookupMouStateLoading(); + final dio = ref.read(dioProvider); + final resp = await MitraRepository(dio: dio) + .lookupMou(companyID: companyID, cancelToken: cancelToken); + state = MitraLookupMouStateDone(list: resp); + } catch (e) { + if (e is BaseRepositoryException) { + state = MitraLookupMouStateError(message: e.message); + } else { + state = MitraLookupMouStateError(message: e.toString()); + } + } + } +} + +abstract class MitraLookupMouState extends Equatable { + final DateTime date; + MitraLookupMouState() : date = DateTime.now(); + @override + List get props => throw [date]; +} + +class MitraLookupMouStateInit extends MitraLookupMouState {} + +class MitraLookupMouStateLoading extends MitraLookupMouState {} + +class MitraLookupMouStateError extends MitraLookupMouState { + final String message; + + MitraLookupMouStateError({ + required this.message, + }); +} + +class MitraLookupMouStateDone extends MitraLookupMouState { + final List list; + + MitraLookupMouStateDone({ + required this.list, + }); +} diff --git a/lib/screen/md_lab_mitra/mitra_search_provider.dart b/lib/screen/md_lab_mitra/mitra_search_provider.dart new file mode 100644 index 0000000..b45d256 --- /dev/null +++ b/lib/screen/md_lab_mitra/mitra_search_provider.dart @@ -0,0 +1,70 @@ +import 'package:dio/dio.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../model/mitra_response_model.dart'; +import '../../provider/dio_provider.dart'; +import '../../repository/base_repository.dart'; +import '../../repository/mitra_repository.dart'; + +final mitraSearchProvider = + StateNotifierProvider( + (ref) => MitraSearchNotifier(ref: ref), +); + +class MitraSearchNotifier extends StateNotifier { + final Ref ref; + CancelToken? cancelToken; + MitraSearchNotifier({ + required this.ref, + }) : super(MitraSearchStateInit()); + + void search({required String query}) async { + if (cancelToken == null) { + cancelToken = CancelToken(); + } else { + cancelToken!.cancel(); + cancelToken = CancelToken(); + } + try { + state = MitraSearchStateLoading(); + final dio = ref.read(dioProvider); + final resp = await MitraRepository(dio: dio) + .search(query: query, cancelToken: cancelToken); + state = MitraSearchStateDone(list: resp); + } catch (e) { + if (e is BaseRepositoryException) { + state = MitraSearchStateError(message: e.message); + } else { + state = MitraSearchStateError(message: e.toString()); + } + } + } +} + +abstract class MitraSearchState extends Equatable { + final DateTime date; + MitraSearchState() : date = DateTime.now(); + @override + List get props => throw [date]; +} + +class MitraSearchStateInit extends MitraSearchState {} + +class MitraSearchStateLoading extends MitraSearchState {} + +class MitraSearchStateError extends MitraSearchState { + final String message; + + MitraSearchStateError({ + required this.message, + }); +} + +class MitraSearchStateDone extends MitraSearchState { + final List list; + + MitraSearchStateDone({ + required this.list, + }); +} diff --git a/lib/screen/md_lab_mitra/selectedCompanyProvider.dart b/lib/screen/md_lab_mitra/selectedCompanyProvider.dart new file mode 100644 index 0000000..81b90a2 --- /dev/null +++ b/lib/screen/md_lab_mitra/selectedCompanyProvider.dart @@ -0,0 +1,5 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../model/ac_company_response_model.dart'; + +final selectdAcCompanyProvider = StateProvider((ref) => null); diff --git a/lib/widget/fx_company_lookup.dart b/lib/widget/fx_company_lookup.dart new file mode 100644 index 0000000..5883be7 --- /dev/null +++ b/lib/widget/fx_company_lookup.dart @@ -0,0 +1,92 @@ +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../model/ac_company_response_model.dart'; +import '../provider/dio_provider.dart'; +import '../repository/mitra_repository.dart'; +import '../screen/md_lab_mitra/selectedCompanyProvider.dart'; +import 'fx_data_mitra.dart'; + +// ignore: must_be_immutable +class FxAcCompany extends HookConsumerWidget { + FxAcCompany({Key? key}) : super(key: key); + CancelToken? cancelToken; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final errorMessage = useState(""); + final ctrl = useTextEditingController(text: ""); + + cancelToken = CancelToken(); + final fc = FocusNode(); + final selectedCompany = ref.read(selectdAcCompanyProvider); + if (selectedCompany != null) { + ctrl.text = selectedCompany.mCompanyName; + } + return RawAutocomplete( + textEditingController: ctrl, + displayStringForOption: (model) => model.mCompanyName, + focusNode: fc, + fieldViewBuilder: (context, ctrl, fc, onChange) { + return TextField( + controller: ctrl, + focusNode: fc, + ); + }, + optionsBuilder: (tv) async { + try { + final dio = ref.read(dioProvider); + await Future.delayed(const Duration(milliseconds: 300)); + final resp = await MitraRepository(dio: dio) + .lookupCompany(query: ctrl.text, cancelToken: cancelToken); + return resp; + } catch (e) { + errorMessage.value = e.toString(); + } + return []; + }, + optionsViewBuilder: (context, onSelect, listModel) { + return Align( + alignment: Alignment.topLeft, + child: SizedBox( + width: 400, + child: Material( + child: ListView.builder( + itemCount: listModel.length, + itemBuilder: (context, idx) { + final model = listModel.elementAt(idx); + return InkWell( + onTap: () { + ref.read(selectdAcCompanyProvider.notifier).state = + model; + onSelect(model); + }, + child: Container( + color: (idx % 2 == 1) + ? Colors.blue.shade100.withOpacity(0.2) + : null, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FxNormalBlueText( + title: model.mCompanyName, + isBold: true, + ), + const SizedBox(height: 5), + FxNormalBlueText( + title: model.mCompanyAddress), + ], + ), + ), + )); + }), + ), + ), + ); + }); + } +} diff --git a/lib/widget/fx_data_mitra.dart b/lib/widget/fx_data_mitra.dart new file mode 100644 index 0000000..dde8b91 --- /dev/null +++ b/lib/widget/fx_data_mitra.dart @@ -0,0 +1,207 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../model/mitra_response_model.dart'; +import '../screen/md_lab_mitra/mitra_search_provider.dart'; +import '../screen/md_lab_mitra/selectedCompanyProvider.dart'; +import 'fx_error_text.dart'; +import 'fx_mitra_add_dialog.dart'; + +class FxDataMitra extends HookConsumerWidget { + final int rowsPerPage; + const FxDataMitra({ + Key? key, + this.rowsPerPage = 10, + }) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final list = useState>(List.empty()); + final isLoading = useState(false); + final errorMessage = useState(""); + + ref.listen(mitraSearchProvider, (prev, next) { + if (next is MitraSearchStateLoading) { + isLoading.value = true; + } else if (next is MitraSearchStateError) { + isLoading.value = false; + errorMessage.value = next.message; + Timer(const Duration(seconds: 3), () { + errorMessage.value = ""; + }); + } else if (next is MitraSearchStateDone) { + isLoading.value = false; + list.value = next.list; + } + }); + final pageWidth = MediaQuery.of(context).size.width - 200; + return PaginatedDataTable( + arrowHeadColor: Colors.red, + columnSpacing: 10, + header: Column( + children: [ + const FxNormalBlueText(title: "Daftar Lab Mitra"), + if (isLoading.value) const LinearProgressIndicator(), + if (errorMessage.value != "") FxErrorText(title: errorMessage.value) + ], + ), + rowsPerPage: rowsPerPage, + actions: [ + SizedBox( + width: 150, + child: TextButton( + child: Row( + mainAxisSize: MainAxisSize.max, + children: const [ + Text("New Lab Mitra"), + Icon(Icons.add_rounded, size: 24), + ], + ), + onPressed: () async { + await showDialog( + context: context, + builder: (context) { + return const FxMitraAddDialog(); + }); + }), + ), + ], + columns: [ + titleColumn("Company"), + titleColumn("Login"), + titleColumn("ID"), + titleColumn("Aggreement"), + titleColumn(""), + ], + source: _MitraDataSource( + totalRow: list.value.length, + list: list.value, + pageWidth: pageWidth, + ), + ); + } + + DataColumn titleColumn(String title) { + return DataColumn( + label: FxNormalBlueText(title: title), + ); + } +} + +class FxNormalBlueText extends StatelessWidget { + final String title; + final bool isBold; + const FxNormalBlueText({ + Key? key, + required this.title, + this.isBold = false, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Text( + title, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 16, + fontWeight: isBold ? FontWeight.w700 : FontWeight.normal, + color: Colors.blue.shade500, + ), + ); + } +} + +class _MitraDataSource extends DataTableSource { + final List list; + final int totalRow; + final double pageWidth; + + _MitraDataSource({ + required this.list, + required this.totalRow, + required this.pageWidth, + }); + @override + DataRow? getRow(int index) { + final model = list[index]; + final List width = [ + pageWidth * 1.5 / 7, + pageWidth * 0.8 / 7, + pageWidth * 0.8 / 7, + pageWidth * 3.9 / 7 + ]; + final agreement = model.aggrement.replaceAll('^', ', '); + return DataRow( + color: (index % 2 == 0) + ? MaterialStateColor.resolveWith( + (state) => Colors.blue.shade50.withOpacity(0.2)) + : null, + cells: [ + dataCell(model.mCompanyName, width[0]), + dataCell(model.mitraUsername, width[1]), + dataCell(model.mitraIDNo, width[2]), + dataCell(agreement, width[3]), + DataCell( + SizedBox( + width: 60, + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + InkWell( + onTap: () {}, + child: Icon( + Icons.edit_rounded, + size: 24, + color: Colors.green.shade700, + ), + ), + InkWell( + onTap: () {}, + child: Icon( + Icons.delete_rounded, + size: 24, + color: Colors.red.shade700, + ), + ), + ], + ), + ), + ), + ], + ); + } + + DataCell dataCell( + String value, + double width, + ) { + return DataCell( + SizedBox( + width: width, + child: Padding( + padding: const EdgeInsets.all(5.0), + child: Text( + value, + style: TextStyle( + fontSize: 16, + color: Colors.blue.shade500, + ), + ), + ), + ), + ); + } + + @override + bool get isRowCountApproximate => false; + + @override + int get rowCount => list.length; + + @override + int get selectedRowCount => 0; +} diff --git a/lib/widget/fx_debouncer.dart b/lib/widget/fx_debouncer.dart new file mode 100644 index 0000000..8ec1032 --- /dev/null +++ b/lib/widget/fx_debouncer.dart @@ -0,0 +1,20 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +class FxDebouncer { + final int milliseconds; + VoidCallback? action; + Timer? _timer; + + FxDebouncer({ + required this.milliseconds, + this.action, + }); + run(VoidCallback action) { + if (_timer != null) { + _timer!.cancel(); + } + _timer = Timer(Duration(milliseconds: milliseconds), action); + } +} diff --git a/lib/widget/fx_error_text.dart b/lib/widget/fx_error_text.dart new file mode 100644 index 0000000..5e8dd76 --- /dev/null +++ b/lib/widget/fx_error_text.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; + +class FxErrorText extends StatelessWidget { + final String title; + const FxErrorText({ + required this.title, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Text( + "Error $title", + style: const TextStyle( + fontSize: 16, + color: Colors.red, + ), + ); + } +} diff --git a/lib/widget/fx_mitra_add_dialog.dart b/lib/widget/fx_mitra_add_dialog.dart new file mode 100644 index 0000000..b7980bd --- /dev/null +++ b/lib/widget/fx_mitra_add_dialog.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import 'fx_company_lookup.dart'; +import 'fx_mitra_mou.dart'; + +class FxMitraAddDialog extends HookConsumerWidget { + const FxMitraAddDialog({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Dialog( + shape: RoundedRectangleBorder( + side: const BorderSide(), + borderRadius: BorderRadius.circular(10), + ), + child: SizedBox( + width: 800, + height: 600, + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FxAcCompany(), + const SizedBox(height: 10), + const FxMitraMou(), + const Text("Login"), + const SizedBox(height: 10), + const Text("ID -- auto generated"), + const SizedBox(height: 10), + ], + ), + )), + ); + } +} diff --git a/lib/widget/fx_mitra_mou.dart b/lib/widget/fx_mitra_mou.dart new file mode 100644 index 0000000..70472e3 --- /dev/null +++ b/lib/widget/fx_mitra_mou.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +import '../model/ac_company_response_model.dart'; +import '../model/ac_mou_response_model.dart'; +import '../screen/md_lab_mitra/mitra_lookup_mou_provider.dart'; +import '../screen/md_lab_mitra/selectedCompanyProvider.dart'; + +class FxMitraMou extends HookConsumerWidget { + final double? width; + const FxMitraMou({ + Key? key, + this.width, + }) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final listMou = useState>(List.empty()); + + final companyModel = ref.watch(selectdAcCompanyProvider); + String companyName = companyModel?.mCompanyName ?? " -- null -- "; + if (companyModel != null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + ref + .read(mitraLookupMouProvider.notifier) + .lookup(companyID: companyModel.mCompanyID); + }); + } + ref.listen(mitraLookupMouProvider, (prev, next) { + if (next is MitraLookupMouStateDone) { + listMou.value = next.list; + } + }); + return Container( + width: double.infinity, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + border: Border.all(color: Colors.blue.shade700), + color: Colors.blue.shade100.withOpacity(0.3), + ), + child: ConstrainedBox( + constraints: BoxConstraints(minHeight: 50, maxHeight: 200), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Agreement " + companyName, + ), + SizedBox(height: 10), + if (listMou.value.length > 0) + Expanded( + child: ListView.builder( + itemCount: listMou.value.length, + itemBuilder: (context, idx) { + final model = listMou.value[idx]; + return Text(model.mMouName); + }), + ), + ]), + ), + ), + ); + } +} diff --git a/lib/widget/loading_page_widget.dart b/lib/widget/loading_page_widget.dart new file mode 100644 index 0000000..b1a1245 --- /dev/null +++ b/lib/widget/loading_page_widget.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; + +class LoadingPageWidget extends StatelessWidget { + final String title; + final Color color; + final Size size; + final double fontSize; + final double circleSize; + const LoadingPageWidget({ + Key? key, + required this.size, + required this.title, + this.color = Colors.blue, + required this.circleSize, + required this.fontSize, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Material( + child: Container( + height: size.height, + width: size.width, + color: Colors.white, + child: Align( + alignment: Alignment.center, + child: Column(children: [ + SizedBox( + width: 32, + height: 32, + child: CircularProgressIndicator(color: color), + ), + const SizedBox(height: 10), + Text( + title, + style: TextStyle(fontSize: fontSize, color: color), + ), + ]), + ), + ), + ); + } +} + +class FxLoadingWidget extends StatelessWidget { + final String title; + final Color color; + final double size; + final double fontSize; + const FxLoadingWidget({ + Key? key, + required this.title, + this.color = Colors.blue, + this.fontSize = 16.0, + this.size = 48.0, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Material( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + children: [ + SizedBox( + width: size, + height: size, + child: CircularProgressIndicator( + color: color, + ), + ), + const SizedBox( + height: 10, + ), + Text( + title, + style: TextStyle( + fontSize: fontSize, + color: color, + ), + ), + ], + ), + ); + } +} diff --git a/php-api/mitra/Md-curl.md b/php-api/mitra/Md-curl.md new file mode 100644 index 0000000..5f2e15f --- /dev/null +++ b/php-api/mitra/Md-curl.md @@ -0,0 +1,12 @@ +-- Add mitra + +```bash +curl http://devone.aplikasi.web.id/one-api/mitra/md/add \ +-d '{"mitraUsername":"lababc","mitraPassword":"d41d8cd98f00b204e9800998ecf8427e",\ +"mitraM_CompanyID":4640,"mitraCommitment":"-- commitment --",\ +"mou": [M_MouID:654,M_MouName:""]}' + +``` + +-- edit mitra +-- disable mitra diff --git a/php-api/mitra/Md.php b/php-api/mitra/Md.php new file mode 100644 index 0000000..52234c4 --- /dev/null +++ b/php-api/mitra/Md.php @@ -0,0 +1,172 @@ +sys_input; + print_r($param); + } + + function corss() + { + global $_SERVER; + if (isset($_SERVER["HTTP_ORIGIN"])) { + header("Access-Control-Allow-Origin:" . $_SERVER["HTTP_ORIGIN"]); + } else { + header("Access-Control-Allow-Origin: */*"); + } + header("Access-Control-Allow-Methods: GET, PUT, POST, DELETE, OPTIONS"); + header( + "Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Authorization" + ); + if ( + isset($_SERVER["REQUEST_METHOD"]) && + $_SERVER["REQUEST_METHOD"] == "OPTIONS" + ) { + http_response_code(200); + echo json_encode("OK"); + exit(); + } + } + function search() + { + $this->corss(); + $sql = "select mitra.*, + M_CompanyName, M_CompanyAddress, + group_concat(concat(M_MouName,' [', date_format(M_MouEndDate,'%d/%m/%Y'),'] ') separator '^') aggrement + from mitra + join m_company + on MitraM_CompanyID = M_CompanyID + and MitraIsActive = 'Y' + and ( + MitraUsername like ? + or M_CompanyName like ?) + join mitra_mou + on MitraID = MitraMouMitraID + and MitraMouIsActive ='Y' + join m_mou on MitraMouM_MouID = M_MouID + group by MitraID "; + $query = "%" . $this->sys_input["query"] . "%"; + $qry = $this->db->query($sql, [$query, $query]); + if (!$qry) { + echo json_encode([ + "status" => "ERR", + "message" => $this->db->error()["message"], + ]); + exit(); + } + echo json_encode(["status" => "OK", "data" => $qry->result_array()]); + } + function lookup_company() + { + $param = $this->sys_input; + $sql = "select * from m_company + where M_CompanyName like ? + and M_CompanyIsActive = 'Y' + limit 0,50"; + $qry = $this->db->query($sql, ["%" . $param["query"] . "%"]); + if (!$qry) { + echo json_encode([ + "status" => "ERR", + "message" => $this->db->error()["message"], + ]); + exit(); + } + echo json_encode(["status" => "OK", "data" => $qry->result_array()]); + } + function lookup_mou($companyID) + { + $sql = "select + * from m_mou + where M_MouM_CompanyID = ? + and M_MouIsReleased = 'Y' + and M_MouIsActive ='Y'"; + $qry = $this->db->query($sql, [$companyID]); + if (!$qry) { + echo json_encode([ + "status" => "ERR", + "message" => $this->db->error()["message"], + ]); + exit(); + } + echo json_encode(["status" => "OK", "data" => $qry->result_array()]); + } +} +/* +drop table if exists mitra; +create table mitra( + MitraID int not null auto_increment primary key, + MitraIDNo varchar(6), + MitraUsername varchar(20), + MitraPassword varchar(32), + MitraM_CompanyID int, + MitraIsActive varchar(1) default 'Y', + MitraCommitment text, + MitraCreated datetime default current_timestamp(), + MitraLastUpdated datetime default current_timestamp() on update current_timestamp(), + MitraM_UserID int, + MitraIsHold varchar(1) default 'N', + MitraHoldDate datetime default current_timestamp(), + MitraHoldM_UserID int, + unique(MitraIDNo,MitraUsername), + key(MitraIsActive), + key(MitraIsHold), + key(MitraM_CompanyID) +); +create table mitra_mou( + MitraMouID int not null auto_increment primary key, + MitraMouMitraID int, + MitraMouM_MouID int, + MitraMouIsActive varchar(1) default 'Y', + MitraMouCreated datetime default current_timestamp(), + MitraMouLastUpdated datetime default current_timestamp() on update current_timestamp(), + MitraMouM_UserID int, + key (MitraMouM_MouID), + key (MitraMouIsActive) +); +delimiter ;; +drop function if exists fn_generate_mitra_id;; +create function fn_generate_mitra_id ( +) returns varchar(6) +reads sql data +begin + set @branchCode = null; + select M_BranchCode into @branchCode + from m_branch + where M_BranchIsDefault = 'Y' and M_BranchIsActive = 'Y'; + if @branchCode is null then + return "ERR.BR"; + end if; + set @counter =0; + check_id: loop + set @sec_key = null; + select concat(@branchCode,substring('ACDEFGHJKLMNPQRSTUVWXYZ235679', rand()*29+1, 1), + substring('ACDEFGHJKLMNPQRSTUVWXYZ235679', rand()*29+1, 1), + substring('ACDEFGHJKLMNPQRSTUVWXYZ235679', rand()*29+1, 1), + substring('ACDEFGHJKLMNPQRSTUVWXYZ235679', rand()*29+1, 1) + ) into @sec_key; + return @sec_key; + set @tot_sec = null; + select count(*) into @tot_sec + from mitra where MitraIDNo = @sec_key; + if @tot_sec = 0 and length(@sec_key) <> 6 then + return @sec_key; + end if; + if @counter > 10 then + return "ERR.DUP"; + end if; + set @counter = @counter+1; + end loop; +end;; + */