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 // void initState() { // super.initState(); // // /// fetch API after widget is built // WidgetsBinding.instance.addPostFrameCallback((_) { // final home = Provider.of(context, listen: false); // provider = Provider.of(context, listen: false); // // provider.fetchAttendanceRequestDetail( // context, // widget.attendanceListId // ); // }); // } @override Widget build(BuildContext context) { return ChangeNotifierProvider( create: (_) => AttendanceDetailsProvider()..fetchAttendanceRequestDetail(context, widget.attendanceListId), child: Consumer( builder: (context, provider, child) { 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, ), ), SizedBox(width: 10), 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: const EdgeInsets.all(16.0), child: Column( children: [ Card( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), elevation: 2, child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( margin: const EdgeInsets.only(bottom: 0.5), padding: const EdgeInsets.all(12), 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/attendanceList.svg", fit: BoxFit.contain, ), ), ), const SizedBox(width: 12), /// Middle text Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( details.type ?? "-", style: const TextStyle( fontSize: 14, fontStyle: FontStyle.normal, fontFamily: "Plus Jakarta Sans", fontWeight: FontWeight.w400, ), ), const SizedBox(height: 2), Text( details.date ?? "-", style: const TextStyle( fontSize: 12, fontStyle: FontStyle.normal, fontFamily: "Plus Jakarta Sans", fontWeight: FontWeight.w400, color: Color(0xff818181), ), ), ], ), ), /// Right side (Live/Manual) Container( height: 30, padding: const EdgeInsets.symmetric(horizontal: 12), decoration: BoxDecoration( borderRadius: BorderRadius.circular(6), 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"), _buildDetailTile("Employee Name", details.employeeName), _buildDetailTile("Created Employee", details.createdEmpName), // Check In/Out _buildSectionHeader("Check In/Out Details"), _buildDate_TimeTile("Check In Date & Time", details.date, details.checkInTime), _buildDate_TimeTile("Check Out Date & Time", details.date, details.checkOutTime), _buildDetailTile("Original Check In", details.checkInTime), _buildDetailTile("Original Check Out", "--"), _buildDetailTile("Original Check In Location", details.checkInLocation), _buildDetailTile("Original Check Out Location", details.checkOutLocation), buildLocationTile("Location", details.location), // Proofs _buildSectionHeader("Proofs"), _buildProofLink(context,"Check In Proof", details.checkInProofDirFilePath), _buildProofLink(context,"Check Out Proof", details.checkOutProofDirFilePath), // Remarks & Approvals _buildSectionHeader("Remarks & Approvals"), _buildDetailTile("Level 1 Approved By", details.level1EmpName), _buildDetailTile("Level 2 Approved By", details.level2EmpName), _buildDetailTile("Level 1 Remark", details.level1Remarks), _buildDetailTile("Level 2 Remark", details.level2Remarks), ///remain data _buildSectionHeader("Other Details"), _buildDetailTile("Check In Type", details.checkInType), _buildDetailTile("Check Out Type", details.chechOutType), _buildDetailTile("Check Out Time", details.checkOutTime), // Attendance Info _buildDetailTile("ID", details.id), _buildDetailTile("Attendance Type", details.attendanceType), _buildDetailTile("Note", details.note), _buildDetailTile("Created Datetime", details.requestedDatetime), ], ), ), ), SizedBox(height: 30,) ], ), ); }, ), ); }, ) ); } /// 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: const TextStyle( fontSize: 14, color: Color(0xff2D2D2D), fontStyle: FontStyle.normal, fontFamily: "Plus Jakarta Sans", fontWeight: FontWeight.w400, ), ), ), Expanded( flex: 0, child: Text(value ?? "-", style: const TextStyle( fontSize: 14, color: Color(0xff818181), fontStyle: FontStyle.normal, fontFamily: "Plus Jakarta Sans", fontWeight: FontWeight.w400, ) ), ), ], ), ); } /// for location Widget buildLocationTile(String label, String? value) { return FutureBuilder( future: getReadableLocation(value), builder: (context, snapshot) { final locationText = snapshot.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: 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, fontStyle: FontStyle.normal, fontFamily: "Plus Jakarta Sans", fontWeight: FontWeight.w400, ), ), ), ), ], ), ); }, ); } 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) { return Padding( padding: const EdgeInsets.symmetric(vertical: 3), child: Row( children: [ Expanded( flex: 5, 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: Row( children: [ Text('$date, ' ?? "-", style: const TextStyle( fontSize: 14, color: Color(0xff818181), fontStyle: FontStyle.normal, fontFamily: "Plus Jakarta Sans", fontWeight: FontWeight.w400, ) ), Text(time ?? "-", style: const TextStyle( fontSize: 14, color: Color(0xff818181), fontStyle: FontStyle.normal, fontFamily: "Plus Jakarta Sans", fontWeight: FontWeight.w400, ) ), ], ) ), ], ), ); } /////////////////////// Widget _buildSectionHeader(String title) { return Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Row( children: [ Text( title, style: const TextStyle( fontStyle: FontStyle.normal, fontFamily: "Plus Jakarta Sans", fontWeight: FontWeight.w600, fontSize: 14, ), ), const SizedBox(width: 10), Expanded( child: DottedLine( dashLength: 4, dashGapLength: 2, lineThickness: 1, dashColor: Color(0xff888888), ) ), ], ), ); } /// Proof section (image/file path) Widget _buildProofLink(BuildContext context, String label, String? filePath) { return Padding( padding: const EdgeInsets.symmetric(vertical: 6), child: Row( children: [ Expanded( flex: 5, child: Text( label, style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w500, ), ), ), Expanded( flex: 0, child: filePath != null ? InkWell( onTap: () { showDialog( context: context, builder: (_) => Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), child: ClipRRect( borderRadius: BorderRadius.circular(12), child: Fileviewer(fileName: label, fileUrl: filePath,), ), ), ); }, 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; } }