padmanto
2 years ago
26 changed files with 801 additions and 188 deletions
@ -0,0 +1,48 @@
|
||||
class AcDoctorResponseModel { |
||||
late String fullName; |
||||
late String mDoctorID; |
||||
AcDoctorResponseModel({ |
||||
required this.fullName, |
||||
required this.mDoctorID, |
||||
}); |
||||
|
||||
AcDoctorResponseModel.fromJson(Map<String, dynamic> json) { |
||||
fullName = json['FullName']; |
||||
mDoctorID = json['M_DoctorID']; |
||||
} |
||||
|
||||
Map<String, dynamic> toJson() { |
||||
final Map<String, dynamic> data = <String, dynamic>{}; |
||||
data['FullName'] = fullName; |
||||
data['M_DoctorID'] = mDoctorID; |
||||
return data; |
||||
} |
||||
} |
||||
|
||||
class AcDoctorAddressResponseModel { |
||||
late String mDoctorAddressDescription; |
||||
late String mDoctorAddressID; |
||||
late bool isCheck; |
||||
|
||||
AcDoctorAddressResponseModel({ |
||||
required this.mDoctorAddressDescription, |
||||
required this.mDoctorAddressID, |
||||
}); |
||||
|
||||
void setIsCheck(bool val) { |
||||
isCheck = val; |
||||
} |
||||
|
||||
AcDoctorAddressResponseModel.fromJson(Map<String, dynamic> json) { |
||||
mDoctorAddressDescription = json['M_DoctorAddressDescription']; |
||||
mDoctorAddressID = json['M_DoctorAddressID']; |
||||
} |
||||
|
||||
Map<String, dynamic> toJson() { |
||||
final Map<String, dynamic> data = <String, dynamic>{}; |
||||
data['M_DoctorAddressDescription'] = mDoctorAddressDescription; |
||||
data['M_DoctorAddressID'] = mDoctorAddressID; |
||||
data['isCheck'] = isCheck; |
||||
return data; |
||||
} |
||||
} |
@ -0,0 +1,32 @@
|
||||
// ignore_for_file: must_be_immutable |
||||
|
||||
import 'dart:async'; |
||||
|
||||
import 'package:equatable/equatable.dart'; |
||||
import 'package:flutter_riverpod/flutter_riverpod.dart'; |
||||
|
||||
final timerProvider = |
||||
StateNotifierProvider.autoDispose<TimerNotifier, TimerState>( |
||||
(ref) => TimerNotifier()); |
||||
|
||||
class TimerNotifier extends StateNotifier<TimerState> { |
||||
TimerNotifier() : super(TimerStateInit()) { |
||||
Timer.periodic(const Duration(seconds: 5), (tmr) { |
||||
state = TimerStateTick(); |
||||
}); |
||||
} |
||||
} |
||||
|
||||
class TimerState extends Equatable { |
||||
late DateTime dateTime; |
||||
|
||||
TimerState() { |
||||
dateTime = DateTime.now(); |
||||
} |
||||
@override |
||||
List<Object?> get props => [dateTime]; |
||||
} |
||||
|
||||
class TimerStateInit extends TimerState {} |
||||
|
||||
class TimerStateTick extends TimerState {} |
@ -0,0 +1,90 @@
|
||||
import 'package:flutter/material.dart'; |
||||
import 'package:flutter_hooks/flutter_hooks.dart'; |
||||
import 'package:hooks_riverpod/hooks_riverpod.dart'; |
||||
import 'package:onemd/model/ac_doctor_model.dart'; |
||||
|
||||
import 'fx_data_mitra.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; |
||||
const FxDoctorAddress({ |
||||
Key? key, |
||||
this.width, |
||||
}) : super(key: key); |
||||
|
||||
@override |
||||
Widget build(BuildContext context, WidgetRef ref) { |
||||
final listAddress = |
||||
useState<List<AcDoctorAddressResponseModel>>(List.empty()); |
||||
|
||||
final doctorModel = ref.watch(selectedAcDoctorProvider); |
||||
String doctorName = doctorModel?.fullName ?? ""; |
||||
ref.listen(doctorAddressLookupProvider, (prev, next) { |
||||
if (next is DoctorAddressLookupStateDone) { |
||||
for (int idx = 0; idx < next.list.length; idx++) { |
||||
next.list[idx].isCheck = false; |
||||
} |
||||
listAddress.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.loose(const Size(double.infinity, 150)), |
||||
child: Padding( |
||||
padding: const EdgeInsets.all(8.0), |
||||
child: Column( |
||||
mainAxisSize: MainAxisSize.min, |
||||
crossAxisAlignment: CrossAxisAlignment.start, |
||||
children: [ |
||||
FxNormalBlueText( |
||||
title: "Alamat dari $doctorName", |
||||
isBold: true, |
||||
), |
||||
const SizedBox(height: 10), |
||||
if (listAddress.value.isNotEmpty) |
||||
ConstrainedBox( |
||||
constraints: |
||||
BoxConstraints.loose(const Size(double.infinity, 100)), |
||||
child: ListView.builder( |
||||
shrinkWrap: true, |
||||
itemCount: listAddress.value.length, |
||||
itemBuilder: (context, idx) { |
||||
final model = listAddress.value[idx]; |
||||
return Row( |
||||
children: [ |
||||
Checkbox( |
||||
value: model.isCheck, |
||||
onChanged: ((val) { |
||||
final List<AcDoctorAddressResponseModel> list = |
||||
List.empty(growable: true); |
||||
list.addAll(listAddress.value); |
||||
list[idx].isCheck = val ?? false; |
||||
listAddress.value = list; |
||||
}), |
||||
), |
||||
const SizedBox(width: 10), |
||||
Expanded( |
||||
child: FxNormalBlueText( |
||||
title: model.mDoctorAddressDescription), |
||||
), |
||||
], |
||||
); |
||||
}, |
||||
), |
||||
), |
||||
], |
||||
), |
||||
), |
||||
)); |
||||
} |
||||
} |
@ -0,0 +1,102 @@
|
||||
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 'package:onemd/widget/provider/selectedDoctorProvider.dart'; |
||||
|
||||
import '../model/ac_doctor_model.dart'; |
||||
import '../provider/dio_provider.dart'; |
||||
import '../repository/mitra_repository.dart'; |
||||
import 'fx_data_mitra.dart'; |
||||
import 'fx_text_field.dart'; |
||||
import 'provider/doctor_address_lookup_provider.dart'; |
||||
|
||||
// ignore: must_be_immutable |
||||
class FxAcDoctor extends HookConsumerWidget { |
||||
FxAcDoctor({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 selectedDoctor = ref.read(selectedAcDoctorProvider); |
||||
if (selectedDoctor != null) { |
||||
ctrl.text = selectedDoctor.fullName; |
||||
} |
||||
ref.listen<AcDoctorResponseModel?>(selectedAcDoctorProvider, ((prev, next) { |
||||
if (next != null) { |
||||
print("Calling for " + next.fullName); |
||||
ref |
||||
.read(doctorAddressLookupProvider.notifier) |
||||
.lookup(doctorID: next.mDoctorID); |
||||
} |
||||
})); |
||||
|
||||
return RawAutocomplete<AcDoctorResponseModel>( |
||||
textEditingController: ctrl, |
||||
displayStringForOption: (model) => model.fullName, |
||||
focusNode: fc, |
||||
fieldViewBuilder: (context, ctrl, fc, onChange) { |
||||
return FxTextField( |
||||
label: "Doctor", |
||||
hint: "Doctor", |
||||
fc: fc, |
||||
ctrl: ctrl, |
||||
); |
||||
}, |
||||
optionsBuilder: (tv) async { |
||||
try { |
||||
final dio = ref.read(dioProvider); |
||||
await Future.delayed(const Duration(milliseconds: 300)); |
||||
final resp = await MitraRepository(dio: dio) |
||||
.lookupDoctor(query: ctrl.text, cancelToken: cancelToken); |
||||
return resp; |
||||
} catch (e) { |
||||
errorMessage.value = "lookupDoctor Error"; |
||||
} |
||||
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(selectedAcDoctorProvider.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.fullName, |
||||
isBold: true, |
||||
), |
||||
], |
||||
), |
||||
), |
||||
)); |
||||
}), |
||||
), |
||||
), |
||||
); |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,46 @@
|
||||
import 'package:flutter/material.dart'; |
||||
|
||||
class FxTextField extends StatelessWidget { |
||||
final TextEditingController? ctrl; |
||||
final FocusNode? fc; |
||||
final String label; |
||||
final String hint; |
||||
final String? errorMessage; |
||||
final bool? isReadOnly; |
||||
final bool? isEnabled; |
||||
final String? suffixText; |
||||
|
||||
const FxTextField({ |
||||
Key? key, |
||||
this.ctrl, |
||||
this.fc, |
||||
required this.label, |
||||
required this.hint, |
||||
this.errorMessage, |
||||
this.isReadOnly, |
||||
this.isEnabled, |
||||
this.suffixText, |
||||
}) : super(key: key); |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return TextField( |
||||
decoration: InputDecoration( |
||||
border: OutlineInputBorder( |
||||
borderRadius: BorderRadius.circular(10), |
||||
), |
||||
hintText: hint, |
||||
labelText: label, |
||||
errorText: errorMessage, |
||||
suffixIcon: Padding( |
||||
padding: const EdgeInsets.only(right: 10.0, top: 10.0), |
||||
child: Text(suffixText ?? ""), |
||||
), |
||||
), |
||||
controller: ctrl, |
||||
focusNode: fc, |
||||
readOnly: isReadOnly ?? false, |
||||
enabled: isEnabled ?? true, |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,74 @@
|
||||
import 'package:dio/dio.dart'; |
||||
import 'package:equatable/equatable.dart'; |
||||
import 'package:flutter_riverpod/flutter_riverpod.dart'; |
||||
|
||||
import '../../model/ac_doctor_model.dart'; |
||||
import '../../provider/dio_provider.dart'; |
||||
import '../../repository/base_repository.dart'; |
||||
import '../../repository/mitra_repository.dart'; |
||||
|
||||
final doctorAddressLookupProvider = StateNotifierProvider< |
||||
DoctorAddressLookupNotifier, DoctorAddressLookupState>( |
||||
(ref) => DoctorAddressLookupNotifier(ref: ref), |
||||
); |
||||
|
||||
class DoctorAddressLookupNotifier |
||||
extends StateNotifier<DoctorAddressLookupState> { |
||||
final Ref ref; |
||||
CancelToken? cancelToken; |
||||
DoctorAddressLookupNotifier({ |
||||
required this.ref, |
||||
}) : super(DoctorAddressLookupStateInit()); |
||||
void reset() { |
||||
state = DoctorAddressLookupStateInit(); |
||||
} |
||||
|
||||
void lookup({required String doctorID}) async { |
||||
if (cancelToken == null) { |
||||
cancelToken = CancelToken(); |
||||
} else { |
||||
cancelToken!.cancel(); |
||||
cancelToken = CancelToken(); |
||||
} |
||||
try { |
||||
state = DoctorAddressLookupStateLoading(); |
||||
final dio = ref.read(dioProvider); |
||||
final resp = await MitraRepository(dio: dio) |
||||
.lookupDoctorAddress(doctorID: doctorID, cancelToken: cancelToken); |
||||
state = DoctorAddressLookupStateDone(list: resp); |
||||
} catch (e) { |
||||
if (e is BaseRepositoryException) { |
||||
state = DoctorAddressLookupStateError(message: e.message); |
||||
} else { |
||||
state = DoctorAddressLookupStateError(message: "Unknown Error"); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
abstract class DoctorAddressLookupState extends Equatable { |
||||
final DateTime date; |
||||
DoctorAddressLookupState() : date = DateTime.now(); |
||||
@override |
||||
List<Object?> get props => throw [date]; |
||||
} |
||||
|
||||
class DoctorAddressLookupStateInit extends DoctorAddressLookupState {} |
||||
|
||||
class DoctorAddressLookupStateLoading extends DoctorAddressLookupState {} |
||||
|
||||
class DoctorAddressLookupStateError extends DoctorAddressLookupState { |
||||
final String message; |
||||
|
||||
DoctorAddressLookupStateError({ |
||||
required this.message, |
||||
}); |
||||
} |
||||
|
||||
class DoctorAddressLookupStateDone extends DoctorAddressLookupState { |
||||
final List<AcDoctorAddressResponseModel> list; |
||||
|
||||
DoctorAddressLookupStateDone({ |
||||
required this.list, |
||||
}); |
||||
} |
@ -0,0 +1,73 @@
|
||||
import 'package:dio/dio.dart'; |
||||
import 'package:equatable/equatable.dart'; |
||||
import 'package:flutter_riverpod/flutter_riverpod.dart'; |
||||
|
||||
import '../../model/ac_doctor_model.dart'; |
||||
import '../../provider/dio_provider.dart'; |
||||
import '../../repository/base_repository.dart'; |
||||
import '../../repository/mitra_repository.dart'; |
||||
|
||||
final doctorLookupProvider = |
||||
StateNotifierProvider<DoctorLookupNotifier, DoctorLookupState>( |
||||
(ref) => DoctorLookupNotifier(ref: ref), |
||||
); |
||||
|
||||
class DoctorLookupNotifier extends StateNotifier<DoctorLookupState> { |
||||
final Ref ref; |
||||
CancelToken? cancelToken; |
||||
DoctorLookupNotifier({ |
||||
required this.ref, |
||||
}) : super(DoctorLookupStateInit()); |
||||
void reset() { |
||||
state = DoctorLookupStateInit(); |
||||
} |
||||
|
||||
void lookup({required String query}) async { |
||||
if (cancelToken == null) { |
||||
cancelToken = CancelToken(); |
||||
} else { |
||||
cancelToken!.cancel(); |
||||
cancelToken = CancelToken(); |
||||
} |
||||
try { |
||||
state = DoctorLookupStateLoading(); |
||||
final dio = ref.read(dioProvider); |
||||
final resp = await MitraRepository(dio: dio) |
||||
.lookupDoctor(query: query, cancelToken: cancelToken); |
||||
state = DoctorLookupStateDone(list: resp); |
||||
} catch (e) { |
||||
if (e is BaseRepositoryException) { |
||||
state = DoctorLookupStateError(message: e.message); |
||||
} else { |
||||
state = DoctorLookupStateError(message: "Unknown Error "); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
abstract class DoctorLookupState extends Equatable { |
||||
final DateTime date; |
||||
DoctorLookupState() : date = DateTime.now(); |
||||
@override |
||||
List<Object?> get props => throw [date]; |
||||
} |
||||
|
||||
class DoctorLookupStateInit extends DoctorLookupState {} |
||||
|
||||
class DoctorLookupStateLoading extends DoctorLookupState {} |
||||
|
||||
class DoctorLookupStateError extends DoctorLookupState { |
||||
final String message; |
||||
|
||||
DoctorLookupStateError({ |
||||
required this.message, |
||||
}); |
||||
} |
||||
|
||||
class DoctorLookupStateDone extends DoctorLookupState { |
||||
final List<AcDoctorResponseModel> list; |
||||
|
||||
DoctorLookupStateDone({ |
||||
required this.list, |
||||
}); |
||||
} |
@ -0,0 +1,5 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart'; |
||||
import 'package:onemd/model/ac_doctor_model.dart'; |
||||
|
||||
final selectedAcDoctorProvider = |
||||
StateProvider<AcDoctorResponseModel?>((ref) => null); |
@ -1,16 +1,22 @@
|
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta charset="UTF-8" /> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
||||
<title>Mitra Lab MasterData</title> |
||||
<script> |
||||
function fx_initial_route() { |
||||
return "/mdLabMitra"; |
||||
} |
||||
</script> |
||||
</head> |
||||
<body> |
||||
<script src="main.dart.js" type="application/javascript"></script> |
||||
</body> |
||||
|
||||
<head> |
||||
<meta charset="UTF-8" /> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
||||
<title>Mitra Lab MasterData</title> |
||||
<script> |
||||
function fx_initial_route() { |
||||
return "/mdLabMitra"; |
||||
} |
||||
function parent_home() { |
||||
parent.window.location.href = "/one-ui/"; |
||||
} |
||||
</script> |
||||
</head> |
||||
|
||||
<body> |
||||
<script src="main.dart.js" type="application/javascript"></script> |
||||
</body> |
||||
|
||||
</html> |
||||
|
Loading…
Reference in new issue