-
This commit is contained in:
parent
ffdc93a4b7
commit
872eb2363a
@ -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,46 +63,70 @@ 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;
|
||||
|
||||
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);
|
||||
@ -108,32 +137,57 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
||||
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);
|
||||
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)
|
||||
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);
|
||||
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
|
||||
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);
|
||||
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");
|
||||
table.Cell().Background("#d8d1f5");
|
||||
table.Cell().Background("#d8d1f5");
|
||||
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);
|
||||
@ -158,6 +212,15 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
||||
{
|
||||
return time?.ToString(@"hh\:mm") ?? "-";
|
||||
}
|
||||
private string GetMonthYearString(List<OtRegisterModel> records)
|
||||
{
|
||||
if (records == null || !records.Any())
|
||||
return "No Data";
|
||||
|
||||
var firstDate = records.First().OtDate;
|
||||
return $"{firstDate:MMMM yyyy}";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@ -112,12 +125,16 @@
|
||||
<td>{{ formatTime(record.outsideFrom) }}</td>
|
||||
<td>{{ formatTime(record.outsideTo) }}</td>
|
||||
<td>{{ record.outsideBreak }} min</td>
|
||||
<td>{{ calcTotalHours(record).toFixed(2) }}</td>
|
||||
<td>{{ formatHourMinute(calcTotalTime(record)) }}</td>
|
||||
<td>{{ calcBreakTotal(record) }}</td>
|
||||
<td>{{ formatHourMinute(calcNetHours(record)) }}</td>
|
||||
<td v-if="isPSTWAIR">{{ record.stationName || 'N/A' }}</td>
|
||||
<td>{{ record.otDays}}</td>
|
||||
<td class="wrap-text">{{ record.otDescription }}</td>
|
||||
<td class="wrap-text">
|
||||
<div class="description-preview" v-on:click ="toggleDescription(index)" :class="{ expanded: expandedDescriptions[index] }">
|
||||
{{ record.otDescription }}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="record.pdfBase64">
|
||||
<button class="btn btn-light border rounded-circle" title="View PDF" v-on:click="viewPdf(record.pdfBase64)">
|
||||
@ -142,8 +159,8 @@
|
||||
<tr class="table-primary fw-bold">
|
||||
<td>TOTAL</td>
|
||||
<td colspan="6"></td>
|
||||
<td>{{ totalHours.toFixed(2) }}</td>
|
||||
<td>{{ totalBreak }}</td>
|
||||
<td>{{ formatHourMinute(totalHours) }}</td>
|
||||
<td>{{ formatHourMinute(totalBreak) }}</td>
|
||||
<td>{{ formatHourMinute(totalNetTime) }}</td>
|
||||
<td v-if="isPSTWAIR"></td>
|
||||
<td colspan="4"></td>
|
||||
@ -153,7 +170,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-3 d-flex flex-wrap gap-2">
|
||||
<button class="btn btn-primary btn-sm" v-on:click="printTable">
|
||||
<button class="btn btn-primary btn-sm" v-on:click="printPdf">
|
||||
<i class="bi bi-printer"></i> Print
|
||||
</button>
|
||||
<button class="btn btn-dark btn-sm" v-on:click="downloadPdf">
|
||||
@ -180,7 +197,8 @@
|
||||
selectedMonth: new Date().getMonth() + 1,
|
||||
selectedYear: currentYear,
|
||||
months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
|
||||
years: Array.from({ length: 10 }, (_, i) => currentYear - 5 + i)
|
||||
years: Array.from({ length: 10 }, (_, i) => currentYear - 5 + i),
|
||||
expandedDescriptions: {}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -190,13 +208,20 @@
|
||||
.sort((a, b) => new Date(a.otDate) - new Date(b.otDate));
|
||||
},
|
||||
totalHours() {
|
||||
return this.filteredRecords.reduce((sum, r) => sum + this.calcTotalHours(r), 0);
|
||||
const total = this.filteredRecords.reduce((sum, r) => sum + this.calcTotalHours(r), 0);
|
||||
const hours = Math.floor(total);
|
||||
const minutes = Math.round((total - hours) * 60);
|
||||
return { hours, minutes };
|
||||
},
|
||||
totalBreak() {
|
||||
return this.filteredRecords.reduce((sum, r) => sum + this.calcBreakTotal(r), 0);
|
||||
const totalMin = this.filteredRecords.reduce((sum, r) => sum + this.calcBreakTotal(r), 0);
|
||||
const hours = Math.floor(totalMin / 60);
|
||||
const minutes = totalMin % 60;
|
||||
return { hours, minutes };
|
||||
},
|
||||
totalNetTime() {
|
||||
const totalMinutes = (this.totalHours * 60) - this.totalBreak;
|
||||
const totalMinutes = (this.totalHours.hours * 60 + this.totalHours.minutes) -
|
||||
(this.totalBreak.hours * 60 + this.totalBreak.minutes);
|
||||
return {
|
||||
hours: Math.floor(totalMinutes / 60),
|
||||
minutes: Math.round(totalMinutes % 60)
|
||||
@ -231,6 +256,10 @@
|
||||
console.error("Records fetch error:", err);
|
||||
}
|
||||
},
|
||||
toggleDescription(index) {
|
||||
this.expandedDescriptions[index] = !this.expandedDescriptions[index];
|
||||
},
|
||||
|
||||
formatDate(d) {
|
||||
return new Date(d).toLocaleDateString();
|
||||
},
|
||||
@ -243,6 +272,13 @@
|
||||
const [th, tm] = to.split(":").map(Number);
|
||||
return ((th * 60 + tm) - (fh * 60 + fm)) / 60;
|
||||
},
|
||||
calcTotalTime(r) {
|
||||
const totalMinutes = this.calcTotalHours(r) * 60;
|
||||
return {
|
||||
hours: Math.floor(totalMinutes / 60),
|
||||
minutes: Math.round(totalMinutes % 60)
|
||||
};
|
||||
},
|
||||
calcTotalHours(r) {
|
||||
return this.getTimeDiff(r.officeFrom, r.officeTo) + this.getTimeDiff(r.outsideFrom, r.outsideTo);
|
||||
},
|
||||
@ -257,7 +293,7 @@
|
||||
};
|
||||
},
|
||||
formatHourMinute(timeObj) {
|
||||
return timeObj ? `${timeObj.hours} hr ${timeObj.minutes} min` : '-';
|
||||
return timeObj ? `${timeObj.hours} h ${timeObj.minutes} m` : '-';
|
||||
},
|
||||
editRecord(index) {
|
||||
const record = this.filteredRecords[index];
|
||||
@ -275,8 +311,26 @@
|
||||
alert("Error deleting record.");
|
||||
}
|
||||
},
|
||||
printTable() {
|
||||
window.print();
|
||||
printPdf() {
|
||||
const today = new Date();
|
||||
const month = today.getMonth() + 1;
|
||||
const year = today.getFullYear();
|
||||
|
||||
fetch(`/OvertimeAPI/GenerateOvertimePdf?month=${month}&year=${year}`)
|
||||
.then(response => response.blob())
|
||||
.then(blob => {
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
const printWindow = window.open(blobUrl, '_blank');
|
||||
|
||||
// Automatically trigger print after window loads
|
||||
printWindow.onload = () => {
|
||||
printWindow.focus();
|
||||
printWindow.print();
|
||||
};
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error generating PDF:", error);
|
||||
});
|
||||
},
|
||||
async downloadPdf() {
|
||||
try {
|
||||
|
||||
@ -492,9 +492,6 @@ namespace PSTW_CentralSystem.Controllers.API
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
[HttpPost("SaveOvertimeRecordsWithPdf")]
|
||||
public async Task<IActionResult> SaveOvertimeRecordsWithPdf([FromBody] List<OtRegisterModel> records)
|
||||
{
|
||||
@ -577,6 +574,7 @@ namespace PSTW_CentralSystem.Controllers.API
|
||||
return File(stream, "application/pdf", $"OvertimeRecords_{year}_{month}.pdf");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user