import 'dart:convert'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show rootBundle; import 'package:flutter_quill/flutter_quill.dart' as quill; import 'package:pdf/widgets.dart' as pw; import 'package:pdf/pdf.dart' as pdf; import 'package:printing/printing.dart'; import '../../models/task.dart'; import '../../models/ticket.dart'; import '../../models/task_activity_log.dart'; import '../../models/task_assignment.dart'; import '../../models/profile.dart'; import '../../utils/app_time.dart'; Future buildTaskPdfBytes( Task task, Ticket? ticket, String officeName, String serviceName, List logs, List assignments, List profiles, 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); final descriptionText = ticket?.description ?? task.description; String plainFromAction(String? at) { if (at == null || at.trim().isEmpty) return ''; dynamic decoded = at; for (var i = 0; i < 3; i++) { if (decoded is String) { try { decoded = jsonDecode(decoded); continue; } catch (_) { break; } } break; } if (decoded is Map && decoded['ops'] is List) { final ops = decoded['ops'] as List; final buf = StringBuffer(); for (final op in ops) { if (op is Map) { final insert = op['insert']; if (insert is String) { buf.write(insert); } else if (insert is Map && insert.containsKey('image')) { buf.write('[image]'); } else { buf.write(insert?.toString() ?? ''); } } else { buf.write(op.toString()); } } return buf.toString().trim(); } if (decoded is List) { try { final doc = quill.Document.fromJson(decoded); return doc.toPlainText().trim(); } catch (_) { return decoded.join(); } } return decoded.toString(); } final actionTakenText = plainFromAction(task.actionTaken); final requestedBy = task.requestedBy ?? ''; final notedBy = task.notedBy ?? ''; final receivedBy = task.receivedBy ?? ''; final profileById = {for (final p in profiles) p.id: p}; final assignedForTask = assignments.where((a) => a.taskId == task.id).toList() ..sort((a, b) => a.createdAt.compareTo(b.createdAt)); final latestAssignment = assignedForTask.isEmpty ? null : assignedForTask.last; final performedBy = latestAssignment == null ? '' : (profileById[latestAssignment.userId]?.fullName ?? latestAssignment.userId); 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: $serviceName'), 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('Task Title: ${task.title}'), pw.SizedBox(height: 6), pw.Text('Description:'), pw.SizedBox(height: 6), pw.Container( padding: pw.EdgeInsets.all(6), child: pw.Text(descriptionText), ), pw.SizedBox(height: 12), // Requested/Noted signature lines pw.Row( children: [ pw.Expanded( child: pw.Column( children: [ pw.Container(height: 28), pw.Container(height: 1, color: pdf.PdfColors.black), pw.SizedBox(height: 6), pw.Text(requestedBy), pw.Text('Requested By'), ], ), ), pw.SizedBox(width: 12), pw.Expanded( child: pw.Column( children: [ pw.Container(height: 28), pw.Container(height: 1, color: pdf.PdfColors.black), pw.SizedBox(height: 6), pw.Text(notedBy), pw.Text('Noted by Supervisor/Senior'), ], ), ), ], ), pw.SizedBox(height: 12), pw.Text('Action Taken:'), pw.SizedBox(height: 6), pw.Container( padding: pw.EdgeInsets.all(6), child: pw.Text(actionTakenText), ), pw.SizedBox(height: 12), pw.Text('History updates:'), pw.SizedBox(height: 6), pw.Column( crossAxisAlignment: pw.CrossAxisAlignment.start, children: [ for (final log in logs) pw.Row( children: [ pw.Text(AppTime.formatDate(log.createdAt)), pw.SizedBox(width: 8), pw.Text(AppTime.formatTime(log.createdAt)), pw.SizedBox(width: 8), pw.Text(log.actionType), ], ), ], ), pw.SizedBox(height: 12), // Performed/Received signature lines pw.Row( children: [ pw.Expanded( child: pw.Column( children: [ pw.Container(height: 28), pw.Container(height: 1, color: pdf.PdfColors.black), pw.SizedBox(height: 6), pw.Text(performedBy), pw.Text('Performed By'), ], ), ), pw.SizedBox(width: 12), pw.Expanded( child: pw.Column( children: [ pw.Container(height: 28), pw.Container(height: 1, color: pdf.PdfColors.black), pw.SizedBox(height: 6), pw.Text(receivedBy), pw.Text('Received By'), ], ), ), ], ), ], ); }, ), ); return doc.save(); } Future showTaskPdfPreview( BuildContext context, Task task, Ticket? ticket, String officeName, String serviceName, List logs, List assignments, List profiles, ) 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, serviceName, logs, assignments, profiles, format, ), allowPrinting: true, allowSharing: true, ), ), actions: [ TextButton( onPressed: () => Navigator.of(ctx).pop(), child: const Text('Close'), ), ], ), ); }