From d778654837340a2ee2b8addffd4ebe7bc797ef6d Mon Sep 17 00:00:00 2001 From: Marc Rejohn Castillano Date: Sat, 21 Feb 2026 22:52:36 +0800 Subject: [PATCH] Initial commit: Task Printout pdf --- lib/screens/tasks/task_detail_screen.dart | 220 +++++++++++++++++++++- pubspec.lock | 60 +++++- pubspec.yaml | 2 + web/index.html | 5 + 4 files changed, 275 insertions(+), 12 deletions(-) diff --git a/lib/screens/tasks/task_detail_screen.dart b/lib/screens/tasks/task_detail_screen.dart index adba60f9..d5ae8986 100644 --- a/lib/screens/tasks/task_detail_screen.dart +++ b/lib/screens/tasks/task_detail_screen.dart @@ -13,6 +13,11 @@ import 'dart:async'; import 'dart:convert'; import 'package:file_picker/file_picker.dart'; import 'package:flutter_quill/flutter_quill.dart' as quill; +import 'dart:typed_data'; +import 'package:flutter/services.dart' show rootBundle; +import 'package:pdf/widgets.dart' as pw; +import 'package:pdf/pdf.dart' as pdf; +import 'package:printing/printing.dart'; import '../../providers/supabase_provider.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart'; import '../../providers/profile_provider.dart'; @@ -334,17 +339,32 @@ class _TaskDetailScreenState extends ConsumerState ), ), const SizedBox(height: 12), - Wrap( - spacing: 12, - runSpacing: 8, - crossAxisAlignment: WrapCrossAlignment.center, + Row( children: [ - _buildStatusChip(context, task, canUpdateStatus), - _MetaBadge(label: 'Office', value: officeName), - _MetaBadge( - label: 'Task #', - value: task.taskNumber ?? task.id, - isMono: true, + Expanded( + child: Wrap( + spacing: 12, + runSpacing: 8, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + _buildStatusChip(context, task, canUpdateStatus), + _MetaBadge(label: 'Office', value: officeName), + _MetaBadge( + label: 'Task #', + value: task.taskNumber ?? task.id, + isMono: true, + ), + ], + ), + ), + IconButton( + tooltip: 'Preview/print task', + onPressed: () async { + try { + await _showPdfPreview(task, ticket, officeName); + } catch (_) {} + }, + icon: const Icon(Icons.print), ), ], ), @@ -2734,6 +2754,186 @@ class _TaskDetailScreenState extends ConsumerState assignment.taskId == taskId && assignment.userId == profile.id, ); } + + Future _buildTaskPdfBytes( + Task task, + Ticket? ticket, + String officeName, + pdf.PdfPageFormat format, + ) async { + final logoData = await rootBundle.load('crmc_logo.png'); + final logoImage = pw.MemoryImage(logoData.buffer.asUint8List()); + final doc = pw.Document(); + final created = AppTime.formatDate(task.createdAt); + + doc.addPage( + pw.Page( + pageFormat: format, + margin: pw.EdgeInsets.all(28), + build: (pw.Context ctx) { + return pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ + pw.Row( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ + pw.Container( + width: 64, + height: 64, + child: pw.Image(logoImage), + ), + pw.SizedBox(width: 12), + pw.Expanded( + child: pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.center, + children: [ + pw.Text( + 'Republic of the Philippines', + textAlign: pw.TextAlign.center, + ), + pw.Text( + 'Department of Health', + textAlign: pw.TextAlign.center, + ), + pw.Text( + 'Regional and Medical Center', + textAlign: pw.TextAlign.center, + ), + pw.SizedBox(height: 6), + pw.Text( + 'Cotabato Regional and Medical Center', + textAlign: pw.TextAlign.center, + style: pw.TextStyle(fontWeight: pw.FontWeight.bold), + ), + pw.Text( + 'Integrated Hospital Operations and Management Program', + textAlign: pw.TextAlign.center, + ), + pw.Text('(IHOMP)', textAlign: pw.TextAlign.center), + ], + ), + ), + ], + ), + pw.SizedBox(height: 12), + pw.Center( + child: pw.Text( + 'IT Job / Maintenance Request Form', + style: pw.TextStyle( + fontSize: 16, + fontWeight: pw.FontWeight.bold, + ), + ), + ), + pw.SizedBox(height: 12), + pw.Row( + children: [ + pw.Text('Task Number: ${task.taskNumber ?? task.id}'), + pw.Spacer(), + pw.Text('Filed At: $created'), + ], + ), + pw.SizedBox(height: 8), + pw.Row( + children: [ + pw.Text('Service: ${task.title}'), + pw.SizedBox(width: 12), + pw.Text('Office: $officeName'), + ], + ), + pw.SizedBox(height: 8), + pw.Row( + children: [ + pw.Text('Type: ${task.requestType ?? ''}'), + pw.SizedBox(width: 12), + pw.Text('Category: ${task.requestCategory ?? ''}'), + ], + ), + pw.SizedBox(height: 12), + pw.Text('Description:'), + pw.SizedBox(height: 6), + pw.Container( + height: 80, + decoration: pw.BoxDecoration( + border: pw.Border( + bottom: pw.BorderSide( + width: 0.5, + color: pdf.PdfColors.grey, + ), + ), + ), + ), + pw.SizedBox(height: 12), + pw.Row( + children: [ + pw.Text('Requested By: ${task.requestedBy ?? ''}'), + pw.Spacer(), + pw.Text('Noted by Supervisor/Senior'), + ], + ), + pw.SizedBox(height: 12), + pw.Text('Action Taken:'), + pw.SizedBox(height: 6), + pw.Container( + height: 80, + decoration: pw.BoxDecoration( + border: pw.Border( + bottom: pw.BorderSide( + width: 0.5, + color: pdf.PdfColors.grey, + ), + ), + ), + ), + pw.SizedBox(height: 12), + pw.Row( + children: [ + pw.Text('Performed By:'), + pw.Spacer(), + pw.Text('Received By: ___________________________'), + ], + ), + ], + ); + }, + ), + ); + + return doc.save(); + } + + Future _showPdfPreview( + Task task, + Ticket? ticket, + String officeName, + ) async { + await showDialog( + context: context, + builder: (ctx) => AlertDialog( + contentPadding: const EdgeInsets.all(8), + content: SizedBox( + width: 700, + height: 900, + child: PdfPreview( + build: (format) => _buildTaskPdfBytes( + task, + ticket, + officeName, + format as pdf.PdfPageFormat, + ), + allowPrinting: true, + allowSharing: true, + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(ctx).pop(), + child: const Text('Close'), + ), + ], + ), + ); + } } class _MetaBadge extends StatelessWidget { diff --git a/pubspec.lock b/pubspec.lock index 768c96aa..c2221156 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -121,6 +121,22 @@ packages: url: "https://pub.dev" source: hosted version: "4.2.1" + barcode: + dependency: transitive + description: + name: barcode + sha256: "7b6729c37e3b7f34233e2318d866e8c48ddb46c1f7ad01ff7bb2a8de1da2b9f4" + url: "https://pub.dev" + source: hosted + version: "2.2.9" + bidi: + dependency: transitive + description: + name: bidi + sha256: "77f475165e94b261745cf1032c751e2032b8ed92ccb2bf5716036db79320637d" + url: "https://pub.dev" + source: hosted + version: "2.0.13" boolean_selector: dependency: transitive description: @@ -625,10 +641,10 @@ packages: dependency: transitive description: name: image - sha256: "492bd52f6c4fbb6ee41f781ff27765ce5f627910e1e0cbecfa3d9add5562604c" + sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" url: "https://pub.dev" source: hosted - version: "4.7.2" + version: "4.5.4" intl: dependency: transitive description: @@ -789,6 +805,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" + url: "https://pub.dev" + source: hosted + version: "1.1.0" path_provider: dependency: transitive description: @@ -837,6 +861,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" + pdf: + dependency: "direct main" + description: + name: pdf + sha256: "28eacad99bffcce2e05bba24e50153890ad0255294f4dd78a17075a2ba5c8416" + url: "https://pub.dev" + source: hosted + version: "3.11.3" + pdf_widget_wrapper: + dependency: transitive + description: + name: pdf_widget_wrapper + sha256: c930860d987213a3d58c7ec3b7ecf8085c3897f773e8dc23da9cae60a5d6d0f5 + url: "https://pub.dev" + source: hosted + version: "1.0.4" petitparser: dependency: transitive description: @@ -893,6 +933,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.6.0" + printing: + dependency: "direct main" + description: + name: printing + sha256: "482cd5a5196008f984bb43ed0e47cbfdca7373490b62f3b27b3299275bf22a93" + url: "https://pub.dev" + source: hosted + version: "5.14.2" proj4dart: dependency: transitive description: @@ -909,6 +957,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + qr: + dependency: transitive + description: + name: qr + sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445" + url: "https://pub.dev" + source: hosted + version: "3.0.2" quill_native_bridge: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 57351827..90243744 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,6 +23,8 @@ dependencies: flutter_typeahead: ^4.1.0 flutter_quill: ^11.5.0 file_picker: ^10.3.10 + pdf: ^3.11.3 + printing: ^5.10.0 dev_dependencies: flutter_test: diff --git a/web/index.html b/web/index.html index d21efd81..0bbf9812 100644 --- a/web/index.html +++ b/web/index.html @@ -31,6 +31,11 @@ tasq + + + + +