From 77f5d417570fd23485f0df9328046ebd0385fc32 Mon Sep 17 00:00:00 2001 From: WaseemAbbasi22 <50428976+WaseemAbbasi22@users.noreply.github.com> Date: Tue, 28 Apr 2026 16:48:07 +0300 Subject: [PATCH] export feature added for asset and my request --- lib/controllers/api_routes/urls.dart | 33 +- lib/main.dart | 10 +- lib/models/device/asset_search.dart | 18 +- lib/models/search_all_requests_model.dart | 69 +++ .../models/asset_column_metadata.dart | 35 ++ .../models/export_form_model.dart | 15 + .../models/export_module_type.dart | 18 + .../export_module/pages/export_form_view.dart | 176 ++++++++ .../provider/export_columns_provider.dart | 54 +++ .../provider/export_format_provider.dart | 35 ++ .../provider/export_provider.dart | 158 +++++++ .../widgets/cleaner_checklist_widget.dart | 412 ++++++++++++++++++ .../device_transfer/device_filter_screen.dart | 218 ++++----- .../my_request/my_requests_page.dart | 20 + .../equipment/single_device_picker.dart | 21 + pubspec.yaml | 1 + 16 files changed, 1186 insertions(+), 107 deletions(-) create mode 100644 lib/modules/export_module/models/asset_column_metadata.dart create mode 100644 lib/modules/export_module/models/export_form_model.dart create mode 100644 lib/modules/export_module/models/export_module_type.dart create mode 100644 lib/modules/export_module/pages/export_form_view.dart create mode 100644 lib/modules/export_module/provider/export_columns_provider.dart create mode 100644 lib/modules/export_module/provider/export_format_provider.dart create mode 100644 lib/modules/export_module/provider/export_provider.dart create mode 100644 lib/modules/export_module/widgets/cleaner_checklist_widget.dart diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index 2ba006ab..a400d6f3 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -4,11 +4,11 @@ class URLs { static const String appReleaseBuildNumber = "40"; // 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://atomsmdev.hmg.com"; // local DEV 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/v1/mobile"; // new V2 apis + static final String _baseUrl = "$_host/mobile"; // host local UAT + // static final String _baseUrl = "$_host/v1/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 @@ -65,6 +65,7 @@ class URLs { static get getAssets => "$_baseUrl/Asset/GetAssets"; // get static get getFaultDescription => "$_baseUrl/Asset/GetDefectForModelDefinition"; // get static get getAssetById => "$_baseUrl/Asset/GetAssetById?assetId="; // get + static get getAssetColumnsMetadata => "$_baseUrl/Asset/columns/metadata"; // get asset export columns metadata static get getModels => "$_baseUrl/ModelDefinition/GetModelDefinitionAsset"; // get ?client=2 static get getAllRequestsAndCount => "$_baseUrl/CallRequest/GetAllRequestsAndCount"; // get static get updateUserContactInformation => "$_baseUrl/Account/UpdateContactInfo"; // get @@ -264,11 +265,17 @@ class URLs { static get getYesNoRequestTypeLookup => "$_baseUrl/Lookups/GetLookup?lookupEnum=4"; static get getDemoPeriodLookup => "$_baseUrl/Lookups/GetLookup?lookupEnum=4101"; + static get incidentStatusLookup => "$_baseUrl/Lookups/GetLookup?lookupEnum=3060"; + static get incidentTypeLookup => "$_baseUrl/Lookups/GetLookup?lookupEnum=3061"; + static get incidentRootCauseLookup => "$_baseUrl/Lookups/GetLookup?lookupEnum=3062"; + static get incidentPersonInvolvedLookup => "$_baseUrl/Lookups/GetLookup?lookupEnum=3063"; + static get clinicalNonClinicalLookup => "$_baseUrl/Lookups/GetLookup?lookupEnum=3064"; + static get genderLookup => "$_baseUrl/Lookups/GetLookup?lookupEnum=3065"; static get getDemoDocumentTypes => "$_baseUrl/Lookups/GetLookup?lookupEnum=4106"; @@ -421,15 +428,21 @@ class URLs { static get getLoanPeriod => "$_baseUrl/Lookups/GetLookup?lookupEnum=3051"; static get getMedDepartmentBasedOnSite => "$_baseUrl/TRAFDataSource/GetDepartmentBasedOnSite"; + static get getFlowMedicalDepartments => "$_baseUrl/FlowDepartments/All"; static get getLoanById => '$_baseUrl/Loan/GetLoanById'; + static get getLoanByWorkOrderId => '$_baseUrl/Loan/GetLoanByWorkOrderId'; + static get getIncidentById => '$_baseUrl/Incident/GetIncidentById'; + static get getOvrTicketDetails => '$_baseUrl/Incident/GetOvrTicketDetails'; static get addLoan => '$_baseUrl/Loan/AddLoan'; + static get loanWorkflowAction => '$_baseUrl/Loan/LoanWorkflowAction'; + static get addIncident => '$_baseUrl/Incident/AddIncident'; static get addDemoTrialOutcome => '$_baseUrl/DemoRequest/addDemoTrialOutcome'; @@ -448,7 +461,9 @@ class URLs { static get getAssetDeliveryInspectionData => '$_baseUrl/AssetDeliveryExternalDeliveryInspection/GetAssetDeliveryExternalDeliveryInspectionByDeliveryId'; static get getOracleUserList => '$_baseUrl/AssetDeliveryExternal/GetEmployeesFromOracle'; + static get saveMasterInfoDeliveryInspection => '$_baseUrl/AssetDeliveryExternalDeliveryInspection/SaveMaseterInfoDeliveryInspection'; + static get sendEmailToAddSignature => '$_baseUrl/Signature/SendEmailToAddSignature'; static get getLineFromExternalPo => '$_baseUrl/AssetDeliveryExternalDeliveryInspection/GetLineFromExternalPo'; @@ -533,5 +548,13 @@ class URLs { static get updateDemoPeriodByRequester => "$_baseUrl/DemoRequest/UpdateDemoPeriodByRequester"; +//export urls + static get exportFormatTypeLookup => "$_baseUrl/Lookups/GetLookup?lookupEnum=4015"; + + static get exportAsset => "$_baseUrl/Asset/Export"; + + static get getAllWorkOrderColumnsMetadata => "$_baseUrl/CallRequest/columns/metadata"; + + static get exportAllWorkOrder => "$_baseUrl/CallRequest/Export"; //4104 attachment type lookup -} \ No newline at end of file +} diff --git a/lib/main.dart b/lib/main.dart index 40ba2eda..c240cefa 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -105,9 +105,12 @@ import 'package:test_sa/providers/work_order/service_type_provider.dart'; import 'package:test_sa/providers/work_order/supplier_engineer_provider.dart'; import 'package:test_sa/providers/work_order/vendor_provider.dart'; import 'package:test_sa/providers/work_order/wo_frame_provider.dart'; +import 'package:test_sa/modules/export_module/provider/export_columns_provider.dart'; +import 'package:test_sa/modules/export_module/provider/export_format_provider.dart'; +import 'package:test_sa/modules/export_module/provider/export_provider.dart'; +import 'package:test_sa/modules/export_module/pages/export_form_view.dart'; import 'package:test_sa/views/pages/user/notifications/notifications_page.dart'; import 'package:test_sa/views/pages/user/profile_page.dart'; -import 'package:test_sa/views/pages/user/requests/create_service_request_page.dart'; import 'package:test_sa/views/update_available_screen.dart'; import 'package:test_sa/views/widgets/departments/single_department_picker.dart'; import 'package:test_sa/views/widgets/equipment/asset_detail_page.dart'; @@ -353,6 +356,11 @@ class MyApp extends StatelessWidget { // CLEANER MODULE PROVIDERS (1) ChangeNotifierProvider(create: (_) => CleanerProvider(), lazy: true), + // EXPORT MODULE PROVIDERS (3) + ChangeNotifierProvider(create: (_) => AssetColumnsProvider(), lazy: true), + ChangeNotifierProvider(create: (_) => ExportFormatProvider(), lazy: true), + ChangeNotifierProvider(create: (_) => ExportApiProvider(), lazy: true), + // UTILITY PROVIDERS (1) ChangeNotifierProvider(create: (_) => OracleCodeProvider(), lazy: true), ], diff --git a/lib/models/device/asset_search.dart b/lib/models/device/asset_search.dart index 1a70196b..6d70fadb 100644 --- a/lib/models/device/asset_search.dart +++ b/lib/models/device/asset_search.dart @@ -81,10 +81,12 @@ class DeviceSearch { Lookup? assetNotScraped; // Now nullable String? assetNo; // Now nullable String? modelDefinition; // Now nullable - String? site; // Now nullable + List? sites; // Changed to List for multiple selection + String? site; // Keep for backward compatibility String? manufacturer; // Now nullable String? model; // Now nullable - String? department; // Now nullable + List? departments; // Changed to List for multiple selection + String? department; // Keep for backward compatibility String? supplier; // Now nullable String? tagCode; // Now nullable String? assetGroupName; // Now nullable @@ -181,10 +183,18 @@ class DeviceSearch { } if (assetNo != null && assetNo!.isNotEmpty) map['assetNo'] = assetNo; if (modelDefinition != null && modelDefinition!.isNotEmpty) map['modelDefinition'] = modelDefinition; - if (site != null && site!.isNotEmpty) map['site'] = site; + if (sites != null && sites!.isNotEmpty) { + map['site'] = sites; + } else if (site != null && site!.isNotEmpty) { + map['site'] = site; + } if (manufacturer != null && manufacturer!.isNotEmpty) map['manufacturer'] = manufacturer; if (model != null && model!.isNotEmpty) map['model'] = model; - if (department != null && department!.isNotEmpty) map['department'] = department; + if (departments != null && departments!.isNotEmpty) { + map['department'] = departments; + } else if (department != null && department!.isNotEmpty) { + map['department'] = department; + } if (supplier != null && supplier!.isNotEmpty) map['supplier'] = supplier; if (tagCode != null && tagCode!.isNotEmpty) map['tagCode'] = tagCode; if (assetGroupName != null && assetGroupName!.isNotEmpty) map['assetGroupName'] = assetGroupName; diff --git a/lib/models/search_all_requests_model.dart b/lib/models/search_all_requests_model.dart index 80ad8647..085bef0a 100644 --- a/lib/models/search_all_requests_model.dart +++ b/lib/models/search_all_requests_model.dart @@ -75,6 +75,75 @@ class SearchAllRequestsModel { isArchived = false; } + + Map toJson({ + List? typeTransaction, + List? statusTransaction, + List? priority, + List? displayData, + }) { + Map json = {}; + + // Add computed fields if provided + if (typeTransaction != null) { + json["typeTransaction"] = typeTransaction; + } else if (this.typeTransaction != null && this.typeTransaction!.isNotEmpty) { + json["typeTransaction"] = this.typeTransaction; + } + + if (statusTransaction != null) { + json["statusTransaction"] = statusTransaction; + } else if (statuses != null && statuses!.isNotEmpty) { + json["statusTransaction"] = statuses; + } + + if (priority != null) { + json["priority"] = priority; + } else { + json["priority"] = []; + } + + if (displayData != null) { + json["displayData"] = displayData; + } else { + json["displayData"] = []; + } + + // Add search fields + if (requestNumber?.value?.isNotEmpty ?? false) { + json["requestNumber"] = requestNumber!.value; + } + + if (assetName?.value?.isNotEmpty ?? false) { + json["assetName"] = assetName!.value; + } + + if (assetNo?.value?.isNotEmpty ?? false) { + json["assetNumber"] = assetNo!.value; + } + + if (sn?.value?.isNotEmpty ?? false) { + json["assetSerialNo"] = sn!.value; + } + + if (model?.value?.isNotEmpty ?? false) { + json["model"] = model!.value; + } + + if (manufacture?.value?.isNotEmpty ?? false) { + json["manufacturer"] = manufacture!.value; + } + + if (startDate != null) { + json["from"] = startDate!.toIso8601String(); + } + + if (endDate != null) { + json["to"] = endDate!.toIso8601String(); + } + + return json; + } } abstract class SearchAllRequestsBaseModel { diff --git a/lib/modules/export_module/models/asset_column_metadata.dart b/lib/modules/export_module/models/asset_column_metadata.dart new file mode 100644 index 00000000..e1d5c6ba --- /dev/null +++ b/lib/modules/export_module/models/asset_column_metadata.dart @@ -0,0 +1,35 @@ +import 'package:test_sa/models/base.dart'; + +class AssetColumnMetadata extends Base { + String? propertyName; + String? displayName; + + AssetColumnMetadata({ + this.propertyName, + this.displayName, + }) : super(identifier: propertyName, name: displayName); + + factory AssetColumnMetadata.fromJson(Map json) { + return AssetColumnMetadata( + propertyName: json['propertyName'], + displayName: json['displayName'], + ); + } + + Map toJson() { + return { + 'propertyName': propertyName, + 'displayName': displayName, + }; + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is AssetColumnMetadata && + runtimeType == other.runtimeType && + propertyName == other.propertyName; + + @override + int get hashCode => propertyName.hashCode; +} diff --git a/lib/modules/export_module/models/export_form_model.dart b/lib/modules/export_module/models/export_form_model.dart new file mode 100644 index 00000000..2885b46d --- /dev/null +++ b/lib/modules/export_module/models/export_form_model.dart @@ -0,0 +1,15 @@ +class ExportRequest { + final List selectedColumns; + final String format; + final T searchModel; + final int pageNo; + final int pageSize; + + ExportRequest({ + required this.selectedColumns, + required this.format, + required this.searchModel, + required this.pageNo, + required this.pageSize, + }); +} diff --git a/lib/modules/export_module/models/export_module_type.dart b/lib/modules/export_module/models/export_module_type.dart new file mode 100644 index 00000000..a86726d9 --- /dev/null +++ b/lib/modules/export_module/models/export_module_type.dart @@ -0,0 +1,18 @@ +/// Enum to identify different module types for export functionality +enum ExportModuleType { + asset(1, 'Asset'), + callRequest(2, 'AllWorkOrder'); + + final int id; + final String displayName; + + const ExportModuleType(this.id, this.displayName); + + /// Get module type from id + static ExportModuleType fromId(int id) { + return ExportModuleType.values.firstWhere( + (type) => type.id == id, + orElse: () => ExportModuleType.asset, + ); + } +} diff --git a/lib/modules/export_module/pages/export_form_view.dart b/lib/modules/export_module/pages/export_form_view.dart new file mode 100644 index 00000000..bf92d351 --- /dev/null +++ b/lib/modules/export_module/pages/export_form_view.dart @@ -0,0 +1,176 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.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/lookup.dart'; +import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; +import 'package:test_sa/modules/export_module/models/asset_column_metadata.dart'; +import 'package:test_sa/modules/export_module/models/export_form_model.dart'; +import 'package:test_sa/modules/export_module/models/export_module_type.dart'; +import 'package:test_sa/modules/export_module/provider/export_provider.dart'; +import 'package:test_sa/modules/export_module/provider/export_columns_provider.dart'; +import 'package:test_sa/modules/export_module/provider/export_format_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/app_lazy_loading.dart'; +import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; +import 'package:test_sa/new_views/common_widgets/multiple_item_drop_down_menu.dart'; +import 'package:test_sa/new_views/common_widgets/single_item_drop_down_menu.dart'; + +class ExportFormView extends StatefulWidget { + final T searchModel; + final ExportModuleType moduleType; + final int pageNo; + final int pageSize; + + const ExportFormView({ + Key? key, + required this.searchModel, + required this.moduleType, + required this.pageNo, + required this.pageSize, + }) : super(key: key); + + @override + State> createState() => _ExportFormViewState(); +} + +class _ExportFormViewState extends State> { + List selectedColumns = []; + Lookup? selectedFormat; + + void _updateSelectedColumns(List? value) { + if (value != null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + setState(() { + selectedColumns = value; + }); + } + }); + } + } + + void _updateSelectedFormat(Lookup? value) { + if (value != null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + setState(() { + selectedFormat = value; + }); + } + }); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: DefaultAppBar( + title: 'Export', + titleStyle: AppTextStyles.heading3.copyWith( + fontWeight: FontWeight.w500, + color: Theme.of(context).brightness == Brightness.dark ? AppColor.neutral30 : AppColor.neutral50, + ), + ), + body: Column( + children: [ + Expanded( + child: SingleChildScrollView( + child: Column( + children: [ + MultipleItemDropDownMenu( + context: context, + title: 'Columns', + showAsBottomSheet: true, + backgroundColor: AppColor.fieldBgColor(context), + showShadow: false, + height: 56.toScreenHeight, + showCancel: true, + requestById: widget.moduleType.id, + onSelect: _updateSelectedColumns, + ), + 18.height, + SingleItemDropDownMenu( + context: context, + title: 'Format', + backgroundColor: AppColor.fieldBgColor(context), + height: 56.toScreenHeight, + showShadow: false, + showAsBottomSheet: false, + showAsFullScreenDialog: true, + requestById: 1, + onSelect: _updateSelectedFormat, + ), + ], + ).toShadowContainer(context).paddingOnly(top: 20, start: 16, end: 16), + ), + ), + 18.height, + FooterActionButton.footerContainer( + context: context, + child: AppFilledButton( + label: 'Export', + buttonColor: AppColor.primary10, + onPressed: () async { + if (selectedColumns.isEmpty) { + 'Please select at least one column to export.'.showToast; + return; + } + if (selectedFormat == null) { + 'Please select an export format.'.showToast; + return; + } + final exportRequest = ExportRequest( + selectedColumns: selectedColumns.map((col) => col.propertyName ?? '').toList(), + format: selectedFormat?.name ?? '', + searchModel: widget.searchModel, + pageNo: widget.pageNo, + pageSize: widget.pageSize, + ); + final exportProvider = Provider.of(context, listen: false); + showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading()); + final success = await exportProvider.exportData(exportRequest, widget.moduleType); + Navigator.pop(context); + // this is for this This should be handled as per requirement in dialog with share and other options + if (success) { + 'Export completed successfully!'.showToast; + } else { + String errorMessage = exportProvider.errorMessage ?? 'Export failed. Please try again.'; + if (errorMessage.contains('No app installed')) { + String fileType = selectedFormat?.name ?? 'this file type'; + errorMessage = 'No app found to open $fileType files. Please install a compatible app'; + } + + errorMessage.showToast; + } + }), + ), + ], + ), + ); + } + //check if we need we can show this as well @waseem + + String _getAppSuggestion(String format) { + final formatLower = format.toLowerCase(); + switch (formatLower) { + case 'excel': + return 'Microsoft Excel, Google Sheets, or WPS Office'; + case 'pdf': + return 'Adobe Acrobat Reader or any PDF viewer'; + case 'word': + case 'doc': + return 'Microsoft Word, Google Docs, or WPS Office'; + case 'text': + case 'txt': + return 'any text editor'; + case 'csv': + return 'Microsoft Excel, Google Sheets, or any spreadsheet app'; + default: + return 'a compatible viewer app'; + } + } +} diff --git a/lib/modules/export_module/provider/export_columns_provider.dart b/lib/modules/export_module/provider/export_columns_provider.dart new file mode 100644 index 00000000..0621d4e3 --- /dev/null +++ b/lib/modules/export_module/provider/export_columns_provider.dart @@ -0,0 +1,54 @@ +import 'dart:convert'; +import 'dart:developer'; +import 'package:flutter/material.dart'; +import 'package:http/http.dart'; +import 'package:test_sa/controllers/api_routes/api_manager.dart'; +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/modules/export_module/models/asset_column_metadata.dart'; +import 'package:test_sa/modules/export_module/models/export_module_type.dart'; +import 'package:test_sa/providers/loading_list_notifier.dart'; + +class AssetColumnsProvider extends LoadingListNotifier { + @override + Future getData({int? id}) async { + loading = true; + notifyListeners(); + + try { + final moduleType = id != null ? ExportModuleType.fromId(id) : ExportModuleType.asset; + String apiUrl; + switch (moduleType) { + case ExportModuleType.asset: + apiUrl = URLs.getAssetColumnsMetadata; + break; + case ExportModuleType.callRequest: + apiUrl = URLs.getAllWorkOrderColumnsMetadata; + break; + } + + Response response = await ApiManager.instance.get(apiUrl); + + stateCode = response.statusCode; + + if (response.statusCode >= 200 && response.statusCode < 300) { + final responseData = json.decode(response.body); + final List data = responseData['data'] ?? []; + log('Raw data received: ${data.length} items for ${moduleType.displayName}'); + + items = data.map((item) => AssetColumnMetadata.fromJson(item)).toList(); + log('First item: ${items.isNotEmpty ? items.first.displayName : "empty"}'); + } else { + log('Failed with status code: ${response.statusCode}'); + items = []; + } + } catch (error) { + debugPrint('Error fetching columns: $error'); + stateCode = -1; + items = []; + } finally { + loading = false; + log('Loading finished. Items count: ${items.length}, Loading: $loading, StateCode: $stateCode'); + notifyListeners(); + } + } +} diff --git a/lib/modules/export_module/provider/export_format_provider.dart b/lib/modules/export_module/provider/export_format_provider.dart new file mode 100644 index 00000000..9a824060 --- /dev/null +++ b/lib/modules/export_module/provider/export_format_provider.dart @@ -0,0 +1,35 @@ +import 'package:test_sa/controllers/api_routes/api_manager.dart'; +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/models/lookup.dart'; +import 'package:test_sa/providers/loading_list_notifier.dart'; + +import 'dart:convert'; + +import 'package:http/http.dart'; +import 'package:test_sa/models/lookup.dart'; + +class ExportFormatProvider extends LoadingListNotifier { + @override + Future getData({int? id}) async { + if (loading ?? false) return -2; + loading = true; + notifyListeners(); + Response response; + try { + response = await ApiManager.instance.get(URLs.exportFormatTypeLookup); + } catch (error) { + loading = false; + stateCode = -1; + notifyListeners(); + return -1; + } + stateCode = response.statusCode; + if (response.statusCode >= 200 && response.statusCode < 300) { + List listJson = json.decode(response.body)["data"]; + items = listJson.map((department) => Lookup.fromJson(department)).toList(); + } + loading = false; + notifyListeners(); + return response.statusCode; + } +} diff --git a/lib/modules/export_module/provider/export_provider.dart b/lib/modules/export_module/provider/export_provider.dart new file mode 100644 index 00000000..88fb3c6a --- /dev/null +++ b/lib/modules/export_module/provider/export_provider.dart @@ -0,0 +1,158 @@ +import 'dart:convert'; +import 'dart:developer'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:http/http.dart'; +import 'package:open_file/open_file.dart'; +import 'package:path_provider/path_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/modules/export_module/models/export_form_model.dart'; +import 'package:test_sa/modules/export_module/models/export_module_type.dart'; + +class ExportApiProvider extends ChangeNotifier { + bool _isExporting = false; + + bool get isExporting => _isExporting; + + int? _stateCode; + + int? get stateCode => _stateCode; + + String? _errorMessage; + + String? get errorMessage => _errorMessage; + + Future exportData(ExportRequest exportRequest, ExportModuleType moduleType) async { + _isExporting = true; + _errorMessage = null; + notifyListeners(); + + try { + final String apiUrl = _getApiUrlForModule(moduleType); + final Map body = _buildRequestBody(exportRequest, moduleType); + + Response response = await ApiManager.instance.post(apiUrl, body: body); + _stateCode = response.statusCode; + + if (response.statusCode >= 200 && response.statusCode < 300) { + String ext = _getFileExtension(exportRequest.format); + String encodedStr = base64.encode(response.bodyBytes); + + String filePath = await _createFileFromString(encodedStr, ext); + await OpenFile.open(filePath); + + return true; + } else { + try { + final responseData = json.decode(response.body); + _errorMessage = responseData['message'] ?? 'Export failed'; + } catch (e) { + _errorMessage = 'Export failed with status: ${response.statusCode}'; + } + return false; + } + } catch (error, stackTrace) { + log('Error exporting data: $error'); + _stateCode = -1; + _errorMessage = 'An error occurred during export: $error'; + return false; + } finally { + _isExporting = false; + notifyListeners(); + } + } + + String _getApiUrlForModule(ExportModuleType moduleType) { + switch (moduleType) { + case ExportModuleType.asset: + return URLs.exportAsset; + case ExportModuleType.callRequest: + return URLs.exportAllWorkOrder; + default: + return URLs.exportAsset; + } + } + + String _getFileExtension(String format) { + final formatLower = format.toLowerCase(); + switch (formatLower) { + case 'excel': + return 'xlsx'; + case 'pdf': + return 'pdf'; + case 'word': + case 'doc': + return 'docx'; + case 'text': + case 'txt': + return 'txt'; + case 'csv': + return 'csv'; + default: + return formatLower; + } + } + + Map _buildRequestBody(ExportRequest exportRequest, ExportModuleType moduleType) { + final Map body = {}; + + if (exportRequest.searchModel != null) { + try { + final searchModelJson = (exportRequest.searchModel as dynamic).toJson() as Map; + body.addAll(searchModelJson); + } catch (e) { + body['searchModel'] = exportRequest.searchModel; + } + } + + // Add CallRequest-specific fields only for callRequest type + if (moduleType == ExportModuleType.callRequest) { + _addCallRequestFields(body); + } + + body['pageNo'] = exportRequest.pageNo; + body['pageSize'] = exportRequest.pageSize; + + body['exportRequest'] = { + 'selectedColumns': exportRequest.selectedColumns, + 'format': exportRequest.format, + }; + + return body; + } + + void _addCallRequestFields(Map body) { + //This is if no filter is selected we will send all the options to the backend to filter accordingly as per we are sending on allworkorder api + if (!body.containsKey('typeTransaction') || (body['typeTransaction'] as List?)?.isEmpty == true) { + body['typeTransaction'] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; + } + + if (!body.containsKey('statusTransaction') || (body['statusTransaction'] as List?)?.isEmpty == true) { + body['statusTransaction'] = [1, 2, 4]; + } + + if (!body.containsKey('priority')) { + body['priority'] = []; + } + + if (!body.containsKey('displayData')) { + body['displayData'] = []; + } + } + + Future _createFileFromString(String encodedStr, String ext) async { + Uint8List bytes = base64.decode(encodedStr); + String dir = (await getApplicationDocumentsDirectory()).path; + File file = File("$dir/${DateTime.now().millisecondsSinceEpoch}.$ext"); + await file.writeAsBytes(bytes); + return file.path; + } + + void resetError() { + _errorMessage = null; + notifyListeners(); + } +} diff --git a/lib/modules/export_module/widgets/cleaner_checklist_widget.dart b/lib/modules/export_module/widgets/cleaner_checklist_widget.dart new file mode 100644 index 00000000..9f6f117b --- /dev/null +++ b/lib/modules/export_module/widgets/cleaner_checklist_widget.dart @@ -0,0 +1,412 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:test_sa/controllers/api_routes/urls.dart'; +import 'package:test_sa/extensions/context_extension.dart'; +import 'package:test_sa/extensions/int_extensions.dart'; +import 'package:test_sa/extensions/string_extensions.dart'; +import 'package:test_sa/extensions/text_extensions.dart'; +import 'package:test_sa/extensions/widget_extensions.dart'; +import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/new_views/common_widgets/app_text_form_field.dart'; +import 'package:test_sa/views/widgets/buttons/app_back_button.dart'; + +class CleanerChecklistWidget extends StatefulWidget { + final List checklistItems; + final Function(List)? onChecklistChanged; + final String? capturedImageBase64; + final Function(String?)? onImageCaptured; + bool isClosed; + + CleanerChecklistWidget({Key? key, required this.checklistItems, this.onChecklistChanged, this.capturedImageBase64, this.onImageCaptured, this.isClosed = false}) : super(key: key); + + @override + State createState() => _CleanerChecklistWidgetState(); +} + +class _CleanerChecklistWidgetState extends State { + @override + Widget build(BuildContext context) { + // Only show if checklist items are not empty + if (widget.checklistItems.isEmpty) { + return const SizedBox(); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Checklist'.addTranslation, + style: AppTextStyles.heading4.copyWith( + color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, + ), + ), + 16.height, + ...widget.checklistItems.map((item) => _buildChecklistItem(context, item)).toList(), + // 16.height, + _buildImageCaptureWidget(context), + ], + ); + } + + Widget _buildChecklistItem(BuildContext context, ChecklistItem item) { + return IgnorePointer( + ignoring: widget.isClosed, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + // Checkbox with title + Row( + children: [ + Checkbox( + // visualDensity: const VisualDensity(horizontal: -4.0, vertical: -4.0), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + value: item.isChecked, + activeColor: AppColor.primary10, + onChanged: (bool? value) { + setState(() { + item.isChecked = value ?? false; + if (!item.isChecked) { + item.commentController.clear(); + } + }); + // Notify parent of changes + widget.onChecklistChanged?.call(widget.checklistItems); + }, + ), + 8.width, + Expanded( + child: RichText( + text: TextSpan( + children: [ + TextSpan( + text: item.title, + style: AppTextStyles.bodyText.copyWith( + color: context.isDark ? AppColor.neutral10 : AppColor.neutral120, + fontWeight: FontWeight.w500, + ), + ), + if (item.isMandatory) + TextSpan( + text: ' *', + style: AppTextStyles.bodyText.copyWith( + color: Colors.red, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ), + ], + ), + + // Comment box - only visible when checked + if (item.isChecked) ...[ + 16.height, + AppTextFormField( + controller: item.commentController, + labelText: context.translation.comment, + backgroundColor: AppColor.fieldBgColor(context), + showShadow: false, + labelStyle: AppTextStyles.textFieldLabelStyle.copyWith( + color: context.isDark ? AppColor.white10 : AppColor.black10, + ), + alignLabelWithHint: true, + textInputType: TextInputType.multiline, + onChange: (value) { + item.comment = value; + // Notify parent of changes + widget.onChecklistChanged?.call(widget.checklistItems); + }, + ), + ], + ], + ).toShadowContainer(context).paddingOnly(bottom: 16), + ); + } + + Widget _buildImageCaptureWidget(BuildContext context) { + // Base64 image display and capture widget + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Captured Image'.addTranslation, + style: AppTextStyles.heading6.copyWith( + color: context.isDark ? AppColor.neutral30 : AppColor.neutral50, + ), + ), + 8.height, + _buildImageWidget(context), + ], + ).toShadowContainer(context); + } + + /// Build the image widget with loading indicator for network images + Widget _buildImageWidget(BuildContext context) { + if (widget.capturedImageBase64 == null || widget.capturedImageBase64!.isEmpty) { + // Show camera icon when no image + return Container( + height: 200, + width: double.infinity, + decoration: BoxDecoration( + color: AppColor.fieldBgColor(context), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + Icons.camera_alt, + size: 48, + color: context.isDark ? AppColor.white10 : AppColor.black10, + ), + ).onPress(() async { + final pickedFile = await ImagePicker().pickImage(source: ImageSource.camera, imageQuality: 70, maxWidth: 800, maxHeight: 800); + if (pickedFile != null) { + // Convert image to Base64 with filename prefix (same format as CM request) + final bytes = await pickedFile.readAsBytes(); + final base64Image = base64Encode(bytes); + final fileName = pickedFile.path.split("/").last; + final formattedImage = "$fileName|$base64Image"; + + // Update parent with captured image + widget.onImageCaptured?.call(formattedImage); + } + }); + } + + Widget imageWidget; + + final imageString = widget.capturedImageBase64!; + + // First check if it's base64 encoded (contains | separator) + if (imageString.contains('|')) { + // Format: "filename|base64data" + try { + final base64Data = imageString.split('|').last; + imageWidget = Image.memory(base64Decode(base64Data), fit: BoxFit.cover); + return Stack( + alignment: AlignmentDirectional.topEnd, + children: [ + Container( + height: 200, + width: double.infinity, + decoration: BoxDecoration( + color: AppColor.fieldBgColor(context), + borderRadius: BorderRadius.circular(8), + ), + child: imageWidget) + .onPress(() { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => Scaffold( + body: SafeArea( + child: Stack( + children: [ + // InteractiveViewer(child: Image(image: getImageObject(itemIndex))).center, + InteractiveViewer(child: imageWidget).center, + const ABackButton(), + ], + ), + ), + ), + ), + ); + }), + _delete(), + ], + ); + } catch (e) { + debugPrint('Error decoding base64 image: $e'); + return _buildCameraIcon(context); + } + } + + // Check if it's a local file path + if (_isLocalUrl(imageString)) { + try { + imageWidget = Image.file(File(imageString), fit: BoxFit.cover); + return Stack( + alignment: AlignmentDirectional.topEnd, + children: [ + Container( + height: 200, + width: double.infinity, + decoration: BoxDecoration( + color: AppColor.fieldBgColor(context), + borderRadius: BorderRadius.circular(8), + ), + child: imageWidget) + .onPress(() { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => Scaffold( + body: SafeArea( + child: Stack( + children: [ + // InteractiveViewer(child: Image(image: getImageObject(itemIndex))).center, + InteractiveViewer(child: imageWidget).center, + const ABackButton(), + ], + ), + ), + ), + ), + ); + }), + _delete(), + ], + ); + } catch (e) { + debugPrint('Error loading local file: $e'); + return _buildCameraIcon(context); + } + } + + // If not local and not base64, it's a server filename - use NetworkImage with loading indicator + final imageUrl = URLs.getFileUrl(imageString); + if (imageUrl != null) { + imageWidget = Image.network( + imageUrl, + fit: BoxFit.cover, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) { + return child; + } + return Center( + child: CircularProgressIndicator( + color: AppColor.primary10, + value: loadingProgress.expectedTotalBytes != null ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! : null, + ), + ); + }, + errorBuilder: (context, error, stackTrace) { + debugPrint('Error loading network image: $error'); + return _buildCameraIcon(context); + }, + ); + return Stack( + alignment: AlignmentDirectional.topEnd, + children: [ + Container( + height: 200, + width: double.infinity, + decoration: BoxDecoration( + color: AppColor.fieldBgColor(context), + borderRadius: BorderRadius.circular(8), + ), + child: imageWidget) + .onPress(() { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => Scaffold( + body: SafeArea( + child: Stack( + children: [ + // InteractiveViewer(child: Image(image: getImageObject(itemIndex))).center, + InteractiveViewer(child: imageWidget).center, + const ABackButton(), + ], + ), + ), + ), + ), + ); + }), + _delete(), + ], + ); + } + + return _buildCameraIcon(context); + } + + /// Build camera icon widget + Widget _buildCameraIcon(BuildContext context) { + return Container( + height: 200, + width: double.infinity, + decoration: BoxDecoration( + color: AppColor.fieldBgColor(context), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + Icons.camera_alt, + size: 48, + color: context.isDark ? AppColor.white10 : AppColor.black10, + ), + ); + } + + /// Check if the URL is a local file path + /// Same logic as multi_image_picker_item.dart + bool _isLocalUrl(String url) { + if (url.isEmpty) return false; + return url.startsWith("/") || url.startsWith("file://") || (url.length > 1 && url.substring(1).startsWith(':\\')); + } + + @override + void dispose() { + // Note: Don't dispose controllers here as they're managed by ChecklistItem + super.dispose(); + } + + Widget _delete() { + if (widget.isClosed) return const SizedBox(); + return Container( + height: 45.toScreenHeight, + width: 45.toScreenHeight, + margin: const EdgeInsets.only(top: 4, right: 4), + decoration: BoxDecoration( + color: (context.isDark ? AppColor.neutral20 : AppColor.neutral40).withValues(alpha: 0.4), + border: Border.all( + color: (context.isDark ? AppColor.neutral20 : AppColor.neutral40).withValues(alpha: 0.7), + ), + borderRadius: BorderRadius.circular(30), + //boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 10)], + ), + child: const Icon(Icons.delete_rounded, color: Color(0xffF63939))) + .onPress(() { + widget.onImageCaptured?.call(null); + }); + } +} + +/// Model class for checklist items +class ChecklistItem { + final int id; + final String title; + bool isChecked; + String comment; + final bool isMandatory; + final TextEditingController commentController; + + ChecklistItem({ + required this.id, + required this.title, + required this.isChecked, + required this.comment, + this.isMandatory = false, + }) : commentController = TextEditingController(text: comment) { + // Update the controller text whenever comment changes + commentController.text = comment; + } + + // Convert to JSON for API submission + Map toJson() { + return { + 'id': id, + 'title': title, + 'isChecked': isChecked, + 'comment': commentController.text, + 'isMandatory': isMandatory, + }; + } + + void dispose() { + commentController.dispose(); + } +} 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 b0cf860f..24d7699b 100644 --- a/lib/modules/tm_module/device_transfer/device_filter_screen.dart +++ b/lib/modules/tm_module/device_transfer/device_filter_screen.dart @@ -9,7 +9,7 @@ import 'package:test_sa/models/lookup.dart'; import 'package:test_sa/models/new_models/site.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/single_item_drop_down_menu.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/views/widgets/date_and_time/date_picker.dart'; @@ -32,8 +32,8 @@ class DeviceFilterScreen extends StatefulWidget { class _DeviceFilterScreenState extends State { late DeviceSearch filter; Lookup? _commissioningStatus; - Site? _site; - Lookup? _department; + List _selectedSites = []; + List _selectedDepartments = []; String? startDate, endDate; bool loading = false; @@ -49,8 +49,17 @@ class _DeviceFilterScreenState extends State { final siteProvider = Provider.of(context, listen: false); if (departmentProvider.items.isEmpty ?? true) await departmentProvider.getData(); if (siteProvider.items.isEmpty ?? true) await siteProvider.getData(); - _department = departmentProvider.items.where((element) => element.name == filter.department).firstOrNull; - _site = siteProvider.items.where((element) => element.name == filter.site).firstOrNull; + + // Initialize selected departments from filter + if (filter.departments != null && filter.departments!.isNotEmpty) { + _selectedDepartments = departmentProvider.items.where((dept) => filter.departments!.contains(dept.name)).toList(); + } + + // Initialize selected sites from filter + if (filter.sites != null && filter.sites!.isNotEmpty) { + _selectedSites = siteProvider.items.where((site) => filter.sites!.contains(site.name)).toList(); + } + loading = false; setState(() {}); }); @@ -59,107 +68,122 @@ class _DeviceFilterScreenState extends State { @override Widget build(BuildContext context) { - return Scaffold( - appBar: DefaultAppBar( - title: context.translation.filter, - actions: [ - if (loading == false && _showResetButton()) - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - context.translation.reset, - style: AppTextStyles.bodyText2.copyWith(color: AppColor.primary50), - ).paddingAll(8).onPress(() { - setState(() { - startDate = null; - endDate = null; - _department = null; - _site = null; - }); - }), - ], - ) - ], - ), - body: Column( - children: [ - SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - CommissioningStatusButtons( - initialValue: _commissioningStatus, - onSelect: (value) { - if (value != null) { - _commissioningStatus = value; - } - }, - ), - 16.height, - SingleItemDropDownMenu( - context: context, - title: context.translation.byDepartment, - initialValue: _department, - loading: loading == false ? null : loading, - onSelect: (value) { + return SafeArea( + top: false, + child: Scaffold( + appBar: DefaultAppBar( + title: context.translation.filter, + actions: [ + if (loading == false && _showResetButton()) + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + context.translation.reset, + style: AppTextStyles.bodyText2.copyWith(color: AppColor.primary50), + ).paddingAll(8).onPress(() { setState(() { - _department = value; + startDate = null; + endDate = null; + _selectedDepartments = []; + _selectedSites = []; }); - }, - ).paddingOnly(start: 16, end: 16), - 8.height, - SingleItemDropDownMenu( - context: context, - title: context.translation.bySite, - initialValue: _site, - loading: loading == false ? null : loading, - onSelect: (value) { - setState(() { - _site = value; - }); - }, - ).paddingOnly(start: 16, end: 16), - 16.height, - context.translation.pmDateRange.heading5(context).paddingOnly(start: 16, end: 16), - 8.height, - Row( - children: [ - ADatePicker( - label: context.translation.to, - from: DateTime(2000), - date: DateTime.tryParse(startDate ?? ""), - onDatePicker: (date) { + }), + ], + ) + ], + ), + body: Column( + children: [ + SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + CommissioningStatusButtons( + initialValue: _commissioningStatus, + onSelect: (value) { + if (value != null) { + _commissioningStatus = value; + } + }, + ), + 16.height, + MultipleItemDropDownMenu( + context: context, + title: context.translation.byDepartment, + showAsBottomSheet: true, + backgroundColor: context.isDark ? AppColor.neutral60 : Colors.white, + // backgroundColor: AppColor.fieldBgColor(context), + showShadow: true, + height: 56.toScreenHeight, + showCancel: true, + onSelect: (value) { + if (value != null) { setState(() { - startDate = date.toIso8601String(); + _selectedDepartments = value; }); - }, - ).expanded, - 8.width, - ADatePicker( - label: context.translation.to, - from: DateTime(2000), - date: DateTime.tryParse(endDate ?? ""), - onDatePicker: (date) { + } + }, + ).paddingOnly(start: 16, end: 16), + 8.height, + MultipleItemDropDownMenu( + context: context, + title: context.translation.bySite, + showAsBottomSheet: true, + // backgroundColor: AppColor.fieldBgColor(context), + backgroundColor: context.isDark ? AppColor.neutral60 : Colors.white, + showShadow: true, + height: 56.toScreenHeight, + showCancel: true, + onSelect: (value) { + if (value != null) { setState(() { - endDate = date.toIso8601String(); + _selectedSites = value; }); - }, - ).expanded, - ], - ).paddingOnly(start: 16, end: 16), - ], - ), - ).expanded, - AppFilledButton(onPressed: _search, label: context.translation.applyFilter).paddingOnly(start: 16, end: 16), - ], - ).paddingOnly(top: 16, bottom: 16), + } + }, + ).paddingOnly(start: 16, end: 16), + 16.height, + context.translation.pmDateRange.heading5(context).paddingOnly(start: 16, end: 16), + 8.height, + Row( + children: [ + ADatePicker( + label: context.translation.to, + from: DateTime(2000), + date: DateTime.tryParse(startDate ?? ""), + onDatePicker: (date) { + setState(() { + startDate = date.toIso8601String(); + }); + }, + ).expanded, + 8.width, + ADatePicker( + label: context.translation.to, + from: DateTime(2000), + date: DateTime.tryParse(endDate ?? ""), + onDatePicker: (date) { + setState(() { + endDate = date.toIso8601String(); + }); + }, + ).expanded, + ], + ).paddingOnly(start: 16, end: 16), + ], + ), + ).expanded, + AppFilledButton(onPressed: _search, label: context.translation.applyFilter).paddingOnly(start: 16, end: 16), + ], + ).paddingOnly(top: 16, bottom: 16), + ), ); } void _search() async { - filter.site = _site?.name; - filter.department = _department?.name; + filter.sites = _selectedSites.map((site) => site.name ?? '').where((name) => name.isNotEmpty).toList(); + filter.departments = _selectedDepartments.map((dept) => dept.name ?? '').where((name) => name.isNotEmpty).toList(); filter.supplyDateFrom = startDate; filter.supplyDateTo = endDate; final deviceProvider = Provider.of(context, listen: false); @@ -171,6 +195,6 @@ class _DeviceFilterScreenState extends State { } bool _showResetButton() { - return (_department != null) || (_site != null) || (startDate != null) || (endDate != null); + return (_selectedDepartments.isNotEmpty) || (_selectedSites.isNotEmpty) || (startDate != null) || (endDate != null); } } diff --git a/lib/new_views/pages/land_page/my_request/my_requests_page.dart b/lib/new_views/pages/land_page/my_request/my_requests_page.dart index ba49458e..26dcd7bc 100644 --- a/lib/new_views/pages/land_page/my_request/my_requests_page.dart +++ b/lib/new_views/pages/land_page/my_request/my_requests_page.dart @@ -15,6 +15,8 @@ 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/search_all_requests_model.dart'; +import 'package:test_sa/modules/export_module/models/export_module_type.dart'; +import 'package:test_sa/modules/export_module/pages/export_form_view.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/new_views/pages/land_page/my_request/all_requests_filter_page.dart'; import 'package:test_sa/new_views/pages/land_page/my_request/all_requests_search_page.dart'; @@ -147,6 +149,24 @@ class _MyRequestsPageState extends State { } }).expanded, 16.width, + Text( + 'Export', + style: AppTextStyles.bodyText2.copyWith(color: AppColor.blueStatus(context)), + ).onPress(() async { + final provider = Provider.of(context, listen: false); + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ExportFormView( + searchModel: _search, + moduleType: ExportModuleType.callRequest, + pageNo: provider.pageItemNumber, + pageSize: (provider.allRequestsAndCount?.requestsDetails?.length ?? 0) ~/ (provider.pageItemNumber > 0 ? provider.pageItemNumber : 1) + 1, + ), + ), + ); + }), + 16.width, Text( context.translation.filter, style: AppTextStyles.bodyText2.copyWith(color: AppColor.blueStatus(context)), diff --git a/lib/views/widgets/equipment/single_device_picker.dart b/lib/views/widgets/equipment/single_device_picker.dart index bd709317..77bbad17 100644 --- a/lib/views/widgets/equipment/single_device_picker.dart +++ b/lib/views/widgets/equipment/single_device_picker.dart @@ -9,6 +9,8 @@ import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/modules/tm_module/device_transfer/device_filter_screen.dart'; import 'package:test_sa/modules/tm_module/device_transfer/device_search_screen.dart'; +import 'package:test_sa/modules/export_module/pages/export_form_view.dart'; +import 'package:test_sa/modules/export_module/models/export_module_type.dart'; import 'package:test_sa/views/widgets/equipment/asset_detail_page.dart'; import 'package:test_sa/views/widgets/equipment/asset_item_gridview.dart'; import 'package:test_sa/views/widgets/equipment/asset_item_listview.dart'; @@ -20,6 +22,7 @@ import 'package:test_sa/views/widgets/qr/scan_qr.dart'; import '../../../models/device/asset.dart'; import '../../../models/device/asset_search.dart'; import '../../../new_views/app_style/app_color.dart'; +import '../../../new_views/app_style/app_text_style.dart'; class MyAssetsPage extends StatefulWidget { static const String id = "/single-device-Picker"; @@ -136,6 +139,24 @@ class _MyAssetsPageState extends State { _searchableList.clear(); _searchableList.addAll(_devicesProvider.devices); }), + 16.width, + Text( + 'Export', + style: AppTextStyles.bodyText2.copyWith(color: AppColor.blueStatus(context)), + ).onPress(() async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ExportFormView( + searchModel: _searchAsset, + moduleType: ExportModuleType.asset, + pageNo: _devicesProvider.pageItemNumber, + pageSize:_devicesProvider.devices.length ~/ _devicesProvider.pageItemNumber + 1 , + ), + ), + ); + }), + 16.width, ], ).paddingOnly(start: 16, end: 16), ), diff --git a/pubspec.yaml b/pubspec.yaml index bf8e3371..35df467d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -109,6 +109,7 @@ dependencies: local_auth_darwin: ^1.4.0 haptic_feedback: ^0.6.4+3 + dev_dependencies: flutter_test: sdk: flutter