diff --git a/Areas/OTcalculate/Controllers/HrDashboardController.cs b/Areas/OTcalculate/Controllers/HrDashboardController.cs index ddcc809..fccffca 100644 --- a/Areas/OTcalculate/Controllers/HrDashboardController.cs +++ b/Areas/OTcalculate/Controllers/HrDashboardController.cs @@ -43,5 +43,9 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Controllers return View(); } + public IActionResult HrUserSetting() + { + return View(); + } } } diff --git a/Areas/OTcalculate/Models/FlexiHourModel.cs b/Areas/OTcalculate/Models/FlexiHourModel.cs new file mode 100644 index 0000000..e5ca354 --- /dev/null +++ b/Areas/OTcalculate/Models/FlexiHourModel.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; + +namespace PSTW_CentralSystem.Areas.OTcalculate.Models +{ + public class FlexiHourModel + { + [Key] + public int FlexiHourId { get; set; } + + public string FlexiHour { get; set; } + + } + +} diff --git a/Areas/OTcalculate/Models/HrUserSettingModel.cs b/Areas/OTcalculate/Models/HrUserSettingModel.cs new file mode 100644 index 0000000..0b55805 --- /dev/null +++ b/Areas/OTcalculate/Models/HrUserSettingModel.cs @@ -0,0 +1,30 @@ +using PSTW_CentralSystem.Models; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace PSTW_CentralSystem.Areas.OTcalculate.Models +{ + public class HrUserSettingModel + { + [Key] + public int HrUserSettingId { get; set; } + + public int UserId { get; set; } + + [ForeignKey("UserId")] + public UserModel? User { get; set; } + + public int? FlexiHourId { get; set; } + + [ForeignKey("FlexiHourId")] + public FlexiHourModel? FlexiHour { get; set; } + public DateTime? FlexiHourUpdate { get; set; } + + public DateTime? StateUpdate { get; set; } + + public int? StateId { get; set; } + + [ForeignKey("StateId")] + public StateModel? State { get; set; } + } +} diff --git a/Areas/OTcalculate/Models/OvertimeSubmissionModel.cs b/Areas/OTcalculate/Models/OvertimeSubmissionModel.cs new file mode 100644 index 0000000..0ebd083 --- /dev/null +++ b/Areas/OTcalculate/Models/OvertimeSubmissionModel.cs @@ -0,0 +1,9 @@ +namespace PSTW_CentralSystem.Areas.OTcalculate.Models +{ + public class OvertimeSubmissionModel + { + public int Month { get; set; } + public int Year { get; set; } + public IFormFile File { get; set; } + } +} diff --git a/Areas/OTcalculate/Services/OvertimeExcelService.cs b/Areas/OTcalculate/Services/OvertimeExcelService.cs new file mode 100644 index 0000000..f784cdc --- /dev/null +++ b/Areas/OTcalculate/Services/OvertimeExcelService.cs @@ -0,0 +1,168 @@ +using ClosedXML.Excel; +using System.IO; +using PSTW_CentralSystem.Models; +using PSTW_CentralSystem.Areas.OTcalculate.Models; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace PSTW_CentralSystem.Areas.OTcalculate.Services +{ + public class OvertimeExcelService + { + public MemoryStream GenerateOvertimeExcel( + List records, + int departmentId, + string userFullName, + string departmentName, + int userStateId, + int weekendId, + List publicHolidays, + bool isAdminUser = false, + byte[]? logoImage = null // This parameter is missing in the call + ) + { + var workbook = new XLWorkbook(); + var worksheet = workbook.Worksheets.Add("Overtime Records"); + int currentRow = 1; + + // Add Header Information + if (!string.IsNullOrEmpty(userFullName)) + { + worksheet.Cell(currentRow, 1).Value = $"Name: {userFullName}"; + currentRow++; + } + if (!string.IsNullOrEmpty(departmentName)) + { + worksheet.Cell(currentRow, 1).Value = $"Department: {departmentName}"; + currentRow++; + } + currentRow++; // Add an empty row after header + + // Header titles + var headers = new List + { + "Day", "Date", "Office From", "Office To", "Office Break", + "After From", "After To", "After Break", + "Total OT", "Break Minutes", "Net OT" + }; + + if (departmentId == 2 || isAdminUser) + headers.Add("Station"); + + headers.Add("Description"); + + // Set header row + int headerRow = currentRow; + for (int i = 0; i < headers.Count; i++) + { + var cell = worksheet.Cell(headerRow, i + 1); + cell.Value = headers[i]; + cell.Style.Font.Bold = true; + cell.Style.Fill.BackgroundColor = XLColor.LightGray; + cell.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center; + cell.Style.Border.OutsideBorder = XLBorderStyleValues.Thin; + cell.Style.Border.InsideBorder = XLBorderStyleValues.Thin; + } + currentRow++; + + // Fill data rows + foreach (var r in records) + { + TimeSpan totalOT = CalculateTotalOT(r); + int totalBreak = (r.OfficeBreak ?? 0) + (r.AfterBreak ?? 0); + TimeSpan netOT = totalOT - TimeSpan.FromMinutes(totalBreak); + + int col = 1; + var dayCell = worksheet.Cell(currentRow, col++); + dayCell.Value = r.OtDate.ToString("ddd"); + dayCell.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center; + dayCell.Style.Border.OutsideBorder = XLBorderStyleValues.Thin; + dayCell.Style.Border.InsideBorder = XLBorderStyleValues.Thin; + + var dateCell = worksheet.Cell(currentRow, col++); + dateCell.Value = r.OtDate.ToString("yyyy-MM-dd"); + dateCell.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center; + dateCell.Style.Border.OutsideBorder = XLBorderStyleValues.Thin; + dateCell.Style.Border.InsideBorder = XLBorderStyleValues.Thin; + + // Apply background color for weekends and public holidays + var dayOfWeek = r.OtDate.DayOfWeek; + bool isWeekend = (weekendId == 1 && (dayOfWeek == DayOfWeek.Friday || dayOfWeek == DayOfWeek.Saturday)) || + (weekendId == 2 && (dayOfWeek == DayOfWeek.Saturday || dayOfWeek == DayOfWeek.Sunday)); + bool isPublicHoliday = publicHolidays.Any(h => h.HolidayDate.Date == r.OtDate.Date); + + if (isPublicHoliday) + { + dayCell.Style.Fill.BackgroundColor = XLColor.Pink; + dateCell.Style.Fill.BackgroundColor = XLColor.Pink; + } + else if (isWeekend) + { + dayCell.Style.Fill.BackgroundColor = XLColor.LightBlue; + dateCell.Style.Fill.BackgroundColor = XLColor.LightBlue; + } + + worksheet.Cell(currentRow, col++).Value = FormatTime(r.OfficeFrom); + worksheet.Cell(currentRow, col++).Value = FormatTime(r.OfficeTo); + worksheet.Cell(currentRow, col++).Value = r.OfficeBreak; + + worksheet.Cell(currentRow, col++).Value = FormatTime(r.AfterFrom); + worksheet.Cell(currentRow, col++).Value = FormatTime(r.AfterTo); + worksheet.Cell(currentRow, col++).Value = r.AfterBreak; + + worksheet.Cell(currentRow, col++).Value = totalOT.ToString(@"hh\:mm"); + worksheet.Cell(currentRow, col++).Value = totalBreak; + worksheet.Cell(currentRow, col++).Value = netOT.ToString(@"hh\:mm"); + + if (departmentId == 2 || isAdminUser) + worksheet.Cell(currentRow, col++).Value = r.Stations?.StationName ?? ""; + + worksheet.Cell(currentRow, col++).Value = r.OtDescription ?? ""; + + // Apply border and alignment for the rest of the row + for (int i = headers.IndexOf("Office From") + 1; i <= headers.Count; i++) + { + var cell = worksheet.Cell(currentRow, i); + cell.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center; + cell.Style.Border.OutsideBorder = XLBorderStyleValues.Thin; + cell.Style.Border.InsideBorder = XLBorderStyleValues.Thin; + } + + currentRow++; + } + + // Add Total row + int totalRow = currentRow; + worksheet.Cell(totalRow, 1).Value = "TOTAL"; + worksheet.Cell(totalRow, 1).Style.Font.Bold = true; + worksheet.Cell(totalRow, headers.IndexOf("Total OT") + 1).Value = $"=SUM(I{headerRow + 1}:I{totalRow - 1})"; + worksheet.Cell(totalRow, headers.IndexOf("Total OT") + 1).Style.Font.Bold = true; + worksheet.Cell(totalRow, headers.IndexOf("Break Minutes") + 1).Value = $"=SUM(J{headerRow + 1}:J{totalRow - 1})"; + worksheet.Cell(totalRow, headers.IndexOf("Break Minutes") + 1).Style.Font.Bold = true; + worksheet.Cell(totalRow, headers.IndexOf("Net OT") + 1).Value = $"=SUM(K{headerRow + 1}:K{totalRow - 1})"; + worksheet.Cell(totalRow, headers.IndexOf("Net OT") + 1).Style.Font.Bold = true; + + worksheet.Columns().AdjustToContents(); + + var stream = new MemoryStream(); + workbook.SaveAs(stream); + stream.Position = 0; + return stream; + } + + private TimeSpan CalculateTotalOT(OtRegisterModel r) + { + TimeSpan office = (r.OfficeTo ?? TimeSpan.Zero) - (r.OfficeFrom ?? TimeSpan.Zero); + TimeSpan after = (r.AfterTo ?? TimeSpan.Zero) - (r.AfterFrom ?? TimeSpan.Zero); + if (after < TimeSpan.Zero) + after += TimeSpan.FromHours(24); + return office + after; + } + + private string FormatTime(TimeSpan? time) + { + return time == null || time == TimeSpan.Zero ? "" : time.Value.ToString(@"hh\:mm"); + } + } +} \ No newline at end of file diff --git a/Areas/OTcalculate/Services/OvertimePdfService.cs b/Areas/OTcalculate/Services/OvertimePdfService.cs index 6f54e82..8da0c80 100644 --- a/Areas/OTcalculate/Services/OvertimePdfService.cs +++ b/Areas/OTcalculate/Services/OvertimePdfService.cs @@ -5,22 +5,24 @@ using System.IO; using System.Collections.Generic; using PSTW_CentralSystem.Areas.OTcalculate.Models; using System; +using System.Linq; namespace PSTW_CentralSystem.Areas.OTcalculate.Services { public class OvertimePdfService { - public MemoryStream GenerateOvertimeTablePdf( - List records, - int departmentId, - string userFullName, - string departmentName, - byte[]? logoImage = null -) + public MemoryStream GenerateOvertimeTablePdf( + List records, + int departmentId, + string userFullName, + string departmentName, + int userStateId, + int weekendId, + List publicHolidays, + bool isAdminUser = false, + byte[]? logoImage = null) { - records = records - .OrderBy(r => r.OtDate) - .ToList(); + records = records.OrderBy(r => r.OtDate).ToList(); var stream = new MemoryStream(); @@ -33,12 +35,11 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services page.Content().Column(column => { + // Header column.Item().Row(row => { row.RelativeItem(2).Column(col => { - - if (logoImage != null) { col.Item().Container().Height(36).Image(logoImage, ImageScaling.FitArea); @@ -47,22 +48,22 @@ 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(); + 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 + // Table column.Item().Table(table => { + // Columns table.ColumnsDefinition(columns => { - columns.RelativeColumn(0.7f); // Days + columns.RelativeColumn(0.7f); // Day columns.RelativeColumn(1.1f); // Date columns.RelativeColumn(0.8f); // Office From columns.RelativeColumn(0.8f); // Office To @@ -70,18 +71,18 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services columns.RelativeColumn(0.9f); // After From columns.RelativeColumn(0.9f); // After To columns.RelativeColumn(0.9f); // After Break - columns.RelativeColumn(); // Total OT - columns.RelativeColumn(); // Break Hours - columns.RelativeColumn(); // Net OT - if (departmentId == 2) + columns.RelativeColumn(); // Total OT + columns.RelativeColumn(); // Break Hours + columns.RelativeColumn(); // Net OT + if (departmentId == 2 || isAdminUser) columns.RelativeColumn(); // Station columns.RelativeColumn(2.7f); // Description }); + // Header table.Header(header => { - // Row 1 — grouped headers - header.Cell().RowSpan(2).Background("#e0f7da").Border(0.25f).Padding(5).Text("Days").FontSize(9).Bold().AlignCenter(); + header.Cell().RowSpan(2).Background("#e0f7da").Border(0.25f).Padding(5).Text("Days").FontSize(9).Bold().AlignCenter(); header.Cell().RowSpan(2).Background("#d0ead2").Border(0.25f).Padding(5).Text("Date").FontSize(9).Bold().AlignCenter(); header.Cell().ColumnSpan(3).Background("#dceefb").Border(0.25f).Padding(5).Text("Office Hours\n(8:30 - 17:30)").FontSize(9).Bold().AlignCenter(); @@ -89,15 +90,14 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services header.Cell().RowSpan(2).Background("#fdebd0").Border(0.25f).Padding(5).Text("Total OT\nHours").FontSize(9).Bold().AlignCenter(); header.Cell().RowSpan(2).Background("#fdebd0").Border(0.25f).Padding(5).Text("Break Hours\n(min)").FontSize(9).Bold().AlignCenter(); - header.Cell().RowSpan(2).Background("#fdebd0").Border(0.25f).Padding(5).Text("Net OT Hours").FontSize(9).Bold().AlignCenter(); + header.Cell().RowSpan(2).Background("#fdebd0").Border(0.25f).Padding(5).Text("Net OT\nHours").FontSize(9).Bold().AlignCenter(); - if (departmentId == 2) + if (departmentId == 2 || isAdminUser) header.Cell().RowSpan(2).Background("#d0f0ef").Border(0.25f).Padding(5).Text("Station").FontSize(9).Bold().AlignCenter(); header.Cell().RowSpan(2).Background("#e3f2fd").Border(0.25f).Padding(5).Text("Description").FontSize(9).Bold().AlignCenter(); - - // Row 2 — subheaders only for grouped columns + // Subheaders for Office/After header.Cell().Background("#dceefb").Border(0.25f).Padding(5).Text("From").FontSize(9).Bold().AlignCenter(); header.Cell().Background("#dceefb").Border(0.25f).Padding(5).Text("To").FontSize(9).Bold().AlignCenter(); header.Cell().Background("#dceefb").Border(0.25f).Padding(5).Text("Break (min)").FontSize(9).Bold().AlignCenter(); @@ -107,8 +107,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services header.Cell().Background("#edf2f7").Border(0.25f).Padding(5).Text("Break (min)").FontSize(9).Bold().AlignCenter(); }); - - // Data Rows + // Data double totalOTSum = 0; int totalBreakSum = 0; TimeSpan totalNetOt = TimeSpan.Zero; @@ -117,89 +116,78 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services if (!records.Any()) { uint colspan = (uint)(departmentId == 2 ? 13 : 12); - - table.Cell().ColumnSpan(colspan) - .Border(0.5f) - .Padding(10) - .AlignCenter() + 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(); + .FontSize(10).FontColor(Colors.Grey.Darken2).Italic(); } else { - var groupedRecords = records.GroupBy(r => r.OtDate.Date); - - foreach (var group in groupedRecords) + foreach (var r in records) { - bool isFirstRow = true; + var totalOT = CalculateTotalOT(r); + var totalBreak = (r.OfficeBreak ?? 0) + (r.AfterBreak ?? 0); + var netOT = totalOT - TimeSpan.FromMinutes(totalBreak); - foreach (var r in group) + totalOTSum += totalOT.TotalHours; + totalBreakSum += totalBreak; + totalNetOt += netOT; + + string rowBg = alternate ? "#f9f9f9" : "#ffffff"; + alternate = !alternate; + + string backgroundColor = GetDayCellBackgroundColor(r.OtDate, userStateId, publicHolidays, weekendId); + + void AddCell(string value, bool center = true, string? bg = null) { - var totalOT = CalculateTotalOT(r); - var totalBreak = (r.OfficeBreak ?? 0) + (r.AfterBreak ?? 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(isFirstRow ? $"{r.OtDate:ddd}" : ""); - AddCell(isFirstRow ? r.OtDate.ToString("dd/MM/yyyy") : ""); - - AddCell(FormatTime(r.OfficeFrom)); - AddCell(FormatTime(r.OfficeTo)); - AddCell($"{r.OfficeBreak ?? 0}"); - AddCell(FormatTime(r.AfterFrom)); - AddCell(FormatTime(r.AfterTo)); - AddCell($"{r.AfterBreak ?? 0}"); - 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"); - table.Cell().Background(rowBg).Border(0.25f).Padding(5).Text(r.OtDescription ?? "-").FontSize(9).WrapAnywhere().LineHeight(1.2f); - - isFirstRow = false; + var text = table.Cell().Background(bg ?? rowBg).Border(0.25f).Padding(5).Text(value).FontSize(9); + if (center) + text.AlignCenter(); + else + text.AlignLeft(); } + + AddCell(r.OtDate.ToString("ddd"), true, backgroundColor); + AddCell(r.OtDate.ToString("dd/MM/yyyy")); + + AddCell(FormatTime(r.OfficeFrom)); + AddCell(FormatTime(r.OfficeTo)); + AddCell(r.OfficeBreak > 0 ? $"{r.OfficeBreak}" : ""); + + AddCell(FormatTime(r.AfterFrom)); + AddCell(FormatTime(r.AfterTo)); + AddCell(r.AfterBreak > 0 ? $"{r.AfterBreak}" : ""); + + AddCell(totalOT > TimeSpan.Zero ? $"{totalOT:hh\\:mm}" : ""); + AddCell(totalBreak > 0 ? $"{totalBreak}" : ""); + AddCell(netOT > TimeSpan.Zero ? $"{netOT:hh\\:mm}" : ""); + + if (departmentId == 2 || isAdminUser) + AddCell(r.Stations?.StationName ?? ""); + + table.Cell().Background(rowBg).Border(0.25f).Padding(5) + .Text(r.OtDescription ?? "-").FontSize(9).WrapAnywhere().LineHeight(1.2f); } - - var totalOTTimeSpan = TimeSpan.FromHours(totalOTSum); - var totalBreakTimeSpan = TimeSpan.FromMinutes(totalBreakSum); - + // Totals row int totalCols = departmentId == 2 ? 13 : 12; - int spanCols = departmentId == 2 ? 7 : 6; + int spanCols = departmentId == 2 ? 9 : 8; 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(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).Padding(5) + .Text($"{TimeSpan.FromHours(totalOTSum):hh\\:mm}").Bold().FontSize(9).AlignCenter(); + table.Cell().Background("#d8d1f5").Border(0.80f).Padding(5) + .Text($"{TimeSpan.FromMinutes(totalBreakSum):hh\\:mm}").Bold().FontSize(9).AlignCenter(); + table.Cell().Background("#d8d1f5").Border(0.80f).Padding(5) + .Text($"{totalNetOt:hh\\:mm}").Bold().FontSize(9).AlignCenter(); + + if (departmentId == 2 || isAdminUser) 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); @@ -212,26 +200,38 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services { TimeSpan office = (r.OfficeTo ?? TimeSpan.Zero) - (r.OfficeFrom ?? TimeSpan.Zero); TimeSpan after = (r.AfterTo ?? TimeSpan.Zero) - (r.AfterFrom ?? TimeSpan.Zero); - if (after < TimeSpan.Zero) after += TimeSpan.FromHours(24); - return office + after; } private string FormatTime(TimeSpan? time) { - return time?.ToString(@"hh\:mm") ?? "-"; + if (time == null || time == TimeSpan.Zero) + return ""; + return time.Value.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}"; } + private string GetDayCellBackgroundColor(DateTime date, int userStateId, List publicHolidays, int weekendId) + { + if (publicHolidays.Any(h => h.HolidayDate.Date == date.Date)) + return "#ffc0cb"; // Pink + + var dayOfWeek = date.DayOfWeek; + if ((weekendId == 1 && (dayOfWeek == DayOfWeek.Friday || dayOfWeek == DayOfWeek.Saturday)) || + (weekendId == 2 && (dayOfWeek == DayOfWeek.Saturday || dayOfWeek == DayOfWeek.Sunday))) + return "#add8e6"; // Blue + + return "#ffffff"; // Default + } } } diff --git a/Areas/OTcalculate/Views/HrDashboard/Calendar.cshtml b/Areas/OTcalculate/Views/HrDashboard/Calendar.cshtml index 89312f8..3462a0f 100644 --- a/Areas/OTcalculate/Views/HrDashboard/Calendar.cshtml +++ b/Areas/OTcalculate/Views/HrDashboard/Calendar.cshtml @@ -4,7 +4,6 @@ } @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers -
@@ -32,9 +31,21 @@
+ +
-
diff --git a/Areas/OTcalculate/Views/HrDashboard/HrUserSetting.cshtml b/Areas/OTcalculate/Views/HrDashboard/HrUserSetting.cshtml new file mode 100644 index 0000000..ef40697 --- /dev/null +++ b/Areas/OTcalculate/Views/HrDashboard/HrUserSetting.cshtml @@ -0,0 +1,382 @@ +@{ + ViewBag.Title = "User Settings"; + Layout = "~/Views/Shared/_Layout.cshtml"; +} + +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers + + + +
+ + +
+
+
+
+
+
+ + + + +
+
+ +
+ + + + + + + + + +
Full NameDepartmentCurrent Flexi HourSelect
+
+ +
+ + +
+
+
+
+ +
+
+ + + + +
+
+ + + + + + + + + +
Full NameDepartmentCurrent StateSelect
+
+
+ + +
+
+
+
+ +@section Scripts { + @await Html.PartialAsync("_ValidationScriptsPartial") + + + + +} diff --git a/Areas/OTcalculate/Views/HrDashboard/Rate.cshtml b/Areas/OTcalculate/Views/HrDashboard/Rate.cshtml index 06c4170..c203ffa 100644 --- a/Areas/OTcalculate/Views/HrDashboard/Rate.cshtml +++ b/Areas/OTcalculate/Views/HrDashboard/Rate.cshtml @@ -31,6 +31,19 @@
+ + @@ -56,7 +69,7 @@ Full Name Department Current Rate - Select Rate + Select @@ -174,7 +187,7 @@ "render": data => data ? parseFloat(data).toFixed(2) : 'N/A' }, { - "title": "Select Rate", + "title": "Select", "data": "id", "className": "text-center", "render": function (data) diff --git a/Areas/OTcalculate/Views/HrDashboard/Settings.cshtml b/Areas/OTcalculate/Views/HrDashboard/Settings.cshtml index eec4d5d..f69164e 100644 --- a/Areas/OTcalculate/Views/HrDashboard/Settings.cshtml +++ b/Areas/OTcalculate/Views/HrDashboard/Settings.cshtml @@ -4,7 +4,6 @@ } @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers -
@@ -32,6 +31,19 @@
+ +
@@ -73,6 +85,28 @@ +
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
@@ -90,6 +124,8 @@ return { rateUpdateDate: null, calendarUpdateDate: null, + flexiHourUpdateDate: null, + regionUpdateDate: null, }; }, mounted() { @@ -106,6 +142,8 @@ const data = await response.json(); this.rateUpdateDate = data.rateUpdateDate; this.calendarUpdateDate = data.calendarUpdateDate; + this.flexiHourUpdateDate = data.flexiHourUpdateDate; + this.regionUpdateDate = data.regionUpdateDate; } catch (error) { console.error(error); } diff --git a/Areas/OTcalculate/Views/Overtime/EditOvertime.cshtml b/Areas/OTcalculate/Views/Overtime/EditOvertime.cshtml index 5aac66d..024984d 100644 --- a/Areas/OTcalculate/Views/Overtime/EditOvertime.cshtml +++ b/Areas/OTcalculate/Views/Overtime/EditOvertime.cshtml @@ -9,31 +9,29 @@
+
- +
- +
OFFICE HOURS
- +
- +
- + @@ -41,25 +39,23 @@
- +
AFTER OFFICE HOURS
- +
- +
- + @@ -67,7 +63,7 @@
- +
- - {{ charCount }} / 150 characters - - + + {{ charCount }} / 150 characters
-
-
- - -
- - -
- -
- - -
- -
- - + +
+
+ +
- +
- +
+
@@ -135,6 +111,7 @@
+ @section Scripts { @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); @@ -154,7 +131,7 @@ afterBreak: 0, stationId: "", otDescription: "", - otDays: "", + otDays: "", userId: null, }, airstationList: [], @@ -162,6 +139,20 @@ totalBreakHours: "0 hr 0 min", currentUser: null, isPSTWAIR: false, + userState: null, // To store the user's state information + publicHolidays: [], // To store public holidays + breakOptions: Array.from({ length: 15 }, (_, i) => { + const totalMinutes = i * 30; + const hours = Math.floor(totalMinutes / 60); + const minutes = totalMinutes % 60; + + let label = ''; + if (hours > 0) label += `${hours} hour${hours > 1 ? 's' : ''}`; + if (minutes > 0) label += `${label ? ' ' : ''}${minutes} min`; + if (!label) label = '0 min'; + + return { label, value: totalMinutes }; + }), }; }, @@ -170,6 +161,7 @@ return this.editForm.otDescription.length; } }, + async mounted() { const urlParams = new URLSearchParams(window.location.search); const overtimeId = urlParams.get('overtimeId'); @@ -177,19 +169,19 @@ await this.fetchOvertimeRecord(overtimeId); } - await this.fetchUser(); - + await this.fetchUserAndRelatedData(); // Fetch user, state, and holidays if (this.isPSTWAIR) { await this.fetchStations(); } }, + methods: { async fetchOvertimeRecord(id) { try { const res = await fetch(`/OvertimeAPI/GetOvertimeRecordById/${id}`); if (res.ok) { const data = await res.json(); - this.populateForm(data); // Fill form fields with data + this.populateForm(data); } else { alert("Failed to fetch overtime record."); } @@ -197,39 +189,42 @@ console.error("Fetch error:", err); } }, + populateForm(record) { this.editForm = { ...record, - otDate: record.otDate.slice(0, 10), - selectedDayType: record.otDays, + otDate: record.otDate ? record.otDate.slice(0, 10) : "", + // We will auto-detect the day, so we don't need to pre-fill otDays for auto-detection }; - this.calculateOTAndBreak(); + this.updateDayType(); // Initial detection after loading data }, + async fetchStations() { try { const response = await fetch(`/OvertimeAPI/GetStationsByDepartment`); if (!response.ok) throw new Error("Failed to fetch stations"); - this.airstationList = await response.json(); } catch (error) { console.error("Error fetching stations:", error); } }, - async fetchUser() { + + async fetchUserAndRelatedData() { try { const response = await fetch(`/IdentityAPI/GetUserInformation/`, { method: 'POST' }); if (response.ok) { const data = await response.json(); this.currentUser = data?.userInfo || null; - this.userId = this.currentUser?.id || null; + this.editForm.userId = this.currentUser?.id || null; - console.log("Fetched User:", this.currentUser); - console.log("Dept ID:", this.currentUser?.departmentId); + const isSuperAdmin = this.currentUser?.role?.includes("SuperAdmin"); + const isSystemAdmin = this.currentUser?.role?.includes("SystemAdmin"); + const isDepartmentTwo = this.currentUser?.department?.departmentId === 2; + this.isPSTWAIR = isSuperAdmin || isSystemAdmin || isDepartmentTwo; - if (this.currentUser?.department?.departmentId === 2) { - this.isPSTWAIR = true; - console.log("User is PSTW AIR"); + if (this.editForm.userId) { + await this.fetchUserStateAndHolidays(); } } else { console.error(`Failed to fetch user: ${response.statusText}`); @@ -238,12 +233,30 @@ console.error("Error fetching user:", error); } }, + + async fetchUserStateAndHolidays() { + try { + const response = await fetch(`/OvertimeAPI/GetUserStateAndHolidays/${this.editForm.userId}`); + if (!response.ok) { + throw new Error(`Failed to fetch user state and holidays: ${response.statusText}`); + } + const data = await response.json(); + this.userState = data.state; + this.publicHolidays = data.publicHolidays; + this.updateDayType(); // Detect day type after loading data + } catch (error) { + console.error("Error fetching user state and holidays:", error); + this.editForm.otDays = "Weekday"; // Default if fetching fails + } + }, + limitCharCount(event) { if (this.editForm.otDescription && this.editForm.otDescription.length > 150) { this.editForm.otDescription = this.editForm.otDescription.substring(0, 150); event.preventDefault(); } }, + calculateOTAndBreak() { let officeOT = this.calculateTimeDifference( this.editForm.officeFrom, @@ -268,36 +281,97 @@ totalBreakMinutes = totalBreakMinutes % 60; this.totalBreakHours = `${totalBreakHours} hr ${totalBreakMinutes} min`; + this.updateDayType(); // Update day type when times or date change + }, + updateTime(fieldName) { + if (fieldName === 'officeFrom') { + this.editForm.officeFrom = this.roundToNearest30(this.editForm.officeFrom); + } else if (fieldName === 'officeTo') { + this.editForm.officeTo = this.roundToNearest30(this.editForm.officeTo); + } else if (fieldName === 'afterFrom') { + this.editForm.afterFrom = this.roundToNearest30(this.editForm.afterFrom); + } else if (fieldName === 'afterTo') { + this.editForm.afterTo = this.roundToNearest30(this.editForm.afterTo); + } + // Recalculate OT and break times after rounding, if necessary + this.calculateOTAndBreak(); + }, + roundToNearest30(timeStr) { + if (!timeStr) return timeStr; + const [hours, minutes] = timeStr.split(':').map(Number); + const roundedMinutes = minutes < 15 ? 0 : minutes < 45 ? 30 : 0; + const adjustedHour = minutes < 45 ? hours : (hours + 1) % 24; + return `${adjustedHour.toString().padStart(2, '0')}:${roundedMinutes.toString().padStart(2, '0')}`; }, calculateTimeDifference(startTime, endTime, breakMinutes) { if (!startTime || !endTime) { return { hours: 0, minutes: 0 }; } - const start = this.parseTime(startTime); const end = this.parseTime(endTime); - let diffMinutes = (end.hours * 60 + end.minutes) - (start.hours * 60 + start.minutes); if (diffMinutes < 0) { diffMinutes += 24 * 60; } - diffMinutes -= breakMinutes || 0; - const hours = Math.floor(diffMinutes / 60); const minutes = diffMinutes % 60; - return { hours, minutes }; }, + parseTime(timeString) { const [hours, minutes] = timeString.split(':').map(Number); return { hours, minutes }; }, + formatTime(timeString) { if (!timeString) return null; const [hours, minutes] = timeString.split(':'); return `${hours.padStart(2, '0')}:${minutes.padStart(2, '0')}:00`; //HH:mm:ss format }, + + handleDateChange() { + this.updateDayType(); + this.calculateOTAndBreak(); + }, + + updateDayType() { + if (!this.editForm.otDate || !this.userState) { + this.editForm.otDays = ""; + return; + } + + const selectedDateObj = new Date(this.editForm.otDate + "T00:00:00"); + const dayOfWeek = selectedDateObj.getDay(); // 0 (Sunday) to 6 (Saturday) + const year = selectedDateObj.getFullYear(); + const month = selectedDateObj.getMonth() + 1; + const day = selectedDateObj.getDate(); + const formattedDate = `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`; + + if (this.publicHolidays.some(holiday => holiday.date === formattedDate)) { + this.editForm.otDays = "Public Holiday"; + return; + } + + const weekendId = this.userState.weekendId; + const isWeekend = (() => { + if (weekendId === 1) { + return dayOfWeek === 5 || dayOfWeek === 6; // Friday and Saturday + } else if (weekendId === 2) { + return dayOfWeek === 6 || dayOfWeek === 0; // Saturday and Sunday + } else { + return dayOfWeek === 0; // Default Sunday + } + })(); + + if (isWeekend) { + this.editForm.otDays = "Weekend"; + return; + } + + this.editForm.otDays = "Weekday"; + }, + async updateRecord() { if (this.editForm.officeFrom && !this.editForm.officeTo) { alert("Please enter a 'To' time for Office Hours."); @@ -338,32 +412,21 @@ } } + const formData = new FormData(); + formData.append("OvertimeId", this.editForm.overtimeId); + formData.append("OtDate", this.editForm.otDate); + formData.append("StationId", this.editForm.stationId || ""); + formData.append("OtDescription", this.editForm.otDescription || ""); + formData.append("OtDays", this.editForm.otDays); // Use the auto-detected day type + formData.append("OfficeFrom", this.formatTime(this.editForm.officeFrom) || ""); + formData.append("OfficeTo", this.formatTime(this.editForm.officeTo) || ""); + formData.append("AfterFrom", this.formatTime(this.editForm.afterFrom) || ""); + formData.append("AfterTo", this.formatTime(this.editForm.afterTo) || ""); + formData.append("officeBreak", this.editForm.officeBreak || 0); + formData.append("afterBreak", this.editForm.afterBreak || 0); + formData.append("userId", this.currentUser?.id); + try { - const formData = new FormData(); - formData.append("OvertimeId", this.editForm.overtimeId); - formData.append("OtDate", this.editForm.otDate); - formData.append("StationId", this.editForm.stationId || ""); - formData.append("OtDescription", this.editForm.otDescription || ""); - formData.append("OtDays", this.editForm.selectedDayType); - formData.append("OfficeFrom", this.formatTime(this.editForm.officeFrom) || ""); - formData.append("OfficeTo", this.formatTime(this.editForm.officeTo) || ""); - formData.append("After Office From", this.formatTime(this.editForm.afterFrom) || ""); - formData.append("After Office To", this.formatTime(this.editForm.afterTo) || ""); - - formData.append("officeBreak", this.editForm.officeBreak || 0); - formData.append("afterBreak", this.editForm.afterBreak || 0); - - // Conditionally include nullable fields - if (this.editForm.afterFrom) { - formData.append("afterFrom", this.formatTime(this.editForm.afterFrom)); - } - if (this.editForm.afterTo) { - formData.append("afterTo", this.formatTime(this.editForm.afterTo)); - } - - // Required field - formData.append("userId", this.currentUser?.id); - const response = await fetch(`/OvertimeAPI/UpdateOvertimeRecord`, { method: 'POST', body: formData @@ -371,7 +434,6 @@ if (response.ok) { alert("Overtime record updated successfully!"); - window.location.href = '/OTcalculate/Overtime/OtRecords'; } else { alert("Failed to update overtime record."); diff --git a/Areas/OTcalculate/Views/Overtime/OtRecords.cshtml b/Areas/OTcalculate/Views/Overtime/OtRecords.cshtml index 8465111..b607dd5 100644 --- a/Areas/OTcalculate/Views/Overtime/OtRecords.cshtml +++ b/Areas/OTcalculate/Views/Overtime/OtRecords.cshtml @@ -111,10 +111,10 @@ {{ formatDate(record.otDate) }} {{ formatTime(record.officeFrom) }} {{ formatTime(record.officeTo) }} - {{ record.officeBreak }} min + {{ record.officeBreak }} {{ formatTime(record.afterFrom) }} {{ formatTime(record.afterTo) }} - {{ record.afterBreak }} min + {{ record.afterBreak }} {{ formatHourMinute(calcTotalTime(record)) }} {{ calcBreakTotal(record) }} {{ formatHourMinute(calcNetHours(record)) }} @@ -126,13 +126,20 @@
- - + No records found. @@ -152,9 +159,26 @@
- - + + + +
+
@@ -162,6 +186,8 @@ @section Scripts { + +