feat: update JobDetailsResponse and JobData models to support nullable fields and improve JSON parsing
This commit is contained in:
parent
02ef996753
commit
c44d10d35a
@ -1,91 +1,95 @@
|
||||
class JobDetailsResponse {
|
||||
final bool success;
|
||||
final String message;
|
||||
final bool? success;
|
||||
final String? message;
|
||||
final JobData? data;
|
||||
final dynamic errors;
|
||||
final int statusCode;
|
||||
final String timestamp;
|
||||
final int? statusCode;
|
||||
final String? timestamp;
|
||||
|
||||
JobDetailsResponse({
|
||||
required this.success,
|
||||
required this.message,
|
||||
this.success,
|
||||
this.message,
|
||||
this.data,
|
||||
this.errors,
|
||||
required this.statusCode,
|
||||
required this.timestamp,
|
||||
this.statusCode,
|
||||
this.timestamp,
|
||||
});
|
||||
|
||||
factory JobDetailsResponse.fromJson(Map<String, dynamic> json) {
|
||||
factory JobDetailsResponse.fromJson(Map<String, dynamic>? json) {
|
||||
if (json == null) return JobDetailsResponse();
|
||||
|
||||
return JobDetailsResponse(
|
||||
success: json['success'] as bool,
|
||||
message: json['message'] as String,
|
||||
success: json['success'] as bool?,
|
||||
message: json['message'] as String?,
|
||||
data: json['data'] != null ? JobData.fromJson(json['data']) : null,
|
||||
errors: json['errors'],
|
||||
statusCode: json['statusCode'] as int,
|
||||
timestamp: json['timestamp'] as String,
|
||||
statusCode: json['statusCode'] as int?,
|
||||
timestamp: json['timestamp'] as String?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class JobData {
|
||||
final String id;
|
||||
final String title;
|
||||
final String description;
|
||||
final String jobTicketUId;
|
||||
final Project project;
|
||||
final List<Assignee> assignees;
|
||||
final Status status;
|
||||
final String startDate;
|
||||
final String dueDate;
|
||||
final bool isActive;
|
||||
final String? id;
|
||||
final String? title;
|
||||
final String? description;
|
||||
final String? jobTicketUId;
|
||||
final Project? project;
|
||||
final List<Assignee>? assignees;
|
||||
final Status? status;
|
||||
final String? startDate;
|
||||
final String? dueDate;
|
||||
final bool? isActive;
|
||||
final dynamic taggingAction;
|
||||
final String? attendanceId;
|
||||
final int? nextTaggingAction;
|
||||
final String createdAt;
|
||||
final User createdBy;
|
||||
final List<Tag> tags;
|
||||
final List<UpdateLog> updateLogs;
|
||||
final String? createdAt;
|
||||
final User? createdBy;
|
||||
final List<Tag>? tags;
|
||||
final List<UpdateLog>? updateLogs;
|
||||
|
||||
JobData({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.jobTicketUId,
|
||||
required this.project,
|
||||
required this.assignees,
|
||||
required this.status,
|
||||
required this.startDate,
|
||||
required this.dueDate,
|
||||
required this.isActive,
|
||||
this.id,
|
||||
this.title,
|
||||
this.description,
|
||||
this.jobTicketUId,
|
||||
this.project,
|
||||
this.assignees,
|
||||
this.status,
|
||||
this.startDate,
|
||||
this.dueDate,
|
||||
this.isActive,
|
||||
this.taggingAction,
|
||||
this.attendanceId,
|
||||
this.nextTaggingAction,
|
||||
required this.createdAt,
|
||||
required this.createdBy,
|
||||
required this.tags,
|
||||
required this.updateLogs,
|
||||
this.createdAt,
|
||||
this.createdBy,
|
||||
this.tags,
|
||||
this.updateLogs,
|
||||
});
|
||||
|
||||
factory JobData.fromJson(Map<String, dynamic> json) {
|
||||
factory JobData.fromJson(Map<String, dynamic>? json) {
|
||||
if (json == null) return JobData();
|
||||
|
||||
return JobData(
|
||||
id: json['id'] as String,
|
||||
title: json['title'] as String,
|
||||
description: json['description'] as String,
|
||||
jobTicketUId: json['jobTicketUId'] as String,
|
||||
project: Project.fromJson(json['project']),
|
||||
id: json['id'] as String?,
|
||||
title: json['title'] as String?,
|
||||
description: json['description'] as String?,
|
||||
jobTicketUId: json['jobTicketUId'] as String?,
|
||||
project: json['project'] != null ? Project.fromJson(json['project']) : null,
|
||||
assignees: (json['assignees'] as List<dynamic>?)
|
||||
?.map((e) => Assignee.fromJson(e))
|
||||
.toList() ??
|
||||
[],
|
||||
status: Status.fromJson(json['status']),
|
||||
startDate: json['startDate'] as String,
|
||||
dueDate: json['dueDate'] as String,
|
||||
isActive: json['isActive'] as bool,
|
||||
status: json['status'] != null ? Status.fromJson(json['status']) : null,
|
||||
startDate: json['startDate'] as String?,
|
||||
dueDate: json['dueDate'] as String?,
|
||||
isActive: json['isActive'] as bool?,
|
||||
taggingAction: json['taggingAction'],
|
||||
attendanceId: json['attendanceId'] as String?,
|
||||
nextTaggingAction: json['nextTaggingAction'] as int?,
|
||||
createdAt: json['createdAt'] as String,
|
||||
createdBy: User.fromJson(json['createdBy']),
|
||||
createdAt: json['createdAt'] as String?,
|
||||
createdBy: json['createdBy'] != null ? User.fromJson(json['createdBy']) : null,
|
||||
tags: (json['tags'] as List<dynamic>?)
|
||||
?.map((e) => Tag.fromJson(e))
|
||||
.toList() ??
|
||||
@ -99,120 +103,128 @@ class JobData {
|
||||
}
|
||||
|
||||
class Project {
|
||||
final String id;
|
||||
final String name;
|
||||
final String shortName;
|
||||
final String assignedDate;
|
||||
final String contactName;
|
||||
final String contactPhone;
|
||||
final String contactEmail;
|
||||
final String? id;
|
||||
final String? name;
|
||||
final String? shortName;
|
||||
final String? assignedDate;
|
||||
final String? contactName;
|
||||
final String? contactPhone;
|
||||
final String? contactEmail;
|
||||
|
||||
Project({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.shortName,
|
||||
required this.assignedDate,
|
||||
required this.contactName,
|
||||
required this.contactPhone,
|
||||
required this.contactEmail,
|
||||
this.id,
|
||||
this.name,
|
||||
this.shortName,
|
||||
this.assignedDate,
|
||||
this.contactName,
|
||||
this.contactPhone,
|
||||
this.contactEmail,
|
||||
});
|
||||
|
||||
factory Project.fromJson(Map<String, dynamic> json) {
|
||||
factory Project.fromJson(Map<String, dynamic>? json) {
|
||||
if (json == null) return Project();
|
||||
|
||||
return Project(
|
||||
id: json['id'] as String,
|
||||
name: json['name'] as String,
|
||||
shortName: json['shortName'] as String,
|
||||
assignedDate: json['assignedDate'] as String,
|
||||
contactName: json['contactName'] as String,
|
||||
contactPhone: json['contactPhone'] as String,
|
||||
contactEmail: json['contactEmail'] as String,
|
||||
id: json['id'] as String?,
|
||||
name: json['name'] as String?,
|
||||
shortName: json['shortName'] as String?,
|
||||
assignedDate: json['assignedDate'] as String?,
|
||||
contactName: json['contactName'] as String?,
|
||||
contactPhone: json['contactPhone'] as String?,
|
||||
contactEmail: json['contactEmail'] as String?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Assignee {
|
||||
final String id;
|
||||
final String firstName;
|
||||
final String lastName;
|
||||
final String? id;
|
||||
final String? firstName;
|
||||
final String? lastName;
|
||||
final String? email;
|
||||
final String? photo;
|
||||
final String jobRoleId;
|
||||
final String jobRoleName;
|
||||
final String? jobRoleId;
|
||||
final String? jobRoleName;
|
||||
|
||||
Assignee({
|
||||
required this.id,
|
||||
required this.firstName,
|
||||
required this.lastName,
|
||||
this.id,
|
||||
this.firstName,
|
||||
this.lastName,
|
||||
this.email,
|
||||
this.photo,
|
||||
required this.jobRoleId,
|
||||
required this.jobRoleName,
|
||||
this.jobRoleId,
|
||||
this.jobRoleName,
|
||||
});
|
||||
|
||||
factory Assignee.fromJson(Map<String, dynamic> json) {
|
||||
factory Assignee.fromJson(Map<String, dynamic>? json) {
|
||||
if (json == null) return Assignee();
|
||||
|
||||
return Assignee(
|
||||
id: json['id'] as String,
|
||||
firstName: json['firstName'] as String,
|
||||
lastName: json['lastName'] as String,
|
||||
id: json['id'] as String?,
|
||||
firstName: json['firstName'] as String?,
|
||||
lastName: json['lastName'] as String?,
|
||||
email: json['email'] as String?,
|
||||
photo: json['photo'] as String?,
|
||||
jobRoleId: json['jobRoleId'] as String,
|
||||
jobRoleName: json['jobRoleName'] as String,
|
||||
jobRoleId: json['jobRoleId'] as String?,
|
||||
jobRoleName: json['jobRoleName'] as String?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Status {
|
||||
final String id;
|
||||
final String name;
|
||||
final String displayName;
|
||||
final int level;
|
||||
final String? id;
|
||||
final String? name;
|
||||
final String? displayName;
|
||||
final int? level;
|
||||
|
||||
Status({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.displayName,
|
||||
required this.level,
|
||||
this.id,
|
||||
this.name,
|
||||
this.displayName,
|
||||
this.level,
|
||||
});
|
||||
|
||||
factory Status.fromJson(Map<String, dynamic> json) {
|
||||
factory Status.fromJson(Map<String, dynamic>? json) {
|
||||
if (json == null) return Status();
|
||||
|
||||
return Status(
|
||||
id: json['id'] as String,
|
||||
name: json['name'] as String,
|
||||
displayName: json['displayName'] as String,
|
||||
level: json['level'] as int,
|
||||
id: json['id'] as String?,
|
||||
name: json['name'] as String?,
|
||||
displayName: json['displayName'] as String?,
|
||||
level: json['level'] as int?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class User {
|
||||
final String id;
|
||||
final String firstName;
|
||||
final String lastName;
|
||||
final String? id;
|
||||
final String? firstName;
|
||||
final String? lastName;
|
||||
final String? email;
|
||||
final String? photo;
|
||||
final String jobRoleId;
|
||||
final String jobRoleName;
|
||||
final String? jobRoleId;
|
||||
final String? jobRoleName;
|
||||
|
||||
User({
|
||||
required this.id,
|
||||
required this.firstName,
|
||||
required this.lastName,
|
||||
this.id,
|
||||
this.firstName,
|
||||
this.lastName,
|
||||
this.email,
|
||||
this.photo,
|
||||
required this.jobRoleId,
|
||||
required this.jobRoleName,
|
||||
this.jobRoleId,
|
||||
this.jobRoleName,
|
||||
});
|
||||
|
||||
factory User.fromJson(Map<String, dynamic> json) {
|
||||
factory User.fromJson(Map<String, dynamic>? json) {
|
||||
if (json == null) return User();
|
||||
|
||||
return User(
|
||||
id: json['id'] as String,
|
||||
firstName: json['firstName'] as String,
|
||||
lastName: json['lastName'] as String,
|
||||
id: json['id'] as String?,
|
||||
firstName: json['firstName'] as String?,
|
||||
lastName: json['lastName'] as String?,
|
||||
email: json['email'] as String?,
|
||||
photo: json['photo'] as String?,
|
||||
jobRoleId: json['jobRoleId'] as String,
|
||||
jobRoleName: json['jobRoleName'] as String,
|
||||
jobRoleId: json['jobRoleId'] as String?,
|
||||
jobRoleName: json['jobRoleName'] as String?,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -223,7 +235,9 @@ class Tag {
|
||||
|
||||
Tag({this.id, this.name});
|
||||
|
||||
factory Tag.fromJson(Map<String, dynamic> json) {
|
||||
factory Tag.fromJson(Map<String, dynamic>? json) {
|
||||
if (json == null) return Tag();
|
||||
|
||||
return Tag(
|
||||
id: json['id'] as String?,
|
||||
name: json['name'] as String?,
|
||||
@ -232,27 +246,29 @@ class Tag {
|
||||
}
|
||||
|
||||
class UpdateLog {
|
||||
final String id;
|
||||
final String? id;
|
||||
final Status? status;
|
||||
final Status nextStatus;
|
||||
final String comment;
|
||||
final User updatedBy;
|
||||
final Status? nextStatus;
|
||||
final String? comment;
|
||||
final User? updatedBy;
|
||||
|
||||
UpdateLog({
|
||||
required this.id,
|
||||
this.id,
|
||||
this.status,
|
||||
required this.nextStatus,
|
||||
required this.comment,
|
||||
required this.updatedBy,
|
||||
this.nextStatus,
|
||||
this.comment,
|
||||
this.updatedBy,
|
||||
});
|
||||
|
||||
factory UpdateLog.fromJson(Map<String, dynamic> json) {
|
||||
factory UpdateLog.fromJson(Map<String, dynamic>? json) {
|
||||
if (json == null) return UpdateLog();
|
||||
|
||||
return UpdateLog(
|
||||
id: json['id'] as String,
|
||||
id: json['id'] as String?,
|
||||
status: json['status'] != null ? Status.fromJson(json['status']) : null,
|
||||
nextStatus: Status.fromJson(json['nextStatus']),
|
||||
comment: json['comment'] as String,
|
||||
updatedBy: User.fromJson(json['updatedBy']),
|
||||
nextStatus: json['nextStatus'] != null ? Status.fromJson(json['nextStatus']) : null,
|
||||
comment: json['comment'] as String?,
|
||||
updatedBy: json['updatedBy'] != null ? User.fromJson(json['updatedBy']) : null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,15 +50,16 @@ class _JobDetailsScreenState extends State<JobDetailsScreen> with UIMixin {
|
||||
controller.fetchJobDetail(widget.jobId).then((_) {
|
||||
final job = controller.jobDetail.value?.data;
|
||||
if (job != null) {
|
||||
_titleController.text = job.title;
|
||||
_descriptionController.text = job.description;
|
||||
_titleController.text = job.title ?? '';
|
||||
_descriptionController.text = job.description ?? '';
|
||||
_startDateController.text = DateTimeUtils.convertUtcToLocal(
|
||||
job.startDate,
|
||||
job.startDate ?? DateTime.now().toIso8601String(),
|
||||
format: "yyyy-MM-dd");
|
||||
_dueDateController.text =
|
||||
DateTimeUtils.convertUtcToLocal(job.dueDate, format: "yyyy-MM-dd");
|
||||
_selectedAssignees.value = job.assignees;
|
||||
_selectedTags.value = job.tags;
|
||||
_dueDateController.text = DateTimeUtils.convertUtcToLocal(
|
||||
job.dueDate ?? '',
|
||||
format: "yyyy-MM-dd");
|
||||
_selectedAssignees.value = job.assignees ?? [];
|
||||
_selectedTags.value = job.tags ?? [];
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -114,14 +115,14 @@ class _JobDetailsScreenState extends State<JobDetailsScreen> with UIMixin {
|
||||
}
|
||||
|
||||
final originalAssignees = job.assignees;
|
||||
final assigneesPayload = originalAssignees.map((a) {
|
||||
final assigneesPayload = originalAssignees?.map((a) {
|
||||
final isSelected = _selectedAssignees.any((s) => s.id == a.id);
|
||||
return {"employeeId": a.id, "isActive": isSelected};
|
||||
}).toList();
|
||||
|
||||
for (var s in _selectedAssignees) {
|
||||
if (!originalAssignees.any((a) => a.id == s.id)) {
|
||||
assigneesPayload.add({"employeeId": s.id, "isActive": true});
|
||||
if (!(originalAssignees?.any((a) => a.id == s.id) ?? false)) {
|
||||
assigneesPayload?.add({"employeeId": s.id, "isActive": true});
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,7 +130,7 @@ class _JobDetailsScreenState extends State<JobDetailsScreen> with UIMixin {
|
||||
{"op": "replace", "path": "/assignees", "value": assigneesPayload});
|
||||
|
||||
final originalTags = job.tags;
|
||||
final replaceTagsPayload = originalTags.map((t) {
|
||||
final replaceTagsPayload = originalTags?.map((t) {
|
||||
final isSelected = _selectedTags.any((s) => s.id == t.id);
|
||||
return {"id": t.id, "name": t.name, "isActive": isSelected};
|
||||
}).toList();
|
||||
@ -139,7 +140,7 @@ class _JobDetailsScreenState extends State<JobDetailsScreen> with UIMixin {
|
||||
.map((t) => {"name": t.name, "isActive": true})
|
||||
.toList();
|
||||
|
||||
if (replaceTagsPayload.isNotEmpty) {
|
||||
if ((replaceTagsPayload?.isNotEmpty ?? false)) {
|
||||
operations
|
||||
.add({"op": "replace", "path": "/tags", "value": replaceTagsPayload});
|
||||
}
|
||||
@ -157,7 +158,7 @@ class _JobDetailsScreenState extends State<JobDetailsScreen> with UIMixin {
|
||||
}
|
||||
|
||||
final success = await ApiService.editServiceProjectJobApi(
|
||||
jobId: job.id,
|
||||
jobId: job.id ?? "",
|
||||
operations: operations,
|
||||
);
|
||||
|
||||
@ -207,7 +208,7 @@ class _JobDetailsScreenState extends State<JobDetailsScreen> with UIMixin {
|
||||
);
|
||||
|
||||
await controller.updateJobAttendance(
|
||||
jobId: job.id,
|
||||
jobId: job.id ?? "",
|
||||
action: action == 0 ? 0 : 1,
|
||||
comment: comment,
|
||||
attachment: attachmentFile,
|
||||
@ -352,14 +353,14 @@ class _JobDetailsScreenState extends State<JobDetailsScreen> with UIMixin {
|
||||
onTap: () async {
|
||||
final initiallySelected = assignees.map<EmployeeModel>((a) {
|
||||
return EmployeeModel(
|
||||
id: a.id,
|
||||
employeeId: a.id,
|
||||
firstName: a.firstName,
|
||||
lastName: a.lastName,
|
||||
id: a.id ?? '',
|
||||
employeeId: a.id ?? '',
|
||||
firstName: a.firstName ?? '',
|
||||
lastName: a.lastName ?? '',
|
||||
name: "${a.firstName} ${a.lastName}",
|
||||
designation: a.jobRoleName,
|
||||
jobRole: a.jobRoleName,
|
||||
jobRoleID: a.jobRoleId,
|
||||
designation: a.jobRoleName ?? '',
|
||||
jobRole: a.jobRoleName ?? '',
|
||||
jobRoleID: a.jobRoleId ?? '',
|
||||
email: a.email ?? '',
|
||||
phoneNumber: '',
|
||||
activity: 0,
|
||||
@ -495,7 +496,7 @@ class _JobDetailsScreenState extends State<JobDetailsScreen> with UIMixin {
|
||||
onPressed: () async {
|
||||
isAttendanceExpanded.value =
|
||||
!isAttendanceExpanded.value;
|
||||
if (isAttendanceExpanded.value && job != null) {
|
||||
if (isAttendanceExpanded.value ) {
|
||||
await controller
|
||||
.fetchJobAttendanceLog(job.attendanceId ?? '');
|
||||
}
|
||||
@ -774,11 +775,11 @@ class _JobDetailsScreenState extends State<JobDetailsScreen> with UIMixin {
|
||||
titleIcon: Icons.label_outline,
|
||||
children: [_tagEditor()]),
|
||||
MySpacing.height(16),
|
||||
if (job.updateLogs.isNotEmpty)
|
||||
if ((job.updateLogs?.isNotEmpty ?? false))
|
||||
_buildSectionCard(
|
||||
title: "Update Logs",
|
||||
titleIcon: Icons.history,
|
||||
children: [JobTimeline(logs: job.updateLogs)]),
|
||||
children: [JobTimeline(logs: job.updateLogs ?? [])]),
|
||||
MySpacing.height(80),
|
||||
],
|
||||
),
|
||||
@ -806,12 +807,14 @@ class JobTimeline extends StatelessWidget {
|
||||
itemBuilder: (_, index) {
|
||||
final log = reversedLogs[index];
|
||||
final statusName = log.status?.displayName ?? "Created";
|
||||
final nextStatusName = log.nextStatus.displayName;
|
||||
final comment = log.comment;
|
||||
final nextStatusName = log.nextStatus?.displayName ?? "N/A";
|
||||
final comment = log.comment ?? '';
|
||||
final updatedBy =
|
||||
"${log.updatedBy.firstName} ${log.updatedBy.lastName}";
|
||||
"${log.updatedBy?.firstName ?? ''} ${log.updatedBy?.lastName ?? ''}";
|
||||
final f = log.updatedBy?.firstName ?? '';
|
||||
final l = log.updatedBy?.lastName ?? '';
|
||||
final initials =
|
||||
"${log.updatedBy.firstName.isNotEmpty ? log.updatedBy.firstName[0] : ''}${log.updatedBy.lastName.isNotEmpty ? log.updatedBy.lastName[0] : ''}";
|
||||
"${f.isNotEmpty ? f[0] : ''}${l.isNotEmpty ? l[0] : ''}";
|
||||
|
||||
return TimelineTile(
|
||||
alignment: TimelineAlign.start,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user