feat: Implement task management features including task creation, assignment, and employee selection

This commit is contained in:
Vaibhav Surve 2025-06-18 11:40:08 +05:30
parent 93a2350858
commit 206c84b3a1
9 changed files with 572 additions and 134 deletions

View File

@ -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<ProjectModel> projects = [];
List<EmployeeModel> employees = [];
RxMap<String, RxBool> uploadingStates = <String, RxBool>{}.obs;
MyFormValidator basicValidator = MyFormValidator();
RxnInt selectedCategoryId = RxnInt();
List<Map<String, dynamic>> roles = [];
RxnString selectedRoleId = RxnString();
RxList<EmployeeModel> selectedEmployees = <EmployeeModel>[].obs;
RxBool isLoadingWorkMasterCategories = false.obs;
RxList<WorkCategoryModel> workMasterCategories = <WorkCategoryModel>[].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<ProjectController>().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<void> fetchRoles() async {
logger.i("Fetching roles...");
final result = await ApiService.getRoles();
if (result != null) {
roles = List<Map<String, dynamic>>.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<bool> assignDailyTask({
required String workItemId,
required int plannedTask,
required String description,
required List<String> 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<bool> createTask({
required String parentTaskId,
required int plannedTask,
required String description,
required List<String> 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<void> 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<void> fetchWorkMasterCategories() async {
isLoadingWorkMasterCategories.value = true;
final response = await ApiService.getMasterWorkCategories();
if (response != null) {
try {
final dataList = response['data'] ?? [];
workMasterCategories.assignAll(
List<WorkCategoryModel>.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();
}
}

View File

@ -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";
}

View File

@ -378,6 +378,11 @@ class ApiService {
_getRequest(ApiEndpoints.getWorkStatus).then((res) => res != null
? _parseResponseForAllData(res, label: 'Work Status')
: null);
static Future<Map<String, dynamic>?> getMasterWorkCategories() async =>
_getRequest(ApiEndpoints.getmasterWorkCategories).then((res) =>
res != null
? _parseResponseForAllData(res, label: 'Master Work Categories')
: null);
static Future<bool> 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<bool> createTask({
required String parentTaskId,
required int plannedTask,
required String description,
required List<String> 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;
}
}

View File

@ -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<String> 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<Color>((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<String>(
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,
),
),
),
],
),
],
),
),

View File

@ -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<String, dynamic> json) {
return WorkCategoryModel(
id: json['id'] ?? '',
name: json['name'] ?? '',
description: json['description'] ?? '',
isSystem: json['isSystem'] ?? false,
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'description': description,
'isSystem': isSystem,
};
}
}

View File

@ -17,11 +17,16 @@ import 'dart:io';
class ReportActionBottomSheet extends StatefulWidget {
final Map<String, dynamic> 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<ReportActionBottomSheet>
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<ReportActionBottomSheet>
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<ReportActionBottomSheet>
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();
}
}
},

View File

@ -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<String, dynamic> _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,
};
}
}
}

View File

@ -379,7 +379,8 @@ class _DailyProgressReportScreenState extends State<DailyProgressReportScreen>
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<DailyProgressReportScreen>
TaskActionButtons.reportActionButton(
context: context,
task: task,
parentTaskID: parentTaskID,
workItemId: workItemId.toString(),
completed: completed,
refreshCallback: _refreshData,
),

View File

@ -98,8 +98,6 @@ flutter:
- assets/avatar/
- assets/coin/
- assets/country/
- assets/data/
- assets/dummy/
- assets/lang/
- assets/logo/
- assets/logo/loading_logo.png