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 { bool _actionSubmitted = false; @override Widget build(BuildContext context) { // Get screen dimensions for responsive scaling final screenWidth = MediaQuery.of(context).size.width; final screenHeight = MediaQuery.of(context).size.height; // Scale factors based on screen size final scaleFactor = screenWidth / 360; // Base width for scaling final textScaleFactor = MediaQuery.of(context).textScaleFactor.clamp(1.0, 1.2); return SafeArea( top: false, child: 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 * scaleFactor, ), ), SizedBox(width: 10 * scaleFactor), 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.response?.requestDetails == null) { return const Center(child: Text("No details found")); } final details = provider.response!.requestDetails!; /// Screen content return SingleChildScrollView( padding: EdgeInsets.all(16.0 * scaleFactor), child: Column( children: [ Card( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16 * scaleFactor), ), elevation: 0, child: Padding( padding: EdgeInsets.all(16.0 * scaleFactor), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ /// Header with status Container( margin: EdgeInsets.only(bottom: 0.5 * scaleFactor), padding: EdgeInsets.symmetric( horizontal: 2.5 * scaleFactor, vertical: 12 * scaleFactor, ), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12 * scaleFactor), ), child: Row( children: [ /// Left Avatar Container( height: 44 * scaleFactor, width: 44 * scaleFactor, decoration: BoxDecoration( shape: BoxShape.circle, color: const Color(0xFFEDF8FF), ), child: Center( child: SvgPicture.asset( "assets/svg/hrm/leaveApplication.svg", height: 24 * scaleFactor, width: 24 * scaleFactor, fit: BoxFit.contain, ), ), ), SizedBox(width: 12 * scaleFactor), /// 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, ), ), SizedBox(height: 2 * scaleFactor), Text( "Applied: ${details.appliedDate ?? "-"}", style: TextStyle( fontFamily: "JakartaRegular", fontSize: 14, color: AppColors.app_blue, ), ), ], ), ), /// Right side status badge Container( height: 30 * scaleFactor, padding: EdgeInsets.symmetric( horizontal: 12 * scaleFactor, ), decoration: BoxDecoration( borderRadius: BorderRadius.circular(6 * scaleFactor), color: _getStatusBackgroundColor(details.status), ), child: Center( child: Text( details.status ?? "-", style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: _getStatusTextColor(details.status), ), ), ), ), ], ), ), /// Leave Details Padding( padding: EdgeInsets.all(8.0 * scaleFactor), child: Column( children: [ _buildSectionHeader("Leave Details", scaleFactor), _buildDetailTile("Application ID", details.id, scaleFactor), _buildDetailTile("Applied Date", details.appliedDate, scaleFactor), _buildDetailTile("Leave Type", details.leaveType, scaleFactor), _buildDateRangeTile("Leave Period", details.fromDate, details.toDate, scaleFactor), _buildTimeRangeTile("Time Period", details.fromTime, details.toTime, scaleFactor), _buildDetailTile("Reason", details.reason, scaleFactor), /// Approval Details _buildSectionHeader("Approval Details", scaleFactor), _buildDetailTile("Requested To", details.requestedTo, scaleFactor), _buildDetailTile("Approved By", details.approvedBy, scaleFactor), _buildDetailTile("Approved Date", details.approvedDate, scaleFactor), _buildDetailTile("Approval Remarks", details.approvalRemarks, scaleFactor), /// Additional Information _buildSectionHeader("Additional Information", scaleFactor), _buildDetailTile("Status", details.status, scaleFactor), _buildDetailTile("From Time", details.fromTime, scaleFactor), _buildDetailTile("To Time", details.toTime, scaleFactor), ], ), ), ], ), ), ), SizedBox(height: 30 * scaleFactor), ], ), ); }, ), bottomNavigationBar: (widget.mode == "teamleader" && !_actionSubmitted && provider.response?.requestDetails?.status != "Approved" && provider.response?.requestDetails?.status != "Rejected") ? Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Color(0xffFFFFFF), Color(0x00FFFFFF), ], ), ), padding: EdgeInsets.symmetric(horizontal: 10 * scaleFactor, vertical: 6 * scaleFactor), height: 61 * scaleFactor, child: Column( children: [ 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!, ); }, ).then((_) { provider.fetchLeaveApplicationDetails(context, widget.leaveRequestId); }); }, child: Container( alignment: Alignment.center, decoration: BoxDecoration( borderRadius: BorderRadius.circular(8 * scaleFactor), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ SvgPicture.asset("assets/svg/finance/level_reject_ic.svg"), SizedBox(width: 6 * scaleFactor), Text( "Reject", style: TextStyle( color: Colors.black87, fontSize: 14 * scaleFactor, fontFamily: "JakartaMedium", ), ), ], ), ), ), ), /// Vertical Divider Container( width: 1, height: 45 * scaleFactor, color: Colors.grey.shade300, ), /// Approve Button Expanded( child: InkWell( onTap: () { showRemarkSheet( context: context, actionType: "Approve", onSubmit: (remark) async { await provider.leaveRequestRejectApprove( context, mode: widget.mode, type: "Approved", remarks: remark, id: provider.response!.requestDetails!.id!, ); }, ).then((_) { provider.fetchLeaveApplicationDetails(context, widget.leaveRequestId); }); }, child: Container( alignment: Alignment.center, decoration: BoxDecoration( borderRadius: BorderRadius.circular(8 * scaleFactor), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ SvgPicture.asset("assets/svg/finance/level_approve_ic.svg"), SizedBox(width: 6 * scaleFactor), Text( "Approve", style: TextStyle( color: Colors.black87, fontSize: 14 * scaleFactor, fontFamily: "JakartaMedium", ), ), ], ), ), ), ), ], ), SizedBox(height: 2 * scaleFactor) ], ), ) : const SizedBox.shrink(), floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, ); }, ), ), ); } Future showRemarkSheet({ required BuildContext context, required String actionType, required Function(String remark) onSubmit, }) { final screenWidth = MediaQuery.of(context).size.width; final scaleFactor = screenWidth / 360; 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: EdgeInsets.only(top: 4 * scaleFactor, left: 4 * scaleFactor), child: Text( msg, style: TextStyle( color: Colors.red, fontSize: 12 * scaleFactor, fontFamily: "JakartaMedium", ), ), ); return SafeArea( child: Container( margin: EdgeInsets.symmetric(horizontal: 15 * scaleFactor, vertical: 10 * scaleFactor), padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, ), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( "$actionType Leave Request", style: TextStyle( fontSize: 16 * scaleFactor, color: Colors.black87, fontFamily: "JakartaMedium", ), ), SizedBox(height: 16 * scaleFactor), Text( "Remark", style: TextStyle( fontSize: 14 * scaleFactor, color: Colors.black87, fontFamily: "JakartaMedium", ), ), SizedBox(height: 6 * scaleFactor), Container( margin: EdgeInsets.only(bottom: 6 * scaleFactor), decoration: BoxDecoration( color: AppColors.text_field_color, borderRadius: BorderRadius.circular(14 * scaleFactor), ), child: TextField( controller: remarkController, maxLines: 3, style: TextStyle( color: Colors.black, fontSize: 14 * scaleFactor, ), onChanged: (val) { if (remarkError != null && val.isNotEmpty) { updateState(() => remarkError = null); } }, decoration: InputDecoration( hintText: "Enter your remark here...", hintStyle: TextStyle( color: Colors.grey.shade500, fontSize: 14 * scaleFactor, ), border: InputBorder.none, contentPadding: EdgeInsets.symmetric( horizontal: 12 * scaleFactor, vertical: 12 * scaleFactor ), ), ), ), errorText(remarkError), SizedBox(height: 20 * scaleFactor), Row( children: [ Expanded( child: InkResponse( onTap: () => Navigator.pop(context), child: Container( height: 45 * scaleFactor, decoration: BoxDecoration( color: Color(0x12AAAAAA), borderRadius: BorderRadius.circular(12 * scaleFactor), ), child: Center( child: Text( "Cancel", style: TextStyle( color: Colors.red, fontFamily: "JakartaMedium", fontSize: 14 * scaleFactor, ), ), ), ), ), ), SizedBox(width: 12 * scaleFactor), Expanded( child: InkResponse( onTap: () async { if (validateFields()) { final remark = remarkController.text.trim(); await onSubmit(remark); Navigator.pop(context); if (mounted) { setState(() { _actionSubmitted = true; }); } ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text("Request submitted successfully"), backgroundColor: Colors.green, behavior: SnackBarBehavior.floating, ), ); } }, child: Container( height: 45 * scaleFactor, decoration: BoxDecoration( color: Colors.blue, borderRadius: BorderRadius.circular(12 * scaleFactor), ), child: Center( child: Text( "Submit", style: TextStyle( color: Colors.white, fontFamily: "JakartaMedium", fontSize: 14 * scaleFactor, ), ), ), ), ), ), ], ), SizedBox(height: 2 * scaleFactor) ], ), ), ), ); }, ); }, ); } /// Reusable Row Widget for details Widget _buildDetailTile(String label, String? value, double scaleFactor) { return Padding( padding: EdgeInsets.symmetric(vertical: 6 * scaleFactor), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Label Expanded( flex: 5, child: Text( label, style: TextStyle( fontFamily: "JakartaRegular", fontSize: 14, color: AppColors.semi_black, ), ), ), SizedBox(width: 4 * scaleFactor), // Value Expanded( flex: 5, child: Text( value ?? "-", style: TextStyle( fontSize: 14, color: const Color(0xFF818181), fontWeight: FontWeight.w400, ), softWrap: true, overflow: TextOverflow.visible, ), ), ], ), ); } /// For date range display Widget _buildDateRangeTile(String label, String? fromDate, String? toDate, double scaleFactor) { return Padding( padding: EdgeInsets.symmetric(vertical: 6 * scaleFactor), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Label Expanded( flex: 5, child: Text( label, style: TextStyle( fontFamily: "JakartaRegular", fontSize: 14, color: AppColors.semi_black, ), ), ), SizedBox(width: 4 * scaleFactor), // Value Expanded( flex: 5, child: Text( '${fromDate ?? "-"} to ${toDate ?? "-"}', style: TextStyle( fontSize: 14, color: const Color(0xFF818181), fontWeight: FontWeight.w400, ), softWrap: true, overflow: TextOverflow.visible, ), ), ], ), ); } /// For time range display Widget _buildTimeRangeTile(String label, String? fromTime, String? toTime, double scaleFactor) { if ((fromTime == null || fromTime.isEmpty) && (toTime == null || toTime.isEmpty)) { return const SizedBox.shrink(); } return Padding( padding: EdgeInsets.symmetric(vertical: 6 * scaleFactor), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Label Expanded( flex: 5, child: Text( label, style: TextStyle( fontSize: 14, color: const Color(0xff2D2D2D), fontStyle: FontStyle.normal, fontFamily: "Plus Jakarta Sans", fontWeight: FontWeight.w400, ), ), ), SizedBox(width: 4 * scaleFactor), // Value Expanded( flex: 5, child: Text( '${fromTime ?? "-"} to ${toTime ?? "-"}', style: TextStyle( fontSize: 14, color: const Color(0xff818181), fontWeight: FontWeight.w400, ), softWrap: true, overflow: TextOverflow.visible, ), ), ], ), ); } /// Section header with dotted line Widget _buildSectionHeader(String title, double scaleFactor) { return Padding( padding: EdgeInsets.symmetric(vertical: 9 * scaleFactor), child: Row( children: [ Text( title, style: TextStyle( fontSize: 14, fontFamily: "JakartaSemiBold", ), ), SizedBox(width: 10 * scaleFactor), 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; } } }