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) { 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, ), ), 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.response?.requestDetails == null) { return const Center(child: Text("No details found")); } // 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 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(10.0 * scaleFactor), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ /// Header with status Container( margin: EdgeInsets.only(bottom: 0.5 * scaleFactor), padding: EdgeInsets.symmetric( vertical: 10 * scaleFactor, horizontal: 2 * scaleFactor, ), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12 * scaleFactor), ), child: Row( children: [ /// Left Avatar Container( height: 48 * scaleFactor, width: 48 * scaleFactor, decoration: BoxDecoration( shape: BoxShape.circle, color: const Color(0xFFEDF8FF), ), child: Center( child: SvgPicture.asset( "assets/svg/hrm/leaveApplication.svg", height: 28 * scaleFactor, width: 28 * 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: 12 * scaleFactor, color: AppColors.semi_black, ), ), SizedBox(height: 2 * scaleFactor), Text( "Applied: ${details.appliedDate ?? "-"}", style: TextStyle( fontFamily: "JakartaRegular", fontSize: 12 * scaleFactor, color: AppColors.app_blue, ), ), ], ), ), /// Right side status badge Container( height: 28 * scaleFactor, padding: EdgeInsets.symmetric( horizontal: 5 * scaleFactor, vertical: 1 * scaleFactor, ), decoration: BoxDecoration( borderRadius: BorderRadius.circular(8 * scaleFactor), color: _getStatusBackgroundColor(details.status), ), child: Center( child: Text( details.status ?? "-", textAlign: TextAlign.center, style: TextStyle( fontFamily: "JakartaMedium", fontSize: 10 * scaleFactor, color: _getStatusTextColor(details.status), ), ), ), ), ], ), ), /// Leave Details Padding( padding: EdgeInsets.all(8.0 * scaleFactor), child: Column( children: [ _buildSectionHeader("Leave Details", ), _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", ), _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", ), _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: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), height: 61, 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), ), 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", ), ), ], ), ), ), ), /// Vertical Divider Container( width: 1, height: 45, 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), ), 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", ), ), ], ), ), ), ), ], ), SizedBox(height: 2,) ], ), ) : 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), Container( margin: const EdgeInsets.only(bottom: 6), decoration: BoxDecoration( color: AppColors.text_field_color, borderRadius: BorderRadius.circular(14), ), child: TextField( controller: remarkController, maxLines: 3, style: TextStyle( color: Colors.black, // Entered text color fontSize: 14, // Optional: adjust font size ), 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: 5), 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(); await onSubmit(remark); Navigator.pop(context); if (mounted) { setState(() { _actionSubmitted = true; }); } // Show snackbar 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", ), ), ), ), ), ), ], ), SizedBox(height: 2,) ], ), ), ), ); }, ); }, ); } /// Reusable Row Widget for details Widget _buildDetailTile(String label, String? value, double scaleFactor) { return Padding( padding: EdgeInsets.symmetric(vertical: 3 * scaleFactor), child: Row( crossAxisAlignment: CrossAxisAlignment.start, // Align top if value wraps children: [ // Label Expanded( flex: 5, child: Text( label, style: TextStyle( fontFamily: "JakartaRegular", fontSize: 12 * scaleFactor, color: AppColors.semi_black, ), ), ), const SizedBox(width: 4), // Value Expanded( flex: 5, child: Text( value ?? "-", style: TextStyle( fontSize: 12 * scaleFactor, 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: 3 * scaleFactor), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Label Expanded( flex: 5, child: Text( label, style: TextStyle( fontFamily: "JakartaRegular", fontSize: 12 * scaleFactor, color: AppColors.semi_black, ), ), ), const SizedBox(width: 4), // Value Expanded( flex: 5, child: Text( '${fromDate ?? "-"} to ${toDate ?? "-"}', style: TextStyle( fontSize: 12 * scaleFactor, 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(); // Hide if no time data } return Padding( padding: EdgeInsets.symmetric(vertical: 3 * scaleFactor), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Label Expanded( flex: 5, child: Text( label, style: TextStyle( fontSize: 12 * scaleFactor, color: const Color(0xff2D2D2D), fontStyle: FontStyle.normal, fontFamily: "Plus Jakarta Sans", fontWeight: FontWeight.w400, ), ), ), const SizedBox(width: 4), // Value Expanded( flex: 5, child: Text( '${fromTime ?? "-"} to ${toTime ?? "-"}', style: TextStyle( fontSize: 12 * scaleFactor, color: const Color(0xff818181), fontWeight: FontWeight.w400, ), softWrap: true, overflow: TextOverflow.visible, ), ), ], ), ); } /// 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; } } }