From eb7236671eba6d3a0d4354100619e75c4c712e67 Mon Sep 17 00:00:00 2001 From: Sas Andy Date: Thu, 22 Feb 2024 10:38:38 +0700 Subject: [PATCH] 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)), + ); + } +}