From 729a03e1a92a3da5100a6b0d80f53f3400a4f361 Mon Sep 17 00:00:00 2001 From: WaseemAbbasi22 <50428976+WaseemAbbasi22@users.noreply.github.com> Date: Tue, 5 May 2026 14:26:23 +0300 Subject: [PATCH] Print request added for CM request --- lib/controllers/api_routes/api_manager.dart | 34 ++++ lib/controllers/api_routes/urls.dart | 4 + lib/modules/cm_module/cm_detail_page.dart | 149 ++++++++++++++++- .../provider/export_provider.dart | 151 ++++++++++++++++++ 4 files changed, 335 insertions(+), 3 deletions(-) diff --git a/lib/controllers/api_routes/api_manager.dart b/lib/controllers/api_routes/api_manager.dart index abd554d4..9e467199 100644 --- a/lib/controllers/api_routes/api_manager.dart +++ b/lib/controllers/api_routes/api_manager.dart @@ -197,6 +197,40 @@ class ApiManager { return streamedResponse; } + /// POST FormData request that returns StreamedResponse for progress tracking + /// This method is specifically for form-data requests (multipart/form-data) + Future postFormDataStreaming(String url, {Map? headers, required Map body}) async { + // Create headers for form data + Map formHeaders = { + 'X-Timezone-Offset': DateTime.now().timeZoneOffset.toString().split(".").first, + if (user != null) 'Authorization': 'Bearer ${user!.token}', + if (assetGroup != null) 'AssetGroup': assetGroup!.id.toString(), + }; + + headers ??= {}; + headers.addAll(formHeaders); + + Uri url0 = Uri.parse(url); + if (!kReleaseMode) { + print("URL:$url0"); + print("Headers:$headers"); + print("FormData Body:$body"); + } + + var request = http.MultipartRequest('POST', url0); + request.fields.addAll(body); + request.headers.addAll(headers); + + http.StreamedResponse streamedResponse = await _httpClient.send(request); + + // Handle 401 immediately + if (streamedResponse.statusCode == 401) { + showLoginDialog(); + } + + return streamedResponse; + } + Future delete(String url, {Map? headers, required Map body}) async { headers ??= {}; diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index a400d6f3..c6a249ed 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -556,5 +556,9 @@ class URLs { static get getAllWorkOrderColumnsMetadata => "$_baseUrl/CallRequest/columns/metadata"; static get exportAllWorkOrder => "$_baseUrl/CallRequest/Export"; + + // Print/Report URLs + static get generateReport => "$_baseUrl/ReportPDF/GenerateReport"; + //4104 attachment type lookup } diff --git a/lib/modules/cm_module/cm_detail_page.dart b/lib/modules/cm_module/cm_detail_page.dart index 452f3138..b42dca49 100644 --- a/lib/modules/cm_module/cm_detail_page.dart +++ b/lib/modules/cm_module/cm_detail_page.dart @@ -1,9 +1,11 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:provider/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/string_extensions.dart'; import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/helper/utils.dart'; @@ -12,16 +14,18 @@ import 'package:test_sa/models/enums/work_order_next_step.dart'; import 'package:test_sa/models/helper_data_models/workorder/work_order_helper_models.dart'; import 'package:test_sa/modules/cm_module/cm_detail_provider.dart'; import 'package:test_sa/modules/cm_module/views/components/bottom_sheets/service_request_bottomsheet.dart'; -import 'package:test_sa/modules/cx_module/chat/chat_page.dart'; import 'package:test_sa/modules/cx_module/chat/chat_provider.dart'; import 'package:test_sa/modules/cx_module/chat/chat_widget.dart'; -import 'package:test_sa/modules/cx_module/survey/survey_page.dart'; +import 'package:test_sa/modules/export_module/provider/export_provider.dart'; import 'package:test_sa/modules/loan_module/pages/create_loan_request_page.dart'; import 'package:test_sa/modules/loan_module/pages/loan_equipment_detail_page.dart'; import 'package:test_sa/modules/loan_module/provider/loan_provider.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; +import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; -import 'package:test_sa/views/widgets/loaders/no_data_found.dart'; +import 'package:open_file/open_file.dart'; +import 'package:share_plus/share_plus.dart'; +import 'dart:developer'; import 'views/components/history_log_view.dart'; import 'views/components/service_request_detail_view.dart'; @@ -170,6 +174,11 @@ class _CMDetailPageState extends State { return const SizedBox(); } }), + // Print Button + IconButton( + icon: const Icon(Icons.print), + onPressed: _handlePrint, + ), IconButton( icon: const Icon(Icons.home), onPressed: () { @@ -237,4 +246,138 @@ class _CMDetailPageState extends State { cProvider.getUserAutoLoginTokenSilent(moduleId, widget.requestId, title, myEmployeeId, receiver); } + + /// Show success dialog after file is downloaded - using same implementation as export module + Future _showSuccessDialog(String filePath) async { + if (!mounted) return; + + return showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext dialogContext) { + return Dialog( + backgroundColor: AppColor.background(context), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + child: Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset( + 'assets/images/done.svg', + width: 80, + height: 80, + ), + const SizedBox(height: 24), + Text( + 'Print Successful', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: AppColor.headingTextColor(context), + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 12), + Text( + 'Your PDF has been generated successfully', + style: TextStyle( + fontSize: 14, + color: AppColor.lightTextColor(context), + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 32), + Row( + children: [ + Expanded( + child: AppFilledButton( + onPressed: () async { + Navigator.of(dialogContext).pop(); + log('📂 Opening printed file...'); + final result = await OpenFile.open(filePath); + if (result.type != ResultType.done) { + log('⚠️ Could not open file: ${result.message}'); + 'No app found to open this file type. File saved to: $filePath'.showToast; + } + }, + label: 'Open', + buttonColor: AppColor.primary10, + showIcon: true, + icon: const Icon(Icons.folder_open, color: Colors.white), + height: 48, + ), + ), + const SizedBox(width: 12), + Expanded( + child: AppFilledButton( + onPressed: () async { + Navigator.of(dialogContext).pop(); + log('📤 Sharing printed file...'); + try { + final xFile = XFile(filePath); + await Share.shareXFiles( + [xFile], + text: 'Printed file', + ); + log('✅ File shared successfully'); + } catch (e) { + log('❌ Error sharing file: $e'); + 'Failed to share file: $e'.showToast; + } + }, + label: 'Share', + buttonColor: AppColor.green40, + textColor: AppColor.green60, + showIcon: true, + icon: Icon(Icons.share, color: AppColor.green60), + height: 48, + ), + ), + ], + ), + ], + ), + ), + ); + }, + ); + } + + /// Handle print button press + Future _handlePrint() async { + try { + final exportProvider = Provider.of(context, listen: false); + + final filePath = await exportProvider.printWorkOrder( + widget.requestId, + widget.moduleId, + context, + ); + + if (filePath != null) { + await _showSuccessDialog(filePath); + } else if (exportProvider.errorMessage != null) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(exportProvider.errorMessage!), + backgroundColor: AppColor.red60, + ), + ); + } + } + } catch (error) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Print failed: $error'), + backgroundColor: AppColor.red60, + ), + ); + } + } + } } diff --git a/lib/modules/export_module/provider/export_provider.dart b/lib/modules/export_module/provider/export_provider.dart index fde94ecf..b6f5a486 100644 --- a/lib/modules/export_module/provider/export_provider.dart +++ b/lib/modules/export_module/provider/export_provider.dart @@ -240,4 +240,155 @@ class ExportApiProvider extends ChangeNotifier { _errorMessage = null; notifyListeners(); } + + /// Print work order data as PDF + Future printWorkOrder(int workOrderId, int moduleId, BuildContext context) async { + _isExporting = true; + _errorMessage = null; + notifyListeners(); + + ProgressDialog pd = ProgressDialog(context: context); + pd.show( + max: 100, + msg: 'Preparing print...', + progressBgColor: Colors.transparent, + progressValueColor: AppColor.primary10, + msgFontWeight: FontWeight.normal, + valueColor: AppColor.primary10, + ); + + IOSink? sink; + File? outputFile; + + try { + if (!context.mounted) return null; + pd.update(msg: 'Sending print request...'); + log('📡 Sending print request...'); + + final startTime = DateTime.now(); + + // Prepare form data + final Map formData = { + 'properties': json.encode({ + 'workOrderId': workOrderId, + 'moduleId': moduleId, + }), + }; + + final streamedResponse = await ApiManager.instance.postFormDataStreaming( + URLs.generateReport, + body: formData + ); + + _stateCode = streamedResponse.statusCode; + + final responseTime = DateTime.now().difference(startTime).inMilliseconds; + log('✅ Print response received in ${responseTime}ms - Status: ${streamedResponse.statusCode}'); + + if (streamedResponse.statusCode >= 200 && streamedResponse.statusCode < 300) { + final contentLength = streamedResponse.contentLength ?? 0; + log('📊 Content length: ${contentLength > 0 ? "${(contentLength / 1024 / 1024).toStringAsFixed(2)} MB" : "Unknown"}'); + + if (!context.mounted) { + await streamedResponse.stream.drain(); + return null; + } + + final String dir = (await getApplicationDocumentsDirectory()).path; + final String filePath = "$dir/print_${DateTime.now().millisecondsSinceEpoch}.pdf"; + outputFile = File(filePath); + sink = outputFile.openWrite(); + + int received = 0; + int lastProgress = 15; + final downloadStartTime = DateTime.now(); + pd.update(value: 15, msg: 'Generating PDF...'); + + await for (var chunk in streamedResponse.stream) { + sink.add(chunk); + received += chunk.length; + + if (!context.mounted) { + await sink.close(); + await outputFile.delete(); + return null; + } + + if (contentLength > 0) { + final progress = (15 + ((received / contentLength) * 70)).clamp(15, 85).toInt(); + if (progress > lastProgress) { + lastProgress = progress; + final downloadedMB = (received / 1024 / 1024).toStringAsFixed(2); + final totalMB = (contentLength / 1024 / 1024).toStringAsFixed(2); + pd.update( + value: progress, + msg: 'Generating PDF: $downloadedMB MB / $totalMB MB', + ); + } + } else { + final downloadedMB = (received / 1024 / 1024).toStringAsFixed(2); + pd.update( + msg: 'Generating PDF: $downloadedMB MB', + ); + } + } + + await sink.close(); + sink = null; + + final downloadTime = DateTime.now().difference(downloadStartTime).inMilliseconds; + final fileSizeMB = (received / 1024 / 1024).toStringAsFixed(2); + log('✅ PDF generated successfully - Size: ${fileSizeMB} MB'); + + if (!context.mounted) { + await outputFile.delete(); + return null; + } + + pd.close(); + return filePath; + } else { + if (context.mounted) { + pd.close(); + } + try { + final responseBody = await streamedResponse.stream.bytesToString(); + final responseData = json.decode(responseBody); + _errorMessage = responseData['message'] ?? 'Print failed'; + } catch (e) { + log('❌ Error parsing print error response: $e'); + _errorMessage = 'Print failed with status: ${streamedResponse.statusCode}'; + } + return null; + } + } catch (error, stackTrace) { + if (sink != null) { + try { + await sink.close(); + } catch (e) { + log('⚠️ Error closing sink: $e'); + } + } + if (outputFile != null && await outputFile.exists()) { + try { + await outputFile.delete(); + log('🗑️ Deleted incomplete file'); + } catch (e) { + log('⚠️ Error deleting incomplete file: $e'); + } + } + + if (context.mounted) { + pd.close(); + } + log('❌ Error printing data: $error'); + _stateCode = -1; + _errorMessage = 'An error occurred during print: $error'; + return null; + } finally { + _isExporting = false; + notifyListeners(); + log('🔚 Print process finished'); + } + } }