Add comment task feature with API integration and UI updates
This commit is contained in:
parent
42935471cf
commit
628aaa0387
@ -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<void> commentTask({
|
||||
required String projectId,
|
||||
required String comment,
|
||||
required int completedTask,
|
||||
required List<Map<String, dynamic>> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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";
|
||||
}
|
||||
|
@ -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<bool> 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 {
|
||||
|
@ -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<String, dynamic> 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<String, dynamic> json) {
|
||||
return Comment(comment: json['comment']);
|
||||
return Comment(
|
||||
comment: json['comment'],
|
||||
commentedBy: TeamMember.fromJson(json['employee']),
|
||||
timestamp: json['commentDate'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -245,16 +245,17 @@ class _DailyTaskScreenState extends State<DailyTaskScreen> 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<DailyTaskScreen> 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),
|
||||
|
@ -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<CommentTaskScreen> 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<dynamic>).join(', ');
|
||||
controller.basicValidator.getController('assigned')?.text =
|
||||
taskData['assigned'] ?? '';
|
||||
controller.basicValidator.getController('task_id')?.text =
|
||||
@ -48,7 +53,7 @@ class _CommentTaskScreenState extends State<CommentTaskScreen> with UIMixin {
|
||||
return Layout(
|
||||
child: GetBuilder<ReportTaskController>(
|
||||
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<CommentTaskScreen> 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<CommentTaskScreen> 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<CommentTaskScreen> 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<CommentTaskScreen> 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<CommentTaskScreen> 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<String> 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");
|
||||
|
Loading…
x
Reference in New Issue
Block a user