Replace CSV export with PDF report generation
- Add QuestPDF library for professional PDF generation - Create PdfReportService with formatted table layout - Export includes score (color-coded), verified employers, gaps, and flags - Report has header, footer with page numbers, and alternating row colors Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
133
src/TrueCV.Web/Services/PdfReportService.cs
Normal file
133
src/TrueCV.Web/Services/PdfReportService.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace TrueCV.Web.Services;
|
||||
|
||||
public class PdfReportService
|
||||
{
|
||||
public byte[] GenerateReport(List<PdfReportData> data)
|
||||
{
|
||||
QuestPDF.Settings.License = LicenseType.Community;
|
||||
|
||||
var document = Document.Create(container =>
|
||||
{
|
||||
container.Page(page =>
|
||||
{
|
||||
page.Size(PageSizes.A4);
|
||||
page.Margin(40);
|
||||
page.DefaultTextStyle(x => x.FontSize(10));
|
||||
|
||||
page.Header().Element(ComposeHeader);
|
||||
page.Content().Element(c => ComposeContent(c, data));
|
||||
page.Footer().Element(ComposeFooter);
|
||||
});
|
||||
});
|
||||
|
||||
return document.GeneratePdf();
|
||||
}
|
||||
|
||||
private void ComposeHeader(IContainer container)
|
||||
{
|
||||
container.Column(column =>
|
||||
{
|
||||
column.Item().Row(row =>
|
||||
{
|
||||
row.RelativeItem().Column(col =>
|
||||
{
|
||||
col.Item().Text("TrueCV").Bold().FontSize(24).FontColor(Colors.Blue.Darken2);
|
||||
col.Item().Text("CV Verification Report Summary").FontSize(14).FontColor(Colors.Grey.Darken1);
|
||||
});
|
||||
row.ConstantItem(100).AlignRight().Text(DateTime.Now.ToString("dd MMM yyyy")).FontSize(10).FontColor(Colors.Grey.Medium);
|
||||
});
|
||||
column.Item().PaddingTop(10).LineHorizontal(1).LineColor(Colors.Grey.Lighten2);
|
||||
});
|
||||
}
|
||||
|
||||
private void ComposeContent(IContainer container, List<PdfReportData> data)
|
||||
{
|
||||
container.PaddingVertical(20).Column(column =>
|
||||
{
|
||||
column.Item().Text("Summary of " + data.Count + " Verified CVs").Bold().FontSize(12);
|
||||
column.Item().PaddingTop(15);
|
||||
|
||||
column.Item().Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.RelativeColumn(3);
|
||||
columns.RelativeColumn(2);
|
||||
columns.ConstantColumn(50);
|
||||
columns.RelativeColumn(2);
|
||||
columns.ConstantColumn(45);
|
||||
columns.ConstantColumn(45);
|
||||
columns.ConstantColumn(45);
|
||||
columns.ConstantColumn(45);
|
||||
});
|
||||
|
||||
table.Header(header =>
|
||||
{
|
||||
header.Cell().Background(Colors.Blue.Darken2).Padding(5).Text("Candidate").Bold().FontColor(Colors.White);
|
||||
header.Cell().Background(Colors.Blue.Darken2).Padding(5).Text("Date").Bold().FontColor(Colors.White);
|
||||
header.Cell().Background(Colors.Blue.Darken2).Padding(5).AlignCenter().Text("Score").Bold().FontColor(Colors.White);
|
||||
header.Cell().Background(Colors.Blue.Darken2).Padding(5).Text("Rating").Bold().FontColor(Colors.White);
|
||||
header.Cell().Background(Colors.Blue.Darken2).Padding(5).AlignCenter().Text("Verified").Bold().FontColor(Colors.White);
|
||||
header.Cell().Background(Colors.Blue.Darken2).Padding(5).AlignCenter().Text("Gaps").Bold().FontColor(Colors.White);
|
||||
header.Cell().Background(Colors.Blue.Darken2).Padding(5).AlignCenter().Text("Critical").Bold().FontColor(Colors.White);
|
||||
header.Cell().Background(Colors.Blue.Darken2).Padding(5).AlignCenter().Text("Warnings").Bold().FontColor(Colors.White);
|
||||
});
|
||||
|
||||
bool alternate = false;
|
||||
foreach (var item in data)
|
||||
{
|
||||
var bgColor = alternate ? Colors.Grey.Lighten4 : Colors.White;
|
||||
var scoreColor = item.Score > 70 ? Colors.Green.Darken1 : (item.Score >= 50 ? Colors.Orange.Darken1 : Colors.Red.Darken1);
|
||||
|
||||
table.Cell().Background(bgColor).Padding(5).Text(item.CandidateName);
|
||||
table.Cell().Background(bgColor).Padding(5).Text(item.UploadDate.ToString("dd MMM yy"));
|
||||
table.Cell().Background(bgColor).Padding(5).AlignCenter().Text(item.Score.ToString()).Bold().FontColor(scoreColor);
|
||||
table.Cell().Background(bgColor).Padding(5).Text(item.ScoreLabel);
|
||||
table.Cell().Background(bgColor).Padding(5).AlignCenter().Text(item.VerifiedEmployers + "/" + (item.VerifiedEmployers + item.UnverifiedEmployers));
|
||||
table.Cell().Background(bgColor).Padding(5).AlignCenter().Text(item.GapMonths + "mo");
|
||||
table.Cell().Background(bgColor).Padding(5).AlignCenter().Text(item.CriticalFlags.ToString()).FontColor(item.CriticalFlags > 0 ? Colors.Red.Darken1 : Colors.Grey.Medium);
|
||||
table.Cell().Background(bgColor).Padding(5).AlignCenter().Text(item.WarningFlags.ToString()).FontColor(item.WarningFlags > 0 ? Colors.Orange.Darken1 : Colors.Grey.Medium);
|
||||
|
||||
alternate = !alternate;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void ComposeFooter(IContainer container)
|
||||
{
|
||||
container.Column(column =>
|
||||
{
|
||||
column.Item().LineHorizontal(1).LineColor(Colors.Grey.Lighten2);
|
||||
column.Item().PaddingTop(10).Row(row =>
|
||||
{
|
||||
row.RelativeItem().Text("Generated by TrueCV - CV Verification Platform").FontSize(8).FontColor(Colors.Grey.Medium);
|
||||
row.RelativeItem().AlignRight().Text(x =>
|
||||
{
|
||||
x.Span("Page ").FontSize(8).FontColor(Colors.Grey.Medium);
|
||||
x.CurrentPageNumber().FontSize(8).FontColor(Colors.Grey.Medium);
|
||||
x.Span(" of ").FontSize(8).FontColor(Colors.Grey.Medium);
|
||||
x.TotalPages().FontSize(8).FontColor(Colors.Grey.Medium);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class PdfReportData
|
||||
{
|
||||
public string CandidateName { get; set; } = "";
|
||||
public DateTime UploadDate { get; set; }
|
||||
public int Score { get; set; }
|
||||
public string ScoreLabel { get; set; } = "";
|
||||
public int VerifiedEmployers { get; set; }
|
||||
public int UnverifiedEmployers { get; set; }
|
||||
public int GapMonths { get; set; }
|
||||
public int OverlapMonths { get; set; }
|
||||
public int CriticalFlags { get; set; }
|
||||
public int WarningFlags { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user