finish caleg, area and tim survei page

This commit is contained in:
kicap
2023-10-26 16:05:01 +08:00
parent 364bee3120
commit 85be29e7ce
24 changed files with 1100 additions and 228 deletions

View File

@ -1,4 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<application <application
android:label="cek_suara" android:label="cek_suara"
android:name="${applicationName}" android:name="${applicationName}"

View File

@ -4,12 +4,14 @@ import 'package:stacked/stacked_annotations.dart';
import '../services/global_var.dart'; import '../services/global_var.dart';
import '../services/http_services.dart'; import '../services/http_services.dart';
import '../services/my_easyloading.dart'; import '../services/my_easyloading.dart';
import '../services/other_function.dart';
import '../ui/views/admin_index_tracking/admin_first_page/admin_first_page_view.dart'; import '../ui/views/admin_index_tracking/admin_first_page/admin_first_page_view.dart';
import '../ui/views/admin_index_tracking/admin_index_tracking_view.dart'; import '../ui/views/admin_index_tracking/admin_index_tracking_view.dart';
import '../ui/views/admin_index_tracking/halaman_area/halaman_area_view.dart'; import '../ui/views/admin_index_tracking/halaman_area/halaman_area_view.dart';
import '../ui/views/admin_index_tracking/halaman_caleg/halaman_caleg_view.dart'; import '../ui/views/admin_index_tracking/halaman_caleg/halaman_caleg_view.dart';
import '../ui/views/admin_index_tracking/halaman_caleg/tambah_edit_caleg/tambah_edit_caleg_view.dart'; import '../ui/views/admin_index_tracking/halaman_caleg/tambah_edit_caleg/tambah_edit_caleg_view.dart';
import '../ui/views/admin_index_tracking/halaman_pengaturan/halaman_pengaturan_view.dart'; import '../ui/views/admin_index_tracking/halaman_pengaturan/halaman_pengaturan_view.dart';
import '../ui/views/admin_index_tracking/tim_survei/tambah_detail_tim_survei/tambah_detail_tim_survei_view.dart';
import '../ui/views/admin_index_tracking/tim_survei/tim_survei_view.dart'; import '../ui/views/admin_index_tracking/tim_survei/tim_survei_view.dart';
import '../ui/views/login_screen/login_screen_view.dart'; import '../ui/views/login_screen/login_screen_view.dart';
import '../ui/views/splash_screen/splash_screen_view.dart'; import '../ui/views/splash_screen/splash_screen_view.dart';
@ -42,6 +44,7 @@ import '../ui/views/splash_screen/splash_screen_view.dart';
], ],
dialogs: [ dialogs: [
StackedDialog(classType: TambahEditCalegView), StackedDialog(classType: TambahEditCalegView),
StackedDialog(classType: TambahDetailTimSurveiView)
], ],
dependencies: [ dependencies: [
LazySingleton(classType: NavigationService), LazySingleton(classType: NavigationService),
@ -54,6 +57,7 @@ import '../ui/views/splash_screen/splash_screen_view.dart';
LazySingleton(classType: MyEasyLoading), LazySingleton(classType: MyEasyLoading),
LazySingleton(classType: MyHttpServices), LazySingleton(classType: MyHttpServices),
LazySingleton(classType: GlobalVar), LazySingleton(classType: GlobalVar),
LazySingleton(classType: MyFunction),
], ],
logger: StackedLogger(), logger: StackedLogger(),
) )

View File

