From 206c84b3a11e4df2c0d0e3fbba636c89a801e550 Mon Sep 17 00:00:00 2001 From: Vaibhav Surve Date: Wed, 18 Jun 2025 11:40:08 +0530 Subject: [PATCH] feat: Implement task management features including task creation, assignment, and employee selection --- .../task_planing/add_task_controller.dart | 202 +++++++++++++ lib/helpers/services/api_endpoints.dart | 2 + lib/helpers/services/api_service.dart | 36 +++ .../create_task_botom_sheet.dart | 284 ++++++++++++++---- .../master_work_category_model.dart | 31 ++ .../report_action_bottom_sheet.dart | 34 ++- .../dailyTaskPlaning/task_action_buttons.dart | 110 +++---- lib/view/taskPlaning/daily_progress.dart | 5 +- pubspec.yaml | 2 - 9 files changed, 572 insertions(+), 134 deletions(-) create mode 100644 lib/controller/task_planing/add_task_controller.dart create mode 100644 lib/model/dailyTaskPlaning/master_work_category_model.dart diff --git a/lib/controller/task_planing/add_task_controller.dart b/lib/controller/task_planing/add_task_controller.dart new file mode 100644 index 0000000..9a8d1d2 --- /dev/null +++ b/lib/controller/task_planing/add_task_controller.dart @@ -0,0 +1,202 @@ +import 'package:get/get.dart'; +import 'package:logger/logger.dart'; +import 'package:marco/helpers/services/api_service.dart'; +import 'package:marco/model/project_model.dart'; +import 'package:marco/model/employee_model.dart'; +import 'package:marco/helpers/widgets/my_form_validator.dart'; +import 'package:marco/helpers/widgets/my_snackbar.dart'; +import 'package:marco/controller/project_controller.dart'; +import 'package:marco/model/dailyTaskPlaning/master_work_category_model.dart'; + +final Logger log = Logger(); + +class AddTaskController extends GetxController { + List projects = []; + List employees = []; + RxMap uploadingStates = {}.obs; + MyFormValidator basicValidator = MyFormValidator(); + RxnInt selectedCategoryId = RxnInt(); + + List> roles = []; + RxnString selectedRoleId = RxnString(); + RxList selectedEmployees = [].obs; + RxBool isLoadingWorkMasterCategories = false.obs; + RxList workMasterCategories = [].obs; + + void updateSelectedEmployees() { + final selected = + employees.where((e) => uploadingStates[e.id]?.value == true).toList(); + selectedEmployees.value = selected; + } + + RxBool isLoading = false.obs; + @override + void onInit() { + super.onInit(); + fetchRoles(); + fetchWorkMasterCategories(); + final projectId = Get.find().selectedProject?.id; + fetchEmployeesByProject(projectId); + } + + String? formFieldValidator(String? value, {required String fieldType}) { + if (value == null || value.trim().isEmpty) { + return 'This field is required'; + } + if (fieldType == "target") { + if (int.tryParse(value.trim()) == null) { + return 'Please enter a valid number'; + } + } + if (fieldType == "description") { + if (value.trim().length < 5) { + return 'Description must be at least 5 characters'; + } + } + return null; + } + + Future fetchRoles() async { + logger.i("Fetching roles..."); + final result = await ApiService.getRoles(); + if (result != null) { + roles = List>.from(result); + logger.i("Roles fetched successfully."); + update(); + } else { + logger.e("Failed to fetch roles."); + } + } + + void onRoleSelected(String? roleId) { + selectedRoleId.value = roleId; + logger.i("Role selected: $roleId"); + } + + Future assignDailyTask({ + required String workItemId, + required int plannedTask, + required String description, + required List taskTeam, + DateTime? assignmentDate, + }) async { + logger.i("Starting assign task..."); + + final response = await ApiService.assignDailyTask( + workItemId: workItemId, + plannedTask: plannedTask, + description: description, + taskTeam: taskTeam, + assignmentDate: assignmentDate, + ); + + if (response == true) { + logger.i("Task assigned successfully."); + showAppSnackbar( + title: "Success", + message: "Task assigned successfully!", + type: SnackbarType.success, + ); + return true; + } else { + logger.e("Failed to assign task."); + showAppSnackbar( + title: "Error", + message: "Failed to assign task.", + type: SnackbarType.error, + ); + return false; + } + } + + Future createTask({ + required String parentTaskId, + required int plannedTask, + required String description, + required List taskTeam, + required String workItemId, + DateTime? assignmentDate, + }) async { + logger.i("Creating new task..."); + + final response = await ApiService.createTask( + parentTaskId: parentTaskId, + plannedTask: plannedTask, + description: description, + taskTeam: taskTeam, + workItemId: workItemId, + assignmentDate: assignmentDate, + ); + + if (response == true) { + logger.i("Task created successfully."); + showAppSnackbar( + title: "Success", + message: "Task created successfully!", + type: SnackbarType.success, + ); + return true; + } else { + logger.e("Failed to create task."); + showAppSnackbar( + title: "Error", + message: "Failed to create task.", + type: SnackbarType.error, + ); + return false; + } + } + + Future fetchEmployeesByProject(String? projectId) async { + if (projectId == null || projectId.isEmpty) { + log.e("Project ID is required but was null or empty."); + return; + } + + isLoading.value = true; + try { + final response = await ApiService.getAllEmployeesByProject(projectId); + if (response != null && response.isNotEmpty) { + employees = + response.map((json) => EmployeeModel.fromJson(json)).toList(); + for (var emp in employees) { + uploadingStates[emp.id] = false.obs; + } + log.i("Employees fetched: ${employees.length} for project $projectId"); + } else { + log.w("No employees found for project $projectId."); + employees = []; + } + } catch (e) { + log.e("Error fetching employees for project $projectId: $e"); + } + + update(); + isLoading.value = false; + } + + Future fetchWorkMasterCategories() async { + isLoadingWorkMasterCategories.value = true; + + final response = await ApiService.getMasterWorkCategories(); + if (response != null) { + try { + final dataList = response['data'] ?? []; + workMasterCategories.assignAll( + List.from( + dataList.map((e) => WorkCategoryModel.fromJson(e)), + ), + ); + logger.i("Work categories fetched: ${dataList.length}"); + } catch (e) { + logger.e("Error parsing work categories: $e"); + workMasterCategories.clear(); + } + } else { + logger.w("No work categories found or API call failed."); + } + + isLoadingWorkMasterCategories.value = false; + update(); + } +} diff --git a/lib/helpers/services/api_endpoints.dart b/lib/helpers/services/api_endpoints.dart index 44b5310..851f264 100644 --- a/lib/helpers/services/api_endpoints.dart +++ b/lib/helpers/services/api_endpoints.dart @@ -26,4 +26,6 @@ class ApiEndpoints { static const String assignDailyTask = "/task/assign"; static const String getWorkStatus = "/master/work-status"; static const String approveReportAction = "/task/approve"; + static const String assignTask = "/task/assign"; + static const String getmasterWorkCategories = "/Master/work-categories"; } diff --git a/lib/helpers/services/api_service.dart b/lib/helpers/services/api_service.dart index b48f255..9c960a1 100644 --- a/lib/helpers/services/api_service.dart +++ b/lib/helpers/services/api_service.dart @@ -378,6 +378,11 @@ class ApiService { _getRequest(ApiEndpoints.getWorkStatus).then((res) => res != null ? _parseResponseForAllData(res, label: 'Work Status') : null); + static Future?> getMasterWorkCategories() async => + _getRequest(ApiEndpoints.getmasterWorkCategories).then((res) => + res != null + ? _parseResponseForAllData(res, label: 'Master Work Categories') + : null); static Future approveTask({ required String id, required String comment, @@ -399,4 +404,35 @@ class ApiService { final json = jsonDecode(response.body); return response.statusCode == 200 && json['success'] == true; } + + static Future createTask({ + required String parentTaskId, + required int plannedTask, + required String description, + required List taskTeam, + required String workItemId, + DateTime? assignmentDate, + }) async { + final body = { + "assignmentDate": + (assignmentDate ?? DateTime.now()).toUtc().toIso8601String(), + "parentTaskId": parentTaskId, + "plannedTask": plannedTask, + "description": description, + "taskTeam": taskTeam, + "workItemId": workItemId, + }; + + final response = await _postRequest(ApiEndpoints.assignTask, body); + if (response == null) return false; + + final json = jsonDecode(response.body); + if (response.statusCode == 200 && json['success'] == true) { + Get.back(); + return true; + } + + _log("Failed to create task: ${json['message'] ?? 'Unknown error'}"); + return false; + } } diff --git a/lib/model/dailyTaskPlaning/create_task_botom_sheet.dart b/lib/model/dailyTaskPlaning/create_task_botom_sheet.dart index 196434c..fe751c1 100644 --- a/lib/model/dailyTaskPlaning/create_task_botom_sheet.dart +++ b/lib/model/dailyTaskPlaning/create_task_botom_sheet.dart @@ -1,20 +1,92 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:marco/controller/task_planing/add_task_controller.dart'; import 'package:marco/helpers/widgets/my_text.dart'; +import 'package:marco/helpers/widgets/my_snackbar.dart'; void showManageTaskBottomSheet({ - required String building, - required String floor, required String workArea, required String activity, - required String plannedWork, required String completedWork, required String unit, required Function(String) onCategoryChanged, + required String parentTaskId, + required int plannedTask, + required String workItemId, required VoidCallback onSubmit, }) { - final List categories = ["Fresh Work", "Repair", "Demo"]; - String selectedCategory = 'Fresh Work'; + final controller = Get.put(AddTaskController()); + final ScrollController employeeListScrollController = ScrollController(); + final TextEditingController plannedTaskController = + TextEditingController(text: plannedTask.toString()); + final TextEditingController descriptionController = TextEditingController(); + + Widget buildEmployeeList() { + final selectedRoleId = controller.selectedRoleId.value; + final filteredEmployees = selectedRoleId == null + ? controller.employees + : controller.employees + .where((e) => e.jobRoleID.toString() == selectedRoleId) + .toList(); + + if (filteredEmployees.isEmpty) { + return MyText.bodySmall("No employees found for selected role."); + } + + return Scrollbar( + controller: employeeListScrollController, + thumbVisibility: true, + interactive: true, + child: ListView.builder( + controller: employeeListScrollController, + shrinkWrap: true, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: filteredEmployees.length, + itemBuilder: (context, index) { + final employee = filteredEmployees[index]; + final rxBool = controller.uploadingStates[employee.id]; + + return Obx(() => Padding( + padding: const EdgeInsets.symmetric(vertical: 0), + child: Row( + children: [ + Theme( + data: Theme.of(context) + .copyWith(unselectedWidgetColor: Colors.black), + child: Checkbox( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4), + side: const BorderSide(color: Colors.black), + ), + value: rxBool?.value ?? false, + onChanged: (bool? selected) { + if (rxBool != null) { + rxBool.value = selected ?? false; + controller.updateSelectedEmployees(); + } + }, + fillColor: + WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return const Color.fromARGB(255, 95, 132, 255); + } + return Colors.transparent; + }), + checkColor: Colors.white, + side: const BorderSide(color: Colors.black), + ), + ), + const SizedBox(width: 8), + Expanded( + child: MyText.bodySmall(employee.name), + ), + ], + ), + )); + }, + ), + ); + } Get.bottomSheet( StatefulBuilder( @@ -22,12 +94,14 @@ void showManageTaskBottomSheet({ return LayoutBuilder( builder: (context, constraints) { final isLarge = constraints.maxWidth > 600; - final horizontalPadding = isLarge ? constraints.maxWidth * 0.2 : 16.0; + final horizontalPadding = + isLarge ? constraints.maxWidth * 0.2 : 16.0; return SafeArea( child: Container( - constraints: const BoxConstraints(maxHeight: 0.95 * 800), - padding: EdgeInsets.fromLTRB(horizontalPadding, 12, horizontalPadding, 24), + constraints: const BoxConstraints(maxHeight: 760), + padding: EdgeInsets.fromLTRB( + horizontalPadding, 12, horizontalPadding, 24), decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(20)), @@ -36,7 +110,6 @@ void showManageTaskBottomSheet({ child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Draggable handle Center( child: Container( width: 40, @@ -48,74 +121,155 @@ void showManageTaskBottomSheet({ ), ), ), - - _infoRowWithIcon(Icons.location_city, "Selected Building", building), - _infoRowWithIcon(Icons.apartment, "Selected Floor", floor), - _infoRowWithIcon(Icons.workspaces, "Selected Work Area", workArea), - _infoRowWithIcon(Icons.list_alt, "Selected Activity", activity), - + _infoRowWithIcon( + Icons.workspaces, "Selected Work Area", workArea), + _infoRowWithIcon( + Icons.list_alt, "Selected Activity", activity), const SizedBox(height: 12), Row( children: [ - Icon(Icons.category_outlined, color: Colors.grey[700], size: 18), + Icon(Icons.edit_calendar, + color: Colors.grey[700], size: 18), const SizedBox(width: 8), - MyText.bodyMedium("Selected Work Category", fontWeight: 600), + MyText.bodyMedium("Planned Work", fontWeight: 600), ], ), const SizedBox(height: 6), - - Container( - width: double.infinity, - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14), - decoration: BoxDecoration( - border: Border.all(color: Colors.grey.shade300), - borderRadius: BorderRadius.circular(8), - ), - child: PopupMenuButton( - padding: EdgeInsets.zero, - onSelected: (val) { - setState(() { - selectedCategory = val; - }); - onCategoryChanged(val); - }, - itemBuilder: (context) => categories - .map((e) => PopupMenuItem(value: e, child: Text(e))) - .toList(), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - selectedCategory, - style: TextStyle(fontSize: 14, color: Colors.black87), - ), - const Icon(Icons.arrow_drop_down), - ], - ), + TextField( + controller: plannedTaskController, + keyboardType: TextInputType.number, + decoration: InputDecoration( + hintText: "Enter planned work", + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8)), + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 10), ), ), - const SizedBox(height: 12), - _infoRowWithIcon(Icons.edit_calendar, "Planned Work", plannedWork), - _infoRowWithIcon(Icons.check_circle_outline, "Completed Work", completedWork), - _infoRowWithIcon(Icons.straighten, "Unit", unit), - - const SizedBox(height: 24), - SizedBox( - width: double.infinity, - height: 48, - child: ElevatedButton( - onPressed: onSubmit, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.blueAccent, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), - ), - child: const Text( - "Submit", - style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600), - ), + _infoRowWithIcon(Icons.check_circle_outline, + "Completed Work", completedWork), + const SizedBox(height: 16), + MyText.bodyMedium("Description", fontWeight: 700), + const SizedBox(height: 6), + TextField( + controller: descriptionController, + maxLines: 3, + decoration: InputDecoration( + hintText: "Enter task description", + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8)), + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 10), ), ), + const SizedBox(height: 24), + MyText.bodyMedium("Select Team Members", fontWeight: 700), + const SizedBox(height: 8), + Obx(() { + if (controller.isLoading.value) { + return const Center( + child: CircularProgressIndicator()); + } + + return Container( + constraints: const BoxConstraints(maxHeight: 150), + child: buildEmployeeList(), + ); + }), + const SizedBox(height: 12), + Obx(() { + if (controller.selectedEmployees.isEmpty) { + return const SizedBox.shrink(); + } + return Wrap( + spacing: 8, + runSpacing: 8, + children: + controller.selectedEmployees.map((employee) { + return Chip( + label: Text(employee.name), + deleteIcon: const Icon(Icons.close), + onDeleted: () { + controller.uploadingStates[employee.id]?.value = + false; + controller.updateSelectedEmployees(); + }, + backgroundColor: Colors.blue.shade100, + labelStyle: const TextStyle(color: Colors.black), + ); + }).toList(), + ); + }), + const SizedBox(height: 24), + Row( + children: [ + Expanded( + child: OutlinedButton( + onPressed: () { + Get.back(); // Close bottom sheet + }, + style: OutlinedButton.styleFrom( + side: const BorderSide(color: Colors.grey), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10)), + ), + child: MyText.bodyMedium( + "Cancel", + fontWeight: 600, + color: Colors.black, + ), + ), + ), + const SizedBox(width: 12), + Expanded( + child: ElevatedButton( + onPressed: () async { + final taskTeam = controller.selectedEmployees + .map((e) => e.id) + .toList(); + + if (taskTeam.isEmpty) { + showAppSnackbar( + title: "Team Required", + message: + "Please select at least one team member.", + type: SnackbarType.warning, + ); + return; + } + + final success = await controller.createTask( + parentTaskId: parentTaskId, + plannedTask: int.tryParse( + plannedTaskController.text.trim()) ?? + 0, + description: + descriptionController.text.trim(), + taskTeam: taskTeam, + workItemId: workItemId, + assignmentDate: DateTime.now(), + ); + + if (success) { + Get.back(); + onSubmit(); + } + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blueAccent, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10)), + ), + child: MyText.bodyMedium( + "Submit", + fontWeight: 600, + color: Colors.white, + ), + ), + ), + ], + ), ], ), ), diff --git a/lib/model/dailyTaskPlaning/master_work_category_model.dart b/lib/model/dailyTaskPlaning/master_work_category_model.dart new file mode 100644 index 0000000..bf87dd0 --- /dev/null +++ b/lib/model/dailyTaskPlaning/master_work_category_model.dart @@ -0,0 +1,31 @@ +class WorkCategoryModel { + final String id; + final String name; + final String description; + final bool isSystem; + + WorkCategoryModel({ + required this.id, + required this.name, + required this.description, + required this.isSystem, + }); + + factory WorkCategoryModel.fromJson(Map json) { + return WorkCategoryModel( + id: json['id'] ?? '', + name: json['name'] ?? '', + description: json['description'] ?? '', + isSystem: json['isSystem'] ?? false, + ); + } + + Map toJson() { + return { + 'id': id, + 'name': name, + 'description': description, + 'isSystem': isSystem, + }; + } +} diff --git a/lib/model/dailyTaskPlaning/report_action_bottom_sheet.dart b/lib/model/dailyTaskPlaning/report_action_bottom_sheet.dart index f4669de..b7a40a8 100644 --- a/lib/model/dailyTaskPlaning/report_action_bottom_sheet.dart +++ b/lib/model/dailyTaskPlaning/report_action_bottom_sheet.dart @@ -17,11 +17,16 @@ import 'dart:io'; class ReportActionBottomSheet extends StatefulWidget { final Map taskData; final VoidCallback? onCommentSuccess; - + final String taskDataId; + final String workItemId; + final VoidCallback onReportSuccess; const ReportActionBottomSheet({ super.key, required this.taskData, this.onCommentSuccess, + required this.taskDataId, + required this.workItemId, + required this.onReportSuccess, }); @override @@ -70,7 +75,10 @@ class _ReportActionBottomSheetState extends State controller.basicValidator.getController('task_id')?.text = data['taskId'] ?? ''; controller.basicValidator.getController('comment')?.clear(); - + controller.basicValidator.getController('task_id')?.text = + widget.taskDataId; + controller.basicValidator.getController('work_item_id')?.text = + widget.workItemId; controller.selectedImages.clear(); WidgetsBinding.instance.addPostFrameCallback((_) { @@ -461,16 +469,11 @@ class _ReportActionBottomSheetState extends State if (success) { Navigator.of(context).pop(); if (shouldShowAddTaskSheet) { - await Future.delayed(Duration( - milliseconds: - 100)); // Ensure animation completes + await Future.delayed( + Duration(milliseconds: 100)); showManageTaskBottomSheet( - building: widget.taskData['building'] ?? '', - floor: widget.taskData['floor'] ?? '', workArea: widget.taskData['location'] ?? '', activity: widget.taskData['activity'] ?? '', - plannedWork: - widget.taskData['plannedWork'] ?? '', completedWork: widget.taskData['completedWork'] ?? '', unit: widget.taskData['unit'] ?? '', @@ -478,15 +481,18 @@ class _ReportActionBottomSheetState extends State debugPrint( "Category changed to: $category"); }, + parentTaskId: widget.taskDataId, + plannedTask: int.tryParse( + widget.taskData['plannedWork'] ?? + '0') ?? + 0, + workItemId: widget.workItemId, onSubmit: () { - Navigator.of(context) - .pop(); // Close second sheet + Navigator.of(context).pop(); }, ); } - - // Optional callback after comment success - widget.onCommentSuccess?.call(); + widget.onReportSuccess.call(); } } }, diff --git a/lib/model/dailyTaskPlaning/task_action_buttons.dart b/lib/model/dailyTaskPlaning/task_action_buttons.dart index 5c188bd..933fc3a 100644 --- a/lib/model/dailyTaskPlaning/task_action_buttons.dart +++ b/lib/model/dailyTaskPlaning/task_action_buttons.dart @@ -82,7 +82,8 @@ class TaskActionButtons { textStyle: const TextStyle(fontSize: 14), ), onPressed: () { - final taskData = _prepareTaskData(task: task, completed: task.completedTask); + final taskData = + _prepareTaskData(task: task, completed: task.completedTask); showModalBottomSheet( context: context, isScrollControlled: true, @@ -104,6 +105,8 @@ class TaskActionButtons { required dynamic task, required int completed, required VoidCallback refreshCallback, + required String parentTaskID, + required String workItemId, }) { return OutlinedButton.icon( icon: const Icon(Icons.report, size: 18, color: Colors.amber), @@ -115,7 +118,7 @@ class TaskActionButtons { textStyle: const TextStyle(fontSize: 14), ), onPressed: () { - final taskData = _prepareTaskData(task: task, completed: completed); + final taskData = _prepareTaskData(task: task, completed: completed); showModalBottomSheet( context: context, @@ -127,6 +130,9 @@ class TaskActionButtons { padding: MediaQuery.of(ctx).viewInsets, child: ReportActionBottomSheet( taskData: taskData, + taskDataId: parentTaskID, + workItemId: workItemId, + onReportSuccess: refreshCallback, ), ), ); @@ -135,61 +141,61 @@ class TaskActionButtons { } static Map _prepareTaskData({ - required dynamic task, - required int completed, -}) { - final activityName = task.workItem?.activityMaster?.activityName ?? 'N/A'; - final assigned = '${(task.plannedTask - completed)}'; - final assignedBy = - "${task.assignedBy.firstName} ${task.assignedBy.lastName ?? ''}"; - final assignedOn = DateFormat('yyyy-MM-dd').format(task.assignmentDate); - final taskId = task.id; + required dynamic task, + required int completed, + }) { + final activityName = task.workItem?.activityMaster?.activityName ?? 'N/A'; + final assigned = '${(task.plannedTask - completed)}'; + final assignedBy = + "${task.assignedBy.firstName} ${task.assignedBy.lastName ?? ''}"; + final assignedOn = DateFormat('yyyy-MM-dd').format(task.assignmentDate); + final taskId = task.id; - final location = [ - task.workItem?.workArea?.floor?.building?.name, - task.workItem?.workArea?.floor?.floorName, - task.workItem?.workArea?.areaName, - ].where((e) => e != null && e.isNotEmpty).join(' > '); + final location = [ + task.workItem?.workArea?.floor?.building?.name, + task.workItem?.workArea?.floor?.floorName, + task.workItem?.workArea?.areaName, + ].where((e) => e != null && e.isNotEmpty).join(' > '); - final teamMembers = task.teamMembers - .map((e) => '${e.firstName} ${e.lastName ?? ''}') - .toList(); + final teamMembers = task.teamMembers + .map((e) => '${e.firstName} ${e.lastName ?? ''}') + .toList(); - final pendingWork = - (task.workItem?.plannedWork ?? 0) - (task.workItem?.completedWork ?? 0); + final pendingWork = + (task.workItem?.plannedWork ?? 0) - (task.workItem?.completedWork ?? 0); - final taskComments = task.comments.map((comment) { - final isoDate = comment.timestamp.toIso8601String(); - final commenterName = comment.commentedBy.firstName.isNotEmpty - ? "${comment.commentedBy.firstName} ${comment.commentedBy.lastName ?? ''}".trim() - : "Unknown"; + final taskComments = task.comments.map((comment) { + final isoDate = comment.timestamp.toIso8601String(); + final commenterName = comment.commentedBy.firstName.isNotEmpty + ? "${comment.commentedBy.firstName} ${comment.commentedBy.lastName ?? ''}" + .trim() + : "Unknown"; + + return { + 'text': comment.comment, + 'date': isoDate, + 'commentedBy': commenterName, + 'preSignedUrls': comment.preSignedUrls, + }; + }).toList(); + + final taskLevelPreSignedUrls = task.reportedPreSignedUrls; return { - 'text': comment.comment, - 'date': isoDate, - 'commentedBy': commenterName, - 'preSignedUrls': comment.preSignedUrls, + 'activity': activityName, + 'assigned': assigned, + 'taskId': taskId, + 'assignedBy': assignedBy, + 'completed': completed, + 'plannedWork': task.plannedTask.toString(), + 'completedWork': completed.toString(), + 'assignedOn': assignedOn, + 'location': location, + 'teamSize': task.teamMembers.length, + 'teamMembers': teamMembers, + 'pendingWork': pendingWork, + 'taskComments': taskComments, + 'reportedPreSignedUrls': taskLevelPreSignedUrls, }; - }).toList(); - - final taskLevelPreSignedUrls = task.reportedPreSignedUrls; - - return { - 'activity': activityName, - 'assigned': assigned, - 'taskId': taskId, - 'assignedBy': assignedBy, - 'completed': completed, - 'plannedWork': task.plannedTask.toString(), - 'completedWork': completed.toString(), - 'assignedOn': assignedOn, - 'location': location, - 'teamSize': task.teamMembers.length, - 'teamMembers': teamMembers, - 'pendingWork': pendingWork, - 'taskComments': taskComments, - 'reportedPreSignedUrls': taskLevelPreSignedUrls, - }; -} - + } } diff --git a/lib/view/taskPlaning/daily_progress.dart b/lib/view/taskPlaning/daily_progress.dart index d60daf2..2ff80aa 100644 --- a/lib/view/taskPlaning/daily_progress.dart +++ b/lib/view/taskPlaning/daily_progress.dart @@ -379,7 +379,8 @@ class _DailyProgressReportScreenState extends State final progress = (planned != 0) ? (completed / planned).clamp(0.0, 1.0) : 0.0; - + final parentTaskID = task.id; + final workItemId = task.workItem?.id; return Column( children: [ Padding( @@ -472,6 +473,8 @@ class _DailyProgressReportScreenState extends State TaskActionButtons.reportActionButton( context: context, task: task, + parentTaskID: parentTaskID, + workItemId: workItemId.toString(), completed: completed, refreshCallback: _refreshData, ), diff --git a/pubspec.yaml b/pubspec.yaml index 004626a..808debf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -98,8 +98,6 @@ flutter: - assets/avatar/ - assets/coin/ - assets/country/ - - assets/data/ - - assets/dummy/ - assets/lang/ - assets/logo/ - assets/logo/loading_logo.png