import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:marco/helpers/widgets/my_text.dart'; import 'package:marco/helpers/utils/base_bottom_sheet.dart'; import 'package:marco/helpers/utils/date_time_utils.dart'; class AttendanceLogViewButton extends StatefulWidget { final dynamic employee; final dynamic attendanceController; const AttendanceLogViewButton({ Key? key, required this.employee, required this.attendanceController, }) : super(key: key); @override State createState() => _AttendanceLogViewButtonState(); } class _AttendanceLogViewButtonState extends State { Future _openGoogleMaps( BuildContext context, double lat, double lon) async { final url = 'https://www.google.com/maps/search/?api=1&query=$lat,$lon'; if (await canLaunchUrl(Uri.parse(url))) { await launchUrl( Uri.parse(url), mode: LaunchMode.externalApplication, ); } else { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Could not open Google Maps')), ); } } void _showImageDialog(BuildContext context, String imageUrl) { showDialog( context: context, builder: (_) => Dialog( child: Image.network( imageUrl, fit: BoxFit.cover, height: 400, errorBuilder: (context, error, stackTrace) { return const Icon( Icons.broken_image, size: 50, color: Colors.grey, ); }, ), ), ); } void _showLogsBottomSheet(BuildContext context) async { await widget.attendanceController .fetchLogsView(widget.employee.id.toString()); showModalBottomSheet( context: context, isScrollControlled: true, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), backgroundColor: Colors.transparent, builder: (context) { Map expandedDescription = {}; return BaseBottomSheet( title: "Attendance Log", onCancel: () => Navigator.pop(context), onSubmit: () => Navigator.pop(context), showButtons: false, child: widget.attendanceController.attendenceLogsView.isEmpty ? Padding( padding: const EdgeInsets.symmetric(vertical: 24.0), child: Column( children: [ Icon(Icons.info_outline, size: 40, color: Colors.grey), SizedBox(height: 8), MyText.bodySmall("No attendance logs available."), ], ), ) : StatefulBuilder( builder: (context, setStateSB) { return ListView.separated( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: widget.attendanceController.attendenceLogsView.length, separatorBuilder: (_, __) => const SizedBox(height: 16), itemBuilder: (_, index) { final log = widget .attendanceController.attendenceLogsView[index]; return Container( decoration: BoxDecoration( color: Theme.of(context).colorScheme.surfaceVariant, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 6, offset: const Offset(0, 2), ) ], ), padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header: Icon + Date + Time Row( children: [ _getLogIcon(log), const SizedBox(width: 12), MyText.bodyLarge( (log.formattedDate != null && log.formattedDate!.isNotEmpty) ? DateTimeUtils.convertUtcToLocal( log.formattedDate!, format: 'd MMM yyyy', ) : '-', fontWeight: 600, ), const SizedBox(width: 12), MyText.bodySmall( log.formattedTime != null ? "Time: ${log.formattedTime}" : "", color: Colors.grey[700], ), ], ), const SizedBox(height: 12), const Divider(height: 1, color: Colors.grey), // Middle Row: Image + Text (Done by, Description & Location) Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Image Column if (log.thumbPreSignedUrl != null) GestureDetector( onTap: () { if (log.preSignedUrl != null) { _showImageDialog( context, log.preSignedUrl!); } }, child: ClipRRect( borderRadius: BorderRadius.circular(8), child: Image.network( log.thumbPreSignedUrl!, height: 60, width: 60, fit: BoxFit.cover, errorBuilder: (_, __, ___) => const Icon(Icons.broken_image, size: 40, color: Colors.grey), ), ), ), if (log.thumbPreSignedUrl != null) const SizedBox(width: 12), // Text Column Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Done by if (log.updatedByEmployee != null) MyText.bodySmall( "By: ${log.updatedByEmployee!.firstName} ${log.updatedByEmployee!.lastName}", color: Colors.grey[700], ), const SizedBox(height: 8), // Location if (log.latitude != null && log.longitude != null) Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ GestureDetector( onTap: () { final lat = double.tryParse( log.latitude .toString()) ?? 0.0; final lon = double.tryParse( log.longitude .toString()) ?? 0.0; if (lat >= -90 && lat <= 90 && lon >= -180 && lon <= 180) { _openGoogleMaps( context, lat, lon); } else { ScaffoldMessenger.of( context) .showSnackBar( SnackBar( content: MyText.bodySmall( "Invalid location coordinates")), ); } }, child: Row( children: [ Icon(Icons.location_on, size: 16, color: Colors.blue), SizedBox(width: 4), MyText.bodySmall( "View Location", color: Colors.blue, decoration: TextDecoration.underline, ), ], ), ), ], ), const SizedBox(height: 8), // Description with label and more/less using MyText if (log.comment != null && log.comment!.isNotEmpty) Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ MyText.bodySmall( "Description: ${log.comment!}", maxLines: expandedDescription[ index] == true ? null : 2, overflow: expandedDescription[ index] == true ? TextOverflow.visible : TextOverflow.ellipsis, ), if (log.comment!.length > 100) GestureDetector( onTap: () { setStateSB(() { expandedDescription[ index] = !(expandedDescription[ index] == true); }); }, child: MyText.bodySmall( expandedDescription[ index] == true ? "less" : "more", color: Colors.blue, fontWeight: 600, ), ), ], ) else MyText.bodySmall( "Description: No description provided", fontWeight: 700, ), ], ), ), ], ), ], ), ); }, ); }, ), ); }, ); } @override Widget build(BuildContext context) { return SizedBox( height: 30, child: ElevatedButton( onPressed: () => _showLogsBottomSheet(context), style: ElevatedButton.styleFrom( backgroundColor: Colors.indigo, textStyle: const TextStyle(fontSize: 12), padding: const EdgeInsets.symmetric(horizontal: 12), ), child: FittedBox( fit: BoxFit.scaleDown, child: MyText.bodySmall( "View", overflow: TextOverflow.ellipsis, color: Colors.white, ), ), ), ); } Widget _getLogIcon(dynamic log) { int activity = log.activity ?? -1; IconData iconData; Color iconColor; bool isTodayOrYesterday = false; try { if (log.formattedDate != null) { final logDate = DateTime.parse(log.formattedDate); final now = DateTime.now(); final today = DateTime(now.year, now.month, now.day); final logDay = DateTime(logDate.year, logDate.month, logDate.day); final yesterday = today.subtract(const Duration(days: 1)); isTodayOrYesterday = (logDay == today) || (logDay == yesterday); } } catch (_) { isTodayOrYesterday = false; } switch (activity) { case 0: case 1: iconData = Icons.arrow_circle_right; iconColor = Colors.green; break; case 2: iconData = Icons.hourglass_top; iconColor = Colors.blueGrey; break; case 4: if (isTodayOrYesterday) { iconData = Icons.arrow_circle_left; iconColor = Colors.red; } else { iconData = Icons.check; iconColor = Colors.green; } break; case 5: iconData = Icons.close; iconColor = Colors.red; break; default: iconData = Icons.info; iconColor = Colors.grey; } return Icon(iconData, color: iconColor, size: 20); } }