import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:on_field_work/helpers/widgets/my_text.dart'; import 'package:on_field_work/helpers/utils/base_bottom_sheet.dart'; import 'package:on_field_work/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: InteractiveViewer( child: CachedNetworkImage( imageUrl: imageUrl, fit: BoxFit.contain, errorWidget: (_, __, ___) => const Icon(Icons.broken_image, size: 50, color: Colors.grey), ), ), ), ); } Future _showLogsBottomSheet(BuildContext context) async { await widget.attendanceController.fetchLogsView(widget.employee.id.toString()); Map expandedDescription = {}; showModalBottomSheet( context: context, isScrollControlled: true, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), backgroundColor: Colors.transparent, builder: (context) => BaseBottomSheet( title: "Attendance Log", showButtons: false, onCancel: () => Navigator.pop(context), onSubmit: () => Navigator.pop(context), child: widget.attendanceController.attendenceLogsView.isEmpty ? Padding( padding: const EdgeInsets.symmetric(vertical: 32.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.info_outline, size: 40, color: Colors.grey), SizedBox(height: 12), MyText.bodySmall("No attendance logs available."), ], ), ) : StatefulBuilder( builder: (context, setStateSB) => 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]; // Determine if the log date is today or yesterday 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; } Widget _logIcon() { int activity = log.activity ?? -1; IconData iconData; Color iconColor; 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); } 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: [ _logIcon(), 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), const SizedBox(height: 12), // Middle row: Image + Text Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (log.thumbPreSignedUrl != null) GestureDetector( onTap: () { if (log.preSignedUrl != null) { _showImageDialog(context, log.preSignedUrl!); } }, child: SizedBox( width: 60, height: 60, child: ClipRRect( borderRadius: BorderRadius.circular(8), child: CachedNetworkImage( imageUrl: log.thumbPreSignedUrl!, fit: BoxFit.cover, placeholder: (context, url) => const Center( child: CircularProgressIndicator(strokeWidth: 2)), errorWidget: (context, url, error) => const Icon( Icons.broken_image, size: 40, color: Colors.grey, ), ), ), ), ), if (log.thumbPreSignedUrl != null) const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (log.updatedByEmployee != null) MyText.bodySmall( "By: ${log.updatedByEmployee!.firstName} ${log.updatedByEmployee!.lastName}", color: Colors.grey[700], ), const SizedBox(height: 8), if (log.latitude != null && log.longitude != null) 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: [ const Icon(Icons.location_on, size: 16, color: Colors.blue), const SizedBox(width: 4), MyText.bodySmall( "View Location", color: Colors.blue, decoration: TextDecoration.underline, ), ], ), ), if (log.latitude != null && log.longitude != null) const SizedBox(height: 8), 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), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)), elevation: 3, ), child: FittedBox( fit: BoxFit.scaleDown, child: MyText.bodySmall( "View", overflow: TextOverflow.ellipsis, color: Colors.white, fontWeight: 600, ), ), ), ); } }