import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:intl/intl.dart'; import 'package:marco/helpers/theme/app_theme.dart'; import 'package:marco/helpers/utils/mixins/ui_mixin.dart'; import 'package:marco/helpers/utils/my_shadow.dart'; import 'package:marco/helpers/widgets/my_card.dart'; import 'package:marco/helpers/widgets/my_container.dart'; import 'package:marco/helpers/widgets/my_spacing.dart'; import 'package:marco/helpers/widgets/my_text.dart'; import 'package:marco/controller/permission_controller.dart'; import 'package:marco/controller/task_planning/daily_task_controller.dart'; import 'package:marco/model/dailyTaskPlanning/daily_progress_report_filter.dart'; import 'package:marco/helpers/widgets/avatar.dart'; import 'package:marco/controller/project_controller.dart'; import 'package:marco/model/dailyTaskPlanning/task_action_buttons.dart'; import 'package:marco/helpers/widgets/my_custom_skeleton.dart'; import 'package:marco/helpers/utils/permission_constants.dart'; import 'package:marco/helpers/widgets/my_refresh_indicator.dart'; import 'package:marco/controller/tenant/service_controller.dart'; import 'package:marco/helpers/widgets/tenant/service_selector.dart'; class DailyProgressReportScreen extends StatefulWidget { const DailyProgressReportScreen({super.key}); @override State createState() => _DailyProgressReportScreenState(); } class TaskChartData { final String label; final num value; final Color color; TaskChartData(this.label, this.value, this.color); } class _DailyProgressReportScreenState extends State with UIMixin { final DailyTaskController dailyTaskController = Get.put(DailyTaskController()); final PermissionController permissionController = Get.put(PermissionController()); final ProjectController projectController = Get.find(); final ServiceController serviceController = Get.put(ServiceController()); final ScrollController _scrollController = ScrollController(); @override void initState() { super.initState(); _scrollController.addListener(() { if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 100 && dailyTaskController.hasMore && !dailyTaskController.isLoadingMore.value) { final projectId = dailyTaskController.selectedProjectId; if (projectId != null && projectId.isNotEmpty) { dailyTaskController.fetchTaskData( projectId, pageNumber: dailyTaskController.currentPage + 1, pageSize: dailyTaskController.pageSize, isLoadMore: true, ); } } }); final initialProjectId = projectController.selectedProjectId.value; if (initialProjectId.isNotEmpty) { dailyTaskController.selectedProjectId = initialProjectId; dailyTaskController.fetchTaskData(initialProjectId); serviceController.fetchServices(initialProjectId); } // Update when project changes ever(projectController.selectedProjectId, (newProjectId) async { if (newProjectId.isNotEmpty && newProjectId != dailyTaskController.selectedProjectId) { dailyTaskController.selectedProjectId = newProjectId; await dailyTaskController.fetchTaskData(newProjectId); await serviceController.fetchServices(newProjectId); dailyTaskController.update(['daily_progress_report_controller']); } }); } @override void dispose() { _scrollController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: PreferredSize( preferredSize: const Size.fromHeight(72), child: AppBar( backgroundColor: const Color(0xFFF5F5F5), elevation: 0.5, automaticallyImplyLeading: false, titleSpacing: 0, title: Padding( padding: MySpacing.xy(16, 0), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ IconButton( icon: const Icon(Icons.arrow_back_ios_new, color: Colors.black, size: 20), onPressed: () => Get.offNamed('/dashboard'), ), MySpacing.width(8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ MyText.titleLarge( 'Daily Progress Report', fontWeight: 700, color: Colors.black, ), MySpacing.height(2), GetBuilder( builder: (projectController) { final projectName = projectController.selectedProject?.name ?? 'Select Project'; return Row( children: [ const Icon(Icons.work_outline, size: 14, color: Colors.grey), MySpacing.width(4), Expanded( child: MyText.bodySmall( projectName, fontWeight: 600, overflow: TextOverflow.ellipsis, color: Colors.grey[700], ), ), ], ); }, ), ], ), ), ], ), ), ), ), body: SafeArea( child: MyRefreshIndicator( onRefresh: _refreshData, child: CustomScrollView( physics: const AlwaysScrollableScrollPhysics(), slivers: [ SliverToBoxAdapter( child: GetBuilder( init: dailyTaskController, tag: 'daily_progress_report_controller', builder: (controller) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ MySpacing.height(flexSpacing), // --- ADD SERVICE SELECTOR HERE --- Padding( padding: MySpacing.x(10), child: ServiceSelector( controller: serviceController, height: 40, onSelectionChanged: (service) async { final projectId = dailyTaskController.selectedProjectId; if (projectId?.isNotEmpty ?? false) { await dailyTaskController.fetchTaskData( projectId!, serviceIds: service != null ? [service.id] : null, pageNumber: 1, pageSize: 20, ); } }, ), ), _buildActionBar(), Padding( padding: MySpacing.x(8), child: _buildDailyProgressReportTab(), ), ], ); }, ), ), ], ), ), ), ); } Widget _buildActionBar() { return Padding( padding: MySpacing.x(flexSpacing), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ _buildActionItem( label: "Filter", icon: Icons.tune, tooltip: 'Filter Project', onTap: _openFilterSheet, ), ], ), ); } Widget _buildActionItem({ required String label, required IconData icon, required String tooltip, required VoidCallback onTap, Color? color, }) { return Row( children: [ MyText.bodyMedium(label, fontWeight: 600), Tooltip( message: tooltip, child: InkWell( borderRadius: BorderRadius.circular(22), onTap: onTap, child: MouseRegion( cursor: SystemMouseCursors.click, child: Padding( padding: const EdgeInsets.all(8.0), child: Icon(icon, color: color, size: 22), ), ), ), ), ], ); } Future _openFilterSheet() async { final result = await showModalBottomSheet>( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (context) => DailyProgressReportFilter( controller: dailyTaskController, permissionController: permissionController, ), ); if (result != null) { final selectedProjectId = result['projectId'] as String?; if (selectedProjectId != null && selectedProjectId != dailyTaskController.selectedProjectId) { dailyTaskController.selectedProjectId = selectedProjectId; await dailyTaskController.fetchTaskData(selectedProjectId); dailyTaskController.update(['daily_progress_report_controller']); } } } Future _refreshData() async { final projectId = dailyTaskController.selectedProjectId; if (projectId != null) { try { await dailyTaskController.fetchTaskData(projectId); } catch (e) { debugPrint('Error refreshing task data: $e'); } } } void _showTeamMembersBottomSheet(List members) { showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, isDismissible: true, enableDrag: true, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(12)), ), builder: (context) { return GestureDetector( onTap: () {}, child: Container( decoration: BoxDecoration( color: Colors.white, borderRadius: const BorderRadius.vertical(top: Radius.circular(12)), ), padding: const EdgeInsets.fromLTRB(16, 24, 16, 24), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ MyText.titleMedium( 'Team Members', fontWeight: 600, ), const SizedBox(height: 8), const Divider(thickness: 1), const SizedBox(height: 8), ...members.map((member) { final firstName = member.firstName ?? 'Unnamed'; final lastName = member.lastName ?? 'User'; return ListTile( contentPadding: EdgeInsets.zero, leading: Avatar( firstName: firstName, lastName: lastName, size: 31, ), title: MyText.bodyMedium( '$firstName $lastName', fontWeight: 600, ), ); }), const SizedBox(height: 8), ], ), ), ); }, ); } Widget _buildDailyProgressReportTab() { return Obx(() { final isLoading = dailyTaskController.isLoading.value; final groupedTasks = dailyTaskController.groupedDailyTasks; // Initial loading skeleton if (isLoading && dailyTaskController.currentPage == 1) { return SkeletonLoaders.dailyProgressReportSkeletonLoader(); } // No tasks if (groupedTasks.isEmpty) { return Center( child: MyText.bodySmall( "No Progress Report Found", fontWeight: 600, ), ); } final sortedDates = groupedTasks.keys.toList() ..sort((a, b) => b.compareTo(a)); // If only one date, make it expanded by default if (sortedDates.length == 1 && !dailyTaskController.expandedDates.contains(sortedDates[0])) { dailyTaskController.expandedDates.add(sortedDates[0]); } return MyCard.bordered( borderRadiusAll: 10, border: Border.all(color: Colors.grey.withOpacity(0.2)), shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom), paddingAll: 8, child: ListView.builder( controller: _scrollController, shrinkWrap: true, physics: const AlwaysScrollableScrollPhysics(), itemCount: sortedDates.length + 1, // +1 for loading indicator itemBuilder: (context, dateIndex) { // Bottom loading indicator if (dateIndex == sortedDates.length) { return Obx(() => dailyTaskController.isLoadingMore.value ? const Padding( padding: EdgeInsets.all(16.0), child: Center(child: CircularProgressIndicator()), ) : const SizedBox.shrink()); } final dateKey = sortedDates[dateIndex]; final tasksForDate = groupedTasks[dateKey]!; final date = DateTime.tryParse(dateKey); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ GestureDetector( onTap: () => dailyTaskController.toggleDate(dateKey), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ MyText.bodyMedium( date != null ? DateFormat('dd MMM yyyy').format(date) : dateKey, fontWeight: 700, ), Obx(() => Icon( dailyTaskController.expandedDates.contains(dateKey) ? Icons.remove_circle : Icons.add_circle, color: Colors.blueAccent, )), ], ), ), Obx(() { if (!dailyTaskController.expandedDates.contains(dateKey)) { return const SizedBox.shrink(); } return Column( children: tasksForDate.asMap().entries.map((entry) { final task = entry.value; final activityName = task.workItem?.activityMaster?.activityName ?? 'N/A'; final activityId = task.workItem?.activityMaster?.id; final workAreaId = task.workItem?.workArea?.id; final location = [ task.workItem?.workArea?.floor?.building?.name, task.workItem?.workArea?.floor?.floorName, task.workItem?.workArea?.areaName ].where((e) => e?.isNotEmpty ?? false).join(' > '); final planned = task.plannedTask; final completed = task.completedTask; final progress = (planned != 0) ? (completed / planned).clamp(0.0, 1.0) : 0.0; final parentTaskID = task.id; return Padding( padding: const EdgeInsets.only(bottom: 8), child: MyContainer( paddingAll: 12, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ MyText.bodyMedium(activityName, fontWeight: 600), const SizedBox(height: 2), MyText.bodySmall(location, color: Colors.grey), const SizedBox(height: 8), GestureDetector( onTap: () => _showTeamMembersBottomSheet( task.teamMembers), child: Row( children: [ const Icon(Icons.group, size: 18, color: Colors.blueAccent), const SizedBox(width: 6), MyText.bodyMedium('Team', color: Colors.blueAccent, fontWeight: 600), ], ), ), const SizedBox(height: 8), MyText.bodySmall( "Completed: $completed / $planned", fontWeight: 600, color: Colors.black87, ), const SizedBox(height: 6), Stack( children: [ Container( height: 5, decoration: BoxDecoration( color: Colors.grey[300], borderRadius: BorderRadius.circular(6), ), ), FractionallySizedBox( widthFactor: progress, child: Container( height: 5, decoration: BoxDecoration( color: progress >= 1.0 ? Colors.green : progress >= 0.5 ? Colors.amber : Colors.red, borderRadius: BorderRadius.circular(6), ), ), ), ], ), const SizedBox(height: 4), MyText.bodySmall( "${(progress * 100).toStringAsFixed(1)}%", fontWeight: 500, color: progress >= 1.0 ? Colors.green[700] : progress >= 0.5 ? Colors.amber[800] : Colors.red[700], ), const SizedBox(height: 12), SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ if ((task.reportedDate == null || task.reportedDate .toString() .isEmpty) && permissionController.hasPermission( Permissions.assignReportTask)) ...[ TaskActionButtons.reportButton( context: context, task: task, completed: completed.toInt(), refreshCallback: _refreshData, ), const SizedBox(width: 4), ] else if (task.approvedBy == null && permissionController.hasPermission( Permissions.approveTask)) ...[ TaskActionButtons.reportActionButton( context: context, task: task, parentTaskID: parentTaskID, workAreaId: workAreaId.toString(), activityId: activityId.toString(), completed: completed.toInt(), refreshCallback: _refreshData, ), const SizedBox(width: 5), ], TaskActionButtons.commentButton( context: context, task: task, parentTaskID: parentTaskID, workAreaId: workAreaId.toString(), activityId: activityId.toString(), refreshCallback: _refreshData, ), ], ), ), ], ), ), ); }).toList(), ); }) ], ); }, ), ); }); } }