From 872eb2363a44c5af262d2bc890c76e51af469ecf Mon Sep 17 00:00:00 2001 From: Naz <2022755409@student.uitm.edu.my> Date: Mon, 14 Apr 2025 12:19:20 +0800 Subject: [PATCH] - --- .../Services/OvertimePdfService.cs | 171 ++++++++++++------ .../Views/Overtime/OtRecords.cshtml | 78 ++++++-- Controllers/API/OvertimeAPI.cs | 6 +- 3 files changed, 185 insertions(+), 70 deletions(-) diff --git a/Areas/OTcalculate/Services/OvertimePdfService.cs b/Areas/OTcalculate/Services/OvertimePdfService.cs index d22a78d..75ad457 100644 --- a/Areas/OTcalculate/Services/OvertimePdfService.cs +++ b/Areas/OTcalculate/Services/OvertimePdfService.cs @@ -18,6 +18,10 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services byte[]? logoImage = null // Optional logo image ) { + records = records + .OrderBy(r => r.OtDate) + .ToList(); + var stream = new MemoryStream(); Document.Create(container => @@ -44,6 +48,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services 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}") @@ -58,82 +63,131 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services { table.ColumnsDefinition(columns => { - columns.RelativeColumn(); // Date - columns.RelativeColumn(1); // Office From - columns.RelativeColumn(1); // Office To - columns.RelativeColumn(); // Office Break - columns.RelativeColumn(1); // Outside From - columns.RelativeColumn(1); // Outside To - columns.RelativeColumn(); // Outside Break + 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(1); // Break Hours + columns.RelativeColumn(); // Break Hours columns.RelativeColumn(); // Net OT if (departmentId == 2) columns.RelativeColumn(); // Station - columns.RelativeColumn(1); // Day Type - columns.RelativeColumn(3); // Description + columns.RelativeColumn(0.9f); // Day Type + columns.RelativeColumn(2.7f); // Description }); // Header Row table.Header(header => { - header.Cell().Background("#d0ead2").Padding(5).Text("Date").FontSize(9).Bold().AlignCenter(); - header.Cell().Background("#dceefb").Padding(5).Text("From\n(Office)").FontSize(9).Bold().AlignCenter(); - header.Cell().Background("#dceefb").Padding(5).Text("To\n(Office)").FontSize(9).Bold().AlignCenter(); - header.Cell().Background("#dceefb").Padding(5).Text("Break\n(Office)").FontSize(9).Bold().AlignCenter(); - header.Cell().Background("#edf2f7").Padding(5).Text("From\n(Outside)").FontSize(9).Bold().AlignCenter(); - header.Cell().Background("#edf2f7").Padding(5).Text("To\n(Outside)").FontSize(9).Bold().AlignCenter(); - header.Cell().Background("#edf2f7").Padding(5).Text("Break\n(Outside)").FontSize(9).Bold().AlignCenter(); - header.Cell().Background("#fdebd0").Padding(5).Text("Total OT\nHours").FontSize(9).Bold().AlignCenter(); - header.Cell().Background("#fdebd0").Padding(5).Text("Break Hours\n(min)").FontSize(9).Bold().AlignCenter(); - header.Cell().Background("#fdebd0").Padding(5).Text("Net OT").FontSize(9).Bold().AlignCenter(); + 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) - header.Cell().Background("#d0f0ef").Padding(5).Text("Station").FontSize(9).Bold().AlignCenter(); - header.Cell().Background("#e0f7da").Padding(5).Text("Days").FontSize(9).Bold().AlignCenter(); - header.Cell().Background("#e3f2fd").Padding(5).Text("Description").FontSize(9).Bold().AlignCenter(); + 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; - foreach (var r in records) + if (!records.Any()) { - var totalOT = CalculateTotalOT(r); - var totalBreak = (r.OfficeBreak ?? 0) + (r.OutsideBreak ?? 0); - var netOT = totalOT - TimeSpan.FromMinutes(totalBreak); + // Show message row if no records + uint colspan = (uint)(departmentId == 2 ? 13 : 12); - totalOTSum += totalOT.TotalHours; - totalBreakSum += totalBreak; - totalNetOt += netOT; - - table.Cell().Padding(5).Text(r.OtDate.ToString("dd/MM/yyyy")).FontSize(9); - table.Cell().Padding(5).Text(FormatTime(r.OfficeFrom)).FontSize(9); - table.Cell().Padding(5).Text(FormatTime(r.OfficeTo)).FontSize(9); - table.Cell().Padding(5).Text($"{r.OfficeBreak ?? 0} min").FontSize(9); - table.Cell().Padding(5).Text(FormatTime(r.OutsideFrom)).FontSize(9); - table.Cell().Padding(5).Text(FormatTime(r.OutsideTo)).FontSize(9); - table.Cell().Padding(5).Text($"{r.OutsideBreak ?? 0} min").FontSize(9); - table.Cell().Padding(5).Text($"{totalOT.TotalHours:F2}").FontSize(9); - table.Cell().Padding(5).Text($"{totalBreak}").FontSize(9); - table.Cell().Padding(5).Text($"{netOT.Hours} hr {netOT.Minutes} min").FontSize(9); - if (departmentId == 2) - table.Cell().Padding(5).Text(r.Stations?.StationName ?? "N/A").FontSize(9); - table.Cell().Padding(5).Text(r.OtDays).FontSize(9); - table.Cell().Padding(5).Text(r.OtDescription ?? "-").FontSize(9).WrapAnywhere().LineHeight(1.2f); + 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); } - // Totals Row - table.Cell().ColumnSpan((uint)(departmentId == 2 ? 7 : 6)).Background("#d8d1f5").Padding(5).Text("TOTAL").Bold().FontSize(9); - table.Cell().Background("#d8d1f5").Padding(5).Text($"{totalOTSum:F2}").Bold().FontSize(9); - table.Cell().Background("#d8d1f5").Padding(5).Text($"{totalBreakSum}").Bold().FontSize(9); - table.Cell().Background("#d8d1f5").Padding(5).Text($"{(int)totalNetOt.TotalHours} hr {totalNetOt.Minutes} min").Bold().FontSize(9); - if (departmentId == 2) - table.Cell().Background("#d8d1f5"); - table.Cell().Background("#d8d1f5"); - table.Cell().Background("#d8d1f5"); }); + }); }); }).GeneratePdf(stream); @@ -158,6 +212,15 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services { 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}"; + } + } } diff --git a/Areas/OTcalculate/Views/Overtime/OtRecords.cshtml b/Areas/OTcalculate/Views/Overtime/OtRecords.cshtml index ca32b29..7d714b0 100644 --- a/Areas/OTcalculate/Views/Overtime/OtRecords.cshtml +++ b/Areas/OTcalculate/Views/Overtime/OtRecords.cshtml @@ -58,6 +58,19 @@ text-align: left; /* Optional: left-align description */ } + .description-preview { + max-height: 3.6em; /* approx. 2 lines */ + overflow: hidden; + text-overflow: ellipsis; + cursor: pointer; + white-space: pre-wrap; + word-wrap: break-word; + transition: max-height 0.3s ease; + } + + .description-preview.expanded { + max-height: none; + } @@ -112,12 +125,16 @@ {{ formatTime(record.outsideFrom) }} {{ formatTime(record.outsideTo) }} {{ record.outsideBreak }} min - {{ calcTotalHours(record).toFixed(2) }} + {{ formatHourMinute(calcTotalTime(record)) }} {{ calcBreakTotal(record) }} {{ formatHourMinute(calcNetHours(record)) }} {{ record.stationName || 'N/A' }} {{ record.otDays}} - {{ record.otDescription }} + +
+ {{ record.otDescription }} +
+