@ -8,9 +8,11 @@ import 'package:stacked_services/stacked_services.dart';
import 'app.locator.dart'; import 'app.locator.dart';
import '../ui/views/admin_index_tracking/halaman_caleg/tambah_edit_caleg/tambah_edit_caleg_view.dart'; import '../ui/views/admin_index_tracking/halaman_caleg/tambah_edit_caleg/tambah_edit_caleg_view.dart';
import '../ui/views/admin_index_tracking/tim_survei/tambah_detail_tim_survei/tambah_detail_tim_survei_view.dart';
enum DialogType { enum DialogType {
tambahEditCalegView, tambahEditCalegView,
tambahDetailTimSurveiView,
} }
void setupDialogUi() { void setupDialogUi() {
@ -19,6 +21,8 @@ void setupDialogUi() {
final Map<DialogType, DialogBuilder> builders = { final Map<DialogType, DialogBuilder> builders = {
DialogType.tambahEditCalegView: (context, request, completer) => DialogType.tambahEditCalegView: (context, request, completer) =>
TambahEditCalegView(request: request, completer: completer), TambahEditCalegView(request: request, completer: completer),
DialogType.tambahDetailTimSurveiView: (context, request, completer) =>
TambahDetailTimSurveiView(request: request, completer: completer),
}; };
dialogService.registerCustomDialogBuilders(builders); dialogService.registerCustomDialogBuilders(builders);

View File

@ -15,6 +15,7 @@ import 'package:stacked_shared/stacked_shared.dart';
import '../services/global_var.dart'; import '../services/global_var.dart';
import '../services/http_services.dart'; import '../services/http_services.dart';
import '../services/my_easyloading.dart'; import '../services/my_easyloading.dart';
import '../services/other_function.dart';
final locator = StackedLocator.instance; final locator = StackedLocator.instance;
@ -34,4 +35,5 @@ Future<void> setupLocator({
locator.registerLazySingleton(() => MyEasyLoading()); locator.registerLazySingleton(() => MyEasyLoading());
locator.registerLazySingleton(() => MyHttpServices()); locator.registerLazySingleton(() => MyHttpServices());
locator.registerLazySingleton(() => GlobalVar()); locator.registerLazySingleton(() => GlobalVar());
locator.registerLazySingleton(() => MyFunction());
} }

View File

@ -6,6 +6,7 @@ import 'package:stacked_services/stacked_services.dart';
import '../../services/global_var.dart'; import '../../services/global_var.dart';
import '../../services/http_services.dart'; import '../../services/http_services.dart';
import '../../services/my_easyloading.dart'; import '../../services/my_easyloading.dart';
import '../../services/other_function.dart';
import '../app.locator.dart'; import '../app.locator.dart';
import '../themes/app_colors.dart'; import '../themes/app_colors.dart';
@ -17,6 +18,7 @@ class CustomBaseViewModel extends BaseViewModel {
final easyLoading = locator<MyEasyLoading>(); final easyLoading = locator<MyEasyLoading>();
final httpService = locator<MyHttpServices>(); final httpService = locator<MyHttpServices>();
final globalVar = locator<GlobalVar>(); final globalVar = locator<GlobalVar>();
final myFunction = locator<MyFunction>();
void back() { void back() {
navigationService.back(); navigationService.back();

View File

@ -0,0 +1,51 @@
import '../app/app.locator.dart';
import '../services/other_function.dart';
class TimSurveiListModel {
List<TimSurveiModel>? survei;
int? jumlah;
TimSurveiListModel({this.survei, this.jumlah});
TimSurveiListModel.fromJson(Map<String, dynamic> json) {
if (json['survei'] != null) {
survei = <TimSurveiModel>[];
json['survei'].forEach((v) {
survei!.add(TimSurveiModel.fromJson(v));
});
}
jumlah = json['jumlah'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
if (survei != null) {
data['survei'] = survei!.map((v) => v.toJson()).toList();
}
data['jumlah'] = jumlah;
return data;
}
}
class TimSurveiModel {
final myFunction = locator<MyFunction>();
String? nik;
String? nama;
String? createdAt;
TimSurveiModel({this.nik, this.nama, this.createdAt});
TimSurveiModel.fromJson(Map<String, dynamic> json) {
nik = json['nik'];
nama = json['nama'];
createdAt = myFunction.convertDateTime(json['created_at']);
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['nik'] = nik;
data['nama'] = nama;
data['created_at'] = createdAt;
return data;
}
}

View File

@ -1,7 +1,13 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:stacked_services/stacked_services.dart';
import '../app/app.locator.dart';
import '../app/app.logger.dart';
class MyHttpServices { class MyHttpServices {
final _log = getLogger('MyHttpServices');
final _snackbarService = locator<SnackbarService>();
final _options = BaseOptions( final _options = BaseOptions(
baseUrl: dotenv.env['api_url']!, baseUrl: dotenv.env['api_url']!,
connectTimeout: const Duration(milliseconds: 60000), connectTimeout: const Duration(milliseconds: 60000),
@ -17,7 +23,16 @@ class MyHttpServices {
Future<Response> get(String path) async { Future<Response> get(String path) async {
try { try {
return await _dio.get(path); return await _dio.get(path);
} on DioException { } on DioException catch (e) {
String response = e.response != null
? e.response!.data['message'].toString()
: e.toString();
_log.e('ini errornya: $response');
_snackbarService.showSnackbar(
message: response,
title: 'Error',
duration: const Duration(milliseconds: 1000),
);
rethrow; rethrow;
} }
} }
@ -25,7 +40,16 @@ class MyHttpServices {
Future<Response> postWithFormData(String path, FormData formData) async { Future<Response> postWithFormData(String path, FormData formData) async {
try { try {
return await _dio.post(path, data: formData); return await _dio.post(path, data: formData);
} on DioException { } on DioException catch (e) {
String response = e.response != null
? e.response!.data['message'].toString()
: e.toString();
// _log.e('ini errornya: $response');
_snackbarService.showSnackbar(
message: response,
title: 'Error',
duration: const Duration(milliseconds: 1000),
);
rethrow; rethrow;
} }
} }
@ -33,7 +57,16 @@ class MyHttpServices {
Future<Response> delete(String path) async { Future<Response> delete(String path) async {
try { try {
return await _dio.delete(path); return await _dio.delete(path);
} on DioException { } on DioException catch (e) {
String response = e.response != null
? e.response!.data['message'].toString()
: e.toString();
// _log.e('ini errornya: $response');
_snackbarService.showSnackbar(
message: response,
title: 'Error',
duration: const Duration(milliseconds: 1000),
);
rethrow; rethrow;
} }
} }

View File

@ -0,0 +1,10 @@
import 'package:intl/intl.dart';
class MyFunction {
String convertDateTime(String input) {
DateTime dateTime = DateTime.parse(input);
String formattedDateTime =
DateFormat('dd-MM-yyyy | hh.mm.ss a').format(dateTime);
return formattedDateTime;
}
}

View File

@ -38,20 +38,27 @@ class AdminFirstPageView extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const TopContainer( TopContainer(
title: 'Jumlah Area', title: 'Jumlah Area',
value: '10 Area', value: '${model.jumlahArea} Area',
icon: Icons.place_outlined, icon: Icons.place_outlined,
background: warningColor, background: warningColor,
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
const TopContainer( TopContainer(
title: 'Jumlah Caleg', title: 'Jumlah Caleg',
value: '10 Caleg', value: '${model.jumlahCaleg} Caleg',
icon: Icons.co_present_outlined, icon: Icons.co_present_outlined,
background: greenColor, background: greenColor,
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
TopContainer(
title: 'Tim Survei',
value: '${model.jumlahTimSurvei} Tim Survei',
icon: Icons.co_present_outlined,
background: orangeColor,
),
const SizedBox(height: 10),
const TopContainer( const TopContainer(
title: 'Jumlah Pemilih', title: 'Jumlah Pemilih',
value: '10 Pemilih', value: '10 Pemilih',
@ -61,6 +68,9 @@ class AdminFirstPageView extends StatelessWidget {
const SizedBox( const SizedBox(
height: 20, height: 20,
), ),
if (model.isBusy)
const Center(child: CircularProgressIndicator()),
if (!model.isBusy && model.status == true)
RichText( RichText(
text: TextSpan( text: TextSpan(
text: 'Selamat Datang, ', text: 'Selamat Datang, ',
@ -105,11 +115,62 @@ class AdminFirstPageView extends StatelessWidget {
], ],
), ),
), ),
if (!model.isBusy && model.status == false)
RichText(
text: TextSpan(
text: 'Selamat Datang, ',
style: regularTextStyle,
children: [
const TextSpan(
text: 'Admin\n',
style: boldTextStyle,
),
const TextSpan(
text: 'Terjadi ',
style: regularTextStyle,
),
TextSpan(
text: 'Error ',
style: boldTextStyle.copyWith(
color: redColor,
fontStyle: FontStyle.italic,
),
),
const TextSpan(
text: 'pada saat mengambil data\n',
style: regularTextStyle,
),
const TextSpan(
text: 'Silahkan coba lagi dengan menekan icon',
style: regularTextStyle,
),
TextSpan(
text: ' Pengaturan\n',
style: boldTextStyle.copyWith(
color: greenColor,
fontStyle: FontStyle.italic,
),
),
const TextSpan(
text: 'di pojok kanan bawah\n',
style: regularTextStyle,
),
],
),
),
], ],
), ),
), ),
), ),
), ),
// with setting icon
floatingActionButton: FloatingActionButton(
onPressed: () {
// model.gotoSetting();
},
child: const Icon(Icons.settings),
),
floatingActionButtonLocation: FloatingActionButtonLocation.miniEndTop,
); );
}, },
); );

View File

@ -1,7 +1,55 @@
import '../../../../app/app.logger.dart';
import '../../../../app/core/custom_base_view_model.dart'; import '../../../../app/core/custom_base_view_model.dart';
import '../../../../model/area_model.dart';
import '../../../../model/caleg_model.dart';
import '../../../../model/my_response.model.dart';
import '../../../../model/tim_survei_model.dart';
class AdminFirstPageViewModel extends CustomBaseViewModel { class AdminFirstPageViewModel extends CustomBaseViewModel {
final log = getLogger('AdminFirstPageViewModel');
// variabel
int jumlahArea = 0;
int jumlahCaleg = 0;
int jumlahTimSurvei = 0;
bool status = false;
Future<void> init() async { Future<void> init() async {
globalVar.backPressed = 'exitApp'; globalVar.backPressed = 'exitApp';
await getDataArea();
}
getDataArea() async {
log.i('getData');
setBusy(true);
globalVar.backPressed = 'cantBack';
try {
var response = await httpService.get('area');
log.i(response.data);
MyResponseModel myResponseModel = MyResponseModel.fromJson(response.data);
AreaListModel areaListModel =
AreaListModel.fromJson(myResponseModel.data);
jumlahArea = areaListModel.jumlah!;
response = await httpService.get('caleg');
log.i(response.data);
myResponseModel = MyResponseModel.fromJson(response.data);
CalegListModel calegListModel =
CalegListModel.fromJson(myResponseModel.data);
jumlahCaleg = calegListModel.jumlah!;
response = await httpService.get('survei');
myResponseModel = MyResponseModel.fromJson(response.data);
TimSurveiListModel timSurveiListModel =
TimSurveiListModel.fromJson(myResponseModel.data);
jumlahTimSurvei = timSurveiListModel.jumlah!;
status = true;
} catch (e) {
status = false;
log.e(e);
} finally {
globalVar.backPressed = 'exitApp';
setBusy(false);
}
} }
} }

View File

@ -92,9 +92,13 @@ class HalamanAreaView extends StatelessWidget {
child: Column( child: Column(
children: [ children: [
if (model.jumlahArea == 0) if (model.jumlahArea == 0)
const Center( Center(
child: Text( child: model.status == true
'Belum ada area diinput')), ? const Text(
'Belum ada area diinput')
: const Text(
'Gagal mengambil data'),
),
if (model.jumlahArea > 0) if (model.jumlahArea > 0)
for (var i = 0; for (var i = 0;
i < model.jumlahArea; i < model.jumlahArea;

View File

@ -13,6 +13,7 @@ class HalamanAreaViewModel extends CustomBaseViewModel {
// variabel // variabel
List<AreaModel> listAreaModel = []; List<AreaModel> listAreaModel = [];
int jumlahArea = 0; int jumlahArea = 0;
bool status = false;
// add area form // add area form
final formKey = GlobalKey<FormState>(); final formKey = GlobalKey<FormState>();
@ -38,7 +39,9 @@ class HalamanAreaViewModel extends CustomBaseViewModel {
log.i('listAreaModel: $listAreaModel'); log.i('listAreaModel: $listAreaModel');
log.i('jumlahArea: $jumlahArea'); log.i('jumlahArea: $jumlahArea');
status = true;
} catch (e) { } catch (e) {
status = false;
log.e(e); log.e(e);
} finally { } finally {
globalVar.backPressed = 'exitApp'; globalVar.backPressed = 'exitApp';

View File

@ -1,8 +1,8 @@
import 'package:cek_suara/app/themes/app_text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import '../../../../app/themes/app_colors.dart'; import '../../../../app/themes/app_colors.dart';
import '../../../../app/themes/app_text.dart';
import '../../../widgets/top_container.dart'; import '../../../widgets/top_container.dart';
import './halaman_caleg_view_model.dart'; import './halaman_caleg_view_model.dart';
@ -71,40 +71,58 @@ class HalamanCalegView extends StatelessWidget {
), ),
if (!model.isBusy && if (!model.isBusy &&
model.listCalegModel.isNotEmpty) model.listCalegModel.isNotEmpty)
Expanded( SingleChildScrollView(
child: SingleChildScrollView(
child: Column( child: Column(
children: [ children: [
for (var i = 0; for (var i = 0; i < model.jumlahCaleg; i++)
i < model.jumlahCaleg;
i++)
Card( Card(
child: ListTile( child: ListTile(
leading: Text('${i + 1}'), leading: Text('${i + 1}'),
title: Text( title: Text(
model model.listCalegModel[i].namaCaleg!,
.listCalegModel[i].namaCaleg!,
style: boldTextStyle, style: boldTextStyle,
), ),
subtitle: Text( subtitle: Text(
'No. Urut: ${model.listCalegModel[i].nomorUrutCaleg!}', 'No. Urut: ${model.listCalegModel[i].nomorUrutCaleg!}',
style: italicTextStyle, style: italicTextStyle,
), ),
trailing: IconButton( trailing: Row(
icon: const Icon(Icons.edit, mainAxisSize: MainAxisSize.min,
color: mainColor), children: [
onPressed: () {}, IconButton(
icon: const Icon(
Icons.info_outline,
color: mainColor,
),
onPressed: () {
model.showDetailCaleg(
model.listCalegModel[i]);
},
),
IconButton(
icon: const Icon(
// trash
Icons.delete_outline,
color: dangerColor,
),
onPressed: () {
model.deleteCaleg(
model.listCalegModel[i]);
},
),
],
), ),
), ),
), ),
], ],
), ),
), ),
),
if (!model.isBusy && model.listCalegModel.isEmpty) if (!model.isBusy && model.listCalegModel.isEmpty)
const Center( Center(
child: Text( child: Text(
'Tidak ada data caleg diinput sebelumnya', model.status == true
? 'Tidak ada data caleg diinput sebelumnya'
: 'Gagal mengambil data caleg',
style: italicTextStyle, style: italicTextStyle,
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),

View File

@ -1,3 +1,5 @@
import 'package:cek_suara/app/themes/app_colors.dart';
import '../../../../app/app.dialogs.dart'; import '../../../../app/app.dialogs.dart';
import '../../../../app/app.logger.dart'; import '../../../../app/app.logger.dart';
import '../../../../app/core/custom_base_view_model.dart'; import '../../../../app/core/custom_base_view_model.dart';
@ -10,6 +12,7 @@ class HalamanCalegViewModel extends CustomBaseViewModel {
// variabel // variabel
List<CalegModel> listCalegModel = []; List<CalegModel> listCalegModel = [];
int jumlahCaleg = 0; int jumlahCaleg = 0;
bool status = false;
Future<void> init() async { Future<void> init() async {
globalVar.backPressed = 'exitApp'; globalVar.backPressed = 'exitApp';
@ -31,8 +34,10 @@ class HalamanCalegViewModel extends CustomBaseViewModel {
log.i('listCalegModel: $listCalegModel'); log.i('listCalegModel: $listCalegModel');
log.i('jumlahCaleg: $jumlahCaleg'); log.i('jumlahCaleg: $jumlahCaleg');
status = true;
} catch (e) { } catch (e) {
log.e(e); log.e(e);
status = false;
} finally { } finally {
globalVar.backPressed = 'exitApp'; globalVar.backPressed = 'exitApp';
setBusy(false); setBusy(false);
@ -43,11 +48,64 @@ class HalamanCalegViewModel extends CustomBaseViewModel {
// log.i('addCaleg'); // log.i('addCaleg');
var res = await dialogService.showCustomDialog( var res = await dialogService.showCustomDialog(
variant: DialogType.tambahEditCalegView, variant: DialogType.tambahEditCalegView,
data: { title: 'Tambah Caleg',
'title': 'Tambah Caleg',
},
); );
log.i(res?.confirmed); if (res!.confirmed) {
snackbarService.showSnackbar(
message: 'Caleg berhasil ditambahkan',
title: 'Berhasil',
duration: const Duration(seconds: 3),
);
await getData();
}
}
deleteCaleg(CalegModel calegModel) {
dialogService
.showDialog(
title: 'Hapus Caleg',
description:
'Apakah anda yakin ingin menghapus caleg ${calegModel.namaCaleg} ?',
buttonTitle: 'Hapus',
cancelTitle: 'Batal',
buttonTitleColor: dangerColor,
cancelTitleColor: mainColor,
)
.then((value) async {
if (value!.confirmed) {
setBusy(true);
easyLoading.customLoading('Menghapus Caleg...');
try {
var response =
await httpService.delete('caleg/${calegModel.idCaleg}');
log.i(response.data);
await getData();
snackbarService.showSnackbar(
message: 'Caleg berhasil dihapus',
title: 'Berhasil',
duration: const Duration(seconds: 3),
);
} catch (e) {
log.e(e);
} finally {
easyLoading.dismissLoading();
setBusy(false);
}
}
});
}
showDetailCaleg(CalegModel calegModel) async {
var res = await dialogService.showCustomDialog(
variant: DialogType.tambahEditCalegView,
title: 'Detail Caleg',
data: calegModel,
);
// log.i('res ini: ${res!.confirmed}');
if (res!.confirmed) {
await deleteCaleg(calegModel);
}
} }
} }

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart'; import 'package:stacked_services/stacked_services.dart';
import 'package:validatorless/validatorless.dart'; import 'package:validatorless/validatorless.dart';
@ -10,13 +11,13 @@ import '../../../../widgets/my_textformfield.dart';
import './tambah_edit_caleg_view_model.dart'; import './tambah_edit_caleg_view_model.dart';
class TambahEditCalegView extends StatelessWidget { class TambahEditCalegView extends StatelessWidget {
final DialogRequest? request; final DialogRequest request;
final Function(DialogResponse)? completer; final Function(DialogResponse) completer;
const TambahEditCalegView({ const TambahEditCalegView({
Key? key, Key? key,
this.request, required this.request,
this.completer, required this.completer,
}) : super(key: key); }) : super(key: key);
@override @override
@ -24,7 +25,7 @@ class TambahEditCalegView extends StatelessWidget {
return ViewModelBuilder<TambahEditCalegViewModel>.reactive( return ViewModelBuilder<TambahEditCalegViewModel>.reactive(
viewModelBuilder: () => TambahEditCalegViewModel(), viewModelBuilder: () => TambahEditCalegViewModel(),
onViewModelReady: (TambahEditCalegViewModel model) async { onViewModelReady: (TambahEditCalegViewModel model) async {
await model.init(); await model.init(request.data);
}, },
builder: ( builder: (
BuildContext context, BuildContext context,
@ -45,9 +46,10 @@ class TambahEditCalegView extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text( Text(
request?.data['title'] ?? '', request.title ?? '',
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
if (model.calegModel == null)
Stack( Stack(
children: [ children: [
CircleAvatar( CircleAvatar(
@ -88,15 +90,54 @@ class TambahEditCalegView extends StatelessWidget {
), ),
], ],
), ),
if (model.calegModel != null)
CircleAvatar(
radius: 50,
backgroundColor: fontParagraphColor,
child: ClipRRect(
borderRadius: BorderRadius.circular(50),
child: Image.network(
'${dotenv.env['api_url']}assets/caleg/${model.calegModel!.idCaleg}/${model.calegModel!.foto}',
width: 100,
height: 100,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return const Icon(
Icons.person,
size: 50,
color: greyBlueColor,
);
},
),
),
),
const SizedBox(height: 10), const SizedBox(height: 10),
MyTextFormField( MyTextFormField(
hintText: 'Masukan Nama Caleg', hintText: 'Masukan Nama Caleg',
labelText: 'Nama Caleg', labelText: 'Nama Caleg',
controller: model.namaController, controller: model.namaController,
readOnly: model.calegModel != null,
validator: Validatorless.required( validator: Validatorless.required(
'Nama Caleg tidak boleh kosong'), 'Nama Caleg tidak boleh kosong'),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
MyTextFormField(
hintText: 'Masukan Nomor Urut',
labelText: 'Nomor Urut',
controller: model.nomorUrutController,
readOnly: model.calegModel != null,
keyboardType: TextInputType.number,
validator: Validatorless.multiple(
// numbers only
[
Validatorless.required(
'Nomor Urut tidak boleh kosong'),
Validatorless.number('Nomor Urut harus berupa angka'),
],
),
),
if (model.calegModel == null) const SizedBox(height: 10),
if (model.calegModel == null)
MyTextFormField( MyTextFormField(
hintText: 'Area', hintText: 'Area',
labelText: 'Pencarian Area', labelText: 'Pencarian Area',
@ -137,6 +178,7 @@ class TambahEditCalegView extends StatelessWidget {
value: model.listAreaId.contains( value: model.listAreaId.contains(
model.listAreaModel[index].idArea!), model.listAreaModel[index].idArea!),
onChanged: (value) { onChanged: (value) {
if (model.calegModel != null) return;
model.tambahHapusArea( model.tambahHapusArea(
model.listAreaModel[index].idArea!, model.listAreaModel[index].idArea!,
); );
@ -175,6 +217,7 @@ class TambahEditCalegView extends StatelessWidget {
), ),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
if (model.calegModel == null)
SizedBox( SizedBox(
width: 250, width: 250,
child: MyButton( child: MyButton(
@ -199,11 +242,86 @@ class TambahEditCalegView extends StatelessWidget {
} }
if (model.formKey.currentState!.validate()) { if (model.formKey.currentState!.validate()) {
model.addCaleg(); // remove keyboard focus
FocusScope.of(context).unfocus();
model.dialogService
.showDialog(
title: 'Tambah Caleg',
description:
'Apakah anda yakin ingin menambah caleg ${model.namaController.text} ?',
buttonTitle: 'Ya',
cancelTitle: 'Batal',
)
.then((value) async {
if (value!.confirmed) {
bool res = await model.addCaleg();
model.log
.i('ini di res bagian dalamnya: $res');
if (res) {
// ignore: use_build_context_synchronously
// Navigator.of(context).pop();
completer(DialogResponse(confirmed: true));
}
// if (res.confirmed) {
// await model.addCaleg(completer!);
// completer!(DialogResponse(confirmed: true));
}
});
// bool res = await model.addCaleg();
// model.log.i('ini di res bagian dalamnya: $res');
// if (res) {
// // ignore: use_build_context_synchronously
// Navigator.of(context).pop();
// completer(DialogResponse(confirmed: true));
// }
// if (res.confirmed) {
// await model.addCaleg(completer!);
// completer!(DialogResponse(confirmed: true));
} }
}, },
), ),
), ),
if (model.calegModel != null)
Row(
mainAxisSize: MainAxisSize.min,
children: [
// create rounde icon with one is delete and one is info
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: mainColor.withOpacity(0.5),
borderRadius: BorderRadius.circular(50),
),
child: IconButton(
onPressed: () {},
icon: const Icon(
Icons.info,
color: Colors.white,
),
),
),
const SizedBox(width: 20),
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: dangerColor,
borderRadius: BorderRadius.circular(50),
),
child: IconButton(
onPressed: () async {
completer(DialogResponse(confirmed: true));
},
icon: const Icon(
Icons.delete,
color: Colors.white,
),
),
),
],
)
], ],
), ),
), ),

View File

@ -11,6 +11,7 @@ import '../../../../../app/core/custom_base_view_model.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import '../../../../../model/area_model.dart'; import '../../../../../model/area_model.dart';
import '../../../../../model/caleg_model.dart';
import '../../../../../model/my_response.model.dart'; import '../../../../../model/my_response.model.dart';
class TambahEditCalegViewModel extends CustomBaseViewModel { class TambahEditCalegViewModel extends CustomBaseViewModel {
@ -24,6 +25,7 @@ class TambahEditCalegViewModel extends CustomBaseViewModel {
final formKey = GlobalKey<FormState>(); final formKey = GlobalKey<FormState>();
TextEditingController namaController = TextEditingController(); TextEditingController namaController = TextEditingController();
TextEditingController cariAreaController = TextEditingController(); TextEditingController cariAreaController = TextEditingController();
TextEditingController nomorUrutController = TextEditingController();
List<int> listAreaId = []; List<int> listAreaId = [];
// image picker // image picker
@ -32,9 +34,49 @@ class TambahEditCalegViewModel extends CustomBaseViewModel {
XFile? imageFile; XFile? imageFile;
Uint8List? imageBytes; Uint8List? imageBytes;
Future<void> init() async { CalegModel? calegModel;
Future<void> init(data) async {
globalVar.backPressed = 'exitApp'; globalVar.backPressed = 'exitApp';
await getData(); await getData();
calegModel = data;
if (calegModel != null) {
namaController.text = calegModel!.namaCaleg!;
nomorUrutController.text = calegModel!.nomorUrutCaleg!.toString();
getAreaList(calegModel!.idCaleg!);
// listAreaId = calegModel!.area!.map((e) => e.id!).toList();
}
log.i(data);
}
getAreaList(int idCaleg) async {
log.i('getAreaList');
setBusy(true);
globalVar.backPressed = 'cantBack';
try {
var response = await httpService.get('caleg/relasi_area/$idCaleg');
log.i(response.data);
MyResponseModel myResponseModel = MyResponseModel.fromJson(response.data);
var data = myResponseModel.data['area'];
for (var item in data) {
listAreaId.add(item['id_area']);
}
listAreaModel = [];
for (var item in allListAreaModel) {
if (listAreaId.contains(item.idArea)) {
listAreaModel.add(item);
}
}
log.i('listAreaModel: $listAreaModel');
// log.i('jumlahArea: $jumlahArea');
} catch (e) {
log.e(e);
} finally {
globalVar.backPressed = 'exitApp';
setBusy(false);
}
} }
getData() async { getData() async {
@ -105,17 +147,7 @@ class TambahEditCalegViewModel extends CustomBaseViewModel {
notifyListeners(); notifyListeners();
} }
addCaleg() async { Future<bool> addCaleg() async {
dialogService
.showDialog(
title: 'Tambah Caleg',
description:
'Apakah anda yakin ingin menambahkan Caleg ${namaController.text}?',
buttonTitle: 'Ya',
cancelTitle: 'Tidak',
)
.then((value) async {
if (value!.confirmed) {
log.i('addCaleg'); log.i('addCaleg');
setBusy(true); setBusy(true);
globalVar.backPressed = 'cantBack'; globalVar.backPressed = 'cantBack';
@ -123,6 +155,7 @@ class TambahEditCalegViewModel extends CustomBaseViewModel {
var formData = FormData.fromMap({ var formData = FormData.fromMap({
'nama': namaController.text, 'nama': namaController.text,
'nomor_urut': nomorUrutController.text,
'area': const JsonEncoder().convert(listAreaId), 'area': const JsonEncoder().convert(listAreaId),
'foto': await MultipartFile.fromFile( 'foto': await MultipartFile.fromFile(
imageFile!.path, imageFile!.path,
@ -134,23 +167,20 @@ class TambahEditCalegViewModel extends CustomBaseViewModel {
try { try {
var response = await httpService.postWithFormData('caleg', formData); var response = await httpService.postWithFormData('caleg', formData);
log.i(response.data); log.i(response.data);
await getData(); // await getData();
navigationService.back();
// navigationService.back(); // navigationService.back();
snackbarService.showSnackbar( // navigationService.back();
message: 'Berhasil menambahkan Caleg', // snackbarService.showSnackbar(
duration: const Duration(seconds: 2), // message: 'Berhasil menambahkan Caleg',
); // duration: const Duration(seconds: 2),
// );
// completer(DialogResponse(confirmed: true));
return true;
// navigationService.popRepeated(2); // navigationService.popRepeated(2);
} catch (e) { } catch (e) {
log.e(e); log.e(e);
return false;
// navigationService.back();
snackbarService.showSnackbar(
message: 'Gagal menambahkan Caleg, ${e.toString()}',
duration: const Duration(seconds: 2),
);
} finally { } finally {
easyLoading.dismissLoading(); easyLoading.dismissLoading();
globalVar.backPressed = 'exitApp'; globalVar.backPressed = 'exitApp';
@ -160,6 +190,4 @@ class TambahEditCalegViewModel extends CustomBaseViewModel {
// remove all dialog // remove all dialog
} }
} }
});
}
} }

View File

@ -1,3 +1,4 @@
import 'package:cek_suara/ui/views/admin_index_tracking/admin_first_page/admin_first_page_view.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
@ -28,11 +29,7 @@ class HalamanPengaturanView extends StatelessWidget {
} }
return false; return false;
}, },
child: const Center( child: const AdminFirstPageView(),
child: Text(
'HalamanPengaturanView',
),
),
), ),
); );
}, },

View File

@ -0,0 +1,154 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart';
import 'package:validatorless/validatorless.dart';
import '../../../../../app/themes/app_colors.dart';
import '../../../../widgets/my_button.dart';
import '../../../../widgets/my_textformfield.dart';
import './tambah_detail_tim_survei_view_model.dart';
class TambahDetailTimSurveiView extends StatelessWidget {
final DialogRequest request;
final Function(DialogResponse) completer;
const TambahDetailTimSurveiView({
Key? key,
required this.request,
required this.completer,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ViewModelBuilder<TambahDetailTimSurveiViewModel>.reactive(
viewModelBuilder: () => TambahDetailTimSurveiViewModel(),
onViewModelReady: (TambahDetailTimSurveiViewModel model) async {
await model.init(request.data);
},
builder: (
BuildContext context,
TambahDetailTimSurveiViewModel model,
Widget? child,
) {
return Dialog(
child: Container(
padding: const EdgeInsets.all(15),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: backgroundColor,
),
child: SingleChildScrollView(
child: Form(
key: model.formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
request.title ?? '',
),
const SizedBox(height: 15),
MyTextFormField(
hintText: 'NIK Tim Survei',
labelText: 'NIK Tim Survei',
keyboardType: TextInputType.number,
controller: model.nikController,
readOnly: model.timSurveiModel != null,
maxLength: 16,
validator: Validatorless.multiple(
[
Validatorless.required(
'NIK Tim Survei tidak boleh kosong'),
Validatorless.min(
16, 'NIK Tim Survei harus 16 digit'),
Validatorless.number(
'NIK Tim Survei harus berupa angka')
],
),
),
const SizedBox(height: 15),
MyTextFormField(
hintText: 'Nama Tim Survei',
labelText: 'Nama Tim Survei',
readOnly: model.timSurveiModel != null,
controller: model.namaController,
validator: Validatorless.required(
'Nama Tim Survei tidak boleh kosong'),
),
const SizedBox(height: 20),
if (model.timSurveiModel == null)
SizedBox(
width: 200,
child: MyButton(
text: 'Tambah',
onPressed: () {
if (model.formKey.currentState!.validate()) {
model.dialogService
.showConfirmationDialog(
title: 'Tambah Tim Survei',
description:
'Apakah anda yakin ingin menambahkan Tim Survei ${model.namaController.text} ?',
confirmationTitle: 'Ya',
cancelTitle: 'Tidak',
)
.then((value) async {
if (value!.confirmed) {
bool res = await model.tambahTimSurvei();
if (res) {
completer(DialogResponse(confirmed: true));
}
}
});
}
},
),
),
if (model.timSurveiModel != null)
Row(
mainAxisSize: MainAxisSize.min,
children: [
// create rounde icon with one is delete and one is info
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: mainColor.withOpacity(0.5),
borderRadius: BorderRadius.circular(50),
),
child: IconButton(
onPressed: () {},
icon: const Icon(
Icons.list_alt_outlined,
color: Colors.white,
),
),
),
const SizedBox(width: 20),
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: dangerColor,
borderRadius: BorderRadius.circular(50),
),
child: IconButton(
onPressed: () async {
completer(DialogResponse(confirmed: true));
},
icon: const Icon(
Icons.delete,
color: Colors.white,
),
),
),
],
)
],
),
),
),
),
);
},
);
}
}

View File

@ -0,0 +1,54 @@
import 'package:cek_suara/model/tim_survei_model.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import '../../../../../app/app.logger.dart';
import '../../../../../app/core/custom_base_view_model.dart';
class TambahDetailTimSurveiViewModel extends CustomBaseViewModel {
final log = getLogger('TambahDetailTimSurveiViewModel');
// form variabel
final formKey = GlobalKey<FormState>();
TextEditingController nikController = TextEditingController();
TextEditingController namaController = TextEditingController();
TimSurveiModel? timSurveiModel;
Future<void> init(data) async {
globalVar.backPressed = 'exitApp';
timSurveiModel = data;
if (timSurveiModel != null) {
nikController.text = timSurveiModel!.nik!;
namaController.text = timSurveiModel!.nama!;
}
}
Future<bool> tambahTimSurvei() async {
globalVar.backPressed = 'cantBack';
setBusy(true);
easyLoading.customLoading('Menambahkan Tim Survei...');
try {
var formData = FormData.fromMap({
'nik': nikController.text,
'nama': namaController.text,
});
var response = await httpService.postWithFormData('survei', formData);
log.i(response.data);
return true;
} catch (e) {
// log.e(e);
// snackbarService.showSnackbar(
// message: 'Gagal menambahkan Tim Survei\n${e.res}',
// title: 'Error',
// duration: const Duration(seconds: 2),
// );
return false;
} finally {
globalVar.backPressed = 'exitApp';
easyLoading.dismissLoading();
setBusy(false);
}
}
}

View File

@ -1,6 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import '../../../../app/themes/app_colors.dart';
import '../../../widgets/top_container.dart';
import './tim_survei_view_model.dart'; import './tim_survei_view_model.dart';
class TimSurveiView extends StatelessWidget { class TimSurveiView extends StatelessWidget {
@ -28,11 +30,121 @@ class TimSurveiView extends StatelessWidget {
} }
return false; return false;
}, },
child: const Center( child: SafeArea(
child: Container(
height: MediaQuery.of(context).size.height,
padding: const EdgeInsets.all(20),
child: Column(
children: [
TopContainer(
title: 'Tim\nSurvei',
value: '${model.jumlahTimSurvei} Orang',
icon: Icons.people_alt_outlined,
background: orangeColor,
),
const SizedBox(height: 15),
Expanded(
child: Container(
alignment: model.isBusy
? Alignment.center
: (model.listTimSurveiModel.isNotEmpty
? null
: Alignment.center),
width: double.infinity,
height: double.infinity,
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(10),
color: warningColor,
),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (model.isBusy)
const Center(
child: LinearProgressIndicator(
minHeight: 5,
color: mainColor,
),
),
if (!model.isBusy &&
model.listTimSurveiModel.isNotEmpty)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// create 10 list of survei person using card
for (int i = 0;
i < model.jumlahTimSurvei;
i++)
Card(
child: ListTile(
leading: Text('${i + 1}'),
title: Text(
model.listTimSurveiModel[i].nama!,
),
subtitle: Text(
model.listTimSurveiModel[i].nik!,
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
// one info and one delete button
IconButton(
onPressed: () {
model.showDetailTimSurvei(
model.listTimSurveiModel[
i]);
},
icon: const Icon(
Icons.info_outline,
color: mainColor,
),
),
IconButton(
onPressed: () {
model.deleteTimSurvei(model
.listTimSurveiModel[i]);
},
icon: const Icon(
Icons.delete_outline,
color: Colors.red,
),
),
],
)),
),
],
),
// if listTimSurveiModel is empty
if (!model.isBusy &&
model.listTimSurveiModel.isEmpty)
Center(
child: Text( child: Text(
'TimSurveiView', model.status == true
? 'Data Tim Survei Kosong\n'
'Silahkan Tambahkan Tim Survei Baru'
: 'Gagal Mengambil Data Tim Survei',
textAlign: TextAlign.center,
), ),
), ),
],
),
),
),
)
],
),
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
model.addTimSurvei();
},
child: const Icon(Icons.add),
), ),
); );
}, },

View File

@ -1,7 +1,96 @@
import '../../../../app/app.dialogs.dart';
import '../../../../app/app.logger.dart';
import '../../../../app/core/custom_base_view_model.dart'; import '../../../../app/core/custom_base_view_model.dart';
import '../../../../app/themes/app_colors.dart';
import '../../../../model/my_response.model.dart';
import '../../../../model/tim_survei_model.dart';
class TimSurveiViewModel extends CustomBaseViewModel { class TimSurveiViewModel extends CustomBaseViewModel {
final log = getLogger('TimSurveiViewModel');
// variabel
List<TimSurveiModel> listTimSurveiModel = [];
int jumlahTimSurvei = 0;
bool status = false;
Future<void> init() async { Future<void> init() async {
globalVar.backPressed = 'exitApp'; globalVar.backPressed = 'exitApp';
await getData();
}
getData() async {
setBusy(true);
// easyLoading.showLoading();
// globalVar.backPressed = 'cantBack';
try {
var response = await httpService.get('survei');
MyResponseModel myResponseModel = MyResponseModel.fromJson(response.data);
TimSurveiListModel timSurveiListModel =
TimSurveiListModel.fromJson(myResponseModel.data);
listTimSurveiModel = timSurveiListModel.survei!;
jumlahTimSurvei = timSurveiListModel.jumlah!;
log.i('listTimSurveiModel: $listTimSurveiModel');
log.i('jumlahTimSurvei: $jumlahTimSurvei');
status = true;
} catch (e) {
log.e(e.toString());
status = false;
} finally {
setBusy(false);
// globalVar.backPressed = 'exitApp';
// easyLoading.dismissLoading();
}
}
addTimSurvei() async {
var res = await dialogService.showCustomDialog(
variant: DialogType.tambahDetailTimSurveiView,
title: 'Tambah Tim Survei',
);
if (res!.confirmed) {
await getData();
snackbarService.showSnackbar(
message: 'Berhasil menambahkan Tim Survei\nPassword default: 12345678',
title: 'Sukses',
duration: const Duration(seconds: 2),
);
}
}
deleteTimSurvei(TimSurveiModel listTimSurveiModel) async {
dialogService
.showDialog(
title: 'Hapus Tim Survei',
description:
'Apakah anda yakin ingin menghapus ${listTimSurveiModel.nama} ?',
buttonTitle: 'Hapus',
cancelTitle: 'Batal',
buttonTitleColor: dangerColor,
cancelTitleColor: mainColor,
)
.then((value) async {
if (value!.confirmed) {
await httpService.delete('survei/${listTimSurveiModel.nik}');
await getData();
snackbarService.showSnackbar(
message: 'Berhasil menghapus ${listTimSurveiModel.nama}',
title: 'Sukses',
duration: const Duration(seconds: 2),
);
}
});
}
showDetailTimSurvei(TimSurveiModel listTimSurveiModel) async {
var res = await dialogService.showCustomDialog(
variant: DialogType.tambahDetailTimSurveiView,
title: 'Detail Tim Survei',
data: listTimSurveiModel,
);
if (res!.confirmed) {
deleteTimSurvei(listTimSurveiModel);
}
} }
} }

View File

@ -15,6 +15,10 @@ class MyTextFormField extends StatelessWidget {
this.controller, this.controller,
this.maxLines = 1, this.maxLines = 1,
this.onEditingComplete, this.onEditingComplete,
this.keyboardType = TextInputType.text,
this.initialValue,
this.readOnly = false,
this.maxLength,
}) : super(key: key); }) : super(key: key);
final String? labelText; final String? labelText;
@ -27,15 +31,23 @@ class MyTextFormField extends StatelessWidget {
final TextEditingController? controller; final TextEditingController? controller;
final int maxLines; final int maxLines;
final VoidCallback? onEditingComplete; final VoidCallback? onEditingComplete;
final TextInputType keyboardType;
final String? initialValue;
final bool readOnly;
final int? maxLength;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return TextFormField( return TextFormField(
maxLength: maxLength,
readOnly: readOnly,
initialValue: initialValue,
onEditingComplete: onEditingComplete, onEditingComplete: onEditingComplete,
maxLines: maxLines, maxLines: maxLines,
controller: controller, controller: controller,
focusNode: focusNode, focusNode: focusNode,
obscureText: obscureText ?? false, obscureText: obscureText ?? false,
keyboardType: keyboardType,
decoration: InputDecoration( decoration: InputDecoration(
prefixIcon: prefixIcon, prefixIcon: prefixIcon,
suffixIcon: suffixIcon, suffixIcon: suffixIcon,

View File

@ -456,6 +456,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.2.1+1" version: "0.2.1+1"
intl:
dependency: "direct main"
description:
name: intl
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
url: "https://pub.dev"
source: hosted
version: "0.18.1"
io: io:
dependency: transitive dependency: transitive
description: description:

View File

@ -50,6 +50,7 @@ dependencies:
stylish_bottom_bar: stylish_bottom_bar:
validatorless: ^1.2.3 validatorless: ^1.2.3
http_parser: http_parser:
intl:
dev_dependencies: dev_dependencies: