feat(report): accomplishment report PDF + shared PDF helpers
- lib/utils/pdf/pdf_letterhead.dart: PdfBrand with load(), letterhead(), theme(), and pageFooter() shared across CRMC PDF documents - lib/widgets/pdf_preview_dialog.dart: showPdfPreviewDialog() with pdfrx PdfViewer.data preview, Print (Printing.layoutPdf) and Download (Printing.sharePdf) buttons - lib/screens/programmer_tasks/report/accomplishment_report_pdf.dart: buildAccomplishmentReportPdf() — A4 MultiPage with letterhead, info box, accomplishment table, footnote, summary, signatures, page footer - test/accomplishment_report_pdf_test.dart: 2 passing tests Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
import 'package:flutter/services.dart' show rootBundle;
|
||||
import 'package:pdf/pdf.dart' as pdf;
|
||||
import 'package:pdf/widgets.dart' as pw;
|
||||
|
||||
/// Shared brand assets and letterhead widget for CRMC PDF documents.
|
||||
class PdfBrand {
|
||||
final pw.Font regular;
|
||||
final pw.Font bold;
|
||||
final pw.ImageProvider logo;
|
||||
|
||||
const PdfBrand({
|
||||
required this.regular,
|
||||
required this.bold,
|
||||
required this.logo,
|
||||
});
|
||||
|
||||
/// Load fonts and logo from the Flutter asset bundle.
|
||||
static Future<PdfBrand> load() async {
|
||||
final logoData = await rootBundle.load('assets/crmc_logo.png');
|
||||
final logo = pw.MemoryImage(logoData.buffer.asUint8List());
|
||||
|
||||
final regular = pw.Font.ttf(
|
||||
await rootBundle.load('assets/fonts/Roboto-Regular.ttf'),
|
||||
);
|
||||
final bold = pw.Font.ttf(
|
||||
await rootBundle.load('assets/fonts/Roboto-Bold.ttf'),
|
||||
);
|
||||
|
||||
return PdfBrand(regular: regular, bold: bold, logo: logo);
|
||||
}
|
||||
|
||||
/// Standard CRMC letterhead: 60×60 logo + four-line organisation text.
|
||||
pw.Widget letterhead() {
|
||||
return pw.Row(
|
||||
mainAxisAlignment: pw.MainAxisAlignment.center,
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.center,
|
||||
children: [
|
||||
pw.Image(logo, width: 60, height: 60),
|
||||
pw.SizedBox(width: 16),
|
||||
pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.center,
|
||||
children: [
|
||||
pw.Text(
|
||||
'Republic of the Philippines',
|
||||
style: pw.TextStyle(font: regular, fontSize: 9),
|
||||
),
|
||||
pw.Text(
|
||||
'Department of Health',
|
||||
style: pw.TextStyle(font: regular, fontSize: 9),
|
||||
),
|
||||
pw.Text(
|
||||
'Cotabato Regional and Medical Center',
|
||||
style: pw.TextStyle(font: regular, fontSize: 9),
|
||||
),
|
||||
pw.SizedBox(height: 3),
|
||||
pw.Text(
|
||||
'Integrated Hospital Operations and Management Program (IHOMP)',
|
||||
style: pw.TextStyle(font: bold, fontSize: 9),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// Convenience: create a ThemeData from these fonts.
|
||||
pw.ThemeData theme() => pw.ThemeData.withFont(base: regular, bold: bold);
|
||||
|
||||
/// Footer widget: right-aligned "Page x of y".
|
||||
pw.Widget pageFooter(pw.Context ctx) => pw.Align(
|
||||
alignment: pw.Alignment.centerRight,
|
||||
child: pw.Text(
|
||||
'Page ${ctx.pageNumber} of ${ctx.pagesCount}',
|
||||
style: pw.TextStyle(
|
||||
font: regular,
|
||||
fontSize: 8,
|
||||
color: pdf.PdfColors.grey600,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user