import 'dart:io'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:generp/screens/notifierExports.dart'; import 'package:generp/Utils/app_colors.dart'; import 'package:generp/Utils/commonWidgets.dart'; import 'package:generp/screens/CheckInScreen.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; import '../Utils/commonServices.dart'; class AttendanceScreen extends StatefulWidget { const AttendanceScreen({super.key}); @override State createState() => _AttendanceScreenState(); } class _AttendanceScreenState extends State { // var homeProvider; Map _source = {ConnectivityResult.mobile: true}; final MyConnectivity _connectivity = MyConnectivity.instance; @override void initState() { super.initState(); _connectivity.initialise(); _connectivity.myStream.listen((source) { setState(() => _source = source); }); final homeProvider = Provider.of(context, listen: false); WidgetsBinding.instance.addPostFrameCallback((_) { final att_provider = Provider.of(context, listen: false); att_provider.getAttendanceList(context); att_provider.init(context); }); } @override void dispose() { // TODO: implement dispose super.dispose(); _connectivity.disposeStream(); } @override Widget build(BuildContext context) { switch (_source.keys.toList()[0]) { case ConnectivityResult.mobile: connection = 'Online'; break; case ConnectivityResult.wifi: connection = 'Online'; break; case ConnectivityResult.none: default: connection = 'Offline'; } return (connection == 'Online') ? Platform.isAndroid ? WillPopScope( onWillPop: () => onBackPressed(context), child: SafeArea( top: false, bottom: true, child: _scaffold(context), ), ) : _scaffold(context) : NoNetwork(context); } Widget _scaffold(BuildContext context) { return Consumer( builder: (context, attendance, index) { final timeString = attendance.attendanceHistory.firstOrNull?['check_in_time'] .toString() ?? ""; final match = RegExp( r'^(\d{1,2}:\d{2})(am|pm)$', caseSensitive: false, ).firstMatch(timeString.replaceAll(' ', '').toLowerCase()); final timeString2 = attendance.attendanceHistory.firstOrNull?['check_out_time'] .toString() ?? ""; final match2 = RegExp( r'^(\d{1,2}:\d{2})(am|pm)$', caseSensitive: false, ).firstMatch(timeString2.replaceAll(' ', '').toLowerCase()); String formattedTime = match?.group(1) ?? "-"; String period = match?.group(2)?.toUpperCase() ?? ""; String formattedTime2 = match2?.group(1) ?? "-"; String period2 = match2?.group(2)?.toUpperCase() ?? ""; final dateArrayList = attendance.dateArrayList; final penalityArrayList = attendance.penalityArrayList; var selectedIndex = attendance.selectedIndex; // Calculate the custom period: 26th of previous month to 25th of current month final startDate = DateTime( attendance.month.year, attendance.month.month - 1, 26, ); final endDate = DateTime( attendance.month.year, attendance.month.month, 25, ); final daysInPeriod = endDate.difference(startDate).inDays + 1; final startingIndex = startDate.weekday % 7; // 0 for Sunday, 1 for Monday, etc. final currentDate = DateTime.now(); // Format the month label (e.g., "Mar - Apr 2025") final prevMonthName = DateFormat( 'MMM', ).format(DateTime(attendance.month.year, attendance.month.month - 1)); final currentMonthName = DateFormat('MMM').format(attendance.month); final year = DateFormat('yyyy').format(attendance.month); // final monthLabel = // prevMonthName == currentMonthName // ? '$currentMonthName $year' // : '$prevMonthName - $currentMonthName $year'; final monthLabel = '$currentMonthName $year'; return RefreshIndicator.adaptive( color: AppColors.app_blue, onRefresh: () async { await Future.delayed(const Duration(milliseconds: 600)); attendance.getAttendanceList(context); attendance.init(context); attendance.loadAttendanceDetails(context); }, child: Scaffold( resizeToAvoidBottomInset: true, backgroundColor: AppColors.scaffold_bg_color, appBar: appbarNew(context, "Attendance", 0xFFFFFFFF), body:SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical( bottom: Radius.circular(20), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: EdgeInsets.only( left: 15, right: 10, top: 15, ), child: Row( children: List.generate(2, (j) { final heads = ["Check-in", "Check-out"]; return Expanded( child: Text( heads[j], style: TextStyle( color: AppColors.grey_thick, fontSize: 14, fontFamily: "JakartaMedium", ), ), ); }), ), ), SizedBox( child: Row( children: List.generate(2, (iii) { final times = [formattedTime, formattedTime2]; final periods = [period, period2]; final locations = [ attendance.attendanceHistory.isNotEmpty? attendance .attendanceHistory .first['check_in_location'].toString()??"-":"-", attendance.attendanceHistory.isNotEmpty? attendance .attendanceHistory .first['check_out_location'].toString()??"-":"-", ]; return Expanded( child: Container( padding: EdgeInsets.symmetric( horizontal: 10, vertical: 15, ), margin: EdgeInsets.symmetric( horizontal: 5, vertical: 5, ), decoration: BoxDecoration( color: Color(0xFFF3EDFF), borderRadius: BorderRadius.circular(20), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ RichText( text: TextSpan( children: [ TextSpan( text: times[iii], style: TextStyle( color: times[iii] != "-" ? Color(0xFF1286C7) : Color(0xFFED3424), fontFamily: "JakartaRegular", fontSize: 20, ), ), TextSpan( text: periods[iii], style: TextStyle( color: periods[iii] != "-" ? Color(0xFF1286C7) : Color(0xFFED3424), fontFamily: "JakartaRegular", fontSize: 14, ), ), ], ), ), SizedBox(height: 10), Text( locations[iii], style: TextStyle( color: AppColors.semi_black, fontSize: 12, ), ), ], ), ), ); }), ), ), Container( padding: EdgeInsets.only(left: 10, top: 10), child: Text( "Attendance Details", style: TextStyle( fontFamily: "JakartaMedium", fontSize: 14, color: Color(0xFF818181), ), ), ), Container( padding: EdgeInsets.symmetric( horizontal: 10, vertical: 10, ), child: GridView.builder( itemCount: 4, shrinkWrap: true, physics: NeverScrollableScrollPhysics(), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, crossAxisSpacing: 10, mainAxisSpacing: 10, childAspectRatio: 20 / 10, ), itemBuilder: (context, index) { final numbers = [ attendance.presentDays, attendance.absentDays, attendance.holidays, attendance.latePenalties, ]; final names = [ "Present \nDays", "Absent \nDays", "Holidays", "Late \nPoints", ]; final colors = [ 0xFFE7FFE5, 0xFFFFEFEF, 0xFFF3EDFF, 0xFFFFF6F0, ]; final textcolors = [ 0xFF0D9C00, 0xFFFF0000, 0xFF493272, 0xFF91481B, ]; final assetNames = [ "assets/svg/attendance/present_ic.svg", "assets/svg/attendance/absent_ic.svg", "assets/svg/attendance/holidays_ic.svg", "assets/svg/attendance/penalty_ic.svg", ]; return Container( padding: EdgeInsets.symmetric(horizontal: 13), decoration: BoxDecoration( color: Color(colors[index]), borderRadius: BorderRadius.circular(20), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Text( numbers[index].toString(), style: TextStyle( fontSize: 20, fontFamily: "JakartaMedium", color: Color(textcolors[index]), ), ), Row( children: [ Expanded( flex: 4, child: Text( names[index], style: TextStyle( fontSize: 14, fontFamily: "JakartaRegular", color: AppColors.semi_black, ), ), ), Expanded( flex: 1, child: SvgPicture.asset( assetNames[index], ), ), ], ), ], ), ); }, ), ), ], ), ), SizedBox(height: 15), ///calendar Container( // padding: EdgeInsets.symmetric(horizontal: 10,vertical: 5), margin: EdgeInsets.symmetric( horizontal: 15, vertical: 10, ), decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), color: Colors.white, ), child: Column( children: [ Padding( padding: const EdgeInsets.fromLTRB(30, 10, 30, 0), child: Container( child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ SizedBox( child: Row( children: [ GestureDetector( onTap: () { attendance.setPreviousMonth( context, ); }, child: Padding( padding: const EdgeInsets.all(4.0), child: SvgPicture.asset( "assets/svg/crm/calendar_left.svg", ), ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 5.0), child: Text( monthLabel, style: TextStyle( overflow: TextOverflow.ellipsis, fontFamily: "JakartaMedium", color: Color(0xFF2D2D2D), ), ), ), GestureDetector( onTap: () { attendance.setNextMonth( context, ); }, child: Padding( padding: const EdgeInsets.all(4.0), child: SvgPicture.asset( "assets/svg/crm/calendar_right.svg", ), ), ), ], ), ), InkResponse( onTap: () { _showInfoBottomSheet(context); }, child: SizedBox( width: 20, height: 20, child: SvgPicture.asset( "assets/svg/ic_info_new.svg", width: 20, height: 20, ), ), ), ], ), ), ), SizedBox(height: 5), Padding( padding: const EdgeInsets.fromLTRB(8, 10, 8, 0), child: Container( child: Row( children: [ for ( var i = 0; i < [ 'S', 'M', 'T', 'W', 'T', 'F', 'S', ].length; i++ ) Expanded( child: Text( ['S', 'M', 'T', 'W', 'T', 'F', 'S'][i], textAlign: TextAlign.center, style: TextStyle( overflow: TextOverflow.ellipsis, color: i == 0 ? Color(0xFFFF0000) : AppColors.semi_black, ), ), ), ], ), ), ), Padding( padding: const EdgeInsets.fromLTRB(0, 0, 0, 10), child: Container( child: GridView.builder( itemCount: daysInPeriod + startingIndex, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 7, crossAxisSpacing: 2, mainAxisSpacing: 1, childAspectRatio: (255 / 245), ), padding: const EdgeInsets.fromLTRB(0, 0, 0, 0), shrinkWrap: true, physics: NeverScrollableScrollPhysics(), itemBuilder: (context, index) { if (index < startingIndex) { return SizedBox.shrink(); } final dayIndex = index - startingIndex; final currentDateInPeriod = startDate.add( Duration(days: dayIndex), ); final currentDay = currentDateInPeriod.day; final isFutureDate = currentDateInPeriod .isAfter(currentDate); // Find matching date in dateArrayList Map? dateMap; try { dateMap = dateArrayList[dayIndex]; } catch (e) { dateMap = {}; } // Find matching penalty Map? penaltyMap; try { penaltyMap = penalityArrayList[dayIndex]; } catch (e) { penaltyMap = {}; } String? dateColor = dateMap.isNotEmpty ? dateMap.values.first : null; String? penaltyKey = penaltyMap.isNotEmpty ? penaltyMap.keys.first : null; int? datePenalty = penaltyMap.isNotEmpty ? penaltyMap.values.first : 0; // Determine if this is the current day final isCurrentDay = currentDateInPeriod.day == currentDate.day && currentDateInPeriod.month == currentDate.month && currentDateInPeriod.year == currentDate.year; return InkWell( onTap: isFutureDate ? null : () { selectedIndex = index; if (penaltyKey != null) { attendance.dateWiseAttendance( penaltyKey, context, ); } attendance.selectedDate = currentDay.toString(); _showAttDetailsBottomSheet( context, ); }, child: Card( elevation: 0, child: Column( children: [ // Row( // mainAxisAlignment: // MainAxisAlignment.center, // children: [ // Text( // "($datePenalty)", // style: TextStyle( // fontWeight: FontWeight.w400, // color: Colors.black, // fontSize: 5 // ), // ), // ], // ), Center( child: Container( decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( color: (isCurrentDay || (selectedIndex == index)) ? Color(0xFF1487C9) : Colors.transparent, ), color: isFutureDate ? Colors.transparent : (isCurrentDay || (selectedIndex == index)) ? Color(0xFFFFFFFF) : dateColor == 'g' ? Color( 0xFFE7FFE5, ).withAlpha(50) : dateColor == 'r' ? Color( 0xFFFF0000, ).withAlpha(50) : dateColor == 'b' ? Color( 0xFF493272, ).withAlpha(50) : dateColor == 'br' ? Color(0xFFFFE8D0) : dateColor == 'y' ? Color(0xFFFFF9B2) : Colors.transparent, ), child: Center( child: Text( currentDay.toString(), style: TextStyle( fontSize: 14, fontWeight: FontWeight.w400, color: isFutureDate ? AppColors .semi_black : (isCurrentDay || (selectedIndex == index)) ? Color(0xFF2D2D2D) : dateColor == 'g' ? Color(0xFF0D9C00) : dateColor == 'r' ? Color(0xFFFF0000) : dateColor == 'b' ? Color(0xFF493272) : dateColor == 'br' ? Color(0xFF6B3A02) : dateColor == 'y' ? Color(0xFF605C00) : Colors .transparent, ), ), ), ), ), ], ), ), ); }, ), ), ), ], ), ), SizedBox(height: 25), ], ), ), bottomNavigationBar: attendance.attendanceStatus == 0 || attendance.attendanceStatus == 1 ? Container( height: 75, decoration: BoxDecoration(color: Colors.white), padding: EdgeInsets.symmetric(vertical: 10), child: Align( alignment: Alignment.center, child: InkWell( onTap: () async { var res = await Navigator.push( context, MaterialPageRoute( builder: (context) => CheckInOutScreen( getAttendanceStatus: attendance.attendanceStatus, ), ), ); if (res == true) { attendance.getAttendanceList( context, ); attendance.init( context); attendance.loadAttendanceDetails( context, ); } var f = FocusScope.of(context); if (!f.hasPrimaryFocus) { f.unfocus(); } }, child: Container( alignment: Alignment.bottomCenter, height: 45, margin: EdgeInsets.symmetric(horizontal: 10), width: MediaQuery.of(context).size.width, decoration: BoxDecoration( color: AppColors.app_blue, borderRadius: BorderRadius.circular(15.0), ), child: Center( child: Text( attendance.attendanceStatus == 0 ? "Check In" : "Check Out", textAlign: TextAlign.center, style: TextStyle( fontSize: 16, color: Colors.white, fontFamily: "JakartaMedium", ), ), ), ), ), ), ) : SizedBox(height: 0), floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, ), ); }, ); } Future _showInfoBottomSheet(BuildContext context) { return showModalBottomSheet( useSafeArea: true, isDismissible: true, isScrollControlled: true, showDragHandle: true, enableDrag: true, context: context, builder: (context) { return StatefulBuilder( builder: (context, setState) { return SafeArea( child: Container( margin: EdgeInsets.only( bottom: 15, left: 15, right: 15, top: 15, ), padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, ), child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Indicators Info", style: TextStyle( color: AppColors.app_blue, fontFamily: "JakartaSemiBold", fontSize: 16, ), ), SizedBox(height: 15), GridView.builder( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, crossAxisSpacing: 0, mainAxisExtent: 40, mainAxisSpacing: 10, childAspectRatio: 6 / 1, ), physics: NeverScrollableScrollPhysics(), shrinkWrap: true, itemCount: 5, itemBuilder: (context, index) { final colors = [ Color(0xFF493272).withAlpha(50), Color(0xFFE7FFE5), Color(0xFFFF0000).withAlpha(50), Color(0xFFFFE8D0), Color(0xFFFFF9B2), ]; final text_colors = [ Color(0xFF493272), Color(0xFF0D9C00), Color(0xFFFF0000), Color(0xFF6B3A02), Color(0xFF605C00), ]; final text_subs = ["24", "7", "13", "17", "2"]; final text = [ "Holiday", "Present", "Absent", "Half Day", "Not Checked Out", ]; return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( flex: 1, child: Container( width: 18, height: 18, decoration: BoxDecoration( shape: BoxShape.circle, color: colors[index], ), child: Center( child: Text( text_subs[index], style: TextStyle( color: text_colors[index], fontSize: 10, ), ), ), ), ), SizedBox(width: 5), Expanded( flex: 4, child: Text( text[index], maxLines: 2, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 14, color: Colors.black, ), ), ), ], ); }, ), ], ), ], ), ), ), ); }, ); }, ); } Future _showAttDetailsBottomSheet(BuildContext context) { return showModalBottomSheet( useSafeArea: true, isDismissible: true, isScrollControlled: true, showDragHandle: true, enableDrag: true, context: context, builder: (context) { return StatefulBuilder( builder: (context, setState) { return Consumer( builder: (context, provider, child) { return SafeArea( child: Container( margin: EdgeInsets.only( bottom: 15, left: 15, right: 15, top: 15, ), padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, ), child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Attendance Info", style: TextStyle( color: AppColors.app_blue, fontFamily: "JakartaSemiBold", fontSize: 16, ), ), Text( provider.date, style: TextStyle( color: Color(0xFF818181), fontSize: 12, ), ), ], ), Spacer(), Container( padding: EdgeInsets.symmetric( horizontal: 10, vertical: 10, ), decoration: BoxDecoration( color: Color(0xFFFFF6F0), borderRadius: BorderRadius.circular(12), ), child: Row( children: [ Text( "Late Points ", style: TextStyle( fontFamily: "JakartaMedium", ), ), Text( provider.penalties, style: TextStyle( color: Color(0xFF91481B), ), ), ], ), ), ], ), Padding( padding: EdgeInsets.symmetric(horizontal: 5), child: Divider( color: Color(0xFFD7D7D7), thickness: 0.5, ), ), SizedBox(height: 15), ...List.generate(4, (j) { final values = [ "Check-in Time", "Check-in Location", "Check-out Time", "Check-out Location", ]; final subvalues = [ (provider.intime), (provider.inlocation), (provider.outtime), (provider.outlocation), ]; return SizedBox( height: 35, child: Row( children: [ Expanded( child: Text( values[j], style: TextStyle( fontFamily: "JakartaMedium", color: AppColors.semi_black, ), ), ), Expanded( child: Text( subvalues[j], style: TextStyle( color: Color(0xFF818181), ), ), ), ], ), ); }), ], ), ), ), ); }, ); }, ); }, ); } }