import 'package:dotted_line/dotted_line.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:provider/provider.dart'; import '../../Notifiers/hrmProvider/leaveApplicationDetailsProvider.dart'; import '../../Utils/app_colors.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; } } }