- Added TaskListModel for managing daily tasks with JSON parsing. - Introduced WorkStatusResponseModel and WorkStatus for handling work status data. - Created MenuResponse and MenuItem models for dynamic menu management. - Updated routes to reflect correct naming conventions for task planning screens. - Enhanced DashboardScreen to include dynamic menu functionality and improved task statistics display. - Developed DailyProgressReportScreen for displaying daily progress reports with filtering options. - Implemented DailyTaskPlanningScreen for planning daily tasks with detailed views and actions. - Refactored left navigation bar to align with updated task planning routes.
		
			
				
	
	
		
			249 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			249 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
| import 'package:file_picker/file_picker.dart';
 | |
| import 'package:flutter/material.dart';
 | |
| import 'package:marco/controller/my_controller.dart';
 | |
| import 'package:marco/helpers/widgets/my_form_validator.dart';
 | |
| import 'package:marco/helpers/services/api_service.dart';
 | |
| import 'package:get/get.dart';
 | |
| import 'package:marco/helpers/services/app_logger.dart';
 | |
| import 'package:marco/helpers/widgets/my_snackbar.dart';
 | |
| import 'package:marco/controller/task_Planning/daily_task_Planning_controller.dart';
 | |
| import 'package:image_picker/image_picker.dart';
 | |
| import 'dart:io';
 | |
| import 'dart:convert';
 | |
| import 'package:marco/helpers/widgets/my_image_compressor.dart';
 | |
| 
 | |
| enum ApiStatus { idle, loading, success, failure }
 | |
| 
 | |
| final DailyTaskPlanningController taskController = Get.put(DailyTaskPlanningController());
 | |
| final ImagePicker _picker = ImagePicker();
 | |
| 
 | |
