From 4f9f76515d53c0942bab3e0fb9ccac1c56fb319b Mon Sep 17 00:00:00 2001 From: WaseemAbbasi22 Date: Thu, 8 Jan 2026 10:20:15 +0300 Subject: [PATCH 1/6] added form design for installation and pullout, handle pullout type task and other improvements --- .../task_request/task_request_model.dart | 83 ++-------- lib/modules/cm_module/cm_detail_page.dart | 8 +- lib/modules/cx_module/chat/chat_widget.dart | 6 +- .../loan_module/models/loan_form_model.dart | 2 - .../loan_installation_pullout_form_model.dart | 25 +++ .../pages/create_loan_request_page.dart | 17 +- .../pages/installation_pullout_form_view.dart | 152 ++++++++++++++++++ .../pages/loan_equipment_detail_page.dart | 103 ++++++------ .../tasks/task_request_detail_view.dart | 2 +- .../tasks/update_task_request_view.dart | 47 +++--- .../task_request_provider.dart | 15 -- 11 files changed, 296 insertions(+), 164 deletions(-) create mode 100644 lib/modules/loan_module/models/loan_installation_pullout_form_model.dart create mode 100644 lib/modules/loan_module/pages/installation_pullout_form_view.dart diff --git a/lib/models/new_models/task_request/task_request_model.dart b/lib/models/new_models/task_request/task_request_model.dart index d1b6dc1a..6d7c9956 100644 --- a/lib/models/new_models/task_request/task_request_model.dart +++ b/lib/models/new_models/task_request/task_request_model.dart @@ -1,3 +1,5 @@ +import 'dart:typed_data'; + import 'package:test_sa/models/base.dart'; import 'package:test_sa/models/lookup.dart'; import 'package:test_sa/models/new_models/assistant_employee.dart'; @@ -83,6 +85,7 @@ class TaskData { String? estimationDeliveryDate; String? reasonOfFSCA; String? correctiveActionDescription; + String? signature; TaskContactUser? evaluatorUser; List? taskJobActivityEngineerTimers = []; TimerModel? taskTimerModel = TimerModel(); @@ -126,6 +129,7 @@ class TaskData { this.alertNo, this.estimationDeliveryDate, this.reasonOfFSCA, + this.signature, this.correctiveActionDescription, this.evaluatorUser, this.taskTimePicker}); @@ -193,6 +197,7 @@ class TaskData { alertNo = json['alertNo']; estimationDeliveryDate = json['estimationDeliveryDate']; reasonOfFSCA = json['reasonOfFSCA']; + signature = json['signature']; correctiveActionDescription = json['correctiveActionDescription']; evaluatorUser = json['evaluatorUser'] != null ? TaskContactUser.fromJson(json['evaluatorUser']) : null; site = json["site"] == null ? null : Site.fromJson(json["site"]); @@ -239,7 +244,7 @@ class TaskData { floor = taskData.floor; department = taskData.department; room = taskData.room; - + signature = taskData.signature; taskTimerModel = taskData.taskTimerModel; totalWorkingHours = taskData.totalWorkingHours; timerModelList = taskData.timerModelList; @@ -350,79 +355,6 @@ class TaskData { return data; } - // Map toJson() { - // final Map data = {}; - // - // data['id'] = id; - // data['statusValue'] = statusValue; - // data['taskJobNo'] = taskJobNo; - // - // if (userCreated != null) { - // data['userCreated'] = userCreated!.toJson(); - // } - // - // if (taskJobContactPersons != null) { - // data['taskJobContactPersons'] = taskJobContactPersons!.map((e) => e.toJson()).toList(); - // } - // - // if (taskType != null) { - // data['taskType'] = taskType!.toJson(); - // } - // - // if (taskJobStatus != null) { - // data['taskJobStatus'] = taskJobStatus!.toJson(); - // } - // - // data['asset'] = asset?.toJson(); - // data['site'] = site; - // data['building'] = building; - // data['floor'] = floor; - // data['department'] = department; - // data['room'] = room; - // data['callComment'] = callComment; - // - // if (assignedEngineer != null) { - // data['assignedEngineer'] = assignedEngineer!.toJson(); - // } - // - // if (taskJobAssistantEmployees != null) { - // data['taskJobAssistantEmployees'] = taskJobAssistantEmployees!.map((v) => v.toJson()).toList(); - // } - // if (taskJobHistories != null) { - // data['taskJobHistories'] = taskJobHistories!.map((e) => e.toJson()).toList(); - // } - // if (installationBuilding != null) { - // data['installationBuilding'] = installationBuilding!.toJson(); - // } - // data['installationFloor'] = installationFloor; - // data['installationDepartment'] = installationDepartment; - // data['serialNo'] = serialNo; - // data['installationDate'] = installationDate; - // data['completedAction'] = completedAction; - // data['impactStatus'] = impactStatus; - // data['isUserAcknowledge'] = isUserAcknowledge; - // data['typeOfAlert'] = typeOfAlert; - // data['riskLevel'] = riskLevel; - // data['resource'] = resource; - // data['actionNeeded'] = actionNeeded; - // data['alertNo'] = alertNo; - // data['estimationDeliveryDate'] = estimationDeliveryDate; - // data['reasonOfFSCA'] = reasonOfFSCA; - // data['correctiveActionDescription'] = correctiveActionDescription; - // - // if (evaluatorUser != null) { - // data['evaluatorUser'] = evaluatorUser!.toJson(); - // } - // if (taskJobActivityEngineerTimers != null) { - // data['taskJobActivityEngineerTimers'] = taskJobActivityEngineerTimers!.map((e) => e.toJson()).toList(); - // } - // if (taskJobAttachments != null) { - // data['taskJobAttachments'] = taskJobAttachments?.map((e) => e.toJson()).toList(); - // } - // - // return data; - // } - Map toEngineerUpdateJson() { final Map data = {}; data['id'] = id; @@ -449,6 +381,9 @@ class TaskData { } data['impactStatusId'] = impactStatus?.id; data['isUserAcknowledge'] = isUserAcknowledge; + if (signature != null) { + data['signature'] = signature; + } return data; } } diff --git a/lib/modules/cm_module/cm_detail_page.dart b/lib/modules/cm_module/cm_detail_page.dart index d396e598..997ff44e 100644 --- a/lib/modules/cm_module/cm_detail_page.dart +++ b/lib/modules/cm_module/cm_detail_page.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -99,6 +101,7 @@ class _CMDetailPageState extends State { CMDetailProvider provider = Provider.of(context, listen: false); int? statusValue = provider.currentWorkOrder?.data?.status?.value; if (statusValue == 2 || statusValue == 3 || statusValue == 5 || statusValue == 6) { + //Need to refactor bad approach calling api inside build getChatToken(moduleId, provider.currentWorkOrder?.data?.workOrderNo ?? ""); return Consumer(builder: (pContext, requestProvider, _) { return IconButton( @@ -135,8 +138,11 @@ class _CMDetailPageState extends State { return const SizedBox(); } else { CMDetailProvider provider = Provider.of(context, listen: false); + //TODO need to verify the check with backend workOrderNextStepEnum int? statusValue = provider.currentWorkOrder?.data?.status?.value; - if (statusValue == 2) { + WorkOrderNextStepEnum? workOrderNextStepEnum = provider.currentWorkOrder?.data?.nextStep?.workOrderNextStepEnum; + log('next step ${workOrderNextStepEnum.toString()}'); + if (statusValue == 2 && workOrderNextStepEnum==WorkOrderNextStepEnum.activity) { return Container( padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6), margin: const EdgeInsets.only(left: 2), diff --git a/lib/modules/cx_module/chat/chat_widget.dart b/lib/modules/cx_module/chat/chat_widget.dart index 4d28bd1b..4924bbf3 100644 --- a/lib/modules/cx_module/chat/chat_widget.dart +++ b/lib/modules/cx_module/chat/chat_widget.dart @@ -40,8 +40,10 @@ class _ChatWidgetState extends State { @override void initState() { super.initState(); - Provider.of(context, listen: false).reset(); - getChatToken(); + WidgetsBinding.instance.addPostFrameCallback((_) { + Provider.of(context, listen: false).reset(); + getChatToken(); + }); } void getChatToken() { diff --git a/lib/modules/loan_module/models/loan_form_model.dart b/lib/modules/loan_module/models/loan_form_model.dart index 79d459ca..d594432c 100644 --- a/lib/modules/loan_module/models/loan_form_model.dart +++ b/lib/modules/loan_module/models/loan_form_model.dart @@ -109,9 +109,7 @@ class LoanAttachments { num? loanId; String? attachmentName; String? attachmentDescription; - LoanAttachments({this.id, this.loanAttachmentTypeId, this.attachmentName, this.loanId, this.attachmentDescription}); - LoanAttachments.fromJson(dynamic json) { id = json['id']; loanAttachmentTypeId = json['loanAttachmentTypeId']; diff --git a/lib/modules/loan_module/models/loan_installation_pullout_form_model.dart b/lib/modules/loan_module/models/loan_installation_pullout_form_model.dart new file mode 100644 index 00000000..11b48644 --- /dev/null +++ b/lib/modules/loan_module/models/loan_installation_pullout_form_model.dart @@ -0,0 +1,25 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:test_sa/modules/loan_module/models/loan_form_model.dart'; + +class LoanInstallationPullOutFormModel { + String? snNo; + DateTime? date; + Uint8List? signature; + List? loanAttachment; + + LoanInstallationPullOutFormModel({ + this.loanAttachment, + }); + + Map toJson() { + //Need to check payload parm they need + return { + 'snNo': snNo, + 'InstallationDate': date?.toIso8601String(), + "loanAttachments": loanAttachment != null ? loanAttachment!.map((v) => v.toJson()).toList() : [], + "signature": signature != null ? "${DateTime.now().toIso8601String()}.png|${base64Encode(signature!)}" : null, + }; + } +} diff --git a/lib/modules/loan_module/pages/create_loan_request_page.dart b/lib/modules/loan_module/pages/create_loan_request_page.dart index 32616761..a6f39c0f 100644 --- a/lib/modules/loan_module/pages/create_loan_request_page.dart +++ b/lib/modules/loan_module/pages/create_loan_request_page.dart @@ -61,6 +61,15 @@ class _CreateLoanRequestPageState extends State with Tick Provider.of(context, listen: false).reset(); Provider.of(context, listen: false).reset(); loanTypes = [Lookup(id: 0, name: "Standard", value: 1), Lookup(id: 1, name: "Backup", value: 2)]; + + if(isBackUpRequest){ + fillSiteDeptInfo(); + } + } + + void fillSiteDeptInfo (){ + _loanFormModel.mappedSite = MappedSite.fromJson(widget.workOrder!.site!.toJson()); + _loanFormModel.mappedDepartment = TrafDepartment.fromJson(widget.workOrder!.department!.toJson()); } @override @@ -94,7 +103,7 @@ class _CreateLoanRequestPageState extends State with Tick 'Asset Information'.addTranslation.bodyText(context).custom(color: AppColor.black10), 8.height, Text( - '${context.translation.assetName}: ${widget.workOrder!.assetNdModel!.name?.cleanupWhitespace?.capitalizeFirstOfEach}', + '${context.translation.assetName}: ${widget.workOrder!.assetNdModel!.name?.cleanupWhitespace.capitalizeFirstOfEach}', style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), ), Text( @@ -106,11 +115,11 @@ class _CreateLoanRequestPageState extends State with Tick style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), ), Text( - '${context.translation.model}: ${widget.workOrder!.model?.name!.cleanupWhitespace?.capitalizeFirstOfEach}', + '${context.translation.model}: ${widget.workOrder!.model?.name!.cleanupWhitespace.capitalizeFirstOfEach}', style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), ), Text( - '${context.translation.manufacture}: ${widget.workOrder!.manufacturer?.name?.cleanupWhitespace?.capitalizeFirstOfEach}', + '${context.translation.manufacture}: ${widget.workOrder!.manufacturer?.name?.cleanupWhitespace.capitalizeFirstOfEach}', style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), ), 16.height, @@ -274,6 +283,7 @@ class _CreateLoanRequestPageState extends State with Tick title: context.translation.site, initialValue: _loanFormModel.mappedSite, showShadow: false, + enabled: !isBackUpRequest, validator: (value) { if (value == null) return "Please select a site"; return null; @@ -295,6 +305,7 @@ class _CreateLoanRequestPageState extends State with Tick context: context, title: context.translation.department, showShadow: false, + enabled: !isBackUpRequest, validator: (value) { if (value == null) return "Please select a department"; return null; diff --git a/lib/modules/loan_module/pages/installation_pullout_form_view.dart b/lib/modules/loan_module/pages/installation_pullout_form_view.dart new file mode 100644 index 00000000..a0379ee2 --- /dev/null +++ b/lib/modules/loan_module/pages/installation_pullout_form_view.dart @@ -0,0 +1,152 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter/services.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/models/generic_attachment_model.dart'; +import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; +import 'package:test_sa/modules/loan_module/models/loan_installation_pullout_form_model.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; +import 'package:test_sa/new_views/common_widgets/app_text_form_field.dart'; +import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; +import 'package:test_sa/views/widgets/date_and_time/date_picker.dart'; +import 'package:test_sa/views/widgets/e_signature/e_signature.dart'; +import 'package:test_sa/views/widgets/images/multi_image_picker.dart'; + +class InstallationPullOutFormView extends StatefulWidget { + final bool isPullout; + const InstallationPullOutFormView({super.key,required this.isPullout}); + + @override + State createState() => _InstallationPullOutFormViewState(); +} + +class _InstallationPullOutFormViewState extends State { + final _formKey = GlobalKey(); + final _snController = TextEditingController(); + List _attachments = []; + final _formData = LoanInstallationPullOutFormModel(); + + @override + void dispose() { + _snController.dispose(); + super.dispose(); + } + + void _pickInstallationDate(DateTime date) async { + final time = await showTimePicker( + context: context, + initialTime: TimeOfDay.now(), + ); + if (time == null) return; + setState(() { + _formData.date = DateTime( + date.year, + date.month, + date.day, + time.hour, + time.minute, + ); + }); + } + + void _onSignatureChanged(Uint8List signature) { + if (signature.isEmpty) return; + setState(() { + _formData.signature = signature; + }); + } + + void _submitForm() { + if (!_formKey.currentState!.validate()) return; + //Validation if required . + + // if (_formData.date == null) { + // context.showSnackBar('Please select installation date'); + // return; + // } + // + // if (_formData.signature == null || _formData.signature!.isEmpty) { + // context.showSnackBar('Signature is required'); + // return; + // } + + _formData.snNo = _snController.text; + + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: DefaultAppBar(title: 'Installation Report'.addTranslation), + body: SafeArea( + child: Form( + key: _formKey, + child: Column( + children: [ + SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if(widget.isPullout==false)...[ + AppTextFormField( + controller: _snController, + labelText: context.translation.snNo, + backgroundColor: AppColor.fieldBgColor(context), + textAlign: TextAlign.center, + labelStyle: AppTextStyles.textFieldLabelStyle, + hintStyle: AppTextStyles.textFieldLabelStyle, + textInputType: TextInputType.text, + textInputAction: TextInputAction.next, + showShadow: false, + validator: (v) => + (v == null || v.isEmpty) ? 'Required' : null, + ), + 8.height, + ], + ADatePicker( + label: widget.isPullout?"Installation Date".addTranslation:"Installation Date".addTranslation, + hideShadow: true, + backgroundColor: AppColor.fieldBgColor(context), + to: DateTime.now().add(const Duration(days: 365)), + formatDateWithTime: true, + onDatePicker: _pickInstallationDate, + ), + 8.height, + ESignature( + title: 'End-user signature'.addTranslation, + backgroundColor: AppColor.fieldBgColor(context), + showShadow: false, + oldSignature:'', + newSignature: _formData.signature, + onChange: _onSignatureChanged, + ), + 24.height, + AttachmentPicker( + label: context.translation.attachments, + attachment: _attachments, + ), + ], + ).toShadowContainer(context, borderRadius: 20), + ).expanded, + FooterActionButton.footerContainer( + context: context, + child: AppFilledButton( + buttonColor: AppColor.primary10, + label: context.translation.submitRequest, + onPressed: _submitForm, + ), + ), + ], + ), + ), + ), + ); + } +} + diff --git a/lib/modules/loan_module/pages/loan_equipment_detail_page.dart b/lib/modules/loan_module/pages/loan_equipment_detail_page.dart index f9ed2c0e..1878a1fe 100644 --- a/lib/modules/loan_module/pages/loan_equipment_detail_page.dart +++ b/lib/modules/loan_module/pages/loan_equipment_detail_page.dart @@ -1,5 +1,3 @@ -import 'dart:developer'; - import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:test_sa/controllers/api_routes/urls.dart'; @@ -9,23 +7,17 @@ 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/modules/cm_module/views/components/action_button/footer_action_button.dart'; -import 'package:test_sa/modules/internal_audit_module/models/internal_audit_attachment_model.dart'; -import 'package:test_sa/modules/internal_audit_module/models/system_internal_audit_data_model.dart'; -import 'package:test_sa/modules/internal_audit_module/pages/equipment_internal_audit/update_equipment_internal_audit_page.dart'; -import 'package:test_sa/modules/internal_audit_module/pages/system_internal_audit/update_system_internal_audit_page.dart'; -import 'package:test_sa/modules/internal_audit_module/provider/internal_audit_provider.dart'; import 'package:test_sa/modules/loan_module/models/loan_attachment_model.dart'; import 'package:test_sa/modules/loan_module/provider/loan_provider.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; import 'package:test_sa/views/widgets/images/files_list.dart'; - -import 'package:test_sa/views/widgets/loaders/app_loading.dart'; import 'package:test_sa/views/widgets/loaders/no_data_found.dart'; import 'package:test_sa/views/widgets/requests/request_status.dart'; import '../models/loan_request_model.dart'; +import 'installation_pullout_form_view.dart'; class LoanEquipmentDetailPage extends StatelessWidget { static const String id = "/loan-equipment-detail-page"; @@ -46,50 +38,69 @@ class LoanEquipmentDetailPage extends StatelessWidget { List allAttachments = snapshot.data!.loanAttachments!; - return ListView( - padding: const EdgeInsets.all(16), + return Column( children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, + ListView( + padding: const EdgeInsets.all(16), children: [ - Row( + Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - StatusLabel( - label: snapshot.data!.loanStatusName!, - textColor: AppColor.getRequestStatusTextColorByName(context, snapshot.data!.loanStatusName!), - backgroundColor: AppColor.getRequestStatusColorByName(context, snapshot.data!.loanStatusName!), - ), - 1.width.expanded, - Text( - snapshot.data!.createdDate?.toServiceRequestCardFormat ?? "-", - textAlign: TextAlign.end, - style: AppTextStyles.tinyFont.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral50), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + StatusLabel( + label: snapshot.data!.loanStatusName!, + textColor: AppColor.getRequestStatusTextColorByName(context, snapshot.data!.loanStatusName!), + backgroundColor: AppColor.getRequestStatusColorByName(context, snapshot.data!.loanStatusName!), + ), + 1.width.expanded, + Text( + snapshot.data!.createdDate?.toServiceRequestCardFormat ?? "-", + textAlign: TextAlign.end, + style: AppTextStyles.tinyFont.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral50), + ), + ], ), + 12.height, + ...requesterDetails(context, snapshot.data!), + 12.height, + ...requestDetails(context, snapshot.data!), + 12.height, + ...assetDetails(context, snapshot.data!), + 12.height, + ...installationDetails(context, snapshot.data!), + 12.height, + ...doctorDetails(context, snapshot.data!), + 12.height, + ...vendorDetails(context, snapshot.data!), + if (allAttachments.isNotEmpty) ...[ + const Divider().defaultStyle(context), + Text( + "Attachments".addTranslation, + style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + ), + FilesList(images: allAttachments.map((e) => URLs.getFileUrl(e.attachmentName ?? '') ?? '').toList() ?? []), + ], ], - ), - 12.height, - ...requesterDetails(context, snapshot.data!), - 12.height, - ...requestDetails(context, snapshot.data!), - 12.height, - ...assetDetails(context, snapshot.data!), - 12.height, - ...installationDetails(context, snapshot.data!), - 12.height, - ...doctorDetails(context, snapshot.data!), - 12.height, - ...vendorDetails(context, snapshot.data!), - if (allAttachments.isNotEmpty) ...[ - const Divider().defaultStyle(context), - Text( - "Attachments".addTranslation, - style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), - ), - FilesList(images: allAttachments.map((e) => URLs.getFileUrl(e.attachmentName ?? '') ?? '').toList() ?? []), - ], + ).toShadowContainer(context, padding: 12), ], - ).toShadowContainer(context, padding: 12), + ).expanded, + if (context.userProvider.isEngineer&&(snapshot.data?.loanStatusValue == 5 || snapshot.data?.loanStatusValue == 8)) + FooterActionButton.footerContainer( + context: context, + child: AppFilledButton( + onPressed: () async { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => InstallationPullOutFormView( + isPullout: snapshot.data?.loanStatusValue == 8, + ))); + }, + label: snapshot.data?.loanStatusValue == 5 ? 'Installation Report' : 'PullOut Report', + ), + ) ], ); }), diff --git a/lib/modules/tm_module/tasks/task_request_detail_view.dart b/lib/modules/tm_module/tasks/task_request_detail_view.dart index 91a75fc7..647b7a9b 100644 --- a/lib/modules/tm_module/tasks/task_request_detail_view.dart +++ b/lib/modules/tm_module/tasks/task_request_detail_view.dart @@ -47,10 +47,10 @@ class _TaskRequestDetailsViewState extends State { @override void initState() { + super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { getTaskData(); }); - super.initState(); } @override diff --git a/lib/modules/tm_module/tasks/update_task_request_view.dart b/lib/modules/tm_module/tasks/update_task_request_view.dart index 20b6148e..72fd8ab3 100644 --- a/lib/modules/tm_module/tasks/update_task_request_view.dart +++ b/lib/modules/tm_module/tasks/update_task_request_view.dart @@ -3,6 +3,7 @@ import 'dart:developer'; import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:provider/provider.dart'; import 'package:test_sa/controllers/api_routes/api_manager.dart'; @@ -35,6 +36,7 @@ import 'package:test_sa/providers/task_request_provider/task_job_provider.dart'; import 'package:test_sa/providers/task_request_provider/task_request_provider.dart'; import 'package:test_sa/views/app_style/sizing.dart'; import 'package:test_sa/views/widgets/date_and_time/date_picker.dart'; +import 'package:test_sa/views/widgets/e_signature/e_signature.dart'; import 'package:test_sa/views/widgets/images/multi_image_picker.dart'; import 'package:test_sa/views/widgets/loaders/app_loading.dart'; import 'package:test_sa/views/widgets/loaders/no_data_found.dart'; @@ -65,6 +67,7 @@ class _UpdateTaskRequestState extends State { String comments = ''; List timerList = []; double totalWorkingHours = 0; + Uint8List? newSignature; @override void initState() { @@ -152,12 +155,24 @@ class _UpdateTaskRequestState extends State { if (taskProvider.taskRequestModel?.taskType?.isRecallAndAlert == true) ...[ recallAlertTypeWidget(taskModel: taskProvider.taskRequestModel!), ], - // if (previousComments.isNotEmpty) ...[ - // 'Previous Comments'.bodyText2(context).custom(color: AppColor.neutral50), - // 8.height, + //Not good approach need to use enums .... + if (taskProvider.taskRequestModel?.taskType?.typeName == "PullOut") ...[ + ESignature( + title: "End-user signature", + backgroundColor: AppColor.fieldBgColor(context), + showShadow: false, + oldSignature: taskProvider.taskRequestModel?.signature, + newSignature: newSignature, + onSaved: (signature) { + newSignature = signature; + if (signature == null || signature.isEmpty) return; + taskProvider.taskRequestModel?.signature = "${DateTime.now().toIso8601String()}.png|${base64Encode(signature)}"; + }, + ), + 8.height, + ], buildPreviousComments(taskProvider: taskProvider), - // 8.height, - // ], + AppTextFormField( initialValue: "", labelText: context.translation.technicalComment, @@ -186,7 +201,6 @@ class _UpdateTaskRequestState extends State { TaskRequestAssistantEmployeeList( assetId: taskProvider.taskRequestModel?.asset?.id, createdDate: widget.createdDate ?? '', - //widget.requestDetails?.date assistantEmployeeList: taskProvider.taskRequestModel?.taskJobAssistantEmployees, cardPadding: 0, onListChanged: (updatedList) { @@ -233,15 +247,9 @@ class _UpdateTaskRequestState extends State { void _updateTask({required BuildContext context, required int status}) async { TaskRequestProvider taskRequestProvider = Provider.of(context, listen: false); - + _formKey.currentState?.save(); TaskData? taskModel = TaskData.copyWith(taskRequestProvider.taskRequestModel!); - taskModel?.statusValue = status; - - // taskModel.taskTimePicker = taskRequestProvider.taskRequestModel?.taskTimePicker; - // taskModel.totalWorkingHours = taskRequestProvider.taskRequestModel?.totalWorkingHours; - // taskModel.taskTimerModel = taskRequestProvider.taskRequestModel?.taskTimerModel; - // taskModel.timerModelList = taskRequestProvider.taskRequestModel?.timerModelList; - + taskModel.statusValue = status; if (validate(model: taskModel)) { showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading()); @@ -253,10 +261,10 @@ class _UpdateTaskRequestState extends State { String fileName = CMRequestUtils.isLocalUrl(item.name ?? '') ? ("${item.name ?? ''.split("/").last}|${base64Encode(File(item.name ?? '').readAsBytesSync())}") : item.name ?? ''; taskAttachment.add(TaskJobAttachment(id: item.id, name: fileName)); } - taskModel?.taskJobAttachments = taskAttachment; + taskModel.taskJobAttachments = taskAttachment; - if (taskModel?.taskTimePicker != null) { - int durationInSecond = taskModel!.taskTimePicker!.endAt!.difference(taskModel.taskTimePicker!.startAt!).inSeconds; + if (taskModel.taskTimePicker != null) { + int durationInSecond = taskModel.taskTimePicker!.endAt!.difference(taskModel.taskTimePicker!.startAt!).inSeconds; taskModel.taskJobActivityEngineerTimers?.add( TaskJobActivityEngineerTimer( id: 0, @@ -267,11 +275,10 @@ class _UpdateTaskRequestState extends State { ), ); } - - taskModel?.timerModelList?.forEach((timer) { + taskModel.timerModelList?.forEach((timer) { int durationInSecond = timer.endAt!.difference(timer.startAt!).inSeconds; taskModel.taskJobActivityEngineerTimers?.add( - TaskJobActivityEngineerTimer ( + TaskJobActivityEngineerTimer( id: 0, startDate: timer.startAt!.toIso8601String(), endDate: timer.endAt?.toIso8601String(), totalWorkingHour: ((durationInSecond) / 60 / 60), comment: timer.comments ?? comments), ); }); diff --git a/lib/providers/task_request_provider/task_request_provider.dart b/lib/providers/task_request_provider/task_request_provider.dart index 1a517d73..72aeb1e9 100644 --- a/lib/providers/task_request_provider/task_request_provider.dart +++ b/lib/providers/task_request_provider/task_request_provider.dart @@ -6,28 +6,13 @@ import 'package:fluttertoast/fluttertoast.dart'; import 'package:http/http.dart'; import 'package:test_sa/controllers/api_routes/api_manager.dart'; import 'package:test_sa/controllers/api_routes/urls.dart'; -import 'package:test_sa/controllers/providers/api/hospitals_provider.dart'; import 'package:test_sa/extensions/context_extension.dart'; -import 'package:test_sa/extensions/string_extensions.dart'; -import 'package:test_sa/models/lookup.dart'; import 'package:test_sa/models/new_models/building.dart'; import 'package:test_sa/models/new_models/department.dart'; import 'package:test_sa/models/new_models/floor.dart'; import 'package:test_sa/models/new_models/site.dart'; import 'package:test_sa/models/new_models/task_request/task_request_model.dart'; -import 'package:test_sa/models/new_models/task_request/task_type_model.dart'; -import 'package:test_sa/models/service_request/pending_service_request_model.dart'; -import 'package:test_sa/models/service_request/service_report.dart'; -import 'package:test_sa/models/service_request/service_request.dart'; -import 'package:test_sa/models/service_request/service_request_search.dart'; -import 'package:test_sa/models/service_request/spare_parts.dart'; -import 'package:test_sa/models/service_request/supp_engineer_work_orders.dart'; -import 'package:test_sa/models/service_request/supplier_engineer_model.dart'; import 'package:test_sa/models/timer_model.dart'; - -import '../../models/service_request/search_work_order.dart'; -import '../../models/service_request/wo_call_request.dart'; -import '../../models/user.dart'; import '../../new_views/common_widgets/app_lazy_loading.dart'; class TaskRequestProvider extends ChangeNotifier { From bfb2685c55e5d8915325d931f0d958101f9caef9 Mon Sep 17 00:00:00 2001 From: WaseemAbbasi22 Date: Sun, 11 Jan 2026 14:31:50 +0300 Subject: [PATCH 2/6] added form design for installation and pullout, handle pullout type task and other improvements --- .../loan_installation_pullout_form_model.dart | 15 ++++ .../pages/installation_pullout_form_view.dart | 51 +++++++++-- .../pages/intallation_details_view.dart | 87 +++++++++++++++++++ .../pages/loan_equipment_detail_page.dart | 19 ++-- .../pages/pullout_detail_page.dart | 80 +++++++++++++++++ 5 files changed, 237 insertions(+), 15 deletions(-) create mode 100644 lib/modules/loan_module/pages/intallation_details_view.dart create mode 100644 lib/modules/loan_module/pages/pullout_detail_page.dart diff --git a/lib/modules/loan_module/models/loan_installation_pullout_form_model.dart b/lib/modules/loan_module/models/loan_installation_pullout_form_model.dart index 11b48644..bcbbff31 100644 --- a/lib/modules/loan_module/models/loan_installation_pullout_form_model.dart +++ b/lib/modules/loan_module/models/loan_installation_pullout_form_model.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'dart:typed_data'; +import 'package:test_sa/models/timer_model.dart'; import 'package:test_sa/modules/loan_module/models/loan_form_model.dart'; class LoanInstallationPullOutFormModel { @@ -8,9 +9,20 @@ class LoanInstallationPullOutFormModel { DateTime? date; Uint8List? signature; List? loanAttachment; + TimerModel? timerModel = TimerModel(); + DateTime? startTime; + DateTime? endTime; + double? totalHours; LoanInstallationPullOutFormModel({ this.loanAttachment, + this.timerModel, + this.snNo, + this.signature, + this.date, + this.startTime, + this.endTime, + this.totalHours, }); Map toJson() { @@ -20,6 +32,9 @@ class LoanInstallationPullOutFormModel { 'InstallationDate': date?.toIso8601String(), "loanAttachments": loanAttachment != null ? loanAttachment!.map((v) => v.toJson()).toList() : [], "signature": signature != null ? "${DateTime.now().toIso8601String()}.png|${base64Encode(signature!)}" : null, + 'startTime': startTime?.toIso8601String(), + 'endTime': endTime?.toIso8601String(), + 'totalHours': totalHours, }; } } diff --git a/lib/modules/loan_module/pages/installation_pullout_form_view.dart b/lib/modules/loan_module/pages/installation_pullout_form_view.dart index a0379ee2..11a3cbfd 100644 --- a/lib/modules/loan_module/pages/installation_pullout_form_view.dart +++ b/lib/modules/loan_module/pages/installation_pullout_form_view.dart @@ -1,3 +1,4 @@ +import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -7,6 +8,7 @@ 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/models/generic_attachment_model.dart'; +import 'package:test_sa/models/timer_model.dart'; import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; import 'package:test_sa/modules/loan_module/models/loan_installation_pullout_form_model.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; @@ -16,10 +18,13 @@ import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; import 'package:test_sa/views/widgets/date_and_time/date_picker.dart'; import 'package:test_sa/views/widgets/e_signature/e_signature.dart'; import 'package:test_sa/views/widgets/images/multi_image_picker.dart'; +import 'package:test_sa/views/widgets/timer/app_timer.dart'; class InstallationPullOutFormView extends StatefulWidget { final bool isPullout; - const InstallationPullOutFormView({super.key,required this.isPullout}); + final String? createdDate; + + const InstallationPullOutFormView({super.key, required this.isPullout, this.createdDate}); @override State createState() => _InstallationPullOutFormViewState(); @@ -76,13 +81,24 @@ class _InstallationPullOutFormViewState extends State - (v == null || v.isEmpty) ? 'Required' : null, + validator: (v) => (v == null || v.isEmpty) ? 'Required' : null, ), 8.height, ], ADatePicker( - label: widget.isPullout?"Installation Date".addTranslation:"Installation Date".addTranslation, + label: !widget.isPullout ? "Installation Date".addTranslation : "Validation Date".addTranslation, hideShadow: true, backgroundColor: AppColor.fieldBgColor(context), to: DateTime.now().add(const Duration(days: 365)), @@ -118,11 +133,32 @@ class _InstallationPullOutFormViewState extends State URLs.getFileUrl(e.attachmentName ?? '') ?? '').toList() ?? []), + ], + ], + ).toShadowContainer(context, borderRadius: 20), + ); + } + + Widget detailContent({required BuildContext context, required String label, required String heading}) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + heading.bodyText(context).custom(color: AppColor.neutral120), + 6.height, + //TODO Need to check which value need to show here + label.bodyText(context).custom(color: AppColor.neutral120), + ], + ); + } +} diff --git a/lib/modules/loan_module/pages/loan_equipment_detail_page.dart b/lib/modules/loan_module/pages/loan_equipment_detail_page.dart index 1878a1fe..00dc04eb 100644 --- a/lib/modules/loan_module/pages/loan_equipment_detail_page.dart +++ b/lib/modules/loan_module/pages/loan_equipment_detail_page.dart @@ -8,6 +8,7 @@ import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; import 'package:test_sa/modules/loan_module/models/loan_attachment_model.dart'; +import 'package:test_sa/modules/loan_module/pages/pullout_detail_page.dart'; import 'package:test_sa/modules/loan_module/provider/loan_provider.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; @@ -86,17 +87,21 @@ class LoanEquipmentDetailPage extends StatelessWidget { ).toShadowContainer(context, padding: 12), ], ).expanded, - if (context.userProvider.isEngineer&&(snapshot.data?.loanStatusValue == 5 || snapshot.data?.loanStatusValue == 8)) + if (context.userProvider.isEngineer && (snapshot.data?.loanStatusValue == 5 || snapshot.data?.loanStatusValue == 8)) FooterActionButton.footerContainer( context: context, child: AppFilledButton( onPressed: () async { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => InstallationPullOutFormView( - isPullout: snapshot.data?.loanStatusValue == 8, - ))); + if (snapshot.data?.loanStatusValue == 5) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => PullOutDetailsPage( + loanData: snapshot.data, + ))); + return; + } + Navigator.push(context, MaterialPageRoute(builder: (context) => const InstallationPullOutFormView(isPullout: false))); }, label: snapshot.data?.loanStatusValue == 5 ? 'Installation Report' : 'PullOut Report', ), diff --git a/lib/modules/loan_module/pages/pullout_detail_page.dart b/lib/modules/loan_module/pages/pullout_detail_page.dart new file mode 100644 index 00000000..09b81048 --- /dev/null +++ b/lib/modules/loan_module/pages/pullout_detail_page.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.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/modules/loan_module/models/loan_request_model.dart'; +import 'package:test_sa/modules/loan_module/pages/installation_pullout_form_view.dart'; +import 'package:test_sa/modules/loan_module/pages/intallation_details_view.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; + +class PullOutDetailsPage extends StatefulWidget { + final LoanRequestModel? loanData; + + PullOutDetailsPage({Key? key, required this.loanData}) : super(key: key); + + @override + _PullOutDetailsPageState createState() { + return _PullOutDetailsPageState(); + } +} + +class _PullOutDetailsPageState extends State { + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + appBar: DefaultAppBar( + title: 'Loan Request'.addTranslation, + ), + body: DefaultTabController( + length: 2, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + margin: EdgeInsets.only(left: 16.toScreenWidth, right: 16.toScreenWidth, top: 12.toScreenHeight), + decoration: BoxDecoration(color: context.isDark ? AppColor.neutral50 : AppColor.white10, borderRadius: BorderRadius.circular(10)), + child: TabBar( + padding: EdgeInsets.symmetric(vertical: 4.toScreenHeight, horizontal: 4.toScreenWidth), + labelColor: context.isDark ? AppColor.neutral30 : AppColor.black20, + unselectedLabelColor: context.isDark ? AppColor.neutral30 : AppColor.black20, + unselectedLabelStyle: AppTextStyles.bodyText, + labelStyle: AppTextStyles.bodyText, + indicatorPadding: EdgeInsets.zero, + indicatorSize: TabBarIndicatorSize.tab, + dividerColor: Colors.transparent, + indicator: BoxDecoration(color: context.isDark ? AppColor.neutral60 : AppColor.neutral110, borderRadius: BorderRadius.circular(7)), + onTap: (index) { + // setState(() {}); + }, + tabs: [ + Tab(text: 'Installation'.addTranslation, height: 57.toScreenHeight), + Tab(text: 'Pull Out'.addTranslation, height: 57.toScreenHeight), + ], + ), + ), + 12.height, + TabBarView( + children: [ + Align( + alignment: Alignment.topCenter, + child: InstallationDetailsView( + loanData: widget.loanData!, + ), + ), + const InstallationPullOutFormView(isPullout: true), + ], + ).expanded, + ], + ), + )); + } +} From afa165dc3dba78fde9fab09d4d4cb73c3cea59ea Mon Sep 17 00:00:00 2001 From: WaseemAbbasi22 Date: Tue, 27 Jan 2026 16:49:06 +0300 Subject: [PATCH 3/6] added vendor dropdown along with free text --- lib/controllers/api_routes/urls.dart | 8 +- .../loan_module/models/loan_form_model.dart | 9 ++ .../pages/create_loan_request_page.dart | 152 ++++++++++++------ 3 files changed, 120 insertions(+), 49 deletions(-) diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index 06d5bff2..2bbf315e 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -3,14 +3,14 @@ class URLs { static const String appReleaseBuildNumber = "32"; - static const host1 = "https://atomsm.hmg.com"; // production url + // static const host1 = "https://atomsm.hmg.com"; // production url // static const host1 = "https://atomsmdev.hmg.com"; // local DEV url - // static const host1 = "https://atomsmuat.hmg.com"; // local UAT url + static const host1 = "https://atomsmuat.hmg.com"; // local UAT url // static final String _baseUrl = "$_host/mobile"; // host local UAT - // static final String _baseUrl = "$_host/v2/mobile"; // new V2 apis + static final String _baseUrl = "$_host/v2/mobile"; // new V2 apis // static final String _baseUrl = "$_host/mobile"; // host local UAT and for internal audit dev - static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM + // static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM // static final String _baseUrl = "$_host/v5/mobile"; // v5 for data segregation static const String chatHubUrl = "https://apiderichat.hmg.com/chathub"; diff --git a/lib/modules/loan_module/models/loan_form_model.dart b/lib/modules/loan_module/models/loan_form_model.dart index d594432c..575dd147 100644 --- a/lib/modules/loan_module/models/loan_form_model.dart +++ b/lib/modules/loan_module/models/loan_form_model.dart @@ -2,6 +2,8 @@ import 'package:test_sa/models/lookup.dart'; import 'package:test_sa/models/new_models/mapped_sites.dart'; import 'package:test_sa/models/new_models/site.dart'; import 'package:test_sa/models/new_models/traf_department.dart'; +import 'package:test_sa/models/service_request/supp_engineer_work_orders.dart'; +import 'package:test_sa/models/service_request/supplier_details.dart'; import 'package:test_sa/modules/loan_module/models/medical_department_model.dart'; class LoanFormModel { @@ -12,12 +14,15 @@ class LoanFormModel { String? requestDescription; String? model; String? manufacturer; + bool isNewVendor; Lookup? loanProvided; String? vendorName; String? vendorRepresentativeName; String? vendorNumber; String? vendorEmail; Site? site; + SupplierDetails? supplier; + SuppEngineerWorkOrders? supEngineer; MedicalDepartmentModel? department; List? loanAttachment; MappedSite? mappedSite; @@ -25,6 +30,7 @@ class LoanFormModel { LoanFormModel({ this.docName, + this.isNewVendor=false, this.docNumber, this.docEmail, this.itemDescription, @@ -38,6 +44,8 @@ class LoanFormModel { this.vendorEmail, this.loanAttachment, this.site, + this.supplier, + this.supEngineer, this.department, this.mappedSite, this.mappedDepartment, @@ -94,6 +102,7 @@ class LoanFormModel { "vendorNumber": vendorNumber, "vendorContact": vendorNumber, "vendorEmail": vendorEmail, + "isNewVendor": isNewVendor, // 'siteId': site?.id, 'siteId': mappedSite?.id, // 'departmentId': department?.id, diff --git a/lib/modules/loan_module/pages/create_loan_request_page.dart b/lib/modules/loan_module/pages/create_loan_request_page.dart index a6f39c0f..a896526d 100644 --- a/lib/modules/loan_module/pages/create_loan_request_page.dart +++ b/lib/modules/loan_module/pages/create_loan_request_page.dart @@ -15,6 +15,8 @@ import 'package:test_sa/models/new_models/mapped_sites.dart'; import 'package:test_sa/models/new_models/task_request/task_type_model.dart'; import 'package:test_sa/models/new_models/traf_department.dart'; import 'package:test_sa/models/new_models/work_order_detail_model.dart'; +import 'package:test_sa/models/service_request/supp_engineer_work_orders.dart'; +import 'package:test_sa/models/service_request/supplier_details.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/loan_module/models/loan_form_model.dart'; @@ -28,6 +30,7 @@ import 'package:test_sa/new_views/common_widgets/single_item_drop_down_menu.dart import 'package:test_sa/providers/gas_request_providers/site_provider.dart'; import 'package:test_sa/providers/loading_list_notifier.dart'; import 'package:test_sa/providers/lookups/department_lookup_provider.dart'; +import 'package:test_sa/providers/work_order/vendor_provider.dart'; import 'package:test_sa/views/widgets/images/multi_image_picker.dart'; import '../../../../../../new_views/common_widgets/default_app_bar.dart'; @@ -47,9 +50,11 @@ class _CreateLoanRequestPageState extends State with Tick final GlobalKey _formKey = GlobalKey(); final GlobalKey _scaffoldKey = GlobalKey(); TaskType? selectedType; - final LoanFormModel _loanFormModel = LoanFormModel(); + final LoanFormModel _loanFormModel = LoanFormModel(isNewVendor: false); TextEditingController commentController = TextEditingController(); + TextEditingController vendorPhoneController = TextEditingController(); + TextEditingController vendorEmailController = TextEditingController(); bool get isBackUpRequest => widget.workOrderId != null; @@ -62,14 +67,21 @@ class _CreateLoanRequestPageState extends State with Tick Provider.of(context, listen: false).reset(); loanTypes = [Lookup(id: 0, name: "Standard", value: 1), Lookup(id: 1, name: "Backup", value: 2)]; - if(isBackUpRequest){ + if (isBackUpRequest) { fillSiteDeptInfo(); } } - void fillSiteDeptInfo (){ - _loanFormModel.mappedSite = MappedSite.fromJson(widget.workOrder!.site!.toJson()); - _loanFormModel.mappedDepartment = TrafDepartment.fromJson(widget.workOrder!.department!.toJson()); + @override + void dispose() { + vendorPhoneController.dispose(); + vendorEmailController.dispose(); + super.dispose(); + } + + void fillSiteDeptInfo() { + _loanFormModel.mappedSite = MappedSite.fromJson(widget.workOrder!.site!.toJson()); + _loanFormModel.mappedDepartment = TrafDepartment.fromJson(widget.workOrder!.department!.toJson()); } @override @@ -291,13 +303,12 @@ class _CreateLoanRequestPageState extends State with Tick backgroundColor: AppColor.fieldBgColor(context), showAsBottomSheet: true, onSelect: (value) { - if(value!=null){ + if (value != null) { _loanFormModel.mappedSite = value; _loanFormModel.mappedDepartment = null; - Provider.of(context,listen:false).getData(id:_loanFormModel.mappedSite?.id); + Provider.of(context, listen: false).getData(id: _loanFormModel.mappedSite?.id); setState(() {}); } - }, ), 8.height, @@ -430,44 +441,95 @@ class _CreateLoanRequestPageState extends State with Tick return [ 'Vendor Details'.addTranslation.bodyText(context).custom(color: AppColor.black10), 8.height, - AppTextFormField( - initialValue: "", - labelText: "Vendor Name", - validator: (value) { - if ((value ?? "").isEmpty) return "Mandatory"; - return null; - }, - style: Theme.of(context).textTheme.titleMedium, - backgroundColor: AppColor.fieldBgColor(context), - labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), - showShadow: false, - textInputType: TextInputType.name, - textInputAction: TextInputAction.next, - onChange: (value) { - _loanFormModel.vendorName = value; - }, - ), - 8.height, - AppTextFormField( - initialValue: "", - labelText: "Vendor Representative Name", - validator: (value) { - if ((value ?? "").isEmpty) return "Mandatory"; - return null; - }, - style: Theme.of(context).textTheme.titleMedium, - backgroundColor: AppColor.fieldBgColor(context), - labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), - showShadow: false, - textInputType: TextInputType.name, - textInputAction: TextInputAction.next, - onChange: (value) { - _loanFormModel.vendorRepresentativeName = value; - }, - ), + if (!_loanFormModel.isNewVendor) ...[ + SingleItemDropDownMenu( + context: context, + title: 'Vendor Name'.addTranslation, + backgroundColor: AppColor.fieldBgColor(context), + initialValue: _loanFormModel.supplier, + showAsBottomSheet: true, + validator: (value) { + if (value == null) return "Please select vendor name"; + return null; + }, + showShadow: false, + showCancel: true, + onSelect: (person) { + _loanFormModel.supplier = person; + _loanFormModel.vendorName = person?.name; + _loanFormModel.supEngineer = null; + _loanFormModel.vendorRepresentativeName = null; + vendorPhoneController.text = person?.contact ?? ''; + vendorEmailController.text = person?.email ?? ''; + setState(() {}); + }, + ), + 8.height, + SingleItemDropDownMenu( + context: context, + backgroundColor: _loanFormModel.supplier?.suppliername == null + ? context.isDark + ? AppColor.neutral20 + : AppColor.neutral40 + : AppColor.fieldBgColor(context), + title: "Representative Name".addTranslation, + showShadow: false, + validator: (value) { + if (value == null) return "Please select representative name"; + return null; + }, + enabled: _loanFormModel.supplier?.suppPersons?.isNotEmpty ?? false, + staticData: _loanFormModel.supplier?.suppPersons, + showAsBottomSheet: true, + initialValue: _loanFormModel.supEngineer == null ? null : SuppPersons.fromJson(_loanFormModel.supEngineer?.toJson()), + onSelect: (suppPerson) { + if (suppPerson != null) { + _loanFormModel.vendorRepresentativeName = suppPerson.name; + _loanFormModel.supEngineer = SuppEngineerWorkOrders.fromJson(suppPerson.toJson()); + setState(() {}); + } + }, + ), + ] else ...[ + AppTextFormField( + initialValue: "", + labelText: "Vendor Name", + validator: (value) { + if ((value ?? "").isEmpty) return "Mandatory"; + return null; + }, + style: Theme.of(context).textTheme.titleMedium, + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + showShadow: false, + textInputType: TextInputType.name, + textInputAction: TextInputAction.next, + onChange: (value) { + _loanFormModel.vendorName = value; + }, + ), + 8.height, + AppTextFormField( + initialValue: "", + labelText: "Vendor Representative Name", + validator: (value) { + if ((value ?? "").isEmpty) return "Mandatory"; + return null; + }, + style: Theme.of(context).textTheme.titleMedium, + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + showShadow: false, + textInputType: TextInputType.name, + textInputAction: TextInputAction.next, + onChange: (value) { + _loanFormModel.vendorRepresentativeName = value; + }, + ), + ], 8.height, AppTextFormField( - initialValue: "", + controller: vendorPhoneController, labelText: "Vendor Number", validator: (value) { if ((value ?? "").isEmpty) return "Mandatory"; @@ -485,7 +547,7 @@ class _CreateLoanRequestPageState extends State with Tick ), 8.height, AppTextFormField( - initialValue: "", + controller: vendorEmailController, labelText: "Vendor Email", validator: (value) { if ((value ?? "").isEmpty) { From 3d77a96fda98b4b12a237ad6e7d9751a110bdbf8 Mon Sep 17 00:00:00 2001 From: WaseemAbbasi22 Date: Wed, 28 Jan 2026 09:42:15 +0300 Subject: [PATCH 4/6] added vendor dropdown along with free text --- .../pages/create_loan_request_page.dart | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/lib/modules/loan_module/pages/create_loan_request_page.dart b/lib/modules/loan_module/pages/create_loan_request_page.dart index a896526d..b544eb99 100644 --- a/lib/modules/loan_module/pages/create_loan_request_page.dart +++ b/lib/modules/loan_module/pages/create_loan_request_page.dart @@ -439,7 +439,42 @@ class _CreateLoanRequestPageState extends State with Tick List vendorDetailsSection() { return [ - 'Vendor Details'.addTranslation.bodyText(context).custom(color: AppColor.black10), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + 'Vendor Details'.addTranslation.bodyText(context).custom(color: AppColor.black10), + Row( + children: [ + Container( + height: 24, + width: 24, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: AppColor.primary10, // or AppColor.blueStatus(context) + ), + alignment: Alignment.center, + child: Icon( + !_loanFormModel.isNewVendor? Icons.add:Icons.search, + color: Colors.white, + size: 16, + ), + ), + 4.width, + Text( + !_loanFormModel.isNewVendor ? "Add new vendor" : "Select existing vendor", + style: AppTextStyles.bodyText.copyWith( + color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, + // decoration: TextDecoration.underline, + ), + ), + ], + ).onPress(() async { + setState(() { + _loanFormModel.isNewVendor = !_loanFormModel.isNewVendor; + }); + }), + ], + ), 8.height, if (!_loanFormModel.isNewVendor) ...[ SingleItemDropDownMenu( From 8f2e9db0628ad0b15f303a4d3dba7b0da474d681 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Wed, 28 Jan 2026 15:22:01 +0300 Subject: [PATCH 5/6] existing loan check api added. --- lib/controllers/api_routes/urls.dart | 1 + lib/modules/cm_module/cm_detail_page.dart | 82 +++++++++++++++---- .../pages/create_loan_request_page.dart | 8 +- .../pages/loan_equipment_detail_page.dart | 38 ++++----- .../loan_module/provider/loan_provider.dart | 13 +++ 5 files changed, 103 insertions(+), 39 deletions(-) diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index 2bbf315e..f4bf299a 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -390,6 +390,7 @@ class URLs { static get getMedDepartmentBasedOnSite => "$_baseUrl/TRAFDataSource/GetDepartmentBasedOnSite"; static get getLoanById => '$_baseUrl/Loan/GetLoanById'; + static get getLoanByWorkOrderId => '$_baseUrl/Loan/GetLoanByWorkOrderId'; static get addLoan => '$_baseUrl/Loan/AddLoan'; } diff --git a/lib/modules/cm_module/cm_detail_page.dart b/lib/modules/cm_module/cm_detail_page.dart index 15b1b503..452f3138 100644 --- a/lib/modules/cm_module/cm_detail_page.dart +++ b/lib/modules/cm_module/cm_detail_page.dart @@ -6,6 +6,7 @@ import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/int_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/enums/user_types.dart'; import 'package:test_sa/models/enums/work_order_next_step.dart'; import 'package:test_sa/models/helper_data_models/workorder/work_order_helper_models.dart'; @@ -16,6 +17,8 @@ import 'package:test_sa/modules/cx_module/chat/chat_provider.dart'; import 'package:test_sa/modules/cx_module/chat/chat_widget.dart'; import 'package:test_sa/modules/cx_module/survey/survey_page.dart'; import 'package:test_sa/modules/loan_module/pages/create_loan_request_page.dart'; +import 'package:test_sa/modules/loan_module/pages/loan_equipment_detail_page.dart'; +import 'package:test_sa/modules/loan_module/provider/loan_provider.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; import 'package:test_sa/views/widgets/loaders/no_data_found.dart'; @@ -104,7 +107,9 @@ class _CMDetailPageState extends State { (provider.currentWorkOrder?.data?.workOrderContactPerson?.isNotEmpty ?? false)) { return ChatWidget( moduleId: widget.moduleId, - isShow: context.settingProvider.isUserFlowMedical && (context.userProvider.isEngineer || context.userProvider.isNurse) && provider.currentWorkOrder?.data?.assignedEmployee != null, + isShow: context.settingProvider.isUserFlowMedical && + (context.userProvider.isEngineer || context.userProvider.isNurse) && + provider.currentWorkOrder?.data?.assignedEmployee != null, isReadOnly: _requestProvider.isReadOnlyRequest, requestId: widget.requestId.toInt(), assigneeEmployeeNumber: provider.currentWorkOrder?.data?.assignedEmployee!.employeeId!, @@ -115,23 +120,64 @@ class _CMDetailPageState extends State { return const SizedBox(); } }), - isNurse - ? IconButton( - icon: 'qr'.toSvgAsset( - height: 24, - width: 24, - ), - onPressed: () { - ServiceRequestBottomSheet.getQRCodeBottomSheet(context: context); - }, - ) - : IconButton( - icon: const Icon(Icons.home), - onPressed: () { - // stopTimer(); - Navigator.pop(context); - }, - ), + if (isNurse) + IconButton( + icon: 'qr'.toSvgAsset(height: 24, width: 24), + onPressed: () { + ServiceRequestBottomSheet.getQRCodeBottomSheet(context: context); + }, + ) + else ...[ + Selector( + selector: (_, myModel) => myModel.isLoading, // Selects only the userName + builder: (_, isLoading, __) { + if (isLoading) { + return const SizedBox(); + } else { + CMDetailProvider provider = Provider.of(context, listen: false); + //TODO need to verify the check with backend workOrderNextStepEnum + int? statusValue = provider.currentWorkOrder?.data?.status?.value; + WorkOrderNextStepEnum? workOrderNextStepEnum = provider.currentWorkOrder?.data?.nextStep?.workOrderNextStepEnum; + if (statusValue == 2 && workOrderNextStepEnum == WorkOrderNextStepEnum.activity) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6), + margin: const EdgeInsets.only(left: 2), + decoration: BoxDecoration(color: context.isDark ? AppColor.neutral50 : AppColor.neutral30, borderRadius: BorderRadius.circular(16)), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.add, size: 20, color: AppColor.black1E), + Text( + "Loan", + style: AppTextStyles.bodyText2.copyWith(color: AppColor.black1E), + ), + 4.width, + ], + ), + ).onPress(() async { + LoanProvider loanProvider = Provider.of(context, listen: false); + Utils.showLoading(context); + var data = await loanProvider.getLoanByWorkOrderId(widget.requestId); + Utils.hideLoading(context); + if (data == null) { + CMDetailProvider cmProvider = Provider.of(context, listen: false); + Navigator.push(context, MaterialPageRoute(builder: (context) => CreateLoanRequestPage(workOrderId: widget.requestId, workOrder: cmProvider.currentWorkOrder!.data))); + } else { + Navigator.push(context, MaterialPageRoute(builder: (context) => LoanEquipmentDetailPage(loanId: data.id!))); + } + }); + } + return const SizedBox(); + } + }), + IconButton( + icon: const Icon(Icons.home), + onPressed: () { + // stopTimer(); + Navigator.pop(context); + }, + ) + ], ], ), body: DefaultTabController( diff --git a/lib/modules/loan_module/pages/create_loan_request_page.dart b/lib/modules/loan_module/pages/create_loan_request_page.dart index b544eb99..0b2dc53e 100644 --- a/lib/modules/loan_module/pages/create_loan_request_page.dart +++ b/lib/modules/loan_module/pages/create_loan_request_page.dart @@ -453,8 +453,8 @@ class _CreateLoanRequestPageState extends State with Tick color: AppColor.primary10, // or AppColor.blueStatus(context) ), alignment: Alignment.center, - child: Icon( - !_loanFormModel.isNewVendor? Icons.add:Icons.search, + child: Icon( + !_loanFormModel.isNewVendor ? Icons.add : Icons.search, color: Colors.white, size: 16, ), @@ -469,6 +469,10 @@ class _CreateLoanRequestPageState extends State with Tick ), ], ).onPress(() async { + _loanFormModel.supplier = null; + _loanFormModel.vendorName = null; + _loanFormModel.supEngineer = null; + _loanFormModel.vendorRepresentativeName = null; setState(() { _loanFormModel.isNewVendor = !_loanFormModel.isNewVendor; }); diff --git a/lib/modules/loan_module/pages/loan_equipment_detail_page.dart b/lib/modules/loan_module/pages/loan_equipment_detail_page.dart index 00dc04eb..8506292c 100644 --- a/lib/modules/loan_module/pages/loan_equipment_detail_page.dart +++ b/lib/modules/loan_module/pages/loan_equipment_detail_page.dart @@ -87,25 +87,25 @@ class LoanEquipmentDetailPage extends StatelessWidget { ).toShadowContainer(context, padding: 12), ], ).expanded, - if (context.userProvider.isEngineer && (snapshot.data?.loanStatusValue == 5 || snapshot.data?.loanStatusValue == 8)) - FooterActionButton.footerContainer( - context: context, - child: AppFilledButton( - onPressed: () async { - if (snapshot.data?.loanStatusValue == 5) { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => PullOutDetailsPage( - loanData: snapshot.data, - ))); - return; - } - Navigator.push(context, MaterialPageRoute(builder: (context) => const InstallationPullOutFormView(isPullout: false))); - }, - label: snapshot.data?.loanStatusValue == 5 ? 'Installation Report' : 'PullOut Report', - ), - ) + // if (context.userProvider.isEngineer && (snapshot.data?.loanStatusValue == 5 || snapshot.data?.loanStatusValue == 8)) + // FooterActionButton.footerContainer( + // context: context, + // child: AppFilledButton( + // onPressed: () async { + // if (snapshot.data?.loanStatusValue == 5) { + // Navigator.push( + // context, + // MaterialPageRoute( + // builder: (context) => PullOutDetailsPage( + // loanData: snapshot.data, + // ))); + // return; + // } + // Navigator.push(context, MaterialPageRoute(builder: (context) => const InstallationPullOutFormView(isPullout: false))); + // }, + // label: snapshot.data?.loanStatusValue == 5 ? 'Installation Report' : 'PullOut Report', + // ), + // ) ], ); }), diff --git a/lib/modules/loan_module/provider/loan_provider.dart b/lib/modules/loan_module/provider/loan_provider.dart index b2a3a5e3..01153eef 100644 --- a/lib/modules/loan_module/provider/loan_provider.dart +++ b/lib/modules/loan_module/provider/loan_provider.dart @@ -35,4 +35,17 @@ class LoanProvider extends ChangeNotifier { } return loanData; } + + Future getLoanByWorkOrderId(int id) async { + LoanRequestModel? loanData; + try { + Response response = await ApiManager.instance.get(URLs.getLoanByWorkOrderId + "?woId=$id", enableToastMessage: false); + if (response.statusCode >= 200 && response.statusCode < 300) { + loanData = LoanRequestModel.fromJson(json.decode(response.body)["data"]); + } + } catch (error) { + print(error); + } + return loanData; + } } From 8b2e47df09cd7fef0bc06f35139e18d2ac136402 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Tue, 3 Feb 2026 14:50:01 +0300 Subject: [PATCH 6/6] check request type is new, user can change the quantity, improvement. --- .../traf_module/create_traf_request_page.dart | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lib/modules/traf_module/create_traf_request_page.dart b/lib/modules/traf_module/create_traf_request_page.dart index 66133805..8a7f5435 100644 --- a/lib/modules/traf_module/create_traf_request_page.dart +++ b/lib/modules/traf_module/create_traf_request_page.dart @@ -117,6 +117,7 @@ class _CreateTRAFRequestPageState extends State { // trafRequest?.qty = 0; } else if (requestType?.value == 2) { trafRequest?.assetNDId = null; + trafRequest?.qty = 1; } setState(() {}); }, @@ -133,6 +134,23 @@ class _CreateTRAFRequestPageState extends State { // setState(() {}); }, ), + 12.height, + AppTextFormField( + initialValue: trafRequest?.qty?.toString() ?? "", + makeMultiLinesNull: true, + labelText: "Quantity", + validator: (value) { + if ((value ?? "").isEmpty) return "Mandatory"; + return null; + }, + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + showShadow: false, + textInputType: TextInputType.number, + onChange: (value) { + trafRequest?.qty = int.parse(value); + }, + ), ], if (requestType?.value == 2) ...[ 12.height,