From b56b998125b82fb4aa797c572363b9d24c2c3e27 Mon Sep 17 00:00:00 2001 From: WaseemAbbasi22 <50428976+WaseemAbbasi22@users.noreply.github.com> Date: Wed, 5 Aug 2026 15:36:26 +0300 Subject: [PATCH 01/22] spare part ui implemented --- lib/controllers/api_routes/urls.dart | 8 +- .../components/activities_list_view.dart | 5 +- .../service_request_bottomsheet.dart | 19 +- .../service_request_detail_view.dart | 21 +- .../forms/cost/cost_detail_form_screen.dart | 389 +++++++----------- .../forms/spare_part/spare_part_request.dart | 64 +-- .../activities/pm_activities_list_view.dart | 207 ++++++++++ .../activities/pm_cost_detail_page.dart | 127 ++++++ .../pm_module/ppm_wo/ppm_details_page.dart | 51 ++- 9 files changed, 603 insertions(+), 288 deletions(-) create mode 100644 lib/modules/pm_module/ppm_wo/activities/pm_activities_list_view.dart create mode 100644 lib/modules/pm_module/ppm_wo/activities/pm_cost_detail_page.dart diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index 2028d9d7..f9374337 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -3,12 +3,12 @@ class URLs { static const String appReleaseBuildNumber = "50"; - 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/mobile"; // host local UAT + static final String _baseUrl = "$_host/v2/mobile"; // new V2 apis // static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM // static final String _baseUrl = "$_host/v4/mobile"; // v4 for Demo module // static final String _baseUrl = "$_host/v5/mobile"; // v5 for data segregation diff --git a/lib/modules/cm_module/views/components/activities_list_view.dart b/lib/modules/cm_module/views/components/activities_list_view.dart index a543651f..d0294f6d 100644 --- a/lib/modules/cm_module/views/components/activities_list_view.dart +++ b/lib/modules/cm_module/views/components/activities_list_view.dart @@ -364,7 +364,10 @@ class _ActivitiesListViewState extends State { requestDetailProvider.updateSparePartHelperModel(requestDetailProvider.sparePartHelperModel); Navigator.of(context).push(MaterialPageRoute( - builder: (_) => const SparePartRequest(), + builder: (_) => SparePartRequest( + model: requestDetailProvider.sparePartHelperModel, + provider: requestDetailProvider, + ), )); } } diff --git a/lib/modules/cm_module/views/components/bottom_sheets/service_request_bottomsheet.dart b/lib/modules/cm_module/views/components/bottom_sheets/service_request_bottomsheet.dart index 04fdd0ce..72e0b6f9 100644 --- a/lib/modules/cm_module/views/components/bottom_sheets/service_request_bottomsheet.dart +++ b/lib/modules/cm_module/views/components/bottom_sheets/service_request_bottomsheet.dart @@ -668,16 +668,20 @@ class ServiceRequestBottomSheet { } void onItemTap({required int index, required BuildContext context}) { + CMDetailProvider requestDetailProvider = Provider.of(context, listen: false); switch (index) { case 0: - Provider.of(context, listen: false).sparePartHelperModel = SparePartHelperModel(); + requestDetailProvider.sparePartHelperModel = SparePartHelperModel(); Navigator.pushReplacement( context, - MaterialPageRoute(builder: (context) => const SparePartRequest()), + MaterialPageRoute( + builder: (context) => SparePartRequest( + model: requestDetailProvider.sparePartHelperModel, + provider: requestDetailProvider, + )), ); break; case 1: - CMDetailProvider requestDetailProvider = Provider.of(context, listen: false); requestDetailProvider.activityMaintenanceHelperModel = ActivityMaintenanceHelperModel( workOrderId: requestDetailProvider.currentWorkOrder?.data?.requestId, id: 0, @@ -804,11 +808,16 @@ class ServiceRequestBottomSheet { buttonColor: AppColor.primary10, loading: false, onPressed: () { + CMDetailProvider provider = Provider.of(context, listen: false); Navigator.pop(context); - Provider.of(context, listen: false).sparePartHelperModel = SparePartHelperModel(); + provider.sparePartHelperModel = SparePartHelperModel(); Navigator.pushReplacement( context, - MaterialPageRoute(builder: (context) => const SparePartRequest()), + MaterialPageRoute( + builder: (context) => SparePartRequest( + model: provider.sparePartHelperModel, + provider: provider, + )), ); }, ).expanded, diff --git a/lib/modules/cm_module/views/components/service_request_detail_view.dart b/lib/modules/cm_module/views/components/service_request_detail_view.dart index 73a7f9ea..766c5586 100644 --- a/lib/modules/cm_module/views/components/service_request_detail_view.dart +++ b/lib/modules/cm_module/views/components/service_request_detail_view.dart @@ -338,8 +338,27 @@ class _ServiceRequestDetailViewState extends State { const Spacer(), if (!provider.isReadOnlyRequest) "edit_icon".toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)).onPress(() async { + WorkOrderData currentWorkOrderData = provider.currentWorkOrder!.data!; + provider.workOrderCostModel = WorkOrderCostModel( + workOrderId: currentWorkOrderData.requestId, + labourCost: currentWorkOrderData.labourCost, + sparePartCost: currentWorkOrderData.sparePartCost, + travelCost: currentWorkOrderData.travelCost, + qAmount: currentWorkOrderData.qAmount, + prNo: currentWorkOrderData.prNo, + poNo: currentWorkOrderData.poNo, + mrNo: currentWorkOrderData.mrNo, + exchangeCost: currentWorkOrderData.exchangeCost, + ); provider.refreshTimer = false; - await Navigator.push(context, MaterialPageRoute(builder: (context) => CostDetailFormScreen(isEdit: true))); + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => CostDetailFormScreen( + isEdit: true, + model: provider.workOrderCostModel, + provider: provider, + ))); provider.refreshTimer = true; }), ], diff --git a/lib/modules/cm_module/views/forms/cost/cost_detail_form_screen.dart b/lib/modules/cm_module/views/forms/cost/cost_detail_form_screen.dart index f7be5cab..1c21e035 100644 --- a/lib/modules/cm_module/views/forms/cost/cost_detail_form_screen.dart +++ b/lib/modules/cm_module/views/forms/cost/cost_detail_form_screen.dart @@ -1,11 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -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/models/helper_data_models/workorder/work_order_helper_models.dart'; -import 'package:test_sa/models/new_models/work_order_detail_model.dart'; import 'package:test_sa/modules/cm_module/cm_detail_provider.dart'; import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; @@ -13,261 +10,189 @@ import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; import 'package:test_sa/new_views/common_widgets/app_lazy_loading.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/providers/service_request_providers/equipment_status_provider.dart'; class CostDetailFormScreen extends StatefulWidget { static const String id = "/cost-detail-form-screen"; - bool isEdit = false; + final bool isEdit; + final WorkOrderCostModel? model; + final CMDetailProvider provider; - CostDetailFormScreen({Key? key, required this.isEdit}) : super(key: key); + const CostDetailFormScreen({ + Key? key, + required this.isEdit, + required this.model, + required this.provider, + }) : super(key: key); @override _CostDetailFormScreenState createState() => _CostDetailFormScreenState(); } class _CostDetailFormScreenState extends State with TickerProviderStateMixin { - CMDetailProvider? _requestDetailProvider; - final GlobalKey _scaffoldKey = GlobalKey(); - @override - void initState() { - super.initState(); - } - - void assignValues() { - // ServiceRequestDetailProvider requestDetailProvider = Provider.of(context, listen: false); - // _requestDetailProvider = Provider.of(context, listen: false); - WorkOrderData currentWorkOrderData = _requestDetailProvider!.currentWorkOrder!.data!; - _requestDetailProvider!.workOrderCostModel = WorkOrderCostModel( - workOrderId: currentWorkOrderData.requestId, - labourCost: currentWorkOrderData.labourCost, - sparePartCost: currentWorkOrderData.sparePartCost, - travelCost: currentWorkOrderData.travelCost, - qAmount: currentWorkOrderData.qAmount, - prNo: currentWorkOrderData.prNo, - poNo: currentWorkOrderData.poNo, - mrNo: currentWorkOrderData.mrNo, - exchangeCost: currentWorkOrderData.exchangeCost, - ); - } - @override Widget build(BuildContext context) { - if (_requestDetailProvider == null) { - _requestDetailProvider = Provider.of(context, listen: false); - assignValues(); - } - return Scaffold( key: _scaffoldKey, appBar: const DefaultAppBar(title: "Cost Details"), - body: Consumer(builder: (context, CMDetailProvider requestDetailProvider, child) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - AppTextFormField( - labelText: "Spare Part Cost", - backgroundColor: AppColor.neutral100, - initialValue: requestDetailProvider.workOrderCostModel?.sparePartCost?.toString(), - textAlign: TextAlign.center, - enable: false, - labelStyle: AppTextStyles.textFieldLabelStyle, - textInputType: TextInputType.number, - showShadow: false, - onChange: (value) { - requestDetailProvider.workOrderCostModel?.sparePartCost = num.parse(value); - }, - style: Theme.of(context).textTheme.titleMedium, - ), - 8.height, - AppTextFormField( - initialValue: requestDetailProvider.workOrderCostModel?.labourCost?.toString(), - textAlign: TextAlign.center, - labelText: "Labor Cost", - backgroundColor: AppColor.fieldBgColor(context), - labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), - textInputType: const TextInputType.numberWithOptions(decimal: true), - showShadow: false, - onChange: (value) { - requestDetailProvider.workOrderCostModel?.labourCost = num.parse(value); - }, - style: Theme.of(context).textTheme.titleMedium, - ), - 8.height, - AppTextFormField( - labelText: "Travel Cost", - backgroundColor: AppColor.fieldBgColor(context), - labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), - initialValue: requestDetailProvider.workOrderCostModel?.travelCost?.toString(), - textAlign: TextAlign.center, - textInputType: const TextInputType.numberWithOptions(decimal: true), - showShadow: false, - onChange: (value) { - requestDetailProvider.workOrderCostModel?.travelCost = num.parse(value); - }, - style: Theme.of(context).textTheme.titleMedium, - ), - 8.height, - AppTextFormField( - labelText: "Quot Amount", - backgroundColor: AppColor.fieldBgColor(context), - labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), - initialValue: requestDetailProvider.workOrderCostModel?.qAmount?.toString(), - textAlign: TextAlign.center, - textInputType: const TextInputType.numberWithOptions(decimal: true), - showShadow: false, - onChange: (value) { - requestDetailProvider.workOrderCostModel?.qAmount = num.parse(value); - }, - style: Theme.of(context).textTheme.titleMedium, - ), - 8.height, - AppTextFormField( - labelText: "PR No", - backgroundColor: AppColor.fieldBgColor(context), - labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), - initialValue: requestDetailProvider.workOrderCostModel?.prNo, - textAlign: TextAlign.center, - textInputType: const TextInputType.numberWithOptions(decimal: true), - showShadow: false, - onChange: (value) { - requestDetailProvider.workOrderCostModel?.prNo = value; - }, - style: Theme.of(context).textTheme.titleMedium, - ), - 8.height, - AppTextFormField( - labelText: "PO No", - backgroundColor: AppColor.fieldBgColor(context), - labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), - initialValue: requestDetailProvider.workOrderCostModel?.poNo, - textAlign: TextAlign.center, - textInputType: const TextInputType.numberWithOptions(decimal: true), - showShadow: false, - onChange: (value) { - requestDetailProvider.workOrderCostModel?.poNo = value; - }, - style: Theme.of(context).textTheme.titleMedium, - ), - 8.height, - AppTextFormField( - labelText: "MR No", - backgroundColor: AppColor.fieldBgColor(context), - labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), - initialValue: requestDetailProvider.workOrderCostModel?.mrNo, - textAlign: TextAlign.center, - textInputType: const TextInputType.numberWithOptions(decimal: true), - showShadow: false, - onChange: (value) { - requestDetailProvider.workOrderCostModel?.mrNo = value; - }, - style: Theme.of(context).textTheme.titleMedium, - ), - 8.height, - AppTextFormField( - labelText: "Exchange Cost", - backgroundColor: AppColor.fieldBgColor(context), - labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), - initialValue: requestDetailProvider.workOrderCostModel?.exchangeCost != null ? requestDetailProvider.workOrderCostModel?.exchangeCost.toString() : '', - textAlign: TextAlign.center, - textInputType: const TextInputType.numberWithOptions(decimal: true), - showShadow: false, - onChange: (value) { - requestDetailProvider.workOrderCostModel?.exchangeCost = num.parse(value); - }, - style: Theme.of(context).textTheme.titleMedium, - ), - ], - ).toShadowContainer(context).paddingAll(16), - ).expanded, - FooterActionButton.footerContainer( - context: context, - child: AppFilledButton( - label: "Update Cost Details", - buttonColor: AppColor.primary10, - onPressed: () async { - if (validateForm(requestDetailProvider: requestDetailProvider)) { - showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading()); - await requestDetailProvider.engineerUpdateCost().then((success) { - Navigator.pop(context); - if (success) { + body: ListenableBuilder( + listenable: widget.provider, + builder: (context, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + AppTextFormField( + labelText: "Spare Part Cost", + backgroundColor: AppColor.neutral100, + initialValue: widget.model?.sparePartCost?.toString(), + textAlign: TextAlign.center, + enable: false, + labelStyle: AppTextStyles.textFieldLabelStyle, + textInputType: TextInputType.number, + showShadow: false, + onChange: (value) { + widget.model?.sparePartCost = num.parse(value); + }, + style: Theme.of(context).textTheme.titleMedium, + ), + 8.height, + AppTextFormField( + initialValue: widget.model?.labourCost?.toString(), + textAlign: TextAlign.center, + labelText: "Labor Cost", + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + textInputType: const TextInputType.numberWithOptions(decimal: true), + showShadow: false, + onChange: (value) { + widget.model?.labourCost = num.parse(value); + }, + style: Theme.of(context).textTheme.titleMedium, + ), + 8.height, + AppTextFormField( + labelText: "Travel Cost", + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + initialValue: widget.model?.travelCost?.toString(), + textAlign: TextAlign.center, + textInputType: const TextInputType.numberWithOptions(decimal: true), + showShadow: false, + onChange: (value) { + widget.model?.travelCost = num.parse(value); + }, + style: Theme.of(context).textTheme.titleMedium, + ), + 8.height, + AppTextFormField( + labelText: "Quot Amount", + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + initialValue: widget.model?.qAmount?.toString(), + textAlign: TextAlign.center, + textInputType: const TextInputType.numberWithOptions(decimal: true), + showShadow: false, + onChange: (value) { + widget.model?.qAmount = num.parse(value); + }, + style: Theme.of(context).textTheme.titleMedium, + ), + 8.height, + AppTextFormField( + labelText: "PR No", + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + initialValue: widget.model?.prNo, + textAlign: TextAlign.center, + textInputType: const TextInputType.numberWithOptions(decimal: true), + showShadow: false, + onChange: (value) { + widget.model?.prNo = value; + }, + style: Theme.of(context).textTheme.titleMedium, + ), + 8.height, + AppTextFormField( + labelText: "PO No", + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + initialValue: widget.model?.poNo, + textAlign: TextAlign.center, + textInputType: const TextInputType.numberWithOptions(decimal: true), + showShadow: false, + onChange: (value) { + widget.model?.poNo = value; + }, + style: Theme.of(context).textTheme.titleMedium, + ), + 8.height, + AppTextFormField( + labelText: "MR No", + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + initialValue: widget.model?.mrNo, + textAlign: TextAlign.center, + textInputType: const TextInputType.numberWithOptions(decimal: true), + showShadow: false, + onChange: (value) { + widget.model?.mrNo = value; + }, + style: Theme.of(context).textTheme.titleMedium, + ), + 8.height, + AppTextFormField( + labelText: "Exchange Cost", + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + initialValue: widget.model?.exchangeCost != null ? widget.model?.exchangeCost.toString() : '', + textAlign: TextAlign.center, + textInputType: const TextInputType.numberWithOptions(decimal: true), + showShadow: false, + onChange: (value) { + widget.model?.exchangeCost = num.parse(value); + }, + style: Theme.of(context).textTheme.titleMedium, + ), + ], + ).toShadowContainer(context).paddingAll(16), + ).expanded, + FooterActionButton.footerContainer( + context: context, + child: AppFilledButton( + label: "Update Cost Details", + buttonColor: AppColor.primary10, + onPressed: () async { + if (validateForm()) { + showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading()); + await widget.provider.engineerUpdateCost().then((success) { Navigator.pop(context); - } - }); - } - }, + if (success) { + Navigator.pop(context); + } + }); + } + }, + ), ), - ), - ], - ); - }), + ], + ); + }, + ), ); } - bool validateForm({required CMDetailProvider requestDetailProvider}) { + bool validateForm() { bool isValid = true; if (!widget.isEdit) { return true; } - // if (requestDetailProvider.engineerUpdateWorkOrderHelperModel!.equipmentStatus != null) { - // if (requestDetailProvider.engineerUpdateWorkOrderHelperModel!.equipmentStatus!.value == 1 || requestDetailProvider.engineerUpdateWorkOrderHelperModel!.equipmentStatus!.value == 2) { - // if (requestDetailProvider.engineerUpdateWorkOrderHelperModel!.returnToService == null) { - // Fluttertoast.showToast(msg: "Return to service is required ", toastLength: Toast.LENGTH_LONG); - // return false; - // } - // } - // } return isValid; } - - Widget assetStatusWidget({required BuildContext context, required EngineerUpdateWorkOrderHelperModel workOrderData}) { - return Consumer(builder: (cxt, snapshot, _) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - context.translation.assetStatus.bodyText(context).custom(color: AppColor.black20), - 8.height, - Wrap( - runSpacing: 8, - spacing: 8, - children: [ - for (var element in snapshot.items) - Row( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox( - width: 24, - height: 24, - child: Radio( - value: element, - activeColor: Colors.red, - fillColor: WidgetStateColor.resolveWith((states) { - if (states.contains(WidgetState.selected)) return AppColor.primary10; - return AppColor.neutral130; - }), - groupValue: workOrderData.equipmentStatus, - onChanged: (state) { - setState(() { - workOrderData.equipmentStatus = element; - }); - }), - ), - 8.width, - Text(element.name ?? '', style: AppTextStyles.tinyFont.copyWith(color: AppColor.neutral120)), - ], - ) - ], - ).toShimmer(isShow: snapshot.loading, context: context), - ], - ); - }); - } } diff --git a/lib/modules/cm_module/views/forms/spare_part/spare_part_request.dart b/lib/modules/cm_module/views/forms/spare_part/spare_part_request.dart index 75140eb4..46a51f77 100644 --- a/lib/modules/cm_module/views/forms/spare_part/spare_part_request.dart +++ b/lib/modules/cm_module/views/forms/spare_part/spare_part_request.dart @@ -33,15 +33,20 @@ import 'package:test_sa/views/widgets/loaders/no_data_found.dart'; class SparePartRequest extends StatefulWidget { static const String id = "/spare-part-request"; + final SparePartHelperModel ?model; + final CMDetailProvider provider; - const SparePartRequest({Key? key}) : super(key: key); + const SparePartRequest({ + Key? key, + required this.model, + required this.provider, + }) : super(key: key); @override _SparePartRequestState createState() => _SparePartRequestState(); } class _SparePartRequestState extends State with TickerProviderStateMixin { - CMDetailProvider? _requestDetailProvider; PartsProvider? _partsProvider; bool _isLoading = false; List _spareParts = []; @@ -62,37 +67,40 @@ class _SparePartRequestState extends State with TickerProvider void initState() { super.initState(); _partsProvider = Provider.of(context, listen: false); - _requestDetailProvider = Provider.of(context, listen: false); - _requestDetailProvider?.sparePartHelperModel = SparePartHelperModel( - id: _requestDetailProvider?.sparePartHelperModel?.id ?? 0, - workOrderId: _requestDetailProvider?.sparePartHelperModel?.workOrderId ?? _requestDetailProvider?.currentWorkOrder?.data?.requestId, - sparePartAttachments: _requestDetailProvider?.sparePartHelperModel?.sparePartAttachments ?? [], - sparePart: _requestDetailProvider?.sparePartHelperModel?.sparePart ?? SparePart(), - quantity: _requestDetailProvider?.sparePartHelperModel?.quantity, - installQty: _requestDetailProvider?.sparePartHelperModel?.installQty, - returnQty: _requestDetailProvider?.sparePartHelperModel?.returnQty, - activityStatusId: _requestDetailProvider?.sparePartHelperModel?.activityStatusId ?? statusLookup.id, - activityStatus: _requestDetailProvider?.sparePartHelperModel?.activityStatus ?? statusLookup, - comment: _requestDetailProvider?.sparePartHelperModel?.comment ?? '', + + // Initialize model with existing values or defaults + widget.provider.sparePartHelperModel = SparePartHelperModel( + id: widget.model?.id ?? 0, + workOrderId: widget.model?.workOrderId ?? widget.provider.currentWorkOrder?.data?.requestId, + sparePartAttachments: widget.model?.sparePartAttachments ?? [], + sparePart: widget.model?.sparePart ?? SparePart(), + quantity: widget.model?.quantity, + installQty: widget.model?.installQty, + returnQty: widget.model?.returnQty, + activityStatusId: widget.model?.activityStatusId ?? statusLookup.id, + activityStatus: widget.model?.activityStatus ?? statusLookup, + comment: widget.model?.comment ?? '', ); - _partQtyController.text = _requestDetailProvider?.sparePartHelperModel?.quantity != null ? _requestDetailProvider!.sparePartHelperModel!.quantity!.round().toString() : ''; - _installQtyController.text = _requestDetailProvider?.sparePartHelperModel?.installQty != null ? _requestDetailProvider!.sparePartHelperModel!.installQty!.round().toString() : ''; - _returnQtyController.text = _requestDetailProvider?.sparePartHelperModel?.returnQty != null ? _requestDetailProvider!.sparePartHelperModel!.returnQty!.round().toString() : ''; - _oracleNoController.text = _requestDetailProvider?.sparePartHelperModel?.sparePart?.oracleCode != null ? _requestDetailProvider!.sparePartHelperModel!.sparePart!.oracleCode! : ''; - _descriptionController.text = _requestDetailProvider?.sparePartHelperModel?.comment != null ? _requestDetailProvider!.sparePartHelperModel!.comment! : ''; - activityStatus = _requestDetailProvider?.sparePartHelperModel?.activityStatus?.value; + + _partQtyController.text = widget.provider.sparePartHelperModel?.quantity != null ? widget.provider.sparePartHelperModel!.quantity!.round().toString() : ''; + _installQtyController.text = widget.provider.sparePartHelperModel?.installQty != null ? widget.provider.sparePartHelperModel!.installQty!.round().toString() : ''; + _returnQtyController.text = widget.provider.sparePartHelperModel?.returnQty != null ? widget.provider.sparePartHelperModel!.returnQty!.round().toString() : ''; + _oracleNoController.text = widget.provider.sparePartHelperModel?.sparePart?.oracleCode != null ? widget.provider.sparePartHelperModel!.sparePart!.oracleCode! : ''; + _descriptionController.text = widget.provider.sparePartHelperModel?.comment != null ? widget.provider.sparePartHelperModel!.comment! : ''; + activityStatus = widget.provider.sparePartHelperModel?.activityStatus?.value; + scheduleMicrotask(() async { _isLoading = true; - attachments = _requestDetailProvider?.sparePartHelperModel?.sparePartAttachments?.map((e) => GenericAttachmentModel(id: e.id!, name: e.name ?? '')).toList() ?? []; + attachments = widget.provider.sparePartHelperModel?.sparePartAttachments?.map((e) => GenericAttachmentModel(id: e.id!, name: e.name ?? '')).toList() ?? []; setState(() {}); - _spareParts = await _partsProvider!.getPartsListByDisplayName(assetId: _requestDetailProvider?.currentWorkOrder?.data?.asset?.id); + _spareParts = await _partsProvider!.getPartsListByDisplayName(assetId: widget.provider.currentWorkOrder?.data?.asset?.id); _isLoading = false; setState(() {}); }); } void restValues({required CMDetailProvider requestDetailProvider}) { - requestDetailProvider.sparePartHelperModel = SparePartHelperModel(id: 0, workOrderId: _requestDetailProvider?.currentWorkOrder?.data?.requestId, sparePartAttachments: []); + requestDetailProvider.sparePartHelperModel = SparePartHelperModel(id: 0, workOrderId: widget.provider.currentWorkOrder?.data?.requestId, sparePartAttachments: []); _partQtyController.clear(); _installQtyController.clear(); _returnQtyController.clear(); @@ -130,7 +138,7 @@ class _SparePartRequestState extends State with TickerProvider SingleChildScrollView( padding: const EdgeInsets.all(16), child: IgnorePointer( - ignoring: _requestDetailProvider!.isReadOnlyRequest, + ignoring: widget.provider.isReadOnlyRequest, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ @@ -164,7 +172,7 @@ class _SparePartRequestState extends State with TickerProvider if (part != null) { requestDetailProvider.sparePartHelperModel?.sparePart = part; _oracleNoController.text = part.oracleCode ?? ''; - requestDetailProvider.updateSparePartHelperModel(_requestDetailProvider?.sparePartHelperModel); + requestDetailProvider.updateSparePartHelperModel(widget.provider.sparePartHelperModel); } }, ), @@ -307,7 +315,7 @@ class _SparePartRequestState extends State with TickerProvider FooterActionButton.footerContainer( context: context, child: AppFilledButton( - label: _requestDetailProvider?.sparePartHelperModel?.id == 0 ? context.translation.addSparePartActivity : context.translation.updateSparePartActivity, + label: widget.provider.sparePartHelperModel?.id == 0 ? context.translation.addSparePartActivity : context.translation.updateSparePartActivity, buttonColor: AppColor.green70, onPressed: () { _onSubmit(requestDetailProvider: requestDetailProvider); @@ -345,7 +353,7 @@ class _SparePartRequestState extends State with TickerProvider builder: (context) => const AppLazyLoading(), ); bool success = false; - if (_requestDetailProvider?.sparePartHelperModel?.id == 0) { + if (widget.provider.sparePartHelperModel?.id == 0) { success = await requestDetailProvider.createActivitySparePart(); } else { success = await requestDetailProvider.updateActivitySparePart(); @@ -356,7 +364,7 @@ class _SparePartRequestState extends State with TickerProvider id: requestDetailProvider.currentWorkOrder!.data!.requestId!, ); if (mounted) Navigator.pop(context); - if (_requestDetailProvider?.sparePartHelperModel?.id == 0) { + if (widget.provider.sparePartHelperModel?.id == 0) { ServiceRequestBottomSheet.addAnotherSpareRequestBottomSheet(context: context); const SizedBox().flushBar( context: context, diff --git a/lib/modules/pm_module/ppm_wo/activities/pm_activities_list_view.dart b/lib/modules/pm_module/ppm_wo/activities/pm_activities_list_view.dart new file mode 100644 index 00000000..4d61b068 --- /dev/null +++ b/lib/modules/pm_module/ppm_wo/activities/pm_activities_list_view.dart @@ -0,0 +1,207 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +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/models/enums/user_types.dart'; +import 'package:test_sa/models/helper_data_models/maintenance_request/activity_maintenance_model.dart'; +import 'package:test_sa/models/helper_data_models/spare_part/activity_spare_part_model.dart'; +import 'package:test_sa/models/new_models/work_order_detail_model.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_details.dart'; +import 'package:test_sa/modules/cm_module/cm_detail_provider.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/cm_module/views/forms/maintenance_request/maintenance_request_main.dart'; +import 'package:test_sa/modules/cm_module/views/forms/spare_part/spare_part_request.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_lazy_loading.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/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/no_data_found.dart'; +import 'package:test_sa/views/widgets/requests/request_status.dart'; + +class PmActivitiesListView extends StatefulWidget { + static const String id = "/activities-list"; + + const PmActivitiesListView({Key? key}) : super(key: key); + + @override + _PmActivitiesListViewState createState() { + return _PmActivitiesListViewState(); + } +} + +class _PmActivitiesListViewState extends State { + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + List reOrderActivities = []; + + List activities = []; + + bool disableNewActivity = false; + + @override + Widget build(BuildContext context) { + UserProvider userProvider = Provider.of(context); + return Scaffold( + appBar: const DefaultAppBar( + title: 'PM Activities', + // actions: [if (!showReorderView) IconButton(onPressed: () => activities.isEmpty ? null : setState(() => showReorderView = true), icon: const Icon(Icons.reorder_rounded))], + ), + + ///TODO replace with pm provider and data from pm provider + body: Consumer(builder: (context, CMDetailProvider requestDetailProvider, child) { + activities = requestDetailProvider.currentWorkOrder!.data!.activities; + requestDetailProvider.lastVisitedRequestActivityId = (activities.isNotEmpty ? activities.last.id : -1)!; + if (activities.isNotEmpty && (activities.last.activityStatus!.value == 14 || activities.last.activityStatus!.value == 19)) { + disableNewActivity = true; + } else { + disableNewActivity = false; + } + + return Column( + children: [ + (activities.isEmpty && reOrderActivities.isEmpty) + ? NoDataFound(message: context.translation.noDataFound).center.expanded + : ListView.separated( + padding: const EdgeInsets.all(16), + itemCount: activities.length, + separatorBuilder: (czt, index) => 16.height, + itemBuilder: (context, index) { + return sparePartActivityCard(requestDetailProvider: requestDetailProvider, userProvider: userProvider, context: context, activity: activities[index]); + }, + ).expanded, + if (userProvider.user!.type == UsersTypes.engineer && + (requestDetailProvider.currentWorkOrder?.data?.status?.value != 5 && requestDetailProvider.currentWorkOrder?.data?.status?.value != 3)) + FooterActionButton.footerContainer( + context: context, + child: AppFilledButton( + label: 'Create New Spare Part', + maxWidth: true, + buttonColor: AppColor.primary10, + loading: requestDetailProvider.isLoading, + disableButton: disableNewActivity, + onPressed: () async { + // ServiceRequestBottomSheet.activityTypeBottomSheet(context: context); + }, + )) + ], + ); + }), + ); + } + + Widget sparePartActivityCard({required CMDetailProvider requestDetailProvider, required UserProvider userProvider, required BuildContext context, required Activities activity}) { + bool showActionButtons = userProvider.user!.userID == requestDetailProvider.currentWorkOrder?.data?.assignedEmployee?.userId && + userProvider.user!.type == UsersTypes.engineer && + requestDetailProvider.currentWorkOrder?.data?.status?.value != 5 && + requestDetailProvider.currentWorkOrder?.data?.status?.value != 3; + + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + StatusLabel( + label: activity.activityStatus?.name, + textColor: AppColor.white10, + backgroundColor: AppColor.primary10, + ), + if (showActionButtons) + Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "edit_icon".toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)).onPress(() { + editSparePartRequest(context: context, requestDetailProvider: requestDetailProvider, activity: activity); + }), + 12.width, + "delete_icon".toSvgAsset(height: 24, width: 24).onPress(() async { + showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading()); + int status = await requestDetailProvider.deleteActivitySparePart(id: activity.id!, workOrderId: requestDetailProvider.currentWorkOrder!.data!.requestId!); + if (status == 200) { + await requestDetailProvider.getWorkOrderById(id: requestDetailProvider.currentWorkOrder!.data!.requestId!); + Navigator.pop(context); + } else { + Navigator.pop(context); + } + }), + ], + ) + ], + ), + 6.height, + InfoHeaderWidget(activity.activityType?.cleanupWhitespace.capitalizeFirstOfEach ?? '-'), + InfoTextWidget( + label: context.translation.partName, + value: activity.activitySparePart?.partCatalogItem?.partName, + ), + InfoTextWidget( + label: context.translation.partNo, + value: activity.activitySparePart?.partCatalogItem?.partNumber, + showArrow: true, + ), + if (activity.activitySparePart?.acitiySparePartAttachments?.isNotEmpty ?? false) ...[ + 6.height, + const Divider().defaultStyle(context), + FilesList(images: activity.activitySparePart!.acitiySparePartAttachments!.map((toElement) => URLs.getFileUrl(toElement.name!)!).toList()), + ], + ], + ).toShadowContainer(context, showShadow: false).onPress(() { + ///Move to cost page .. + // if (showActionButtons) return; + // if (requestDetailProvider.isReadOnlyRequest) { + // editSparePartRequest(context: context, requestDetailProvider: requestDetailProvider, activity: activity); + // } else if (userProvider.user!.userID == requestDetailProvider.currentWorkOrder?.data?.assignedEmployee?.userId) { + // requestDetailProvider.isReadOnlyRequest = true; + // editSparePartRequest(context: context, requestDetailProvider: requestDetailProvider, activity: activity); + // } + }); + } + + void editSparePartRequest({required BuildContext context, required CMDetailProvider requestDetailProvider, required Activities activity}) async { + requestDetailProvider.sparePartHelperModel = SparePartHelperModel( + id: activity.id, + workOrderId: requestDetailProvider.currentWorkOrder?.data?.requestId, + comment: activity.activitySparePart?.comment, + sparePartAttachments: activity.activitySparePart?.acitiySparePartAttachments ?? [], + activityStatus: activity.activityStatus, + sparePart: SparePart( + id: activity.activitySparePart?.partCatalogItem?.id, + partName: activity.activitySparePart?.partCatalogItem?.partName, + partNo: activity.activitySparePart?.partCatalogItem?.partNumber, + oracleCode: activity.activitySparePart?.partCatalogItem?.oracleCode), + quantity: activity.activitySparePart?.quantity, + installQty: activity.activitySparePart?.installQty, + returnQty: activity.activitySparePart?.returnQty, + activityStatusId: activity.activityStatus?.id, + ); + + requestDetailProvider.updateSparePartHelperModel(requestDetailProvider.sparePartHelperModel); + + Navigator.of(context).push(MaterialPageRoute( + builder: (_) => SparePartRequest( + model: requestDetailProvider.sparePartHelperModel, + provider: requestDetailProvider, + ), + )); + } +} diff --git a/lib/modules/pm_module/ppm_wo/activities/pm_cost_detail_page.dart b/lib/modules/pm_module/ppm_wo/activities/pm_cost_detail_page.dart new file mode 100644 index 00000000..24cd885b --- /dev/null +++ b/lib/modules/pm_module/ppm_wo/activities/pm_cost_detail_page.dart @@ -0,0 +1,127 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/controllers/providers/api/user_provider.dart'; +import 'package:test_sa/controllers/providers/settings/setting_provider.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/enums/user_types.dart'; +import 'package:test_sa/models/enums/work_order_next_step.dart'; +import 'package:test_sa/models/generic_attachment_model.dart'; +import 'package:test_sa/models/helper_data_models/workorder/work_order_helper_models.dart'; +import 'package:test_sa/models/new_models/work_order_detail_model.dart'; +import 'package:test_sa/modules/cm_module/cm_detail_provider.dart'; +import 'package:test_sa/modules/cm_module/views/forms/asset_retired/asset_retired.dart'; +import 'package:test_sa/modules/cm_module/views/forms/cost/cost_detail_form_screen.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/views/widgets/equipment/asset_detail_page.dart'; +import 'package:test_sa/views/widgets/images/files_list.dart'; +import 'package:test_sa/views/widgets/images/multi_image_picker.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/no_data_found.dart'; +import 'package:test_sa/views/widgets/requests/request_status.dart'; +import 'package:test_sa/views/widgets/sound/sound_player.dart'; + +class PmCostDetailsView extends StatefulWidget { + PmCostDetailsView({Key? key}) : super(key: key); + + @override + State createState() => _PmCostDetailsViewState(); +} + +class _PmCostDetailsViewState extends State { + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + UserProvider userProvider = Provider.of(context, listen: false); + + ///TODO: replace with pm provider and data from provider and remove the extra widgets as well. + return Consumer(builder: (pContext, requestProvider, _) { + bool showCostCard = context.userProvider.user!.type == UsersTypes.engineer && + !requestProvider.isReadOnlyRequest && + requestProvider.currentWorkOrder?.data?.nextStep?.workOrderNextStepEnum == WorkOrderNextStepEnum.activity; + + return requestProvider.isLoading + ? CircularProgressIndicator(color: AppColor.loadingColor(context)).center + : requestProvider.currentWorkOrder == null + ? const NoDataFound() + : Column( + children: [ + SingleChildScrollView( + padding: EdgeInsets.symmetric(horizontal: 16.toScreenWidth), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (showCostCard) ...[ + 16.height, + costCard(context, requestProvider), + ], + ], + ), + ).expanded, + ], + ); + }); + } + + Widget costCard(BuildContext context, CMDetailProvider provider) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + InfoHeader16Widget("Cost"), + const Spacer(), + if (!provider.isReadOnlyRequest) + + ///TODO replace with exact provider and data from provider and remove the extra widgets as well. + "edit_icon".toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)).onPress(() async { + WorkOrderData currentWorkOrderData = provider.currentWorkOrder!.data!; + provider.workOrderCostModel = WorkOrderCostModel( + workOrderId: currentWorkOrderData.requestId, + labourCost: currentWorkOrderData.labourCost, + sparePartCost: currentWorkOrderData.sparePartCost, + travelCost: currentWorkOrderData.travelCost, + qAmount: currentWorkOrderData.qAmount, + prNo: currentWorkOrderData.prNo, + poNo: currentWorkOrderData.poNo, + mrNo: currentWorkOrderData.mrNo, + exchangeCost: currentWorkOrderData.exchangeCost, + ); + provider.refreshTimer = false; + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => CostDetailFormScreen( + isEdit: true, + model: provider.workOrderCostModel, + provider: provider, + ))); + provider.refreshTimer = true; + }), + ], + ), + InfoTextWidget(label: 'Spare Part Cost'.addTranslation, value: provider.currentWorkOrder?.data?.sparePartCost?.toString() ?? '-'), + InfoTextWidget(label: 'Labor Cost'.addTranslation, value: provider.currentWorkOrder?.data?.labourCost?.toString() ?? '-'), + InfoTextWidget(label: 'Travel Cost'.addTranslation, value: provider.currentWorkOrder?.data?.travelCost?.toString() ?? '-'), + InfoTextWidget(label: 'Quot Amount'.addTranslation, value: provider.currentWorkOrder?.data?.qAmount?.toString() ?? '-'), + InfoTextWidget(label: 'PR No'.addTranslation, value: provider.currentWorkOrder?.data?.prNo?.toString() ?? '-'), + InfoTextWidget(label: 'PO No'.addTranslation, value: provider.currentWorkOrder?.data?.poNo?.toString() ?? '-'), + InfoTextWidget(label: 'MR No'.addTranslation, value: provider.currentWorkOrder?.data?.mrNo?.toString() ?? '-'), + InfoTextWidget(label: 'Exchange Cost'.addTranslation, value: provider.currentWorkOrder?.data?.exchangeCost?.toString() ?? '-'), + ], + ).toShadowContainer(context); + } +} diff --git a/lib/modules/pm_module/ppm_wo/ppm_details_page.dart b/lib/modules/pm_module/ppm_wo/ppm_details_page.dart index bf64bf9a..84bac35f 100644 --- a/lib/modules/pm_module/ppm_wo/ppm_details_page.dart +++ b/lib/modules/pm_module/ppm_wo/ppm_details_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/models/plan_preventive_visit/plan_preventive_visit_model.dart'; import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; +import 'package:test_sa/modules/pm_module/ppm_wo/activities/pm_activities_list_view.dart'; import 'package:test_sa/new_views/common_widgets/default_app_bar.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'; @@ -82,6 +83,20 @@ class _PpmDetailsPageState extends State { textColor: AppColor.getRequestStatusTextColorByName(context, planPreventiveVisit.visitStatus!.name!), backgroundColor: AppColor.getRequestStatusColorByName(context, planPreventiveVisit.visitStatus!.name!), ), + // "edit_icon".toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)).onPress(() { + // if(userProvider!.user!.type == UsersTypes.engineer && (!ppmProvider.isReadOnly)){ + // FooterActionButton.footerContainer( + // child: AppFilledButton( + // onPressed: () async { + // await Navigator.of(context).push(MaterialPageRoute(builder: (_) => UpdatePpm(ppm: null, planPreventiveVisit: planPreventiveVisit))); + // getVisitData(); + // }, + // label: context.translation.updateWorkOrder, + // ), + // context: context, + // ); + // } + // }) ], ), InfoHeader16Widget(planPreventiveVisit.assetName?.cleanupWhitespace.capitalizeFirstOfEach ?? ''), @@ -104,26 +119,28 @@ class _PpmDetailsPageState extends State { ).expanded, if (userProvider!.user!.type == UsersTypes.engineer && (!ppmProvider.isReadOnly)) ...[ FooterActionButton.footerContainer( - child: AppFilledButton( - onPressed: () async { - await Navigator.of(context).push(MaterialPageRoute(builder: (_) => UpdatePpm(ppm: null, planPreventiveVisit: planPreventiveVisit))); - getVisitData(); - }, - label: context.translation.updateWorkOrder, + child: Column( + children: [ + AppFilledButton( + onPressed: () async { + await Navigator.of(context).push(MaterialPageRoute(builder: (_) => UpdatePpm(ppm: null, planPreventiveVisit: planPreventiveVisit))); + getVisitData(); + }, + label: context.translation.updateWorkOrder, + ), + 12.height, + AppFilledButton( + onPressed: () async { + await Navigator.of(context).push(MaterialPageRoute(builder: (_) => const PmActivitiesListView())); + getVisitData(); + }, + buttonColor: AppColor.neutral50, + label: context.translation.activities, + ), + ], ), context: context, ) - ] else ...[ - // FooterActionButton.footerContainer( - // context: context, - // child: AppFilledButton( - // onPressed: () async { - // await Navigator.of(context).push(MaterialPageRoute(builder: (_) => UpdatePpm(ppm: null, planPreventiveVisit: planPreventiveVisit))); - // getVisitData(); - // }, - // label: context.translation.viewDetails, - // ), - // ) ] ]) : const Center(child: NoDataFound()); From 7f4a06b0db8693a668a2f3e8e514bbba126d4e84 Mon Sep 17 00:00:00 2001 From: WaseemAbbasi22 <50428976+WaseemAbbasi22@users.noreply.github.com> Date: Tue, 11 Aug 2026 11:38:36 +0300 Subject: [PATCH 02/22] PM activity and cost flow implemented --- lib/controllers/api_routes/urls.dart | 13 +- .../providers/api/parts_provider.dart | 17 +- .../providers/api/ppm_provider.dart | 159 +++++ .../spare_part/activity_spare_part_model.dart | 50 +- .../workorder/work_order_helper_models.dart | 3 + .../plan_preventive_visit_model.dart | 644 +++-------------- .../components/activities_list_view.dart | 18 +- .../service_request_bottomsheet.dart | 38 +- .../service_request_detail_view.dart | 1 - .../forms/cost/cost_detail_form_screen.dart | 304 ++++---- .../forms/spare_part/spare_part_request.dart | 658 ++++++++++-------- .../activities/pm_activities_list_view.dart | 137 ++-- .../activities/pm_cost_detail_page.dart | 127 ---- .../pm_module/ppm_wo/ppm_details_page.dart | 16 +- .../ppm_wo/update_ppm/update_ppm.dart | 168 +++-- .../ppm_wo/update_ppm/wo_info_form.dart | 77 ++ 16 files changed, 1150 insertions(+), 1280 deletions(-) delete mode 100644 lib/modules/pm_module/ppm_wo/activities/pm_cost_detail_page.dart diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index f9374337..b03cec52 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -254,8 +254,13 @@ class URLs { static get getRegularVisits => "$_baseUrl/Visit/GetVisits"; // get static get updateRegularVisits => "$_baseUrl/Visit/UpdateVisits"; // get - static get getPlanPreventiveVisitById => "$_baseUrl/PlanPreventiveVisit/GetPlanPreventiveVisitById"; // get - static get updateVisitByEngineer => "$_baseUrl/PlanPreventiveVisit/UpdateVisitByEngineer"; // get + static get getPlanPreventiveVisitById => "$_baseUrl/PlanPreventiveVisit/GetPlanPreventiveVisitById"; + static get updateVisitByEngineer => "$_baseUrl/PlanPreventiveVisit/UpdateVisitByEngineer"; + static get createPmActivitySparePart => "$_baseUrl/PlanPreventiveVisit/CreateActivitySparePart"; + static get updatePmActivitySparePart => "$_baseUrl/PlanPreventiveVisit/UpdateActivitySparePart"; + static get deletePmActivitySparePart => "$_baseUrl/PlanPreventiveVisit/DeleteActivitySparePart"; + static get updatePmActivityCost => "$_baseUrl/PlanPreventiveVisit/EngineerUpdateCost"; + static get getSingleServiceRequest => "$_baseUrl/return/call/information"; // get static get getSuppliersAutoComplete => "$_baseUrl/Supplier/GetSuppliersAutoComplete"; @@ -604,4 +609,8 @@ class URLs { static get submitApplicationDemoRequest => "$_baseUrl/DemoUser/CreateDemoUser"; // post submit application demo request// get application demo request history static get sendDemoUserOtp => "$_baseUrl/DemoUser/EmailVerification"; // post send OTP to user static get verifyDemoUserOtp => "$_baseUrl/DemoUser/VerifyOtp"; // post verify OTP + +// spare part request endpoint. + + } diff --git a/lib/controllers/providers/api/parts_provider.dart b/lib/controllers/providers/api/parts_provider.dart index fc2829b8..e50b7a7b 100644 --- a/lib/controllers/providers/api/parts_provider.dart +++ b/lib/controllers/providers/api/parts_provider.dart @@ -88,32 +88,39 @@ class PartsProvider extends ChangeNotifier { late Response response; try { if (partNo != null) { - response = await ApiManager.instance.post(URLs.getPartNumber, body: {"partNo": partNo, }); + response = await ApiManager.instance.post(URLs.getPartNumber, body: { + "partNo": partNo, + }); } else { - response = await ApiManager.instance.post(URLs.getPartNumber, body: {"partName": partName, }); + response = await ApiManager.instance.post(URLs.getPartNumber, body: { + "partName": partName, + }); } List page = []; if (response.statusCode >= 200 && response.statusCode < 300) { // client's request was successfully received List categoriesListJson = json.decode(response.body)["data"]; - page = categoriesListJson.map((part) => SparePart.fromJson(part,false)).toList(); + page = categoriesListJson.map((part) => SparePart.fromJson(part, false)).toList(); } return page; } catch (error) { return []; } } + // Implement this only for spare part request for now show and search on display name .... Future> getPartsListByDisplayName({num? assetId, String? displayName}) async { late Response response; try { - response = await ApiManager.instance.post(URLs.getPartNumber, body: {"displayName": displayName, }); + response = await ApiManager.instance.post(URLs.getPartNumber, body: { + "displayName": displayName, + }); List page = []; if (response.statusCode >= 200 && response.statusCode < 300) { // client's request was successfully received List categoriesListJson = json.decode(response.body)["data"]; - page = categoriesListJson.map((part) => SparePart.fromJson(part,true)).toList(); + page = categoriesListJson.map((part) => SparePart.fromJson(part, true)).toList(); } return page; } catch (error) { diff --git a/lib/controllers/providers/api/ppm_provider.dart b/lib/controllers/providers/api/ppm_provider.dart index 81a7e066..19d222cc 100644 --- a/lib/controllers/providers/api/ppm_provider.dart +++ b/lib/controllers/providers/api/ppm_provider.dart @@ -9,9 +9,12 @@ import 'package:test_sa/controllers/api_routes/api_manager.dart'; import 'package:test_sa/controllers/api_routes/urls.dart'; import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/models/generic_attachment_model.dart'; +import 'package:test_sa/models/helper_data_models/spare_part/activity_spare_part_model.dart'; +import 'package:test_sa/models/helper_data_models/workorder/work_order_helper_models.dart'; import 'package:test_sa/models/plan_preventive_visit/plan_preventive_visit_model.dart'; import 'package:test_sa/models/ppm/ppm.dart'; import 'package:test_sa/models/ppm/ppm_search.dart'; +import 'package:test_sa/models/service_request/spare_parts.dart'; import 'package:test_sa/models/user.dart'; import '../../../new_views/common_widgets/app_lazy_loading.dart'; @@ -20,6 +23,15 @@ class PpmProvider extends ChangeNotifier { // number of items call in each request final pageItemNumber = 12; + bool _isReadOnlyRequest = false; + + bool get isReadOnlyRequest => _isReadOnlyRequest; + + set isReadOnlyRequest(bool value) { + _isReadOnlyRequest = value; + notifyListeners(); + } + PlanPreventiveVisit? _planPreventiveVisit; late int _totalTabs = 4; TabController? tabController; @@ -60,6 +72,14 @@ class PpmProvider extends ChangeNotifier { nextPage = true; stateCode = null; } + List _storeAvailability = []; + + List get storeAvailability => _storeAvailability; + + set storeAvailability(List value) { + _storeAvailability = value; + notifyListeners(); + } // state code of current request to defied error message // like 400 customer request failed @@ -78,6 +98,13 @@ class PpmProvider extends ChangeNotifier { bool isLoading = false; PpmSearch visitsSearch = PpmSearch(); + WorkOrderCostModel? workOrderCostModel; + SparePartHelperModel? sparePartHelperModel; + + void updateSparePartHelperModel(SparePartHelperModel? value) { + sparePartHelperModel = value; + notifyListeners(); + } /// return -2 if request in progress /// return -1 if error happen when sending request @@ -164,11 +191,24 @@ class PpmProvider extends ChangeNotifier { if (response.statusCode >= 200 && response.statusCode < 300) { ppmPlanAttachments = []; planPreventiveVisit = PlanPreventiveVisit.fromJson(json.decode(response.body)["data"]); + + // Set isReadOnly based on visit status (1 or 3 means closed/completed) if (planPreventiveVisit?.visitStatus?.value! == 1 || planPreventiveVisit?.visitStatus?.value == 3) { isReadOnly = true; } else { isReadOnly = false; } + + // Set isReadOnlyRequest based on visit status - same logic as CMDetailProvider + //Need to confirm this with backend Status values: 3 = Closed, 5 = Completed, 6 = Cancelled (adjust based on your PM status values) + if (planPreventiveVisit?.visitStatus?.value == 3 || + planPreventiveVisit?.visitStatus?.value == 5 || + planPreventiveVisit?.visitStatus?.value == 6) { + isReadOnlyRequest = true; + } else { + isReadOnlyRequest = false; + } + isLoading = false; notifyListeners(); return planPreventiveVisit; @@ -364,4 +404,123 @@ class PpmProvider extends ChangeNotifier { return true; } + + Future createActivitySparePart() async { + isLoading = true; + Response response; + try { + response = await ApiManager.instance.post( + URLs.createPmActivitySparePart, + body: sparePartHelperModel!.toPMJson(), + ); + stateCode = response.statusCode; + isLoading = false; + notifyListeners(); + if (stateCode == 200) { + return true; + } + return false; + } catch (error) { + isLoading = false; + stateCode = -1; + notifyListeners(); + return false; + } + } + + Future updateActivitySparePart() async { + isLoading = true; + Response response; + try { + response = await ApiManager.instance.post(URLs.updatePmActivitySparePart, body: sparePartHelperModel!.toPMJson()); + stateCode = response.statusCode; + isLoading = false; + notifyListeners(); + if (stateCode == 200) { + return true; + } + return false; + } catch (error) { + isLoading = false; + stateCode = -1; + notifyListeners(); + return false; + } + } + + Future deleteActivitySparePart({required int id}) async { + isLoading = true; + notifyListeners(); + try { + final response = await ApiManager.instance.delete('${URLs.deletePmActivitySparePart}?activitySparePartPPMId=$id', body: {}); + stateCode = response.statusCode; + if (response.statusCode >= 200 && response.statusCode < 300) { + } + isLoading = false; + notifyListeners(); + return response.statusCode; + } catch (error) { + isLoading = false; + notifyListeners(); + return -1; + } + } + + Future engineerUpdateCost() async { + isLoading = true; + Response response; + try { + response = await ApiManager.instance.post(URLs.updatePmActivityCost, body: workOrderCostModel!.toPMCostJson()); + stateCode = response.statusCode; + isLoading = false; + notifyListeners(); + if (stateCode == 200) { + planPreventiveVisit = PlanPreventiveVisit.fromJson(json.decode(response.body)["data"]); + + // Update isReadOnlyRequest after fetching updated data + if (planPreventiveVisit?.visitStatus?.value == 3 || + planPreventiveVisit?.visitStatus?.value == 5 || + planPreventiveVisit?.visitStatus?.value == 6) { + isReadOnlyRequest = true; + } else { + isReadOnlyRequest = false; + } + + notifyListeners(); + return true; + } + return false; + } catch (error) { + isLoading = false; + stateCode = -1; + notifyListeners(); + return false; + } + } + + Future getStoreAvailabilityById({required num? partId}) async { + try { + isLoading = true; + notifyListeners(); + + final response = await ApiManager.instance.post( + URLs.getSparePartAvailableQuantity + "?PartId=$partId", + body: {}, + ); + + stateCode = response.statusCode; + + if (response.statusCode >= 200 && response.statusCode < 300) { + final List data = json.decode(response.body)["data"]; + storeAvailability = data.map((item) => StoreAvailability.fromJson(item)).toList(); + } + + isLoading = false; + notifyListeners(); + } catch (e) { + log("getStoreAvalivility [error] : $e"); + isLoading = false; + notifyListeners(); + } + } } diff --git a/lib/models/helper_data_models/spare_part/activity_spare_part_model.dart b/lib/models/helper_data_models/spare_part/activity_spare_part_model.dart index 1c51821f..548d47bb 100644 --- a/lib/models/helper_data_models/spare_part/activity_spare_part_model.dart +++ b/lib/models/helper_data_models/spare_part/activity_spare_part_model.dart @@ -4,6 +4,7 @@ import 'package:test_sa/models/service_request/spare_parts.dart'; class SparePartHelperModel { int? id; int? workOrderId; + String? pmVisitId; int? partCatalogItemId; int? activityStatusId; SparePart? sparePart; @@ -12,11 +13,13 @@ class SparePartHelperModel { num? installQty; num? returnQty; String? comment; - List? sparePartAttachments; + + List sparePartAttachments; SparePartHelperModel({ this.id, this.workOrderId, + this.pmVisitId, this.partCatalogItemId, this.quantity, this.installQty, @@ -24,9 +27,9 @@ class SparePartHelperModel { this.sparePart, this.activityStatusId, this.activityStatus, - this.comment,this.sparePartAttachments, - }); - + this.comment, + List? sparePartAttachments, + }) : sparePartAttachments = sparePartAttachments ?? []; Map toJson() { final Map data = {}; @@ -36,13 +39,42 @@ class SparePartHelperModel { data['quantity'] = quantity; data['installQty'] = installQty; data['returnQty'] = returnQty; - if(activityStatus!=null){ + if (activityStatus != null) { data['activityStatusId'] = activityStatus?.id; } data['comment'] = comment; - if (sparePartAttachments != null) { - data['acitiySparePartAttachments'] = - sparePartAttachments!.map((v) => v.toJson()).toList(); + if (sparePartAttachments.isNotEmpty) { + data['acitiySparePartAttachments'] = sparePartAttachments!.map((v) => v.toJson()).toList(); + } + return data; + } + + //{ + // "id": 0, + // "planPreventiveVisitId": 0, + // "partCatalogItemId": 0, + // "quantity": 0, + // "comment": "string", + // "acitiySparePartAttachments": [ + // { + // "id": 0, + // "name": "string", + // "originalName": "string", + // "attachmentTypeId": 0, + // "attachmentName": "string", + // "attachmentURL": "string" + // } + // ] + // } + Map toPMJson() { + final Map data = {}; + data['id'] = id; + data['planPreventiveVisitId'] = pmVisitId; + data['partCatalogItemId'] = sparePart?.id; + data['quantity'] = quantity; + data['comment'] = comment; + if (sparePartAttachments.isNotEmpty) { + data['acitiySparePartAttachments'] = sparePartAttachments!.map((v) => v.toJson()).toList(); } return data; } @@ -65,4 +97,4 @@ class SparePartAttachments { data['name'] = name; return data; } -} \ No newline at end of file +} diff --git a/lib/models/helper_data_models/workorder/work_order_helper_models.dart b/lib/models/helper_data_models/workorder/work_order_helper_models.dart index b15924d6..5515c27d 100644 --- a/lib/models/helper_data_models/workorder/work_order_helper_models.dart +++ b/lib/models/helper_data_models/workorder/work_order_helper_models.dart @@ -178,4 +178,7 @@ class WorkOrderCostModel { Map toJson() { return {'workOrderId': workOrderId, 'sparePartCost': sparePartCost, 'laborCost': labourCost, 'travelCost': travelCost, 'qAmount': qAmount, 'prNo': prNo, 'poNo': poNo,'mrNo':mrNo,'exchangeCost':exchangeCost}; } + Map toPMCostJson() { + return {'planPreventiveVisitId': workOrderId, 'sparePartCost': sparePartCost, 'laborCost': labourCost, 'travelCost': travelCost, 'qAmount': qAmount, 'prNo': prNo, 'poNo': poNo,'mrNo':mrNo,'exchangeCost':exchangeCost}; + } } diff --git a/lib/models/plan_preventive_visit/plan_preventive_visit_model.dart b/lib/models/plan_preventive_visit/plan_preventive_visit_model.dart index b8f5fd94..87087c3b 100644 --- a/lib/models/plan_preventive_visit/plan_preventive_visit_model.dart +++ b/lib/models/plan_preventive_visit/plan_preventive_visit_model.dart @@ -1,9 +1,11 @@ +import 'dart:developer'; import 'dart:io'; import 'package:flutter/widgets.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/models/device/asset.dart'; +import 'package:test_sa/models/helper_data_models/spare_part/activity_spare_part_model.dart'; import 'package:test_sa/models/lookup.dart'; import 'package:test_sa/models/service_request/supplier_details.dart'; import 'package:test_sa/models/timer_model.dart'; @@ -49,10 +51,20 @@ class PlanPreventiveVisit { List? preventiveVisitKits; List? preventiveVisitTimers; List? preventiveVisitSuppliers; + List? activities; + num? sparePartCost; + num? labourCost; + num? travelCost; + num? qAmount; + String? prNo; + String? poNo; + String? mrNo; + num? exchangeCost; TimerModel? tbsTimer = TimerModel(); List? timerModelList = []; TimerModel? ppMTimePicker; + PlanPreventiveVisit( {this.id, this.visitNo, @@ -95,6 +107,15 @@ class PlanPreventiveVisit { this.preventiveVisitTimers, this.timerModelList, this.ppMTimePicker, + this.activities, + this.labourCost, + this.sparePartCost, + this.travelCost, + this.qAmount, + this.prNo, + this.poNo, + this.mrNo, + this.exchangeCost, this.preventiveVisitSuppliers}); PlanPreventiveVisit.fromJson(Map json) { @@ -131,6 +152,14 @@ class PlanPreventiveVisit { safety = json['safety'] != null ? Lookup.fromJson(json['safety']) : null; engSignature = json['engSignature']; nurseSignature = json['nurseSignature']; + travelCost= json['travelCost']; + sparePartCost= json['sparePartCost']; + labourCost =json['laborCost']; + qAmount= json['qAmount']; + prNo= json['prNo']; + poNo= json['poNo']; + mrNo= json['mrNo']; + exchangeCost= json['exchangeCost']; if (json['preventiveVisitAttachments'] != null) { preventiveVisitAttachments = []; json['preventiveVisitAttachments'].forEach((v) { @@ -167,6 +196,13 @@ class PlanPreventiveVisit { preventiveVisitSuppliers!.add(PreventiveVisitSuppliers.fromJson(v)); }); } + if (json['activitySparePartsPPM'] != null) { + log("activitySparePartsPPM: ${json['activitySparePartsPPM']}"); + activities = []; + json['activitySparePartsPPM'].forEach((v) { + activities!.add(PMSparePartActivityModel.fromJson(v)); + }); + } } Map toJson({required int status}) { @@ -183,50 +219,6 @@ class PlanPreventiveVisit { data['safetyId'] = safety?.id; data['engSignature'] = engSignature; data['nurseSignature'] = nurseSignature; - // - // if (asset != null) { - // data['asset'] = asset!.toJson(); - // } - // data['visitNo'] = visitNo; - // data['planNo'] = planNo; - // data['planName'] = planName; - // data['nextPMDate'] = nextPMDate; - // data['assetName'] = assetName; - // data['model'] = model; - // data['manufacturer'] = manufacturer; - // data['supplierName'] = supplierName; - // data['siteName'] = siteName; - // data['buildingName'] = buildingName; - // data['floorName'] = floorName; - // data['departmentName'] = departmentName; - // data['roomName'] = roomName; - // data['fromDate'] = fromDate; - // data['toDate'] = toDate; - // if (assignedEmployee != null) { - // data['assignedEmployee'] = assignedEmployee!.toJson(); - // } - // data['acutalDateOfVisit'] = acutalDateOfVisit; - // if (typeOfService != null) { - // data['typeOfService'] = typeOfService!.toJson(); - // } - // if (visitStatus != null) { - // data['visitStatus'] = visitStatus!.toJson(); - // } - // data['travelingHours'] = travelingHours; - // data['comments'] = comments; - // data['executionTimeFrame'] = executionTimeFrame; - // if (taskStatus != null) { - // data['taskStatus'] = taskStatus!.toJson(); - // } - // if (deviceStatus != null) { - // data['deviceStatus'] = deviceStatus!.toJson(); - // } - // data['assetAvailability'] = assetAvailability; - // if (safety != null) { - // data['safety'] = safety!.toJson(); - // } - // data['engSignature'] = engSignature; - // data['nurseSignature'] = nurseSignature; if (preventiveVisitAttachments != null) { data['preventiveVisitAttachments'] = preventiveVisitAttachments!.map((v) => v.toJson()).toList(); @@ -246,14 +238,12 @@ class PlanPreventiveVisit { if (preventiveVisitSuppliers != null) { data['preventiveVisitSuppliers'] = preventiveVisitSuppliers!.map((v) => v.toJson()).toList(); } + if (activities != null) { + data['activitySparePartsPPM'] = activities!.map((v) => v.toJson()).toList(); + } return data; } - bool _isLocalUrl(String url) { - if (url.isEmpty != false) return false; - return url.startsWith("/") || url.startsWith("file://") || url.substring(1).startsWith(':\\'); - } - Future validate(BuildContext context) async { if (visitStatus?.id == null) { await Fluttertoast.showToast(msg: "${context.translation.youHaveToSelect} ${context.translation.status}"); @@ -290,276 +280,6 @@ class PlanPreventiveVisit { } } -// class PlanPreventiveVisit { -// String? id; -// String? visitNo; -// Asset? asset; -// String? planNo; -// String? planName; -// String? nextPMDate; -// String? assetName; -// String? model; -// String? manufacturer; -// String? supplierName; -// String? siteName; -// String? buildingName; -// String? floorName; -// String? departmentName; -// String? roomName; -// String? fromDate; -// String? toDate; -// AssignedEmployee? assignedEmployee; -// String? acutalDateOfVisit; -// TypeOfService? typeOfService; -// VisitStatus? visitStatus; -// String? travelingHours; -// String? comments; -// int? executionTimeFrame; -// Lookup? taskStatus; -// String? deviceStatus; -// Lookup? assetAvailability; -// Lookup? safety; -// String? engSignature; -// String? nurseSignature; -// List? preventiveVisitAttachments; -// List? preventiveVisitCalibrations; -// List? preventiveVisitChecklists; -// List? preventiveVisitKits; -// List? preventiveVisitTimers; -// List? preventiveVisitSuppliers; -// TimerModel? tbsTimer = TimerModel(); -// -// PlanPreventiveVisit( -// {this.id, -// this.visitNo, -// this.asset, -// this.planNo, -// this.planName, -// this.nextPMDate, -// this.assetName, -// this.model, -// this.manufacturer, -// this.supplierName, -// this.siteName, -// this.buildingName, -// this.floorName, -// this.departmentName, -// this.roomName, -// this.fromDate, -// this.toDate, -// this.assignedEmployee, -// this.acutalDateOfVisit, -// this.typeOfService, -// this.visitStatus, -// this.travelingHours, -// this.comments, -// this.executionTimeFrame, -// this.taskStatus, -// this.deviceStatus, -// this.assetAvailability, -// this.safety, -// this.engSignature, -// this.nurseSignature, -// this.preventiveVisitAttachments, -// this.preventiveVisitCalibrations, -// this.preventiveVisitChecklists, -// this.preventiveVisitKits, -// this.preventiveVisitTimers, -// this.preventiveVisitSuppliers}); -// -// PlanPreventiveVisit.fromJson(Map json) { -// id = json['id']; -// visitNo = json['visitNo']; -// asset = json['asset'] != null ? Asset.fromJson(json['asset']) : null; -// planNo = json['planNo']; -// planName = json['planName']; -// nextPMDate = json['nextPMDate']; -// assetName = json['assetName']; -// model = json['model']; -// manufacturer = json['manufacturer']; -// supplierName = json['supplierName']; -// siteName = json['siteName']; -// buildingName = json['buildingName']; -// floorName = json['floorName']; -// departmentName = json['departmentName']; -// roomName = json['roomName']; -// fromDate = json['fromDate']; -// toDate = json['toDate']; -// assignedEmployee = json['assignedEmployee'] != null ? AssignedEmployee.fromJson(json['assignedEmployee']) : null; -// acutalDateOfVisit = json['acutalDateOfVisit']; -// typeOfService = json['typeOfService'] != null ? TypeOfService.fromJson(json['typeOfService']) : null; -// visitStatus = json['visitStatus'] != null ? VisitStatus.fromJson(json['visitStatus']) : null; -// travelingHours = json['travelingHours']; -// comments = json['comments']; -// executionTimeFrame = json['executionTimeFrame']; -// taskStatus = json['taskStatus']; -// deviceStatus = json['deviceStatus']; -// assetAvailability = json['assetAvailability']; -// safety = json['safety']; -// engSignature = json['engSignature']; -// nurseSignature = json['nurseSignature']; -// if (json['preventiveVisitAttachments'] != null) { -// preventiveVisitAttachments = []; -// json['preventiveVisitAttachments'].forEach((v) { -// preventiveVisitAttachments!.add(PreventiveVisitChecklists.fromJson(v)); -// }); -// } -// if (json['preventiveVisitCalibrations'] != null) { -// preventiveVisitCalibrations = []; -// json['preventiveVisitCalibrations'].forEach((v) { -// preventiveVisitCalibrations!.add( PreventiveVisitChecklists.fromJson(v)); -// }); -// } -// if (json['preventiveVisitChecklists'] != null) { -// preventiveVisitChecklists = []; -// json['preventiveVisitChecklists'].forEach((v) { -// preventiveVisitChecklists!.add( PreventiveVisitChecklists.fromJson(v)); -// }); -// } -// if (json['preventiveVisitKits'] != null) { -// preventiveVisitKits = []; -// json['preventiveVisitKits'].forEach((v) { -// preventiveVisitKits!.add( PreventiveVisitChecklists.fromJson(v)); -// }); -// } -// if (json['preventiveVisitTimers'] != null) { -// preventiveVisitTimers = []; -// json['preventiveVisitTimers'].forEach((v) { -// preventiveVisitTimers!.add( PreventiveVisitTimers.fromJson(v)); -// }); -// } -// if (json['preventiveVisitSuppliers'] != null) { -// preventiveVisitSuppliers = []; -// json['preventiveVisitSuppliers'].forEach((v) { -// preventiveVisitSuppliers!.add( PreventiveVisitChecklists.fromJson(v)); -// }); -// } -// } -// -// Map toJson() { -// final Map data = Map(); -// data['id'] = this.id; -// data['visitNo'] = this.visitNo; -// if (this.asset != null) { -// data['asset'] = this.asset!.toJson(); -// } -// data['planNo'] = this.planNo; -// data['planName'] = this.planName; -// data['nextPMDate'] = this.nextPMDate; -// data['assetName'] = this.assetName; -// data['model'] = this.model; -// data['manufacturer'] = this.manufacturer; -// data['supplierName'] = this.supplierName; -// data['siteName'] = this.siteName; -// data['buildingName'] = this.buildingName; -// data['floorName'] = this.floorName; -// data['departmentName'] = this.departmentName; -// data['roomName'] = this.roomName; -// data['fromDate'] = this.fromDate; -// data['toDate'] = this.toDate; -// if (this.assignedEmployee != null) { -// data['assignedEmployee'] = this.assignedEmployee!.toJson(); -// } -// data['acutalDateOfVisit'] = this.acutalDateOfVisit; -// if (this.typeOfService != null) { -// data['typeOfService'] = this.typeOfService!.toJson(); -// } -// if (this.visitStatus != null) { -// data['visitStatus'] = this.visitStatus!.toJson(); -// } -// data['travelingHours'] = this.travelingHours; -// data['comments'] = this.comments; -// data['executionTimeFrame'] = this.executionTimeFrame; -// data['taskStatus'] = this.taskStatus; -// data['deviceStatus'] = this.deviceStatus; -// data['assetAvailability'] = this.assetAvailability; -// data['safety'] = this.safety; -// data['engSignature'] = this.engSignature; -// data['nurseSignature'] = this.nurseSignature; -// if (this.preventiveVisitAttachments != null) { -// data['preventiveVisitAttachments'] = this.preventiveVisitAttachments!.map((v) => v.toJson()).toList(); -// } -// if (this.preventiveVisitCalibrations != null) { -// data['preventiveVisitCalibrations'] = this.preventiveVisitCalibrations!.map((v) => v.toJson()).toList(); -// } -// if (this.preventiveVisitChecklists != null) { -// data['preventiveVisitChecklists'] = this.preventiveVisitChecklists!.map((v) => v.toJson()).toList(); -// } -// if (this.preventiveVisitKits != null) { -// data['preventiveVisitKits'] = this.preventiveVisitKits!.map((v) => v.toJson()).toList(); -// } -// if (this.preventiveVisitTimers != null) { -// data['preventiveVisitTimers'] = this.preventiveVisitTimers!.map((v) => v.toJson()).toList(); -// } -// if (this.preventiveVisitSuppliers != null) { -// data['preventiveVisitSuppliers'] = this.preventiveVisitSuppliers!.map((v) => v.toJson()).toList(); -// } -// return data; -// } -// -// bool _isLocalUrl(String url) { -// if (url.isEmpty != false) return false; -// return url.startsWith("/") || url.startsWith("file://") || url.substring(1).startsWith(':\\'); -// } -// -// Future validate(BuildContext context) async { -// if (visitStatus?.id == null) { -// await Fluttertoast.showToast(msg: "${context.translation.youHaveToSelect} ${context.translation.status}"); -// return false; -// } -// if (acutalDateOfVisit == null) { -// await Fluttertoast.showToast(msg: "${context.translation.youHaveToSelect} ${context.translation.actualDate}"); -// return false; -// } -// // if (expectedDate == null) { -// // await Fluttertoast.showToast(msg: "${context.translation.youHaveToSelect} ${context.translation.visitDate}"); -// // return false; -// // } -// if (tbsTimer?.startAt == null) { -// await Fluttertoast.showToast(msg: "Working Hours Required"); -// return false; -// } -// if (tbsTimer?.endAt == null) { -// await Fluttertoast.showToast(msg: "Please Stop The Timer"); -// return false; -// } -// -// // if (externalEngineerTimer?.startAt != null && externalEngineerTimer?.endAt == null) { -// // await Fluttertoast.showToast(msg: "Please Stop External Engineer Timer"); -// // return false; -// // } -// -// return true; -// } -// -// void removeEmptyObjects() { -// // if (vCalibrationTools?.isNotEmpty ?? false) vCalibrationTools!.removeWhere((element) => element.assetId == null && element.calibrationDateOfTesters == null); -// // if (vKits?.isNotEmpty ?? false) vKits!.removeWhere((element) => element.partName == null && element.partNumber == null); -// } -// } - -// class Asset { -// num? id; -// String? assetNumber; -// String? assetSerialNo; -// -// Asset({this.id, this.assetNumber, this.assetSerialNo}); -// -// Asset.fromJson(Map json) { -// id = json['id']; -// assetNumber = json['assetNumber']; -// assetSerialNo = json['assetSerialNo']; -// } -// -// Map toJson() { -// final Map data = Map(); -// data['id'] = this.id; -// data['assetNumber'] = this.assetNumber; -// data['assetSerialNo'] = this.assetSerialNo; -// return data; -// } -// } - class AssignedEmployee { String? userId; String? userName; @@ -588,50 +308,6 @@ class AssignedEmployee { } } -// class Lookup { -// int? id; -// String? name; -// int? value; -// -// Lookup({this.id, this.name, this.value}); -// -// Lookup.fromJson(Map json) { -// id = json['id']; -// name = json['name']; -// value = json['value']; -// } -// -// Map toJson() { -// final Map data = Map(); -// data['id'] = this.id; -// data['name'] = this.name; -// data['value'] = this.value; -// return data; -// } -// } - -// class VisitStatus { -// int? id; -// String? name; -// int? value; -// -// VisitStatus({this.id, this.name, this.value}); -// -// VisitStatus.fromJson(Map json) { -// id = json['id']; -// name = json['name']; -// value = json['value']; -// } -// -// Map toJson() { -// final Map data = Map(); -// data['id'] = this.id; -// data['name'] = this.name; -// data['value'] = this.value; -// return data; -// } -// } - class PreventiveVisitChecklists { int? id; Lookup? taskStatus; @@ -788,171 +464,6 @@ class PreventiveVisitSuppliers { } } -// class Supplier { -// int? id; -// String? suppliername; -// String? name; -// String? website; -// String? email; -// String? code; -// int? suppNo; -// String? suppStatusId; -// String? cityId; -// String? person; -// String? comment; -// String? zipcode; -// String? contact; -// List? telephones; -// List? faxes; -// List? addresses; -// List? attachments; -// List? suppPersons; -// List? suppTCodes; -// -// Supplier( -// {this.id, -// this.suppliername, -// this.name, -// this.website, -// this.email, -// this.code, -// this.suppNo, -// this.suppStatusId, -// this.cityId, -// this.person, -// this.comment, -// this.zipcode, -// this.contact, -// this.telephones, -// this.faxes, -// this.addresses, -// this.attachments, -// this.suppPersons, -// this.suppTCodes}); -// -// Supplier.fromJson(Map json) { -// id = json['id']; -// suppliername = json['suppliername']; -// name = json['name']; -// website = json['website']; -// email = json['email']; -// code = json['code']; -// suppNo = json['suppNo']; -// suppStatusId = json['suppStatusId']; -// cityId = json['cityId']; -// person = json['person']; -// comment = json['comment']; -// zipcode = json['zipcode']; -// contact = json['contact']; -// if (json['telephones'] != null) { -// telephones = []; -// json['telephones'].forEach((v) { -// telephones!.add(v); -// }); -// } -// if (json['faxes'] != null) { -// faxes = []; -// json['faxes'].forEach((v) { -// faxes!.add(v); -// }); -// } -// if (json['addresses'] != null) { -// addresses = []; -// json['addresses'].forEach((v) { -// addresses!.add(v); -// }); -// } -// if (json['attachments'] != null) { -// attachments = []; -// json['attachments'].forEach((v) { -// attachments!.add(v); -// }); -// } -// if (json['suppPersons'] != null) { -// suppPersons = []; -// json['suppPersons'].forEach((v) { -// suppPersons!.add(SuppPersons.fromJson(v)); -// }); -// } -// if (json['suppTCodes'] != null) { -// suppTCodes = []; -// json['suppTCodes'].forEach((v) { -// suppTCodes!.add(v); -// }); -// } -// } -// -// Map toJson() { -// final Map data = {}; -// data['id'] = id; -// data['suppliername'] = suppliername; -// data['name'] = name; -// data['website'] = website; -// data['email'] = email; -// data['code'] = code; -// data['suppNo'] = suppNo; -// data['suppStatusId'] = suppStatusId; -// data['cityId'] = cityId; -// data['person'] = person; -// data['comment'] = comment; -// data['zipcode'] = zipcode; -// data['contact'] = contact; -// if (telephones != null) { -// data['telephones'] = telephones!.map((v) => v).toList(); -// } -// if (faxes != null) { -// data['faxes'] = faxes!.map((v) => v).toList(); -// } -// if (addresses != null) { -// data['addresses'] = addresses!.map((v) => v).toList(); -// } -// if (attachments != null) { -// data['attachments'] = attachments!.map((v) => v).toList(); -// } -// if (suppPersons != null) { -// data['suppPersons'] = suppPersons!.map((v) => v.toJson()).toList(); -// } -// if (suppTCodes != null) { -// data['suppTCodes'] = suppTCodes!.map((v) => v).toList(); -// } -// return data; -// } -// } - -// class SuppPersons { -// int? id; -// int? supplierId; -// String? personName; -// int? personRoleId; -// String? contact; -// String? externalEngCode; -// String? email; -// -// SuppPersons({this.id, this.supplierId, this.personName, this.personRoleId, this.contact, this.externalEngCode, this.email}); -// -// SuppPersons.fromJson(Map json) { -// id = json['id']; -// supplierId = json['supplierId']; -// personName = json['personName']; -// personRoleId = json['personRoleId']; -// contact = json['contact']; -// externalEngCode = json['externalEngCode']; -// email = json['email']; -// } -// -// Map toJson() { -// final Map data = Map(); -// data['id'] = this.id; -// data['supplierId'] = this.supplierId; -// data['personName'] = this.personName; -// data['personRoleId'] = this.personRoleId; -// data['contact'] = this.contact; -// data['externalEngCode'] = this.externalEngCode; -// data['email'] = this.email; -// return data; -// } -// } - class PreventiveVisitKits { int? id; PartCatalogItem? partCatalogItem; @@ -999,3 +510,80 @@ class PartCatalogItem { return data; } } + +class PMSparePartActivityModel { + int? id; + PartCatalogItem? partCatalogItem; + Lookup? activityStatus; + double? quantity; + double? installQty; + double? returnQty; + String? comment; + dynamic price; + + List activitySparePartPPMAttachments; + + PMSparePartActivityModel({ + this.id, + this.partCatalogItem, + this.activityStatus, + this.quantity, + this.installQty, + this.returnQty, + this.comment, + this.price, + List? activitySparePartPPMAttachments, + }) : activitySparePartPPMAttachments = activitySparePartPPMAttachments ?? []; + + PMSparePartActivityModel.fromJson(Map json) : activitySparePartPPMAttachments = [] { + id = json['id']; + + partCatalogItem = json['partCatalogItem'] != null ? PartCatalogItem.fromJson(json['partCatalogItem']) : null; + + activityStatus = json['activityStatus'] != null ? Lookup.fromJson(json['activityStatus']) : null; + + quantity = json['quantity']?.toDouble(); + installQty = json['installQty']?.toDouble(); + returnQty = json['returnQty']?.toDouble(); + + comment = json['comment']; + price = json['price']; + + // Handle typo in API response: "acitiySparePartPPMAttachments" instead of "activitySparePartPPMAttachments" + final attachmentsKey = json.containsKey('acitiySparePartPPMAttachments') + ? 'acitiySparePartPPMAttachments' + : 'activitySparePartPPMAttachments'; + + activitySparePartPPMAttachments = json[attachmentsKey] == null + ? [] + : List.from(json[attachmentsKey]).map((e) => SparePartAttachments.fromJson(e)).toList(); + } + + Map toJson() { + final Map data = {}; + + data['id'] = id; + + if (partCatalogItem != null) { + data['partCatalogItem'] = partCatalogItem!.toJson(); + } + + if (activityStatus != null) { + data['activityStatus'] = activityStatus!.toJson(); + } + + data['quantity'] = quantity; + data['installQty'] = installQty; + data['returnQty'] = returnQty; + data['comment'] = comment; + data['price'] = price; + + data['activitySparePartPPMAttachments'] = activitySparePartPPMAttachments + .map( + (v) => v.toJson(), + ) + .toList(); + + return data; + } +} diff --git a/lib/modules/cm_module/views/components/activities_list_view.dart b/lib/modules/cm_module/views/components/activities_list_view.dart index d0294f6d..b589e787 100644 --- a/lib/modules/cm_module/views/components/activities_list_view.dart +++ b/lib/modules/cm_module/views/components/activities_list_view.dart @@ -251,7 +251,6 @@ class _ActivitiesListViewState extends State { label: context.translation.assistantEmployee, value: activity.activityMaintenance?.assistantEmployees?.first.user?.userName, ), - ] else ...[ InfoTextWidget( label: context.translation.assignedEmployee, @@ -344,29 +343,30 @@ class _ActivitiesListViewState extends State { } void editSparePartRequest({required BuildContext context, required CMDetailProvider requestDetailProvider, required Activities activity}) async { - requestDetailProvider.sparePartHelperModel = SparePartHelperModel( + final model = SparePartHelperModel( id: activity.id, workOrderId: requestDetailProvider.currentWorkOrder?.data?.requestId, comment: activity.activitySparePart?.comment, sparePartAttachments: activity.activitySparePart?.acitiySparePartAttachments ?? [], activityStatus: activity.activityStatus, sparePart: SparePart( - id: activity.activitySparePart?.partCatalogItem?.id, - partName: activity.activitySparePart?.partCatalogItem?.partName, - partNo: activity.activitySparePart?.partCatalogItem?.partNumber, - oracleCode: activity.activitySparePart?.partCatalogItem?.oracleCode), + id: activity.activitySparePart?.partCatalogItem?.id, + partName: activity.activitySparePart?.partCatalogItem?.partName, + partNo: activity.activitySparePart?.partCatalogItem?.partNumber, + oracleCode: activity.activitySparePart?.partCatalogItem?.oracleCode, + ), quantity: activity.activitySparePart?.quantity, installQty: activity.activitySparePart?.installQty, returnQty: activity.activitySparePart?.returnQty, activityStatusId: activity.activityStatus?.id, ); - requestDetailProvider.updateSparePartHelperModel(requestDetailProvider.sparePartHelperModel); - + // Navigate and let SparePartRequest._initializeModel() handle provider setup Navigator.of(context).push(MaterialPageRoute( builder: (_) => SparePartRequest( - model: requestDetailProvider.sparePartHelperModel, + model: model, provider: requestDetailProvider, + fromCm: true, ), )); } diff --git a/lib/modules/cm_module/views/components/bottom_sheets/service_request_bottomsheet.dart b/lib/modules/cm_module/views/components/bottom_sheets/service_request_bottomsheet.dart index 72e0b6f9..2272212a 100644 --- a/lib/modules/cm_module/views/components/bottom_sheets/service_request_bottomsheet.dart +++ b/lib/modules/cm_module/views/components/bottom_sheets/service_request_bottomsheet.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'package:test_sa/app_strings/app_asset.dart'; +import 'package:test_sa/controllers/providers/api/ppm_provider.dart'; import 'package:test_sa/controllers/providers/api/service_requests_provider.dart'; import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/int_extensions.dart'; @@ -671,14 +672,20 @@ class ServiceRequestBottomSheet { CMDetailProvider requestDetailProvider = Provider.of(context, listen: false); switch (index) { case 0: - requestDetailProvider.sparePartHelperModel = SparePartHelperModel(); + // Create fresh model - SparePartRequest will handle setting it in provider + final model = SparePartHelperModel( + id: 0, + workOrderId: requestDetailProvider.currentWorkOrder?.data?.requestId, + ); Navigator.pushReplacement( context, MaterialPageRoute( - builder: (context) => SparePartRequest( - model: requestDetailProvider.sparePartHelperModel, - provider: requestDetailProvider, - )), + builder: (context) => SparePartRequest( + model: model, + provider: requestDetailProvider, + fromCm: true, + ), + ), ); break; case 1: @@ -772,7 +779,7 @@ class ServiceRequestBottomSheet { })); } - static Future addAnotherSpareRequestBottomSheet({required BuildContext context}) { + static Future addAnotherSpareRequestBottomSheet({required BuildContext context, bool fromCm = false}) { return buildBottomSheetParent( context: context, childWidget: Consumer( @@ -808,16 +815,23 @@ class ServiceRequestBottomSheet { buttonColor: AppColor.primary10, loading: false, onPressed: () { - CMDetailProvider provider = Provider.of(context, listen: false); + dynamic provider = fromCm ? Provider.of(context, listen: false) : Provider.of(context, listen: false); Navigator.pop(context); - provider.sparePartHelperModel = SparePartHelperModel(); + final model = SparePartHelperModel( + id: 0, + workOrderId: fromCm? provider.currentWorkOrder?.data?.requestId:null, + pmVisitId: !fromCm? provider.planPreventiveVisit?.id:null, + ); + Navigator.pushReplacement( context, MaterialPageRoute( - builder: (context) => SparePartRequest( - model: provider.sparePartHelperModel, - provider: provider, - )), + builder: (context) => SparePartRequest( + model: model, + provider: provider, + fromCm: fromCm, + ), + ), ); }, ).expanded, diff --git a/lib/modules/cm_module/views/components/service_request_detail_view.dart b/lib/modules/cm_module/views/components/service_request_detail_view.dart index 766c5586..6e4e3bd3 100644 --- a/lib/modules/cm_module/views/components/service_request_detail_view.dart +++ b/lib/modules/cm_module/views/components/service_request_detail_view.dart @@ -355,7 +355,6 @@ class _ServiceRequestDetailViewState extends State { context, MaterialPageRoute( builder: (context) => CostDetailFormScreen( - isEdit: true, model: provider.workOrderCostModel, provider: provider, ))); diff --git a/lib/modules/cm_module/views/forms/cost/cost_detail_form_screen.dart b/lib/modules/cm_module/views/forms/cost/cost_detail_form_screen.dart index 1c21e035..94b7f842 100644 --- a/lib/modules/cm_module/views/forms/cost/cost_detail_form_screen.dart +++ b/lib/modules/cm_module/views/forms/cost/cost_detail_form_screen.dart @@ -1,9 +1,10 @@ +import 'dart:developer'; + import 'package:flutter/material.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/models/helper_data_models/workorder/work_order_helper_models.dart'; -import 'package:test_sa/modules/cm_module/cm_detail_provider.dart'; import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; @@ -13,13 +14,11 @@ import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; class CostDetailFormScreen extends StatefulWidget { static const String id = "/cost-detail-form-screen"; - final bool isEdit; final WorkOrderCostModel? model; - final CMDetailProvider provider; + final dynamic provider; // CMDetailProvider or PpmProvider const CostDetailFormScreen({ Key? key, - required this.isEdit, required this.model, required this.provider, }) : super(key: key); @@ -28,13 +27,29 @@ class CostDetailFormScreen extends StatefulWidget { _CostDetailFormScreenState createState() => _CostDetailFormScreenState(); } -class _CostDetailFormScreenState extends State with TickerProviderStateMixin { - final GlobalKey _scaffoldKey = GlobalKey(); +class _CostDetailFormScreenState extends State { + late WorkOrderCostModel _localModel; + + @override + void initState() { + super.initState(); + _localModel = WorkOrderCostModel( + workOrderId: widget.model?.workOrderId, + labourCost: widget.model?.labourCost, + sparePartCost: widget.model?.sparePartCost, + travelCost: widget.model?.travelCost, + qAmount: widget.model?.qAmount, + prNo: widget.model?.prNo, + poNo: widget.model?.poNo, + mrNo: widget.model?.mrNo, + exchangeCost: widget.model?.exchangeCost, + ); + log('Initialized Cost Model: ${_localModel.toJson()}'); + } @override Widget build(BuildContext context) { return Scaffold( - key: _scaffoldKey, appBar: const DefaultAppBar(title: "Cost Details"), body: ListenableBuilder( listenable: widget.provider, @@ -43,142 +58,131 @@ class _CostDetailFormScreenState extends State with Ticker crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - AppTextFormField( - labelText: "Spare Part Cost", - backgroundColor: AppColor.neutral100, - initialValue: widget.model?.sparePartCost?.toString(), - textAlign: TextAlign.center, - enable: false, - labelStyle: AppTextStyles.textFieldLabelStyle, - textInputType: TextInputType.number, - showShadow: false, - onChange: (value) { - widget.model?.sparePartCost = num.parse(value); - }, - style: Theme.of(context).textTheme.titleMedium, - ), - 8.height, - AppTextFormField( - initialValue: widget.model?.labourCost?.toString(), - textAlign: TextAlign.center, - labelText: "Labor Cost", - backgroundColor: AppColor.fieldBgColor(context), - labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), - textInputType: const TextInputType.numberWithOptions(decimal: true), - showShadow: false, - onChange: (value) { - widget.model?.labourCost = num.parse(value); - }, - style: Theme.of(context).textTheme.titleMedium, - ), - 8.height, - AppTextFormField( - labelText: "Travel Cost", - backgroundColor: AppColor.fieldBgColor(context), - labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), - initialValue: widget.model?.travelCost?.toString(), - textAlign: TextAlign.center, - textInputType: const TextInputType.numberWithOptions(decimal: true), - showShadow: false, - onChange: (value) { - widget.model?.travelCost = num.parse(value); - }, - style: Theme.of(context).textTheme.titleMedium, - ), - 8.height, - AppTextFormField( - labelText: "Quot Amount", - backgroundColor: AppColor.fieldBgColor(context), - labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), - initialValue: widget.model?.qAmount?.toString(), - textAlign: TextAlign.center, - textInputType: const TextInputType.numberWithOptions(decimal: true), - showShadow: false, - onChange: (value) { - widget.model?.qAmount = num.parse(value); - }, - style: Theme.of(context).textTheme.titleMedium, - ), - 8.height, - AppTextFormField( - labelText: "PR No", - backgroundColor: AppColor.fieldBgColor(context), - labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), - initialValue: widget.model?.prNo, - textAlign: TextAlign.center, - textInputType: const TextInputType.numberWithOptions(decimal: true), - showShadow: false, - onChange: (value) { - widget.model?.prNo = value; - }, - style: Theme.of(context).textTheme.titleMedium, - ), - 8.height, - AppTextFormField( - labelText: "PO No", - backgroundColor: AppColor.fieldBgColor(context), - labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), - initialValue: widget.model?.poNo, - textAlign: TextAlign.center, - textInputType: const TextInputType.numberWithOptions(decimal: true), - showShadow: false, - onChange: (value) { - widget.model?.poNo = value; - }, - style: Theme.of(context).textTheme.titleMedium, - ), - 8.height, - AppTextFormField( - labelText: "MR No", - backgroundColor: AppColor.fieldBgColor(context), - labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), - initialValue: widget.model?.mrNo, - textAlign: TextAlign.center, - textInputType: const TextInputType.numberWithOptions(decimal: true), - showShadow: false, - onChange: (value) { - widget.model?.mrNo = value; - }, - style: Theme.of(context).textTheme.titleMedium, - ), - 8.height, - AppTextFormField( - labelText: "Exchange Cost", - backgroundColor: AppColor.fieldBgColor(context), - labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), - initialValue: widget.model?.exchangeCost != null ? widget.model?.exchangeCost.toString() : '', - textAlign: TextAlign.center, - textInputType: const TextInputType.numberWithOptions(decimal: true), - showShadow: false, - onChange: (value) { - widget.model?.exchangeCost = num.parse(value); - }, - style: Theme.of(context).textTheme.titleMedium, - ), - ], - ).toShadowContainer(context).paddingAll(16), - ).expanded, + Expanded( + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + AppTextFormField( + labelText: "Spare Part Cost", + backgroundColor: AppColor.neutral100, + initialValue: _localModel.sparePartCost?.toString() ?? '0', + textAlign: TextAlign.center, + enable: false, + labelStyle: AppTextStyles.textFieldLabelStyle, + textInputType: TextInputType.number, + showShadow: false, + style: Theme.of(context).textTheme.titleMedium, + ), + 8.height, + AppTextFormField( + initialValue: _localModel.labourCost?.toString() ?? '', + textAlign: TextAlign.center, + labelText: "Labor Cost", + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + textInputType: const TextInputType.numberWithOptions(decimal: true), + showShadow: false, + onChange: (value) { + _localModel.labourCost = num.tryParse(value); + }, + style: Theme.of(context).textTheme.titleMedium, + ), + 8.height, + AppTextFormField( + labelText: "Travel Cost", + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + initialValue: _localModel.travelCost?.toString() ?? '', + textAlign: TextAlign.center, + textInputType: const TextInputType.numberWithOptions(decimal: true), + showShadow: false, + onChange: (value) { + _localModel.travelCost = num.tryParse(value); + }, + style: Theme.of(context).textTheme.titleMedium, + ), + 8.height, + AppTextFormField( + labelText: "Quot Amount", + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + initialValue: _localModel.qAmount?.toString() ?? '', + textAlign: TextAlign.center, + textInputType: const TextInputType.numberWithOptions(decimal: true), + showShadow: false, + onChange: (value) { + _localModel.qAmount = num.tryParse(value); + }, + style: Theme.of(context).textTheme.titleMedium, + ), + 8.height, + AppTextFormField( + labelText: "PR No", + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + initialValue: _localModel.prNo ?? '', + textAlign: TextAlign.center, + textInputType: TextInputType.text, + showShadow: false, + onChange: (value) { + _localModel.prNo = value.isEmpty ? null : value; + }, + style: Theme.of(context).textTheme.titleMedium, + ), + 8.height, + AppTextFormField( + labelText: "PO No", + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + initialValue: _localModel.poNo ?? '', + textAlign: TextAlign.center, + textInputType: TextInputType.text, + showShadow: false, + onChange: (value) { + _localModel.poNo = value.isEmpty ? null : value; + }, + style: Theme.of(context).textTheme.titleMedium, + ), + 8.height, + AppTextFormField( + labelText: "MR No", + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + initialValue: _localModel.mrNo ?? '', + textAlign: TextAlign.center, + textInputType: TextInputType.text, + showShadow: false, + onChange: (value) { + _localModel.mrNo = value.isEmpty ? null : value; + }, + style: Theme.of(context).textTheme.titleMedium, + ), + 8.height, + AppTextFormField( + labelText: "Exchange Cost", + backgroundColor: AppColor.fieldBgColor(context), + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith(color: AppColor.textColor(context)), + initialValue: _localModel.exchangeCost?.toString() ?? '', + textAlign: TextAlign.center, + textInputType: const TextInputType.numberWithOptions(decimal: true), + showShadow: false, + onChange: (value) { + _localModel.exchangeCost = num.tryParse(value); + }, + style: Theme.of(context).textTheme.titleMedium, + ), + ], + ).toShadowContainer(context).paddingAll(16), + ), + ), FooterActionButton.footerContainer( context: context, child: AppFilledButton( label: "Update Cost Details", buttonColor: AppColor.primary10, - onPressed: () async { - if (validateForm()) { - showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading()); - await widget.provider.engineerUpdateCost().then((success) { - Navigator.pop(context); - if (success) { - Navigator.pop(context); - } - }); - } - }, + onPressed: () => _onSubmit(), ), ), ], @@ -188,11 +192,25 @@ class _CostDetailFormScreenState extends State with Ticker ); } - bool validateForm() { - bool isValid = true; - if (!widget.isEdit) { - return true; + Future _onSubmit() async { + widget.provider.workOrderCostModel = _localModel; + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => const AppLazyLoading(), + ); + + try { + final success = await widget.provider.engineerUpdateCost(); + + if (mounted) Navigator.pop(context); // Close loading + + if (success && mounted) { + Navigator.pop(context); + } + } catch (e) { + if (mounted) Navigator.pop(context); + log('Error updating cost: $e'); } - return isValid; } } diff --git a/lib/modules/cm_module/views/forms/spare_part/spare_part_request.dart b/lib/modules/cm_module/views/forms/spare_part/spare_part_request.dart index 46a51f77..2cc85ef4 100644 --- a/lib/modules/cm_module/views/forms/spare_part/spare_part_request.dart +++ b/lib/modules/cm_module/views/forms/spare_part/spare_part_request.dart @@ -1,7 +1,7 @@ -import 'dart:async'; + import 'dart:convert'; +import 'dart:developer'; import 'dart:io'; - import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:test_sa/controllers/providers/api/parts_provider.dart'; @@ -16,7 +16,6 @@ import 'package:test_sa/models/helper_data_models/spare_part/activity_spare_part import 'package:test_sa/models/lookup.dart'; import 'package:test_sa/models/service_request/spare_parts.dart'; import 'package:test_sa/models/size_config.dart'; -import 'package:test_sa/modules/cm_module/cm_detail_provider.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/cm_module/views/components/bottom_sheets/service_request_bottomsheet.dart'; @@ -33,339 +32,372 @@ import 'package:test_sa/views/widgets/loaders/no_data_found.dart'; class SparePartRequest extends StatefulWidget { static const String id = "/spare-part-request"; - final SparePartHelperModel ?model; - final CMDetailProvider provider; + final SparePartHelperModel? model; + final dynamic provider; + final bool fromCm; const SparePartRequest({ Key? key, required this.model, required this.provider, + this.fromCm = false, }) : super(key: key); @override _SparePartRequestState createState() => _SparePartRequestState(); } -class _SparePartRequestState extends State with TickerProviderStateMixin { +class _SparePartRequestState extends State { PartsProvider? _partsProvider; bool _isLoading = false; List _spareParts = []; + List _attachments = []; - List attachments = []; final GlobalKey _formKey = GlobalKey(); - final GlobalKey _scaffoldKey = GlobalKey(); - final TextEditingController _partQtyController = TextEditingController(); - final TextEditingController _installQtyController = TextEditingController(); - final TextEditingController _returnQtyController = TextEditingController(); - final TextEditingController _oracleNoController = TextEditingController(); - final TextEditingController _descriptionController = TextEditingController(); - Lookup statusLookup = Lookup.fromJson({"id": 5619, "name": "New", "value": 1}); - int? activityStatus; @override void initState() { super.initState(); _partsProvider = Provider.of(context, listen: false); - - // Initialize model with existing values or defaults - widget.provider.sparePartHelperModel = SparePartHelperModel( - id: widget.model?.id ?? 0, - workOrderId: widget.model?.workOrderId ?? widget.provider.currentWorkOrder?.data?.requestId, - sparePartAttachments: widget.model?.sparePartAttachments ?? [], - sparePart: widget.model?.sparePart ?? SparePart(), - quantity: widget.model?.quantity, - installQty: widget.model?.installQty, - returnQty: widget.model?.returnQty, - activityStatusId: widget.model?.activityStatusId ?? statusLookup.id, - activityStatus: widget.model?.activityStatus ?? statusLookup, - comment: widget.model?.comment ?? '', - ); - - _partQtyController.text = widget.provider.sparePartHelperModel?.quantity != null ? widget.provider.sparePartHelperModel!.quantity!.round().toString() : ''; - _installQtyController.text = widget.provider.sparePartHelperModel?.installQty != null ? widget.provider.sparePartHelperModel!.installQty!.round().toString() : ''; - _returnQtyController.text = widget.provider.sparePartHelperModel?.returnQty != null ? widget.provider.sparePartHelperModel!.returnQty!.round().toString() : ''; - _oracleNoController.text = widget.provider.sparePartHelperModel?.sparePart?.oracleCode != null ? widget.provider.sparePartHelperModel!.sparePart!.oracleCode! : ''; - _descriptionController.text = widget.provider.sparePartHelperModel?.comment != null ? widget.provider.sparePartHelperModel!.comment! : ''; - activityStatus = widget.provider.sparePartHelperModel?.activityStatus?.value; - - scheduleMicrotask(() async { - _isLoading = true; - attachments = widget.provider.sparePartHelperModel?.sparePartAttachments?.map((e) => GenericAttachmentModel(id: e.id!, name: e.name ?? '')).toList() ?? []; - setState(() {}); - _spareParts = await _partsProvider!.getPartsListByDisplayName(assetId: widget.provider.currentWorkOrder?.data?.asset?.id); - _isLoading = false; - setState(() {}); - }); + _initializeModel(); + _loadInitialData(); } - void restValues({required CMDetailProvider requestDetailProvider}) { - requestDetailProvider.sparePartHelperModel = SparePartHelperModel(id: 0, workOrderId: widget.provider.currentWorkOrder?.data?.requestId, sparePartAttachments: []); - _partQtyController.clear(); - _installQtyController.clear(); - _returnQtyController.clear(); - _oracleNoController.clear(); - _descriptionController.clear(); - attachments = []; + void _initializeModel() { + final bool isNewActivity = widget.model?.id == null || widget.model?.id == 0; + if (isNewActivity) { + widget.provider.sparePartHelperModel = SparePartHelperModel( + id: 0, + workOrderId: widget.fromCm ? widget.provider.currentWorkOrder?.data?.requestId : null, + pmVisitId: !widget.fromCm ? widget.provider.planPreventiveVisit?.id : null, + sparePartAttachments: [], + sparePart: SparePart(), + quantity: null, + installQty: null, + returnQty: null, + activityStatusId: statusLookup.id, + activityStatus: statusLookup, + comment: '', + ); + } else if (widget.provider.sparePartHelperModel == null || + widget.provider.sparePartHelperModel?.id != widget.model?.id) { + widget.provider.sparePartHelperModel = SparePartHelperModel( + id: widget.model?.id ?? 0, + workOrderId: widget.fromCm ? widget.model?.workOrderId ?? widget.provider.currentWorkOrder?.data?.requestId : null, + pmVisitId: !widget.fromCm ? widget.model?.pmVisitId : null, + sparePartAttachments: widget.model?.sparePartAttachments ?? [], + sparePart: widget.model?.sparePart ?? SparePart(), + quantity: widget.model?.quantity, + installQty: widget.model?.installQty, + returnQty: widget.model?.returnQty, + activityStatusId: widget.model?.activityStatusId ?? statusLookup.id, + activityStatus: widget.model?.activityStatus ?? statusLookup, + comment: widget.model?.comment ?? '', + ); + } + log('Initialized SparePartHelperModel: ${widget.provider.sparePartHelperModel?.toJson()}'); } - @override - void dispose() { - _partQtyController.dispose(); - _installQtyController.dispose(); - _returnQtyController.dispose(); - _oracleNoController.dispose(); - _descriptionController.dispose(); - super.dispose(); - } + Future _loadInitialData() async { + setState(() => _isLoading = true); + _spareParts = await _partsProvider!.getPartsListByDisplayName( + assetId: widget.fromCm + ? widget.provider.currentWorkOrder?.data?.asset?.id + : widget.provider.planPreventiveVisit.asset.id, + ); + final bool isNewActivity = widget.model?.id == null || widget.model?.id == 0; + _attachments = []; + if (!isNewActivity) { + try { + final attachmentsList = widget.model?.sparePartAttachments; + if (attachmentsList != null && attachmentsList.isNotEmpty) { + for (var item in attachmentsList) { + _attachments.add(GenericAttachmentModel( + id: item.id ?? 0, + name: item.name ?? '', + )); + } + } + log('Loaded ${_attachments.length} attachments for existing activity'); + } catch (e) { + log('Error loading attachments: $e'); + _attachments = []; + } + } else { + log('New activity - starting with empty attachments'); + } - bool _isLocalUrl(String url) { - if (url.isEmpty != false) return false; - return url.startsWith("/") || url.startsWith("file://") || url.substring(1).startsWith(':\\'); + if (mounted) setState(() => _isLoading = false); } @override Widget build(BuildContext context) { - bool showInstallAndReturnQty = activityStatus == 3 || activityStatus == 4; return Scaffold( - key: _scaffoldKey, appBar: DefaultAppBar(title: context.translation.sparePartRequest), - body: Consumer(builder: (context, CMDetailProvider requestDetailProvider, child) { - return Form( - key: _formKey, - child: Column( - children: [ - SingleChildScrollView( - padding: const EdgeInsets.all(16), - child: IgnorePointer( - ignoring: widget.provider.isReadOnlyRequest, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Column( + body: ListenableBuilder( + listenable: widget.provider, + builder: (context, _) { + final provider = widget.provider; + final model = provider.sparePartHelperModel; + final bool showInstallAndReturnQty = (model?.activityStatus?.value == 3 || model?.activityStatus?.value == 4); + final bool isReadOnly = provider.isReadOnlyRequest ?? false; + + return Form( + key: _formKey, + child: Column( + children: [ + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: IgnorePointer( + ignoring: isReadOnly, + child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - context.translation.sparePartDetails.heading5(context).custom(color: AppColor.textStyleColor(context)), - // 12.height, - // SingleItemDropDownMenu( - // context: context, - // height: 56.toScreenHeight, - // title: context.translation.activityStatus, - // showShadow: false, - // backgroundColor: AppColor.neutral100, - // initialValue: requestDetailProvider.sparePartHelperModel?.activityStatus, - // onSelect: (status) { - // requestDetailProvider.sparePartHelperModel?.activityStatus = status; - // }, - // ), - 12.height, - SingleItemDropDownMenu( - context: context, - title: context.translation.part, - staticData: _spareParts, - showShadow: false, - showAsBottomSheet: true, - loading: _isLoading, - initialValue: requestDetailProvider.sparePartHelperModel?.sparePart, - // backgroundColor: context.isDark ? AppColor.neutral20 : AppColor.neutral90, - onSelect: (part) async { - if (part != null) { - requestDetailProvider.sparePartHelperModel?.sparePart = part; - _oracleNoController.text = part.oracleCode ?? ''; - requestDetailProvider.updateSparePartHelperModel(widget.provider.sparePartHelperModel); - } - }, - ), - - if (requestDetailProvider.sparePartHelperModel?.sparePart?.id != null) ...[ - 12.height, - SizedBox( - height: 30.toScreenHeight, - child: Text('View Parts Availability', - style: TextStyle( - fontSize: 12.toScreenWidth, - fontWeight: FontWeight.w500, - fontStyle: FontStyle.normal, - color: AppColor.primary10, - decorationColor: AppColor.primary10, - decoration: TextDecoration.underline, - )).onPress( - () async { - requestDetailProvider.getStoreAvailabilityById(partId: requestDetailProvider.sparePartHelperModel?.sparePart?.id); - showModalBottomSheet( - context: context, - useSafeArea: true, - isScrollControlled: true, - isDismissible: true, - backgroundColor: AppColor.bottomSheetColor(context), - builder: (BuildContext context) => PartDetailBottomSheetSheet().bottomSheetContainer(context), - ); - }, - ), - ), - ], - 12.height, - AppTextFormField( - controller: _partQtyController, - labelText: context.translation.quantity, - textInputType: TextInputType.number, - labelStyle: AppTextStyles.textFieldLabelStyle, - // showWithoutDecoration: true, - // backgroundColor: context.isDark ? AppColor.neutral20 : AppColor.neutral90, - enable: requestDetailProvider.sparePartHelperModel?.sparePart?.id != null, - validator: (value) => value == null || value.isEmpty - ? context.translation.requiredField - : Validator.isNumeric(value) - ? null - : context.translation.onlyNumbers, - onChange: (value) { - requestDetailProvider.sparePartHelperModel?.quantity = num.tryParse(value ?? ""); - }, - onSaved: (text) { - requestDetailProvider.sparePartHelperModel?.quantity = num.tryParse(text ?? ""); - }, - ), - 12.height, - if (showInstallAndReturnQty) ...[ - AppTextFormField( - controller: _installQtyController, - labelText: context.translation.installedQty, - textInputType: const TextInputType.numberWithOptions(decimal: true), - labelStyle: AppTextStyles.textFieldLabelStyle, - // showWithoutDecoration: true, - // backgroundColor: context.isDark ? AppColor.neutral20 : AppColor.neutral90, - enable: requestDetailProvider.sparePartHelperModel?.sparePart?.id != null, - validator: (value) => value == null || value.isEmpty - ? context.translation.requiredField - : Validator.isNumeric(value) - ? null - : context.translation.onlyNumbers, - onChange: (value) { - requestDetailProvider.sparePartHelperModel?.installQty = num.tryParse(value ?? ""); - }, - onSaved: (text) { - requestDetailProvider.sparePartHelperModel?.installQty = num.tryParse(text ?? ""); - }, - ), - 12.height, - AppTextFormField( - controller: _returnQtyController, - labelText: context.translation.returnQty, - textInputType: TextInputType.number, - labelStyle: AppTextStyles.textFieldLabelStyle, - // showWithoutDecoration: true, - // backgroundColor: context.isDark ? AppColor.neutral20 : AppColor.neutral90, - enable: requestDetailProvider.sparePartHelperModel?.sparePart?.id != null, - validator: (value) => value == null || value.isEmpty - ? context.translation.requiredField - : Validator.isNumeric(value) - ? null - : context.translation.onlyNumbers, - onChange: (value) { - requestDetailProvider.sparePartHelperModel?.returnQty = num.tryParse(value ?? ""); - }, - onSaved: (text) { - requestDetailProvider.sparePartHelperModel?.returnQty = num.tryParse(text ?? ""); - }, - ), - 12.height, - ], - AppTextFormField( - labelStyle: AppTextStyles.textFieldLabelStyle, - controller: _oracleNoController, - labelText: context.translation.oracleNo, - textInputType: TextInputType.number, - // showWithoutDecoration: true, - backgroundColor: AppColor.disableFieldBackground(context), - enable: false, - ), - 12.height, - AppTextFormField( - initialValue: requestDetailProvider.sparePartHelperModel?.comment, - labelStyle: AppTextStyles.textFieldLabelStyle, - controller: _descriptionController, - showSpeechToText: true, - // backgroundColor: context.isDark ? AppColor.neutral20 : AppColor.neutral90, - labelText: "Engineer Comment", - alignLabelWithHint: true, - // showWithoutDecoration: true, - textInputType: TextInputType.multiline, - onChange: (value) { - requestDetailProvider.sparePartHelperModel?.comment = value; - }, - onSaved: (value) { - requestDetailProvider.sparePartHelperModel?.comment = value; - }, - ), - 12.height, - AttachmentPicker( - label: context.translation.attachQuotation, - attachment: attachments, - buttonIcon: 'quotation_icon'.toSvgAsset(color: AppColor.primary10), - buttonColor: AppColor.primary10, + _buildFormContent( + context, + model, + provider, + showInstallAndReturnQty, ), ], - ).toShadowContainer(context), - 8.height, - ], + ), + ), ), ), - ).expanded, - if (!requestDetailProvider.isReadOnlyRequest) - FooterActionButton.footerContainer( - context: context, - child: AppFilledButton( - label: widget.provider.sparePartHelperModel?.id == 0 ? context.translation.addSparePartActivity : context.translation.updateSparePartActivity, - buttonColor: AppColor.green70, - onPressed: () { - _onSubmit(requestDetailProvider: requestDetailProvider); - }, - )), - ], + if (!isReadOnly) _buildSubmitButton(context, provider), + ], + ), + ); + }, + ), + ); + } + + Widget _buildFormContent( + BuildContext context, + SparePartHelperModel? model, + dynamic provider, + bool showInstallAndReturnQty, + ) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + context.translation.sparePartDetails.heading5(context).custom(color: AppColor.textStyleColor(context)), + 12.height, + SingleItemDropDownMenu( + context: context, + title: context.translation.part, + staticData: _spareParts, + showShadow: false, + showAsBottomSheet: true, + loading: _isLoading, + initialValue: model?.sparePart, + onSelect: (part) { + if (part != null) { + model?.sparePart = part; + provider.updateSparePartHelperModel(model); + } + }, + ), + if (model?.sparePart?.id != null) ...[ + 12.height, + SizedBox( + height: 30.toScreenHeight, + child: Text( + 'View Parts Availability', + style: TextStyle( + fontSize: 12.toScreenWidth, + fontWeight: FontWeight.w500, + color: AppColor.primary10, + decorationColor: AppColor.primary10, + decoration: TextDecoration.underline, + ), + ).onPress(() { + provider.getStoreAvailabilityById(partId: model?.sparePart?.id); + showModalBottomSheet( + context: context, + useSafeArea: true, + isScrollControlled: true, + isDismissible: true, + backgroundColor: AppColor.bottomSheetColor(context), + builder: (ctx) => PartDetailBottomSheetSheet(provider: provider).bottomSheetContainer(ctx), + ); + }), ), - ); - }), + ], + + 12.height, + AppTextFormField( + key: ValueKey('quantity_${model?.quantity}'), + initialValue: model?.quantity?.round().toString() ?? '', + labelText: context.translation.quantity, + textInputType: TextInputType.number, + labelStyle: AppTextStyles.textFieldLabelStyle, + enable: model?.sparePart?.id != null, + validator: (value) => value == null || value.isEmpty + ? context.translation.requiredField + : Validator.isNumeric(value) + ? null + : context.translation.onlyNumbers, + onChange: (value) { + model?.quantity = num.tryParse(value); + }, + onSaved: (text) { + model?.quantity = num.tryParse(text ?? ''); + }, + ), + + 12.height, + + if (showInstallAndReturnQty) ...[ + AppTextFormField( + key: ValueKey('installQty_${model?.installQty}'), + initialValue: model?.installQty?.round().toString() ?? '', + labelText: context.translation.installedQty, + textInputType: const TextInputType.numberWithOptions(decimal: true), + labelStyle: AppTextStyles.textFieldLabelStyle, + enable: model?.sparePart?.id != null, + validator: (value) => value == null || value.isEmpty + ? context.translation.requiredField + : Validator.isNumeric(value) + ? null + : context.translation.onlyNumbers, + onChange: (value) { + model?.installQty = num.tryParse(value); + }, + onSaved: (text) { + model?.installQty = num.tryParse(text ?? ''); + }, + ), + 12.height, + AppTextFormField( + key: ValueKey('returnQty_${model?.returnQty}'), + initialValue: model?.returnQty?.round().toString() ?? '', + labelText: context.translation.returnQty, + textInputType: TextInputType.number, + labelStyle: AppTextStyles.textFieldLabelStyle, + enable: model?.sparePart?.id != null, + validator: (value) => value == null || value.isEmpty + ? context.translation.requiredField + : Validator.isNumeric(value) + ? null + : context.translation.onlyNumbers, + onChange: (value) { + model?.returnQty = num.tryParse(value); + }, + onSaved: (text) { + model?.returnQty = num.tryParse(text ?? ''); + }, + ), + 12.height, + ], + AppTextFormField( + key: ValueKey('oracle_${model?.sparePart?.oracleCode}'), + initialValue: model?.sparePart?.oracleCode ?? '', + labelStyle: AppTextStyles.textFieldLabelStyle, + labelText: context.translation.oracleNo, + textInputType: TextInputType.number, + backgroundColor: AppColor.disableFieldBackground(context), + enable: false, + ), + + 12.height, + AppTextFormField( + key: ValueKey('comment_${model?.comment}'), + initialValue: model?.comment ?? '', + labelStyle: AppTextStyles.textFieldLabelStyle, + showSpeechToText: true, + labelText: "Engineer Comment", + alignLabelWithHint: true, + textInputType: TextInputType.multiline, + onChange: (value) { + model?.comment = value; + }, + onSaved: (value) { + model?.comment = value; + }, + ), + + 12.height, + AttachmentPicker( + label: context.translation.attachQuotation, + attachment: _attachments, + buttonIcon: 'quotation_icon'.toSvgAsset(color: AppColor.primary10), + buttonColor: AppColor.primary10, + ), + ], + ).toShadowContainer(context); + } + + Widget _buildSubmitButton(BuildContext context, dynamic provider) { + return FooterActionButton.footerContainer( + context: context, + child: AppFilledButton( + label: provider.sparePartHelperModel?.id == 0 ? context.translation.addSparePartActivity : context.translation.updateSparePartActivity, + buttonColor: AppColor.green70, + onPressed: () => _onSubmit(provider), + ), ); } - _onSubmit({required CMDetailProvider requestDetailProvider}) async { - if (requestDetailProvider.sparePartHelperModel?.sparePart?.id == null) { + Future _onSubmit(dynamic provider) async { + final model = provider.sparePartHelperModel; + + if (model?.sparePart?.id == null) { "Please select spare part".showToast; return; - } else if (requestDetailProvider.sparePartHelperModel?.quantity == null) { + } else if (model?.quantity == null) { "Please enter quantity".showToast; return; } - requestDetailProvider.sparePartHelperModel?.sparePartAttachments?.clear(); - - for (var item in attachments) { + model?.sparePartAttachments?.clear(); + for (var item in _attachments) { String fileName = CMRequestUtils.isLocalUrl(item.name ?? '') ? ("${item.name ?? ''.split("/").last}|${base64Encode(File(item.name ?? '').readAsBytesSync())}") : item.name ?? ''; - requestDetailProvider.sparePartHelperModel?.sparePartAttachments?.add( + + model?.sparePartAttachments?.add( SparePartAttachments(id: item.id, name: fileName), ); } - // Show loading dialog + // Show loading showDialog( context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading(), ); + + // Submit bool success = false; - if (widget.provider.sparePartHelperModel?.id == 0) { - success = await requestDetailProvider.createActivitySparePart(); + if (model?.id == 0) { + success = await provider.createActivitySparePart(); } else { - success = await requestDetailProvider.updateActivitySparePart(); + success = await provider.updateActivitySparePart(); } - if (mounted) Navigator.pop(context); + + if (mounted) Navigator.pop(context); // Close loading + if (success) { - requestDetailProvider.getWorkOrderById( - id: requestDetailProvider.currentWorkOrder!.data!.requestId!, - ); - if (mounted) Navigator.pop(context); - if (widget.provider.sparePartHelperModel?.id == 0) { - ServiceRequestBottomSheet.addAnotherSpareRequestBottomSheet(context: context); + if (widget.fromCm) { + provider.getWorkOrderById( + id: provider.currentWorkOrder!.data!.requestId!, + ); + } + else{ + provider.getPlanPreventiveVisitById( + int.tryParse(provider.planPreventiveVisit?.id ?? ''), + ); + } + + if (mounted) Navigator.pop(context); // Close form + + if (model?.id == 0) { + ServiceRequestBottomSheet.addAnotherSpareRequestBottomSheet( + context: context, + fromCm: widget.fromCm + ); const SizedBox().flushBar( context: context, title: context.translation.sparePartActivitySuccess, @@ -376,8 +408,14 @@ class _SparePartRequestState extends State with TickerProvider } } +/// Bottom Sheet for showing parts availability class PartDetailBottomSheetSheet extends StatelessWidget { - PartDetailBottomSheetSheet({Key? key}) : super(key: key); + final dynamic provider; + + const PartDetailBottomSheetSheet({ + Key? key, + required this.provider, + }) : super(key: key); @override Widget build(BuildContext context) { @@ -390,31 +428,61 @@ class PartDetailBottomSheetSheet extends StatelessWidget { children: [ 8.height, 'Parts Availability'.heading6(context).paddingOnly(top: 12, start: 12), - Consumer(builder: (context, requestDetailProvider, child) { - return requestDetailProvider.isLoading - ? CircularProgressIndicator(color: AppColor.loadingColor(context)).center - : requestDetailProvider.storeAvailability.isEmpty - ? const NoDataFound().center - : ListView.separated( - padding: const EdgeInsets.only(top: 12), - itemCount: requestDetailProvider.storeAvailability.length, - separatorBuilder: (czt, index) => 12.height, - itemBuilder: (context, index) { - StoreAvailability model = requestDetailProvider.storeAvailability[index]; - return partAvailableQuantityCard(context: context, model: model); - }, - ); - }).expanded, + Expanded( + child: ListenableBuilder( + listenable: provider, + builder: (context, _) { + return provider.isLoading + ? CircularProgressIndicator( + color: AppColor.loadingColor(context), + ).center + : provider.storeAvailability.isEmpty + ? const NoDataFound().center + : ListView.separated( + padding: const EdgeInsets.only(top: 12), + itemCount: provider.storeAvailability.length, + separatorBuilder: (czt, index) => 12.height, + itemBuilder: (context, index) { + StoreAvailability model = provider.storeAvailability[index]; + return _partAvailableQuantityCard( + context: context, + model: model, + ); + }, + ); + }, + ), + ), ], ), ); } - Widget partAvailableQuantityCard({required StoreAvailability model, required BuildContext context}) { - return Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - InfoTextWidget(label: "Site Name:".addTranslation, value: model.siteName ?? ''), - InfoTextWidget(label: "Store Name:".addTranslation, value: model.storeName ?? ''), - InfoTextWidget(label: "Available Quantity:".addTranslation, value: model.availablityQty != null ? model.availablityQty.toString() : ''), - ]).toShadowContainer(context, padding: 12, showShadow: false,); + Widget _partAvailableQuantityCard({ + required StoreAvailability model, + required BuildContext context, + }) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + InfoTextWidget( + label: "Site Name:".addTranslation, + value: model.siteName ?? '', + ), + InfoTextWidget( + label: "Store Name:".addTranslation, + value: model.storeName ?? '', + ), + InfoTextWidget( + label: "Available Quantity:".addTranslation, + value: model.availablityQty?.toString() ?? '', + ), + ], + ).toShadowContainer( + context, + padding: 12, + showShadow: false, + ); } } diff --git a/lib/modules/pm_module/ppm_wo/activities/pm_activities_list_view.dart b/lib/modules/pm_module/ppm_wo/activities/pm_activities_list_view.dart index 4d61b068..fa749597 100644 --- a/lib/modules/pm_module/ppm_wo/activities/pm_activities_list_view.dart +++ b/lib/modules/pm_module/ppm_wo/activities/pm_activities_list_view.dart @@ -1,29 +1,24 @@ + import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/controllers/providers/api/ppm_provider.dart'; import 'package:test_sa/controllers/providers/api/user_provider.dart'; 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/models/enums/user_types.dart'; -import 'package:test_sa/models/helper_data_models/maintenance_request/activity_maintenance_model.dart'; import 'package:test_sa/models/helper_data_models/spare_part/activity_spare_part_model.dart'; -import 'package:test_sa/models/new_models/work_order_detail_model.dart'; +import 'package:test_sa/models/plan_preventive_visit/plan_preventive_visit_model.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_details.dart'; -import 'package:test_sa/modules/cm_module/cm_detail_provider.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/cm_module/views/forms/maintenance_request/maintenance_request_main.dart'; import 'package:test_sa/modules/cm_module/views/forms/spare_part/spare_part_request.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_lazy_loading.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/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/no_data_found.dart'; import 'package:test_sa/views/widgets/requests/request_status.dart'; @@ -50,9 +45,7 @@ class _PmActivitiesListViewState extends State { super.dispose(); } - List reOrderActivities = []; - - List activities = []; + List activities = []; bool disableNewActivity = false; @@ -60,15 +53,12 @@ class _PmActivitiesListViewState extends State { Widget build(BuildContext context) { UserProvider userProvider = Provider.of(context); return Scaffold( - appBar: const DefaultAppBar( - title: 'PM Activities', - // actions: [if (!showReorderView) IconButton(onPressed: () => activities.isEmpty ? null : setState(() => showReorderView = true), icon: const Icon(Icons.reorder_rounded))], + appBar: DefaultAppBar( + showHomeActionButton: true, + title: context.translation.activities, ), - - ///TODO replace with pm provider and data from pm provider - body: Consumer(builder: (context, CMDetailProvider requestDetailProvider, child) { - activities = requestDetailProvider.currentWorkOrder!.data!.activities; - requestDetailProvider.lastVisitedRequestActivityId = (activities.isNotEmpty ? activities.last.id : -1)!; + body: Consumer(builder: (context, PpmProvider pmProvider, child) { + activities = pmProvider.planPreventiveVisit?.activities ?? []; if (activities.isNotEmpty && (activities.last.activityStatus!.value == 14 || activities.last.activityStatus!.value == 19)) { disableNewActivity = true; } else { @@ -77,28 +67,42 @@ class _PmActivitiesListViewState extends State { return Column( children: [ - (activities.isEmpty && reOrderActivities.isEmpty) + (activities.isEmpty) ? NoDataFound(message: context.translation.noDataFound).center.expanded : ListView.separated( padding: const EdgeInsets.all(16), itemCount: activities.length, - separatorBuilder: (czt, index) => 16.height, + separatorBuilder: (czt, index) => 12.height, itemBuilder: (context, index) { - return sparePartActivityCard(requestDetailProvider: requestDetailProvider, userProvider: userProvider, context: context, activity: activities[index]); + return sparePartActivityCard(pmProvider: pmProvider, userProvider: userProvider, context: context, activity: activities[index]); }, ).expanded, if (userProvider.user!.type == UsersTypes.engineer && - (requestDetailProvider.currentWorkOrder?.data?.status?.value != 5 && requestDetailProvider.currentWorkOrder?.data?.status?.value != 3)) + (pmProvider.planPreventiveVisit?.visitStatus?.value != 5 && pmProvider.planPreventiveVisit?.visitStatus?.value != 3)) FooterActionButton.footerContainer( context: context, child: AppFilledButton( - label: 'Create New Spare Part', + label: context.translation.createNewActivity, maxWidth: true, buttonColor: AppColor.primary10, - loading: requestDetailProvider.isLoading, + loading: pmProvider.isLoading, disableButton: disableNewActivity, onPressed: () async { - // ServiceRequestBottomSheet.activityTypeBottomSheet(context: context); + final model = SparePartHelperModel( + id: 0, + pmVisitId: pmProvider.planPreventiveVisit?.id, + ); + + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => SparePartRequest( + model: model, + provider: pmProvider, + fromCm: false, + ), + ), + ); }, )) ], @@ -107,11 +111,11 @@ class _PmActivitiesListViewState extends State { ); } - Widget sparePartActivityCard({required CMDetailProvider requestDetailProvider, required UserProvider userProvider, required BuildContext context, required Activities activity}) { - bool showActionButtons = userProvider.user!.userID == requestDetailProvider.currentWorkOrder?.data?.assignedEmployee?.userId && + Widget sparePartActivityCard({required PpmProvider pmProvider, required UserProvider userProvider, required BuildContext context, required PMSparePartActivityModel activity}) { + bool showActionButtons = userProvider.user!.userID == pmProvider.planPreventiveVisit?.assignedEmployee?.userId && userProvider.user!.type == UsersTypes.engineer && - requestDetailProvider.currentWorkOrder?.data?.status?.value != 5 && - requestDetailProvider.currentWorkOrder?.data?.status?.value != 3; + pmProvider.planPreventiveVisit?.visitStatus?.value != 5 && + pmProvider.planPreventiveVisit?.visitStatus?.value != 3; return Column( mainAxisSize: MainAxisSize.min, @@ -131,14 +135,14 @@ class _PmActivitiesListViewState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ "edit_icon".toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)).onPress(() { - editSparePartRequest(context: context, requestDetailProvider: requestDetailProvider, activity: activity); + editSparePartRequest(context: context, pmProvider: pmProvider, activity: activity); }), 12.width, "delete_icon".toSvgAsset(height: 24, width: 24).onPress(() async { showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading()); - int status = await requestDetailProvider.deleteActivitySparePart(id: activity.id!, workOrderId: requestDetailProvider.currentWorkOrder!.data!.requestId!); + int status = await pmProvider.deleteActivitySparePart(id: activity.id!); if (status == 200) { - await requestDetailProvider.getWorkOrderById(id: requestDetailProvider.currentWorkOrder!.data!.requestId!); + await pmProvider.getPlanPreventiveVisitById((int.tryParse(pmProvider.planPreventiveVisit?.id ?? '')) as int); Navigator.pop(context); } else { Navigator.pop(context); @@ -149,58 +153,61 @@ class _PmActivitiesListViewState extends State { ], ), 6.height, - InfoHeaderWidget(activity.activityType?.cleanupWhitespace.capitalizeFirstOfEach ?? '-'), InfoTextWidget( label: context.translation.partName, - value: activity.activitySparePart?.partCatalogItem?.partName, + value: activity.partCatalogItem?.partName, ), InfoTextWidget( label: context.translation.partNo, - value: activity.activitySparePart?.partCatalogItem?.partNumber, - showArrow: true, + value: activity.partCatalogItem?.partNumber, + ), + InfoTextWidget( + label: context.translation.oracleCode, + value: activity.partCatalogItem?.oracleCode, ), - if (activity.activitySparePart?.acitiySparePartAttachments?.isNotEmpty ?? false) ...[ - 6.height, + if (activity.comment?.isNotEmpty ?? false) ...[ const Divider().defaultStyle(context), - FilesList(images: activity.activitySparePart!.acitiySparePartAttachments!.map((toElement) => URLs.getFileUrl(toElement.name!)!).toList()), + InfoTextLabelWidget(label: activity.comment ?? ""), + ], + if (activity.activitySparePartPPMAttachments.isNotEmpty) ...[ + const Divider().defaultStyle(context), + FilesList(images: activity.activitySparePartPPMAttachments.map((toElement) => URLs.getFileUrl(toElement.name??'')??'').toList()), ], ], ).toShadowContainer(context, showShadow: false).onPress(() { - ///Move to cost page .. - // if (showActionButtons) return; - // if (requestDetailProvider.isReadOnlyRequest) { - // editSparePartRequest(context: context, requestDetailProvider: requestDetailProvider, activity: activity); - // } else if (userProvider.user!.userID == requestDetailProvider.currentWorkOrder?.data?.assignedEmployee?.userId) { - // requestDetailProvider.isReadOnlyRequest = true; - // editSparePartRequest(context: context, requestDetailProvider: requestDetailProvider, activity: activity); - // } + if (showActionButtons) return; + if (pmProvider.isReadOnlyRequest) { + editSparePartRequest(context: context, pmProvider: pmProvider, activity: activity); + } else if (userProvider.user!.userID == pmProvider.planPreventiveVisit?.assignedEmployee?.userId) { + pmProvider.isReadOnlyRequest = true; + editSparePartRequest(context: context, pmProvider: pmProvider, activity: activity); + } }); } - void editSparePartRequest({required BuildContext context, required CMDetailProvider requestDetailProvider, required Activities activity}) async { - requestDetailProvider.sparePartHelperModel = SparePartHelperModel( + void editSparePartRequest({required BuildContext context, required PpmProvider pmProvider, required PMSparePartActivityModel activity}) async { + final model = SparePartHelperModel( id: activity.id, - workOrderId: requestDetailProvider.currentWorkOrder?.data?.requestId, - comment: activity.activitySparePart?.comment, - sparePartAttachments: activity.activitySparePart?.acitiySparePartAttachments ?? [], + pmVisitId: pmProvider.planPreventiveVisit?.id, + comment: activity.comment, + sparePartAttachments: activity.activitySparePartPPMAttachments, activityStatus: activity.activityStatus, sparePart: SparePart( - id: activity.activitySparePart?.partCatalogItem?.id, - partName: activity.activitySparePart?.partCatalogItem?.partName, - partNo: activity.activitySparePart?.partCatalogItem?.partNumber, - oracleCode: activity.activitySparePart?.partCatalogItem?.oracleCode), - quantity: activity.activitySparePart?.quantity, - installQty: activity.activitySparePart?.installQty, - returnQty: activity.activitySparePart?.returnQty, + id: activity.partCatalogItem?.id, + partName: activity.partCatalogItem?.partName, + partNo: activity.partCatalogItem?.partNumber, + oracleCode: activity.partCatalogItem?.oracleCode, + ), + quantity: activity.quantity, + installQty: activity.installQty, + returnQty: activity.returnQty, activityStatusId: activity.activityStatus?.id, ); - - requestDetailProvider.updateSparePartHelperModel(requestDetailProvider.sparePartHelperModel); - Navigator.of(context).push(MaterialPageRoute( builder: (_) => SparePartRequest( - model: requestDetailProvider.sparePartHelperModel, - provider: requestDetailProvider, + model: model, + provider: pmProvider, + fromCm: false, // This is PM, not CM ), )); } diff --git a/lib/modules/pm_module/ppm_wo/activities/pm_cost_detail_page.dart b/lib/modules/pm_module/ppm_wo/activities/pm_cost_detail_page.dart deleted file mode 100644 index 24cd885b..00000000 --- a/lib/modules/pm_module/ppm_wo/activities/pm_cost_detail_page.dart +++ /dev/null @@ -1,127 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:test_sa/controllers/api_routes/urls.dart'; -import 'package:test_sa/controllers/providers/api/user_provider.dart'; -import 'package:test_sa/controllers/providers/settings/setting_provider.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/enums/user_types.dart'; -import 'package:test_sa/models/enums/work_order_next_step.dart'; -import 'package:test_sa/models/generic_attachment_model.dart'; -import 'package:test_sa/models/helper_data_models/workorder/work_order_helper_models.dart'; -import 'package:test_sa/models/new_models/work_order_detail_model.dart'; -import 'package:test_sa/modules/cm_module/cm_detail_provider.dart'; -import 'package:test_sa/modules/cm_module/views/forms/asset_retired/asset_retired.dart'; -import 'package:test_sa/modules/cm_module/views/forms/cost/cost_detail_form_screen.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/views/widgets/equipment/asset_detail_page.dart'; -import 'package:test_sa/views/widgets/images/files_list.dart'; -import 'package:test_sa/views/widgets/images/multi_image_picker.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/no_data_found.dart'; -import 'package:test_sa/views/widgets/requests/request_status.dart'; -import 'package:test_sa/views/widgets/sound/sound_player.dart'; - -class PmCostDetailsView extends StatefulWidget { - PmCostDetailsView({Key? key}) : super(key: key); - - @override - State createState() => _PmCostDetailsViewState(); -} - -class _PmCostDetailsViewState extends State { - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - UserProvider userProvider = Provider.of(context, listen: false); - - ///TODO: replace with pm provider and data from provider and remove the extra widgets as well. - return Consumer(builder: (pContext, requestProvider, _) { - bool showCostCard = context.userProvider.user!.type == UsersTypes.engineer && - !requestProvider.isReadOnlyRequest && - requestProvider.currentWorkOrder?.data?.nextStep?.workOrderNextStepEnum == WorkOrderNextStepEnum.activity; - - return requestProvider.isLoading - ? CircularProgressIndicator(color: AppColor.loadingColor(context)).center - : requestProvider.currentWorkOrder == null - ? const NoDataFound() - : Column( - children: [ - SingleChildScrollView( - padding: EdgeInsets.symmetric(horizontal: 16.toScreenWidth), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (showCostCard) ...[ - 16.height, - costCard(context, requestProvider), - ], - ], - ), - ).expanded, - ], - ); - }); - } - - Widget costCard(BuildContext context, CMDetailProvider provider) { - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - InfoHeader16Widget("Cost"), - const Spacer(), - if (!provider.isReadOnlyRequest) - - ///TODO replace with exact provider and data from provider and remove the extra widgets as well. - "edit_icon".toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)).onPress(() async { - WorkOrderData currentWorkOrderData = provider.currentWorkOrder!.data!; - provider.workOrderCostModel = WorkOrderCostModel( - workOrderId: currentWorkOrderData.requestId, - labourCost: currentWorkOrderData.labourCost, - sparePartCost: currentWorkOrderData.sparePartCost, - travelCost: currentWorkOrderData.travelCost, - qAmount: currentWorkOrderData.qAmount, - prNo: currentWorkOrderData.prNo, - poNo: currentWorkOrderData.poNo, - mrNo: currentWorkOrderData.mrNo, - exchangeCost: currentWorkOrderData.exchangeCost, - ); - provider.refreshTimer = false; - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => CostDetailFormScreen( - isEdit: true, - model: provider.workOrderCostModel, - provider: provider, - ))); - provider.refreshTimer = true; - }), - ], - ), - InfoTextWidget(label: 'Spare Part Cost'.addTranslation, value: provider.currentWorkOrder?.data?.sparePartCost?.toString() ?? '-'), - InfoTextWidget(label: 'Labor Cost'.addTranslation, value: provider.currentWorkOrder?.data?.labourCost?.toString() ?? '-'), - InfoTextWidget(label: 'Travel Cost'.addTranslation, value: provider.currentWorkOrder?.data?.travelCost?.toString() ?? '-'), - InfoTextWidget(label: 'Quot Amount'.addTranslation, value: provider.currentWorkOrder?.data?.qAmount?.toString() ?? '-'), - InfoTextWidget(label: 'PR No'.addTranslation, value: provider.currentWorkOrder?.data?.prNo?.toString() ?? '-'), - InfoTextWidget(label: 'PO No'.addTranslation, value: provider.currentWorkOrder?.data?.poNo?.toString() ?? '-'), - InfoTextWidget(label: 'MR No'.addTranslation, value: provider.currentWorkOrder?.data?.mrNo?.toString() ?? '-'), - InfoTextWidget(label: 'Exchange Cost'.addTranslation, value: provider.currentWorkOrder?.data?.exchangeCost?.toString() ?? '-'), - ], - ).toShadowContainer(context); - } -} diff --git a/lib/modules/pm_module/ppm_wo/ppm_details_page.dart b/lib/modules/pm_module/ppm_wo/ppm_details_page.dart index 84bac35f..ffcc3679 100644 --- a/lib/modules/pm_module/ppm_wo/ppm_details_page.dart +++ b/lib/modules/pm_module/ppm_wo/ppm_details_page.dart @@ -117,7 +117,7 @@ class _PpmDetailsPageState extends State { ], ).toShadowContainer(context), ).expanded, - if (userProvider!.user!.type == UsersTypes.engineer && (!ppmProvider.isReadOnly)) ...[ + // if (userProvider!.user!.type == UsersTypes.engineer && (!ppmProvider.isReadOnly)) ...[ FooterActionButton.footerContainer( child: Column( children: [ @@ -126,22 +126,14 @@ class _PpmDetailsPageState extends State { await Navigator.of(context).push(MaterialPageRoute(builder: (_) => UpdatePpm(ppm: null, planPreventiveVisit: planPreventiveVisit))); getVisitData(); }, - label: context.translation.updateWorkOrder, - ), - 12.height, - AppFilledButton( - onPressed: () async { - await Navigator.of(context).push(MaterialPageRoute(builder: (_) => const PmActivitiesListView())); - getVisitData(); - }, - buttonColor: AppColor.neutral50, - label: context.translation.activities, + label: userProvider!.user!.type == UsersTypes.engineer && (!ppmProvider.isReadOnly)? context.translation.updateWorkOrder:context.translation.viewDetails, ), + ], ), context: context, ) - ] + // ] ]) : const Center(child: NoDataFound()); }), diff --git a/lib/modules/pm_module/ppm_wo/update_ppm/update_ppm.dart b/lib/modules/pm_module/ppm_wo/update_ppm/update_ppm.dart index a61a8efd..e431d5de 100644 --- a/lib/modules/pm_module/ppm_wo/update_ppm/update_ppm.dart +++ b/lib/modules/pm_module/ppm_wo/update_ppm/update_ppm.dart @@ -14,6 +14,7 @@ import 'package:test_sa/models/plan_preventive_visit/plan_preventive_visit_model import 'package:test_sa/models/ppm/ppm.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/pm_module/ppm_wo/activities/pm_activities_list_view.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_lazy_loading.dart'; @@ -94,27 +95,36 @@ class _UpdatePpmState extends State with TickerProviderStateMixin { @override void initState() { + super.initState(); ppmProvider = Provider.of(context, listen: false); _planPreventiveVisit = widget.planPreventiveVisit; - // WidgetsBinding.instance.addPostFrameCallback((_) { - initTabs(_planPreventiveVisit.typeOfService); - // }); - super.initState(); + int initialTabs = (_planPreventiveVisit.typeOfService?.value == 2) ? 4 : 3; + _tabController = TabController(length: initialTabs, vsync: this); + WidgetsBinding.instance.addPostFrameCallback((_) { + ppmProvider.totalTabs = initialTabs; + }); } + void initTabs(typeOfService) { if (typeOfService == null) { return; } - if (typeOfService?.value == 2) { - ppmProvider.totalTabs = 4; - } else { - ppmProvider.totalTabs = 3; + + int newTabCount = (typeOfService?.value == 2) ? 4 : 3; + + // Only recreate TabController if tab count has changed + if (_tabController?.length != newTabCount) { + _tabController?.dispose(); + _tabController = TabController(length: newTabCount, vsync: this); + + // Update provider safely after current build + WidgetsBinding.instance.addPostFrameCallback((_) { + ppmProvider.totalTabs = newTabCount; + }); } - _tabController?.dispose; - _tabController = TabController(length: ppmProvider.totalTabs, vsync: this); } int tabIndex = 0; @@ -208,70 +218,84 @@ class _UpdatePpmState extends State with TickerProviderStateMixin { ).expanded, FooterActionButton.footerContainer( context: context, - child: Row( + child: Column( children: [ - if (tabIndex == 1) ...[ - AppFilledButton( - buttonColor: _tabController!.index == 0 ? null : AppColor.background(context), - // buttonColor: _tabController!.index == 0 ? null : AppColor.background(context), - textColor: _tabController!.index == 0 ? null : AppColor.blueStatus(context), - showBorder: true, - disableButton: _tabController!.index == 0, - onPressed: () { - if (_tabController!.index == 0) { - return; - } - _tabController?.animateTo(_tabController!.index - 1); - setState(() {}); - return; - }, - label: context.translation.back, - ).expanded, - 8.width, - ], - if (!ppmProvider.isReadOnly) ...[ - AppFilledButton( - // buttonColor: AppColor.white60, - // textColor: AppColor.neutral50, - buttonColor: context.isDark ? AppColor.neutral50 : AppColor.white60, - textColor: context.isDark ? Colors.white : AppColor.neutral50, - onPressed: () { - _onSubmit(status: 0); - }, - label: context.translation.save, - ).expanded, - 8.width, - AppFilledButton( - onPressed: () { - if (tabIndex == 0) { - _onSubmit(status: 1); - return; - } - if (ppmProvider.totalTabs == _tabController!.index + 1) { - _onSubmit(status: 1); - } else { - _tabController?.animateTo(_tabController!.index + 1); - setState(() {}); - } - }, - label: tabIndex == 0 ? "Complete".addTranslation : ((ppmProvider.totalTabs == _tabController!.index + 1) ? "Complete".addTranslation : context.translation.next), - ).expanded, - ] else ...[ - Visibility( - visible: tabIndex != 0, - child: AppFilledButton( - onPressed: () { - if (ppmProvider.totalTabs == _tabController!.index + 1) { - Navigator.pop(context); + Row( + children: [ + if (tabIndex == 1) ...[ + AppFilledButton( + buttonColor: _tabController!.index == 0 ? null : AppColor.background(context), + // buttonColor: _tabController!.index == 0 ? null : AppColor.background(context), + textColor: _tabController!.index == 0 ? null : AppColor.blueStatus(context), + showBorder: true, + disableButton: _tabController!.index == 0, + onPressed: () { + if (_tabController!.index == 0) { + return; + } + _tabController?.animateTo(_tabController!.index - 1); + setState(() {}); return; - } - _tabController?.animateTo(_tabController!.index + 1); - setState(() {}); - }, - label: ppmProvider.totalTabs == _tabController!.index + 1 ? context.translation.close : context.translation.next, - ).expanded, - ), - ] + }, + label: context.translation.back, + ).expanded, + 8.width, + ], + if (!ppmProvider.isReadOnly) ...[ + AppFilledButton( + // buttonColor: AppColor.white60, + // textColor: AppColor.neutral50, + buttonColor: context.isDark ? AppColor.neutral50 : AppColor.white60, + textColor: context.isDark ? Colors.white : AppColor.neutral50, + onPressed: () { + _onSubmit(status: 0); + }, + label: context.translation.save, + ).expanded, + 8.width, + AppFilledButton( + onPressed: () { + if (tabIndex == 0) { + _onSubmit(status: 1); + return; + } + if (ppmProvider.totalTabs == _tabController!.index + 1) { + _onSubmit(status: 1); + } else { + _tabController?.animateTo(_tabController!.index + 1); + setState(() {}); + } + }, + label: tabIndex == 0 ? "Complete".addTranslation : ((ppmProvider.totalTabs == _tabController!.index + 1) ? "Complete".addTranslation : context.translation.next), + ).expanded, + ] else ...[ + Visibility( + visible: tabIndex != 0, + child: AppFilledButton( + onPressed: () { + if (ppmProvider.totalTabs == _tabController!.index + 1) { + Navigator.pop(context); + return; + } + _tabController?.animateTo(_tabController!.index + 1); + setState(() {}); + }, + label: ppmProvider.totalTabs == _tabController!.index + 1 ? context.translation.close : context.translation.next, + ).expanded, + ), + ] + ], + ), + + 12.height, + AppFilledButton( + onPressed: () async { + Navigator.of(context).push(MaterialPageRoute(builder: (_) => const PmActivitiesListView())); + // getVisitData(); + }, + buttonColor: AppColor.neutral50, + label: context.translation.activities, + ), ], ), ), diff --git a/lib/modules/pm_module/ppm_wo/update_ppm/wo_info_form.dart b/lib/modules/pm_module/ppm_wo/update_ppm/wo_info_form.dart index 55170e3b..91eafc9d 100644 --- a/lib/modules/pm_module/ppm_wo/update_ppm/wo_info_form.dart +++ b/lib/modules/pm_module/ppm_wo/update_ppm/wo_info_form.dart @@ -13,6 +13,7 @@ import 'package:test_sa/models/device/asset.dart'; import 'package:test_sa/models/device/model_definition.dart'; import 'package:test_sa/models/device/supplier.dart'; import 'package:test_sa/models/generic_attachment_model.dart'; +import 'package:test_sa/models/helper_data_models/workorder/work_order_helper_models.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'; @@ -21,6 +22,7 @@ import 'package:test_sa/models/new_models/room_model.dart'; import 'package:test_sa/models/new_models/site.dart'; import 'package:test_sa/models/plan_preventive_visit/plan_preventive_visit_model.dart'; import 'package:test_sa/modules/cm_module/cm_request_utils.dart'; +import 'package:test_sa/modules/cm_module/views/forms/cost/cost_detail_form_screen.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/new_views/common_widgets/app_text_form_field.dart'; import 'package:test_sa/new_views/common_widgets/single_item_drop_down_menu.dart'; @@ -357,6 +359,8 @@ class _WoInfoFormState extends State { ), ], ).toShadowContainer(context), + 16.height, + costCard(context, ppmProvider), ], ), ), @@ -454,6 +458,79 @@ class _WoInfoFormState extends State { ); } + Widget costCard(BuildContext context, PpmProvider provider) { + ///Need to update this when actual flow decided... + PlanPreventiveVisit? model = provider.planPreventiveVisit; + final costModel = WorkOrderCostModel( + workOrderId: int.tryParse(model?.id ?? ''), + labourCost: model?.labourCost, + sparePartCost: model?.sparePartCost, + travelCost: model?.travelCost, + qAmount: model?.qAmount, + prNo: model?.prNo, + poNo: model?.poNo, + mrNo: model?.mrNo, + exchangeCost: model?.exchangeCost, + ); + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + InfoHeader16Widget("Cost"), + const Spacer(), + if (!provider.isReadOnlyRequest) + "edit_icon".toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)).onPress(() async { + provider.workOrderCostModel = costModel; + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => CostDetailFormScreen( + model: provider.workOrderCostModel, + provider: provider, + ), + ), + ); + }), + ], + ), + InfoTextWidget( + label: 'Spare Part Cost'.addTranslation, + value: costModel.sparePartCost?.toString() ?? '-', + ), + InfoTextWidget( + label: 'Labor Cost'.addTranslation, + value: costModel.labourCost?.toString() ?? '-', + ), + InfoTextWidget( + label: 'Travel Cost'.addTranslation, + value: costModel.travelCost?.toString() ?? '-', + ), + InfoTextWidget( + label: 'Quot Amount'.addTranslation, + value: costModel.qAmount?.toString() ?? '-', + ), + InfoTextWidget( + label: 'PR No'.addTranslation, + value: costModel.prNo?.toString() ?? '-', + ), + InfoTextWidget( + label: 'PO No'.addTranslation, + value: costModel.poNo?.toString() ?? '-', + ), + InfoTextWidget( + label: 'MR No'.addTranslation, + value: costModel.mrNo?.toString() ?? '-', + ), + InfoTextWidget( + label: 'Exchange Cost'.addTranslation, + value: costModel.exchangeCost?.toString() ?? '-', + ), + ], + ).toShadowContainer(context); + } + // String formatDuration1(int seconds) { // int hours = seconds ~/ 3600; // int minutes = (seconds % 3600) ~/ 60; From 75349c4724d785c19e49dc0dc899244c8af92984 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Tue, 11 Aug 2026 16:08:51 +0300 Subject: [PATCH 03/22] improvements --- .../plan_preventive_visit_model.dart | 1 - .../forms/spare_part/spare_part_request.dart | 63 +++++-------------- .../ppm_wo/update_ppm/update_ppm.dart | 10 +-- .../ppm_wo/update_ppm/wo_info_form.dart | 16 ++--- lib/views/widgets/timer/app_timer.dart | 4 +- 5 files changed, 25 insertions(+), 69 deletions(-) diff --git a/lib/models/plan_preventive_visit/plan_preventive_visit_model.dart b/lib/models/plan_preventive_visit/plan_preventive_visit_model.dart index 87087c3b..1feb33d7 100644 --- a/lib/models/plan_preventive_visit/plan_preventive_visit_model.dart +++ b/lib/models/plan_preventive_visit/plan_preventive_visit_model.dart @@ -197,7 +197,6 @@ class PlanPreventiveVisit { }); } if (json['activitySparePartsPPM'] != null) { - log("activitySparePartsPPM: ${json['activitySparePartsPPM']}"); activities = []; json['activitySparePartsPPM'].forEach((v) { activities!.add(PMSparePartActivityModel.fromJson(v)); diff --git a/lib/modules/cm_module/views/forms/spare_part/spare_part_request.dart b/lib/modules/cm_module/views/forms/spare_part/spare_part_request.dart index 2cc85ef4..f961f470 100644 --- a/lib/modules/cm_module/views/forms/spare_part/spare_part_request.dart +++ b/lib/modules/cm_module/views/forms/spare_part/spare_part_request.dart @@ -1,4 +1,3 @@ - import 'dart:convert'; import 'dart:developer'; import 'dart:io'; @@ -27,6 +26,7 @@ import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; import 'package:test_sa/new_views/common_widgets/single_item_drop_down_menu.dart'; import 'package:test_sa/providers/loading_list_notifier.dart'; import 'package:test_sa/views/widgets/images/multi_image_picker.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/no_data_found.dart'; @@ -80,8 +80,7 @@ class _SparePartRequestState extends State { activityStatus: statusLookup, comment: '', ); - } else if (widget.provider.sparePartHelperModel == null || - widget.provider.sparePartHelperModel?.id != widget.model?.id) { + } else if (widget.provider.sparePartHelperModel == null || widget.provider.sparePartHelperModel?.id != widget.model?.id) { widget.provider.sparePartHelperModel = SparePartHelperModel( id: widget.model?.id ?? 0, workOrderId: widget.fromCm ? widget.model?.workOrderId ?? widget.provider.currentWorkOrder?.data?.requestId : null, @@ -102,9 +101,7 @@ class _SparePartRequestState extends State { Future _loadInitialData() async { setState(() => _isLoading = true); _spareParts = await _partsProvider!.getPartsListByDisplayName( - assetId: widget.fromCm - ? widget.provider.currentWorkOrder?.data?.asset?.id - : widget.provider.planPreventiveVisit.asset.id, + assetId: widget.fromCm ? widget.provider.currentWorkOrder?.data?.asset?.id : widget.provider.planPreventiveVisit.asset.id, ); final bool isNewActivity = widget.model?.id == null || widget.model?.id == 0; _attachments = []; @@ -183,9 +180,9 @@ class _SparePartRequestState extends State { ) { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, + spacing: 12, children: [ - context.translation.sparePartDetails.heading5(context).custom(color: AppColor.textStyleColor(context)), - 12.height, + InfoHeader16Widget(context.translation.sparePartDetails), SingleItemDropDownMenu( context: context, title: context.translation.part, @@ -202,9 +199,8 @@ class _SparePartRequestState extends State { }, ), if (model?.sparePart?.id != null) ...[ - 12.height, SizedBox( - height: 30.toScreenHeight, + // height: 30.toScreenHeight, child: Text( 'View Parts Availability', style: TextStyle( @@ -227,8 +223,6 @@ class _SparePartRequestState extends State { }), ), ], - - 12.height, AppTextFormField( key: ValueKey('quantity_${model?.quantity}'), initialValue: model?.quantity?.round().toString() ?? '', @@ -248,9 +242,6 @@ class _SparePartRequestState extends State { model?.quantity = num.tryParse(text ?? ''); }, ), - - 12.height, - if (showInstallAndReturnQty) ...[ AppTextFormField( key: ValueKey('installQty_${model?.installQty}'), @@ -271,7 +262,6 @@ class _SparePartRequestState extends State { model?.installQty = num.tryParse(text ?? ''); }, ), - 12.height, AppTextFormField( key: ValueKey('returnQty_${model?.returnQty}'), initialValue: model?.returnQty?.round().toString() ?? '', @@ -291,7 +281,6 @@ class _SparePartRequestState extends State { model?.returnQty = num.tryParse(text ?? ''); }, ), - 12.height, ], AppTextFormField( key: ValueKey('oracle_${model?.sparePart?.oracleCode}'), @@ -302,8 +291,6 @@ class _SparePartRequestState extends State { backgroundColor: AppColor.disableFieldBackground(context), enable: false, ), - - 12.height, AppTextFormField( key: ValueKey('comment_${model?.comment}'), initialValue: model?.comment ?? '', @@ -319,8 +306,6 @@ class _SparePartRequestState extends State { model?.comment = value; }, ), - - 12.height, AttachmentPicker( label: context.translation.attachQuotation, attachment: _attachments, @@ -384,20 +369,16 @@ class _SparePartRequestState extends State { provider.getWorkOrderById( id: provider.currentWorkOrder!.data!.requestId!, ); - } - else{ + } else { provider.getPlanPreventiveVisitById( - int.tryParse(provider.planPreventiveVisit?.id ?? ''), + int.tryParse(provider.planPreventiveVisit?.id ?? ''), ); } if (mounted) Navigator.pop(context); // Close form if (model?.id == 0) { - ServiceRequestBottomSheet.addAnotherSpareRequestBottomSheet( - context: context, - fromCm: widget.fromCm - ); + ServiceRequestBottomSheet.addAnotherSpareRequestBottomSheet(context: context, fromCm: widget.fromCm); const SizedBox().flushBar( context: context, title: context.translation.sparePartActivitySuccess, @@ -426,8 +407,8 @@ class PartDetailBottomSheetSheet extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - 8.height, - 'Parts Availability'.heading6(context).paddingOnly(top: 12, start: 12), + 12.height, + 'Parts Availability'.heading4(context), Expanded( child: ListenableBuilder( listenable: provider, @@ -444,10 +425,7 @@ class PartDetailBottomSheetSheet extends StatelessWidget { separatorBuilder: (czt, index) => 12.height, itemBuilder: (context, index) { StoreAvailability model = provider.storeAvailability[index]; - return _partAvailableQuantityCard( - context: context, - model: model, - ); + return _partAvailableQuantityCard(context: context, model: model); }, ); }, @@ -458,31 +436,24 @@ class PartDetailBottomSheetSheet extends StatelessWidget { ); } - Widget _partAvailableQuantityCard({ - required StoreAvailability model, - required BuildContext context, - }) { + Widget _partAvailableQuantityCard({required StoreAvailability model, required BuildContext context}) { return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ InfoTextWidget( - label: "Site Name:".addTranslation, + label: "Site Name".addTranslation, value: model.siteName ?? '', ), InfoTextWidget( - label: "Store Name:".addTranslation, + label: "Store Name".addTranslation, value: model.storeName ?? '', ), InfoTextWidget( - label: "Available Quantity:".addTranslation, + label: "Available Quantity".addTranslation, value: model.availablityQty?.toString() ?? '', ), ], - ).toShadowContainer( - context, - padding: 12, - showShadow: false, - ); + ).toShadowContainer(context); } } diff --git a/lib/modules/pm_module/ppm_wo/update_ppm/update_ppm.dart b/lib/modules/pm_module/ppm_wo/update_ppm/update_ppm.dart index e431d5de..f6be4486 100644 --- a/lib/modules/pm_module/ppm_wo/update_ppm/update_ppm.dart +++ b/lib/modules/pm_module/ppm_wo/update_ppm/update_ppm.dart @@ -45,7 +45,6 @@ class _UpdatePpmState extends State with TickerProviderStateMixin { TabController? _tabController; late PpmProvider ppmProvider; - _onSubmit({required int status}) async { if (ppmProvider.validate()) { showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading()); @@ -105,9 +104,6 @@ class _UpdatePpmState extends State with TickerProviderStateMixin { }); } - - - void initTabs(typeOfService) { if (typeOfService == null) { return; @@ -286,15 +282,13 @@ class _UpdatePpmState extends State with TickerProviderStateMixin { ] ], ), - 12.height, AppFilledButton( onPressed: () async { - Navigator.of(context).push(MaterialPageRoute(builder: (_) => const PmActivitiesListView())); - // getVisitData(); + Navigator.of(context).push(MaterialPageRoute(builder: (_) => const PmActivitiesListView())); }, buttonColor: AppColor.neutral50, - label: context.translation.activities, + label: "View Spare Parts", ), ], ), diff --git a/lib/modules/pm_module/ppm_wo/update_ppm/wo_info_form.dart b/lib/modules/pm_module/ppm_wo/update_ppm/wo_info_form.dart index 91eafc9d..3b25ec42 100644 --- a/lib/modules/pm_module/ppm_wo/update_ppm/wo_info_form.dart +++ b/lib/modules/pm_module/ppm_wo/update_ppm/wo_info_form.dart @@ -106,6 +106,7 @@ class _WoInfoFormState extends State { IgnorePointer( ignoring: ppmProvider.isReadOnly, child: Column( + spacing: 12, children: [ Column( mainAxisSize: MainAxisSize.min, @@ -147,7 +148,6 @@ class _WoInfoFormState extends State { // '${context.translation.executionTimeFrame}: ${widget.planPreventiveVisit.executionTimeFrame ?? ""} days'.bodyText2(context).custom(color: AppColor.neutral120), ], ).toShadowContainer(context), - 12.height, Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -221,9 +221,10 @@ class _WoInfoFormState extends State { // } // }, // ), - 8.height, + // 8.height,// Column( mainAxisAlignment: MainAxisAlignment.start, + spacing: 12, children: [ SingleItemDropDownMenu( context: context, @@ -238,7 +239,6 @@ class _WoInfoFormState extends State { } }, ), - 8.height, ADatePicker( label: context.translation.actualVisit, hideShadow: true, @@ -285,8 +285,6 @@ class _WoInfoFormState extends State { // } // }, // ), - - 8.height, SingleItemDropDownMenu( context: context, backgroundColor: AppColor.fieldBgColor(context), @@ -299,7 +297,6 @@ class _WoInfoFormState extends State { } }, ), - 8.height, SingleItemDropDownMenu( context: context, backgroundColor: AppColor.fieldBgColor(context), @@ -314,7 +311,6 @@ class _WoInfoFormState extends State { } }, ), - 8.height, SingleItemDropDownMenu( context: context, backgroundColor: AppColor.fieldBgColor(context), @@ -335,9 +331,7 @@ class _WoInfoFormState extends State { } }, ), - 8.height, _timerWidget(context, totalWorkingHours), - 8.height, AppTextFormField( labelText: context.translation.comment, backgroundColor: AppColor.fieldBgColor(context), @@ -349,7 +343,6 @@ class _WoInfoFormState extends State { widget.planPreventiveVisit.comments = value; }, ), - 16.height, AttachmentPicker( label: context.translation.attachments, attachment: ppmProvider.ppmPlanAttachments, @@ -359,7 +352,6 @@ class _WoInfoFormState extends State { ), ], ).toShadowContainer(context), - 16.height, costCard(context, ppmProvider), ], ), @@ -417,6 +409,7 @@ class _WoInfoFormState extends State { Widget _timerWidget(BuildContext context, double totalWorkingHours) { return Column( + spacing: 12, children: [ AppTimer( label: context.translation.workingHours, @@ -438,7 +431,6 @@ class _WoInfoFormState extends State { return true; }, ), - 11.height, if (totalWorkingHours > 0.0) ...[ WorkingTimeTile( timerList: timerList, diff --git a/lib/views/widgets/timer/app_timer.dart b/lib/views/widgets/timer/app_timer.dart index d33b0274..759a9c17 100644 --- a/lib/views/widgets/timer/app_timer.dart +++ b/lib/views/widgets/timer/app_timer.dart @@ -158,6 +158,7 @@ class _AppTimerState extends State { @override Widget build(BuildContext context) { return Column( + spacing: 12, children: [ if (canPickTime) ...[ Row( @@ -228,7 +229,6 @@ class _AppTimerState extends State { ], ), if (_tempPickerTimer != null) ...[ - 8.height, Row( children: [ Column( @@ -248,7 +248,7 @@ class _AppTimerState extends State { ], ).toShadowContainer(context) ], - 8.height, + // 8.height, ], if (widget.showTimer) Container( From c88b5770c9b7c8baccbc3ce5b20a2882134ec470 Mon Sep 17 00:00:00 2001 From: WaseemAbbasi22 <50428976+WaseemAbbasi22@users.noreply.github.com> Date: Wed, 12 Aug 2026 10:11:17 +0300 Subject: [PATCH 04/22] improvements --- .../cm_module/views/forms/spare_part/spare_part_request.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/cm_module/views/forms/spare_part/spare_part_request.dart b/lib/modules/cm_module/views/forms/spare_part/spare_part_request.dart index f961f470..cc5fb0a1 100644 --- a/lib/modules/cm_module/views/forms/spare_part/spare_part_request.dart +++ b/lib/modules/cm_module/views/forms/spare_part/spare_part_request.dart @@ -377,7 +377,7 @@ class _SparePartRequestState extends State { if (mounted) Navigator.pop(context); // Close form - if (model?.id == 0) { + if (model?.id == 0&&widget.fromCm) { ServiceRequestBottomSheet.addAnotherSpareRequestBottomSheet(context: context, fromCm: widget.fromCm); const SizedBox().flushBar( context: context, From 86aa0f67d85fac53b33ee8424b71abc9278de745 Mon Sep 17 00:00:00 2001 From: WaseemAbbasi22 <50428976+WaseemAbbasi22@users.noreply.github.com> Date: Wed, 12 Aug 2026 11:34:06 +0300 Subject: [PATCH 05/22] improvements --- .../spare_part/activity_spare_part_model.dart | 2 ++ .../activities/pm_activities_list_view.dart | 21 +++++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/lib/models/helper_data_models/spare_part/activity_spare_part_model.dart b/lib/models/helper_data_models/spare_part/activity_spare_part_model.dart index 548d47bb..79b8b162 100644 --- a/lib/models/helper_data_models/spare_part/activity_spare_part_model.dart +++ b/lib/models/helper_data_models/spare_part/activity_spare_part_model.dart @@ -73,6 +73,8 @@ class SparePartHelperModel { data['partCatalogItemId'] = sparePart?.id; data['quantity'] = quantity; data['comment'] = comment; + data['installQty'] = installQty; + data['returnQty'] = returnQty; if (sparePartAttachments.isNotEmpty) { data['acitiySparePartAttachments'] = sparePartAttachments!.map((v) => v.toJson()).toList(); } diff --git a/lib/modules/pm_module/ppm_wo/activities/pm_activities_list_view.dart b/lib/modules/pm_module/ppm_wo/activities/pm_activities_list_view.dart index fa749597..bf8527e0 100644 --- a/lib/modules/pm_module/ppm_wo/activities/pm_activities_list_view.dart +++ b/lib/modules/pm_module/ppm_wo/activities/pm_activities_list_view.dart @@ -1,4 +1,3 @@ - import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:test_sa/controllers/api_routes/urls.dart'; @@ -77,8 +76,7 @@ class _PmActivitiesListViewState extends State { return sparePartActivityCard(pmProvider: pmProvider, userProvider: userProvider, context: context, activity: activities[index]); }, ).expanded, - if (userProvider.user!.type == UsersTypes.engineer && - (pmProvider.planPreventiveVisit?.visitStatus?.value != 5 && pmProvider.planPreventiveVisit?.visitStatus?.value != 3)) + if (userProvider.user!.type == UsersTypes.engineer && (pmProvider.planPreventiveVisit?.visitStatus?.value != 5 && pmProvider.planPreventiveVisit?.visitStatus?.value != 3)) FooterActionButton.footerContainer( context: context, child: AppFilledButton( @@ -165,13 +163,28 @@ class _PmActivitiesListViewState extends State { label: context.translation.oracleCode, value: activity.partCatalogItem?.oracleCode, ), + if (activity.quantity != null && activity.quantity! > 0) + InfoTextWidget( + label: context.translation.quantity, + value: activity.quantity.toString(), + ), + if (activity.installQty != null && activity.installQty! > 0) + InfoTextWidget( + label: context.translation.installedQty, + value: activity.installQty.toString(), + ), + if (activity.returnQty != null && activity.returnQty! > 0) + InfoTextWidget( + label: context.translation.returnQty, + value: activity.returnQty.toString(), + ), if (activity.comment?.isNotEmpty ?? false) ...[ const Divider().defaultStyle(context), InfoTextLabelWidget(label: activity.comment ?? ""), ], if (activity.activitySparePartPPMAttachments.isNotEmpty) ...[ const Divider().defaultStyle(context), - FilesList(images: activity.activitySparePartPPMAttachments.map((toElement) => URLs.getFileUrl(toElement.name??'')??'').toList()), + FilesList(images: activity.activitySparePartPPMAttachments.map((toElement) => URLs.getFileUrl(toElement.name ?? '') ?? '').toList()), ], ], ).toShadowContainer(context, showShadow: false).onPress(() { From d63f7d8939b3cba6c757c5c4449780480149b66c Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Mon, 17 Aug 2026 14:39:58 +0300 Subject: [PATCH 06/22] improvements --- .../forms/spare_part/spare_part_request.dart | 60 ++++++++++--------- .../ppm_wo/update_ppm/update_ppm.dart | 2 +- 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/lib/modules/cm_module/views/forms/spare_part/spare_part_request.dart b/lib/modules/cm_module/views/forms/spare_part/spare_part_request.dart index cc5fb0a1..63dcd3b7 100644 --- a/lib/modules/cm_module/views/forms/spare_part/spare_part_request.dart +++ b/lib/modules/cm_module/views/forms/spare_part/spare_part_request.dart @@ -377,7 +377,7 @@ class _SparePartRequestState extends State { if (mounted) Navigator.pop(context); // Close form - if (model?.id == 0&&widget.fromCm) { + if (model?.id == 0 && widget.fromCm) { ServiceRequestBottomSheet.addAnotherSpareRequestBottomSheet(context: context, fromCm: widget.fromCm); const SizedBox().flushBar( context: context, @@ -403,35 +403,37 @@ class PartDetailBottomSheetSheet extends StatelessWidget { return SizedBox( height: SizeConfig.screenHeight! / 2.2, width: SizeConfig.screenWidth, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - 12.height, - 'Parts Availability'.heading4(context), - Expanded( - child: ListenableBuilder( - listenable: provider, - builder: (context, _) { - return provider.isLoading - ? CircularProgressIndicator( - color: AppColor.loadingColor(context), - ).center - : provider.storeAvailability.isEmpty - ? const NoDataFound().center - : ListView.separated( - padding: const EdgeInsets.only(top: 12), - itemCount: provider.storeAvailability.length, - separatorBuilder: (czt, index) => 12.height, - itemBuilder: (context, index) { - StoreAvailability model = provider.storeAvailability[index]; - return _partAvailableQuantityCard(context: context, model: model); - }, - ); - }, + child: SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 12.height, + 'Parts Availability'.heading4(context), + Expanded( + child: ListenableBuilder( + listenable: provider, + builder: (context, _) { + return provider.isLoading + ? CircularProgressIndicator( + color: AppColor.loadingColor(context), + ).center + : provider.storeAvailability.isEmpty + ? const NoDataFound().center + : ListView.separated( + padding: const EdgeInsets.only(top: 12,bottom: 12), + itemCount: provider.storeAvailability.length, + separatorBuilder: (czt, index) => 12.height, + itemBuilder: (context, index) { + StoreAvailability model = provider.storeAvailability[index]; + return _partAvailableQuantityCard(context: context, model: model); + }, + ); + }, + ), ), - ), - ], + ], + ), ), ); } diff --git a/lib/modules/pm_module/ppm_wo/update_ppm/update_ppm.dart b/lib/modules/pm_module/ppm_wo/update_ppm/update_ppm.dart index f6be4486..0e5a39ab 100644 --- a/lib/modules/pm_module/ppm_wo/update_ppm/update_ppm.dart +++ b/lib/modules/pm_module/ppm_wo/update_ppm/update_ppm.dart @@ -287,7 +287,7 @@ class _UpdatePpmState extends State with TickerProviderStateMixin { onPressed: () async { Navigator.of(context).push(MaterialPageRoute(builder: (_) => const PmActivitiesListView())); }, - buttonColor: AppColor.neutral50, + buttonColor: context.isDark ? AppColor.primary10 : AppColor.neutral50, label: "View Spare Parts", ), ], From 30a69b1ca64c2515d1f2b1ecb626f874742f12d2 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Mon, 17 Aug 2026 17:43:12 +0300 Subject: [PATCH 07/22] improvements-cont --- .../providers/api/ppm_provider.dart | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/lib/controllers/providers/api/ppm_provider.dart b/lib/controllers/providers/api/ppm_provider.dart index 19d222cc..f56f7fdd 100644 --- a/lib/controllers/providers/api/ppm_provider.dart +++ b/lib/controllers/providers/api/ppm_provider.dart @@ -72,6 +72,7 @@ class PpmProvider extends ChangeNotifier { nextPage = true; stateCode = null; } + List _storeAvailability = []; List get storeAvailability => _storeAvailability; @@ -191,24 +192,22 @@ class PpmProvider extends ChangeNotifier { if (response.statusCode >= 200 && response.statusCode < 300) { ppmPlanAttachments = []; planPreventiveVisit = PlanPreventiveVisit.fromJson(json.decode(response.body)["data"]); - + // Set isReadOnly based on visit status (1 or 3 means closed/completed) if (planPreventiveVisit?.visitStatus?.value! == 1 || planPreventiveVisit?.visitStatus?.value == 3) { isReadOnly = true; } else { isReadOnly = false; } - + // Set isReadOnlyRequest based on visit status - same logic as CMDetailProvider //Need to confirm this with backend Status values: 3 = Closed, 5 = Completed, 6 = Cancelled (adjust based on your PM status values) - if (planPreventiveVisit?.visitStatus?.value == 3 || - planPreventiveVisit?.visitStatus?.value == 5 || - planPreventiveVisit?.visitStatus?.value == 6) { + if (planPreventiveVisit?.visitStatus?.value == 3 || planPreventiveVisit?.visitStatus?.value == 5 || planPreventiveVisit?.visitStatus?.value == 6) { isReadOnlyRequest = true; } else { isReadOnlyRequest = false; } - + isLoading = false; notifyListeners(); return planPreventiveVisit; @@ -428,22 +427,23 @@ class PpmProvider extends ChangeNotifier { } } - Future updateActivitySparePart() async { - isLoading = true; + Future updateActivitySparePart(BuildContext context) async { Response response; try { - response = await ApiManager.instance.post(URLs.updatePmActivitySparePart, body: sparePartHelperModel!.toPMJson()); - stateCode = response.statusCode; - isLoading = false; - notifyListeners(); - if (stateCode == 200) { + response = await ApiManager.instance.post(URLs.updatePmActivitySparePart, body: sparePartHelperModel!.toPMJson(), showToast: false); + + if (response.statusCode == 200) { + final responseBody = jsonDecode(response.body); + if (responseBody is Map) { + final message = responseBody["message"]; // Reuse parsed body + if (message != null && message.toString().isNotEmpty) { + Fluttertoast.showToast(msg: message, toastLength: Toast.LENGTH_LONG); + } + } return true; } return false; } catch (error) { - isLoading = false; - stateCode = -1; - notifyListeners(); return false; } } @@ -454,8 +454,7 @@ class PpmProvider extends ChangeNotifier { try { final response = await ApiManager.instance.delete('${URLs.deletePmActivitySparePart}?activitySparePartPPMId=$id', body: {}); stateCode = response.statusCode; - if (response.statusCode >= 200 && response.statusCode < 300) { - } + if (response.statusCode >= 200 && response.statusCode < 300) {} isLoading = false; notifyListeners(); return response.statusCode; @@ -476,16 +475,14 @@ class PpmProvider extends ChangeNotifier { notifyListeners(); if (stateCode == 200) { planPreventiveVisit = PlanPreventiveVisit.fromJson(json.decode(response.body)["data"]); - + // Update isReadOnlyRequest after fetching updated data - if (planPreventiveVisit?.visitStatus?.value == 3 || - planPreventiveVisit?.visitStatus?.value == 5 || - planPreventiveVisit?.visitStatus?.value == 6) { + if (planPreventiveVisit?.visitStatus?.value == 3 || planPreventiveVisit?.visitStatus?.value == 5 || planPreventiveVisit?.visitStatus?.value == 6) { isReadOnlyRequest = true; } else { isReadOnlyRequest = false; } - + notifyListeners(); return true; } From 764814171b7da78c2f755de5941b2185b270a90b Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Tue, 18 Aug 2026 11:10:30 +0300 Subject: [PATCH 08/22] improvements --- .../providers/api/ppm_provider.dart | 16 +++++++-------- .../forms/spare_part/spare_part_request.dart | 10 ++++++++-- .../activities/pm_activities_list_view.dart | 20 +++++++++++-------- .../swipe_module/dialoge/info_dialog.dart | 2 +- 4 files changed, 28 insertions(+), 20 deletions(-) diff --git a/lib/controllers/providers/api/ppm_provider.dart b/lib/controllers/providers/api/ppm_provider.dart index f56f7fdd..7861b5b1 100644 --- a/lib/controllers/providers/api/ppm_provider.dart +++ b/lib/controllers/providers/api/ppm_provider.dart @@ -408,10 +408,7 @@ class PpmProvider extends ChangeNotifier { isLoading = true; Response response; try { - response = await ApiManager.instance.post( - URLs.createPmActivitySparePart, - body: sparePartHelperModel!.toPMJson(), - ); + response = await ApiManager.instance.post(URLs.createPmActivitySparePart, body: sparePartHelperModel!.toPMJson()); stateCode = response.statusCode; isLoading = false; notifyListeners(); @@ -427,24 +424,25 @@ class PpmProvider extends ChangeNotifier { } } - Future updateActivitySparePart(BuildContext context) async { + Future updateActivitySparePart() async { Response response; try { response = await ApiManager.instance.post(URLs.updatePmActivitySparePart, body: sparePartHelperModel!.toPMJson(), showToast: false); if (response.statusCode == 200) { + return ""; + } else { final responseBody = jsonDecode(response.body); if (responseBody is Map) { final message = responseBody["message"]; // Reuse parsed body if (message != null && message.toString().isNotEmpty) { - Fluttertoast.showToast(msg: message, toastLength: Toast.LENGTH_LONG); + return message; } } - return true; + return "Failed, Try Again"; } - return false; } catch (error) { - return false; + return "Failed, Try Again"; } } diff --git a/lib/modules/cm_module/views/forms/spare_part/spare_part_request.dart b/lib/modules/cm_module/views/forms/spare_part/spare_part_request.dart index 63dcd3b7..19b58ed1 100644 --- a/lib/modules/cm_module/views/forms/spare_part/spare_part_request.dart +++ b/lib/modules/cm_module/views/forms/spare_part/spare_part_request.dart @@ -356,10 +356,12 @@ class _SparePartRequestState extends State { // Submit bool success = false; + String message = ""; if (model?.id == 0) { success = await provider.createActivitySparePart(); } else { - success = await provider.updateActivitySparePart(); + message = await provider.updateActivitySparePart(); + success = message.isEmpty; } if (mounted) Navigator.pop(context); // Close loading @@ -385,6 +387,10 @@ class _SparePartRequestState extends State { message: '', ); } + } else { + if(message.isNotEmpty) { + context.showInfoDialog(message, title: "Error"); + } } } } @@ -421,7 +427,7 @@ class PartDetailBottomSheetSheet extends StatelessWidget { : provider.storeAvailability.isEmpty ? const NoDataFound().center : ListView.separated( - padding: const EdgeInsets.only(top: 12,bottom: 12), + padding: const EdgeInsets.only(top: 12, bottom: 12), itemCount: provider.storeAvailability.length, separatorBuilder: (czt, index) => 12.height, itemBuilder: (context, index) { diff --git a/lib/modules/pm_module/ppm_wo/activities/pm_activities_list_view.dart b/lib/modules/pm_module/ppm_wo/activities/pm_activities_list_view.dart index bf8527e0..d9a43385 100644 --- a/lib/modules/pm_module/ppm_wo/activities/pm_activities_list_view.dart +++ b/lib/modules/pm_module/ppm_wo/activities/pm_activities_list_view.dart @@ -115,6 +115,9 @@ class _PmActivitiesListViewState extends State { pmProvider.planPreventiveVisit?.visitStatus?.value != 5 && pmProvider.planPreventiveVisit?.visitStatus?.value != 3; + bool canDelete = showActionButtons && activity.activityStatus?.value == 1; + bool canEdit = showActionButtons && (canDelete || activity.activityStatus!.value! == 3); + return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -127,15 +130,16 @@ class _PmActivitiesListViewState extends State { textColor: AppColor.white10, backgroundColor: AppColor.primary10, ), - if (showActionButtons) - Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + spacing: 12, + children: [ + if (canEdit) "edit_icon".toSvgAsset(height: 24, width: 24, color: AppColor.icon2Color(context)).onPress(() { editSparePartRequest(context: context, pmProvider: pmProvider, activity: activity); }), - 12.width, + if (canDelete) "delete_icon".toSvgAsset(height: 24, width: 24).onPress(() async { showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading()); int status = await pmProvider.deleteActivitySparePart(id: activity.id!); @@ -146,8 +150,8 @@ class _PmActivitiesListViewState extends State { Navigator.pop(context); } }), - ], - ) + ], + ) ], ), 6.height, diff --git a/lib/new_views/swipe_module/dialoge/info_dialog.dart b/lib/new_views/swipe_module/dialoge/info_dialog.dart index 5809ab6d..8dcc0cb7 100644 --- a/lib/new_views/swipe_module/dialoge/info_dialog.dart +++ b/lib/new_views/swipe_module/dialoge/info_dialog.dart @@ -41,7 +41,7 @@ class InfoDialog extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( - child: InfoHeader16Widget("Information").paddingOnly(top: 16), + child: InfoHeader16Widget(title ?? "Information").paddingOnly(top: 16), // Text( // title ?? "Information", // style: TextStyle(fontSize: 24, fontWeight: FontWeight.w600, color: AppColor.headingTextColor(context), height: 35 / 24, letterSpacing: -0.96), From fb6716f3dfe000682fe0739075e2a54132b98631 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Tue, 18 Aug 2026 15:03:57 +0300 Subject: [PATCH 09/22] improvement. --- lib/views/widgets/loaders/image_loader.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/views/widgets/loaders/image_loader.dart b/lib/views/widgets/loaders/image_loader.dart index eed5d473..6197e620 100644 --- a/lib/views/widgets/loaders/image_loader.dart +++ b/lib/views/widgets/loaders/image_loader.dart @@ -1,5 +1,6 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; class ImageLoader extends StatelessWidget { final String? url; @@ -20,7 +21,7 @@ class ImageLoader extends StatelessWidget { alignment: Alignment.center, width: width, height: height, - placeholder: (context, url) => const Center(child: CircularProgressIndicator()), + placeholder: (context, url) => Center(child: CircularProgressIndicator(color: AppColor.loadingColor(context))), errorWidget: (context, url, error) => Icon(showDefaultIcon ? Icons.image_outlined : Icons.broken_image_rounded), ); } From d7c8d9f3cf97e985071dce9ede3ec1d44bc5ecc2 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Wed, 19 Aug 2026 15:50:31 +0300 Subject: [PATCH 10/22] rename changes, apk provided to FM. --- .../ppm_wo/activities/pm_activities_list_view.dart | 7 ++----- lib/modules/pm_module/ppm_wo/update_ppm/update_ppm.dart | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/modules/pm_module/ppm_wo/activities/pm_activities_list_view.dart b/lib/modules/pm_module/ppm_wo/activities/pm_activities_list_view.dart index d9a43385..a9a1542f 100644 --- a/lib/modules/pm_module/ppm_wo/activities/pm_activities_list_view.dart +++ b/lib/modules/pm_module/ppm_wo/activities/pm_activities_list_view.dart @@ -52,10 +52,7 @@ class _PmActivitiesListViewState extends State { Widget build(BuildContext context) { UserProvider userProvider = Provider.of(context); return Scaffold( - appBar: DefaultAppBar( - showHomeActionButton: true, - title: context.translation.activities, - ), + appBar: const DefaultAppBar(title: "Spare Part Requests"), body: Consumer(builder: (context, PpmProvider pmProvider, child) { activities = pmProvider.planPreventiveVisit?.activities ?? []; if (activities.isNotEmpty && (activities.last.activityStatus!.value == 14 || activities.last.activityStatus!.value == 19)) { @@ -80,7 +77,7 @@ class _PmActivitiesListViewState extends State { FooterActionButton.footerContainer( context: context, child: AppFilledButton( - label: context.translation.createNewActivity, + label: "Create Spare Part Request", maxWidth: true, buttonColor: AppColor.primary10, loading: pmProvider.isLoading, diff --git a/lib/modules/pm_module/ppm_wo/update_ppm/update_ppm.dart b/lib/modules/pm_module/ppm_wo/update_ppm/update_ppm.dart index 0e5a39ab..0060c4e9 100644 --- a/lib/modules/pm_module/ppm_wo/update_ppm/update_ppm.dart +++ b/lib/modules/pm_module/ppm_wo/update_ppm/update_ppm.dart @@ -288,7 +288,7 @@ class _UpdatePpmState extends State with TickerProviderStateMixin { Navigator.of(context).push(MaterialPageRoute(builder: (_) => const PmActivitiesListView())); }, buttonColor: context.isDark ? AppColor.primary10 : AppColor.neutral50, - label: "View Spare Parts", + label: "Spare Parts", ), ], ), From 5f91a195f4590a51352752e0f758594f5f760925 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Thu, 20 Aug 2026 16:04:45 +0300 Subject: [PATCH 11/22] improvement --- lib/modules/demo_module/demo_request_item_view.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/modules/demo_module/demo_request_item_view.dart b/lib/modules/demo_module/demo_request_item_view.dart index 6abc6d58..6d8dd30c 100644 --- a/lib/modules/demo_module/demo_request_item_view.dart +++ b/lib/modules/demo_module/demo_request_item_view.dart @@ -28,7 +28,7 @@ class DemoRequestItemView extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ StatusLabel( - label: requestData!.statusName!, + label: requestData!.statusName, textColor: AppColor.demoRequestStatusTextColor(context, requestData?.statusValue ?? 0), backgroundColor: AppColor.demoRequestStatus(context, requestData?.statusValue ?? 0), ), @@ -54,7 +54,7 @@ class DemoRequestItemView extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ StatusLabel( - label: requestDetails!.status!, + label: requestDetails!.status, textColor: AppColor.demoRequestStatusTextColor(context, requestDetails?.statusValue ?? 0), backgroundColor: AppColor.demoRequestStatus(context, requestDetails?.statusValue ?? 0), ), From 51d1f38083da4f143d003685642a002bc7fa416c Mon Sep 17 00:00:00 2001 From: WaseemAbbasi22 <50428976+WaseemAbbasi22@users.noreply.github.com> Date: Tue, 1 Sep 2026 17:30:09 +0300 Subject: [PATCH 12/22] user site api added --- lib/controllers/api_routes/urls.dart | 9 +++-- .../providers/site_request_provider.dart | 19 +++++++++ .../screens/site_request_form_screen.dart | 39 ++++++++++++++++++- 3 files changed, 61 insertions(+), 6 deletions(-) diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index f45e772e..1055c3e4 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -3,13 +3,13 @@ class URLs { static const String appReleaseBuildNumber = "53"; - 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/mobile"; // host local UAT // static final String _baseUrl = "$_host/v2/mobile"; // new V2 apis - 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/v4/mobile"; // v4 for Demo module // static final String _baseUrl = "$_host/v5/mobile"; // v5 for data segregation // static final String _baseUrl = "$_host/v6/mobile"; // for asset delivery module @@ -68,6 +68,7 @@ class URLs { static get getSiteRequests => "$_baseUrl/SiteRequest/GetSiteRequests"; // get site request history static get getDepartmentsBySites => "$_baseUrl/Customer/GetDepartmentsBySites"; // post get departments by sites static getUserSites(String userId) => "$_baseUrl/Customer/GetSitesForUser?userId=$userId"; // get user's current sites + static getSitesNotAccessibleForUser(String userId) => "$_baseUrl/Customer/GetSitesNotAccessibleForUser?userId=$userId"; // get user's current sites // static get login => "$_baseUrl/MobileAuth/LoginIntegration"; // mobile login static get register => "$_baseUrl/handle/create/user"; // post diff --git a/lib/modules/site_request_module/providers/site_request_provider.dart b/lib/modules/site_request_module/providers/site_request_provider.dart index d97b5b7d..0b4191ab 100644 --- a/lib/modules/site_request_module/providers/site_request_provider.dart +++ b/lib/modules/site_request_module/providers/site_request_provider.dart @@ -27,6 +27,7 @@ class SiteRequestProvider extends ChangeNotifier { // Data List _requestTypes = []; List _userCurrentSites = []; + List userFilterSites = []; List _requestHistory = []; // Pagination @@ -124,6 +125,24 @@ class SiteRequestProvider extends ChangeNotifier { errorMessage: 'Failed to fetch user sites', ); } + Future fetchUserFilteredSites(String userId) async { + if (_loading) return -2; + return _performApiCall( + apiCall: () => ApiManager.instance.post(URLs.getSitesNotAccessibleForUser(userId), showToast: false, body: {}), + onSuccess: (responseData) { + List listJson = responseData['data'] ?? []; + userFilterSites = listJson + .map((item) => SiteRequestSiteModel( + id: item['siteId']?.toString() ?? '', + name: item['siteName'] ?? '', + )) + .toList(); + notifyListeners(); + }, + errorMessage: 'Failed to fetch user sites', + ); + + } /// Submits a site request to the server Future submitSiteRequest(SiteRequestModel request, UserProvider userProvider) async { diff --git a/lib/modules/site_request_module/screens/site_request_form_screen.dart b/lib/modules/site_request_module/screens/site_request_form_screen.dart index 1dde072a..86b905d5 100644 --- a/lib/modules/site_request_module/screens/site_request_form_screen.dart +++ b/lib/modules/site_request_module/screens/site_request_form_screen.dart @@ -175,11 +175,12 @@ class _SiteRequestFormScreenState extends State { _siteReplacements.clear(); _loadedDepartmentSiteIds.clear(); // Reset loaded department site IDs }); - + final userId = _provider.getCurrentUserUserId(_userProvider); // Fetch user's current sites if Change Site or Remove Site is selected if ((_isChangeSiteRequest() || _isRemoveSiteRequest()) && value != null) { - final userId = _provider.getCurrentUserUserId(_userProvider); await _provider.fetchUserCurrentSites(userId); + }else { + await _provider.fetchUserFilteredSites(userId); } // Fetch all sites if Change Site is selected (needed for new site dropdown) @@ -318,6 +319,40 @@ class _SiteRequestFormScreenState extends State { } Widget _buildSiteMultiSelect() { + return MultipleItemDropDownMenu( + context: context, + title: context.translation.site, + initialValue: _selectedSites + .map((s) => SiteRequestSiteModel( + id: s.identifier ?? '', + name: s.name ?? '', + )) + .toList(), + staticData: _provider.userFilterSites, + showAsFullScreenDialog: true, + backgroundColor: context.isDark ? AppColor.neutral60 : Colors.white, + showShadow: true, + height: 56.toScreenHeight, + showCancel: true, + onSelect: (value) { + if (value != null) { + setState(() { + _selectedSites = value.map((s) { + final site = Site( + id: int.tryParse(s.identifier ?? '0'), + custName: s.name, + ); + return site; + }).toList(); + final departmentProvider = Provider.of(context, listen: false); + final siteIds = value.map((site) => int.tryParse(site.identifier ?? '0') ?? 0).toList(); + departmentProvider.setSiteIds(siteIds); + departmentProvider.getData(); + }); + + } + }, + ); return MultipleItemDropDownMenu( context: context, title: context.translation.site, From 2bb126c91f0f57080771230afc941f2236ffa9dc Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Wed, 9 Sep 2026 12:16:27 +0300 Subject: [PATCH 13/22] ios version upgraded to 15 --- ios/Podfile | 6 +++--- ios/Runner.xcodeproj/project.pbxproj | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/ios/Podfile b/ios/Podfile index 5596a08a..72d600e0 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -platform :ios, '14.0' +platform :ios, '15.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' @@ -40,7 +40,7 @@ post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) target.build_configurations.each do |config| - config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '14.0' + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0' # Fix for Xcode 15+ DT_TOOLCHAIN_DIR issue if config.base_configuration_reference && File.exist?(config.base_configuration_reference.real_path) @@ -55,7 +55,7 @@ post_install do |installer| installer.generated_projects.each do |project| project.targets.each do |target| target.build_configurations.each do |config| - config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '14.0' + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0' config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ '$(inherited)', ## dart: PermissionGroup.microphone diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 3c09fbf5..e0fcb9d1 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -379,7 +379,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -403,7 +403,7 @@ ENABLE_USER_SCRIPT_SANDBOXING = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Atoms; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -534,7 +534,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -587,7 +587,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -613,7 +613,7 @@ ENABLE_USER_SCRIPT_SANDBOXING = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Atoms; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -709,7 +709,7 @@ ENABLE_USER_SCRIPT_SANDBOXING = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Atoms; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", From eb880893e246263a16d50b7de17f09a5bff8391b Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Wed, 9 Sep 2026 15:45:25 +0300 Subject: [PATCH 14/22] release config --- lib/controllers/api_routes/urls.dart | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index ead55e82..777758c6 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -1,7 +1,7 @@ class URLs { URLs._(); - static const String appReleaseBuildNumber = "53"; + static const String appReleaseBuildNumber = "54"; // static const host1 = "https://atomsm.hmg.com"; // production url // static const host1 = "https://atomsmdev.hmg.com"; // local DEV url diff --git a/pubspec.yaml b/pubspec.yaml index ccf2f535..4634a19a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.9.3+53 +version: 1.9.4+54 environment: sdk: ">=3.5.0 <4.0.0" From fbd5d8167d6f2a6af60867577624f1af704feba6 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Thu, 10 Sep 2026 10:37:22 +0300 Subject: [PATCH 15/22] v1.9.4+54 uploaded. --- lib/controllers/api_routes/urls.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index 777758c6..0e1858df 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -3,9 +3,9 @@ class URLs { static const String appReleaseBuildNumber = "54"; - // 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 From 6ad8471e9b3b396af3fa55dc7e0db6aaf3152ebd Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Tue, 29 Sep 2026 09:58:50 +0300 Subject: [PATCH 16/22] loading issue fixed in cm update spare part --- lib/modules/cm_module/cm_detail_provider.dart | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/lib/modules/cm_module/cm_detail_provider.dart b/lib/modules/cm_module/cm_detail_provider.dart index 9aa4aa4d..f328ad33 100644 --- a/lib/modules/cm_module/cm_detail_provider.dart +++ b/lib/modules/cm_module/cm_detail_provider.dart @@ -747,23 +747,16 @@ class CMDetailProvider extends ChangeNotifier { } } - Future updateActivitySparePart() async { - isLoading = true; + Future updateActivitySparePart() async { Response response; try { response = await ApiManager.instance.post(URLs.updateActivitySparePartUrl, body: sparePartHelperModel!.toJson()); - stateCode = response.statusCode; - isLoading = false; - notifyListeners(); if (stateCode == 200) { - return true; + return ""; } - return false; + return "Failed, Try Again"; } catch (error) { - isLoading = false; - stateCode = -1; - notifyListeners(); - return false; + return "Failed, Try Again"; } } From 072953a3a72d5af0955d467af76f1654262fbc36 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Sun, 11 Oct 2026 16:02:19 +0300 Subject: [PATCH 17/22] role name added --- lib/dashboard_latest/widgets/app_bar_widget.dart | 2 +- lib/models/user.dart | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/dashboard_latest/widgets/app_bar_widget.dart b/lib/dashboard_latest/widgets/app_bar_widget.dart index 39a97f7a..a08a9809 100644 --- a/lib/dashboard_latest/widgets/app_bar_widget.dart +++ b/lib/dashboard_latest/widgets/app_bar_widget.dart @@ -145,7 +145,7 @@ class _AppBarWidgetState extends State with TickerProviderStateMix style: AppTextStyles.heading6.copyWith(color: AppColor.headingTextColor(context), fontSize: context.isTablet() ? 14 : null), ), Text( - snapshot.user?.type?.name.toCamelCase ?? "", + snapshot.user?.roleName ?? "", style: AppTextStyles.bodyText.copyWith(color: AppColor.lightTextColor(context), fontSize: context.isTablet() ? 12 : null), ), ], diff --git a/lib/models/user.dart b/lib/models/user.dart index aa3389dc..608e0989 100644 --- a/lib/models/user.dart +++ b/lib/models/user.dart @@ -100,6 +100,8 @@ class User { return {"username": userName, "password": password, "fireBaseToken": FirebaseNotificationManger?.token ?? "", "notificationType": notificationType}; } + String get roleName => userRoles?.first.name ?? ""; + UsersTypes? get type { switch (userRoles?.first.value) { case "R-6": From e4a09fed1f694916d50ab5f5cbdc2db85bf8bcff Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Sun, 11 Oct 2026 18:08:15 +0300 Subject: [PATCH 18/22] improvement --- .../widgets/app_bar_widget.dart | 11 ++++++----- .../device_transfer/device_filter_screen.dart | 19 +++++++++++-------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/lib/dashboard_latest/widgets/app_bar_widget.dart b/lib/dashboard_latest/widgets/app_bar_widget.dart index a08a9809..8dcc73b2 100644 --- a/lib/dashboard_latest/widgets/app_bar_widget.dart +++ b/lib/dashboard_latest/widgets/app_bar_widget.dart @@ -142,18 +142,19 @@ class _AppBarWidgetState extends State with TickerProviderStateMix children: [ Text( snapshot.user?.username ?? "", + maxLines: 2, style: AppTextStyles.heading6.copyWith(color: AppColor.headingTextColor(context), fontSize: context.isTablet() ? 14 : null), ), - Text( - snapshot.user?.roleName ?? "", - style: AppTextStyles.bodyText.copyWith(color: AppColor.lightTextColor(context), fontSize: context.isTablet() ? 12 : null), - ), + // Text( + // snapshot.user?.roleName ?? "", + // style: AppTextStyles.bodyText.copyWith(color: AppColor.lightTextColor(context), fontSize: context.isTablet() ? 12 : null), + // ), ], ), ), ), ), - const Spacer(), + // const Spacer(), // Action icons with right-to-left fade animation SlideTransition( position: _rightSlideAnimation, diff --git a/lib/modules/tm_module/device_transfer/device_filter_screen.dart b/lib/modules/tm_module/device_transfer/device_filter_screen.dart index 04802a5e..9540c5d8 100644 --- a/lib/modules/tm_module/device_transfer/device_filter_screen.dart +++ b/lib/modules/tm_module/device_transfer/device_filter_screen.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.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/devices_provider.dart'; import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/int_extensions.dart'; @@ -117,14 +119,15 @@ class _DeviceFilterScreenState extends State { ListView( padding: const EdgeInsets.all(16), children: [ - AssetStatusButtons( - initialValue: _commissioningStatus, - onSelect: (value) { - if (value != null) { - _commissioningStatus = value; - } - }, - ), + // AssetStatusButtons( + // initialValue: _commissioningStatus, + // onSelect: (value) { + // if (value != null) { + // _commissioningStatus = value; + // } + // }, + // ), + context.translation.filter.heading5(context).custom(color: AppColor.textStyleColor(context)), 16.height, MultipleItemDropDownMenu( context: context, From 2ab2abd6938742dbe7a7d4736042ca138434c428 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Mon, 12 Oct 2026 11:06:29 +0300 Subject: [PATCH 19/22] site are changed as per organization --- lib/controllers/api_routes/urls.dart | 5 ++-- lib/main.dart | 1 + .../create_device_transfer_request.dart | 3 ++- .../device_transfer/device_filter_screen.dart | 3 ++- .../gas_request_providers/site_provider.dart | 25 +++++++++++++++++++ 5 files changed, 33 insertions(+), 4 deletions(-) diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index 0e1858df..68dfcd26 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -3,9 +3,9 @@ class URLs { static const String appReleaseBuildNumber = "54"; - 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 @@ -69,6 +69,7 @@ class URLs { static get getDepartmentsBySites => "$_baseUrl/Customer/GetDepartmentsBySites"; // post get departments by sites static getUserSites(String userId) => "$_baseUrl/Customer/GetSitesForUser?userId=$userId"; // get user's current sites static getSitesNotAccessibleForUser(String userId) => "$_baseUrl/Customer/GetSitesNotAccessibleForUser?userId=$userId"; // get user's current sites + static get getCustomersAutoCompleteSameOrg => "$_baseUrl/Customer/GetCustomersAutoCompleteSameOrg"; // get user's current sites // static get login => "$_baseUrl/MobileAuth/LoginIntegration"; // mobile login static get register => "$_baseUrl/handle/create/user"; // post diff --git a/lib/main.dart b/lib/main.dart index 90efc744..de8877f4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -357,6 +357,7 @@ class MyApp extends StatelessWidget { ChangeNotifierProvider(create: (_) => DepartmentProvider(), lazy: true), ChangeNotifierProvider(create: (_) => DepartmentBySitesProvider(), lazy: true), ChangeNotifierProvider(create: (_) => SiteProvider(), lazy: true), + ChangeNotifierProvider(create: (_) => SameOrgSiteProvider(), lazy: true), ChangeNotifierProvider(create: (_) => MappedSiteProvider(), lazy: true), ChangeNotifierProvider(create: (_) => MedicalDepartmentProvider(), lazy: true), ChangeNotifierProvider(create: (_) => FlowMedicalDepartmentProvider(), lazy: true), diff --git a/lib/modules/tm_module/device_transfer/create_device_transfer_request.dart b/lib/modules/tm_module/device_transfer/create_device_transfer_request.dart index 89c92bc1..f639fcb2 100644 --- a/lib/modules/tm_module/device_transfer/create_device_transfer_request.dart +++ b/lib/modules/tm_module/device_transfer/create_device_transfer_request.dart @@ -155,8 +155,9 @@ class _CreateDeviceTransferRequestState extends State( + SingleItemDropDownMenu( context: context, + requestById: context.userProvider.user?.clientId, title: context.translation.site, initialValue: _assetDestination.site, disableValue: isInternal ? null : _pickedAsset?.site, diff --git a/lib/modules/tm_module/device_transfer/device_filter_screen.dart b/lib/modules/tm_module/device_transfer/device_filter_screen.dart index 9540c5d8..19fb163a 100644 --- a/lib/modules/tm_module/device_transfer/device_filter_screen.dart +++ b/lib/modules/tm_module/device_transfer/device_filter_screen.dart @@ -148,11 +148,12 @@ class _DeviceFilterScreenState extends State { }, ), 12.height, - MultipleItemDropDownMenu( + MultipleItemDropDownMenu( context: context, title: context.translation.bySite, initialValue: _selectedSites, showAsBottomSheet: true, + requestById: context.userProvider.user?.clientId, // backgroundColor: AppColor.fieldBgColor(context), backgroundColor: context.isDark ? AppColor.neutral60 : Colors.white, showShadow: true, diff --git a/lib/providers/gas_request_providers/site_provider.dart b/lib/providers/gas_request_providers/site_provider.dart index d497a88d..c2ce34f4 100644 --- a/lib/providers/gas_request_providers/site_provider.dart +++ b/lib/providers/gas_request_providers/site_provider.dart @@ -33,6 +33,31 @@ class SiteProvider extends LoadingListNotifier { } } +class SameOrgSiteProvider extends LoadingListNotifier { + @override + Future getData({int? id}) async { + if (loading ?? false) return -2; + loading = true; + notifyListeners(); + try { + Response response = await ApiManager.instance.get(URLs.getCustomersAutoCompleteSameOrg + "?senderSiteId=$id"); + stateCode = response.statusCode; + if (response.statusCode >= 200 && response.statusCode < 300) { + List categoriesListJson = json.decode(response.body)["data"]; + items = categoriesListJson.map((item) => Site.fromJson(item)).toList(); + } + loading = false; + notifyListeners(); + return response.statusCode; + } catch (error) { + loading = false; + stateCode = -1; + notifyListeners(); + return -1; + } + } +} + class MappedSiteProvider extends LoadingListNotifier { @override Future getData({int? id}) async { From 494d9ba8d737cf5a3e977d2d4355db7b64c17b59 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Mon, 12 Oct 2026 15:36:45 +0300 Subject: [PATCH 20/22] get site by user id replace in asset filters --- .../providers/site_request_provider.dart | 20 +++++++++++- .../device_transfer/device_filter_screen.dart | 32 +++++++++++-------- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/lib/modules/site_request_module/providers/site_request_provider.dart b/lib/modules/site_request_module/providers/site_request_provider.dart index 0b4191ab..375c4d86 100644 --- a/lib/modules/site_request_module/providers/site_request_provider.dart +++ b/lib/modules/site_request_module/providers/site_request_provider.dart @@ -107,6 +107,24 @@ class SiteRequestProvider extends ChangeNotifier { ); } + /// Fetches user's current sites for Change/Remove Site operations + Future> getSitesByUserId(String userId) async { + List usersSite = []; + try { + Response response = await ApiManager.instance.post(URLs.getUserSites(userId), body: {},showToast: false); + if (response.statusCode >= 200 && response.statusCode < 300) { + List categoriesListJson = json.decode(response.body)["data"]; + usersSite = categoriesListJson + .map((item) => SiteRequestSiteModel( + id: item['siteId']?.toString() ?? '', + name: item['siteName'] ?? '', + )) + .toList(); + } + } catch (ex) {} + return usersSite; + } + /// Fetches user's current sites for Change/Remove Site operations Future fetchUserCurrentSites(String userId) async { if (_loading) return -2; @@ -125,6 +143,7 @@ class SiteRequestProvider extends ChangeNotifier { errorMessage: 'Failed to fetch user sites', ); } + Future fetchUserFilteredSites(String userId) async { if (_loading) return -2; return _performApiCall( @@ -141,7 +160,6 @@ class SiteRequestProvider extends ChangeNotifier { }, errorMessage: 'Failed to fetch user sites', ); - } /// Submits a site request to the server diff --git a/lib/modules/tm_module/device_transfer/device_filter_screen.dart b/lib/modules/tm_module/device_transfer/device_filter_screen.dart index 19fb163a..3458f9cb 100644 --- a/lib/modules/tm_module/device_transfer/device_filter_screen.dart +++ b/lib/modules/tm_module/device_transfer/device_filter_screen.dart @@ -1,23 +1,21 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.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/devices_provider.dart'; 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/lookup.dart'; -import 'package:test_sa/models/new_models/site.dart'; import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; +import 'package:test_sa/modules/site_request_module/models/dropdown_models.dart'; +import 'package:test_sa/modules/site_request_module/providers/site_request_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/multiple_item_drop_down_menu.dart'; import 'package:test_sa/providers/department_provider.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/views/widgets/date_and_time/date_picker.dart'; -import 'package:test_sa/views/widgets/equipment/asset_status_buttons.dart'; - import '../../../models/device/asset_search.dart'; import '../../../new_views/common_widgets/app_lazy_loading.dart'; import '../../../new_views/common_widgets/default_app_bar.dart'; @@ -35,11 +33,13 @@ class DeviceFilterScreen extends StatefulWidget { class _DeviceFilterScreenState extends State { late DeviceSearch filter; Lookup? _commissioningStatus; - List _selectedSites = []; + List _selectedSites = []; List _selectedDepartments = []; String? startDate, endDate; bool loading = false; + List userSites = []; + @override void initState() { filter = widget.data ?? DeviceSearch(); @@ -49,10 +49,14 @@ class _DeviceFilterScreenState extends State { startDate = filter.supplyDateFrom; endDate = filter.supplyDateTo; _commissioningStatus = filter.assetStatus; + final departmentProvider = Provider.of(context, listen: false); - final siteProvider = Provider.of(context, listen: false); if (departmentProvider.items.isEmpty ?? true) await departmentProvider.getData(); - if (siteProvider.items.isEmpty ?? true) await siteProvider.getData(); + if (userSites.isEmpty) { + Utils.showLoading(context); + userSites = await Provider.of(context, listen: false).getSitesByUserId(context.userProvider.user?.userID ?? ""); + Utils.hideLoading(context); + } // Initialize selected departments from filter if (filter.departments != null && filter.departments!.isNotEmpty) { @@ -72,11 +76,11 @@ class _DeviceFilterScreenState extends State { if (filter.sites != null && filter.sites!.isNotEmpty) { _selectedSites = []; for (var siteName in filter.sites!) { - final match = siteProvider.items.firstWhere( + final match = userSites.firstWhere( (site) => site.name == siteName, - orElse: () => Site(), + orElse: () => SiteRequestSiteModel(id: '', name: ''), ); - if (match.id != null) { + if (match.identifier != null) { _selectedSites.add(match); } } @@ -148,12 +152,12 @@ class _DeviceFilterScreenState extends State { }, ), 12.height, - MultipleItemDropDownMenu( + MultipleItemDropDownMenu( context: context, title: context.translation.bySite, initialValue: _selectedSites, showAsBottomSheet: true, - requestById: context.userProvider.user?.clientId, + staticData: userSites, // backgroundColor: AppColor.fieldBgColor(context), backgroundColor: context.isDark ? AppColor.neutral60 : Colors.white, showShadow: true, From 4218acb4b8fa0e066c9952f9c258a5dd52829982 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Mon, 12 Oct 2026 15:44:10 +0300 Subject: [PATCH 21/22] improvement --- .../tm_module/device_transfer/device_filter_screen.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/modules/tm_module/device_transfer/device_filter_screen.dart b/lib/modules/tm_module/device_transfer/device_filter_screen.dart index 3458f9cb..cdd65c2d 100644 --- a/lib/modules/tm_module/device_transfer/device_filter_screen.dart +++ b/lib/modules/tm_module/device_transfer/device_filter_screen.dart @@ -51,13 +51,13 @@ class _DeviceFilterScreenState extends State { _commissioningStatus = filter.assetStatus; final departmentProvider = Provider.of(context, listen: false); + Utils.showLoading(context); if (departmentProvider.items.isEmpty ?? true) await departmentProvider.getData(); if (userSites.isEmpty) { - Utils.showLoading(context); userSites = await Provider.of(context, listen: false).getSitesByUserId(context.userProvider.user?.userID ?? ""); - Utils.hideLoading(context); } - + await Future.delayed(const Duration(milliseconds: 100)); + Utils.hideLoading(context); // Initialize selected departments from filter if (filter.departments != null && filter.departments!.isNotEmpty) { _selectedDepartments = []; From a9000de678158628b8362e39dcad2ebc972931a3 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Mon, 12 Oct 2026 16:05:30 +0300 Subject: [PATCH 22/22] v1.9.5+55 uploaded on stores --- lib/controllers/api_routes/urls.dart | 6 +++--- pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index 68dfcd26..1a13c9c3 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -1,11 +1,11 @@ class URLs { URLs._(); - static const String appReleaseBuildNumber = "54"; + static const String appReleaseBuildNumber = "55"; - // 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 diff --git a/pubspec.yaml b/pubspec.yaml index 4634a19a..b2e61e4a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.9.4+54 +version: 1.9.5+55 environment: sdk: ">=3.5.0 <4.0.0"