import 'package:dotted_line/dotted_line.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../Notifiers/hrmProvider/leaveApplicationDetailsProvider.dart'; import '../../Notifiers/HomeScreenNotifier.dart'; import '../../Utils/app_colors.dart'; import '../finance/FileViewer.dart'; /// Screen for leave application details class LeaveApplicationDetailScreen extends StatefulWidget { final String leaveRequestId; final String mode; const LeaveApplicationDetailScreen({super.key, required this.leaveRequestId, required this.mode}); @override State createState() => _LeaveApplicationDetailScreenState(); } class _LeaveApplicationDetailScreenState extends State { @override Widget build(BuildContext context) { return ChangeNotifierProvider( create: (_) => LeaveApplicationDetailsProvider()..fetchLeaveApplicationDetails(context, widget.leaveRequestId), child: Consumer( builder: (context, provider, child) { return Scaffold( appBar: AppBar( automaticallyImplyLeading: false, backgroundColor: const Color(0xFFFFFFFF), title: Row( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ InkResponse( onTap: () => Navigator.pop(context, true), child: SvgPicture.asset( "assets/svg/appbar_back_button.svg", height: 25, ), ), const SizedBox(width: 10), InkResponse( onTap: () => Navigator.pop(context, true), child: Text( "Leave Application Details", style: TextStyle( fontSize: 18, height: 1.1, fontFamily: "Plus Jakarta Sans", fontWeight: FontWeight.w600, color: AppColors.semi_black, ), ), ), ], ), ), backgroundColor: const Color(0xFFF6F6F8), body: 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?.requestDetails == null) { return const Center(child: Text("No details found")); } final details = provider.response!.requestDetails!; /// Screen content return SingleChildScrollView( padding: const EdgeInsets.all(16.0), child: Column( children: [ Card( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), elevation: 2, child: Padding( padding: const EdgeInsets.all(10.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ /// Header with status Container( margin: const EdgeInsets.only(bottom: 0.5), padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 2), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), ), child: Row( children: [ /// Left Avatar Container( height: 48, width: 48, decoration: BoxDecoration( shape: BoxShape.circle, color: const Color(0xFFEDF8FF), // icon bg ), child: Center( child: SvgPicture.asset( height: 28, width: 28, "assets/svg/hrm/leaveApplication.svg", // Use appropriate icon fit: BoxFit.contain, ), ), ), const SizedBox(width: 12), /// Middle text Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( details.leaveType ?? "-", style: TextStyle( decoration: TextDecoration.underline, decorationStyle: TextDecorationStyle.dotted, decorationColor: AppColors.grey_thick, height: 1.2, fontFamily: "JakartaRegular", fontSize: 14, color: AppColors.semi_black, ), ), const SizedBox(height: 2), Text( "Applied: ${details.appliedDate ?? "-"}", style: TextStyle( fontFamily: "JakartaRegular", fontSize: 14, color: AppColors.app_blue, ), ), ], ), ), /// Right side status badge Container( height: 30, padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 1), decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), color: _getStatusBackgroundColor(details.status), ), child: Center( child: Text( details.status ?? "-", textAlign: TextAlign.center, style: TextStyle( fontFamily: "JakartaMedium", fontSize: 12, color: _getStatusTextColor(details.status), ), ), ), ), ], ), ), /// Leave Details Padding( padding: const EdgeInsets.all(8.0), child: Column( children: [ _buildSectionHeader("Leave Details"), _buildDetailTile("Application ID", details.id), _buildDetailTile("Applied Date", details.appliedDate), _buildDetailTile("Leave Type", details.leaveType), _buildDateRangeTile("Leave Period", details.fromDate, details.toDate), _buildTimeRangeTile("Time Period", details.fromTime, details.toTime), _buildDetailTile("Reason", details.reason), /// Approval Details _buildSectionHeader("Approval Details"), _buildDetailTile("Requested To", details.requestedTo), _buildDetailTile("Approved By", details.approvedBy), _buildDetailTile("Approved Date", details.approvedDate), _buildDetailTile("Approval Remarks", details.approvalRemarks), /// Additional Information _buildSectionHeader("Additional Information"), _buildDetailTile("Status", details.status), _buildDetailTile("From Time", details.fromTime), _buildDetailTile("To Time", details.toTime), ], ), ), ], ), ), ), const SizedBox(height: 30), ], ), ); }, ), bottomNavigationBar: widget.mode == "teamleader" ? Container( decoration: const BoxDecoration(color: Colors.white), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), height: 80, child: Row( children: [ /// Reject Button Expanded( child: InkWell( onTap: () { showRemarkSheet( context: context, actionType: "Reject", onSubmit: (remark) { provider.leaveRequestRejectApprove( context, mode: widget.mode, type: "Rejected", remarks: remark, id: provider.response!.requestDetails!.id!, ); }, ); }, child: Container( alignment: Alignment.center, height: 45, decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), color: const Color(0xFFFFFFFF), border: Border.all( color: const Color(0xFFE0E0E0), width: 0.5, ), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ SvgPicture.asset("assets/svg/finance/level_reject_ic.svg"), const SizedBox(width: 6), const Text( "Reject", style: TextStyle( color: Colors.black87, fontSize: 14, fontFamily: "JakartaMedium", ), ), ], ), ), ), ), const SizedBox(width: 10), /// Approve Button Expanded( child: InkWell( onTap: () { showRemarkSheet( context: context, actionType: "Approve", onSubmit: (remark) { provider.leaveRequestRejectApprove( context, mode: widget.mode, type: "Approved", remarks: remark, id: provider.response!.requestDetails!.id!, ); }, ); }, child: Container( alignment: Alignment.center, height: 45, decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), color: const Color(0xFFFFFFFF), border: Border.all( color: const Color(0xFFE0E0E0), width: 0.5, ), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ SvgPicture.asset("assets/svg/finance/level_approve_ic.svg"), const SizedBox(width: 6), const Text( "Approve", style: TextStyle( color: Colors.black87, fontSize: 14, fontFamily: "JakartaMedium", ), ), ], ), ), ), ), ], ), ) : const SizedBox.shrink(), floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, ); }, ), ); } 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), TextField( controller: remarkController, maxLines: 3, onChanged: (val) { if (remarkError != null && val.isNotEmpty) { updateState(() => remarkError = null); } }, decoration: InputDecoration( hintText: "Enter your remark here...", filled: true, fillColor: Colors.grey.shade100, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), ), contentPadding: const EdgeInsets.symmetric( vertical: 12, horizontal: 12, ), ), ), errorText(remarkError), const SizedBox(height: 20), Row( children: [ Expanded( child: InkResponse( onTap: () => Navigator.pop(context), child: Container( height: 45, decoration: BoxDecoration( color: Colors.red.shade100, 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", ), ), ), ), ), ), ], ), ], ), ), ), ); }, ); }, ); } /// Reusable Row Widget for details Widget _buildDetailTile(String label, String? value) { return Padding( padding: const EdgeInsets.symmetric(vertical: 3), child: Row( children: [ Expanded( flex: 6, child: Text( label, style: TextStyle( fontFamily: "JakartaRegular", fontSize: 14, color: AppColors.semi_black, ), ), ), Expanded( flex: 0, child: Text( value ?? "-", style: const TextStyle( fontSize: 14, color: Color(0xff818181), fontWeight: FontWeight.w400, ), ), ), ], ), ); } /// For date range display Widget _buildDateRangeTile(String label, String? fromDate, String? toDate) { return Padding( padding: const EdgeInsets.symmetric(vertical: 3), child: Row( children: [ Expanded( flex: 6, child: Text( label, style: TextStyle( fontFamily: "JakartaRegular", fontSize: 14, color: AppColors.semi_black, ), ), ), Expanded( flex: 0, child: Text( '${fromDate ?? "-"} to ${toDate ?? "-"}', style: const TextStyle( fontSize: 14, color: Color(0xff818181), fontWeight: FontWeight.w400, ), ), ), ], ), ); } /// For time range display Widget _buildTimeRangeTile(String label, String? fromTime, String? toTime) { if ((fromTime == null || fromTime.isEmpty) && (toTime == null || toTime.isEmpty)) { return const SizedBox.shrink(); // Hide if no time data } return Padding( padding: const EdgeInsets.symmetric(vertical: 3), child: Row( children: [ Expanded( flex: 6, child: Text( label, style: const TextStyle( fontSize: 14, color: Color(0xff2D2D2D), fontStyle: FontStyle.normal, fontFamily: "Plus Jakarta Sans", fontWeight: FontWeight.w400, ), ), ), Expanded( flex: 0, child: Text( '${fromTime ?? "-"} to ${toTime ?? "-"}', style: const TextStyle( fontSize: 14, color: Color(0xff818181), fontWeight: FontWeight.w400, ), ), ), ], ), ); } /// Section header with dotted line Widget _buildSectionHeader(String title) { return Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Row( children: [ Text( title, style: TextStyle( fontSize: 14, fontFamily: "JakartaSemiBold", ), ), const SizedBox(width: 10), Expanded( child: DottedLine( dashGapLength: 4, dashGapColor: Colors.white, dashColor: AppColors.grey_semi, dashLength: 2, lineThickness: 0.5, ), ), ], ), ); } /// Status background color Color _getStatusBackgroundColor(String? status) { switch (status?.toLowerCase()) { case 'approved': return AppColors.approved_bg_color; case 'rejected': return AppColors.rejected_bg_color; case 'requested': default: return AppColors.requested_bg_color; } } /// Status text color Color _getStatusTextColor(String? status) { switch (status?.toLowerCase()) { case 'approved': return AppColors.approved_text_color; case 'rejected': return AppColors.rejected_text_color; case 'requested': default: return AppColors.requested_text_color; } } }