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 { UserProvider? userProvider; TaskRequestProvider? taskProvider; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { getTaskData(); }); } @override void dispose() { super.dispose(); } Future getTaskData() async { taskProvider = Provider.of(context, listen: false); await taskProvider?.getTaskById(id: widget.taskId); } @override Widget build(BuildContext context) { userProvider ??= Provider.of(context, listen: false); return Scaffold( appBar: DefaultAppBar( title: context.translation.taskRequest, actions: [ if (context.userProvider.isEngineer || context.userProvider.isNurse) Selector( 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(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 timer) { if (context.userProvider.user!.type == UsersTypes.normal_user) return const SizedBox(); double totalWorkingHours = timer.fold(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), WidgetSpan( child: Icon(Icons.info_outline_rounded, color: AppColor.primary10, size: 14), ), ], ), ).onPress(() => showModalBottomSheet( context: context, useSafeArea: true, isScrollControlled: true, isDismissible: true, backgroundColor: AppColor.bottomSheetColor(context), builder: (BuildContext context) => TotalWorkingTimeDetailBottomSheet( timerList: timer.map((e) { return TimerHistoryModel( id: e.id, startTime: e.startDate, endTime: e.endDate, workingHours: e.totalWorkingHour, ); }).toList()) .bottomSheetContainer(context), )); } 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(); } }