315 lines
9.8 KiB
Dart
315 lines
9.8 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:logger/logger.dart';
|
|
import 'package:marco/helpers/widgets/my_snackbar.dart';
|
|
import 'package:marco/controller/task_planing/daily_task_planing_controller.dart';
|
|
import 'package:image_picker/image_picker.dart';
|
|
import 'dart:io';
|
|
import 'dart:convert';
|
|
import 'package:marco/helpers/widgets/my_image_compressor.dart';
|
|
|
|
final Logger logger = Logger();
|
|
|
|
enum ApiStatus { idle, loading, success, failure }
|
|
|
|
final DailyTaskPlaningController taskController =
|
|
Get.put(DailyTaskPlaningController());
|
|
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;
|
|
|
|
// Controllers for each form field
|
|
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();
|
|
logger.i("Initializing ReportTaskController...");
|
|
|
|
basicValidator.addField('assigned_date',
|
|
label: "Assigned Date", controller: assignedDateController);
|
|
basicValidator.addField('work_area',
|
|
label: "Work Area", controller: workAreaController);
|
|
basicValidator.addField('activity',
|
|
label: "Activity", controller: activityController);
|
|
basicValidator.addField('team_size',
|
|
label: "Team Size", controller: teamSizeController);
|
|
basicValidator.addField('task_id',
|
|
label: "Task Id", controller: taskIdController);
|
|
basicValidator.addField('assigned',
|
|
label: "Assigned", controller: assignedController);
|
|
basicValidator.addField('completed_work',
|
|
label: "Completed Work",
|
|
required: true,
|
|
controller: completedWorkController);
|
|
basicValidator.addField('comment',
|
|
label: "Comment", required: true, controller: commentController);
|
|
basicValidator.addField('assigned_by',
|
|
label: "Assigned By", controller: assignedByController);
|
|
basicValidator.addField('team_members',
|
|
label: "Team Members", controller: teamMembersController);
|
|
basicValidator.addField('planned_work',
|
|
label: "Planned Work", controller: plannedWorkController);
|
|
|
|
logger.i(
|
|
"Fields initialized for assigned_date, work_area, activity, team_size, assigned, completed_work, and comment.");
|
|
}
|
|
|
|
@override
|
|
void onClose() {
|
|
assignedDateController.dispose();
|
|
workAreaController.dispose();
|
|
activityController.dispose();
|
|
teamSizeController.dispose();
|
|
taskIdController.dispose();
|
|
assignedController.dispose();
|
|
completedWorkController.dispose();
|
|
commentController.dispose();
|
|
assignedByController.dispose();
|
|
teamMembersController.dispose();
|
|
plannedWorkController.dispose();
|
|
super.onClose();
|
|
}
|
|
|
|
Future<void> reportTask({
|
|
required String projectId,
|
|
required String comment,
|
|
required int completedTask,
|
|
required List<Map<String, dynamic>> checklist,
|
|
required DateTime reportedDate,
|
|
List<File>? images,
|
|
}) async {
|
|
logger.i("Starting task report...");
|
|
|
|
final completedWork = completedWorkController.text.trim();
|
|
|
|
if (completedWork.isEmpty) {
|
|
showAppSnackbar(
|
|
title: "Error",
|
|
message: "Completed work is required.",
|
|
type: SnackbarType.error,
|
|
);
|
|
return;
|
|
}
|
|
|
|
final completedWorkInt = int.tryParse(completedWork);
|
|
if (completedWorkInt == null || completedWorkInt < 0) {
|
|
showAppSnackbar(
|
|
title: "Error",
|
|
message: "Completed work must be a positive integer.",
|
|
type: SnackbarType.error,
|
|
);
|
|
return;
|
|
}
|
|
|
|
final commentField = commentController.text.trim();
|
|
if (commentField.isEmpty) {
|
|
showAppSnackbar(
|
|
title: "Error",
|
|
message: "Comment is required.",
|
|
type: SnackbarType.error,
|
|
);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
isLoading.value = true;
|
|
List<Map<String, dynamic>>? imageData;
|
|
if (images != null && images.isNotEmpty) {
|
|
if (images != null && images.isNotEmpty) {
|
|
final imageFutures = images.map((file) async {
|
|
final compressedBytes = await compressImageToUnder100KB(file);
|
|
if (compressedBytes == null) return null;
|
|
|
|
final base64Image = base64Encode(compressedBytes);
|
|
final fileName = file.path.split('/').last;
|
|
final contentType = _getContentTypeFromFileName(fileName);
|
|
|
|
return {
|
|
"fileName": fileName,
|
|
"base64Data": base64Image,
|
|
"contentType": contentType,
|
|
"fileSize": compressedBytes.lengthInBytes,
|
|
"description": "Image uploaded for task report",
|
|
};
|
|
}).toList();
|
|
|
|
final results = await Future.wait(imageFutures);
|
|
imageData = results.whereType<Map<String, dynamic>>().toList();
|
|
}
|
|
}
|
|
|
|
final success = await ApiService.reportTask(
|
|
id: projectId,
|
|
comment: commentField,
|
|
completedTask: completedWorkInt,
|
|
checkList: checklist,
|
|
images: imageData,
|
|
);
|
|
|
|
if (success) {
|
|
showAppSnackbar(
|
|
title: "Success",
|
|
message: "Task reported successfully!",
|
|
type: SnackbarType.success,
|
|
);
|
|
await taskController.fetchTaskData(projectId);
|
|
} else {
|
|
showAppSnackbar(
|
|
title: "Error",
|
|
message: "Failed to report task.",
|
|
type: SnackbarType.error,
|
|
);
|
|
}
|
|
} catch (e) {
|
|
logger.e("Error reporting task: $e");
|
|
showAppSnackbar(
|
|
title: "Error",
|
|
message: "An error occurred while reporting the task.",
|
|
type: SnackbarType.error,
|
|
);
|
|
} finally {
|
|
isLoading.value = false;
|
|
}
|
|
}
|
|
|
|
String _getContentTypeFromFileName(String fileName) {
|
|
final ext = fileName.split('.').last.toLowerCase();
|
|
switch (ext) {
|
|
case 'jpg':
|
|
case 'jpeg':
|
|
return 'image/jpeg';
|
|
case 'png':
|
|
return 'image/png';
|
|
case 'webp':
|
|
return 'image/webp';
|
|
case 'gif':
|
|
return 'image/gif';
|
|
default:
|
|
return 'application/octet-stream';
|
|
}
|
|
}
|
|
|
|
Future<void> commentTask({
|
|
required String projectId,
|
|
required String comment,
|
|
List<File>? images,
|
|
}) async {
|
|
logger.i("Starting task comment...");
|
|
|
|
final commentField = commentController.text.trim();
|
|
if (commentField.isEmpty) {
|
|
showAppSnackbar(
|
|
title: "Error",
|
|
message: "Comment is required.",
|
|
type: SnackbarType.error,
|
|
);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
isLoading.value = true;
|
|
List<Map<String, dynamic>>? imageData;
|
|
|
|
if (images != null && images.isNotEmpty) {
|
|
final imageFutures = images.map((file) async {
|
|
final compressedBytes = await compressImageToUnder100KB(file);
|
|
if (compressedBytes == null) return null;
|
|
|
|
final base64Image = base64Encode(compressedBytes);
|
|
final fileName = file.path.split('/').last;
|
|
final contentType = _getContentTypeFromFileName(fileName);
|
|
|
|
return {
|
|
"fileName": fileName,
|
|
"base64Data": base64Image,
|
|
"contentType": contentType,
|
|
"fileSize": compressedBytes.lengthInBytes,
|
|
"description": "Image uploaded for task comment",
|
|
};
|
|
}).toList();
|
|
|
|
final results = await Future.wait(imageFutures);
|
|
imageData = results.whereType<Map<String, dynamic>>().toList();
|
|
}
|
|
|
|
final success = await ApiService.commentTask(
|
|
id: projectId,
|
|
comment: commentField,
|
|
images: imageData,
|
|
).timeout(const Duration(seconds: 30), onTimeout: () {
|
|
logger.e("Request timed out.");
|
|
throw Exception("Request timed out.");
|
|
});
|
|
|
|
if (success) {
|
|
showAppSnackbar(
|
|
title: "Success",
|
|
message: "Task commented successfully!",
|
|
type: SnackbarType.success,
|
|
);
|
|
await taskController.fetchTaskData(projectId);
|
|
} else {
|
|
showAppSnackbar(
|
|
title: "Error",
|
|
message: "Failed to comment task.",
|
|
type: SnackbarType.error,
|
|
);
|
|
}
|
|
} catch (e) {
|
|
logger.e("Error commenting task: $e");
|
|
showAppSnackbar(
|
|
title: "Error",
|
|
message: "An error occurred while commenting the task.",
|
|
type: SnackbarType.error,
|
|
);
|
|
} finally {
|
|
isLoading.value = false;
|
|
}
|
|
}
|
|
|
|
Future<void> pickImages({required bool fromCamera}) async {
|
|
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);
|
|
if (pickedFiles != null && pickedFiles.isNotEmpty) {
|
|
selectedImages.addAll(pickedFiles.map((xfile) => File(xfile.path)));
|
|
}
|
|
}
|
|
}
|
|
|
|
void removeImageAt(int index) {
|
|
if (index >= 0 && index < selectedImages.length) {
|
|
selectedImages.removeAt(index);
|
|
}
|
|
}
|
|
}
|