| class ReportTaskController extends MyController {
 | |
|   List<PlatformFile> files = [];
 | |
|   MyFormValidator basicValidator = MyFormValidator();
 | |
|   RxBool isLoading = false.obs;
 | |
|   Rx<ApiStatus> reportStatus = ApiStatus.idle.obs;
 | |
|   Rx<ApiStatus> commentStatus = ApiStatus.idle.obs;
 | |
| 
 | |
|   RxList<File> selectedImages = <File>[].obs;
 | |
| 
 | |
|   final assignedDateController = TextEditingController();
 | |
|   final workAreaController = TextEditingController();
 | |
|   final activityController = TextEditingController();
 | |
|   final teamSizeController = TextEditingController();
 | |
|   final taskIdController = TextEditingController();
 | |
|   final assignedController = TextEditingController();
 | |
|   final completedWorkController = TextEditingController();
 | |
|   final commentController = TextEditingController();
 | |
|   final assignedByController = TextEditingController();
 | |
|   final teamMembersController = TextEditingController();
 | |
|   final plannedWorkController = TextEditingController();
 | |
| 
 | |
|   @override
 | |
|   void onInit() {
 | |
|     super.onInit();
 | |
|     logSafe("Initializing ReportTaskController...");
 | |
|     basicValidator
 | |
|       ..addField('assigned_date', label: "Assigned Date", controller: assignedDateController)
 | |
|       ..addField('work_area', label: "Work Area", controller: workAreaController)
 | |
|       ..addField('activity', label: "Activity", controller: activityController)
 | |
|       ..addField('team_size', label: "Team Size", controller: teamSizeController)
 | |
|       ..addField('task_id', label: "Task Id", controller: taskIdController)
 | |
|       ..addField('assigned', label: "Assigned", controller: assignedController)
 | |
|       ..addField('completed_work', label: "Completed Work", required: true, controller: completedWorkController)
 | |
|       ..addField('comment', label: "Comment", required: true, controller: commentController)
 | |
|       ..addField('assigned_by', label: "Assigned By", controller: assignedByController)
 | |
|       ..addField('team_members', label: "Team Members", controller: teamMembersController)
 | |
|       ..addField('planned_work', label: "Planned Work", controller: plannedWorkController);
 | |
|     logSafe("Form fields initialized.");
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   void onClose() {
 | |
|     [
 | |
|       assignedDateController,
 | |
|       workAreaController,
 | |
|       activityController,
 | |
|       teamSizeController,
 | |
|       taskIdController,
 | |
|       assignedController,
 | |
|       completedWorkController,
 | |
|       commentController,
 | |
|       assignedByController,
 | |
|       teamMembersController,
 | |
|       plannedWorkController,
 | |
|     ].forEach((controller) => controller.dispose());
 | |
|     super.onClose();
 | |
|   }
 | |
| 
 | |
|   Future<bool> reportTask({
 | |
|     required String projectId,
 | |
|     required String comment,
 | |
|     required int completedTask,
 | |
|     required List<Map<String, dynamic>> checklist,
 | |
|     required DateTime reportedDate,
 | |
|     List<File>? images,
 | |
|   }) async {
 | |
|     logSafe("Reporting task for projectId",  );
 | |
|     final completedWork = completedWorkController.text.trim();
 | |
|     if (completedWork.isEmpty || int.tryParse(completedWork) == null || int.parse(completedWork) < 0) {
 | |
|       _showError("Completed work must be a positive number.");
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     final commentField = commentController.text.trim();
 | |
|     if (commentField.isEmpty) {
 | |
|       _showError("Comment is required.");
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     try {
 | |
|       reportStatus.value = ApiStatus.loading;
 | |
|       isLoading.value = true;
 | |
| 
 | |
|       final imageData = await _prepareImages(images, "task report");
 | |
| 
 | |
|       final success = await ApiService.reportTask(
 | |
|         id: projectId,
 | |
|         comment: commentField,
 | |
|         completedTask: int.parse(completedWork),
 | |
|         checkList: checklist,
 | |
|         images: imageData,
 | |
|       );
 | |
| 
 | |
|       if (success) {
 | |
|         reportStatus.value = ApiStatus.success;
 | |
|         _showSuccess("Task reported successfully!");
 | |
|         await taskController.fetchTaskData(projectId);
 | |
|         return true;
 | |
|       } else {
 | |
|         reportStatus.value = ApiStatus.failure;
 | |
|         _showError("Failed to report task.");
 | |
|         return false;
 | |
|       }
 | |
|     } catch (e, s) {
 | |
|       logSafe("Exception while reporting task", level: LogLevel.error, error: e, stackTrace: s);
 | |
|       reportStatus.value = ApiStatus.failure;
 | |
|       _showError("An error occurred while reporting the task.");
 | |
|       return false;
 | |
|     } finally {
 | |
|       isLoading.value = false;
 | |
|       Future.delayed(const Duration(milliseconds: 500), () {
 | |
|         reportStatus.value = ApiStatus.idle;
 | |
|       });
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   Future<void> commentTask({
 | |
|     required String projectId,
 | |
|     required String comment,
 | |
|     List<File>? images,
 | |
|   }) async {
 | |
|     logSafe("Submitting comment for project",  );
 | |
| 
 | |
|     final commentField = commentController.text.trim();
 | |
|     if (commentField.isEmpty) {
 | |
|       _showError("Comment is required.");
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     try {
 | |
|       isLoading.value = true;
 | |
|       final imageData = await _prepareImages(images, "task comment");
 | |
| 
 | |
|       final success = await ApiService.commentTask(
 | |
|         id: projectId,
 | |
|         comment: commentField,
 | |
|         images: imageData,
 | |
|       ).timeout(const Duration(seconds: 30), onTimeout: () {
 | |
|         logSafe("Task comment request timed out.", level: LogLevel.error);
 | |
|         throw Exception("Request timed out.");
 | |
|       });
 | |
| 
 | |
|       if (success) {
 | |
|         _showSuccess("Task commented successfully!");
 | |
|         await taskController.fetchTaskData(projectId);
 | |
|       } else {
 | |
|         _showError("Failed to comment task.");
 | |
|       }
 | |
|     } catch (e, s) {
 | |
|       logSafe("Exception while commenting task", level: LogLevel.error, error: e, stackTrace: s);
 | |
|       _showError("An error occurred while commenting the task.");
 | |
|     } finally {
 | |
|       isLoading.value = false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   Future<List<Map<String, dynamic>>?> _prepareImages(List<File>? images, String context) async {
 | |
|     if (images == null || images.isEmpty) return null;
 | |
| 
 | |
|     logSafe("Preparing images for $context upload...");
 | |
| 
 | |
|     final results = await Future.wait(images.map((file) async {
 | |
|       try {
 | |
|         final compressed = await compressImageToUnder100KB(file);
 | |
|         if (compressed == null) return null;
 | |
| 
 | |
|         return {
 | |
|           "fileName": file.path.split('/').last,
 | |
|           "base64Data": base64Encode(compressed),
 | |
|           "contentType": _getContentTypeFromFileName(file.path),
 | |
|           "fileSize": compressed.lengthInBytes,
 | |
|           "description": "Image uploaded for $context",
 | |
|         };
 | |
|       } catch (e) {
 | |
|         logSafe("Image processing failed: ${file.path}", level: LogLevel.warning, error: e);
 | |
|         return null;
 | |
|       }
 | |
|     }));
 | |
| 
 | |
|     return results.whereType<Map<String, dynamic>>().toList();
 | |
|   }
 | |
| 
 | |
|   String _getContentTypeFromFileName(String fileName) {
 | |
|     final ext = fileName.split('.').last.toLowerCase();
 | |
|     return switch (ext) {
 | |
|       'jpg' || 'jpeg' => 'image/jpeg',
 | |
|       'png' => 'image/png',
 | |
|       'webp' => 'image/webp',
 | |
|       'gif' => 'image/gif',
 | |
|       _ => 'application/octet-stream',
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   Future<void> pickImages({required bool fromCamera}) async {
 | |
|     try {
 | |
|       if (fromCamera) {
 | |
|         final pickedFile = await _picker.pickImage(source: ImageSource.camera, imageQuality: 75);
 | |
|         if (pickedFile != null) {
 | |
|           selectedImages.add(File(pickedFile.path));
 | |
|         }
 | |
|       } else {
 | |
|         final pickedFiles = await _picker.pickMultiImage(imageQuality: 75);
 | |
|         selectedImages.addAll(pickedFiles.map((xfile) => File(xfile.path)));
 | |
|       }
 | |
|       logSafe("Images picked: ${selectedImages.length}",  );
 | |
|     } catch (e) {
 | |
|       logSafe("Error picking images", level: LogLevel.warning, error: e);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   void removeImageAt(int index) {
 | |
|     if (index >= 0 && index < selectedImages.length) {
 | |
|       selectedImages.removeAt(index);
 | |
|       logSafe("Removed image at index $index");
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   void _showError(String message) => showAppSnackbar(
 | |
|         title: "Error",
 | |
|         message: message,
 | |
|         type: SnackbarType.error,
 | |
|       );
 | |
| 
 | |
|   void _showSuccess(String message) => showAppSnackbar(
 | |
|         title: "Success",
 | |
|         message: message,
 | |
|         type: SnackbarType.success,
 | |
|       );
 | |
| }
 |