import 'package:dotted_line/dotted_line.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:geocoding/geocoding.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../Notifiers/hrmProvider/attendanceDetailsProvider.dart'; import '../../Notifiers/HomeScreenNotifier.dart'; import '../../Utils/app_colors.dart'; import '../finance/FileViewer.dart'; /// screen for attendance details class AttendanceRequestDetailScreen extends StatefulWidget { final attendanceListId; const AttendanceRequestDetailScreen({super.key, required this.attendanceListId}); @override State createState() => _AttendanceRequestDetailScreenState(); } class _AttendanceRequestDetailScreenState extends State { late AttendanceDetailsProvider provider; @override Widget build(BuildContext context) { return ChangeNotifierProvider( create: (_) => AttendanceDetailsProvider()..fetchAttendanceRequestDetail(context, widget.attendanceListId), child: Consumer( builder: (context, provider, child) { // 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 Scaffold( appBar: AppBar( automaticallyImplyLeading: false, backgroundColor: 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( "Attendance Details", style: TextStyle( fontSize: 18, height: 1.1, fontFamily: "Plus Jakarta Sans", fontWeight: FontWeight.w600, color: AppColors.semi_black, ), ), ), ], ), ), backgroundColor: 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!; /// scr return SingleChildScrollView( padding: EdgeInsets.all(16.0 * scaleFactor), child: Column( children: [ Card( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16 * scaleFactor), ), elevation: 2, child: Padding( padding: EdgeInsets.all(16.0 * scaleFactor), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( margin: EdgeInsets.only(bottom: 0.5 * scaleFactor), padding: EdgeInsets.all(12 * 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), // icon bg ), child: Center( child: SvgPicture.asset( height: 28 * scaleFactor, width: 28 * scaleFactor, "assets/svg/hrm/attendanceList.svg", fit: BoxFit.contain, ), ), ), SizedBox(width: 12 * scaleFactor), /// Middle text Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( details.type ?? "-", 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( details.date ?? "-", style: TextStyle( fontFamily: "JakartaRegular", fontSize: 14, color: AppColors.app_blue, ), ), ], ), ), /// Right side (Live/Manual) Container( height: 30 * scaleFactor, padding: EdgeInsets.symmetric( horizontal: 12 * scaleFactor, ), decoration: BoxDecoration( borderRadius: BorderRadius.circular(6 * scaleFactor), color: getDecorationColor(details.status) ), child: Center( child: Text( details.status ?? "-", style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: getTextColor(details.status.toString()), ), ), ), ), ], ), ), // Employee Details _buildSectionHeader("Employee Details", scaleFactor), _buildDetailTile("Employee Name", details.employeeName, scaleFactor), _buildDetailTile("Created Employee", details.createdEmpName, scaleFactor), // Check In/Out _buildSectionHeader("Check In/Out Details", scaleFactor), _buildDate_TimeTile("Check In Date & Time", details.date, details.checkInTime, scaleFactor), _buildDate_TimeTile("Check Out Date & Time", details.date, details.checkOutTime, scaleFactor), _buildDetailTile("Original Check In", details.checkInTime, scaleFactor), _buildDetailTile("Original Check Out", "--", scaleFactor), _buildDetailTile("Original Check In Location", details.checkInLocation, scaleFactor), _buildDetailTile("Original Check Out Location", details.checkOutLocation, scaleFactor), buildLocationTile("Location", details.location, scaleFactor), // Proofs if ((details.checkInProofDirFilePath != null && details.checkInProofDirFilePath!.isNotEmpty) || (details.checkOutProofDirFilePath != null && details.checkOutProofDirFilePath!.isNotEmpty)) ...[ _buildSectionHeader("Proofs", scaleFactor), if (details.checkInProofDirFilePath != null && details.checkInProofDirFilePath!.isNotEmpty) _buildProofLink(context, "Check In Proof", details.checkInProofDirFilePath, scaleFactor), if (details.checkOutProofDirFilePath != null && details.checkOutProofDirFilePath!.isNotEmpty) _buildProofLink(context, "Check Out Proof", details.checkOutProofDirFilePath, scaleFactor), ], // Remarks & Approvals _buildSectionHeader("Remarks & Approvals", scaleFactor), _buildDetailTile("Level 1 Approved By", details.level1EmpName, scaleFactor), _buildDetailTile("Level 2 Approved By", details.level2EmpName, scaleFactor), _buildDetailTile("Level 1 Remark", details.level1Remarks, scaleFactor), _buildDetailTile("Level 2 Remark", details.level2Remarks, scaleFactor), ///remain data _buildSectionHeader("Other Details", scaleFactor), _buildDetailTile("Check In Type", details.checkInType, scaleFactor), _buildDetailTile("Check Out Type", details.chechOutType, scaleFactor), _buildDetailTile("Check Out Time", details.checkOutTime, scaleFactor), // Attendance Info _buildDetailTile("ID", details.id, scaleFactor), _buildDetailTile("Attendance Type", details.attendanceType, scaleFactor), _buildDetailTile("Note", details.note, scaleFactor), _buildDetailTile("Created Datetime", details.requestedDatetime, scaleFactor), ], ), ), ), SizedBox(height: 30 * scaleFactor), ], ), ); }, ), ); }, ) ); } /// 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 wraps children: [ // Label Expanded( flex: 5, // keep same ratio as other tiles child: Text( label, style: TextStyle( fontFamily: "JakartaRegular", fontSize: 14, color: AppColors.semi_black, ), ), ), SizedBox(width: 4,), // Value Expanded( flex: 5, // take remaining width child: Text( value ?? "-", style: const TextStyle( fontSize: 14, color: Color(0xFF818181), ), softWrap: true, overflow: TextOverflow.visible, // wrap instead of clipping ), ), ], ), ); } /// for location Widget buildLocationTile(String label, String? value, double scaleFactor) { return FutureBuilder( future: getReadableLocation(value), builder: (context, snapshot) { final locationText = snapshot.data ?? "-"; return Padding( padding: EdgeInsets.symmetric(vertical: 6 * scaleFactor), child: Row( crossAxisAlignment: CrossAxisAlignment.start, // aligns top when wrapping children: [ // Label Expanded( flex: 5, // ratio (adjust same as your Date/Time tile) child: Text( label, style: TextStyle( fontFamily: "JakartaRegular", fontSize: 14, color: AppColors.semi_black, ), ), ), // Value (Clickable Location) Expanded( flex: 5, // take remaining space child: GestureDetector( onTap: () async { final uri = Uri.parse( "https://www.google.com/maps/search/?api=1&query=$value"); if (await canLaunchUrl(uri)) { await launchUrl(uri, mode: LaunchMode.externalApplication); } }, child: Text( locationText, style: const TextStyle( fontSize: 14, color: Colors.blue, decoration: TextDecoration.underline, fontWeight: FontWeight.w400, ), softWrap: true, overflow: TextOverflow.visible, ), ), ), ], ), ); }, ); } Future getReadableLocation(String? value) async { if (value == null) return "-"; try { List locations = await locationFromAddress(value); List placemarks = await placemarkFromCoordinates( locations[0].latitude, locations[0].longitude, ); return placemarks.first.locality ?? value; } catch (e) { return value; // fallback to raw coordinates } } /// for date and time Widget _buildDate_TimeTile(String label, String? date, String? time, double scaleFactor) { return Padding( padding: EdgeInsets.symmetric(vertical: 6 * scaleFactor), child: Row( crossAxisAlignment: CrossAxisAlignment.start, // align top when wrapped children: [ // Label Expanded( flex: 5, // adjust ratio child: Text( label, style: TextStyle( fontFamily: "JakartaRegular", fontSize: 14, color: AppColors.semi_black, ), ), ), SizedBox(width: 4,), // Value (date + time) Expanded( flex: 5, // adjust ratio so both fill row child: Text( '${date ?? "-"}, ${time ?? "-"}', style: const TextStyle( fontSize: 14, color: Color(0xff818181), fontWeight: FontWeight.w400, ), softWrap: true, // allow wrapping overflow: TextOverflow.visible, ), ), ], ), ); } 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, ), ), ], ), ); } /// Proof section (image/file path) Widget _buildProofLink(BuildContext context, String label, String? filePath, double scaleFactor) { return Padding( padding: EdgeInsets.symmetric(vertical: 6 * scaleFactor), child: Row( children: [ Expanded( flex: 5, child: Text( label, style: TextStyle( fontFamily: "JakartaRegular", fontSize: 14, color: AppColors.semi_black, ), ), ), Expanded( flex: 0, child: filePath != null ? InkWell( onTap: () { print("++++++++++++++++ImageUrel: $filePath"); Navigator.push( context, MaterialPageRoute( builder: (context) => Image.network(filePath), // Fileviewer(fileName: label, fileUrl: "assets/images/capa.svg"), ), ); }, child: const Text( "View", style: TextStyle( fontSize: 14, color: Colors.blue, fontStyle: FontStyle.normal, fontFamily: "Plus Jakarta Sans", fontWeight: FontWeight.w400, decoration: TextDecoration.underline ), ), ) : const Text("-"), ), ], ), ); } 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 'Processed': return AppColors.processed_text_color; case 'Payment Rejected': return AppColors.rejected_text_color; } return color; } Color getDecorationColor(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 'Processed': return AppColors.processed_bg_color; case 'Payment Rejected': return AppColors.rejected_bg_color; } return color; } }