diff --git a/images/presensi_finger_clok_out.png b/images/presensi_finger_clok_out.png new file mode 100644 index 0000000..3b6b30b Binary files /dev/null and b/images/presensi_finger_clok_out.png differ diff --git a/lib/app/constant.dart b/lib/app/constant.dart index 107e38b..8dc736c 100644 --- a/lib/app/constant.dart +++ b/lib/app/constant.dart @@ -13,6 +13,9 @@ class Constant { static double designHeightPhone = 844; static double designWidthPhone = 390; + // NOTE VERSI HARUS SAMA DENGAN PUBSPEC.YAML + static String version = "1.0.1"; + // color theme static Color textTrueBlack = const Color(0xff000000); static Color textBlack = const Color(0xff212B36); diff --git a/lib/model/check_presensi_jam_model.dart b/lib/model/check_presensi_jam_model.dart new file mode 100644 index 0000000..95a85c9 --- /dev/null +++ b/lib/model/check_presensi_jam_model.dart @@ -0,0 +1,36 @@ +class CheckPresensiJamModel { + String? status; + String? message; + String? jamClockIn; + String? jamClockOut; + String? isAbsenClockIn; + String? isAbsenClockOut; + + CheckPresensiJamModel( + {this.status, + this.message, + this.jamClockIn, + this.jamClockOut, + this.isAbsenClockIn, + this.isAbsenClockOut}); + + CheckPresensiJamModel.fromJson(Map json) { + status = json['status']; + message = json['message']; + jamClockIn = json['jam_clock_in']; + jamClockOut = json['jam_clock_out']; + isAbsenClockIn = json['is_absen_clock_in']; + isAbsenClockOut = json['is_absen_clock_out']; + } + + Map toJson() { + final Map data = new Map(); + data['status'] = this.status; + data['message'] = this.message; + data['jam_clock_in'] = this.jamClockIn; + data['jam_clock_out'] = this.jamClockOut; + data['is_absen_clock_in'] = this.isAbsenClockIn; + data['is_absen_clock_out'] = this.isAbsenClockOut; + return data; + } +} \ No newline at end of file diff --git a/lib/provider/current_check_jam_presensi_provider.dart b/lib/provider/current_check_jam_presensi_provider.dart new file mode 100644 index 0000000..e03f681 --- /dev/null +++ b/lib/provider/current_check_jam_presensi_provider.dart @@ -0,0 +1,4 @@ +import 'package:absensi_sas_flutter/model/check_presensi_jam_model.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final currentCheckJamPresensiProvider = StateProvider((ref) => null); \ No newline at end of file diff --git a/lib/provider/current_menu_provider.dart b/lib/provider/current_menu_provider.dart new file mode 100644 index 0000000..1c7d735 --- /dev/null +++ b/lib/provider/current_menu_provider.dart @@ -0,0 +1,3 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final currentPageProvider = StateProvider((ref) => null); \ No newline at end of file diff --git a/lib/provider/graphql_provider.dart b/lib/provider/graphql_provider.dart index 351a245..eb3e744 100644 --- a/lib/provider/graphql_provider.dart +++ b/lib/provider/graphql_provider.dart @@ -5,6 +5,9 @@ import '../app/constant.dart'; final graphqlProvider = Provider.family( (_, token) { + final policies = Policies( + fetch: FetchPolicy.noCache, + ); return GraphQLClient( link: (token != "") ? HttpLink( @@ -17,6 +20,11 @@ final graphqlProvider = Provider.family( Constant.baseURLGraphQl, ), cache: GraphQLCache(), + defaultPolicies: DefaultPolicies( + watchQuery: policies, + query: policies, + mutate: policies, + ), ); }, ); diff --git a/lib/repository/presensi_repository.dart b/lib/repository/presensi_repository.dart index 8537b56..c08df67 100644 --- a/lib/repository/presensi_repository.dart +++ b/lib/repository/presensi_repository.dart @@ -1,4 +1,5 @@ import 'package:absensi_sas_flutter/model/check_distance_model.dart'; +import 'package:absensi_sas_flutter/model/check_presensi_jam_model.dart'; import 'base_repository.dart'; class PresensiRepository extends BaseRepository { @@ -23,7 +24,7 @@ class PresensiRepository extends BaseRepository { final resp = await postGraphQlMutation(query, inpVariables); // final loginData = AuthModel.fromJson(resp['userLogin']); - print(inpVariables); + // print(inpVariables); print('obj queryCheckDistance : ${resp["queryCheckDistance"]}'); @@ -33,12 +34,145 @@ class PresensiRepository extends BaseRepository { // ); final result = CheckDistanceModel( - currentDistance: resp['queryCheckDistance']['current_distance'], - maxDistance: resp['queryCheckDistance']['max_distance'], - message: resp['queryCheckDistance']['message'], - selfie: resp['queryCheckDistance']['selfie'], - status: resp['queryCheckDistance']['status'], - unit: resp['queryCheckDistance']['unit'] + currentDistance: resp['queryCheckDistance']['current_distance'], + maxDistance: resp['queryCheckDistance']['max_distance'], + message: resp['queryCheckDistance']['message'], + selfie: resp['queryCheckDistance']['selfie'], + status: resp['queryCheckDistance']['status'], + unit: resp['queryCheckDistance']['unit']); + return result; + } + + // presensi normal Clock In + Future presensiNormalClockIn( + String T_TransactionM_StaffID, + String T_TransactionM_CompanyID, + String T_TransactionCurrentLatitude, + String T_TransactionCurrentLongitude, + String T_TransactionCurrentDistance, + String T_TransactionSelfiePhoto, + String token, + String isSelfie, + Map paramInpVariables) async { + const String query = + r'''mutation($T_TransactionM_StaffID:String!, $T_TransactionM_CompanyID:String!, $T_TransactionCurrentLatitude:String!, $T_TransactionCurrentLongitude:String!, $T_TransactionCurrentDistance:String!, $T_TransactionSelfiePhoto:String, $token:String!, $isSelfie:String!){ mutationClockInAttendance(T_TransactionM_StaffID:$T_TransactionM_StaffID, T_TransactionM_CompanyID:$T_TransactionM_CompanyID, T_TransactionCurrentLatitude:$T_TransactionCurrentLatitude, T_TransactionCurrentLongitude:$T_TransactionCurrentLongitude, T_TransactionCurrentDistance:$T_TransactionCurrentDistance, T_TransactionSelfiePhoto:$T_TransactionSelfiePhoto, token:$token, isSelfie:$isSelfie){ status message } }'''; + + Map inpVariables = paramInpVariables; + print(paramInpVariables); + + final resp = await postGraphQlMutation(query, inpVariables); + + // print(inpVariables); + + print( + 'obj mutationClockInAttendance : ${resp["mutationClockInAttendance"]}'); + + // final result = AuthModel( + // token: resp["loginAttendance"]["token"], + // model: StaffModel.fromJson(resp["loginAttendance"]), + // ); + + final result = resp["mutationClockInAttendance"]['status']; + return result; + } + + // presensi normal clock Out + Future presensiNormalClockOut( + String T_TransactionM_StaffID, + String T_TransactionM_CompanyID, + String T_TransactionCurrentLatitude, + String T_TransactionCurrentLongitude, + String T_TransactionCurrentDistance, + String T_TransactionSelfiePhoto, + String token, + String isSelfie, + Map paramInpVariables) async { + const String query = + r'''mutation( + $T_TransactionM_StaffID:String!, + $T_TransactionM_CompanyID:String!, + $T_TransactionCurrentLatitude:String!, + $T_TransactionCurrentLongitude:String!, + $T_TransactionCurrentDistance:String!, + $T_TransactionSelfiePhoto:String, + $token:String!, + $isSelfie:String! + ){ + mutationClockOutAttendance( + T_TransactionM_StaffID:$T_TransactionM_StaffID, + T_TransactionM_CompanyID:$T_TransactionM_CompanyID, + T_TransactionCurrentLatitude:$T_TransactionCurrentLatitude, + T_TransactionCurrentLongitude:$T_TransactionCurrentLongitude, + T_TransactionCurrentDistance:$T_TransactionCurrentDistance, + T_TransactionSelfiePhoto:$T_TransactionSelfiePhoto, + token:$token, isSelfie:$isSelfie + ){ + status + message + } + }'''; + + Map inpVariables = paramInpVariables; + print(paramInpVariables); + + final resp = await postGraphQlMutation(query, inpVariables); + + // print(inpVariables); + + print( + 'obj mutationClockOutAttendance : ${resp["mutationClockOutAttendance"]}'); + + // final result = AuthModel( + // token: resp["loginAttendance"]["token"], + // model: StaffModel.fromJson(resp["loginAttendance"]), + // ); + + final result = resp["mutationClockOutAttendance"]['status']; + return result; + } + + // check presensi jam + Future checkPresensiJam( + String M_StaffID, + String M_CompanyID, + String token, + Map paramInpVariables, + ) async { + const String query = + r'''query($M_StaffID:String!, $M_CompanyID:String!, $token:String!){ + queryCheckTimeAttendance(M_StaffID:$M_StaffID, M_CompanyID:$M_CompanyID, token:$token){ + status + message + jam_clock_in + jam_clock_out + is_absen_clock_in + is_absen_clock_out + } + }'''; + + // Map inpVariables = paramInpVariables; + + // Map inpVariables = { + // "M_StaffID": M_StaffID, + // "M_CompanyID": M_CompanyID, + // "token": token, + // }; + + print(paramInpVariables); + + final resp = await postGraphQlQuery(query, paramInpVariables); + + // print(inpVariables); + + print('obj queryCheckTimeAttendance : ${resp["queryCheckTimeAttendance"]}'); + + final result = CheckPresensiJamModel( + isAbsenClockIn: resp['queryCheckTimeAttendance']['is_absen_clock_in'], + isAbsenClockOut: resp['queryCheckTimeAttendance']['is_absen_clock_out'], + jamClockIn: resp['queryCheckTimeAttendance']['jam_clock_in'], + jamClockOut: resp['queryCheckTimeAttendance']['jam_clock_out'], + message: resp['queryCheckTimeAttendance']['message'], + status: resp['queryCheckTimeAttendance']['status'], ); return result; } diff --git a/lib/screen/home/home_screen.dart b/lib/screen/home/home_screen.dart index f777421..9b5cb48 100644 --- a/lib/screen/home/home_screen.dart +++ b/lib/screen/home/home_screen.dart @@ -9,12 +9,16 @@ import 'package:permission_handler/permission_handler.dart'; import '../../app/route.dart'; import '../../provider/current_check_distance_provider.dart'; +import '../../provider/current_check_jam_presensi_provider.dart'; +import '../../provider/current_menu_provider.dart'; import '../../provider/current_user_provider.dart'; import '../../provider/google_login_provider.dart'; +import '../../widget/custom_drawer.dart'; import '../../widget/real_date.dart'; import '../../widget/real_time.dart'; import '../../widget/sankbar_widget.dart'; import '../presensi/check_distance_provider.dart'; +import '../presensi/check_presensi_jam_provider.dart'; class HomeScreen extends HookConsumerWidget { const HomeScreen({super.key}); @@ -24,6 +28,8 @@ class HomeScreen extends HookConsumerWidget { final selectedUser = ref.read(currentUserProvider); final isLoadingProsesCheckDistance = useState(false); final varCurrentDistanceProvider = ref.watch(currentCheckDistanceProvider); + final varCurrentCheckJamProvider = + ref.watch(currentCheckJamPresensiProvider); final positionLatitude = useState(""); final positionLongitude = useState(""); GoogleSignInAccount? currentUserGoogle = @@ -44,6 +50,29 @@ class HomeScreen extends HookConsumerWidget { return () {}; }, []); + useEffect(() { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { + final staffID = ref.read(currentUserProvider)?.model.staffId ?? "0"; + + if (staffID != "0") { + // panggil check jam presensi provider + Map inpVariablesCheckPresensiJam = { + "M_StaffID": selectedUser?.model.staffId ?? "", + "M_CompanyID": selectedUser?.model.companyId ?? "", + "token": selectedUser?.token ?? "", + }; + + ref.read(checkPresensiJamProvider.notifier).checkPresensiJam( + selectedUser?.model.staffId ?? "", + selectedUser?.model.companyId ?? "", + selectedUser?.token ?? "", + inpVariablesCheckPresensiJam, + ); + } + }); + return () {}; + }, []); + Future getAddressFromLocation() async { try { isLoadingProsesCheckDistance.value = true; @@ -123,13 +152,29 @@ class HomeScreen extends HookConsumerWidget { isLoadingProsesCheckDistance.value = false; if (varCurrentDistanceProvider?.selfie == "TRUE") { + ref.read(currentPageProvider.notifier).update((state) => -1); Navigator.pushNamed(context, presensiSelfieRoute); } else { + ref.read(currentPageProvider.notifier).update((state) => -1); Navigator.pushNamed(context, presensiRoute); } } }); + // check jam presensi + ref.listen(checkPresensiJamProvider, (prev, next) { + if (next is CheckPresensiJamStateLoading) { + isLoadingProsesCheckDistance.value = true; + } else if (next is CheckPresensiJamStateError) { + isLoadingProsesCheckDistance.value = false; + print("Error : " + next.toString()); + SanckbarWidget( + context, "Error : " + next.toString(), snackbarType.warning); + } else if (next is CheckPresensiJamStateDone) { + isLoadingProsesCheckDistance.value = false; + } + }); + return Scaffold( floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, floatingActionButton: Container( @@ -223,552 +268,582 @@ class HomeScreen extends HookConsumerWidget { child: Container( width: Constant.getActualXPhone(context: context, x: 390), // height: Constant.getActualYPhone(context: context, y: 844), - child: Column( - children: [ - Padding( - padding: EdgeInsets.only( - top: Constant.getActualYPhone(context: context, y: 58), - left: Constant.getActualXPhone(context: context, x: 33), - right: Constant.getActualXPhone(context: context, x: 27), - ), - child: Container( - child: (currentUserGoogle == null) - ? Center( - child: CircularProgressIndicator(), - ) - : ListTile( - // leading: Container( - // width: Constant.getActualXPhone( - // context: context, x: 36), - // height: Constant.getActualYPhone( - // context: context, y: 36), - // child: Image( - // image: AssetImage('images/avatar_c.png'), - // ), - // ), - leading: GoogleUserCircleAvatar( - identity: currentUserGoogle, - ), - title: Text( - // "Stephen Kusumo", - // selectedUser?.model.name ?? "", - currentUserGoogle.displayName ?? "", - overflow: TextOverflow.ellipsis, - style: Constant.titleH1_700(context: context) - ..copyWith( - color: Constant.textBlack, - ), - ), - subtitle: Text( - // "Step@example.com", - // currentUserGoogle?.email ?? "", - selectedUser?.model.email ?? "", - style: Constant.subtitle_500_12(context: context) - .copyWith( - color: Constant.textLightGrey, - ), - ), - trailing: Container( - width: Constant.getActualXPhone( - context: context, x: 36), - height: Constant.getActualYPhone( - context: context, y: 36), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12.0), - color: Colors.white, - shape: BoxShape.rectangle, - boxShadow: [ - BoxShadow( - offset: Offset(0, 12), - blurRadius: 24, - color: Color.fromRGBO(145, 158, 171, 0.12), - ), - ], - ), - child: IconButton( - onPressed: () {}, - icon: Container( - width: Constant.getActualXPhone( - context: context, x: 20), - height: Constant.getActualYPhone( - context: context, y: 20), - child: Image( - image: AssetImage('images/alert_badge.png'), - ), - ), - ), - ), - ), - ), - ), - - SizedBox( - height: Constant.getActualYPhone(context: context, y: 44), - ), - - //Card Time - Padding( - padding: EdgeInsets.only( - left: Constant.getActualXPhone(context: context, x: 33), - right: Constant.getActualXPhone(context: context, x: 27), - ), - child: Container( - width: Constant.getActualXPhone(context: context, x: 330), - height: Constant.getActualYPhone(context: context, y: 200), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(40), - image: DecorationImage( - image: AssetImage( - 'images/card_bg_1.png'), // Ganti dengan path gambar Anda - fit: BoxFit.fill, // Sesuaikan cara gambar ditampilkan - ), - ), - child: Padding( - padding: EdgeInsets.only( - top: Constant.getActualYPhone(context: context, y: 16), - left: Constant.getActualXPhone(context: context, x: 25), - right: - Constant.getActualXPhone(context: context, x: 25), - ), - child: Container( - width: - Constant.getActualXPhone(context: context, x: 280), - height: - Constant.getActualYPhone(context: context, y: 150), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - // Date - RealTimeFormattedDate(), - SizedBox( - height: Constant.getActualYPhone( - context: context, y: 8), - ), - - //Time - RealTimeClock(), // Menampilkan waktu real-time menggunakan RealTimeClock - SizedBox( - height: Constant.getActualYPhone( - context: context, y: 20), - ), - Row( - mainAxisAlignment: MainAxisAlignment - .center, // Menengahkan secara horizontal - children: [ - Spacer(), // Spasi di sebelah kiri "Check In" - Column( - children: [ - Image.asset( - 'images/finger_tap.png', // Path gambar untuk "Check In" - width: Constant.getActualXPhone( - context: context, x: 22), - height: Constant.getActualYPhone( - context: context, y: 22), - ), - SizedBox( - height: Constant.getActualYPhone( - context: context, y: 8), - ), - Text( - '--:--', - style: TextStyle( - // Atur gaya teks '--:--' sesuai kebutuhan - ), - ), - Text( - 'Clock In', - style: - Constant.titleH2_700(context: context) - .copyWith( - color: Constant.textLightGrey, - ), - ), - ], - ), - - SizedBox( - width: Constant.getActualXPhone( - context: context, x: 96), - ), // Jarak antara "Check In" dan "Check Out" - Column( - children: [ - Image.asset( - 'images/finger_tap.png', // Path gambar untuk "Check Out" - width: Constant.getActualXPhone( - context: context, x: 22), - height: Constant.getActualYPhone( - context: context, y: 22), - ), - SizedBox( - height: Constant.getActualYPhone( - context: context, y: 8), - ), - Text( - '--:--', - style: TextStyle( - // Atur gaya teks '--:--' sesuai kebutuhan - ), - ), - Text( - 'Clock Out', - style: - Constant.titleH2_700(context: context) - .copyWith( - color: Constant.textLightGrey, - ), - ), - ], - ), - Spacer(), // Spasi di sebelah kanan "Check Out" - ], - ), - - SizedBox( - height: Constant.getActualYPhone( - context: context, y: 16), - ), - ], - ), - ), - ), - ), - ), - - SizedBox( - height: Constant.getActualYPhone(context: context, y: 56), - ), - - //Menu Cuti Lembur - Padding( - padding: EdgeInsets.only( - left: Constant.getActualXPhone(context: context, x: 33), - right: Constant.getActualXPhone(context: context, x: 27), - ), - child: Container( - width: Constant.getActualXPhone(context: context, x: 330), - child: Row( - children: [ - //Menu Cuti - 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: () {}, - child: Column( - children: [ - Container( - child: Image( - width: Constant.getActualXPhone( - context: context, x: 50), - height: Constant.getActualYPhone( - context: context, y: 50), - image: AssetImage('images/person.png'), - ), - ), - SizedBox( - height: Constant.getActualYPhone( - context: context, y: 8), - ), - Text( - 'Cuti', - style: Constant.titleH2_600_14( - context: context) - .copyWith( - color: Constant.textDarkGrey, - ), - ), - ], - ), - ), - ), - ), - - SizedBox( - width: - Constant.getActualXPhone(context: context, x: 18), - ), - - //Menu Lembur - 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: () {}, - child: Column( - children: [ - Container( - child: Image( - width: Constant.getActualXPhone( - context: context, x: 50), - height: Constant.getActualYPhone( - context: context, y: 50), - image: AssetImage('images/task.png'), - ), - ), - SizedBox( - height: Constant.getActualYPhone( - context: context, y: 8), - ), - Text( - 'Lembur', - style: Constant.titleH2_600_14( - context: context) - .copyWith( - color: Constant.textDarkGrey, - ), - ), - ], - ), - ), - ), - ), - ], - ), - ), - ), - - SizedBox( - height: Constant.getActualYPhone(context: context, y: 56), - ), - - //Menu Rekap Presensi - Padding( - padding: EdgeInsets.only( - right: Constant.getActualXPhone(context: context, x: 27), + child: RefreshIndicator( + onRefresh: () async {}, + child: Column( + children: [ + Padding( + padding: EdgeInsets.only( + top: Constant.getActualYPhone(context: context, y: 58), left: Constant.getActualXPhone(context: context, x: 33), - bottom: - Constant.getActualYPhone(context: context, y: 40)), - child: Container( - width: Constant.getActualXPhone(context: context, x: 330), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Rekap Presensi Bulan Ini', - style: Constant.titleH1_500_18(context: context) - .copyWith( - color: Constant.textTrueBlack, - ), + right: Constant.getActualXPhone(context: context, x: 27), + ), + child: Container( + child: (currentUserGoogle == null) + ? Center( + child: CircularProgressIndicator(), + ) + : ListTile( + // leading: Container( + // width: Constant.getActualXPhone( + // context: context, x: 36), + // height: Constant.getActualYPhone( + // context: context, y: 36), + // child: Image( + // image: AssetImage('images/avatar_c.png'), + // ), + // ), + leading: GoogleUserCircleAvatar( + identity: currentUserGoogle, + ), + title: Text( + // "Stephen Kusumo", + // selectedUser?.model.name ?? "", + currentUserGoogle.displayName ?? "", + overflow: TextOverflow.ellipsis, + style: Constant.titleH1_700(context: context) + ..copyWith( + color: Constant.textBlack, + ), + ), + subtitle: Text( + // "Step@example.com", + // currentUserGoogle?.email ?? "", + selectedUser?.model.email ?? "", + style: + Constant.subtitle_500_12(context: context) + .copyWith( + color: Constant.textLightGrey, + ), + ), + trailing: Container( + width: Constant.getActualXPhone( + context: context, x: 36), + height: Constant.getActualYPhone( + context: context, y: 36), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.0), + color: Colors.white, + shape: BoxShape.rectangle, + boxShadow: [ + BoxShadow( + offset: Offset(0, 12), + blurRadius: 24, + color: + Color.fromRGBO(145, 158, 171, 0.12), + ), + ], + ), + child: IconButton( + onPressed: () {}, + icon: Container( + width: Constant.getActualXPhone( + context: context, x: 20), + height: Constant.getActualYPhone( + context: context, y: 20), + child: Image( + image: + AssetImage('images/alert_badge.png'), + ), + ), + ), + ), + ), + ), + ), + + SizedBox( + height: Constant.getActualYPhone(context: context, y: 44), + ), + + //Card Time + Padding( + padding: EdgeInsets.only( + left: Constant.getActualXPhone(context: context, x: 33), + right: Constant.getActualXPhone(context: context, x: 27), + ), + child: Container( + width: Constant.getActualXPhone(context: context, x: 330), + height: + Constant.getActualYPhone(context: context, y: 200), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(40), + image: DecorationImage( + image: AssetImage( + 'images/card_bg_1.png'), // Ganti dengan path gambar Anda + fit: BoxFit.fill, // Sesuaikan cara gambar ditampilkan ), - SizedBox( - height: - Constant.getActualYPhone(context: context, y: 20), + ), + child: Padding( + padding: EdgeInsets.only( + top: + Constant.getActualYPhone(context: context, y: 16), + left: + Constant.getActualXPhone(context: context, x: 25), + right: + Constant.getActualXPhone(context: context, x: 25), ), - Container( - decoration: BoxDecoration( - shape: BoxShape.rectangle, - borderRadius: BorderRadius.circular(16), - color: Colors.white, // Set background color to #FFF - boxShadow: [ - BoxShadow( - color: Color.fromRGBO(145, 158, 171, 0.20), - blurRadius: 2, + child: Container( + width: Constant.getActualXPhone( + context: context, x: 280), + height: Constant.getActualYPhone( + context: context, y: 150), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Date + RealTimeFormattedDate(), + SizedBox( + height: Constant.getActualYPhone( + context: context, y: 8), + ), + + //Time + RealTimeClock(), // Menampilkan waktu real-time menggunakan RealTimeClock + SizedBox( + height: Constant.getActualYPhone( + context: context, y: 20), + ), + Row( + mainAxisAlignment: MainAxisAlignment + .center, // Menengahkan secara horizontal + children: [ + Spacer(), // Spasi di sebelah kiri "Check In" + Column( + children: [ + Image.asset( + 'images/finger_tap.png', // Path gambar untuk "Check In" + width: Constant.getActualXPhone( + context: context, x: 22), + height: Constant.getActualYPhone( + context: context, y: 22), + ), + SizedBox( + height: Constant.getActualYPhone( + context: context, y: 8), + ), + (varCurrentCheckJamProvider + ?.isAbsenClockIn == + "TRUE") + ? Text( + // '--:--', + varCurrentCheckJamProvider + ?.jamClockIn ?? + "NULL", + ) + : Text( + '--:--', + style: TextStyle( + // Atur gaya teks '--:--' sesuai kebutuhan + ), + ), + Text( + 'Clock In', + style: Constant.titleH2_700( + context: context) + .copyWith( + color: Constant.textLightGrey, + ), + ), + ], + ), + + SizedBox( + width: Constant.getActualXPhone( + context: context, x: 96), + ), // Jarak antara "Check In" dan "Check Out" + Column( + children: [ + Image.asset( + 'images/finger_tap.png', // Path gambar untuk "Check Out" + width: Constant.getActualXPhone( + context: context, x: 22), + height: Constant.getActualYPhone( + context: context, y: 22), + ), + SizedBox( + height: Constant.getActualYPhone( + context: context, y: 8), + ), + (varCurrentCheckJamProvider + ?.isAbsenClockOut == + "TRUE") + ? Text( + // '--:--', + varCurrentCheckJamProvider + ?.jamClockOut ?? + "NULL", + ) + : Text( + '--:--', + style: TextStyle( + // Atur gaya teks '--:--' sesuai kebutuhan + ), + ), + Text( + 'Clock Out', + style: Constant.titleH2_700( + context: context) + .copyWith( + color: Constant.textLightGrey, + ), + ), + ], + ), + Spacer(), // Spasi di sebelah kanan "Check Out" + ], + ), + + SizedBox( + height: Constant.getActualYPhone( + context: context, y: 16), ), ], ), - child: Padding( - padding: EdgeInsets.only( - top: Constant.getActualYPhone( - context: context, y: 12), - bottom: Constant.getActualYPhone( - context: context, y: 12), - left: Constant.getActualXPhone( - context: context, x: 24), - right: Constant.getActualXPhone( - context: context, x: 24), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - '24 hari', - style: Constant.subtitle_600_14( - context: context) - .copyWith( - color: Constant.textOrange, - ), - ), - SizedBox( - height: Constant.getActualYPhone( - context: context, y: 4), - ), - SizedBox( - child: Row( - children: [ - Image( - image: AssetImage( - 'images/person_available_grey.png'), - ), - SizedBox( - width: Constant.getActualXPhone( - context: context, x: 4), - ), - Text( - 'Kehadiran', - style: Constant.subtitle_500_12( - context: context) - .copyWith( - color: Constant.textDarkGrey, - ), - ), - ], - ), - ) - ], - ), - ), + ), + ), + ), + ), - Image( - image: AssetImage('images/divider.png'), - ), + SizedBox( + height: Constant.getActualYPhone(context: context, y: 56), + ), - //Tidak Hadir - Container( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - '2 hari', - style: Constant.subtitle_600_14( - context: context) - .copyWith( - color: Constant.textOrange, - ), - ), - SizedBox( - height: Constant.getActualYPhone( - context: context, y: 4), - ), - SizedBox( - child: Row( - children: [ - Image( - image: AssetImage( - 'images/person_delete_grey.png'), - ), - SizedBox( - width: Constant.getActualXPhone( - context: context, x: 4), - ), - Text( - 'Tidak Hadir', - style: Constant.subtitle_500_12( - context: context) - .copyWith( - color: Constant.textDarkGrey, - ), - ), - ], - ), - ) - ], - ), - ), - - Image( - image: AssetImage('images/divider.png'), - ), - - //Tidak Hadir - Container( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - '5 hari', - style: Constant.subtitle_600_14( - context: context) - .copyWith( - color: Constant.textOrange, - ), - ), - SizedBox( - height: Constant.getActualYPhone( - context: context, y: 4), - ), - SizedBox( - child: Row( - children: [ - Image( - image: AssetImage( - 'images/task_pending_grey.png'), - ), - SizedBox( - width: Constant.getActualXPhone( - context: context, x: 4), - ), - Text( - 'Lembur', - style: Constant.subtitle_500_12( - context: context) - .copyWith( - color: Constant.textDarkGrey, - ), - ), - ], - ), - ), - ], - ), + //Menu Cuti Lembur + Padding( + padding: EdgeInsets.only( + left: Constant.getActualXPhone(context: context, x: 33), + right: Constant.getActualXPhone(context: context, x: 27), + ), + child: Container( + width: Constant.getActualXPhone(context: context, x: 330), + child: Row( + children: [ + //Menu Cuti + 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: () {}, + child: Column( + children: [ + Container( + child: Image( + width: Constant.getActualXPhone( + context: context, x: 50), + height: Constant.getActualYPhone( + context: context, y: 50), + image: AssetImage('images/person.png'), + ), + ), + SizedBox( + height: Constant.getActualYPhone( + context: context, y: 8), + ), + Text( + 'Cuti', + style: Constant.titleH2_600_14( + context: context) + .copyWith( + color: Constant.textDarkGrey, + ), + ), + ], + ), + ), + ), ), - ), - ], + + SizedBox( + width: Constant.getActualXPhone( + context: context, x: 18), + ), + + //Menu Lembur + 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: () {}, + child: Column( + children: [ + Container( + child: Image( + width: Constant.getActualXPhone( + context: context, x: 50), + height: Constant.getActualYPhone( + context: context, y: 50), + image: AssetImage('images/task.png'), + ), + ), + SizedBox( + height: Constant.getActualYPhone( + context: context, y: 8), + ), + Text( + 'Lembur', + style: Constant.titleH2_600_14( + context: context) + .copyWith( + color: Constant.textDarkGrey, + ), + ), + ], + ), + ), + ), + ), + ], + ), ), ), - ), - ], + + SizedBox( + height: Constant.getActualYPhone(context: context, y: 56), + ), + + //Menu Rekap Presensi + Padding( + padding: EdgeInsets.only( + right: + Constant.getActualXPhone(context: context, x: 27), + left: Constant.getActualXPhone(context: context, x: 33), + bottom: + Constant.getActualYPhone(context: context, y: 40)), + child: Container( + width: Constant.getActualXPhone(context: context, x: 330), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Rekap Presensi Bulan Ini', + style: Constant.titleH1_500_18(context: context) + .copyWith( + color: Constant.textTrueBlack, + ), + ), + SizedBox( + height: Constant.getActualYPhone( + context: context, y: 20), + ), + Container( + decoration: BoxDecoration( + shape: BoxShape.rectangle, + borderRadius: BorderRadius.circular(16), + color: + Colors.white, // Set background color to #FFF + boxShadow: [ + BoxShadow( + color: Color.fromRGBO(145, 158, 171, 0.20), + blurRadius: 2, + ), + ], + ), + child: Padding( + padding: EdgeInsets.only( + top: Constant.getActualYPhone( + context: context, y: 12), + bottom: Constant.getActualYPhone( + context: context, y: 12), + left: Constant.getActualXPhone( + context: context, x: 24), + right: Constant.getActualXPhone( + context: context, x: 24), + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Container( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + '24 hari', + style: Constant.subtitle_600_14( + context: context) + .copyWith( + color: Constant.textOrange, + ), + ), + SizedBox( + height: Constant.getActualYPhone( + context: context, y: 4), + ), + SizedBox( + child: Row( + children: [ + Image( + image: AssetImage( + 'images/person_available_grey.png'), + ), + SizedBox( + width: Constant.getActualXPhone( + context: context, x: 4), + ), + Text( + 'Kehadiran', + style: Constant.subtitle_500_12( + context: context) + .copyWith( + color: Constant.textDarkGrey, + ), + ), + ], + ), + ) + ], + ), + ), + + Image( + image: AssetImage('images/divider.png'), + ), + + //Tidak Hadir + Container( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + '2 hari', + style: Constant.subtitle_600_14( + context: context) + .copyWith( + color: Constant.textOrange, + ), + ), + SizedBox( + height: Constant.getActualYPhone( + context: context, y: 4), + ), + SizedBox( + child: Row( + children: [ + Image( + image: AssetImage( + 'images/person_delete_grey.png'), + ), + SizedBox( + width: Constant.getActualXPhone( + context: context, x: 4), + ), + Text( + 'Tidak Hadir', + style: Constant.subtitle_500_12( + context: context) + .copyWith( + color: Constant.textDarkGrey, + ), + ), + ], + ), + ) + ], + ), + ), + + Image( + image: AssetImage('images/divider.png'), + ), + + //Tidak Hadir + Container( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + '5 hari', + style: Constant.subtitle_600_14( + context: context) + .copyWith( + color: Constant.textOrange, + ), + ), + SizedBox( + height: Constant.getActualYPhone( + context: context, y: 4), + ), + SizedBox( + child: Row( + children: [ + Image( + image: AssetImage( + 'images/task_pending_grey.png'), + ), + SizedBox( + width: Constant.getActualXPhone( + context: context, x: 4), + ), + Text( + 'Lembur', + style: Constant.subtitle_500_12( + context: context) + .copyWith( + color: Constant.textDarkGrey, + ), + ), + ], + ), + ), + ], + ), + ), + ], + ), + ), + ), + ], + ), + ), + ), + ], + ), ), ), ), diff --git a/lib/screen/login/login_screen.dart b/lib/screen/login/login_screen.dart index accdeef..80e8735 100644 --- a/lib/screen/login/login_screen.dart +++ b/lib/screen/login/login_screen.dart @@ -11,6 +11,7 @@ import 'package:shared_preferences/shared_preferences.dart'; import '../../app/constant.dart'; import '../../app/route.dart'; import '../../model/auth_model.dart'; +import '../../provider/current_menu_provider.dart'; import '../../provider/current_user_provider.dart'; import '../../provider/google_login_provider.dart'; @@ -92,6 +93,7 @@ class LoginScreen extends HookConsumerWidget { .read(currentUserGoogleProvider.notifier) .update((state) => account); ref.read(currentUserProvider.notifier).state = authModel; + ref.read(currentPageProvider.state).update((state) => 0); Navigator.of(context).pushNamedAndRemoveUntil( homeRoute, (route) => false, @@ -121,7 +123,7 @@ class LoginScreen extends HookConsumerWidget { } else if (next is LoginStateDone) { isLoading.value = false; isSuccess.value = true; - // ref.read(currentPageProvider.state).update((state) => 0); + ref.read(currentPageProvider.state).update((state) => 0); Navigator.of(context) .pushNamedAndRemoveUntil(homeRoute, (route) => false); } diff --git a/lib/screen/presensi/check_distance_provider.dart b/lib/screen/presensi/check_distance_provider.dart index b0e5c4d..ae6d2e2 100644 --- a/lib/screen/presensi/check_distance_provider.dart +++ b/lib/screen/presensi/check_distance_provider.dart @@ -1,3 +1,4 @@ +import 'package:absensi_sas_flutter/provider/current_user_provider.dart'; import 'package:absensi_sas_flutter/repository/presensi_repository.dart'; import 'package:equatable/equatable.dart'; @@ -7,6 +8,7 @@ import '../../provider/current_check_distance_provider.dart'; import '../../provider/dio_provider.dart'; import '../../provider/graphql_provider.dart'; import '../../repository/base_repository.dart'; +import 'check_presensi_jam_provider.dart'; abstract class CheckDistanceState extends Equatable { final DateTime date; @@ -63,6 +65,21 @@ class CheckDistanceNotifier extends StateNotifier { ); // set ke global state provider ref.read(currentCheckDistanceProvider.notifier).update((state) => resp); + + // panggil provider cek jam masuk dan pulang (checkPresensiJamProvider) + final user = ref.read(currentUserProvider); + Map inpVariablesCheckPresensiJam = { + "M_StaffID": M_StaffID, + "M_CompanyID": M_CompanyID, + "token": user?.token ?? "", + }; + ref.read(checkPresensiJamProvider.notifier).checkPresensiJam( + M_StaffID, + M_CompanyID, + user?.token ?? "", + inpVariablesCheckPresensiJam, + ); + state = CheckDistanceStateDone(model: resp); } catch (e) { if (e is BaseRepositoryException) { diff --git a/lib/screen/presensi/check_presensi_jam_provider.dart b/lib/screen/presensi/check_presensi_jam_provider.dart new file mode 100644 index 0000000..4e870f6 --- /dev/null +++ b/lib/screen/presensi/check_presensi_jam_provider.dart @@ -0,0 +1,81 @@ +import 'package:absensi_sas_flutter/model/check_presensi_jam_model.dart'; +import 'package:absensi_sas_flutter/repository/presensi_repository.dart'; + +import 'package:equatable/equatable.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../provider/current_check_jam_presensi_provider.dart'; +import '../../provider/dio_provider.dart'; +import '../../provider/graphql_provider.dart'; +import '../../repository/base_repository.dart'; + +abstract class CheckPresensiJamState extends Equatable { + final DateTime date; + const CheckPresensiJamState(this.date); + @override + List get props => [date]; +} + +class CheckPresensiJamStateInit extends CheckPresensiJamState { + CheckPresensiJamStateInit() : super(DateTime.now()); +} + +class CheckPresensiJamStateLoading extends CheckPresensiJamState { + CheckPresensiJamStateLoading() : super(DateTime.now()); +} + +class CheckPresensiJamStateError extends CheckPresensiJamState { + final String message; + CheckPresensiJamStateError({ + required this.message, + }) : super(DateTime.now()); +} + +class CheckPresensiJamStateDone extends CheckPresensiJamState { + final CheckPresensiJamModel model; + CheckPresensiJamStateDone({ + required this.model, + }) : super(DateTime.now()); +} + +//notifier +class CheckPresensiJamNotifier extends StateNotifier { + final Ref ref; + CheckPresensiJamNotifier({ + required this.ref, + }) : super(CheckPresensiJamStateInit()); + + void checkPresensiJam( + String M_StaffID, + String M_CompanyID, + String token, + Map paramInpVariables, + ) async { + try { + state = CheckPresensiJamStateLoading(); + final graphql = ref.read(graphqlProvider('')); + final dio = ref.read(dioProvider); + final resp = await PresensiRepository(graphql: graphql, dio: dio) + .checkPresensiJam( + M_StaffID, + M_CompanyID, + token, + paramInpVariables, + ); + // set ke global state provider currentCheckJamPresensiProvider + ref.read(currentCheckJamPresensiProvider.notifier).update((state) => resp); + state = CheckPresensiJamStateDone(model: resp); + } catch (e) { + if (e is BaseRepositoryException) { + state = CheckPresensiJamStateError(message: e.message ?? ""); + } else { + state = CheckPresensiJamStateError(message: e.toString()); + } + } + } +} + +// provider +final checkPresensiJamProvider = + StateNotifierProvider( + (ref) => CheckPresensiJamNotifier(ref: ref), +); diff --git a/lib/screen/presensi/presensi_clock_in_provider.dart b/lib/screen/presensi/presensi_clock_in_provider.dart new file mode 100644 index 0000000..be04ec2 --- /dev/null +++ b/lib/screen/presensi/presensi_clock_in_provider.dart @@ -0,0 +1,102 @@ +import 'package:absensi_sas_flutter/repository/presensi_repository.dart'; + +import 'package:equatable/equatable.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../provider/dio_provider.dart'; +import '../../provider/graphql_provider.dart'; +import '../../repository/base_repository.dart'; +import 'check_presensi_jam_provider.dart'; + +abstract class PresensiClockInState extends Equatable { + final DateTime date; + const PresensiClockInState(this.date); + @override + List get props => [date]; +} + +class PresensiClockInStateInit extends PresensiClockInState { + PresensiClockInStateInit() : super(DateTime.now()); +} + +class PresensiClockInStateLoading extends PresensiClockInState { + PresensiClockInStateLoading() : super(DateTime.now()); +} + +class PresensiClockInStateError extends PresensiClockInState { + final String message; + PresensiClockInStateError({ + required this.message, + }) : super(DateTime.now()); +} + +class PresensiClockInStateDone extends PresensiClockInState { + final String model; + PresensiClockInStateDone({ + required this.model, + }) : super(DateTime.now()); +} + +//notifier +class PresensiClockInNotifier extends StateNotifier { + final Ref ref; + PresensiClockInNotifier({ + required this.ref, + }) : super(PresensiClockInStateInit()); + + void presensiClockIn( + String T_TransactionM_StaffID, + String T_TransactionM_CompanyID, + String T_TransactionCurrentLatitude, + String T_TransactionCurrentLongitude, + String T_TransactionCurrentDistance, + String T_TransactionSelfiePhoto, + String token, + String isSelfie, + Map paramInpVariables, + ) async { + try { + state = PresensiClockInStateLoading(); + final graphql = ref.read(graphqlProvider('')); + final dio = ref.read(dioProvider); + final resp = await PresensiRepository(graphql: graphql, dio: dio) + .presensiNormalClockIn( + T_TransactionM_StaffID, + T_TransactionM_CompanyID, + T_TransactionCurrentLatitude, + T_TransactionCurrentLongitude, + T_TransactionCurrentDistance, + T_TransactionSelfiePhoto, + token, + isSelfie, + paramInpVariables, + ); + + // panggil provider cek jam masuk dan pulang (checkPresensiJamProvider) + Map inpVariablesCheckPresensiJam = { + "M_StaffID": T_TransactionM_StaffID, + "M_CompanyID": T_TransactionM_CompanyID, + "token": token, + }; + ref.read(checkPresensiJamProvider.notifier).checkPresensiJam( + T_TransactionM_StaffID, + T_TransactionM_CompanyID, + token, + inpVariablesCheckPresensiJam, + ); + print('call check presensi'); + state = PresensiClockInStateDone(model: resp); + } catch (e) { + if (e is BaseRepositoryException) { + state = PresensiClockInStateError(message: e.message ?? ""); + } else { + state = PresensiClockInStateError(message: e.toString()); + } + } + } +} + +// provider +final presensiClockInProvider = + StateNotifierProvider( + (ref) => PresensiClockInNotifier(ref: ref), +); diff --git a/lib/screen/presensi/presensi_clock_out_provider.dart b/lib/screen/presensi/presensi_clock_out_provider.dart new file mode 100644 index 0000000..b18bef2 --- /dev/null +++ b/lib/screen/presensi/presensi_clock_out_provider.dart @@ -0,0 +1,102 @@ +import 'package:absensi_sas_flutter/repository/presensi_repository.dart'; + +import 'package:equatable/equatable.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../provider/dio_provider.dart'; +import '../../provider/graphql_provider.dart'; +import '../../repository/base_repository.dart'; +import 'check_presensi_jam_provider.dart'; + +abstract class PresensiClockOutState extends Equatable { + final DateTime date; + const PresensiClockOutState(this.date); + @override + List get props => [date]; +} + +class PresensiClockOutStateInit extends PresensiClockOutState { + PresensiClockOutStateInit() : super(DateTime.now()); +} + +class PresensiClockOutStateLoading extends PresensiClockOutState { + PresensiClockOutStateLoading() : super(DateTime.now()); +} + +class PresensiClockOutStateError extends PresensiClockOutState { + final String message; + PresensiClockOutStateError({ + required this.message, + }) : super(DateTime.now()); +} + +class PresensiClockOutStateDone extends PresensiClockOutState { + final String model; + PresensiClockOutStateDone({ + required this.model, + }) : super(DateTime.now()); +} + +//notifier +class PresensiClockOutNotifier extends StateNotifier { + final Ref ref; + PresensiClockOutNotifier({ + required this.ref, + }) : super(PresensiClockOutStateInit()); + + void presensiClockOut( + String T_TransactionM_StaffID, + String T_TransactionM_CompanyID, + String T_TransactionCurrentLatitude, + String T_TransactionCurrentLongitude, + String T_TransactionCurrentDistance, + String T_TransactionSelfiePhoto, + String token, + String isSelfie, + Map paramInpVariables, + ) async { + try { + state = PresensiClockOutStateLoading(); + final graphql = ref.read(graphqlProvider('')); + final dio = ref.read(dioProvider); + final resp = await PresensiRepository(graphql: graphql, dio: dio) + .presensiNormalClockOut( + T_TransactionM_StaffID, + T_TransactionM_CompanyID, + T_TransactionCurrentLatitude, + T_TransactionCurrentLongitude, + T_TransactionCurrentDistance, + T_TransactionSelfiePhoto, + token, + isSelfie, + paramInpVariables, + ); + + // panggil provider cek jam masuk dan pulang (checkPresensiJamProvider) + Map inpVariablesCheckPresensiJam = { + "M_StaffID": T_TransactionM_StaffID, + "M_CompanyID": T_TransactionM_CompanyID, + "token": token, + }; + ref.read(checkPresensiJamProvider.notifier).checkPresensiJam( + T_TransactionM_StaffID, + T_TransactionM_CompanyID, + token, + inpVariablesCheckPresensiJam, + ); + print('call check presensi clockout'); + state = PresensiClockOutStateDone(model: resp); + } catch (e) { + if (e is BaseRepositoryException) { + state = PresensiClockOutStateError(message: e.message ?? ""); + } else { + state = PresensiClockOutStateError(message: e.toString()); + } + } + } +} + +// provider +final presensiClockOutProvider = + StateNotifierProvider( + (ref) => PresensiClockOutNotifier(ref: ref), +); diff --git a/lib/screen/presensi/presensi_screen.dart b/lib/screen/presensi/presensi_screen.dart index 4899b30..7f1fed8 100644 --- a/lib/screen/presensi/presensi_screen.dart +++ b/lib/screen/presensi/presensi_screen.dart @@ -1,4 +1,9 @@ +import 'dart:convert'; + +import 'package:absensi_sas_flutter/provider/current_check_jam_presensi_provider.dart'; import 'package:absensi_sas_flutter/screen/presensi/check_distance_provider.dart'; +import 'package:absensi_sas_flutter/screen/presensi/check_presensi_jam_provider.dart'; +import 'package:absensi_sas_flutter/screen/presensi/presensi_clock_in_provider.dart'; import 'package:absensi_sas_flutter/widget/real_date.dart'; import 'package:absensi_sas_flutter/widget/sankbar_widget.dart'; import 'package:flutter/material.dart'; @@ -12,7 +17,9 @@ import '../../app/constant.dart'; import '../../app/route.dart'; import '../../provider/current_check_distance_provider.dart'; import '../../provider/current_user_provider.dart'; +import '../../widget/custom_drawer.dart'; import '../../widget/real_time.dart'; +import 'presensi_clock_out_provider.dart'; class PresensiScreen extends HookConsumerWidget { const PresensiScreen({super.key}); @@ -23,7 +30,9 @@ class PresensiScreen extends HookConsumerWidget { final isLoadingAddressUserLocation = useState(false); final isLoadingProsesCheckDistance = useState(false); final varCurrentDistanceProvider = ref.watch(currentCheckDistanceProvider); - final selectedUser = ref.read(currentUserProvider); + final varCurrentCheckJamProvider = + ref.watch(currentCheckJamPresensiProvider); + final selectedUser = ref.watch(currentUserProvider); final positionLatitude = useState(""); final positionLongitude = useState(""); @@ -78,6 +87,7 @@ class PresensiScreen extends HookConsumerWidget { if (positionLatitude.value.isNotEmpty && positionLongitude.value.isNotEmpty) { print('check distance provider'); + // panggil check distance provider ref.read(checkDistanceProvider.notifier).checkDistance( selectedUser?.model.staffId ?? "", @@ -130,6 +140,64 @@ class PresensiScreen extends HookConsumerWidget { } }); + // check jam presensi + ref.listen(checkPresensiJamProvider, (prev, next) { + if (next is CheckPresensiJamStateLoading) { + isLoadingProsesCheckDistance.value = true; + } else if (next is CheckPresensiJamStateError) { + isLoadingProsesCheckDistance.value = false; + SanckbarWidget( + context, "Error : " + next.toString(), snackbarType.warning); + } else if (next is CheckPresensiJamStateDone) { + isLoadingProsesCheckDistance.value = false; + } + }); + + // proses presensi clock in + ref.listen(presensiClockInProvider, (prev, next) { + if (next is PresensiClockInStateLoading) { + isLoadingProsesCheckDistance.value = true; + } else if (next is PresensiClockInStateError) { + isLoadingProsesCheckDistance.value = false; + SanckbarWidget( + context, "Error : " + next.toString(), snackbarType.warning); + } else if (next is PresensiClockInStateDone) { + isLoadingProsesCheckDistance.value = false; + if (next.model == "OK") { + SanckbarWidget(context, "Berhasil Absen Masuk", snackbarType.success); + requestLocationPermission(); + } + // else{ + // if(next.model == "WARNING"){ + // SanckbarWidget(context, "Berhasil Absen Masuk", snackbarType.success); + // } + // } + } + }); + + // proses presensi clock in + ref.listen(presensiClockOutProvider, (prev, next) { + if (next is PresensiClockOutStateLoading) { + isLoadingProsesCheckDistance.value = true; + } else if (next is PresensiClockOutStateError) { + isLoadingProsesCheckDistance.value = false; + SanckbarWidget( + context, "Error : " + next.toString(), snackbarType.warning); + } else if (next is PresensiClockOutStateDone) { + isLoadingProsesCheckDistance.value = false; + if (next.model == "OK") { + SanckbarWidget( + context, "Berhasil Absen Pulang", snackbarType.success); + requestLocationPermission(); + } + // else{ + // if(next.model == "WARNING"){ + // SanckbarWidget(context, "Berhasil Absen Masuk", snackbarType.success); + // } + // } + } + }); + useEffect(() { WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { final staffID = ref.read(currentUserProvider)?.model.staffId ?? "0"; @@ -150,18 +218,6 @@ class PresensiScreen extends HookConsumerWidget { useEffect(() { WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { requestLocationPermission(); - - // // jika sudah dapat latitude dan logitude baru panggil check distance provider - // if (positionLatitude.value.isNotEmpty && - // positionLongitude.value.isNotEmpty) { - // // panggil check distance provider - // ref.read(checkDistanceProvider.notifier).checkDistance( - // selectedUser?.model.staffId ?? "", - // selectedUser?.model.companyId ?? "", - // positionLatitude.value, - // positionLongitude.value, - // ); - // } }); return () {}; }, []); @@ -176,13 +232,14 @@ class PresensiScreen extends HookConsumerWidget { String T_TransactionCurrentDistance, String T_TransactionSelfiePhoto, String token, + String tipeAbsen, ) async { return showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: Text('Konfirmasi'), - content: Text('Apakah anda yakin untuk melakukan Clock In?'), + content: Text('Apakah anda yakin untuk melakukan $tipeAbsen?'), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.0), ), @@ -191,7 +248,7 @@ class PresensiScreen extends HookConsumerWidget { onPressed: () { T_TransactionSelfiePhoto = ""; - var param = { + Map param = { "T_TransactionM_StaffID": T_TransactionM_StaffID, "T_TransactionM_CompanyID": T_TransactionM_CompanyID, "T_TransactionCurrentLatitude": @@ -201,10 +258,42 @@ class PresensiScreen extends HookConsumerWidget { "T_TransactionCurrentDistance": T_TransactionCurrentDistance, "T_TransactionSelfiePhoto": T_TransactionSelfiePhoto, - "token":token, + "token": token, + "isSelfie": "FALSE" }; - print(param); - Navigator.of(context).pop(); + // print(param); + + if (tipeAbsen == "Clock In") { + ref.read(presensiClockInProvider.notifier).presensiClockIn( + T_TransactionM_StaffID, + T_TransactionM_CompanyID, + T_TransactionCurrentLatitude, + T_TransactionCurrentLongitude, + T_TransactionCurrentDistance, + T_TransactionSelfiePhoto, + token, + "FALSE", + param, + ); + Navigator.of(context).pop(); + } else { + if (tipeAbsen == "Clock Out") { + ref + .read(presensiClockOutProvider.notifier) + .presensiClockOut( + T_TransactionM_StaffID, + T_TransactionM_CompanyID, + T_TransactionCurrentLatitude, + T_TransactionCurrentLongitude, + T_TransactionCurrentDistance, + T_TransactionSelfiePhoto, + token, + "FALSE", + param, + ); + Navigator.of(context).pop(); + } + } }, child: Text( 'Yakin', @@ -280,6 +369,7 @@ class PresensiScreen extends HookConsumerWidget { // elevation: 1.0, elevation: 0.5, ), + drawer: CustomDrawer(), body: SafeArea( child: Padding( padding: EdgeInsets.only( @@ -415,74 +505,137 @@ class PresensiScreen extends HookConsumerWidget { height: Constant.getActualYPhone(context: context, y: 50), ), - // gambar icon presensi - Container( - width: Constant.getActualXPhone(context: context, x: 130), - height: Constant.getActualYPhone(context: context, y: 130), - child: FittedBox( - child: FloatingActionButton( - onPressed: () async { - final T_TransactionM_StaffID = - selectedUser?.model.staffId ?? ""; - final T_TransactionM_CompanyID = - selectedUser?.model.companyId ?? ""; - final T_TransactionCurrentLatitude = - positionLatitude.value; - final T_TransactionCurrentLongitude = - positionLongitude.value; - final T_TransactionCurrentDistance = - varCurrentDistanceProvider?.currentDistance ?? ""; - final T_TransactionSelfiePhoto = ""; - final token = selectedUser?.token ?? ""; + // tombol aksi absen masuk & pulang + if (varCurrentCheckJamProvider?.isAbsenClockIn == "TRUE" && + varCurrentCheckJamProvider?.isAbsenClockIn == "TRUE") ...[ + SizedBox.shrink() + ] else ...[ + if (varCurrentCheckJamProvider?.isAbsenClockIn == "FALSE" && + varCurrentCheckJamProvider?.jamClockIn == "") ...[ + // gambar icon presensi clock in + (isLoadingAddressUserLocation.value) + ? Center( + child: CircularProgressIndicator(), + ) + : Container( + width: Constant.getActualXPhone( + context: context, x: 130), + height: Constant.getActualYPhone( + context: context, y: 130), + child: FittedBox( + child: FloatingActionButton( + onPressed: () async { + final T_TransactionM_StaffID = + selectedUser?.model.staffId ?? ""; + final T_TransactionM_CompanyID = + selectedUser?.model.companyId ?? ""; + final T_TransactionCurrentLatitude = + positionLatitude.value; + final T_TransactionCurrentLongitude = + positionLongitude.value; + final T_TransactionCurrentDistance = + varCurrentDistanceProvider + ?.currentDistance ?? + ""; + final T_TransactionSelfiePhoto = ""; + final token = selectedUser?.token ?? ""; - showConfirmationDialog( - context, - T_TransactionM_StaffID, - T_TransactionM_CompanyID, - T_TransactionCurrentLatitude, - T_TransactionCurrentLongitude, - T_TransactionCurrentDistance, - T_TransactionSelfiePhoto, - token, - ); - }, - backgroundColor: Color(0xFFFFFFFF), - shape: CircleBorder(), - child: Container( - width: Constant.getActualXPhone( - context: context, x: 130), - height: Constant.getActualYPhone( - context: context, y: 130), - decoration: BoxDecoration( - image: DecorationImage( - // fit: BoxFit.cover, - image: AssetImage( - 'images/presensi_finger2.png'), // Ganti dengan path gambar Anda + showConfirmationDialog( + context, + T_TransactionM_StaffID, + T_TransactionM_CompanyID, + T_TransactionCurrentLatitude, + T_TransactionCurrentLongitude, + T_TransactionCurrentDistance, + T_TransactionSelfiePhoto, + token, + "Clock In"); + }, + backgroundColor: Color(0xFFFFFFFF), + shape: CircleBorder(), + child: Container( + width: Constant.getActualXPhone( + context: context, x: 130), + height: Constant.getActualYPhone( + context: context, y: 130), + decoration: BoxDecoration( + image: DecorationImage( + // fit: BoxFit.cover, + image: AssetImage( + 'images/presensi_finger2.png'), // Ganti dengan path gambar Anda + ), + ), + ), + ), + ), ), - ), - ), - ), - ), - ), + ] else ...[ + // gambar icon presensi clock out + if (varCurrentCheckJamProvider?.isAbsenClockIn == + "TRUE" && + varCurrentCheckJamProvider?.jamClockIn != "") ...[ + (isLoadingAddressUserLocation.value) + ? Center( + child: CircularProgressIndicator(), + ) + : Container( + width: Constant.getActualXPhone( + context: context, x: 130), + height: Constant.getActualYPhone( + context: context, y: 130), + child: FittedBox( + child: FloatingActionButton( + onPressed: () async { + final T_TransactionM_StaffID = + selectedUser?.model.staffId ?? ""; + final T_TransactionM_CompanyID = + selectedUser?.model.companyId ?? ""; + final T_TransactionCurrentLatitude = + positionLatitude.value; + final T_TransactionCurrentLongitude = + positionLongitude.value; + final T_TransactionCurrentDistance = + varCurrentDistanceProvider + ?.currentDistance ?? + ""; + final T_TransactionSelfiePhoto = ""; + final token = selectedUser?.token ?? ""; - // Container( - // width: Constant.getActualXPhone(context: context, x: 100), - // height: Constant.getActualYPhone(context: context, y: 200), - // decoration: BoxDecoration( - // image: DecorationImage( - // fit: BoxFit.cover, - // image: AssetImage( - // 'images/presensi_finger1.png'), // Ganti dengan path gambar Anda - // ), - // ), - // ), + showConfirmationDialog( + context, + T_TransactionM_StaffID, + T_TransactionM_CompanyID, + T_TransactionCurrentLatitude, + T_TransactionCurrentLongitude, + T_TransactionCurrentDistance, + T_TransactionSelfiePhoto, + token, + "Clock Out"); + }, + backgroundColor: Color(0xFFFFFFFF), + shape: CircleBorder(), + child: Container( + width: Constant.getActualXPhone( + context: context, x: 130), + height: Constant.getActualYPhone( + context: context, y: 130), + decoration: BoxDecoration( + image: DecorationImage( + // fit: BoxFit.cover, + image: AssetImage( + 'images/presensi_finger_clok_out.png'), // Ganti dengan path gambar Anda + ), + ), + ), + ), + ), + ), + ] + ], + ], Spacer(), - // Expanded( - // child: SizedBox(), - // ), - // clock in & clock out Padding( padding: EdgeInsets.only( @@ -510,19 +663,44 @@ class PresensiScreen extends HookConsumerWidget { height: Constant.getActualYPhone( context: context, y: 8), ), - Text( - '--:--', - style: Constant.titlePresensiH2_700( - context: context) - .copyWith( - color: Constant.textTrueBlack, - ), + (isLoadingAddressUserLocation.value) + ? Center( + child: CircularProgressIndicator(), + ) + : (varCurrentCheckJamProvider + ?.isAbsenClockIn == + "TRUE" && + varCurrentCheckJamProvider + ?.jamClockIn != + "") + ? Text( + // '--:--', + varCurrentCheckJamProvider + ?.jamClockIn ?? + "NULL", + style: Constant.titlePresensiH2_700( + context: context) + .copyWith( + color: Constant.textTrueBlack, + ), + ) + : Text( + '--:--', + style: Constant.titlePresensiH2_700( + context: context) + .copyWith( + color: Constant.textTrueBlack, + ), + ), + SizedBox( + height: Constant.getActualYPhone( + context: context, y: 4), ), Text( 'Clock In', style: Constant.titleH2_700(context: context) .copyWith( - color: Constant.textTrueBlack, + color: Constant.textLightGrey, ), ), ], @@ -541,19 +719,44 @@ class PresensiScreen extends HookConsumerWidget { height: Constant.getActualYPhone( context: context, y: 8), ), - Text( - '--:--', - style: Constant.titlePresensiH2_700( - context: context) - .copyWith( - color: Constant.textTrueBlack, - ), + (isLoadingAddressUserLocation.value) + ? Center( + child: CircularProgressIndicator(), + ) + : (varCurrentCheckJamProvider + ?.isAbsenClockOut == + "TRUE" && + varCurrentCheckJamProvider + ?.jamClockOut != + "") + ? Text( + // '--:--', + varCurrentCheckJamProvider + ?.jamClockOut ?? + "NULL", + style: Constant.titlePresensiH2_700( + context: context) + .copyWith( + color: Constant.textTrueBlack, + ), + ) + : Text( + '--:--', + style: Constant.titlePresensiH2_700( + context: context) + .copyWith( + color: Constant.textTrueBlack, + ), + ), + SizedBox( + height: Constant.getActualYPhone( + context: context, y: 4), ), Text( 'Clock Out', style: Constant.titleH2_700(context: context) .copyWith( - color: Constant.textTrueBlack, + color: Constant.textLightGrey, ), ), ], @@ -567,81 +770,6 @@ class PresensiScreen extends HookConsumerWidget { ), ), ), - // bottomNavigationBar: Container( - // width: Constant.getActualXPhone(context: context, x: 390), - // height: Constant.getActualYPhone(context: context, y: 84), - // decoration: BoxDecoration( - // color: Color(0xFFFFFFFF), - // boxShadow: [ - // BoxShadow( - // offset: Offset(0, -1), - // blurRadius: 8, - // spreadRadius: -8, - // color: Color.fromRGBO(0, 0, 0, 0.10), - // ), - // ], - // ), - // child: Row( - // children: [ - // Expanded( - // child: Container( - // child: Column( - // children: [ - // Image.asset( - // 'images/clockin_presensi.png', // Path gambar untuk "Clock In" - // width: Constant.getActualXPhone(context: context, x: 22), - // height: Constant.getActualYPhone(context: context, y: 22), - // ), - // SizedBox( - // height: Constant.getActualYPhone(context: context, y: 8), - // ), - // Text( - // '--:--', - // style: Constant.titlePresensiH2_700(context: context) - // .copyWith( - // color: Constant.textTrueBlack, - // ), - // ), - // Text( - // 'Clock In', - // style: Constant.titleH2_700(context: context).copyWith( - // color: Constant.textTrueBlack, - // ), - // ), - // ], - // ), - // )), - // Expanded( - // child: Container( - // child: Column( - // children: [ - // Image.asset( - // 'images/clockout_presensi.png', // Path gambar untuk "Check In" - // width: Constant.getActualXPhone(context: context, x: 22), - // height: Constant.getActualYPhone(context: context, y: 22), - // ), - // SizedBox( - // height: Constant.getActualYPhone(context: context, y: 8), - // ), - // Text( - // '--:--', - // style: Constant.titlePresensiH2_700(context: context) - // .copyWith( - // color: Constant.textTrueBlack, - // ), - // ), - // Text( - // 'Clock Out', - // style: Constant.titleH2_700(context: context).copyWith( - // color: Constant.textTrueBlack, - // ), - // ), - // ], - // ), - // )), - // ], - // ), - // ), ), ); } diff --git a/lib/widget/custom_drawer.dart b/lib/widget/custom_drawer.dart new file mode 100644 index 0000000..69eb791 --- /dev/null +++ b/lib/widget/custom_drawer.dart @@ -0,0 +1,183 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../app/constant.dart'; +import '../app/route.dart'; +import '../provider/current_menu_provider.dart'; +import '../provider/current_user_provider.dart'; +import '../screen/login/logout_provider.dart'; + +class CustomDrawer extends HookConsumerWidget { + // final String userCompany; + const CustomDrawer({ + Key? key, + // required this.userCompany, + }) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final selectedUser = ref.read(currentUserProvider); + final isLoading = useState(false); + final errorMessage = useState(""); + final successMessage = useState(""); + final M_CompanyName = useState("-"); + + // useEffect(() { + // WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { + // final staffID = ref.read(currentUserProvider)?.model.staffId ?? "0"; + // if (staffID == "0") { + // //not login + // Navigator.of(context) + // .pushNamedAndRemoveUntil(loginRoute, (route) => true); + + // // Navigator.popAndPushNamed(context, loginRoute); + // return; + // } + // }); + // return () {}; + // }, []); + + ref.listen(logoutProvider, (prev, next) async { + if (next is LogoutStateLoading) { + isLoading.value = true; + } else if (next is LogoutStateError) { + isLoading.value = false; + errorMessage.value = next.message; + Timer(const Duration(seconds: 3), () { + errorMessage.value = ""; + }); + } else if (next is LogoutStateDone) { + isLoading.value = false; + final shared = await SharedPreferences.getInstance(); + final bearerString = shared.get(Constant.bearerName).toString(); + // print(bearerString); + if (bearerString.isNotEmpty) { + shared.remove(bearerString); + shared.clear(); + // Navigator.popAndPushNamed(context, loginRoute); + Navigator.of(context) + .pushNamedAndRemoveUntil(loginRoute, (route) => false); + } + Timer(const Duration(seconds: 3), () async { + successMessage.value = ""; + }); + } + }); + + final currentMenu = ref.read(currentPageProvider); + + return Container( + width: Constant.getActualXPhone(context: context, x: 300), + height: Constant.getActualYPhone(context: context, y: 844), + child: Drawer( + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(0))), + child: Column( + children: [ + Expanded( + child: ListView( + children: [ + // Container( + // child: Image( + // image: + // AssetImage('images/logo_sismedika_landscape.png')), + // ), + + SizedBox( + height: Constant.getActualYPhone(context: context, y: 8), + ), + + Padding( + padding: EdgeInsets.only( + top: Constant.getActualYPhone(context: context, y: 10), + bottom: Constant.getActualYPhone(context: context, y: 10), + right: Constant.getActualXPhone(context: context, x: 24), + left: Constant.getActualXPhone(context: context, x: 24), + ), + child: Container( + width: Constant.getActualXPhone(context: context, x: 300), + ), + ), + Chip( + backgroundColor: Constant.textLightGrey.withOpacity(0.16), + label: Text( + M_CompanyName.value, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: Constant.textLightGrey, + ), + ), + ), + + ListTile( + leading: Icon( + Icons.home, + color: (currentMenu == 0) + ? Constant.textOrange + : Constant.textLightGrey, + ), + title: Text( + 'Home', + style: TextStyle( + color: (currentMenu == 0) + ? Constant.textOrange + : Constant.textLightGrey, + ), + ), + onTap: () { + // Handle navigation to Home screen + Navigator.pop(context); + ref.read(currentPageProvider.state).update((state) => 0); + Navigator.pushNamed(context, homeRoute); + }, + ), + + ListTile( + leading: Icon( + Icons.logout, + color: Constant.textLightGrey, + ), + title: Text( + 'Logout', + style: TextStyle(color: Constant.textLightGrey), + ), + onTap: () { + // di set ke 0 lagi + ref.read(currentPageProvider.state).update((state) => 0); + // ref.read(logoutProvider.notifier).logout( + // M_UserID: selectedUser?.model.M_UserID ?? "", + // M_UserUsername: + // selectedUser?.model.M_UserUsername ?? "", + // ); + }, + ), + ], + ), + ), + // Versi Aplikasi + Padding( + padding: EdgeInsets.only( + right: Constant.getActualXPhone(context: context, x: 20), + bottom: Constant.getActualYPhone(context: context, y: 10), + ), + child: Align( + alignment: Alignment.bottomRight, + child: Text( + 'Versi ${Constant.version}', + style: Constant.titleH1_700(context: context) + .copyWith(color: Constant.textLightGrey), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 82db703..e334ccb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.0.0+1 +version: 1.0.1 environment: sdk: '>=3.0.6 <4.0.0'