import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:flutter_svg/svg.dart'; import 'package:generp/Notifiers/hrmProvider/AttendanceDetailsProvider.dart'; import 'package:generp/Utils/GlobalConstants.dart'; import 'package:generp/screens/hrm/AddManualAttendance.dart'; import 'package:generp/screens/hrm/AttendanceRequestDetail.dart'; import 'package:provider/provider.dart'; import '../../Notifiers/HomeScreenNotifier.dart'; import '../../Notifiers/hrmProvider/attendanceListProvider.dart'; import '../../Utils/app_colors.dart'; import '../../Utils/commonWidgets.dart'; import '../CommonFilter2.dart'; import '../commonDateRangeFilter.dart'; import 'AddLiveAttendance.dart'; class AttendanceListScreen extends StatefulWidget { final mode; const AttendanceListScreen({super.key,required this.mode}); @override State createState() => _AttendanceListScreenState(); } class _AttendanceListScreenState extends State { // @override // void initState() { // super.initState(); // WidgetsBinding.instance.addPostFrameCallback((timeStamp) { // final provider = Provider.of(context, listen: false); // provider.fetchAttendanceRequests(context); // }); // } @override Widget build(BuildContext context) { String _truncate(String text, int maxLength) { if (text.length <= maxLength) return text; return text.substring(0, maxLength).trim() + '...'; } return SafeArea( top: false, child: ChangeNotifierProvider( create: (_) { final provider = Attendancelistprovider(); Future.microtask(() { provider.fetchAttendanceRequests(context, widget.mode); }); return provider; }, builder: (context, child) { return Consumer( builder: (context, provider, child) { final requestProvider = Provider.of(context, listen: false); return Scaffold( appBar: AppBar( automaticallyImplyLeading: false, backgroundColor: Colors.white, title: Row( children: [ InkResponse( onTap: () => Navigator.pop(context, true), child: SvgPicture.asset( "assets/svg/appbar_back_button.svg", height: 25, ), ), const SizedBox(width: 10), Text( "Attendance List", style: TextStyle( fontSize: 18, fontFamily: "Plus Jakarta Sans", fontWeight: FontWeight.w600, color: AppColors.semi_black, ), ), ], ), actions: [ InkResponse( onTap: () async { final result = await CommonFilter2().showFilterBottomSheet(context); if (result != null) { final provider = Provider.of(context, listen: false); provider.updateFiltersFromSheet( widget.mode, context, type: result['type'] ?? "All", selectedValue: result['selectedValue'] ?? "This Month", customRange: result['dateRange'], ); } }, child: SvgPicture.asset( "assets/svg/filter_ic.svg", height: 25, ), ), const SizedBox(width: 20), ], ), backgroundColor: const Color(0xFFF6F6F8), body: Column( children: [ /// Filter chips - show active filters // if (provider.selectedType != "All" || provider.selectedDateRange != "This Month") // Container( // padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), // color: Colors.white, // child: Wrap( // spacing: 8, // children: [ // if (provider.selectedType != "All") // Chip( // label: Text('Type: ${provider.selectedType}'), // onDeleted: () { // provider.setTypeFilter(context, "All"); // }, // ), // if (provider.selectedDateRange != "This Month") // Chip( // label: Text('Date: ${provider.selectedDateRange}'), // onDeleted: () { // provider.setDateRangeFilter(context, "This Month"); // }, // ), // ], // ), // ), /// Attendance list Expanded( child: Builder( builder: (context) { if (provider.isLoading) { return const Center(child: CircularProgressIndicator(color: Colors.blue)); } // if (provider.errorMessage != null) { // return Center(child: Text(provider.errorMessage!)); // } if (provider.response?.requestList == null || provider.response!.requestList!.isEmpty) { return const Center( child: Text( "No attendance records found", style: TextStyle(fontSize: 16, color: Colors.grey), ), ); } final list = provider.response!.requestList!; return ListView.builder( padding: const EdgeInsets.all(8), itemCount: list.length, itemBuilder: (context, index) { final item = list[index]; final canSwipe = widget.mode == "apr_lvl1" && item.status != "Level 1 Approved" && item.status != "Rejected"; final homeProvider = Provider.of(context, listen: false); return Slidable( key: ValueKey(item.id), // Left swipe (Reject) startActionPane: canSwipe ? ActionPane( motion: const ScrollMotion(), dragDismissible: false, children: [ SlidableAction( onPressed: (_) { showRemarkSheet( context: context, actionType: "Reject", onSubmit: (remark) async { await provider.rejectApproveAttendanceRequest( session: homeProvider.session, empId: homeProvider.empId, mode: widget.mode, type: "Rejected", remarks: remark, id: item.id ?? "0", ); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text("Attendance request rejected successfully.")), ); // refresh list provider.fetchAttendanceRequests(context, widget.mode); }, ).then((_) { provider.fetchAttendanceRequests(context, widget.mode); // or setState(() {}) if needed }); }, backgroundColor: const Color(0xFFFFE5E5), foregroundColor: const Color(0xFFEF3739), icon: Icons.clear, label: 'Reject', ), ], ) : null, // Right swipe (Approve) endActionPane: canSwipe ? ActionPane( motion: const ScrollMotion(), dragDismissible: false, children: [ SlidableAction( onPressed: (context) { showRemarkSheet( context: context, actionType: "Approve", onSubmit: (remark) async { await provider.rejectApproveAttendanceRequest( session: homeProvider.session, empId: homeProvider.empId, mode: widget.mode, type: "Approved", remarks: remark, id: item.id ?? "0", ); }, ).then((_) { provider.fetchAttendanceRequests(context, widget.mode); }); print("######################################"); }, backgroundColor: const Color(0xFFE9FFE8), foregroundColor: const Color(0xFF4CB443), icon: Icons.check, label: 'Approve', ), ], ) : null, child: InkWell( borderRadius: BorderRadius.circular(16), onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => AttendanceRequestDetailScreen( attendanceListId: item.id, mode: widget.mode, ), ), ); }, child: Container( margin: const EdgeInsets.symmetric(horizontal: 8.5, vertical: 5), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8.5), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), ), child: Row( children: [ /// Left Avatar Circle Container( height: 48, width: 50, padding: const EdgeInsets.all(8.0), decoration: BoxDecoration( color: _getAvatarColor(item.status), shape: BoxShape.circle, ), child: Center( child: Text( getText(item.status), style: TextStyle( color: _getTextColor(item.status), fontSize: 14, fontWeight: FontWeight.bold, ), ), ), ), const SizedBox(width: 10), /// Middle Section Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( widget.mode == "apr_lvl1" ? _truncate(item.employeeName ?? "-", 20) : item.type ?? "-", maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontFamily: "JakartaRegular", fontSize: 14, color: AppColors.semi_black, ), ), Row( children: [ Text( widget.mode == "apr_lvl1" ? item.type ?? "-" : item.type ?? "-", style: TextStyle( fontFamily: "JakartaRegular", fontSize: 14, color: AppColors.grey_semi, ), ), const SizedBox(width: 2), Text( " - ${item.date}" ?? "-", style: TextStyle( fontFamily: "JakartaRegular", fontSize: 14, color: AppColors.grey_semi, ), ), ], ), ], ), ), /// Right Status (Live / Manual) Text( item.attendanceType ?? "-", textAlign: TextAlign.right, style: TextStyle( fontFamily: "JakartaMedium", fontSize: 14, color: (item.attendanceType ?? "").toLowerCase() == "live" ? Colors.green : Colors.orange, ), ), ], ), ), ), ); }, ); }, ), ) ], ), bottomNavigationBar: widget.mode == "apr_lvl1" ? null : Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), alignment: Alignment.bottomCenter, height: 61, decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Color(0xffFFFFFF), Color(0x00FFFFFF), ], ), ), child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Expanded( child: InkResponse( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => AddLiveAttendanceScreen(), settings: const RouteSettings( name: 'AddLiveAttendanceScreen', ), ), ).then((_) { provider.fetchAttendanceRequests(context, widget.mode); }); }, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ SvgPicture.asset("assets/svg/hrm/live.svg"), const SizedBox(width: 10), Text("Live Request", style: TextStyle(color: AppColors.semi_black)), ], ), ), ), const SizedBox(width: 10), SvgPicture.asset("assets/svg/crm/vertical_line_ic.svg"), const SizedBox(width: 10), Expanded( child: InkResponse( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => const AddManualAttendanceScreen(), settings: const RouteSettings( name: 'AddManualAttendanceScreen'), ), ).then((_) { provider.fetchAttendanceRequests(context, widget.mode); }); }, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ SvgPicture.asset("assets/svg/hrm/manual.svg"), const SizedBox(width: 10), Text("Manual Request", style: TextStyle(color: AppColors.semi_black)), ], ), ), ), ], ), ), ); }, ); }, ) ); } Future showRemarkSheet({ required BuildContext context, required String actionType, // "Approved" or "Rejected" required Function(String remark) onSubmit, }) { final remarkController = TextEditingController(); String? remarkError; return showModalBottomSheet( useSafeArea: true, isDismissible: true, isScrollControlled: true, showDragHandle: true, backgroundColor: Colors.white, enableDrag: true, context: context, builder: (context) { return StatefulBuilder( builder: (context, setState) { void updateState(VoidCallback fn) { setState(fn); } bool validateFields() { String? newRemarkError = remarkController.text.trim().isEmpty ? "Remark required" : null; if (remarkError != newRemarkError) { updateState(() { remarkError = newRemarkError; }); } return newRemarkError == null; } Widget errorText(String? msg) => msg == null ? const SizedBox() : Padding( padding: const EdgeInsets.only(top: 4, left: 4), child: Text( msg, style: const TextStyle( color: Colors.red, fontSize: 12, fontFamily: "JakartaMedium", ), ), ); return SafeArea( child: Container( margin: const EdgeInsets.symmetric(horizontal: 15, vertical: 10), padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, ), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( "$actionType Attendance Request", style: const TextStyle( fontSize: 16, color: Colors.black87, fontFamily: "JakartaMedium", ), ), const SizedBox(height: 16), Text( "Remark", style: const TextStyle( fontSize: 14, color: Colors.black87, fontFamily: "JakartaMedium", ), ), const SizedBox(height: 6), Container( margin: const EdgeInsets.only(bottom: 6), decoration: BoxDecoration( color: AppColors.text_field_color, borderRadius: BorderRadius.circular(14), ), child: TextField( controller: remarkController, maxLines: 3, onChanged: (val) { if (remarkError != null && val.isNotEmpty) { updateState(() => remarkError = null); } }, decoration: InputDecoration( hintText: "Enter your remark here...", hintStyle: TextStyle( color: Colors.grey.shade500, // Customize this color fontSize: 14, // Optional: tweak font size ), border: InputBorder.none, contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), ), ), ), errorText(remarkError), const SizedBox(height: 20), Row( children: [ Expanded( child: InkResponse( onTap: () => Navigator.pop(context), child: Container( height: 45, decoration: BoxDecoration( color: Color(0x12AAAAAA), borderRadius: BorderRadius.circular(12), ), child: const Center( child: Text( "Cancel", style: TextStyle( color: Colors.red, fontFamily: "JakartaMedium", ), ), ), ), ), ), const SizedBox(width: 12), Expanded( child: InkResponse( onTap: () async { if (validateFields()) { final remark = remarkController.text.trim(); // Call provider await onSubmit(remark); // SnackBar here Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text("Request submitted successfully"), backgroundColor: Colors.green, behavior: SnackBarBehavior.floating, ), ); } }, child: Container( height: 45, decoration: BoxDecoration( color: Colors.blue, borderRadius: BorderRadius.circular(12), ), child: const Center( child: Text( "Submit", style: TextStyle( color: Colors.white, fontFamily: "JakartaMedium", ), ), ), ), ), ), ], ), ], ), ), ), ); }, ); }, ); } /// Avatar color generator Color _getAvatarColor(value) { var color = AppColors.approved_bg_color; switch (value) { case 'Requested': return AppColors.requested_bg_color; case 'Level 1 Approved': return AppColors.approved_bg_color; case 'Level 1 Rejected': return AppColors.rejected_bg_color; case 'Level 2 Approved': return AppColors.approved_bg_color; case 'Level 2 Rejected': return AppColors.rejected_bg_color; case 'Updated': return AppColors.processed_bg_color; case 'Payment Rejected': return AppColors.rejected_bg_color; } return color; } Color _getTextColor(value) { var color = AppColors.approved_text_color; switch (value) { case 'Requested': return AppColors.requested_text_color; case 'Level 1 Approved': return AppColors.approved_text_color; case 'Level 1 Rejected': return AppColors.rejected_text_color; case 'Level 2 Approved': return AppColors.approved_text_color; case 'Level 2 Rejected': return AppColors.rejected_text_color; case 'Updated': return AppColors.processed_text_color; } return color; } getText(value) { switch (value) { case 'Requested': return "R"; case 'Level 1 Approved': return "L1A"; case 'Level 1 Rejected': return "L1R"; case 'Level 2 Approved': return "L2A"; case 'Level 2 Rejected': return "L2R"; case 'Updated': return "U"; default: return "Requested"; } } }