From d35ca7ab8b8396b735bc447f13a90022901dc881 Mon Sep 17 00:00:00 2001 From: Sas Andy Date: Thu, 22 Feb 2024 10:36:22 +0700 Subject: [PATCH 1/4] Revisi constant & model --- images/btn_approval.png | Bin 0 -> 2416 bytes lib/app/constant.dart | 43 +++++++++++++ lib/model/approval_detail_model.dart | 56 +++++++++++++++++ lib/model/approval_model.dart | 68 +++++++++++++++++++++ lib/model/approval_type_model.dart | 24 ++++++++ lib/provider/approval_filter_provider.dart | 28 +++++++++ 6 files changed, 219 insertions(+) create mode 100644 images/btn_approval.png create mode 100644 lib/model/approval_detail_model.dart create mode 100644 lib/model/approval_model.dart create mode 100644 lib/model/approval_type_model.dart create mode 100644 lib/provider/approval_filter_provider.dart diff --git a/images/btn_approval.png b/images/btn_approval.png new file mode 100644 index 0000000000000000000000000000000000000000..5120d10aba6a085f37aac26c6c56e8173912812b GIT binary patch literal 2416 zcmV-$36J)PP)Nlg&42#NPjG+++bP$<$BCUHTuYkFsU6Sch!6qu$I^bDVJwi;xB8YZTw~jY+lTV}#$Clr=+i|$C$Zkc8@qVjyWW`{Hj=^J~ z$08z;EQH>Gfe)Ll(#H$Ga9lC0wzwF+J{DS#N(>JoiV{kYN{kgm5GC+{YClK*Fh(I4~U|9^FQB?SY$Lb9&ito_iTM+g3_OVKg5FDd1BBDpwUZuIuugb$MIJt*y;!R+Rw8Xqm!;rGmJ@XMY_P$-uKN zj{v{iLfASglEovyF9=duM@n#jzlnu4q(uve^MMj>}8Hbd)h_D{o*|E70x z7D6a2fGZ5K(MXeLV3@6AO8AtB&$f3{k$rft5@I zG5ck$S><1p&DN|S`l5w$mr3$13XQ$Kl@DVJxs9W6kz2k+`K6naxPsh!tcIgOenr`W z_E4@MYH*Y;1I?!>W=qF#scZ8`ugso5Q8L@`XNI2tHJNH_5m9PH|^o!U7Rkrt$cJW&>x^`AK_DIPXe^iUp7k(E%jco26@ zEIl@kOA|Kltb?*}3PSx4qL0AjVLioEG|P&s;9U`&oG zuKvC$n;3M@($wP&9dnh|OwORJ(1MjvHye+k^bF2W`g@Js*;UU&awa^LDb_xx*ZBv~@6hW!iyV`^g=isYkd*qn&n4U! z=)d_zghAL8^zBcV{wyaJBGhj4WM*;1y~^aZ18eZlU9y%d2i78cx9Lyu%0b#R*uOY< zCj0l79+d|{Y>*HgWJ7o{7|J>5Tz(CWJj`N>lPuPyBl#1uAiNl3Mxv0*bJlK*W@*J{ zzwMYVErNS=-YHHgx!olxC*NP=O@3>9}}(> zzJ@i%!BA4A*G8I1c^+Y4F&yKHP8jXM^bId9tcky&4AAsL;(p58p$c3LddiF# zw=ik7bu1re=;>`p@7AU!j1xQ_ctrxZb*rFa+=U;6a}D`LZ$0Y@vV#ySI-&V|?)j+& zQG!~AT5(YW*RW)tZ+8ejp-77jceblt7H{6D-l8~?MrQ&cD5F;M%dD;_!~IEpDu|on zN-!sdazGZKo=4H(`0+c}OHBk2oy2x)o?)n7_6W2SO9@)%QpTuk;^fx?4rACpnCVUg z`AJG{OV-*@;w6utvM8VE5C5f=7hG3IIS2~6n%J0ZU49^lc$)JOBc0%G;|Ba+%;t>_ z{-d=KDWY8P_9n7|-PO}rVj+z6FhwgF&8WgMo{8z#qu9YWi-tn+d-)f#f*Z(8F)PTQ z(bY}0(qMX;8%shf@k{q@QtzHElSSR0j8OheY; z(b~ehRPqN>ur9K>0HVxruMrRHU_1rU>m-kNJWs3$2{#oxr18t4#YFKl?CS0q5uYiO2Go6P) zdm9EGRQ(H{>9nhv*CDuKglaTdwats0i5Q_=d(~JnP9loIL>fr_i^)$`fgWxEcVkAR zv54NgmHi~oB#yKwln%tyoH*8g>X|J6W(_5M_!VZh3y(tl4er0fI=*g}zrnqpWp;dL z;C_i+5Ju-J+#jQ?%fM8%XUSuZ7G`fF=#a%^P>Gv?{Sq_atk8j7Az6w1nav{hxx<1Cu0q&8*P%SfdfmndL zz87Z5xALVG8~lL9FLyt7jYjo5jdgR?I iw&?LG46<@Xe)$mrv7~{R5h1+*0000 json) { + id = json['id']; + name = json['Name']; + nip = json['nip']; + date = json['date']; + time = json['time']; + address = json['address']; + lat = json['lat']; + long = json['long']; + imagePath = json['imagePath']; + statusID = json['statusID']; + statusName = json['statusName']; + } + + Map toJson() { + final Map data = new Map(); + data['id'] = this.id; + data['Name'] = this.name; + data['nip'] = this.nip; + data['date'] = this.date; + data['time'] = this.time; + data['address'] = this.address; + data['lat'] = this.lat; + data['long'] = this.long; + data['imagePath'] = this.imagePath; + data['statusID'] = this.statusID; + data['statusName'] = this.statusName; + return data; + } +} diff --git a/lib/model/approval_model.dart b/lib/model/approval_model.dart new file mode 100644 index 0000000..c01bbfc --- /dev/null +++ b/lib/model/approval_model.dart @@ -0,0 +1,68 @@ +class ApprovalModel { + String? id; + String? name; + String? date; + String? time; + String? typename; + String? typeID; + String? presensiTypeName; + String? presensitypeID; + String? address; + String? reasonType; + String? reasontypeID; + String? reasonDescription; + String? statusID; + String? statusName; + + ApprovalModel( + {this.id, + this.name, + this.date, + this.time, + this.typename, + this.typeID, + this.presensiTypeName, + this.presensitypeID, + this.address, + this.reasonType, + this.reasontypeID, + this.reasonDescription, + this.statusID, + this.statusName}); + + ApprovalModel.fromJson(Map json) { + id = json['id']; + name = json['name']; + date = json['date']; + time = json['time']; + typename = json['typename']; + typeID = json['typeID']; + presensiTypeName = json['presensiTypeName']; + presensitypeID = json['presensitypeID']; + address = json['address']; + reasonType = json['reasonType']; + reasontypeID = json['reasontypeID']; + reasonDescription = json['reasonDescription']; + statusID = json['statusID']; + statusName = json['statusName']; + } + + Map toJson() { + final Map data = new Map(); + data['id'] = this.id; + data['name'] = this.name; + data['date'] = this.date; + data['time'] = this.time; + data['typename'] = this.typename; + data['typeID'] = this.typeID; + data['presensiTypeName'] = this.presensiTypeName; + data['presensitypeID'] = this.presensitypeID; + data['address'] = this.address; + data['reasonType'] = this.reasonType; + data['reasontypeID'] = this.reasontypeID; + data['reasonDescription'] = this.reasonDescription; + data['statusID'] = this.statusID; + data['statusName'] = this.statusName; + return data; + } +} diff --git a/lib/model/approval_type_model.dart b/lib/model/approval_type_model.dart new file mode 100644 index 0000000..bdd8366 --- /dev/null +++ b/lib/model/approval_type_model.dart @@ -0,0 +1,24 @@ +class ApprovalTypeModel { + String? id; + String? value; + String? name; + bool? isActive; + + ApprovalTypeModel({this.id, this.value, this.name, this.isActive}); + + ApprovalTypeModel.fromJson(Map json) { + id = json['id']; + value = json['value']; + name = json['name']; + isActive = json['isActive']; + } + + Map toJson() { + final Map data = new Map(); + data['id'] = this.id; + data['value'] = this.value; + data['name'] = this.name; + data['isActive'] = this.isActive; + return data; + } +} diff --git a/lib/provider/approval_filter_provider.dart b/lib/provider/approval_filter_provider.dart new file mode 100644 index 0000000..d767f7e --- /dev/null +++ b/lib/provider/approval_filter_provider.dart @@ -0,0 +1,28 @@ +import 'package:absensi_sas/model/approval_type_model.dart'; + +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +class ApprovalFilterProvider { + DateTime startDate = DateTime.now(); + DateTime endDate = DateTime.now(); + String keyword = ""; + List selectedApprovalType = []; + final approvalTypeList = [ + ApprovalTypeModel(id: "1", name: "Presensi", isActive: false, value: "p"), + ApprovalTypeModel(id: "2", name: "Cuti", isActive: true, value: "c"), + ApprovalTypeModel(id: "3", name: "Lembur", isActive: false, value: "l"), + ]; + ApprovalFilterProvider( + {required this.endDate, + required this.keyword, + required this.selectedApprovalType, + required this.startDate}); +} + +final approvalFilterProvider = StateProvider((ref) { + return ApprovalFilterProvider( + selectedApprovalType: [], + endDate: DateTime.now(), + keyword: "", + startDate: DateTime.now()); +}); From c23e68de36d5d6ac68b7660c2f5227ce0f0fab92 Mon Sep 17 00:00:00 2001 From: Sas Andy Date: Thu, 22 Feb 2024 10:37:01 +0700 Subject: [PATCH 2/4] theme & package --- lib/main.dart | 17 +++++++++ pubspec.lock | 98 ++++++++++++++++++++++++++++++++++++++++++--------- pubspec.yaml | 3 ++ 3 files changed, 101 insertions(+), 17 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index f7f52e3..50a7412 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,4 @@ +import 'package:absensi_sas/app/constant.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -43,7 +44,23 @@ class MyApp extends StatelessWidget { // ), theme: ThemeData( primarySwatch: Colors.orange, + primaryColor: Constant.textOrange, + primaryColorDark: Constant.textOrange, + primaryColorLight: Constant.textOrange, + colorScheme: Theme.of(context).colorScheme.copyWith( + primary: Constant.textOrange, + background: Colors.white, + surface: Colors.white), + // datePickerTheme: DatePickerThemeData( + // // backgroundColor: Colors.white, + // // headerHeadlineStyle: + // // Constant.h3_400(context: context).copyWith(color: Colors.white), + // headerBackgroundColor: Constant.textOrange, + // headerForegroundColor: Colors.white, + // surfaceTintColor: Colors.white, + // backgroundColor: Colors.white), ), + // home: TestMap(), initialRoute: loginRoute, // initialRoute: presensiSelfieRoute, diff --git a/pubspec.lock b/pubspec.lock index c49342e..27fcba8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -93,10 +93,18 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.18.0" + colorful_iconify_flutter: + dependency: transitive + description: + name: colorful_iconify_flutter + sha256: "5ebc63d10c3e97279849162b415fda8e71b7ff24ddd663539d7328279dae9c1d" + url: "https://pub.dev" + source: hosted + version: "0.0.2" convert: dependency: transitive description: @@ -161,6 +169,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.5" + eva_icons_flutter: + dependency: "direct main" + description: + name: eva_icons_flutter + sha256: "6d48a10b93590ab83eb092bee5adacdeb14f3d83f527a4b9e4092c363d56e2a8" + url: "https://pub.dev" + source: hosted + version: "3.1.0" fake_async: dependency: transitive description: @@ -225,6 +241,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.9.3+1" + fluentui_system_icons: + dependency: "direct main" + description: + name: fluentui_system_icons + sha256: abe7c343e2151e0ad6544653e0b6601686b993bc436ccde72b88cea677db0c0a + url: "https://pub.dev" + source: hosted + version: "1.1.226" flutter: dependency: "direct main" description: flutter @@ -278,6 +302,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + flutter_svg: + dependency: transitive + description: + name: flutter_svg + sha256: "6ff9fa12892ae074092de2fa6a9938fb21dbabfdaa2ff57dc697ff912fc8d4b2" + url: "https://pub.dev" + source: hosted + version: "1.1.6" flutter_test: dependency: "direct dev" description: flutter @@ -528,6 +560,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" + iconify_flutter: + dependency: "direct main" + description: + name: iconify_flutter + sha256: a2d5d36a8381331c8d4e0a6b71378fca51bd6d15e38192568a30c0a91133a9af + url: "https://pub.dev" + source: hosted + version: "0.0.5" image: dependency: transitive description: @@ -660,26 +700,26 @@ packages: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" mgrs_dart: dependency: transitive description: @@ -736,6 +776,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.8.3" + path_drawing: + dependency: transitive + description: + name: path_drawing + sha256: bbb1934c0cbb03091af082a6389ca2080345291ef07a5fa6d6e078ba8682f977 + url: "https://pub.dev" + source: hosted + version: "1.0.1" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf + url: "https://pub.dev" + source: hosted + version: "1.0.1" path_provider: dependency: "direct main" description: @@ -977,10 +1033,10 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" sprintf: dependency: transitive description: @@ -993,10 +1049,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" state_notifier: dependency: transitive description: @@ -1009,10 +1065,10 @@ packages: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" string_scanner: dependency: transitive description: @@ -1033,10 +1089,10 @@ packages: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.6.1" top_snackbar_flutter: dependency: "direct main" description: @@ -1125,6 +1181,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + url: "https://pub.dev" + source: hosted + version: "0.3.0" web_socket_channel: dependency: transitive description: @@ -1174,5 +1238,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.0.6 <4.0.0" + dart: ">=3.2.0-194.0.dev <4.0.0" flutter: ">=3.10.0" diff --git a/pubspec.yaml b/pubspec.yaml index 0e3c512..f97d3bf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -61,6 +61,9 @@ dependencies: photo_view: ^0.14.0 mime: ^1.0.4 flutter_launcher_icons: ^0.13.1 + iconify_flutter: ^0.0.5 + fluentui_system_icons: ^1.1.226 + eva_icons_flutter: ^3.1.0 dev_dependencies: flutter_test: From 3790e9e11acc6ec42e2abf135ad8803022fab208 Mon Sep 17 00:00:00 2001 From: Sas Andy Date: Thu, 22 Feb 2024 10:37:29 +0700 Subject: [PATCH 3/4] approval menu --- lib/screen/home/home_screen.dart | 68 +++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/lib/screen/home/home_screen.dart b/lib/screen/home/home_screen.dart index 49d9ce8..0de1ad3 100644 --- a/lib/screen/home/home_screen.dart +++ b/lib/screen/home/home_screen.dart @@ -7,6 +7,7 @@ import 'package:geolocator/geolocator.dart'; import 'package:google_sign_in/google_sign_in.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:permission_handler/permission_handler.dart'; +import 'package:fluentui_system_icons/fluentui_system_icons.dart'; import '../../app/route.dart'; import '../../provider/current_check_distance_provider.dart'; @@ -720,6 +721,69 @@ class HomeScreen extends HookConsumerWidget { ), ), ), + SizedBox( + width: Constant.getActualXPhone( + context: context, x: 18), + ), + + //Menu + Container( + width: Constant.getActualXPhone( + context: context, x: 98), + // color: Colors.amber, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Color.fromRGBO(145, 158, 171, 0.20), + ), + ], + ), + child: Padding( + padding: EdgeInsets.only( + left: Constant.getActualXPhone( + context: context, x: 12), + right: Constant.getActualXPhone( + context: context, x: 12), + top: Constant.getActualYPhone( + context: context, y: 8), + bottom: Constant.getActualYPhone( + context: context, y: 8), + ), + child: InkWell( + onTap: () { + Navigator.pushNamed(context, approvalRoute); + }, + child: Column( + children: [ + Container( + child: Image( + width: Constant.getActualXPhone( + context: context, x: 50), + height: Constant.getActualYPhone( + context: context, y: 50), + image: AssetImage( + 'images/btn_approval.png'), + ), + ), + SizedBox( + height: Constant.getActualYPhone( + context: context, y: 8), + ), + Text( + 'Approval', + style: Constant.titleH2_600_14( + context: context) + .copyWith( + color: Constant.textDarkGrey, + ), + ), + ], + ), + ), + ), + ), ], ), ), @@ -790,7 +854,9 @@ class HomeScreen extends HookConsumerWidget { children: [ Text( // '24 hari', - varCurrentRekapKehadiranProvider?.kehadiran ?? "", + varCurrentRekapKehadiranProvider + ?.kehadiran ?? + "", style: Constant.subtitle_600_14( context: context) .copyWith( From eb7236671eba6d3a0d4354100619e75c4c712e67 Mon Sep 17 00:00:00 2001 From: Sas Andy Date: Thu, 22 Feb 2024 10:38:38 +0700 Subject: [PATCH 4/4] route , approval screen & approval detail --- lib/app/route.dart | 24 + lib/screen/approval/approval_mockup.dart | 158 ++++++ lib/screen/approval/approval_screen.dart | 159 ++++++ .../approval/widget/approval_card_cuti.dart | 200 +++++++ .../approval/widget/approval_card_lembur.dart | 200 +++++++ .../widget/approval_card_presensi.dart | 203 +++++++ .../widget/approval_card_status_widget.dart | 52 ++ .../widget/approval_dialog_confirmation.dart | 75 +++ .../widget/approval_filter_chip_widget.dart | 71 +++ .../widget/approval_filter_widget.dart | 69 +++ .../approval/widget/filter_bottom_sheet.dart | 534 ++++++++++++++++++ .../approval_detail_screen.dart | 368 ++++++++++++ lib/widget/custom_button_approval.dart | 77 +++ 13 files changed, 2190 insertions(+) create mode 100644 lib/screen/approval/approval_mockup.dart create mode 100644 lib/screen/approval/approval_screen.dart create mode 100644 lib/screen/approval/widget/approval_card_cuti.dart create mode 100644 lib/screen/approval/widget/approval_card_lembur.dart create mode 100644 lib/screen/approval/widget/approval_card_presensi.dart create mode 100644 lib/screen/approval/widget/approval_card_status_widget.dart create mode 100644 lib/screen/approval/widget/approval_dialog_confirmation.dart create mode 100644 lib/screen/approval/widget/approval_filter_chip_widget.dart create mode 100644 lib/screen/approval/widget/approval_filter_widget.dart create mode 100644 lib/screen/approval/widget/filter_bottom_sheet.dart create mode 100644 lib/screen/approval_detail/approval_detail_screen.dart create mode 100644 lib/widget/custom_button_approval.dart diff --git a/lib/app/route.dart b/lib/app/route.dart index 5cf9620..4b49499 100644 --- a/lib/app/route.dart +++ b/lib/app/route.dart @@ -1,3 +1,5 @@ +import 'package:absensi_sas/screen/approval/approval_screen.dart'; +import 'package:absensi_sas/screen/approval_detail/approval_detail_screen.dart'; import 'package:absensi_sas/screen/home/home_screen_v1.dart'; import 'package:absensi_sas/screen/presensi/presensi_screen.dart'; import 'package:absensi_sas/screen/presensi/presensi_selfie_screen.dart'; @@ -13,6 +15,8 @@ const testFlutterMapRoute = "/testFlutterMapRoute"; const homeRoute = "/homeRoute"; const presensiRoute = "/presensiRoute"; const presensiSelfieRoute = "/presensiSelfieRoute"; +const approvalRoute = "/approvalRoute"; +const approvalDetailRoute = "/approvalDetailRoute"; class AppRoute { static Route generateRoute(RouteSettings settings) { @@ -81,6 +85,26 @@ class AppRoute { ); }); } + // approval + if (settings.name == approvalRoute) { + return MaterialPageRoute(builder: (context) { + return MediaQuery( + data: MediaQuery.of(context) + .copyWith(textScaleFactor: 1.0, padding: EdgeInsets.all(0)), + child: ApprovalScreen(), + ); + }); + } + // approvalDetail + if (settings.name == approvalDetailRoute) { + return MaterialPageRoute(builder: (context) { + return MediaQuery( + data: MediaQuery.of(context) + .copyWith(textScaleFactor: 1.0, padding: EdgeInsets.all(0)), + child: ApprovalDetailScreen(), + ); + }); + } return MaterialPageRoute(builder: (context) { return MediaQuery( diff --git a/lib/screen/approval/approval_mockup.dart b/lib/screen/approval/approval_mockup.dart new file mode 100644 index 0000000..cf5a1b5 --- /dev/null +++ b/lib/screen/approval/approval_mockup.dart @@ -0,0 +1,158 @@ +import 'package:absensi_sas/model/approval_detail_model.dart'; +import 'package:absensi_sas/model/approval_model.dart'; + +List approvalCardList = [ + ApprovalModel( + id: "1", + name: "Alfianto Andy P", + date: "21 Feb 2024", + time: "08:03", + typeID: "1", + typename: "Presensi", + presensitypeID: "1", + presensiTypeName: "Clock In", + address: + "Jl. Kwini No.1, RT.5/RW.1, Senen, Kec. Senen, Kota Jakarta Pusat, Daerah Khusus Ibukota Jakarta 10410", + statusID: "", + statusName: ""), + ApprovalModel( + id: "1", + name: "Alfianto Andy P", + date: "21 Feb 2024", + time: "08:03", + typeID: "1", + typename: "Presensi", + presensitypeID: "1", + presensiTypeName: "Clock In", + address: + "Jl. Kwini No.1, RT.5/RW.1, Senen, Kec. Senen, Kota Jakarta Pusat, Daerah Khusus Ibukota Jakarta 10410", + statusID: "1", + statusName: "Approve"), + ApprovalModel( + id: "2", + name: "Alfianto Andy P", + date: "21 Feb 2024", + time: "17:03", + typeID: "1", + typename: "Presensi", + presensitypeID: "2", + presensiTypeName: "Clock Out", + address: + "Jl. Kwini No.1, RT.5/RW.1, Senen, Kec. Senen, Kota Jakarta Pusat, Daerah Khusus Ibukota Jakarta 10410", + statusID: "2", + statusName: "Reject"), + ApprovalModel( + id: "3", + name: "Hanan Askarim", + date: "06 Jan 2024 - 06 Jan 2024", + time: "0", + typeID: "2", + typename: "Cuti", + statusID: "0", + reasonType: "Cuti Tahunan", + reasonDescription: "Kondangan", + reasontypeID: "1", + statusName: "0"), + ApprovalModel( + id: "4", + name: "Hanan Askarim", + date: "06 Jan 2024 - 06 Jan 2024", + time: "0", + typeID: "2", + typename: "Cuti", + statusID: "1", + reasonType: "Cuti Tahunan", + reasonDescription: "Tidur", + reasontypeID: "1", + statusName: "Approve"), + ApprovalModel( + id: "4", + name: "Hanan Askarim", + date: "06 Jan 2024 - 06 Jan 2024", + time: "0", + typeID: "2", + typename: "Cuti", + statusID: "2", + reasonType: "Cuti Tahunan", + reasonDescription: "Tidur", + reasontypeID: "1", + statusName: "Reject"), + ApprovalModel( + id: "5", + name: "Stephen Kusumo", + date: "06 Jan 2024", + time: "17:00 - 19:00", + typeID: "3", + typename: "Lembur", + statusID: "0", + reasonType: "0", + reasonDescription: "Menyelesaikan project Petty Cash", + reasontypeID: "0", + statusName: ""), + ApprovalModel( + id: "5", + name: "Stephen Kusumo", + date: "06 Jan 2024", + time: "17:00 - 19:00", + typeID: "3", + typename: "Lembur", + statusID: "1", + reasonType: "0", + reasonDescription: "Menyelesaikan project Petty Cash", + reasontypeID: "0", + statusName: "Approve"), + ApprovalModel( + id: "5", + name: "Stephen Kusumo", + date: "06 Jan 2024", + time: "17:00 - 19:00", + typeID: "3", + typename: "Lembur", + statusID: "2", + reasonType: "0", + reasonDescription: "Menyelesaikan project Petty Cash", + reasontypeID: "0", + statusName: "Reject"), +]; + +List approvalDetailMockup = [ + ApprovalDetailModel( + id: "1", + name: "Alfianto Andy P", + address: + "Jl. Kwini No.1, RT.5/RW.1, Senen, Kec. Senen, Kota Jakarta Pusat, Daerah Khusus Ibukota Jakarta 10410", + date: "06 Feb 2024", + time: "08:08", + nip: "SS202308", + lat: -7.539538, + long: 110.798357, + imagePath: "https://images4.alphacoders.com/262/262196.jpg", + statusID: "1", + statusName: "Approve"), + ApprovalDetailModel( + id: "2", + name: "Alfianto Andy P", + address: + "Jl. Kwini No.1, RT.5/RW.1, Senen, Kec. Senen, Kota Jakarta Pusat, Daerah Khusus Ibukota Jakarta 10410", + date: "06 Feb 2024", + time: "08:08", + nip: "SS202308", + lat: -7.539538, + long: 110.798357, + imagePath: "https://images3.alphacoders.com/147/147465.jpg", + statusID: "2", + statusName: "Reject"), + ApprovalDetailModel( + id: "3", + name: "Alfianto Andy P", + address: + "Jl. Kwini No.1, RT.5/RW.1, Senen, Kec. Senen, Kota Jakarta Pusat, Daerah Khusus Ibukota Jakarta 10410", + date: "06 Feb 2024", + time: "08:08", + nip: "SS202308", + lat: -7.539538, + long: 110.798357, + imagePath: "https://images4.alphacoders.com/146/146466.jpg", + statusID: "0", + statusName: ""), +]; diff --git a/lib/screen/approval/approval_screen.dart b/lib/screen/approval/approval_screen.dart new file mode 100644 index 0000000..b054dd8 --- /dev/null +++ b/lib/screen/approval/approval_screen.dart @@ -0,0 +1,159 @@ +import 'package:absensi_sas/app/constant.dart'; +import 'package:absensi_sas/app/route.dart'; +import 'package:absensi_sas/model/approval_model.dart'; + +import 'package:absensi_sas/provider/approval_filter_provider.dart'; +import 'package:absensi_sas/screen/approval/approval_mockup.dart'; +import 'package:absensi_sas/screen/approval/widget/approval_card_cuti.dart'; +import 'package:absensi_sas/screen/approval/widget/approval_card_lembur.dart'; +import 'package:absensi_sas/screen/approval/widget/approval_card_presensi.dart'; +import 'package:absensi_sas/screen/approval/widget/approval_filter_chip_widget.dart'; +import 'package:absensi_sas/screen/approval/widget/approval_filter_widget.dart'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +class ApprovalScreen extends HookConsumerWidget { + const ApprovalScreen({super.key}); + @override + Widget build(BuildContext context, WidgetRef ref) { + final approvalFilterTempProvider = ref.watch(approvalFilterProvider); + final approvalTypeList = approvalFilterTempProvider.approvalTypeList; + final scrollCtr = useScrollController(); + + final List dataMockup = approvalCardList; + + onTap(String tipeID) { + if (tipeID == "1") { + print("Tipe Id 1"); + Navigator.pushNamed(context, approvalDetailRoute); + } + } + + return SafeArea( + minimum: EdgeInsets.only( + top: Constant.getActualYPhone(context: context, y: 30)), + child: Scaffold( + backgroundColor: Constant.textWhite, + appBar: AppBar( + backgroundColor: Colors.white, + shadowColor: Colors.white, + surfaceTintColor: Colors.white, + scrolledUnderElevation: 0, + elevation: 0, + leading: Container( + child: IconButton( + onPressed: () { + Navigator.pop(context); + }, + icon: Icon(Icons.arrow_back_ios_new_rounded)), + ), + centerTitle: true, + title: Text( + "Approval", + style: Constant.title_screen(context: context), + ), + ), + body: Padding( + padding: EdgeInsets.symmetric( + horizontal: Constant.getActualXPhone(context: context, x: 20)), + child: Column( + children: [ + Container( + color: Colors.white, + child: Row( + children: [ + ApprovalFilterWidget(), + Expanded( + child: LayoutBuilder( + builder: (context, constraints) { + return Container( + width: constraints.maxWidth, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + ...approvalTypeList.map( + (e) => ApprovalFilterChipWidget(data: e)) + ], + ), + ), + ); + }, + ), + ) + ], + ), + ), + SizedBox( + height: Constant.getActualYPhone(context: context, y: 27), + ), + Expanded(child: Container( + // color: Colors.red, + child: LayoutBuilder(builder: (context, Constraints) { + return Container( + height: Constraints.maxWidth, + child: RefreshIndicator( + onRefresh: () async { + print("Refresh"); + }, + child: ListView( + controller: scrollCtr, + children: [ + Container( + margin: EdgeInsets.only(bottom: 28), + width: MediaQuery.of(context).size.width, + child: Row( + children: [ + Container( + child: Text( + "Februari 2024", + style: Constant.body_16(context: context) + .copyWith(fontWeight: FontWeight.w600), + ), + ), + Expanded( + child: Container( + margin: EdgeInsets.only(left: 10), + child: Divider( + height: 10, + ), + ), + ) + ], + ), + ), + ...dataMockup.map((e) { + switch (e.typeID) { + case "1": + return ApprovalCardPresensi( + data: e, + onTap: onTap, + ); + case "2": + return ApprovalCardCuti( + data: e, + ); + case "3": + return ApprovalCardLembur( + data: e, + ); + default: + return Container(); + } + }).toList() + ], + ), + ), + ); + }), + )) + ], + ), + ), + ), + ); + } +} diff --git a/lib/screen/approval/widget/approval_card_cuti.dart b/lib/screen/approval/widget/approval_card_cuti.dart new file mode 100644 index 0000000..a27b5c8 --- /dev/null +++ b/lib/screen/approval/widget/approval_card_cuti.dart @@ -0,0 +1,200 @@ +import 'package:absensi_sas/app/constant.dart'; +import 'package:absensi_sas/model/approval_model.dart'; +import 'package:absensi_sas/screen/approval/widget/approval_card_status_widget.dart'; +import 'package:absensi_sas/screen/approval/widget/approval_dialog_confirmation.dart'; +import 'package:absensi_sas/widget/custom_button_approval.dart'; +import 'package:eva_icons_flutter/eva_icons_flutter.dart'; +import 'package:flutter/material.dart'; +import 'package:iconify_flutter/iconify_flutter.dart'; +import 'package:iconify_flutter/icons/heroicons.dart'; + +class ApprovalCardCuti extends StatelessWidget { + const ApprovalCardCuti({super.key, required this.data}); + final ApprovalModel data; + + @override + Widget build(BuildContext context) { + return Container( + margin: EdgeInsets.only( + bottom: Constant.getActualYPhone(context: context, y: 20)), + child: InkWell( + customBorder: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + splashColor: Constant.bgOrange, + overlayColor: MaterialStatePropertyAll(Constant.bgOrange), + onTap: () { + print("tapped"); + }, + child: Ink( + decoration: BoxDecoration(boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.12), + offset: Offset(0.0, 10), //(x,y) + blurRadius: 4.0, + ), + BoxShadow( + color: Colors.grey.withOpacity(0.2), + offset: Offset(0.0, 0), //(x,y) + blurRadius: 2.0, + ), + ], color: Colors.white, borderRadius: BorderRadius.circular(16)), + child: Column( + children: [ + Container( + padding: EdgeInsets.symmetric( + horizontal: + Constant.getActualXPhone(context: context, x: 20), + vertical: + Constant.getActualYPhone(context: context, y: 16)), + child: Column( + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + data.name ?? "", + style: Constant.body_16(context: context), + ), + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Constant.primaryOrange.withOpacity(0.2)), + padding: EdgeInsets.symmetric( + horizontal: Constant.getActualXPhone( + context: context, x: 8), + vertical: Constant.getActualYPhone( + context: context, y: 1)), + child: Text( + data.typename ?? "", + style: Constant.body_12(context: context) + .copyWith(color: Constant.primaryOrange), + ), + ) + ], + ), + SizedBox( + height: + Constant.getActualYPhone(context: context, y: 8), + ), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon( + EvaIcons.calendar, + color: Constant.primaryOrange, + size: 14, + ), + SizedBox( + width: Constant.getActualXPhone( + context: context, x: 8), + ), + Flexible( + child: Text( + data.date ?? "", + style: Constant.titleH2_400_14(context: context) + .copyWith(color: Constant.textDarkGrey), + ), + ) + ], + ), + SizedBox( + height: + Constant.getActualYPhone(context: context, y: 8), + ), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Icon( + // , + // color: Constant.primaryOrange, + // size: 14, + // ), + Iconify( + Heroicons.chat_bubble_bottom_center_text_solid, + color: Constant.primaryOrange, + size: 14, + ), + SizedBox( + width: Constant.getActualXPhone( + context: context, x: 8), + ), + + Flexible( + child: Text( + "${data.reasonType} ▪️ ${data.reasonDescription}", + style: Constant.titleH2_400_14(context: context) + .copyWith(color: Constant.textDarkGrey), + ), + ), + ], + ), + SizedBox( + height: + Constant.getActualYPhone(context: context, y: 20), + ), + Row( + children: List.generate( + 1000 ~/ 10, + (index) => Expanded( + child: Container( + color: index % 2 == 0 + ? Colors.transparent + : Constant.textLightGrey + .withOpacity(0.24), + height: 1, + ), + )), + ), + if (data.statusID != "1" && data.statusID != "2") + SizedBox( + height: + Constant.getActualYPhone(context: context, y: 20), + ), + if (data.statusID != "1" && data.statusID != "2") + Row( + children: [ + Expanded( + child: CustomButtonWhite( + btnText: "Reject", + onPressed: () { + ApprovalDialogConfirmation( + context: context, + confirmFunc: () {}, + textInformation: "Menolak ", + staffName: data.name ?? "", + typename: data.typename ?? ""); + }), + ), + SizedBox( + width: Constant.getActualXPhone( + context: context, x: 24), + ), + Expanded( + child: CustomButtonOrange( + btnText: "Approve", + onPressed: () { + ApprovalDialogConfirmation( + context: context, + confirmFunc: () {}, + textInformation: "Menyetujui ", + staffName: data.name ?? "", + typename: data.typename ?? ""); + }), + ), + ], + ) + ], + ), + ), + AprrovalCardStatusWidget( + statusID: data.statusID ?? "", + statusName: data.statusName ?? '', + ) + ], + )), + ), + ); + } +} diff --git a/lib/screen/approval/widget/approval_card_lembur.dart b/lib/screen/approval/widget/approval_card_lembur.dart new file mode 100644 index 0000000..a1a27f8 --- /dev/null +++ b/lib/screen/approval/widget/approval_card_lembur.dart @@ -0,0 +1,200 @@ +import 'package:absensi_sas/app/constant.dart'; +import 'package:absensi_sas/model/approval_model.dart'; +import 'package:absensi_sas/screen/approval/widget/approval_card_status_widget.dart'; +import 'package:absensi_sas/screen/approval/widget/approval_dialog_confirmation.dart'; +import 'package:absensi_sas/widget/custom_button_approval.dart'; +import 'package:eva_icons_flutter/eva_icons_flutter.dart'; +import 'package:flutter/material.dart'; +import 'package:iconify_flutter/iconify_flutter.dart'; +import 'package:iconify_flutter/icons/heroicons.dart'; + +class ApprovalCardLembur extends StatelessWidget { + const ApprovalCardLembur({super.key, required this.data}); + final ApprovalModel data; + + @override + Widget build(BuildContext context) { + return Container( + margin: EdgeInsets.only( + bottom: Constant.getActualYPhone(context: context, y: 20)), + child: InkWell( + customBorder: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + splashColor: Constant.bgOrange, + overlayColor: MaterialStatePropertyAll(Constant.bgOrange), + onTap: () { + print("tapped"); + }, + child: Ink( + decoration: BoxDecoration(boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.12), + offset: Offset(0.0, 10), //(x,y) + blurRadius: 4.0, + ), + BoxShadow( + color: Colors.grey.withOpacity(0.2), + offset: Offset(0.0, 0), //(x,y) + blurRadius: 2.0, + ), + ], color: Colors.white, borderRadius: BorderRadius.circular(16)), + child: Column( + children: [ + Container( + padding: EdgeInsets.symmetric( + horizontal: + Constant.getActualXPhone(context: context, x: 20), + vertical: + Constant.getActualYPhone(context: context, y: 16)), + child: Column( + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + data.name ?? "", + style: Constant.body_16(context: context), + ), + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Constant.bgSecondaryBlue), + padding: EdgeInsets.symmetric( + horizontal: Constant.getActualXPhone( + context: context, x: 8), + vertical: Constant.getActualYPhone( + context: context, y: 1)), + child: Text( + data.typename ?? "", + style: Constant.body_12(context: context) + .copyWith(color: Constant.secondaryBlue), + ), + ) + ], + ), + SizedBox( + height: + Constant.getActualYPhone(context: context, y: 8), + ), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon( + EvaIcons.calendar, + color: Constant.textOrange, + size: 14, + ), + SizedBox( + width: Constant.getActualXPhone( + context: context, x: 8), + ), + Flexible( + child: Text( + data.date ?? "", + style: Constant.titleH2_400_14(context: context) + .copyWith(color: Constant.textDarkGrey), + ), + ) + ], + ), + SizedBox( + height: + Constant.getActualYPhone(context: context, y: 8), + ), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Icon( + // , + // color: Constant.textOrange, + // size: 14, + // ), + Iconify( + Heroicons.chat_bubble_bottom_center_text_solid, + color: Constant.textOrange, + size: 14, + ), + SizedBox( + width: Constant.getActualXPhone( + context: context, x: 8), + ), + + Flexible( + child: Text( + data.reasonDescription ?? '', + style: Constant.titleH2_400_14(context: context) + .copyWith(color: Constant.textDarkGrey), + ), + ), + ], + ), + SizedBox( + height: + Constant.getActualYPhone(context: context, y: 20), + ), + Row( + children: List.generate( + 1000 ~/ 10, + (index) => Expanded( + child: Container( + color: index % 2 == 0 + ? Colors.transparent + : Constant.textLightGrey + .withOpacity(0.24), + height: 1, + ), + )), + ), + if (data.statusID != "1" && data.statusID != "2") + SizedBox( + height: + Constant.getActualYPhone(context: context, y: 20), + ), + if (data.statusID != "1" && data.statusID != "2") + Row( + children: [ + Expanded( + child: CustomButtonWhite( + btnText: "Reject", + onPressed: () { + ApprovalDialogConfirmation( + context: context, + confirmFunc: () {}, + textInformation: "Menolak ", + staffName: data.name ?? "", + typename: data.typename ?? ""); + }), + ), + SizedBox( + width: Constant.getActualXPhone( + context: context, x: 24), + ), + Expanded( + child: CustomButtonOrange( + btnText: "Approve", + onPressed: () { + ApprovalDialogConfirmation( + context: context, + confirmFunc: () {}, + textInformation: "Menyetujui ", + staffName: data.name ?? "", + typename: data.typename ?? ""); + }), + ), + ], + ), + ], + ), + ), + AprrovalCardStatusWidget( + statusID: data.statusID ?? "", + statusName: data.statusName ?? '', + ) + ], + )), + ), + ); + } +} diff --git a/lib/screen/approval/widget/approval_card_presensi.dart b/lib/screen/approval/widget/approval_card_presensi.dart new file mode 100644 index 0000000..d97bbb0 --- /dev/null +++ b/lib/screen/approval/widget/approval_card_presensi.dart @@ -0,0 +1,203 @@ +import 'package:absensi_sas/app/constant.dart'; +import 'package:absensi_sas/model/approval_model.dart'; +import 'package:absensi_sas/screen/approval/widget/approval_card_status_widget.dart'; +import 'package:eva_icons_flutter/eva_icons_flutter.dart'; +import 'package:flutter/material.dart'; + +class ApprovalCardPresensi extends StatelessWidget { + const ApprovalCardPresensi( + {super.key, required this.data, required this.onTap}); + final ApprovalModel data; + final Function onTap; + + @override + Widget build(BuildContext context) { + return Container( + margin: EdgeInsets.only( + bottom: Constant.getActualYPhone(context: context, y: 20)), + child: InkWell( + customBorder: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + splashColor: Constant.bgOrange, + // hoverColor: Colors.red, + overlayColor: MaterialStatePropertyAll(Constant.bgOrange), + + onTap: () { + onTap(data.typeID); + }, + child: Ink( + decoration: BoxDecoration(boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.12), + offset: Offset(0.0, 10), //(x,y) + blurRadius: 4.0, + ), + BoxShadow( + color: Colors.grey.withOpacity(0.2), + offset: Offset(0.0, 0), //(x,y) + blurRadius: 2.0, + ), + ], color: Colors.white, borderRadius: BorderRadius.circular(16)), + child: Column( + children: [ + Container( + padding: EdgeInsets.symmetric( + horizontal: + Constant.getActualXPhone(context: context, x: 20), + vertical: + Constant.getActualYPhone(context: context, y: 16)), + child: Column( + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + data.name ?? "", + style: Constant.body_16(context: context), + ), + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Constant.bgBlue), + padding: EdgeInsets.symmetric( + horizontal: Constant.getActualXPhone( + context: context, x: 8), + vertical: Constant.getActualYPhone( + context: context, y: 1)), + child: Text( + data.typename ?? "", + style: Constant.body_12(context: context) + .copyWith(color: Constant.primaryBlue), + ), + ) + ], + ), + SizedBox( + height: + Constant.getActualYPhone(context: context, y: 8), + ), + Row( + children: [ + Row( + children: [ + Icon( + EvaIcons.calendar, + color: Constant.primaryOrange, + size: 14, + ), + SizedBox( + width: Constant.getActualXPhone( + context: context, x: 8), + ), + Text( + data.date ?? "", + style: Constant.titleH2_400_14(context: context) + .copyWith(color: Constant.textDarkGrey), + ) + ], + ), + SizedBox( + width: Constant.getActualXPhone( + context: context, x: 20), + ), + Row( + children: [ + Icon( + EvaIcons.clock, + color: Constant.primaryOrange, + size: 14, + ), + SizedBox( + width: Constant.getActualXPhone( + context: context, x: 8), + ), + Text( + data.time ?? "", + style: Constant.titleH2_400_14(context: context) + .copyWith(color: Constant.textDarkGrey), + ) + ], + ), + SizedBox( + width: Constant.getActualXPhone( + context: context, x: 20), + ), + Row( + children: [ + Image.asset( + 'images/finger_tap_orange_botnav.png', + width: Constant.getActualXPhone( + context: context, x: 14), + ), + SizedBox( + width: Constant.getActualXPhone( + context: context, x: 8), + ), + Text( + data.presensiTypeName ?? "", + style: Constant.titleH2_400_14(context: context) + .copyWith(color: Constant.textDarkGrey), + ) + ], + ), + ], + ), + SizedBox( + height: + Constant.getActualYPhone(context: context, y: 8), + ), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon( + EvaIcons.pin, + color: Constant.primaryOrange, + size: 14, + ), + SizedBox( + width: Constant.getActualXPhone( + context: context, x: 8), + ), + Flexible( + child: Text( + data.address ?? "", + style: Constant.titleH2_400_14(context: context) + .copyWith(color: Constant.textDarkGrey), + ), + ) + ], + ), + if (data.statusID == "1" || data.statusID == "2") + SizedBox( + height: + Constant.getActualYPhone(context: context, y: 20), + ), + if (data.statusID == "1" || data.statusID == "2") + Row( + children: List.generate( + 1000 ~/ 10, + (index) => Expanded( + child: Container( + color: index % 2 == 0 + ? Colors.transparent + : Constant.textLightGrey + .withOpacity(0.24), + height: 1, + ), + )), + ), + ], + ), + ), + AprrovalCardStatusWidget( + statusID: data.statusID ?? "", + statusName: data.statusName ?? "", + ) + ], + )), + ), + ); + } +} diff --git a/lib/screen/approval/widget/approval_card_status_widget.dart b/lib/screen/approval/widget/approval_card_status_widget.dart new file mode 100644 index 0000000..13d3845 --- /dev/null +++ b/lib/screen/approval/widget/approval_card_status_widget.dart @@ -0,0 +1,52 @@ +import 'package:absensi_sas/app/constant.dart'; +import 'package:flutter/material.dart'; + +class AprrovalCardStatusWidget extends StatelessWidget { + const AprrovalCardStatusWidget( + {super.key, required this.statusID, required this.statusName}); + final String statusID; + final String statusName; + + @override + Widget build(BuildContext context) { + switch (statusID) { + case "1": + return Container( + decoration: BoxDecoration( + color: Constant.bgGreen, + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(16), + bottomRight: Radius.circular(16))), + padding: EdgeInsets.symmetric( + horizontal: Constant.getActualXPhone(context: context, x: 20), + vertical: Constant.getActualYPhone(context: context, y: 16)), + width: MediaQuery.of(context).size.width, + child: Text( + statusName, + style: Constant.body_12(context: context) + .copyWith(color: Constant.primaryGreen), + ), + ); + case "2": + return Container( + decoration: BoxDecoration( + color: Constant.bgRed, + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(16), + bottomRight: Radius.circular(16))), + padding: EdgeInsets.symmetric( + horizontal: Constant.getActualXPhone(context: context, x: 20), + vertical: Constant.getActualYPhone(context: context, y: 16)), + width: MediaQuery.of(context).size.width, + child: Text( + statusName, + style: Constant.body_12(context: context) + .copyWith(color: Constant.primaryRed), + ), + ); + + default: + return Container(); + } + } +} diff --git a/lib/screen/approval/widget/approval_dialog_confirmation.dart b/lib/screen/approval/widget/approval_dialog_confirmation.dart new file mode 100644 index 0000000..b9d7715 --- /dev/null +++ b/lib/screen/approval/widget/approval_dialog_confirmation.dart @@ -0,0 +1,75 @@ +import 'package:absensi_sas/app/constant.dart'; +import 'package:absensi_sas/widget/custom_button_approval.dart'; +import 'package:flutter/material.dart'; + +Future ApprovalDialogConfirmation( + {required BuildContext context, + required String typename, + required String textInformation, + required String staffName, + required Function() confirmFunc}) { + return showDialog( + context: context, + builder: (BuildContext context) => Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerRight, + end: Alignment.centerLeft, + colors: [ + Color(0xff161C24).withOpacity(0.91), + Color(0xff161C24).withOpacity(0.3) + ])), + child: AlertDialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + backgroundColor: Colors.white, + surfaceTintColor: Colors.white, + shadowColor: Colors.white, + title: Text( + 'Konfirmasi', + style: Constant.titleH1_700_18(context: context), + ), + content: Text.rich(TextSpan( + text: "Apakah anda yakin untuk ", + style: Constant.body_16(context: context) + .copyWith(fontWeight: FontWeight.w400), + children: [ + TextSpan( + text: textInformation, + style: Constant.body_16(context: context)), + TextSpan( + text: "${typename} ", + style: Constant.body_16(context: context) + .copyWith(fontWeight: FontWeight.w400)), + TextSpan( + text: "${staffName} ?", + style: Constant.body_16(context: context)) + ])), + actions: [ + Column( + children: [ + Container( + width: MediaQuery.of(context).size.width, + child: CustomButtonOrange( + btnText: "Yakin", + onPressed: confirmFunc, + ), + ), + SizedBox( + height: 3, + ), + Container( + width: MediaQuery.of(context).size.width, + child: CustomButtonWhite( + btnText: "Batal", + onPressed: () { + Navigator.pop(context); + }, + ), + ), + ], + ), + ], + ), + ), + ); +} diff --git a/lib/screen/approval/widget/approval_filter_chip_widget.dart b/lib/screen/approval/widget/approval_filter_chip_widget.dart new file mode 100644 index 0000000..39aef91 --- /dev/null +++ b/lib/screen/approval/widget/approval_filter_chip_widget.dart @@ -0,0 +1,71 @@ +import 'package:absensi_sas/app/constant.dart'; +import 'package:absensi_sas/model/approval_type_model.dart'; +import 'package:absensi_sas/provider/approval_filter_provider.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +class ApprovalFilterChipWidget extends HookConsumerWidget { + const ApprovalFilterChipWidget({ + super.key, + required this.data, + }); + final ApprovalTypeModel data; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final currentApprovalFilter = ref.watch(approvalFilterProvider); + handleOnTap(String value) { + var tmp = currentApprovalFilter.selectedApprovalType.toList(); + var cek = tmp.contains(value); + if (cek) { + tmp.remove(value); + } else { + tmp.add(value); + } + ref.read(approvalFilterProvider.notifier).state = ApprovalFilterProvider( + endDate: currentApprovalFilter.endDate, + keyword: currentApprovalFilter.keyword, + selectedApprovalType: tmp, + startDate: currentApprovalFilter.startDate); + } + + return Container( + margin: EdgeInsets.only( + right: Constant.getActualXPhone(context: context, x: 16)), + child: ElevatedButton( + onPressed: () { + handleOnTap(data.value!); + }, + style: ButtonStyle( + elevation: MaterialStatePropertyAll(0), + shape: MaterialStatePropertyAll(RoundedRectangleBorder( + side: BorderSide( + color: currentApprovalFilter.selectedApprovalType + .contains(data.value) + ? Constant.textOrange + : Colors.grey.shade300, + ), + borderRadius: BorderRadius.circular(16))), + backgroundColor: MaterialStateProperty.resolveWith((states) { + if (currentApprovalFilter.selectedApprovalType + .contains(data.value) == + true) { + return Constant.textOrange.withOpacity(0.1); + } + return Colors.white; + }), + overlayColor: + MaterialStatePropertyAll(Constant.textOrange.withOpacity(0.48)), + foregroundColor: MaterialStatePropertyAll(Colors.white), + shadowColor: MaterialStatePropertyAll(Colors.white), + surfaceTintColor: MaterialStatePropertyAll(Colors.white)), + child: Text(data.name ?? "", + style: Constant.subtitle_600_14(context: context).copyWith( + color: currentApprovalFilter.selectedApprovalType + .contains(data.value) + ? Constant.textOrange + : Colors.black)), + ), + ); + } +} diff --git a/lib/screen/approval/widget/approval_filter_widget.dart b/lib/screen/approval/widget/approval_filter_widget.dart new file mode 100644 index 0000000..d6a7d0d --- /dev/null +++ b/lib/screen/approval/widget/approval_filter_widget.dart @@ -0,0 +1,69 @@ +import 'package:absensi_sas/app/constant.dart'; + +import 'package:absensi_sas/screen/approval/widget/filter_bottom_sheet.dart'; +import 'package:fluentui_system_icons/fluentui_system_icons.dart'; +import 'package:flutter/material.dart'; + +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +class ApprovalFilterWidget extends HookConsumerWidget { + const ApprovalFilterWidget({super.key}); + @override + Widget build(BuildContext context, WidgetRef ref) { + return Container( + margin: EdgeInsets.only( + right: Constant.getActualXPhone(context: context, x: 16)), + child: ElevatedButton( + onPressed: () { + showModalBottomSheet( + backgroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(32), + topRight: Radius.circular(32))), + context: context, + isScrollControlled: true, + enableDrag: true, + useSafeArea: true, + // useRootNavigator: true, + builder: (context) { + return FilterBottomSheet(); + }, + ); + }, + style: ButtonStyle( + elevation: MaterialStatePropertyAll(0), + shape: MaterialStatePropertyAll(RoundedRectangleBorder( + side: BorderSide( + color: Colors.grey.shade300, + ), + borderRadius: BorderRadius.circular(16))), + backgroundColor: MaterialStateProperty.resolveWith((states) { + return Colors.white; + }), + overlayColor: + MaterialStatePropertyAll(Constant.textOrange.withOpacity(0.48)), + foregroundColor: MaterialStatePropertyAll(Colors.white), + shadowColor: MaterialStatePropertyAll(Colors.white), + surfaceTintColor: MaterialStatePropertyAll(Colors.white)), + child: Row( + children: [ + Icon( + FluentIcons.options_24_filled, + size: 20, + color: Colors.black, + ), + SizedBox( + width: 8, + ), + Text( + "Filter", + style: Constant.subtitle_600_14(context: context) + .copyWith(color: Colors.black), + ), + ], + ), + ), + ); + } +} diff --git a/lib/screen/approval/widget/filter_bottom_sheet.dart b/lib/screen/approval/widget/filter_bottom_sheet.dart new file mode 100644 index 0000000..037bcf9 --- /dev/null +++ b/lib/screen/approval/widget/filter_bottom_sheet.dart @@ -0,0 +1,534 @@ +import 'package:absensi_sas/app/constant.dart'; +import 'package:absensi_sas/model/approval_type_model.dart'; +import 'package:absensi_sas/provider/approval_filter_provider.dart'; +import 'package:absensi_sas/widget/custom_button_approval.dart'; +import 'package:eva_icons_flutter/eva_icons_flutter.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:intl/intl.dart'; + +class FilterBottomSheet extends HookConsumerWidget { + const FilterBottomSheet({ + super.key, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final currApprovalTypeState = ref.read(approvalFilterProvider); + final approvalTypeList = [ + ApprovalTypeModel(id: "1", name: "Presensi", isActive: false, value: "p"), + ApprovalTypeModel(id: "2", name: "Cuti", isActive: true, value: "c"), + ApprovalTypeModel(id: "3", name: "Lembur", isActive: false, value: "l"), + ]; + final staffFocuseNode = useFocusNode(); + final startDateFocusNode = useFocusNode(); + final endDateFocusNode = useFocusNode(); + final staffCtr = + useTextEditingController(text: currApprovalTypeState.keyword); + final startDateCtr = useTextEditingController( + text: DateFormat('dd-MM-yyyy').format(currApprovalTypeState.startDate)); + final startDateState = useState(currApprovalTypeState.startDate); + final endDateCtr = useTextEditingController( + text: DateFormat('dd-MM-yyyy').format(currApprovalTypeState.endDate)); + final endDateState = useState(currApprovalTypeState.endDate); + final dateKey = useState(0); + + final selectedApprovalTypeState = useState>( + currApprovalTypeState.selectedApprovalType.toList()); + final checkboxKey = useState(1000); + + selectApprovalTypeFunction(String value) { + var tmp = selectedApprovalTypeState.value; + var cek = tmp.contains(value); + if (cek) { + tmp.remove(value); + } else { + tmp.add(value); + } + selectedApprovalTypeState.value = tmp; + checkboxKey.value = checkboxKey.value + 1; + } + + return Container( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).viewInsets.bottom * 0.75), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(32), topRight: Radius.circular(32))), + child: SingleChildScrollView( + child: Container( + margin: EdgeInsets.symmetric( + horizontal: Constant.getActualXPhone(context: context, x: 24)), + height: MediaQuery.of(context).size.height * 0.75, + child: Column( + children: [ + Container( + height: MediaQuery.of(context).size.height * 0.1, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + "Filter", + style: Constant.titleH1_700(context: context), + ), + IconButton( + onPressed: () { + Navigator.pop(context); + }, + icon: Icon(Icons.close)) + ], + ), + ), + Container( + height: MediaQuery.of(context).size.height * 0.55, + // color: Colors.green, + + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Staff", + style: Constant.titleH1_700_18(context: context) + .copyWith(fontWeight: FontWeight.w700), + ), + SizedBox( + height: + Constant.getActualYPhone(context: context, y: 16), + ), + TextFormField( + controller: staffCtr, + focusNode: staffFocuseNode, + style: Constant.titleH1_500_18(context: context), + decoration: InputDecoration( + focusColor: Constant.textOrange, + // hoverColor: Constant.textOrange, + suffixIcon: Icon( + EvaIcons.searchOutline, + color: staffFocuseNode.hasFocus + ? Constant.textOrange + : Color(0xff212B36), + ), + label: Text( + "Cari Staff", + style: Constant.titleH1_500_18(context: context) + .copyWith( + color: staffFocuseNode.hasFocus + ? Constant.textOrange + : Colors.grey.shade400), + ), + hintText: "Cari Staff", + isDense: false, + contentPadding: EdgeInsets.symmetric( + vertical: Constant.getActualYPhone( + context: context, y: 16), + horizontal: Constant.getActualXPhone( + context: context, x: 14)), + hintStyle: + Constant.titleH1_500_18(context: context), + focusedBorder: OutlineInputBorder( + borderSide: + BorderSide(color: Constant.textOrange), + borderRadius: BorderRadius.circular(8)), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.grey.shade500, width: 1), + borderRadius: BorderRadius.circular(8)), + border: OutlineInputBorder( + borderSide: + BorderSide(color: Colors.grey.shade500), + borderRadius: BorderRadius.circular(8))), + ), + SizedBox( + height: + Constant.getActualYPhone(context: context, y: 20), + ), + Row( + children: [ + Expanded( + child: Text( + "Tanggal Awal", + style: Constant.titleH1_700_18(context: context) + .copyWith(fontWeight: FontWeight.w700), + ), + ), + SizedBox( + width: Constant.getActualXPhone( + context: context, x: 20), + ), + Expanded( + child: Text( + "Tanggal Akhir", + style: Constant.titleH1_700_18(context: context) + .copyWith(fontWeight: FontWeight.w700), + ), + ) + ], + ), + SizedBox( + height: + Constant.getActualYPhone(context: context, y: 20), + ), + Row( + children: [ + Expanded( + child: TextFormField( + key: ValueKey(dateKey.value), + controller: startDateCtr, + focusNode: startDateFocusNode, + readOnly: true, + style: Constant.titleH1_500_18(context: context), + decoration: InputDecoration( + focusColor: Constant.textOrange, + // hoverColor: Constant.textOrange, + suffixIcon: Icon( + EvaIcons.calendar, + color: startDateFocusNode.hasFocus + ? Constant.textOrange + : Color(0xff212B36), + ), + label: Text( + "Tanggal Awal", + style: Constant.titleH1_500_18( + context: context) + .copyWith( + overflow: TextOverflow.ellipsis, + color: startDateFocusNode.hasFocus + ? Constant.textOrange + : Colors.grey.shade400), + ), + hintText: "Tanggal Awal", + isDense: false, + contentPadding: EdgeInsets.symmetric( + vertical: Constant.getActualYPhone( + context: context, y: 16), + horizontal: Constant.getActualXPhone( + context: context, x: 14)), + hintStyle: + Constant.titleH1_500_18(context: context) + .copyWith( + overflow: TextOverflow.ellipsis), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Constant.textOrange), + borderRadius: BorderRadius.circular(8)), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.grey.shade500, + width: 1), + borderRadius: BorderRadius.circular(8)), + border: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.grey.shade500), + borderRadius: BorderRadius.circular(8))), + onTap: () async { + startDateFocusNode.requestFocus(); + dateKey.value = dateKey.value + 1; + DateTime? pickedDate = await showDatePicker( + helpText: "Tanggal Awal", + // locale: Locale('id', 'ID'), + builder: (context, child) { + return Theme( + data: ThemeData().copyWith( + datePickerTheme: + DatePickerThemeData( + dayBackgroundColor: + MaterialStateProperty + .resolveWith( + (states) { + if (states.contains( + MaterialState + .selected)) { + return Constant + .textOrange; + } + return Colors.white; + }), + todayBackgroundColor: + MaterialStateProperty + .resolveWith( + (states) { + if (states.contains( + MaterialState + .selected)) { + return Constant + .textOrange; + } + return Colors.white; + }), + todayBorder: BorderSide( + style: + BorderStyle.solid, + color: Constant + .textOrange, + width: 1), + headerBackgroundColor: + Constant.textOrange, + headerForegroundColor: + Colors.white, + surfaceTintColor: + Colors.white, + backgroundColor: + Colors.white), + primaryColor: Constant.textOrange, + primaryColorDark: + Constant.textOrange, + primaryColorLight: + Constant.textOrange), + child: child!); + }, + confirmText: "OK", + cancelText: "Batal", + context: context, + initialDate: startDateState.value, + firstDate: DateTime(1950), + initialEntryMode: + DatePickerEntryMode.calendarOnly, + lastDate: DateTime(2100)); + + if (pickedDate != null) { + print( + pickedDate); //pickedDate output format => 2021-03-10 00:00:00.000 + String formattedDate = + DateFormat('dd-MM-yyyy') + .format(pickedDate); + print( + formattedDate); //formatted date output using intl package => 2021-03-16 + startDateCtr.text = + formattedDate; //set output date to TextField value. + startDateState.value = pickedDate; + startDateFocusNode.requestFocus(); + } else {} + }, + ), + ), + SizedBox( + width: Constant.getActualXPhone( + context: context, x: 20), + ), + Expanded( + child: TextFormField( + key: ValueKey(dateKey.value), + controller: endDateCtr, + focusNode: endDateFocusNode, + readOnly: true, + style: Constant.titleH1_500_18(context: context), + decoration: InputDecoration( + focusColor: Constant.textOrange, + // hoverColor: Constant.textOrange, + suffixIcon: Icon( + EvaIcons.calendar, + color: endDateFocusNode.hasFocus + ? Constant.textOrange + : Color(0xff212B36), + ), + label: Text( + "Tanggal Akhir", + style: Constant.titleH1_500_18( + context: context) + .copyWith( + overflow: TextOverflow.ellipsis, + color: endDateFocusNode.hasFocus + ? Constant.textOrange + : Colors.grey.shade400), + ), + hintText: "Tanggal Akhir", + isDense: false, + contentPadding: EdgeInsets.symmetric( + vertical: Constant.getActualYPhone( + context: context, y: 16), + horizontal: Constant.getActualXPhone( + context: context, x: 14)), + hintStyle: + Constant.titleH1_500_18(context: context) + .copyWith( + overflow: TextOverflow.ellipsis), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Constant.textOrange), + borderRadius: BorderRadius.circular(8)), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.grey.shade500, + width: 1), + borderRadius: BorderRadius.circular(8)), + border: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.grey.shade500), + borderRadius: BorderRadius.circular(8))), + onTap: () async { + endDateFocusNode.requestFocus(); + dateKey.value = dateKey.value + 1; + DateTime? pickedDate = await showDatePicker( + helpText: "Tanggal Akhir", + // locale: Locale('id', 'ID'), + builder: (context, child) { + return Theme( + data: ThemeData().copyWith( + datePickerTheme: + DatePickerThemeData( + dayBackgroundColor: + MaterialStateProperty + .resolveWith( + (states) { + if (states.contains( + MaterialState + .selected)) { + return Constant + .textOrange; + } + return Colors.white; + }), + todayBackgroundColor: + MaterialStateProperty + .resolveWith( + (states) { + if (states.contains( + MaterialState + .selected)) { + return Constant + .textOrange; + } + return Colors.white; + }), + todayBorder: BorderSide( + style: + BorderStyle.solid, + color: Constant + .textOrange, + width: 1), + headerBackgroundColor: + Constant.textOrange, + headerForegroundColor: + Colors.white, + surfaceTintColor: + Colors.white, + backgroundColor: + Colors.white), + primaryColor: Constant.textOrange, + primaryColorDark: + Constant.textOrange, + primaryColorLight: + Constant.textOrange), + child: child!); + }, + confirmText: "OK", + cancelText: "Batal", + context: context, + initialDate: startDateState.value, + firstDate: DateTime(1950), + initialEntryMode: + DatePickerEntryMode.calendarOnly, + lastDate: DateTime(2100)); + + if (pickedDate != null) { + print( + pickedDate); //pickedDate output format => 2021-03-10 00:00:00.000 + String formattedDate = + DateFormat('dd-MM-yyyy') + .format(pickedDate); + print( + formattedDate); //formatted date output using intl package => 2021-03-16 + endDateCtr.text = + formattedDate; //set output date to TextField value. + endDateState.value = pickedDate; + endDateFocusNode.requestFocus(); + } else {} + }, + ), + ) + ], + ), + SizedBox( + height: + Constant.getActualYPhone(context: context, y: 16), + ), + // Text( + // ref + // .watch(approvalFilterProvider) + // .selectedApprovalType + // .toString(), + // style: Constant.titleH1_700_18(context: context), + // ), + Text( + "Approval", + style: Constant.titleH1_700_18(context: context) + .copyWith(fontWeight: FontWeight.w700), + ), + SizedBox( + height: + Constant.getActualYPhone(context: context, y: 16), + ), + ...approvalTypeList.map( + (e) { + return Row( + children: [ + Checkbox( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4)), + value: selectedApprovalTypeState.value + .contains(e.value), + onChanged: (val) { + selectApprovalTypeFunction(e.value!); + }, + ), + Text( + e.name ?? "", + style: Constant.titleH1_700_18(context: context) + .copyWith(fontWeight: FontWeight.normal), + ) + ], + ); + }, + ) + ], + ), + ), + ), + Container( + height: MediaQuery.of(context).size.height * 0.1, + child: Row( + children: [ + Expanded( + child: CustomButtonWhite( + btnText: "Reset", + onPressed: () { + ref.read(approvalFilterProvider.notifier).state = + ApprovalFilterProvider( + startDate: DateTime.now(), + endDate: DateTime.now(), + keyword: "", + selectedApprovalType: []); + Navigator.pop(context); + }), + ), + SizedBox( + width: Constant.getActualXPhone(context: context, x: 24), + ), + Expanded( + child: CustomButtonOrange( + btnText: "Simpan", + onPressed: () { + ref.read(approvalFilterProvider.notifier).state = + ApprovalFilterProvider( + endDate: endDateState.value, + keyword: staffCtr.text, + selectedApprovalType: + selectedApprovalTypeState.value, + startDate: startDateState.value); + Navigator.pop(context); + }), + ), + ], + ), + ) + ], + ), + ), + ), + ); + } +} diff --git a/lib/screen/approval_detail/approval_detail_screen.dart b/lib/screen/approval_detail/approval_detail_screen.dart new file mode 100644 index 0000000..0695c3f --- /dev/null +++ b/lib/screen/approval_detail/approval_detail_screen.dart @@ -0,0 +1,368 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:absensi_sas/app/constant.dart'; +import 'package:absensi_sas/model/approval_detail_model.dart'; +import 'package:absensi_sas/screen/approval/approval_mockup.dart'; +import 'package:absensi_sas/screen/approval/widget/approval_dialog_confirmation.dart'; +import 'package:absensi_sas/widget/custom_button_approval.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:latlong2/latlong.dart'; + +class ApprovalDetailScreen extends HookConsumerWidget { + const ApprovalDetailScreen({super.key}); + @override + Widget build(BuildContext context, WidgetRef ref) { + final loading = useState(true); + final data = useState( + ApprovalDetailModel(imagePath: "", lat: 0, long: 0)); + init() { + loading.value = true; + var randIdx = Random().nextInt(approvalDetailMockup.length); + var tmp = approvalDetailMockup[randIdx]; + Timer(Duration(seconds: 2), () { + data.value = tmp; + loading.value = false; + }); + } + + useEffect(() { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { + init(); + return; + }); + return () {}; + }, []); + return SafeArea( + minimum: EdgeInsets.only( + top: Constant.getActualYPhone(context: context, y: 30)), + child: Scaffold( + backgroundColor: Constant.textWhite, + appBar: AppBar( + backgroundColor: Colors.white, + shadowColor: Colors.white, + surfaceTintColor: Colors.white, + scrolledUnderElevation: 0, + elevation: 0, + leading: Container( + child: IconButton( + onPressed: () { + Navigator.pop(context); + }, + icon: Icon(Icons.arrow_back_ios_new_rounded)), + ), + centerTitle: true, + title: Text( + "Detail", + style: Constant.title_screen(context: context), + ), + ), + body: loading.value + ? Center( + child: CircularProgressIndicator(), + ) + : Container( + color: Colors.white, + padding: EdgeInsets.symmetric( + horizontal: + Constant.getActualXPhone(context: context, x: 20)), + child: RefreshIndicator( + onRefresh: () async { + init(); + }, + child: SingleChildScrollView( + child: Column( + children: [ + SizedBox( + height: Constant.getActualYPhone( + context: context, y: 36), + ), + if (data.value.statusID == "1" || + data.value.statusID == "2") + Row( + children: [ + Spacer(), + if (data.value.statusID == "1") + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Constant.bgGreen), + padding: EdgeInsets.symmetric( + horizontal: Constant.getActualXPhone( + context: context, x: 8), + vertical: Constant.getActualYPhone( + context: context, y: 2)), + child: Text( + "Approve", + style: Constant.body_12(context: context) + .copyWith( + color: Constant.primaryGreen), + ), + ), + if (data.value.statusID == "2") + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Constant.bgRed), + padding: EdgeInsets.symmetric( + horizontal: Constant.getActualXPhone( + context: context, x: 8), + vertical: Constant.getActualYPhone( + context: context, y: 2)), + child: Text( + "Reject", + style: Constant.body_12(context: context) + .copyWith(color: Constant.primaryRed), + ), + ) + ], + ), + if (data.value.statusID == "1" || + data.value.statusID == "2") + SizedBox( + height: Constant.getActualYPhone( + context: context, y: 8), + ), + Container( + margin: EdgeInsets.only(bottom: 8), + child: Row( + children: [ + Expanded( + flex: 4, + child: Text("Nama : ", + style: Constant.body_16(context: context) + .copyWith( + color: Constant.textDarkGrey, + fontWeight: FontWeight.w400)), + ), + Expanded( + flex: 9, + child: Text(data.value.name ?? "", + style: Constant.body_16(context: context) + .copyWith( + fontWeight: FontWeight.w400)), + ) + ], + ), + ), + Container( + margin: EdgeInsets.only(bottom: 8), + child: Row( + children: [ + Expanded( + flex: 4, + child: Text("NIP : ", + style: Constant.body_16(context: context) + .copyWith( + color: Constant.textDarkGrey, + fontWeight: FontWeight.w400)), + ), + Expanded( + flex: 9, + child: Text(data.value.nip ?? "", + style: Constant.body_16(context: context) + .copyWith( + fontWeight: FontWeight.w400)), + ) + ], + ), + ), + Container( + margin: EdgeInsets.only(bottom: 8), + child: Row( + children: [ + Expanded( + flex: 4, + child: Text("Tanggal : ", + style: Constant.body_16(context: context) + .copyWith( + color: Constant.textDarkGrey, + fontWeight: FontWeight.w400)), + ), + Expanded( + flex: 9, + child: Text(data.value.date ?? "", + style: Constant.body_16(context: context) + .copyWith( + fontWeight: FontWeight.w400)), + ) + ], + ), + ), + Container( + margin: EdgeInsets.only(bottom: 8), + child: Row( + children: [ + Expanded( + flex: 4, + child: Text("Jam : ", + style: Constant.body_16(context: context) + .copyWith( + color: Constant.textDarkGrey, + fontWeight: FontWeight.w400)), + ), + Expanded( + flex: 9, + child: Text(data.value.time ?? "", + style: Constant.body_16(context: context) + .copyWith( + fontWeight: FontWeight.w400)), + ) + ], + ), + ), + Container( + margin: EdgeInsets.only(bottom: 8), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + flex: 4, + child: Text("Alamat : ", + style: Constant.body_16(context: context) + .copyWith( + color: Constant.textDarkGrey, + fontWeight: FontWeight.w400)), + ), + Expanded( + flex: 9, + child: Text(data.value.address ?? "", + style: Constant.body_16(context: context) + .copyWith( + fontWeight: FontWeight.w400)), + ) + ], + ), + ), + Container( + margin: EdgeInsets.only(top: 16), + width: MediaQuery.of(context).size.width, + height: Constant.getActualYPhone( + context: context, y: 400), + child: FlutterMap( + options: MapOptions( + center: LatLng(data.value.lat ?? 0, + data.value.long ?? 0), + zoom: 16, + ), + children: [ + TileLayer( + urlTemplate: + 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', + userAgentPackageName: 'com.example.app', + ), + MarkerLayer( + markers: [ + Marker( + point: LatLng(-7.539538, 110.798357), + child: const Icon( + Icons.location_on, + color: Colors.red, + size: 50, + ), + ) + // Marker( + // point: LatLng(lat, long), + // width: 100, + // height: 100, + // builder: (context) => const Icon( + // Icons.location_on, + // color: Colors.red, + // size: 50, + // )), + ], + ), + ]), + ), + if (data.value.imagePath != "" && + data.value.imagePath != null) + Container( + margin: EdgeInsets.only(top: 16, bottom: 48), + child: Image.network( + data.value.imagePath!, + errorBuilder: (context, error, stackTrace) { + return Text( + "Error load image : ${error.toString()}", + style: Constant.body_12(context: context) + .copyWith(color: Constant.primaryRed), + ); + }, + loadingBuilder: + (context, child, loadingProgress) { + if (loadingProgress == null) return child; + return Container( + child: Column( + children: [ + Text(loadingProgress.expectedTotalBytes != + null + ? ((loadingProgress.cumulativeBytesLoaded / + loadingProgress + .expectedTotalBytes!) * + 100) + .round() + .toString() + + " %" + : ""), + LinearProgressIndicator( + value: loadingProgress + .expectedTotalBytes != + null + ? loadingProgress + .cumulativeBytesLoaded / + loadingProgress + .expectedTotalBytes! + : null, + ), + ], + )); + }, + ), + ), + if (data.value.statusID != "1" && + data.value.statusID != "2") + Container( + margin: EdgeInsets.symmetric(vertical: 24), + child: Row( + children: [ + Expanded( + child: CustomButtonWhite( + btnText: "Reject", + onPressed: () { + ApprovalDialogConfirmation( + context: context, + typename: "presensi ", + textInformation: "menolak ", + staffName: data.value.name ?? "", + confirmFunc: () {}); + }), + ), + SizedBox( + width: Constant.getActualXPhone( + context: context, x: 24), + ), + Expanded( + child: CustomButtonOrange( + btnText: "Approve", + onPressed: () { + ApprovalDialogConfirmation( + context: context, + typename: "presensi ", + textInformation: "menyetujui ", + staffName: data.value.name ?? "", + confirmFunc: () {}); + }), + ), + ], + ), + ) + ], + ), + ), + ), + ), + )); + } +} diff --git a/lib/widget/custom_button_approval.dart b/lib/widget/custom_button_approval.dart new file mode 100644 index 0000000..af86406 --- /dev/null +++ b/lib/widget/custom_button_approval.dart @@ -0,0 +1,77 @@ +import 'package:absensi_sas/app/constant.dart'; +import 'package:flutter/material.dart'; + +class CustomButtonOrange extends StatelessWidget { + const CustomButtonOrange({super.key, required this.btnText, this.onPressed}); + final Function()? onPressed; + final String btnText; + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration(boxShadow: [ + BoxShadow( + color: Constant.textOrange.withOpacity(0.24), + spreadRadius: 0, + blurRadius: 16, + offset: Offset(0, 8), // changes position of shadow + ), + ]), + child: ElevatedButton( + style: ButtonStyle( + elevation: MaterialStatePropertyAll(0), + shape: MaterialStatePropertyAll(RoundedRectangleBorder( + side: BorderSide( + color: Constant.textOrange, + ), + borderRadius: BorderRadius.circular(8))), + backgroundColor: MaterialStateProperty.resolveWith((states) { + return Constant.textOrange; + }), + overlayColor: + MaterialStatePropertyAll(Colors.white.withOpacity(0.3)), + foregroundColor: MaterialStatePropertyAll(Constant.textOrange), + shadowColor: MaterialStatePropertyAll(Constant.textOrange), + surfaceTintColor: MaterialStatePropertyAll(Constant.textOrange)), + onPressed: onPressed, + child: Text( + btnText, + style: Constant.body_14(context: context) + .copyWith(color: Colors.white, fontWeight: FontWeight.w700), + )), + ); + } +} + +class CustomButtonWhite extends StatelessWidget { + const CustomButtonWhite({super.key, required this.btnText, this.onPressed}); + final Function()? onPressed; + final String btnText; + + @override + Widget build(BuildContext context) { + return ElevatedButton( + onPressed: onPressed, + child: Text( + btnText, + style: Constant.body_14(context: context) + .copyWith(color: Colors.black, fontWeight: FontWeight.w700), + ), + style: ButtonStyle( + elevation: MaterialStatePropertyAll(0), + shape: MaterialStatePropertyAll(RoundedRectangleBorder( + side: BorderSide( + color: Colors.grey.shade300, + ), + borderRadius: BorderRadius.circular(8))), + backgroundColor: MaterialStateProperty.resolveWith((states) { + return Colors.white; + }), + overlayColor: + MaterialStatePropertyAll(Constant.textOrange.withOpacity(0.48)), + foregroundColor: MaterialStatePropertyAll(Colors.white), + shadowColor: MaterialStatePropertyAll(Colors.white), + surfaceTintColor: MaterialStatePropertyAll(Colors.white)), + ); + } +}