You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cloudsolutions-atoms/lib/modules/tm_module/tasks/task_request_detail_view.dart

416 lines
22 KiB
Dart

import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:test_sa/controllers/api_routes/urls.dart';
import 'package:test_sa/extensions/context_extension.dart';
import 'package:test_sa/extensions/int_extensions.dart';
import 'package:test_sa/extensions/string_extensions.dart';
import 'package:test_sa/extensions/text_extensions.dart';
import 'package:test_sa/extensions/widget_extensions.dart';
import 'package:test_sa/helper/utils.dart';
import 'package:test_sa/models/all_requests_and_count_model.dart';
import 'package:test_sa/models/new_models/task_request/task_request_model.dart';
import 'package:test_sa/modules/cm_module/cm_request_utils.dart';
import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart';
import 'package:test_sa/modules/cx_module/chat/chat_widget.dart';
import 'package:test_sa/new_views/common_widgets/default_app_bar.dart';
import 'package:test_sa/new_views/common_widgets/working_time_tile.dart';
import 'package:test_sa/providers/task_request_provider/task_request_provider.dart';
import 'package:test_sa/views/widgets/bottom_sheets/rejection_reason_bottom_sheet.dart';
import 'package:test_sa/views/widgets/images/files_list.dart';
import 'package:test_sa/views/widgets/item_views/info_date_widget.dart';
import 'package:test_sa/views/widgets/item_views/info_header_widget.dart';
import 'package:test_sa/views/widgets/item_views/info_text_widget.dart';
import 'package:test_sa/views/widgets/loaders/app_loading.dart';
import 'package:test_sa/views/widgets/requests/request_status.dart';
import 'package:test_sa/views/widgets/total_working_time_detail_bottomsheet.dart';
import '../../../../controllers/providers/api/user_provider.dart';
import '../../../../models/enums/user_types.dart';
import '../../../../new_views/app_style/app_color.dart';
import '../../../../new_views/common_widgets/app_filled_button.dart';
import 'update_task_request_view.dart';
class TaskRequestDetailsView extends StatefulWidget {
static const String id = "/task-request-detail";
final int taskId;
final RequestsDetails? requestDetails;
final int moduleId;
const TaskRequestDetailsView({Key? key, required this.taskId, this.requestDetails, required this.moduleId}) : super(key: key);
@override
_TaskRequestDetailsViewState createState() {
return _TaskRequestDetailsViewState();
}
}
class _TaskRequestDetailsViewState extends State<TaskRequestDetailsView> {
UserProvider? userProvider;
TaskRequestProvider? taskProvider;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
getTaskData();
});
}
@override
void dispose() {
super.dispose();
}
Future<void> getTaskData() async {
taskProvider = Provider.of<TaskRequestProvider>(context, listen: false);
await taskProvider?.getTaskById(id: widget.taskId);
}
@override
Widget build(BuildContext context) {
userProvider ??= Provider.of<UserProvider>(context, listen: false);
return Scaffold(
appBar: DefaultAppBar(
title: context.translation.taskRequest,
actions: [
if (context.userProvider.isEngineer || context.userProvider.isNurse)
Selector<TaskRequestProvider, TaskData?>(
selector: (_, myModel) => myModel.taskRequestModel, // Selects only the userName
builder: (_, _taskReqModel, __) {
if (_taskReqModel == null) return const SizedBox();
if ((context.userProvider.isEngineer || context.userProvider.isNurse) &&
_taskReqModel.assignedEngineer?.employeeId != null &&
(_taskReqModel.taskJobContactPersons?.isNotEmpty ?? false)) {
return ChatWidget(
moduleId: widget.moduleId,
isShow: context.settingProvider.isUserFlowMedical && _taskReqModel.taskJobStatus!.value! != 1,
isReadOnly: _taskReqModel.taskJobStatus!.value! != 2,
requestId: widget.taskId.toInt(),
assigneeEmployeeNumber: _taskReqModel.assignedEngineer?.employeeId!,
myLoginUserID: context.userProvider.user!.employeeId ?? context.userProvider.user!.username!,
contactEmployeeINumber: _taskReqModel.taskJobContactPersons!.first.user?.employeeId,
);
} else
return const SizedBox();
})
],
),
body: Consumer<TaskRequestProvider>(builder: (context, taskProvider, child) {
TaskData? taskModel = taskProvider.taskRequestModel;
return taskProvider.isLoading
? const ALoading()
: Column(children: [
SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
StatusLabel(
label: widget.requestDetails?.priority!,
textColor: AppColor.getRequestStatusTextColorByName(context, widget.requestDetails?.priority!),
backgroundColor: AppColor.getRequestStatusColorByName(context, widget.requestDetails?.priority!),
),
8.width,
StatusLabel(
label: taskModel?.taskJobStatus?.name ?? widget.requestDetails?.status!,
textColor: AppColor.getRequestStatusTextColorByName(context, taskModel?.taskJobStatus?.name ?? widget.requestDetails?.status!),
backgroundColor: AppColor.getRequestStatusColorByName(context, taskModel?.taskJobStatus?.name ?? widget.requestDetails?.status!),
),
1.width.expanded,
InfoDateWidget(widget.requestDetails?.date?.toString() ?? ""),
],
),
8.height,
if (taskProvider.taskRequestModel?.taskType?.relatedTo?.value == 1) ...[
assetDetails(taskModel: taskProvider.taskRequestModel!),
],
if (taskProvider.taskRequestModel?.taskType?.isInstallation == true) ...[
const Divider().defaultStyle(context),
installationWidget(taskModel: taskProvider.taskRequestModel!)
],
if (taskProvider.taskRequestModel?.taskType?.relatedTo?.value == 2) ...[
const Divider().defaultStyle(context),
linkWithLocationWidget(taskModel: taskProvider.taskRequestModel!),
],
const Divider().defaultStyle(context),
InfoHeader16Widget("Task Details".addTranslation),
InfoTextWidget(label: context.translation.taskType, value: taskModel?.taskType?.typeName ?? "-"),
InfoTextWidget(label: context.translation.taskNo, value: taskModel?.taskJobNo ?? "-"),
InfoTextWidget(label: context.translation.createdBy, value: taskModel?.userCreated?.userName ?? "-"),
if (taskProvider.taskRequestModel?.taskType?.isRecallAndAlert == true) ...[
recallAlertTypeWidget(taskModel: taskProvider.taskRequestModel!),
InfoTextWidget(label: 'Type of alert', value: taskProvider.taskRequestModel?.typeOfAlert?.name ?? '-'),
InfoTextWidget(label: 'Risk Level', value: taskProvider.taskRequestModel?.riskLevel?.name ?? '-'),
InfoTextWidget(label: 'Resource', value: taskProvider.taskRequestModel?.resource?.name ?? '-'),
InfoTextWidget(label: 'Evaluator User', value: taskProvider.taskRequestModel?.evaluatorUser?.userName ?? '-'),
],
showWorkingTime(taskModel?.taskJobActivityEngineerTimers ?? []),
if ((taskProvider.taskRequestModel?.taskJobContactPersons ?? []).isNotEmpty) ...[contactInfoCard(context, taskProvider.taskRequestModel!)],
if (taskProvider.taskRequestModel?.callComment != null) ...[
const Divider().defaultStyle(context),
InfoHeader16Widget(context.translation.comment),
Text(
taskProvider.taskRequestModel!.callComment ?? '',
style: AppTextStyles.bodyText2.copyWith(color: AppColor.lightTextColor(context)),
),
],
if (taskProvider.taskRequestModel?.taskJobAttachments?.isNotEmpty ?? false) ...[
const Divider().defaultStyle(context),
InfoHeader16Widget("Attachments".addTranslation),
FilesList(images: taskProvider.taskRequestModel!.taskJobAttachments!.map((toElement) => URLs.getFileUrl(toElement.name ?? '') ?? '').toList()),
],
buildTechnicalComments(taskModel: taskModel),
],
).toShadowContainer(context, padding: 16),
).expanded,
if (taskProvider.taskRequestModel?.taskType?.isRecallAndAlert == false &&
context.settingProvider.isUserFlowMedical &&
userProvider!.user!.type == UsersTypes.engineer &&
(taskModel?.taskJobStatus?.value == 1))
FooterActionButton.footerContainer(
context: context,
child: AppFilledButton(
onPressed: () async {
Utils.showLoading(context);
await taskProvider.assignEngineerToHimself(widget.taskId);
Utils.hideLoading(context);
},
label: 'Assign To Me',
),
)
else if (userProvider!.user!.type == UsersTypes.engineer && taskModel?.taskJobStatus?.value == 5)
...[]
else if (userProvider!.user!.type == UsersTypes.engineer && (taskModel?.taskJobStatus?.value != 4 && taskModel?.taskJobStatus?.value != 3
&& taskModel?.taskJobStatus?.value != 1
))
FooterActionButton.footerContainer(
context: context,
child: Row(
children: [
AppFilledButton(
label: context.translation.cancel,
buttonColor: AppColor.white60,
textColor: AppColor.black10,
loading: false,
onPressed: () async {
context.showBottomSheet(RejectionReasonBottomSheet(
onRejectPressed: (int reasonId, String comment) async {
await Future.delayed(const Duration(milliseconds: 500));
return await taskProvider.engineerReject({"taskJobId": widget.taskId, "rejectReasonId": reasonId, "rejectComment": comment});
},
), title: "Rejection Reason".addTranslation);
},
).expanded,
16.width,
AppFilledButton(
onPressed: () async {
if (taskProvider.taskRequestModel?.taskType?.isInstallation == true) {
taskProvider.getSiteData(siteId: taskProvider.taskRequestModel?.asset?.siteId);
}
Navigator.of(context).push(MaterialPageRoute(
builder: (_) => UpdateTaskRequest(
taskId: widget.taskId,
createdDate: widget.requestDetails?.date,
)));
},
label: context.translation.updateRequest,
).expanded,
],
),
)
]);
}),
);
}
Widget showWorkingTime(List<TaskJobActivityEngineerTimer> timer) {
if (context.userProvider.user!.type == UsersTypes.normal_user) return const SizedBox();
double totalWorkingHours = timer.fold<double>(0.0, (sum, item) {
if (item.startDate == null || item.endDate == null) return sum;
try {
final start = DateTime.parse(item.startDate!);
final end = DateTime.parse(item.endDate!);
final diffInHours = end.difference(start).inSeconds / 3600.0; // convert to hours
return sum + diffInHours;
} catch (_) {
return sum;
}
});
String totalHourString = (totalWorkingHours > 0.0) ? CMRequestUtils.formatTotalWorkingHours(totalWorkingHours) : totalWorkingHours.toString();
return RichText(
text: TextSpan(
text: "${context.translation.totalWorkingTime}: ",
style: TextStyle(
fontSize: 14.toScreenWidth, fontWeight: FontWeight.w500, fontStyle: FontStyle.normal, decoration: TextDecoration.none, color: context.isDark ? AppColor.neutral10 : AppColor.neutral20),
children: [
TextSpan(text: "$totalHourString ", style: AppTextStyles.bodyText),
const WidgetSpan(
child: Icon(Icons.info_outline_rounded, color: AppColor.primary10, size: 14),
),
],
),
).onPress(() => showModalBottomSheet(
context: context,
isDismissible: true,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
clipBehavior: Clip.antiAliasWithSaveLayer,
builder: (BuildContext context) => TotalWorkingTimeDetailBottomSheet(
timerList: timer.map((e) {
return TimerHistoryModel(
id: e.id,
startTime: e.startDate,
endTime: e.endDate,
workingHours: e.totalWorkingHour,
);
}).toList()),
));
}
Widget contactInfoCard(BuildContext context, TaskData? taskModel) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Divider().defaultStyle(context),
InfoHeader16Widget("Contact Info"),
ListView.separated(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
padding: EdgeInsets.zero,
itemBuilder: (cxt, index) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InfoTextWidget(label: context.translation.name, value: taskModel.taskJobContactPersons?[index].user?.userName ?? '-'),
InfoTextWidget(label: context.translation.employeeId, value: taskModel.taskJobContactPersons?[index].user?.employeeId ?? '-'),
InfoTextWidget(label: context.translation.telephone, value: taskModel.taskJobContactPersons?[index].user?.phoneNumber ?? '-'),
InfoTextWidget(label: context.translation.email, value: taskModel.taskJobContactPersons?[index].user?.email ?? '-'),
InfoTextWidget(label: 'Extension', value: taskModel.taskJobContactPersons?[index].user?.extensionNo ?? '-'),
],
),
separatorBuilder: (cxt, index) => const Divider().defaultStyle(context),
itemCount: taskModel!.taskJobContactPersons!.length),
],
);
}
Widget installationWidget({required TaskData taskModel}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InfoHeader16Widget("Site Details".addTranslation),
InfoTextWidget(label: context.translation.installationSite, value: taskModel.asset?.siteName?.cleanupWhitespace.capitalizeFirstOfEach ?? "-"),
InfoTextWidget(label: context.translation.installationBuilding, value: taskModel.installationBuilding?.name?.cleanupWhitespace.capitalizeFirstOfEach ?? '-'),
InfoTextWidget(label: context.translation.installationFloor, value: taskModel.installationFloor?.name?.cleanupWhitespace.capitalizeFirstOfEach ?? '-'),
InfoTextWidget(label: context.translation.installationDepartment, value: taskModel.installationDepartment?.name?.cleanupWhitespace.capitalizeFirstOfEach ?? '-'),
InfoTextWidget(label: context.translation.installationDate, value: taskModel.installationDate?.toAssetDetailsFormat ?? "-"),
InfoTextWidget(label: context.translation.serialNo, value: taskModel.serialNo ?? '-'),
],
);
}
Widget buildTechnicalComments({TaskData? taskModel}) {
String previousComments = taskModel?.taskJobActivityEngineerTimers?.map((e) => e.comment?.trim()).where((comment) => comment != null && comment.isNotEmpty).join('\n') ?? '';
if (previousComments.isNotEmpty) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Divider().defaultStyle(context),
InfoHeader16Widget("Technical Comments".addTranslation),
Text(
previousComments,
style: AppTextStyles.bodyText2.copyWith(color: AppColor.lightTextColor(context)),
),
],
);
}
return const SizedBox();
}
Widget linkWithLocationWidget({required TaskData taskModel}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
InfoHeader16Widget("Location Details".addTranslation),
InfoTextWidget(label: context.translation.site, value: taskModel.site?.name ?? "-"),
InfoTextWidget(label: context.translation.building, value: taskModel.building?.name ?? "-"),
InfoTextWidget(label: context.translation.floor, value: taskModel.floor?.name ?? "-"),
InfoTextWidget(label: context.translation.department, value: taskModel.department?.name ?? "-"),
InfoTextWidget(label: context.translation.room, value: taskModel.room?.name ?? "-"),
],
);
}
Widget assetDetails({required TaskData taskModel}) {
return taskModel.asset != null
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
InfoHeader16Widget(context.translation.assetDetails),
InfoTextWidget(label: context.translation.assetName, value: taskModel.asset?.assetName?.cleanupWhitespace.capitalizeFirstOfEach ?? "-"),
InfoTextWidget(label: context.translation.assetNo, value: taskModel.asset?.assetNumber ?? "-"),
InfoTextWidget(label: context.translation.manufacture, value: taskModel.asset?.manufacturer ?? "-"),
InfoTextWidget(label: context.translation.model, value: taskModel.asset?.model ?? "-"),
InfoTextWidget(label: context.translation.serialNo, value: taskModel.asset?.serialNo ?? "-"),
InfoTextWidget(label: context.translation.assetType, value: taskModel.asset?.assetTypeName ?? "-"),
InfoTextWidget(label: context.translation.site, value: taskModel.asset?.siteName ?? "-"),
InfoTextWidget(label: context.translation.building, value: taskModel.asset?.buildingName ?? "-"),
InfoTextWidget(label: context.translation.floor, value: taskModel.asset?.floorName ?? "-"),
InfoTextWidget(label: context.translation.department, value: taskModel.asset?.departmentName ?? "-"),
InfoTextWidget(label: context.translation.room, value: taskModel.asset?.roomName ?? "-"),
InfoTextWidget(label: context.translation.supplier, value: taskModel.asset?.supplierName ?? "-"),
InfoTextWidget(label: 'PO Number', value: taskModel.asset?.poNumber ?? "-"),
InfoTextWidget(label: context.translation.installationDate, value: taskModel.asset?.installationDate?.toAssetDetailsFormat ?? "-"),
],
)
: const SizedBox();
}
Widget recallAlertTypeWidget({required TaskData taskModel}) {
if (taskModel.typeOfAlert != null) {
switch (taskModel.typeOfAlert!.value) {
case 1:
return InfoTextWidget(label: context.translation.acknowledge, value: taskModel.isUserAcknowledge?.toString() ?? "-");
case 2:
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InfoTextWidget(label: context.translation.completedActions, value: taskModel.actionNeeded?.name ?? "-"),
if (taskModel.actionNeeded?.value == 1) ...[
InfoTextWidget(label: context.translation.impactStatus, value: taskModel.impactStatus?.name ?? "-"),
],
],
);
case 3:
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InfoTextWidget(label: context.translation.acknowledge, value: taskModel.isUserAcknowledge?.toString() ?? "-"),
InfoTextWidget(label: context.translation.completedActions, value: taskModel.actionNeeded?.name ?? "-"),
if (taskModel.actionNeeded?.value == 1) ...[
'${context.translation.impactStatus}: ${taskModel.impactStatus?.name ?? "-"}'.bodyText(context),
],
],
);
default:
return const SizedBox();
}
}
return const SizedBox();
}
}