From 628aaa0387cb16ac73e9ab37be7836ea824be9a3 Mon Sep 17 00:00:00 2001 From: Vaibhav Surve Date: Tue, 13 May 2025 17:02:31 +0530 Subject: [PATCH] Add comment task feature with API integration and UI updates --- .../task_planing/report_task_controller.dart | 59 +++++++- lib/helpers/services/api_endpoints.dart | 1 + lib/helpers/services/api_service.dart | 26 ++++ lib/model/daily_task_model.dart | 32 ++-- lib/view/dashboard/daily_task_screen.dart | 58 +++++-- lib/view/taskPlaning/comment_task_screen.dart | 143 +++++++++++++----- 6 files changed, 257 insertions(+), 62 deletions(-) diff --git a/lib/controller/task_planing/report_task_controller.dart b/lib/controller/task_planing/report_task_controller.dart index 5c8935f..0141bc5 100644 --- a/lib/controller/task_planing/report_task_controller.dart +++ b/lib/controller/task_planing/report_task_controller.dart @@ -39,7 +39,7 @@ class ReportTaskController extends MyController { label: "Team Size", controller: TextEditingController(), ); - basicValidator.addField( + basicValidator.addField( 'task_id', label: "Task Id", controller: TextEditingController(), @@ -66,6 +66,16 @@ class ReportTaskController extends MyController { label: "Assigned By", controller: TextEditingController(), ); + basicValidator.addField( + 'team_members', + label: "Team Members", + controller: TextEditingController(), + ); + basicValidator.addField( + 'planned_work', + label: "Planned Work", + controller: TextEditingController(), + ); logger.i( "Fields initialized for assigned_date, work_area, activity, team_size, assigned, completed_work, and comment."); @@ -95,7 +105,7 @@ class ReportTaskController extends MyController { } try { - isLoading.value = true; + isLoading.value = true; final success = await ApiService.reportTask( id: projectId, @@ -113,7 +123,50 @@ class ReportTaskController extends MyController { logger.e("Error reporting task: $e"); Get.snackbar("Error", "An error occurred while reporting the task."); } finally { - isLoading.value = false; + isLoading.value = false; + } + } + Future commentTask({ + required String projectId, + required String comment, + required int completedTask, + required List> checklist, + required DateTime reportedDate, + }) async { + logger.i("Starting task report..."); + + final completedWork = + basicValidator.getController('completed_work')?.text.trim(); + final commentField = basicValidator.getController('comment')?.text.trim(); + + if (completedWork == null || completedWork.isEmpty) { + Get.snackbar("Error", "Completed work is required."); + return; + } + + if (commentField == null || commentField.isEmpty) { + Get.snackbar("Error", "Comment is required."); + return; + } + + try { + isLoading.value = true; + + final success = await ApiService.commentTask( + id: projectId, + comment: commentField, + ); + + if (success) { + Get.snackbar("Success", "Task reported successfully!"); + } else { + Get.snackbar("Error", "Failed to report task."); + } + } catch (e) { + logger.e("Error reporting task: $e"); + Get.snackbar("Error", "An error occurred while reporting the task."); + } finally { + isLoading.value = false; } } } diff --git a/lib/helpers/services/api_endpoints.dart b/lib/helpers/services/api_endpoints.dart index 03e4350..15a45ae 100644 --- a/lib/helpers/services/api_endpoints.dart +++ b/lib/helpers/services/api_endpoints.dart @@ -18,4 +18,5 @@ class ApiEndpoints { // Daily Task Screen API Endpoints static const String getDailyTask = "/task/list"; static const String reportTask = "/task/report"; + static const String commentTask = "task/comment"; } diff --git a/lib/helpers/services/api_service.dart b/lib/helpers/services/api_service.dart index 8c626ed..ce74a51 100644 --- a/lib/helpers/services/api_service.dart +++ b/lib/helpers/services/api_service.dart @@ -331,6 +331,32 @@ class ApiService { final json = jsonDecode(response.body); + if (response.statusCode == 200 && json['success'] == true) { + return true; + } else { + _log("Failed to report task: ${json['message'] ?? 'Unknown error'}"); + return false; + } + } + static Future commentTask({ + required String id, + required String comment, + }) async { + final body = { + "taskAllocationId": id, + "comment": comment, + "commentDate": DateTime.now().toUtc().toIso8601String(), + }; + + final response = await _postRequest(ApiEndpoints.commentTask, body); + + if (response == null) { + _log("Error: No response from server."); + return false; + } + + final json = jsonDecode(response.body); + if (response.statusCode == 200 && json['success'] == true) { return true; } else { diff --git a/lib/model/daily_task_model.dart b/lib/model/daily_task_model.dart index 3a1df1c..6290c80 100644 --- a/lib/model/daily_task_model.dart +++ b/lib/model/daily_task_model.dart @@ -40,9 +40,8 @@ class TaskModel { teamMembers: (json['teamMembers'] as List) .map((e) => TeamMember.fromJson(e)) .toList(), - comments: (json['comments'] as List) - .map((e) => Comment.fromJson(e)) - .toList(), + comments: + (json['comments'] as List).map((e) => Comment.fromJson(e)).toList(), ); } } @@ -65,9 +64,8 @@ class WorkItem { activityMaster: json['activityMaster'] != null ? ActivityMaster.fromJson(json['activityMaster']) : null, - workArea: json['workArea'] != null - ? WorkArea.fromJson(json['workArea']) - : null, + workArea: + json['workArea'] != null ? WorkArea.fromJson(json['workArea']) : null, plannedWork: json['plannedWork'], completedWork: json['completedWork'], ); @@ -139,20 +137,34 @@ class AssignedBy { class TeamMember { final String firstName; + final String? lastName; - TeamMember({required this.firstName}); + TeamMember({required this.firstName, this.lastName}); factory TeamMember.fromJson(Map json) { - return TeamMember(firstName: json['firstName']); + return TeamMember( + firstName: json['firstName'], + lastName: json['lastName'], + ); } } class Comment { final String comment; + final TeamMember commentedBy; + final String timestamp; - Comment({required this.comment}); + Comment({ + required this.comment, + required this.commentedBy, + required this.timestamp, + }); factory Comment.fromJson(Map json) { - return Comment(comment: json['comment']); + return Comment( + comment: json['comment'], + commentedBy: TeamMember.fromJson(json['employee']), + timestamp: json['commentDate'], + ); } } diff --git a/lib/view/dashboard/daily_task_screen.dart b/lib/view/dashboard/daily_task_screen.dart index f818637..d4de1f2 100644 --- a/lib/view/dashboard/daily_task_screen.dart +++ b/lib/view/dashboard/daily_task_screen.dart @@ -245,16 +245,17 @@ class _DailyTaskScreenState extends State with UIMixin { return DataRow(cells: [ DataCell( - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - MyText.bodyMedium(workItem?.activityMaster?.activityName ?? 'N/A', fontWeight: 600), - SizedBox(height: 2), - MyText.bodySmall(location, color: Colors.grey), - ], - ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + MyText.bodyMedium(workItem?.activityMaster?.activityName ?? 'N/A', + fontWeight: 600), + SizedBox(height: 2), + MyText.bodySmall(location, color: Colors.grey), + ], ), + ), DataCell( MyText.bodyMedium( '${task.plannedTask ?? "NA"} / ' @@ -313,7 +314,44 @@ class _DailyTaskScreenState extends State with UIMixin { ), const SizedBox(width: 8), ElevatedButton( - onPressed: () {}, + onPressed: () { + final activityName = + task.workItem?.activityMaster?.activityName ?? 'N/A'; + final assigned = '${task.plannedTask ?? "NA"} / ' + '${(task.workItem?.plannedWork != null && task.workItem?.completedWork != null) ? (task.workItem!.plannedWork! - task.workItem.completedWork!) : "NA"}'; + final plannedWork = '${(task.plannedTask.toString())}'; + final assignedBy = + "${task.assignedBy.firstName} ${task.assignedBy.lastName ?? ''}"; + final completedWork = '${(task.completedTask.toString())}'; + final assignedOn = DateFormat('dd-MM-yyyy') + .format(DateTime.parse(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 teamMembers = + task.teamMembers.map((member) => member.firstName).toList(); + final taskComments = task.comments.map((comment) => comment.comment ?? 'No Content').toList(); + Get.toNamed( + '/daily-task/comment-task', + arguments: { + 'activity': activityName, + 'assigned': assigned, + 'taskId': taskId, + 'assignedBy': assignedBy, + 'completedWork': completedWork, + 'plannedWork': plannedWork, + 'assignedOn': assignedOn, + 'location': location, + 'teamSize': task.teamMembers.length, + 'teamMembers': teamMembers, + 'taskComments': taskComments + }, + ); + }, style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6), minimumSize: const Size(60, 20), diff --git a/lib/view/taskPlaning/comment_task_screen.dart b/lib/view/taskPlaning/comment_task_screen.dart index 75a590f..4cfacc7 100644 --- a/lib/view/taskPlaning/comment_task_screen.dart +++ b/lib/view/taskPlaning/comment_task_screen.dart @@ -15,6 +15,7 @@ import 'package:marco/helpers/widgets/my_spacing.dart'; import 'package:marco/helpers/widgets/my_text.dart'; import 'package:marco/helpers/widgets/my_text_style.dart'; import 'package:marco/view/layouts/layout.dart'; +import 'package:marco/helpers/widgets/avatar.dart'; class CommentTaskScreen extends StatefulWidget { const CommentTaskScreen({super.key}); @@ -38,8 +39,12 @@ class _CommentTaskScreenState extends State with UIMixin { taskData['location'] ?? ''; controller.basicValidator.getController('activity')?.text = taskData['activity'] ?? ''; - controller.basicValidator.getController('team_size')?.text = - taskData['teamSize'].toString(); + controller.basicValidator.getController('planned_work')?.text = + taskData['plannedWork'] ?? ''; + controller.basicValidator.getController('completed_work')?.text = + taskData['completedWork'] ?? ''; + controller.basicValidator.getController('team_members')?.text = + (taskData['teamMembers'] as List).join(', '); controller.basicValidator.getController('assigned')?.text = taskData['assigned'] ?? ''; controller.basicValidator.getController('task_id')?.text = @@ -48,7 +53,7 @@ class _CommentTaskScreenState extends State with UIMixin { return Layout( child: GetBuilder( init: controller, - tag: 'report_task_controller', + tag: 'comment_task_controller', builder: (controller) { return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -58,12 +63,12 @@ class _CommentTaskScreenState extends State with UIMixin { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - MyText.titleMedium("Report Task", + MyText.titleMedium("Comment Task", fontSize: 18, fontWeight: 600), MyBreadcrumb( children: [ MyBreadcrumbItem(name: 'Daily Task'), - MyBreadcrumbItem(name: 'Report Task'), + MyBreadcrumbItem(name: 'Comment Task'), ], ), ], @@ -100,18 +105,11 @@ class _CommentTaskScreenState extends State with UIMixin { children: [ Icon(LucideIcons.server, size: 16), MySpacing.width(12), - MyText.titleMedium("General", fontWeight: 600), + MyText.titleMedium("Activity Summary", fontWeight: 600), ], ), MySpacing.height(24), - // Static fields - buildRow( - "Assigned Date", - controller.basicValidator - .getController('assigned_date') - ?.text - .trim()), buildRow( "Assigned By", controller.basicValidator @@ -131,39 +129,19 @@ class _CommentTaskScreenState extends State with UIMixin { ?.text .trim()), buildRow( - "Team Size", + "Planned Work", controller.basicValidator - .getController('team_size') + .getController('planned_work') ?.text .trim()), buildRow( - "Assigned", + "Completed Work", controller.basicValidator - .getController('assigned') + .getController('completed_work') ?.text .trim()), + buildTeamMembers(), - // Input fields - MyText.labelMedium("Completed Work"), - MySpacing.height(8), - TextFormField( - validator: - controller.basicValidator.getValidation('completed_work'), - controller: - controller.basicValidator.getController('completed_work'), - keyboardType: TextInputType.number, - decoration: InputDecoration( - hintText: "eg: 10", - hintStyle: MyTextStyle.bodySmall(xMuted: true), - border: outlineInputBorder, - enabledBorder: outlineInputBorder, - focusedBorder: focusedInputBorder, - contentPadding: MySpacing.all(16), - isCollapsed: true, - floatingLabelBehavior: FloatingLabelBehavior.never, - ), - ), - MySpacing.height(24), MyText.labelMedium("Comment"), MySpacing.height(8), TextFormField( @@ -221,7 +199,7 @@ class _CommentTaskScreenState extends State with UIMixin { backgroundColor: contentTheme.primary, borderRadiusAll: AppStyle.buttonRadius.medium, child: MyText.bodySmall( - 'Save', + 'Comment', color: contentTheme.onPrimary, ), ), @@ -239,6 +217,93 @@ class _CommentTaskScreenState extends State with UIMixin { ), ); } +Widget buildTeamMembers() { + final teamMembersText = + controller.basicValidator.getController('team_members')?.text ?? ''; + final members = teamMembersText + .split(',') + .map((e) => e.trim()) + .where((e) => e.isNotEmpty) + .toList(); + + return Padding( + padding: const EdgeInsets.only(bottom: 16), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + MyText.labelMedium("Team Members:"), + MySpacing.width(12), + GestureDetector( + onTap: () => showTeamMembersDialog(members), + child: SizedBox( + height: 32, + width: 100, + child: Stack( + children: [ + for (int i = 0; i < members.length.clamp(0, 3); i++) + Positioned( + left: i * 24.0, + child: Tooltip( + message: members[i], + child: Avatar( + firstName: members[i], + lastName: '', + size: 32, + ), + ), + ), + if (members.length > 3) + Positioned( + left: 2 * 24.0, + child: CircleAvatar( + radius: 16, + backgroundColor: Colors.grey.shade300, + child: MyText.bodyMedium( + '+${members.length - 3}', + style: const TextStyle( + fontSize: 12, color: Colors.black87), + ), + ), + ), + ], + ), + ), + ), + ], + ), + ); +} + + void showTeamMembersDialog(List members) { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: MyText.titleMedium('Team Members'), + content: SizedBox( + width: 300, + child: ListView.builder( + shrinkWrap: true, + itemCount: members.length, + itemBuilder: (context, index) { + final name = members[index]; + return ListTile( + leading: Avatar(firstName: name, lastName: "", size: 32), + title: MyText.bodyMedium(name), + ); + }, + ), + ), + actions: [ + TextButton( + onPressed: () => Get.back(), + child: MyText.bodySmall("Close"), + ), + ], + ); + }, + ); + } Widget buildRow(String label, String? value) { print("Label: $label, Value: $value");