using QuestPDF.Fluent; using QuestPDF.Helpers; using QuestPDF.Infrastructure; using System.IO; using System.Collections.Generic; using PSTW_CentralSystem.Areas.OTcalculate.Models; using System; namespace PSTW_CentralSystem.Areas.OTcalculate.Services { public class OvertimePdfService { public MemoryStream GenerateOvertimeTablePdf( List records, int departmentId, string userFullName, string departmentName, byte[]? logoImage = null // Optional logo image ) { records = records .OrderBy(r => r.OtDate) .ToList(); var stream = new MemoryStream(); Document.Create(container => { container.Page(page => { page.Size(PageSizes.A4.Landscape()); page.Margin(30); // Header section with logo and user info page.Content().Column(column => { column.Item().Row(row => { row.RelativeItem(2).Column(col => { if (logoImage != null) { col.Item().Container().Height(36).Image(logoImage, ImageScaling.FitArea); col.Spacing(10); } col.Item().Text($"Name: {userFullName}").FontSize(9).SemiBold(); col.Item().Text($"Department: {departmentName}").FontSize(9).Italic(); col.Item().Text($"Overtime Record: { GetMonthYearString(records)}").FontSize(9).Italic(); }); row.RelativeItem(1).AlignRight().Text($"Generated: {DateTime.Now:dd MMM yyyy HH:mm}") .FontSize(9).FontColor(Colors.Grey.Medium); }); column.Item().PaddingVertical(10).LineHorizontal(0.5f).LineColor(Colors.Grey.Lighten2); // Table section column.Item().Table(table => { table.ColumnsDefinition(columns => { columns.RelativeColumn(1.1f); // Date columns.RelativeColumn(0.8f); // Office From columns.RelativeColumn(0.8f); // Office To columns.RelativeColumn(0.8f); // Office Break columns.RelativeColumn(0.9f); // Outside From columns.RelativeColumn(0.9f); // Outside To columns.RelativeColumn(0.9f); // Outside Break columns.RelativeColumn(); // Total OT columns.RelativeColumn(); // Break Hours columns.RelativeColumn(); // Net OT if (departmentId == 2) columns.RelativeColumn(); // Station columns.RelativeColumn(0.9f); // Day Type columns.RelativeColumn(2.7f); // Description }); // Header Row table.Header(header => { void AddHeaderCell(string text, string bgColor) { header.Cell().Background(bgColor).Border(0.25f).Padding(5).Text(text).FontSize(9).Bold().AlignCenter(); } AddHeaderCell("Date", "#d0ead2"); AddHeaderCell("From\n(Office)", "#dceefb"); AddHeaderCell("To\n(Office)", "#dceefb"); AddHeaderCell("Break\n(Office)", "#dceefb"); AddHeaderCell("From\n(Outside)", "#edf2f7"); AddHeaderCell("To\n(Outside)", "#edf2f7"); AddHeaderCell("Break\n(Outside)", "#edf2f7"); AddHeaderCell("Total OT\nHours", "#fdebd0"); AddHeaderCell("Break Hours\n(min)", "#fdebd0"); AddHeaderCell("Net OT", "#fdebd0"); if (departmentId == 2) AddHeaderCell("Station", "#d0f0ef"); AddHeaderCell("Days", "#e0f7da"); AddHeaderCell("Description", "#e3f2fd"); }); // Data Rows // Data Rows double totalOTSum = 0; int totalBreakSum = 0; TimeSpan totalNetOt = TimeSpan.Zero; bool alternate = false; if (!records.Any()) { // Show message row if no records uint colspan = (uint)(departmentId == 2 ? 13 : 12); table.Cell().ColumnSpan(colspan) .Border(0.5f) .Padding(10) .AlignCenter() .Text("No records found for selected month and year.") .FontSize(10) .FontColor(Colors.Grey.Darken2) .Italic(); } else { foreach (var r in records) { var totalOT = CalculateTotalOT(r); var totalBreak = (r.OfficeBreak ?? 0) + (r.OutsideBreak ?? 0); var netOT = totalOT - TimeSpan.FromMinutes(totalBreak); totalOTSum += totalOT.TotalHours; totalBreakSum += totalBreak; totalNetOt += netOT; string rowBg = alternate ? "#f9f9f9" : "#ffffff"; alternate = !alternate; void AddCell(string value, bool alignLeft = false) { var text = table.Cell().Background(rowBg).Border(0.25f).Padding(5).Text(value).FontSize(9); if (alignLeft) text.AlignLeft(); else text.AlignCenter(); } AddCell(r.OtDate.ToString("dd/MM/yyyy")); AddCell(FormatTime(r.OfficeFrom)); AddCell(FormatTime(r.OfficeTo)); AddCell($"{r.OfficeBreak ?? 0} min"); AddCell(FormatTime(r.OutsideFrom)); AddCell(FormatTime(r.OutsideTo)); AddCell($"{r.OutsideBreak ?? 0} min"); AddCell($"{(int)totalOT.TotalHours} hr {totalOT.Minutes} min"); AddCell($"{totalBreak}"); AddCell($"{netOT.Hours} hr {netOT.Minutes} min"); if (departmentId == 2) AddCell(r.Stations?.StationName ?? "N/A"); AddCell(r.OtDays); table.Cell().Background(rowBg).Border(0.25f).Padding(5).Text(r.OtDescription ?? "-").FontSize(9).WrapAnywhere().LineHeight(1.2f); } // Totals Row var totalOTTimeSpan = TimeSpan.FromHours(totalOTSum); var totalBreakTimeSpan = TimeSpan.FromMinutes(totalBreakSum); int totalCols = departmentId == 2 ? 13 : 12; int spanCols = departmentId == 2 ? 7 : 6; for (int i = 0; i < spanCols; i++) table.Cell().Background("#d8d1f5").Border(0.80f).Padding(5).Text(i == 0 ? "TOTAL" : "").Bold().FontSize(9).AlignCenter(); table.Cell().Background("#d8d1f5").Border(0.80f).Padding(5).Text($"{(int)totalOTTimeSpan.TotalHours} hr {totalOTTimeSpan.Minutes} min").Bold().FontSize(9).AlignCenter(); table.Cell().Background("#d8d1f5").Border(0.80f).Padding(5).Text($"{(int)totalBreakTimeSpan.TotalHours} hr {totalBreakTimeSpan.Minutes} min").Bold().FontSize(9).AlignCenter(); table.Cell().Background("#d8d1f5").Border(0.80f).Padding(5).Text($"{(int)totalNetOt.TotalHours} hr {totalNetOt.Minutes} min").Bold().FontSize(9).AlignCenter(); if (departmentId == 2) table.Cell().Background("#d8d1f5").Border(0.80f); else table.Cell().Background("#d8d1f5").Border(0.80f); table.Cell().Background("#d8d1f5").Border(0.80f); table.Cell().Background("#d8d1f5").Border(0.80f); } }); }); }); }).GeneratePdf(stream); stream.Position = 0; return stream; } private TimeSpan CalculateTotalOT(OtRegisterModel r) { TimeSpan office = (r.OfficeTo ?? TimeSpan.Zero) - (r.OfficeFrom ?? TimeSpan.Zero); TimeSpan outside = (r.OutsideTo ?? TimeSpan.Zero) - (r.OutsideFrom ?? TimeSpan.Zero); if (outside < TimeSpan.Zero) outside += TimeSpan.FromHours(24); return office + outside; } private string FormatTime(TimeSpan? time) { return time?.ToString(@"hh\:mm") ?? "-"; } private string GetMonthYearString(List records) { if (records == null || !records.Any()) return "No Data"; var firstDate = records.First().OtDate; return $"{firstDate:MMMM yyyy}"; } } }