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'; 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 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); } // Update when project changes ever(projectController.selectedProjectId, (newProjectId) async { if (newProjectId.isNotEmpty && newProjectId != dailyTaskController.selectedProjectId) { dailyTaskController.selectedProjectId = newProjectId; await dailyTaskController.fetchTaskData(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), Padding( padding: MySpacing.x(15), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ InkWell( borderRadius: BorderRadius.circular(22), onTap: _openFilterSheet, child: Padding( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4), child: Row( children: [ MyText.bodySmall( "Filter", fontWeight: 600, color: Colors.black, ), const SizedBox(width: 4), Icon(Icons.tune, size: 20, color: Colors.black), ], ), ), ), ], ), ), MySpacing.height(8), Padding( padding: MySpacing.x(8), child: _buildDailyProgressReportTab(), ), ], ); }, ), ), ], ), ), ), ); } Future _openFilterSheet() async { // ✅ Fetch filter data first if (dailyTaskController.taskFilterData == null) { await dailyTaskController .fetchTaskFilter(dailyTaskController.selectedProjectId ?? ''); } final result = await showModalBottomSheet( context: context, isScrollControlled: true, builder: (context) => DailyTaskFilterBottomSheet( controller: dailyTaskController, ), ); 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; // 🟡 Show loading skeleton on first load if (isLoading && dailyTaskController.currentPage == 1) { return SkeletonLoaders.dailyProgressReportSkeletonLoader(); } // ⚪ No data available if (groupedTasks.isEmpty) { return Center( child: MyText.bodySmall( "No Progress Report Found", fontWeight: 600, ), ); } // 🔽 Sort all date keys by descending (latest first) final sortedDates = groupedTasks.keys.toList() ..sort((a, b) => b.compareTo(a)); // 🔹 Auto expand if only one date present if (sortedDates.length == 1 && !dailyTaskController.expandedDates.contains(sortedDates[0])) { dailyTaskController.expandedDates.add(sortedDates[0]); } // 🧱 Return a scrollable column of cards return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ...sortedDates.map((dateKey) { final tasksForDate = groupedTasks[dateKey]!; final date = DateTime.tryParse(dateKey); return Padding( padding: const EdgeInsets.only(bottom: 12), child: MyCard.bordered( borderRadiusAll: 10, border: Border.all(color: Colors.grey.withOpacity(0.2)), shadow: MyShadow(elevation: 1, position: MyShadowPosition.bottom), paddingAll: 12, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 🗓️ Date Header GestureDetector( onTap: () => dailyTaskController.toggleDate(dateKey), child: Padding( padding: const EdgeInsets.only(bottom: 8), 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, )), ], ), ), ), // 🔽 Task List (expandable) Obx(() { if (!dailyTaskController.expandedDates .contains(dateKey)) { return const SizedBox.shrink(); } return Column( children: tasksForDate.map((task) { 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: 10), child: MyContainer( paddingAll: 12, borderRadiusAll: 8, border: Border.all( color: Colors.grey.withOpacity(0.2)), color: Colors.white, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 🏗️ Activity name & location MyText.bodyMedium(activityName, fontWeight: 600), const SizedBox(height: 2), MyText.bodySmall(location, color: Colors.grey), const SizedBox(height: 8), // 👥 Team Members 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), // 📊 Progress info 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), // 🎯 Action Buttons SingleChildScrollView( scrollDirection: Axis.horizontal, physics: const ClampingScrollPhysics(), primary: false, 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(), ); }), ], ), ), ); }), // 🔻 Loading More Indicator Obx(() => dailyTaskController.isLoadingMore.value ? const Padding( padding: EdgeInsets.symmetric(vertical: 16), child: Center(child: CircularProgressIndicator()), ) : const SizedBox.shrink()), ], ); }); } }