-
This commit is contained in:
parent
4248484877
commit
6d7dc52724
@ -43,5 +43,9 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Controllers
|
|||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IActionResult HrUserSetting()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
14
Areas/OTcalculate/Models/FlexiHourModel.cs
Normal file
14
Areas/OTcalculate/Models/FlexiHourModel.cs
Normal file
@ -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; }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
30
Areas/OTcalculate/Models/HrUserSettingModel.cs
Normal file
30
Areas/OTcalculate/Models/HrUserSettingModel.cs
Normal file
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
9
Areas/OTcalculate/Models/OvertimeSubmissionModel.cs
Normal file
9
Areas/OTcalculate/Models/OvertimeSubmissionModel.cs
Normal file
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
168
Areas/OTcalculate/Services/OvertimeExcelService.cs
Normal file
168
Areas/OTcalculate/Services/OvertimeExcelService.cs
Normal file
@ -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<OtRegisterModel> records,
|
||||||
|
int departmentId,
|
||||||
|
string userFullName,
|
||||||
|
string departmentName,
|
||||||
|
int userStateId,
|
||||||
|
int weekendId,
|
||||||
|
List<CalendarModel> 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<string>
|
||||||
|
{
|
||||||
|
"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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,6 +5,7 @@ using System.IO;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using PSTW_CentralSystem.Areas.OTcalculate.Models;
|
using PSTW_CentralSystem.Areas.OTcalculate.Models;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
||||||
{
|
{
|
||||||
@ -15,12 +16,13 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
int departmentId,
|
int departmentId,
|
||||||
string userFullName,
|
string userFullName,
|
||||||
string departmentName,
|
string departmentName,
|
||||||
byte[]? logoImage = null
|
int userStateId,
|
||||||
)
|
int weekendId,
|
||||||
|
List<CalendarModel> publicHolidays,
|
||||||
|
bool isAdminUser = false,
|
||||||
|
byte[]? logoImage = null)
|
||||||
{
|
{
|
||||||
records = records
|
records = records.OrderBy(r => r.OtDate).ToList();
|
||||||
.OrderBy(r => r.OtDate)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var stream = new MemoryStream();
|
var stream = new MemoryStream();
|
||||||
|
|
||||||
@ -33,12 +35,11 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
|
|
||||||
page.Content().Column(column =>
|
page.Content().Column(column =>
|
||||||
{
|
{
|
||||||
|
// Header
|
||||||
column.Item().Row(row =>
|
column.Item().Row(row =>
|
||||||
{
|
{
|
||||||
row.RelativeItem(2).Column(col =>
|
row.RelativeItem(2).Column(col =>
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
if (logoImage != null)
|
if (logoImage != null)
|
||||||
{
|
{
|
||||||
col.Item().Container().Height(36).Image(logoImage, ImageScaling.FitArea);
|
col.Item().Container().Height(36).Image(logoImage, ImageScaling.FitArea);
|
||||||
@ -54,15 +55,15 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
.FontSize(9).FontColor(Colors.Grey.Medium);
|
.FontSize(9).FontColor(Colors.Grey.Medium);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
column.Item().PaddingVertical(10).LineHorizontal(0.5f).LineColor(Colors.Grey.Lighten2);
|
column.Item().PaddingVertical(10).LineHorizontal(0.5f).LineColor(Colors.Grey.Lighten2);
|
||||||
|
|
||||||
// Table section
|
// Table
|
||||||
column.Item().Table(table =>
|
column.Item().Table(table =>
|
||||||
{
|
{
|
||||||
|
// Columns
|
||||||
table.ColumnsDefinition(columns =>
|
table.ColumnsDefinition(columns =>
|
||||||
{
|
{
|
||||||
columns.RelativeColumn(0.7f); // Days
|
columns.RelativeColumn(0.7f); // Day
|
||||||
columns.RelativeColumn(1.1f); // Date
|
columns.RelativeColumn(1.1f); // Date
|
||||||
columns.RelativeColumn(0.8f); // Office From
|
columns.RelativeColumn(0.8f); // Office From
|
||||||
columns.RelativeColumn(0.8f); // Office To
|
columns.RelativeColumn(0.8f); // Office To
|
||||||
@ -73,14 +74,14 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
columns.RelativeColumn(); // Total OT
|
columns.RelativeColumn(); // Total OT
|
||||||
columns.RelativeColumn(); // Break Hours
|
columns.RelativeColumn(); // Break Hours
|
||||||
columns.RelativeColumn(); // Net OT
|
columns.RelativeColumn(); // Net OT
|
||||||
if (departmentId == 2)
|
if (departmentId == 2 || isAdminUser)
|
||||||
columns.RelativeColumn(); // Station
|
columns.RelativeColumn(); // Station
|
||||||
columns.RelativeColumn(2.7f); // Description
|
columns.RelativeColumn(2.7f); // Description
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Header
|
||||||
table.Header(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().RowSpan(2).Background("#d0ead2").Border(0.25f).Padding(5).Text("Date").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("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("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("#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();
|
header.Cell().RowSpan(2).Background("#e3f2fd").Border(0.25f).Padding(5).Text("Description").FontSize(9).Bold().AlignCenter();
|
||||||
|
|
||||||
|
// Subheaders for Office/After
|
||||||
// Row 2 — subheaders only for grouped columns
|
|
||||||
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("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("To").FontSize(9).Bold().AlignCenter();
|
||||||
header.Cell().Background("#dceefb").Border(0.25f).Padding(5).Text("Break (min)").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();
|
header.Cell().Background("#edf2f7").Border(0.25f).Padding(5).Text("Break (min)").FontSize(9).Bold().AlignCenter();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Data
|
||||||
// Data Rows
|
|
||||||
double totalOTSum = 0;
|
double totalOTSum = 0;
|
||||||
int totalBreakSum = 0;
|
int totalBreakSum = 0;
|
||||||
TimeSpan totalNetOt = TimeSpan.Zero;
|
TimeSpan totalNetOt = TimeSpan.Zero;
|
||||||
@ -117,25 +116,13 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
if (!records.Any())
|
if (!records.Any())
|
||||||
{
|
{
|
||||||
uint colspan = (uint)(departmentId == 2 ? 13 : 12);
|
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.")
|
.Text("No records found for selected month and year.")
|
||||||
.FontSize(10)
|
.FontSize(10).FontColor(Colors.Grey.Darken2).Italic();
|
||||||
.FontColor(Colors.Grey.Darken2)
|
|
||||||
.Italic();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var groupedRecords = records.GroupBy(r => r.OtDate.Date);
|
foreach (var r in records)
|
||||||
|
|
||||||
foreach (var group in groupedRecords)
|
|
||||||
{
|
|
||||||
bool isFirstRow = true;
|
|
||||||
|
|
||||||
foreach (var r in group)
|
|
||||||
{
|
{
|
||||||
var totalOT = CalculateTotalOT(r);
|
var totalOT = CalculateTotalOT(r);
|
||||||
var totalBreak = (r.OfficeBreak ?? 0) + (r.AfterBreak ?? 0);
|
var totalBreak = (r.OfficeBreak ?? 0) + (r.AfterBreak ?? 0);
|
||||||
@ -148,58 +135,59 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
string rowBg = alternate ? "#f9f9f9" : "#ffffff";
|
string rowBg = alternate ? "#f9f9f9" : "#ffffff";
|
||||||
alternate = !alternate;
|
alternate = !alternate;
|
||||||
|
|
||||||
void AddCell(string value, bool alignLeft = false)
|
string backgroundColor = GetDayCellBackgroundColor(r.OtDate, userStateId, publicHolidays, weekendId);
|
||||||
|
|
||||||
|
void AddCell(string value, bool center = true, string? bg = null)
|
||||||
{
|
{
|
||||||
var text = table.Cell().Background(rowBg).Border(0.25f).Padding(5).Text(value).FontSize(9);
|
var text = table.Cell().Background(bg ?? rowBg).Border(0.25f).Padding(5).Text(value).FontSize(9);
|
||||||
if (alignLeft)
|
if (center)
|
||||||
text.AlignLeft();
|
|
||||||
else
|
|
||||||
text.AlignCenter();
|
text.AlignCenter();
|
||||||
|
else
|
||||||
|
text.AlignLeft();
|
||||||
}
|
}
|
||||||
|
|
||||||
AddCell(isFirstRow ? $"{r.OtDate:ddd}" : "");
|
AddCell(r.OtDate.ToString("ddd"), true, backgroundColor);
|
||||||
AddCell(isFirstRow ? r.OtDate.ToString("dd/MM/yyyy") : "");
|
AddCell(r.OtDate.ToString("dd/MM/yyyy"));
|
||||||
|
|
||||||
AddCell(FormatTime(r.OfficeFrom));
|
AddCell(FormatTime(r.OfficeFrom));
|
||||||
AddCell(FormatTime(r.OfficeTo));
|
AddCell(FormatTime(r.OfficeTo));
|
||||||
AddCell($"{r.OfficeBreak ?? 0}");
|
AddCell(r.OfficeBreak > 0 ? $"{r.OfficeBreak}" : "");
|
||||||
|
|
||||||
AddCell(FormatTime(r.AfterFrom));
|
AddCell(FormatTime(r.AfterFrom));
|
||||||
AddCell(FormatTime(r.AfterTo));
|
AddCell(FormatTime(r.AfterTo));
|
||||||
AddCell($"{r.AfterBreak ?? 0}");
|
AddCell(r.AfterBreak > 0 ? $"{r.AfterBreak}" : "");
|
||||||
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;
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Totals row
|
||||||
var totalOTTimeSpan = TimeSpan.FromHours(totalOTSum);
|
|
||||||
var totalBreakTimeSpan = TimeSpan.FromMinutes(totalBreakSum);
|
|
||||||
|
|
||||||
int totalCols = departmentId == 2 ? 13 : 12;
|
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++)
|
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)
|
||||||
table.Cell().Background("#d8d1f5").Border(0.80f).Padding(5).Text($"{(int)totalBreakTimeSpan.TotalHours} hr {totalBreakTimeSpan.Minutes} min").Bold().FontSize(9).AlignCenter();
|
.Text($"{TimeSpan.FromHours(totalOTSum):hh\\:mm}").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();
|
table.Cell().Background("#d8d1f5").Border(0.80f).Padding(5)
|
||||||
if (departmentId == 2)
|
.Text($"{TimeSpan.FromMinutes(totalBreakSum):hh\\:mm}").Bold().FontSize(9).AlignCenter();
|
||||||
table.Cell().Background("#d8d1f5").Border(0.80f);
|
table.Cell().Background("#d8d1f5").Border(0.80f).Padding(5)
|
||||||
else
|
.Text($"{totalNetOt:hh\\:mm}").Bold().FontSize(9).AlignCenter();
|
||||||
table.Cell().Background("#d8d1f5").Border(0.80f);
|
|
||||||
|
if (departmentId == 2 || isAdminUser)
|
||||||
table.Cell().Background("#d8d1f5").Border(0.80f);
|
table.Cell().Background("#d8d1f5").Border(0.80f);
|
||||||
table.Cell().Background("#d8d1f5").Border(0.80f);
|
table.Cell().Background("#d8d1f5").Border(0.80f);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}).GeneratePdf(stream);
|
}).GeneratePdf(stream);
|
||||||
@ -212,26 +200,38 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|||||||
{
|
{
|
||||||
TimeSpan office = (r.OfficeTo ?? TimeSpan.Zero) - (r.OfficeFrom ?? TimeSpan.Zero);
|
TimeSpan office = (r.OfficeTo ?? TimeSpan.Zero) - (r.OfficeFrom ?? TimeSpan.Zero);
|
||||||
TimeSpan after = (r.AfterTo ?? TimeSpan.Zero) - (r.AfterFrom ?? TimeSpan.Zero);
|
TimeSpan after = (r.AfterTo ?? TimeSpan.Zero) - (r.AfterFrom ?? TimeSpan.Zero);
|
||||||
|
|
||||||
if (after < TimeSpan.Zero)
|
if (after < TimeSpan.Zero)
|
||||||
after += TimeSpan.FromHours(24);
|
after += TimeSpan.FromHours(24);
|
||||||
|
|
||||||
return office + after;
|
return office + after;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string FormatTime(TimeSpan? time)
|
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<OtRegisterModel> records)
|
private string GetMonthYearString(List<OtRegisterModel> records)
|
||||||
{
|
{
|
||||||
if (records == null || !records.Any())
|
if (records == null || !records.Any())
|
||||||
return "No Data";
|
return "No Data";
|
||||||
|
|
||||||
var firstDate = records.First().OtDate;
|
var firstDate = records.First().OtDate;
|
||||||
return $"{firstDate:MMMM yyyy}";
|
return $"{firstDate:MMMM yyyy}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetDayCellBackgroundColor(DateTime date, int userStateId, List<CalendarModel> 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
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-6 col-md-6 col-lg-3">
|
<div class="col-6 col-md-6 col-lg-3">
|
||||||
@ -32,9 +31,21 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<div class="col-6 col-md-6 col-lg-3">
|
||||||
|
<div class="card card-hover">
|
||||||
|
<a asp-area="OTcalculate" asp-controller="HrDashboard" asp-action="HrUserSetting">
|
||||||
|
<div class="box bg-megna text-center">
|
||||||
|
<h1 class="font-light text-white">
|
||||||
|
<i class="mdi mdi-account-settings"></i>
|
||||||
|
</h1>
|
||||||
|
<h6 class="text-white">User Setting</h6>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<div class="container mt-3">
|
<div class="container mt-3">
|
||||||
|
|
||||||
|
|||||||
382
Areas/OTcalculate/Views/HrDashboard/HrUserSetting.cshtml
Normal file
382
Areas/OTcalculate/Views/HrDashboard/HrUserSetting.cshtml
Normal file
@ -0,0 +1,382 @@
|
|||||||
|
@{
|
||||||
|
ViewBag.Title = "User Settings";
|
||||||
|
Layout = "~/Views/Shared/_Layout.cshtml";
|
||||||
|
}
|
||||||
|
|
||||||
|
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-6 col-md-6 col-lg-3">
|
||||||
|
<div class="card card-hover">
|
||||||
|
<a asp-area="OTcalculate" asp-controller="HrDashboard" asp-action="Rate">
|
||||||
|
<div class="box bg-success text-center">
|
||||||
|
<h1 class="font-light text-white"><i class="mdi mdi-currency-usd"></i></h1>
|
||||||
|
<h6 class="text-white">Rate</h6>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-6 col-md-6 col-lg-3">
|
||||||
|
<div class="card card-hover">
|
||||||
|
<a asp-area="OTcalculate" asp-controller="HrDashboard" asp-action="Calendar">
|
||||||
|
<div class="box bg-purple text-center">
|
||||||
|
<h1 class="font-light text-white"><i class="mdi mdi-calendar"></i></h1>
|
||||||
|
<h6 class="text-white">Calendar</h6>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-6 col-md-6 col-lg-3">
|
||||||
|
<div class="card card-hover">
|
||||||
|
<a asp-area="OTcalculate" asp-controller="HrDashboard" asp-action="HrUserSetting">
|
||||||
|
<div class="box bg-megna text-center">
|
||||||
|
<h1 class="font-light text-white"><i class="mdi mdi-account-settings"></i></h1>
|
||||||
|
<h6 class="text-white">User Setting</h6>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container mt-4" id="app">
|
||||||
|
<ul class="nav nav-tabs">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" :class="{ 'bg-purple text-white': activeTab === 'flexi', 'bg-light text-dark': activeTab !== 'flexi' }"
|
||||||
|
style="border: 1px solid #ddd;" v-on:click="changeTab('flexi')">
|
||||||
|
Flexi Hour Settings
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" :class="{ 'bg-purple text-white': activeTab === 'state', 'bg-light text-dark': activeTab !== 'state' }"
|
||||||
|
style="border: 1px solid #ddd;" v-on:click="changeTab('state')">
|
||||||
|
Region Update
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="tab-content mt-3">
|
||||||
|
<div v-if="activeTab === 'flexi'" class="card shadow-sm">
|
||||||
|
<div class="card m-1">
|
||||||
|
<form v-on:submit.prevent="updateFlexiHours" data-aos="fade-right">
|
||||||
|
<div class="d-flex justify-content-center align-items-center mt-3">
|
||||||
|
<div class="card-body d-flex justify-content-center align-items-center gap-3">
|
||||||
|
<label for="flexiHour" class="mb-0">Flexi Hour</label>
|
||||||
|
<select id="flexiHour" class="form-select" v-model="selectedFlexiHourId" style="max-width: 150px;">
|
||||||
|
<option disabled value="">--Select--</option>
|
||||||
|
<option v-for="option in flexiHours" :value="option.flexiHourId">{{ option.flexiHour }}</option>
|
||||||
|
</select>
|
||||||
|
<button type="button" class="btn btn-danger" v-on:click="clearForm">Clear</button>
|
||||||
|
<button type="submit" class="btn btn-success">Update Flexi Hour</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<table id="userDatatable" class="table table-bordered table-hover table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Full Name</th>
|
||||||
|
<th>Department</th>
|
||||||
|
<th>Current Flexi Hour</th>
|
||||||
|
<th class="text-center">Select</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-center gap-2 my-3">
|
||||||
|
<button type="button" class="btn btn-danger" v-on:click="clearForm">Clear</button>
|
||||||
|
<button type="submit" class="btn btn-success">Update Flexi Hour</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="activeTab === 'state'" class="card shadow-sm">
|
||||||
|
<div class="card-body d-flex justify-content-center align-items-center gap-3">
|
||||||
|
<label class="mb-0">Select State:</label>
|
||||||
|
<select class="form-select" v-model="selectedStateAll" style="max-width: 150px;">
|
||||||
|
<option disabled value="">--Select--</option>
|
||||||
|
<option v-for="state in stateList" :value="state.stateId">{{ state.stateName }}</option>
|
||||||
|
</select>
|
||||||
|
<button class="btn btn-danger" v-on:click="clearStateSelection">Cancel</button>
|
||||||
|
<button class="btn btn-success" v-on:click="saveState">Update State</button>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<table id="stateUpdateTable" class="table table-bordered table-hover table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Full Name</th>
|
||||||
|
<th>Department</th>
|
||||||
|
<th>Current State</th>
|
||||||
|
<th class="text-center">Select</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<button class="btn btn-danger" v-on:click="clearStateSelection">Clear Selection</button>
|
||||||
|
<button class="btn btn-success ms-3" v-on:click="saveState">Update User State</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
@await Html.PartialAsync("_ValidationScriptsPartial")
|
||||||
|
<script src="https://unpkg.com/vue@3.2.37/dist/vue.global.js"></script>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.onload = function () {
|
||||||
|
const app = Vue.createApp({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeTab: 'flexi',
|
||||||
|
flexiHours: [],
|
||||||
|
selectedFlexiHourId: '',
|
||||||
|
selectedUsers: [],
|
||||||
|
userList: [],
|
||||||
|
stateList: [],
|
||||||
|
selectedStateAll: '',
|
||||||
|
selectedUsersState: [],
|
||||||
|
stateUserList: [],
|
||||||
|
userDatatable: null,
|
||||||
|
stateDatatable: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
console.log("Vue App Mounted Successfully");
|
||||||
|
this.fetchFlexiHours();
|
||||||
|
this.fetchStates();
|
||||||
|
this.changeTab('flexi'); // Initialize the default tab
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
changeTab(tab) {
|
||||||
|
this.activeTab = tab;
|
||||||
|
if (tab === 'flexi') {
|
||||||
|
this.clearForm();
|
||||||
|
if (!this.userList.length) {
|
||||||
|
this.fetchUsers().then(() => this.initiateTable());
|
||||||
|
} else {
|
||||||
|
this.initiateTable();
|
||||||
|
}
|
||||||
|
} else if (tab === 'state') {
|
||||||
|
this.clearStateSelection();
|
||||||
|
if (!this.stateUserList.length) {
|
||||||
|
this.fetchUsersState().then(() => this.initiateStateTable());
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
this.initiateStateTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async updateUserSettings(apiUrl, selectedUsers, selectedValue, successMessage, clearCallback, fetchCallback, valueKey) {
|
||||||
|
if (!selectedUsers.length || !selectedValue) {
|
||||||
|
alert("Please select at least one user and a value.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = selectedUsers.map(userId => ({ UserId: userId, [valueKey]: selectedValue }));
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(apiUrl, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
alert(successMessage);
|
||||||
|
clearCallback(); // Clears form selections
|
||||||
|
await fetchCallback(); // Fetches the updated data
|
||||||
|
} else {
|
||||||
|
const errorData = await response.json();
|
||||||
|
alert(errorData.message || "Failed to update. Please try again.");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating:", error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
async fetchFlexiHours() {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/OvertimeAPI/GetFlexiHours");
|
||||||
|
this.flexiHours = await response.json();
|
||||||
|
console.log("Fetched flexi hours:", this.flexiHours);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching Flexi Hours:", error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async fetchUsers() {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/OvertimeAPI/GetUserFlexiHours", { method: "GET" });
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch users');
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
this.userList = data.filter(e => e.fullName !== "MAAdmin" && e.fullName !== "SysAdmin");
|
||||||
|
console.log("Fetched User data", this.userList);
|
||||||
|
|
||||||
|
// Reinitialize DataTable with updated data
|
||||||
|
this.initiateTable();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching users:", error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
async fetchStates() {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/OvertimeAPI/GetStatesName");
|
||||||
|
this.stateList = await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching States:", error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async fetchUsersState() {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/OvertimeAPI/GetUserStates", { method: "GET" });
|
||||||
|
const data = await response.json();
|
||||||
|
this.stateUserList = data.filter(e => e.fullName !== "MAAdmin" && e.fullName !== "SysAdmin");
|
||||||
|
console.log("Fetched state users", this.stateUserList);
|
||||||
|
|
||||||
|
// Reinitialize the state table to reflect updated data
|
||||||
|
this.initiateStateTable();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching users for State:", error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
initiateTable() {
|
||||||
|
if (this.userDatatable) {
|
||||||
|
this.userDatatable.destroy();
|
||||||
|
this.userDatatable = null;
|
||||||
|
}
|
||||||
|
const self = this;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
self.userDatatable = $('#userDatatable').DataTable({
|
||||||
|
data: self.userList,
|
||||||
|
columns: [
|
||||||
|
{ data: "fullName" },
|
||||||
|
{ data: "departmentName" },
|
||||||
|
{ data: "flexiHour", defaultContent: "N/A" },
|
||||||
|
{
|
||||||
|
data: "userId",
|
||||||
|
className: "text-center",
|
||||||
|
render: data => `<input type='checkbox' class='user-checkbox' value='${data}'>`
|
||||||
|
}
|
||||||
|
],
|
||||||
|
destroy: true,
|
||||||
|
responsive: true
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#userDatatable tbody').off('change').on('change', '.user-checkbox', (e) => {
|
||||||
|
const userId = $(e.target).val();
|
||||||
|
if (e.target.checked) {
|
||||||
|
self.selectedUsers.push(userId);
|
||||||
|
} else {
|
||||||
|
self.selectedUsers = self.selectedUsers.filter(id => id !== userId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
initiateStateTable() {
|
||||||
|
if (this.stateDatatable) {
|
||||||
|
this.stateDatatable.destroy();
|
||||||
|
this.stateDatatable = null;
|
||||||
|
}
|
||||||
|
const self = this;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
self.stateDatatable = $('#stateUpdateTable').DataTable({
|
||||||
|
data: self.stateUserList,
|
||||||
|
columns: [
|
||||||
|
{ data: "fullName" },
|
||||||
|
{ data: "departmentName" },
|
||||||
|
{ data: "state", defaultContent: "N/A" },
|
||||||
|
{
|
||||||
|
data: "id",
|
||||||
|
className: "text-center",
|
||||||
|
render: data => `<input type='checkbox' class='state-checkbox' value='${data || ""}'>`
|
||||||
|
}
|
||||||
|
],
|
||||||
|
destroy: true,
|
||||||
|
responsive: true
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#stateUpdateTable tbody').off('change').on('change', '.state-checkbox', (e) => {
|
||||||
|
const userId = $(e.target).val();
|
||||||
|
if (e.target.checked) {
|
||||||
|
self.selectedUsersState.push(userId);
|
||||||
|
} else {
|
||||||
|
self.selectedUsersState = self.selectedUsersState.filter(id => id !== userId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
clearForm() {
|
||||||
|
this.selectedFlexiHourId = '';
|
||||||
|
this.selectedUsers = [];
|
||||||
|
if (this.userDatatable) {
|
||||||
|
this.userDatatable.rows().every(function () {
|
||||||
|
$(this.node()).find('input[type="checkbox"]').prop('checked', false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
clearStateSelection() {
|
||||||
|
this.selectedUsersState = [];
|
||||||
|
if (this.stateDatatable) {
|
||||||
|
this.stateDatatable.rows().every(function () {
|
||||||
|
$(this.node()).find('input[type="checkbox"]').prop('checked', false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async saveState() {
|
||||||
|
await this.updateUserSettings(
|
||||||
|
"/OvertimeAPI/UpdateUserStates",
|
||||||
|
this.selectedUsersState,
|
||||||
|
this.selectedStateAll,
|
||||||
|
"State updated successfully!",
|
||||||
|
this.clearStateSelection,
|
||||||
|
this.fetchUsersState, // This will fetch the updated state users after the update
|
||||||
|
"StateId"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fetch the updated state list to ensure the page reflects the new state data
|
||||||
|
await this.fetchUsersState(); // This fetch will make sure stateUserList is updated
|
||||||
|
this.initiateStateTable(); // Reinitialize the state table with new data
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
async updateFlexiHours() {
|
||||||
|
await this.updateUserSettings(
|
||||||
|
"/OvertimeAPI/UpdateUserFlexiHours",
|
||||||
|
this.selectedUsers,
|
||||||
|
this.selectedFlexiHourId,
|
||||||
|
"Flexi Hours updated successfully!",
|
||||||
|
this.clearForm,
|
||||||
|
this.fetchUsers,
|
||||||
|
"FlexiHourId"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.mount('#app');
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
}
|
||||||
@ -31,6 +31,19 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="col-6 col-md-6 col-lg-3">
|
||||||
|
<div class="card card-hover">
|
||||||
|
<a asp-area="OTcalculate" asp-controller="HrDashboard" asp-action="HrUserSetting">
|
||||||
|
<div class="box bg-megna text-center">
|
||||||
|
<h1 class="font-light text-white">
|
||||||
|
<i class="mdi mdi-account-settings"></i>
|
||||||
|
</h1>
|
||||||
|
<h6 class="text-white">User Setting</h6>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -56,7 +69,7 @@
|
|||||||
<th>Full Name</th>
|
<th>Full Name</th>
|
||||||
<th>Department</th>
|
<th>Department</th>
|
||||||
<th>Current Rate</th>
|
<th>Current Rate</th>
|
||||||
<th class="text-center">Select Rate</th>
|
<th class="text-center">Select</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
</table>
|
</table>
|
||||||
@ -174,7 +187,7 @@
|
|||||||
"render": data => data ? parseFloat(data).toFixed(2) : 'N/A'
|
"render": data => data ? parseFloat(data).toFixed(2) : 'N/A'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Select Rate",
|
"title": "Select",
|
||||||
"data": "id",
|
"data": "id",
|
||||||
"className": "text-center",
|
"className": "text-center",
|
||||||
"render": function (data)
|
"render": function (data)
|
||||||
|
|||||||
@ -4,7 +4,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-6 col-md-6 col-lg-3">
|
<div class="col-6 col-md-6 col-lg-3">
|
||||||
@ -32,6 +31,19 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="col-6 col-md-6 col-lg-3">
|
||||||
|
<div class="card card-hover">
|
||||||
|
<a asp-area="OTcalculate" asp-controller="HrDashboard" asp-action="HrUserSetting">
|
||||||
|
<div class="box bg-megna text-center">
|
||||||
|
<h1 class="font-light text-white">
|
||||||
|
<i class="mdi mdi-account-settings"></i>
|
||||||
|
</h1>
|
||||||
|
<h6 class="text-white">User Setting</h6>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -73,6 +85,28 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="tab-content" id="myTabContent">
|
||||||
|
<div class="tab-pane fade show active" id="home" role="tabpanel" aria-labelledby="home-tab">
|
||||||
|
<div class="card-header text-center" style="background-color: white;">
|
||||||
|
<label class="date-heading text-center">Flexi Hour Latest Update: {{ flexiHourUpdateDate || 'N/A' }}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="tab-content" id="myTabContent">
|
||||||
|
<div class="tab-pane fade show active" id="home" role="tabpanel" aria-labelledby="home-tab">
|
||||||
|
<div class="card-header text-center" style="background-color: white;">
|
||||||
|
<label class="date-heading text-center">Region Latest Update: {{ regionUpdateDate || 'N/A' }}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -90,6 +124,8 @@
|
|||||||
return {
|
return {
|
||||||
rateUpdateDate: null,
|
rateUpdateDate: null,
|
||||||
calendarUpdateDate: null,
|
calendarUpdateDate: null,
|
||||||
|
flexiHourUpdateDate: null,
|
||||||
|
regionUpdateDate: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@ -106,6 +142,8 @@
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
this.rateUpdateDate = data.rateUpdateDate;
|
this.rateUpdateDate = data.rateUpdateDate;
|
||||||
this.calendarUpdateDate = data.calendarUpdateDate;
|
this.calendarUpdateDate = data.calendarUpdateDate;
|
||||||
|
this.flexiHourUpdateDate = data.flexiHourUpdateDate;
|
||||||
|
this.regionUpdateDate = data.regionUpdateDate;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,31 +9,29 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-7">
|
<div class="col-md-7">
|
||||||
|
<!-- Overtime Date Input -->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label" for="dateInput">Date</label>
|
<label class="form-label" for="dateInput">Date</label>
|
||||||
<input type="date" class="form-control" v-model="editForm.otDate"
|
<input type="date" class="form-control" v-model="editForm.otDate" v-on:input="calculateOTAndBreak">
|
||||||
v-on:input="calculateOTAndBreak">
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- OFFICE HOURS -->
|
<!-- Office Hours Section -->
|
||||||
<h6 class="fw-bold">OFFICE HOURS</h6>
|
<h6 class="fw-bold">OFFICE HOURS</h6>
|
||||||
<div class="d-flex gap-3 mb-3 align-items-end flex-wrap">
|
<div class="d-flex gap-3 mb-3 align-items-end flex-wrap">
|
||||||
<div style="flex: 1;">
|
<div style="flex: 1;">
|
||||||
<label for="officeFrom">From</label>
|
<label for="officeFrom">From</label>
|
||||||
<input type="time" class="form-control" v-model="editForm.officeFrom"
|
<input type="time" id="officeFrom" class="form-control" v-model="editForm.officeFrom" v-on:change="updateTime('officeFrom')">
|
||||||
v-on:input="calculateOTAndBreak">
|
|
||||||
</div>
|
</div>
|
||||||
<div style="flex: 1;">
|
<div style="flex: 1;">
|
||||||
<label for="officeTo">To</label>
|
<label for="officeTo">To</label>
|
||||||
<input type="time" id="officeTo" class="form-control" v-model="editForm.officeTo"
|
<input type="time" id="officeTo" class="form-control" v-model="editForm.officeTo" v-on:change="updateTime('officeTo')">
|
||||||
v-on:input="calculateOTAndBreak">
|
|
||||||
</div>
|
</div>
|
||||||
<div style="flex: 1;">
|
<div style="flex: 1;">
|
||||||
<label for="officeBreak">Break Hours (Minutes)</label>
|
<label for="officeBreak">Break Hours (Minutes)</label>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<input type="number" id="officeBreak" class="form-control"
|
<select id="officeBreak" class="form-control" v-model.number="editForm.officeBreak" v-on:change="calculateOTAndBreak">
|
||||||
v-model="editForm.officeBreak"
|
<option v-for="opt in breakOptions" :key="opt.value" :value="opt.value">{{ opt.label }}</option>
|
||||||
v-on:input="calculateOTAndBreak" placeholder="e.g. 30">
|
</select>
|
||||||
<button class="btn btn-outline-danger ms-2" v-on:click="clearOfficeHours" title="Clear Office Hours">
|
<button class="btn btn-outline-danger ms-2" v-on:click="clearOfficeHours" title="Clear Office Hours">
|
||||||
<i class="bi bi-x-circle"></i>
|
<i class="bi bi-x-circle"></i>
|
||||||
</button>
|
</button>
|
||||||
@ -41,25 +39,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- AFTER OFFICE HOURS -->
|
<!-- After Office Hours Section -->
|
||||||
<h6 class="fw-bold text-danger">AFTER OFFICE HOURS</h6>
|
<h6 class="fw-bold text-danger">AFTER OFFICE HOURS</h6>
|
||||||
<div class="d-flex gap-3 mb-3 align-items-end flex-wrap">
|
<div class="d-flex gap-3 mb-3 align-items-end flex-wrap">
|
||||||
<div style="flex: 1;">
|
<div style="flex: 1;">
|
||||||
<label for="afterFrom">From</label>
|
<label for="afterFrom">From</label>
|
||||||
<input type="time" id="afterFrom" class="form-control" v-model="editForm.afterFrom"
|
<input type="time" id="afterFrom" class="form-control" v-model="editForm.afterFrom" v-on:change="updateTime('afterFrom')">
|
||||||
v-on:input="calculateOTAndBreak">
|
|
||||||
</div>
|
</div>
|
||||||
<div style="flex: 1;">
|
<div style="flex: 1;">
|
||||||
<label for="afterTo">To</label>
|
<label for="afterTo">To</label>
|
||||||
<input type="time" id="afterTo" class="form-control" v-model="editForm.afterTo"
|
<input type="time" id="afterTo" class="form-control" v-model="editForm.afterTo" v-on:change="updateTime('afterTo')">
|
||||||
v-on:input="calculateOTAndBreak">
|
|
||||||
</div>
|
</div>
|
||||||
<div style="flex: 1;">
|
<div style="flex: 1;">
|
||||||
<label for="afterBreak">Break Hours (Minutes)</label>
|
<label for="afterBreak">Break Hours (Minutes)</label>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<input type="number" id="afterBreak" class="form-control"
|
<select id="afterBreak" class="form-control" v-model.number="editForm.afterBreak" v-on:change="calculateOTAndBreak">
|
||||||
v-model="editForm.afterBreak"
|
<option v-for="opt in breakOptions" :key="opt.value" :value="opt.value">{{ opt.label }}</option>
|
||||||
v-on:input="calculateOTAndBreak" placeholder="e.g. 45">
|
</select>
|
||||||
<button class="btn btn-outline-danger ms-2" v-on:click="clearAfterHours" title="Clear After Office Hours">
|
<button class="btn btn-outline-danger ms-2" v-on:click="clearAfterHours" title="Clear After Office Hours">
|
||||||
<i class="bi bi-x-circle"></i>
|
<i class="bi bi-x-circle"></i>
|
||||||
</button>
|
</button>
|
||||||
@ -67,7 +63,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Air Station Dropdown (only for PSTW AIR) -->
|
||||||
<div class="mb-3" v-if="isPSTWAIR">
|
<div class="mb-3" v-if="isPSTWAIR">
|
||||||
<label for="airstationDropdown">Air Station</label>
|
<label for="airstationDropdown">Air Station</label>
|
||||||
<select id="airstationDropdown" class="form-control" v-model="editForm.stationId">
|
<select id="airstationDropdown" class="form-control" v-model="editForm.stationId">
|
||||||
@ -79,54 +75,34 @@
|
|||||||
<small class="text-danger">*Only for PSTW AIR</small>
|
<small class="text-danger">*Only for PSTW AIR</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Work Description Input -->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="otDescription">Work Brief Description</label>
|
<label for="otDescription">Work Brief Description</label>
|
||||||
<textarea id="otDescription" class="form-control"
|
<textarea id="otDescription" class="form-control" v-model="editForm.otDescription" v-on:input="limitCharCount" placeholder="Describe the work done..."></textarea>
|
||||||
v-model="editForm.otDescription"
|
<small class="text-muted">{{ charCount }} / 150 characters</small>
|
||||||
v-on:input="limitCharCount"
|
</div>
|
||||||
placeholder="Describe the work done..."></textarea>
|
|
||||||
<small class="text-muted">
|
|
||||||
{{ charCount }} / 150 characters
|
|
||||||
</small>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
<!-- Overtime Hours and Break Section -->
|
||||||
|
<div class="col-md-5 mt-5">
|
||||||
<div class="col-md-5">
|
<div class="mb-3 d-flex flex-column align-items-center">
|
||||||
<label class="mb-2">Day</label>
|
<label for="detectedDayType">Day</label>
|
||||||
|
<input type="text" class="form-control text-center" v-model="editForm.otDays" readonly style="width: 200px;">
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="radio" id="weekday" v-model="editForm.selectedDayType" value="Weekday">
|
|
||||||
<label class="form-check-label" for="weekday">Weekday</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="radio" id="weekend" v-model="editForm.selectedDayType" value="Weekend">
|
|
||||||
<label class="form-check-label" for="weekend">Weekend</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="radio" id="publicHoliday" v-model="editForm.selectedDayType"
|
|
||||||
value="Public Holiday">
|
|
||||||
<label class="form-check-label" for="publicHoliday">Public Holiday</label>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3 d-flex flex-column align-items-center">
|
<div class="mb-3 d-flex flex-column align-items-center">
|
||||||
<label for="totalOTHours">Total OT Hours</label>
|
<label for="totalOTHours">Total OT Hours</label>
|
||||||
<input type="text" id="totalOTHours" class="form-control text-center" v-model="totalOTHours"
|
<input type="text" id="totalOTHours" class="form-control text-center" v-model="totalOTHours" style="width: 200px;" readonly>
|
||||||
style="width: 200px;" readonly>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3 d-flex flex-column align-items-center">
|
<div class="mb-3 d-flex flex-column align-items-center">
|
||||||
<label for="totalBreakHours">Total Break Hours</label>
|
<label for="totalBreakHours">Total Break Hours</label>
|
||||||
<input type="text" id="totalBreakHours" class="form-control text-center" v-model="totalBreakHours"
|
<input type="text" id="totalBreakHours" class="form-control text-center" v-model="totalBreakHours" style="width: 200px;" readonly>
|
||||||
style="width: 200px;" readonly>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Action Buttons -->
|
||||||
<div class="d-flex justify-content-end mt-3">
|
<div class="d-flex justify-content-end mt-3">
|
||||||
<button class="btn btn-danger" v-on:click="goBack">Cancel</button>
|
<button class="btn btn-danger" v-on:click="goBack">Cancel</button>
|
||||||
<button class="btn btn-success ms-3" v-on:click="updateRecord">Update</button>
|
<button class="btn btn-success ms-3" v-on:click="updateRecord">Update</button>
|
||||||
@ -135,6 +111,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@section Scripts {
|
@section Scripts {
|
||||||
@{
|
@{
|
||||||
await Html.RenderPartialAsync("_ValidationScriptsPartial");
|
await Html.RenderPartialAsync("_ValidationScriptsPartial");
|
||||||
@ -162,6 +139,20 @@
|
|||||||
totalBreakHours: "0 hr 0 min",
|
totalBreakHours: "0 hr 0 min",
|
||||||
currentUser: null,
|
currentUser: null,
|
||||||
isPSTWAIR: false,
|
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;
|
return this.editForm.otDescription.length;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
const overtimeId = urlParams.get('overtimeId');
|
const overtimeId = urlParams.get('overtimeId');
|
||||||
@ -177,19 +169,19 @@
|
|||||||
await this.fetchOvertimeRecord(overtimeId);
|
await this.fetchOvertimeRecord(overtimeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.fetchUser();
|
await this.fetchUserAndRelatedData(); // Fetch user, state, and holidays
|
||||||
|
|
||||||
if (this.isPSTWAIR) {
|
if (this.isPSTWAIR) {
|
||||||
await this.fetchStations();
|
await this.fetchStations();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async fetchOvertimeRecord(id) {
|
async fetchOvertimeRecord(id) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/OvertimeAPI/GetOvertimeRecordById/${id}`);
|
const res = await fetch(`/OvertimeAPI/GetOvertimeRecordById/${id}`);
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
this.populateForm(data); // Fill form fields with data
|
this.populateForm(data);
|
||||||
} else {
|
} else {
|
||||||
alert("Failed to fetch overtime record.");
|
alert("Failed to fetch overtime record.");
|
||||||
}
|
}
|
||||||
@ -197,39 +189,42 @@
|
|||||||
console.error("Fetch error:", err);
|
console.error("Fetch error:", err);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
populateForm(record) {
|
populateForm(record) {
|
||||||
this.editForm = {
|
this.editForm = {
|
||||||
...record,
|
...record,
|
||||||
otDate: record.otDate.slice(0, 10),
|
otDate: record.otDate ? record.otDate.slice(0, 10) : "",
|
||||||
selectedDayType: record.otDays,
|
// We will auto-detect the day, so we don't need to pre-fill otDays for auto-detection
|
||||||
};
|
};
|
||||||
|
|
||||||
this.calculateOTAndBreak();
|
this.calculateOTAndBreak();
|
||||||
|
this.updateDayType(); // Initial detection after loading data
|
||||||
},
|
},
|
||||||
|
|
||||||
async fetchStations() {
|
async fetchStations() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/OvertimeAPI/GetStationsByDepartment`);
|
const response = await fetch(`/OvertimeAPI/GetStationsByDepartment`);
|
||||||
if (!response.ok) throw new Error("Failed to fetch stations");
|
if (!response.ok) throw new Error("Failed to fetch stations");
|
||||||
|
|
||||||
this.airstationList = await response.json();
|
this.airstationList = await response.json();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching stations:", error);
|
console.error("Error fetching stations:", error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async fetchUser() {
|
|
||||||
|
async fetchUserAndRelatedData() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/IdentityAPI/GetUserInformation/`, { method: 'POST' });
|
const response = await fetch(`/IdentityAPI/GetUserInformation/`, { method: 'POST' });
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
this.currentUser = data?.userInfo || null;
|
this.currentUser = data?.userInfo || null;
|
||||||
this.userId = this.currentUser?.id || null;
|
this.editForm.userId = this.currentUser?.id || null;
|
||||||
|
|
||||||
console.log("Fetched User:", this.currentUser);
|
const isSuperAdmin = this.currentUser?.role?.includes("SuperAdmin");
|
||||||
console.log("Dept ID:", this.currentUser?.departmentId);
|
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) {
|
if (this.editForm.userId) {
|
||||||
this.isPSTWAIR = true;
|
await this.fetchUserStateAndHolidays();
|
||||||
console.log("User is PSTW AIR");
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error(`Failed to fetch user: ${response.statusText}`);
|
console.error(`Failed to fetch user: ${response.statusText}`);
|
||||||
@ -238,12 +233,30 @@
|
|||||||
console.error("Error fetching user:", error);
|
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) {
|
limitCharCount(event) {
|
||||||
if (this.editForm.otDescription && this.editForm.otDescription.length > 150) {
|
if (this.editForm.otDescription && this.editForm.otDescription.length > 150) {
|
||||||
this.editForm.otDescription = this.editForm.otDescription.substring(0, 150);
|
this.editForm.otDescription = this.editForm.otDescription.substring(0, 150);
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
calculateOTAndBreak() {
|
calculateOTAndBreak() {
|
||||||
let officeOT = this.calculateTimeDifference(
|
let officeOT = this.calculateTimeDifference(
|
||||||
this.editForm.officeFrom,
|
this.editForm.officeFrom,
|
||||||
@ -268,36 +281,97 @@
|
|||||||
totalBreakMinutes = totalBreakMinutes % 60;
|
totalBreakMinutes = totalBreakMinutes % 60;
|
||||||
|
|
||||||
this.totalBreakHours = `${totalBreakHours} hr ${totalBreakMinutes} min`;
|
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) {
|
calculateTimeDifference(startTime, endTime, breakMinutes) {
|
||||||
if (!startTime || !endTime) {
|
if (!startTime || !endTime) {
|
||||||
return { hours: 0, minutes: 0 };
|
return { hours: 0, minutes: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
const start = this.parseTime(startTime);
|
const start = this.parseTime(startTime);
|
||||||
const end = this.parseTime(endTime);
|
const end = this.parseTime(endTime);
|
||||||
|
|
||||||
let diffMinutes = (end.hours * 60 + end.minutes) - (start.hours * 60 + start.minutes);
|
let diffMinutes = (end.hours * 60 + end.minutes) - (start.hours * 60 + start.minutes);
|
||||||
if (diffMinutes < 0) {
|
if (diffMinutes < 0) {
|
||||||
diffMinutes += 24 * 60;
|
diffMinutes += 24 * 60;
|
||||||
}
|
}
|
||||||
|
|
||||||
diffMinutes -= breakMinutes || 0;
|
diffMinutes -= breakMinutes || 0;
|
||||||
|
|
||||||
const hours = Math.floor(diffMinutes / 60);
|
const hours = Math.floor(diffMinutes / 60);
|
||||||
const minutes = diffMinutes % 60;
|
const minutes = diffMinutes % 60;
|
||||||
|
|
||||||
return { hours, minutes };
|
return { hours, minutes };
|
||||||
},
|
},
|
||||||
|
|
||||||
parseTime(timeString) {
|
parseTime(timeString) {
|
||||||
const [hours, minutes] = timeString.split(':').map(Number);
|
const [hours, minutes] = timeString.split(':').map(Number);
|
||||||
return { hours, minutes };
|
return { hours, minutes };
|
||||||
},
|
},
|
||||||
|
|
||||||
formatTime(timeString) {
|
formatTime(timeString) {
|
||||||
if (!timeString) return null;
|
if (!timeString) return null;
|
||||||
const [hours, minutes] = timeString.split(':');
|
const [hours, minutes] = timeString.split(':');
|
||||||
return `${hours.padStart(2, '0')}:${minutes.padStart(2, '0')}:00`; //HH:mm:ss format
|
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() {
|
async updateRecord() {
|
||||||
if (this.editForm.officeFrom && !this.editForm.officeTo) {
|
if (this.editForm.officeFrom && !this.editForm.officeTo) {
|
||||||
alert("Please enter a 'To' time for Office Hours.");
|
alert("Please enter a 'To' time for Office Hours.");
|
||||||
@ -338,32 +412,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("OvertimeId", this.editForm.overtimeId);
|
formData.append("OvertimeId", this.editForm.overtimeId);
|
||||||
formData.append("OtDate", this.editForm.otDate);
|
formData.append("OtDate", this.editForm.otDate);
|
||||||
formData.append("StationId", this.editForm.stationId || "");
|
formData.append("StationId", this.editForm.stationId || "");
|
||||||
formData.append("OtDescription", this.editForm.otDescription || "");
|
formData.append("OtDescription", this.editForm.otDescription || "");
|
||||||
formData.append("OtDays", this.editForm.selectedDayType);
|
formData.append("OtDays", this.editForm.otDays); // Use the auto-detected day type
|
||||||
formData.append("OfficeFrom", this.formatTime(this.editForm.officeFrom) || "");
|
formData.append("OfficeFrom", this.formatTime(this.editForm.officeFrom) || "");
|
||||||
formData.append("OfficeTo", this.formatTime(this.editForm.officeTo) || "");
|
formData.append("OfficeTo", this.formatTime(this.editForm.officeTo) || "");
|
||||||
formData.append("After Office From", this.formatTime(this.editForm.afterFrom) || "");
|
formData.append("AfterFrom", this.formatTime(this.editForm.afterFrom) || "");
|
||||||
formData.append("After Office To", this.formatTime(this.editForm.afterTo) || "");
|
formData.append("AfterTo", this.formatTime(this.editForm.afterTo) || "");
|
||||||
|
|
||||||
formData.append("officeBreak", this.editForm.officeBreak || 0);
|
formData.append("officeBreak", this.editForm.officeBreak || 0);
|
||||||
formData.append("afterBreak", this.editForm.afterBreak || 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);
|
formData.append("userId", this.currentUser?.id);
|
||||||
|
|
||||||
|
try {
|
||||||
const response = await fetch(`/OvertimeAPI/UpdateOvertimeRecord`, {
|
const response = await fetch(`/OvertimeAPI/UpdateOvertimeRecord`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData
|
body: formData
|
||||||
@ -371,7 +434,6 @@
|
|||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
alert("Overtime record updated successfully!");
|
alert("Overtime record updated successfully!");
|
||||||
|
|
||||||
window.location.href = '/OTcalculate/Overtime/OtRecords';
|
window.location.href = '/OTcalculate/Overtime/OtRecords';
|
||||||
} else {
|
} else {
|
||||||
alert("Failed to update overtime record.");
|
alert("Failed to update overtime record.");
|
||||||
|
|||||||
@ -111,10 +111,10 @@
|
|||||||
<td>{{ formatDate(record.otDate) }}</td>
|
<td>{{ formatDate(record.otDate) }}</td>
|
||||||
<td>{{ formatTime(record.officeFrom) }}</td>
|
<td>{{ formatTime(record.officeFrom) }}</td>
|
||||||
<td>{{ formatTime(record.officeTo) }}</td>
|
<td>{{ formatTime(record.officeTo) }}</td>
|
||||||
<td>{{ record.officeBreak }} min</td>
|
<td>{{ record.officeBreak }}</td>
|
||||||
<td>{{ formatTime(record.afterFrom) }}</td>
|
<td>{{ formatTime(record.afterFrom) }}</td>
|
||||||
<td>{{ formatTime(record.afterTo) }}</td>
|
<td>{{ formatTime(record.afterTo) }}</td>
|
||||||
<td>{{ record.afterBreak }} min</td>
|
<td>{{ record.afterBreak }}</td>
|
||||||
<td>{{ formatHourMinute(calcTotalTime(record)) }}</td>
|
<td>{{ formatHourMinute(calcTotalTime(record)) }}</td>
|
||||||
<td>{{ calcBreakTotal(record) }}</td>
|
<td>{{ calcBreakTotal(record) }}</td>
|
||||||
<td>{{ formatHourMinute(calcNetHours(record)) }}</td>
|
<td>{{ formatHourMinute(calcNetHours(record)) }}</td>
|
||||||
@ -126,13 +126,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="btn btn-light border rounded-circle me-1" title="Edit" v-on:click ="editRecord(index)">
|
<button class="btn btn-light border rounded-circle me-1"
|
||||||
|
title="Edit"
|
||||||
|
:disabled="hasSubmitted"
|
||||||
|
v-on:click="editRecord(index)">
|
||||||
<i class="bi bi-pencil-fill text-warning fs-5"></i>
|
<i class="bi bi-pencil-fill text-warning fs-5"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-light border rounded-circle" title="Delete" v-on:click="deleteRecord(index)">
|
<button class="btn btn-light border rounded-circle"
|
||||||
|
title="Delete"
|
||||||
|
:disabled="hasSubmitted"
|
||||||
|
v-on:click="deleteRecord(index)">
|
||||||
<i class="bi bi-trash-fill text-danger fs-5"></i>
|
<i class="bi bi-trash-fill text-danger fs-5"></i>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="filteredRecords.length === 0">
|
<tr v-if="filteredRecords.length === 0">
|
||||||
<td :colspan="isPSTWAIR ? 14 : 13">No records found.</td>
|
<td :colspan="isPSTWAIR ? 14 : 13">No records found.</td>
|
||||||
@ -152,9 +159,26 @@
|
|||||||
|
|
||||||
<div class="mt-3 d-flex flex-wrap gap-2">
|
<div class="mt-3 d-flex flex-wrap gap-2">
|
||||||
<button class="btn btn-primary btn-sm" v-on:click="printPdf"><i class="bi bi-printer"></i> Print</button>
|
<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"><i class="bi bi-download"></i> Save</button>
|
<button class="btn btn-dark btn-sm" v-on:click="downloadPdf"><i class="bi bi-file-pdf"></i> Save</button>
|
||||||
<button class="btn btn-success btn-sm" v-on:click=""><i class="bi bi-send"></i> Submit
|
<button class="btn btn-success btn-sm" v-on:click="downloadExcel(selectedMonth, selectedYear)"><i class="bi bi-file-earmark-excel"></i>Excel</button>
|
||||||
</button>
|
<button class="btn btn-success btn-sm" :disabled="hasSubmitted" v-on:click="openSubmitModal"><i class="bi bi-send"></i>{{ hasSubmitted ? 'Submitted' : 'Submit' }}</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal fade" id="submitModal" tabindex="-1" aria-labelledby="submitModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="submitModalLabel">Submit Overtime</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<input type="file" class="form-control" v-on:change ="handleFileUpload">
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<button type="button" class="btn btn-primary" v-on:click ="submitOvertime">Submit</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -162,6 +186,8 @@
|
|||||||
@section Scripts {
|
@section Scripts {
|
||||||
<script src="https://cdn.jsdelivr.net/npm/vue@3"></script>
|
<script src="https://cdn.jsdelivr.net/npm/vue@3"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const app = Vue.createApp({
|
const app = Vue.createApp({
|
||||||
data() {
|
data() {
|
||||||
@ -176,8 +202,18 @@
|
|||||||
months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
|
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: {},
|
expandedDescriptions: {},
|
||||||
|
selectedFile: null,
|
||||||
|
hasSubmitted: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
selectedMonth() {
|
||||||
|
this.checkSubmissionStatus();
|
||||||
|
},
|
||||||
|
selectedYear() {
|
||||||
|
this.checkSubmissionStatus();
|
||||||
|
}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
filteredRecords() {
|
filteredRecords() {
|
||||||
return this.otRecords
|
return this.otRecords
|
||||||
@ -207,6 +243,7 @@
|
|||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
await this.initUserAndRecords();
|
await this.initUserAndRecords();
|
||||||
|
await this.checkSubmissionStatus();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async initUserAndRecords() {
|
async initUserAndRecords() {
|
||||||
@ -217,16 +254,32 @@
|
|||||||
},
|
},
|
||||||
async fetchUser() {
|
async fetchUser() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/IdentityAPI/GetUserInformation', { method: 'POST' });
|
const response = await fetch(`/IdentityAPI/GetUserInformation/`, { method: 'POST' });
|
||||||
const data = await res.json();
|
if (response.ok) {
|
||||||
const department = data.userInfo?.department;
|
const data = await response.json();
|
||||||
this.userId = data.userInfo?.id;
|
this.currentUser = data?.userInfo || null;
|
||||||
const deptName = department?.departmentName?.toUpperCase?.() || '';
|
this.userId = this.currentUser?.id || null;
|
||||||
this.isPSTWAIR = deptName.includes("PSTW") && deptName.includes("AIR") && department?.departmentId === 2;
|
|
||||||
} catch (err) {
|
console.log("Fetched User:", this.currentUser);
|
||||||
console.error("User fetch error:", err);
|
console.log("Dept ID:", this.currentUser?.department?.departmentId);
|
||||||
|
console.log("Roles:", this.currentUser?.role);
|
||||||
|
|
||||||
|
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;
|
||||||
|
console.log("isPSTWAIR:", this.isPSTWAIR);
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
console.error(`Failed to fetch user: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching user:", error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async fetchOtRecords() {
|
async fetchOtRecords() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/OvertimeAPI/GetUserOvertimeRecords/${this.userId}`);
|
const res = await fetch(`/OvertimeAPI/GetUserOvertimeRecords/${this.userId}`);
|
||||||
@ -235,6 +288,14 @@
|
|||||||
console.error("Records fetch error:", err);
|
console.error("Records fetch error:", err);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async checkSubmissionStatus() {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/OvertimeAPI/CheckOvertimeSubmitted/${this.userId}/${this.selectedMonth}/${this.selectedYear}`);
|
||||||
|
this.hasSubmitted = await res.json();
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to check submission status", err);
|
||||||
|
}
|
||||||
|
},
|
||||||
toggleDescription(index) {
|
toggleDescription(index) {
|
||||||
this.expandedDescriptions[index] = !this.expandedDescriptions[index];
|
this.expandedDescriptions[index] = !this.expandedDescriptions[index];
|
||||||
},
|
},
|
||||||
@ -243,7 +304,9 @@
|
|||||||
return new Date(d).toLocaleDateString();
|
return new Date(d).toLocaleDateString();
|
||||||
},
|
},
|
||||||
formatTime(t) {
|
formatTime(t) {
|
||||||
return t ? t.slice(0, 5) : "-";
|
if (!t) return "-";
|
||||||
|
const [hours, minutes] = t.split(':').map(Number);
|
||||||
|
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
|
||||||
},
|
},
|
||||||
getTimeDiff(from, to) {
|
getTimeDiff(from, to) {
|
||||||
if (!from || !to) return 0;
|
if (!from || !to) return 0;
|
||||||
@ -272,7 +335,10 @@
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
formatHourMinute(timeObj) {
|
formatHourMinute(timeObj) {
|
||||||
return timeObj ? `${timeObj.hours} h ${timeObj.minutes} m` : '-';
|
if (!timeObj) return "-";
|
||||||
|
const hours = timeObj.hours.toString().padStart(2, '0');
|
||||||
|
const minutes = timeObj.minutes.toString().padStart(2, '0');
|
||||||
|
return `${hours}:${minutes}`;
|
||||||
},
|
},
|
||||||
editRecord(index) {
|
editRecord(index) {
|
||||||
const record = this.filteredRecords[index];
|
const record = this.filteredRecords[index];
|
||||||
@ -290,26 +356,19 @@
|
|||||||
alert("Error deleting record.");
|
alert("Error deleting record.");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
printPdf() {
|
async printPdf() {
|
||||||
const today = new Date();
|
try {
|
||||||
const month = this.selectedMonth;
|
const response = await fetch(`/OvertimeAPI/GenerateOvertimePdf?month=${this.selectedMonth}&year=${this.selectedYear}`);
|
||||||
const year = this.selectedYear;
|
const blob = await response.blob();
|
||||||
|
|
||||||
fetch(`/OvertimeAPI/GenerateOvertimePdf?month=${month}&year=${year}`)
|
|
||||||
.then(response => response.blob())
|
|
||||||
.then(blob => {
|
|
||||||
const blobUrl = URL.createObjectURL(blob);
|
const blobUrl = URL.createObjectURL(blob);
|
||||||
const printWindow = window.open(blobUrl, '_blank');
|
const printWindow = window.open(blobUrl, '_blank');
|
||||||
|
|
||||||
// Trigger print after window loads
|
|
||||||
printWindow.onload = () => {
|
printWindow.onload = () => {
|
||||||
printWindow.focus();
|
printWindow.focus();
|
||||||
printWindow.print();
|
printWindow.print();
|
||||||
};
|
};
|
||||||
})
|
} catch (error) {
|
||||||
.catch(error => {
|
|
||||||
console.error("Error generating PDF:", error);
|
console.error("Error generating PDF:", error);
|
||||||
});
|
}
|
||||||
},
|
},
|
||||||
async downloadPdf() {
|
async downloadPdf() {
|
||||||
try {
|
try {
|
||||||
@ -330,6 +389,51 @@
|
|||||||
alert("An error occurred while generating the PDF.");
|
alert("An error occurred while generating the PDF.");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
downloadExcel(month, year) {
|
||||||
|
window.open(`/OvertimeAPI/GenerateOvertimeExcel?month=${month}&year=${year}`, '_blank');
|
||||||
|
},
|
||||||
|
openSubmitModal() {
|
||||||
|
const modal = new bootstrap.Modal(document.getElementById('submitModal'));
|
||||||
|
modal.show();
|
||||||
|
},
|
||||||
|
handleFileUpload(event) {
|
||||||
|
this.selectedFile = event.target.files[0];
|
||||||
|
},
|
||||||
|
async submitOvertime() {
|
||||||
|
if (!this.selectedFile) {
|
||||||
|
alert("Please select a file to upload.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('Month', this.selectedMonth);
|
||||||
|
formData.append('Year', this.selectedYear);
|
||||||
|
formData.append('File', this.selectedFile);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await axios.post('/OvertimeAPI/SubmitOvertime', formData, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status === 200) {
|
||||||
|
alert('Overtime submitted successfully!');
|
||||||
|
|
||||||
|
const modalEl = document.getElementById('submitModal');
|
||||||
|
const modalInstance = bootstrap.Modal.getInstance(modalEl);
|
||||||
|
modalInstance.hide();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
alert('Submission failed.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
alert('An error occurred during submission.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
app.mount("#app");
|
app.mount("#app");
|
||||||
|
|||||||
@ -13,7 +13,8 @@
|
|||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label" for="dateInput">Date</label>
|
<label class="form-label" for="dateInput">Date</label>
|
||||||
<input type="date" id="dateInput" class="form-control" v-model="selectedDate"
|
<input type="date" id="dateInput" class="form-control" v-model="selectedDate"
|
||||||
v-on:input="calculateOTAndBreak">
|
v-on:input="handleDateChange">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h6 class="fw-bold">OFFICE HOURS</h6>
|
<h6 class="fw-bold">OFFICE HOURS</h6>
|
||||||
@ -21,17 +22,22 @@
|
|||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
<label for="officeFrom">From</label>
|
<label for="officeFrom">From</label>
|
||||||
<input type="time" id="officeFrom" class="form-control" v-model="officeFrom"
|
<input type="time" id="officeFrom" class="form-control" v-model="officeFrom"
|
||||||
v-on:input="calculateOTAndBreak">
|
v-on:change ="officeFrom = roundToNearest30(officeFrom); calculateOTAndBreak()">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
<label for="officeTo">To</label>
|
<label for="officeTo">To</label>
|
||||||
<input type="time" id="officeTo" class="form-control" v-model="officeTo"
|
<input type="time" id="officeTo" class="form-control" v-model="officeTo"
|
||||||
v-on:input="calculateOTAndBreak">
|
v-on:change ="officeTo = roundToNearest30(officeTo); calculateOTAndBreak()">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
<label for="officeBreak">Break Hours (Minutes)</label>
|
<label for="officeBreak">Break Hours (Minutes)</label>
|
||||||
<input type="number" id="officeBreak" class="form-control" v-model="officeBreak"
|
<select id="officeBreak" class="form-control" v-model.number="officeBreak" v-on:change ="calculateOTAndBreak">
|
||||||
v-on:input="calculateOTAndBreak" placeholder="e.g. 30">
|
<option v-for="opt in breakOptions" :key="opt.value" :value="opt.value">
|
||||||
|
{{ opt.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -40,17 +46,20 @@
|
|||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
<label for="afterFrom">From</label>
|
<label for="afterFrom">From</label>
|
||||||
<input type="time" id="afterFrom" class="form-control" v-model="afterFrom"
|
<input type="time" id="afterFrom" class="form-control" v-model="afterFrom"
|
||||||
v-on:input="calculateOTAndBreak">
|
v-on:change ="afterFrom = roundToNearest30(afterFrom); calculateOTAndBreak()">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
<label for="afterTo">To</label>
|
<label for="afterTo">To</label>
|
||||||
<input type="time" id="afterTo" class="form-control" v-model="afterTo"
|
<input type="time" id="afterTo" class="form-control" v-model="afterTo"
|
||||||
v-on:input="calculateOTAndBreak">
|
v-on:change ="afterTo = roundToNearest30(afterTo); calculateOTAndBreak()">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
<label for="afterBreak">Break Hours (Minutes)</label>
|
<label for="afterBreak">Break Hours (Minutes)</label>
|
||||||
<input type="number" id="afterBreak" class="form-control" v-model="afterBreak"
|
<select id="afterBreak" class="form-control" v-model.number="afterBreak" v-on:change ="calculateOTAndBreak">
|
||||||
v-on:input="calculateOTAndBreak" placeholder="e.g. 45">
|
<option v-for="opt in breakOptions" :key="opt.value" :value="opt.value">
|
||||||
|
{{ opt.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -79,23 +88,11 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-5">
|
<div class="col-md-5 mt-5">
|
||||||
<label class="mb-2">Day</label>
|
<div class="mb-3 d-flex flex-column align-items-center">
|
||||||
|
<label for="detectedDayType">Day</label>
|
||||||
<div class="form-check">
|
<input type="text" class="form-control text-center" v-model="detectedDayType" readonly
|
||||||
<input class="form-check-input" type="radio" id="weekday" v-model="selectedDayType" value="Weekday">
|
style="width: 200px;">
|
||||||
<label class="form-check-label" for="weekday">Weekday</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="radio" id="weekend" v-model="selectedDayType" value="Weekend">
|
|
||||||
<label class="form-check-label" for="weekend">Weekend</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="radio" id="publicHoliday" v-model="selectedDayType"
|
|
||||||
value="Public Holiday">
|
|
||||||
<label class="form-check-label" for="publicHoliday">Public Holiday</label>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3 d-flex flex-column align-items-center">
|
<div class="mb-3 d-flex flex-column align-items-center">
|
||||||
@ -138,13 +135,26 @@
|
|||||||
selectedAirStation: "",
|
selectedAirStation: "",
|
||||||
airstationList: [],
|
airstationList: [],
|
||||||
otDescription: "",
|
otDescription: "",
|
||||||
selectedDayType: "",
|
detectedDayType: "", // To display the auto-detected day type
|
||||||
totalOTHours: "0 hr 0 min",
|
totalOTHours: "0 hr 0 min",
|
||||||
totalBreakHours: "0 hr 0 min",
|
totalBreakHours: "0 hr 0 min",
|
||||||
currentUser: null,
|
currentUser: null,
|
||||||
userId: null,
|
userId: null,
|
||||||
|
userState: null, // To store the user's state information
|
||||||
|
publicHolidays: [], // To store public holidays for the user's state and year
|
||||||
isPSTWAIR: false,
|
isPSTWAIR: false,
|
||||||
|
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 };
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -178,12 +188,25 @@
|
|||||||
this.userId = this.currentUser?.id || null;
|
this.userId = this.currentUser?.id || null;
|
||||||
|
|
||||||
console.log("Fetched User:", this.currentUser);
|
console.log("Fetched User:", this.currentUser);
|
||||||
console.log("Dept ID:", this.currentUser?.departmentId);
|
console.log("Dept ID:", this.currentUser?.department?.departmentId);
|
||||||
|
console.log("Roles:", this.currentUser?.role);
|
||||||
|
|
||||||
if (this.currentUser?.department?.departmentId === 2) {
|
const isSuperAdmin = this.currentUser?.role?.includes("SuperAdmin");
|
||||||
this.isPSTWAIR = true;
|
const isSystemAdmin = this.currentUser?.role?.includes("SystemAdmin");
|
||||||
console.log("User is PSTW AIR");
|
const isDepartmentTwo = this.currentUser?.department?.departmentId === 2;
|
||||||
|
|
||||||
|
this.isPSTWAIR = isSuperAdmin || isSystemAdmin || isDepartmentTwo;
|
||||||
|
console.log("isPSTWAIR:", this.isPSTWAIR);
|
||||||
|
|
||||||
|
if (this.isPSTWAIR) {
|
||||||
|
this.fetchStations();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch user's state and public holidays after fetching user info
|
||||||
|
if (this.userId) {
|
||||||
|
await this.fetchUserStateAndHolidays();
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
console.error(`Failed to fetch user: ${response.statusText}`);
|
console.error(`Failed to fetch user: ${response.statusText}`);
|
||||||
}
|
}
|
||||||
@ -191,6 +214,28 @@
|
|||||||
console.error("Error fetching user:", error);
|
console.error("Error fetching user:", error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async fetchUserStateAndHolidays() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/OvertimeAPI/GetUserStateAndHolidays/${this.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(); // Initial detection after loading data
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching user state and holidays:", error);
|
||||||
|
this.detectedDayType = "Weekday"; // Default if fetching fails
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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')}`;
|
||||||
|
},
|
||||||
limitCharCount(event) {
|
limitCharCount(event) {
|
||||||
if (this.otDescription.length > 150) {
|
if (this.otDescription.length > 150) {
|
||||||
this.otDescription = this.otDescription.substring(0, 150);
|
this.otDescription = this.otDescription.substring(0, 150);
|
||||||
@ -242,6 +287,10 @@
|
|||||||
const [hours, minutes] = timeString.split(':');
|
const [hours, minutes] = timeString.split(':');
|
||||||
return `${hours.padStart(2, '0')}:${minutes.padStart(2, '0')}:00`; // HH:mm:ss format
|
return `${hours.padStart(2, '0')}:${minutes.padStart(2, '0')}:00`; // HH:mm:ss format
|
||||||
},
|
},
|
||||||
|
handleDateChange() {
|
||||||
|
this.updateDayType();
|
||||||
|
this.calculateOTAndBreak();
|
||||||
|
},
|
||||||
async addOvertime() {
|
async addOvertime() {
|
||||||
if (this.isPSTWAIR && !this.selectedAirStation) {
|
if (this.isPSTWAIR && !this.selectedAirStation) {
|
||||||
alert("Please fill in all required fields.");
|
alert("Please fill in all required fields.");
|
||||||
@ -306,7 +355,7 @@
|
|||||||
afterBreak: this.afterBreak || null, // Make this optional
|
afterBreak: this.afterBreak || null, // Make this optional
|
||||||
stationId: this.isPSTWAIR ? parseInt(this.selectedAirStation) : null,
|
stationId: this.isPSTWAIR ? parseInt(this.selectedAirStation) : null,
|
||||||
otDescription: this.otDescription.trim().split(/\s+/).slice(0, 50).join(' '),
|
otDescription: this.otDescription.trim().split(/\s+/).slice(0, 50).join(' '),
|
||||||
otDays: this.selectedDayType,
|
otDays: this.detectedDayType, // Use the auto-detected day type
|
||||||
userId: this.userId
|
userId: this.userId
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -332,7 +381,48 @@
|
|||||||
alert("Failed to save overtime. Please check the console for errors.");
|
alert("Failed to save overtime. Please check the console for errors.");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
updateDayType() {
|
||||||
|
if (!this.selectedDate || !this.userState) {
|
||||||
|
this.detectedDayType = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force parsing at midnight local time
|
||||||
|
const selectedDateObj = new Date(this.selectedDate + "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')}`;
|
||||||
|
|
||||||
|
// 1. Check if it's a Public Holiday
|
||||||
|
if (this.publicHolidays.some(holiday => holiday.date === formattedDate)) {
|
||||||
|
this.detectedDayType = "Public Holiday";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Check if it's a Weekend according to user's weekendId
|
||||||
|
const weekendId = this.userState.weekendId;
|
||||||
|
const isWeekend = (() => {
|
||||||
|
if (weekendId === 1) {
|
||||||
|
// WeekendId 1: Friday and Saturday
|
||||||
|
return dayOfWeek === 5 || dayOfWeek === 6;
|
||||||
|
} else if (weekendId === 2) {
|
||||||
|
// WeekendId 2: Saturday and Sunday
|
||||||
|
return dayOfWeek === 6 || dayOfWeek === 0;
|
||||||
|
} else {
|
||||||
|
return dayOfWeek === 0; // Default Sunday
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (isWeekend) {
|
||||||
|
this.detectedDayType = "Weekend";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Otherwise, it's a normal Weekday
|
||||||
|
this.detectedDayType = "Weekday";
|
||||||
|
},
|
||||||
|
|
||||||
clearForm() {
|
clearForm() {
|
||||||
this.selectedDate = "";
|
this.selectedDate = "";
|
||||||
@ -344,7 +434,7 @@
|
|||||||
this.afterBreak = 0;
|
this.afterBreak = 0;
|
||||||
this.selectedAirStation = "";
|
this.selectedAirStation = "";
|
||||||
this.otDescription = "";
|
this.otDescription = "";
|
||||||
this.selectedDayType = "";
|
this.detectedDayType = "";
|
||||||
this.totalOTHours = "0 hr 0 min";
|
this.totalOTHours = "0 hr 0 min";
|
||||||
this.totalBreakHours = "0 hr 0 min";
|
this.totalBreakHours = "0 hr 0 min";
|
||||||
},
|
},
|
||||||
|
|||||||
@ -24,6 +24,7 @@ using QuestPDF.Fluent;
|
|||||||
using QuestPDF.Helpers;
|
using QuestPDF.Helpers;
|
||||||
using QuestPDF.Infrastructure;
|
using QuestPDF.Infrastructure;
|
||||||
using PSTW_CentralSystem.Areas.OTcalculate.Services;
|
using PSTW_CentralSystem.Areas.OTcalculate.Services;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Controllers.API
|
namespace PSTW_CentralSystem.Controllers.API
|
||||||
@ -37,14 +38,16 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
private readonly UserManager<UserModel> _userManager;
|
private readonly UserManager<UserModel> _userManager;
|
||||||
private readonly OvertimePdfService _pdfService;
|
private readonly OvertimePdfService _pdfService;
|
||||||
private readonly IWebHostEnvironment _env;
|
private readonly IWebHostEnvironment _env;
|
||||||
|
private readonly OvertimeExcelService _excelService;
|
||||||
|
|
||||||
public OvertimeAPI(ILogger<OvertimeAPI> logger, CentralSystemContext centralDbContext, UserManager<UserModel> userManager, OvertimePdfService pdfService, IWebHostEnvironment env)
|
public OvertimeAPI(ILogger<OvertimeAPI> logger, CentralSystemContext centralDbContext, UserManager<UserModel> userManager, OvertimePdfService pdfService, IWebHostEnvironment env, OvertimeExcelService excelService)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_centralDbContext = centralDbContext;
|
_centralDbContext = centralDbContext;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_pdfService = pdfService;
|
_pdfService = pdfService;
|
||||||
_env = env;
|
_env = env;
|
||||||
|
_excelService = excelService;
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Settings
|
#region Settings
|
||||||
@ -56,11 +59,15 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
|
|
||||||
var latestRateUpdate = _centralDbContext.Rates.OrderByDescending(r => r.LastUpdated).FirstOrDefault()?.LastUpdated;
|
var latestRateUpdate = _centralDbContext.Rates.OrderByDescending(r => r.LastUpdated).FirstOrDefault()?.LastUpdated;
|
||||||
var latestCalendarUpdate = _centralDbContext.Holidays.OrderByDescending(c => c.LastUpdated).FirstOrDefault()?.LastUpdated;
|
var latestCalendarUpdate = _centralDbContext.Holidays.OrderByDescending(c => c.LastUpdated).FirstOrDefault()?.LastUpdated;
|
||||||
|
var latestFlexiHourUpdate = _centralDbContext.Hrusersetting.OrderByDescending(r => r.FlexiHourUpdate).FirstOrDefault()?.FlexiHourUpdate;
|
||||||
|
var latestRegionUpdate = _centralDbContext.Hrusersetting.OrderByDescending(c => c.StateUpdate).FirstOrDefault()?.StateUpdate;
|
||||||
|
|
||||||
var updateDates = new
|
var updateDates = new
|
||||||
{
|
{
|
||||||
rateUpdateDate = latestRateUpdate.HasValue ? latestRateUpdate.Value.ToString("dd MMMM yyyy") : null,
|
rateUpdateDate = latestRateUpdate.HasValue ? latestRateUpdate.Value.ToString("dd MMMM yyyy") : null,
|
||||||
calendarUpdateDate = latestCalendarUpdate.HasValue ? latestCalendarUpdate.Value.ToString("dd MMMM yyyy") : null
|
calendarUpdateDate = latestCalendarUpdate.HasValue ? latestCalendarUpdate.Value.ToString("dd MMMM yyyy") : null,
|
||||||
|
flexiHourUpdateDate = latestFlexiHourUpdate.HasValue ? latestFlexiHourUpdate.Value.ToString("dd MMMM yyyy") : null,
|
||||||
|
regionUpdateDate = latestRegionUpdate.HasValue ? latestRegionUpdate.Value.ToString("dd MMMM yyyy") : null
|
||||||
};
|
};
|
||||||
|
|
||||||
return Json(updateDates);
|
return Json(updateDates);
|
||||||
@ -154,6 +161,179 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region FlexiHour State
|
||||||
|
private async Task UpdateOrInsertUserSettingAsync(int userId, int? flexiHourId = null, int? stateId = null)
|
||||||
|
{
|
||||||
|
var setting = await _centralDbContext.Hrusersetting
|
||||||
|
.FirstOrDefaultAsync(h => h.UserId == userId);
|
||||||
|
|
||||||
|
if (setting != null)
|
||||||
|
{
|
||||||
|
if (flexiHourId.HasValue)
|
||||||
|
{
|
||||||
|
setting.FlexiHourId = flexiHourId;
|
||||||
|
setting.FlexiHourUpdate = DateTime.Now;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stateId.HasValue)
|
||||||
|
{
|
||||||
|
setting.StateId = stateId;
|
||||||
|
setting.StateUpdate = DateTime.Now;
|
||||||
|
}
|
||||||
|
|
||||||
|
_centralDbContext.Hrusersetting.Update(setting);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var newSetting = new HrUserSettingModel
|
||||||
|
{
|
||||||
|
UserId = userId,
|
||||||
|
FlexiHourId = flexiHourId,
|
||||||
|
FlexiHourUpdate = flexiHourId.HasValue ? DateTime.Now : null,
|
||||||
|
StateId = stateId,
|
||||||
|
StateUpdate = stateId.HasValue ? DateTime.Now : null
|
||||||
|
};
|
||||||
|
_centralDbContext.Hrusersetting.Add(newSetting);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region FlexiHour
|
||||||
|
[HttpGet("GetFlexiHours")]
|
||||||
|
public IActionResult GetAllFlexiHours()
|
||||||
|
{
|
||||||
|
var flexiHours = _centralDbContext.Flexihour
|
||||||
|
.Select(f => new { f.FlexiHourId, f.FlexiHour })
|
||||||
|
.ToList();
|
||||||
|
return Ok(flexiHours);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("GetUserFlexiHours")]
|
||||||
|
public async Task<IActionResult> GetUserFlexiHours()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var users = await _centralDbContext.Users
|
||||||
|
.Include(u => u.Department)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var hrUserSettings = await _centralDbContext.Hrusersetting
|
||||||
|
.Include(hr => hr.FlexiHour)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var result = users.Select(u => new
|
||||||
|
{
|
||||||
|
UserId = u.Id,
|
||||||
|
FullName = u.FullName,
|
||||||
|
DepartmentName = u.Department != null ? u.Department.DepartmentName : "N/A",
|
||||||
|
FlexiHour = hrUserSettings
|
||||||
|
.Where(hr => hr.UserId == u.Id)
|
||||||
|
.Select(hr => hr.FlexiHour != null ? hr.FlexiHour.FlexiHour : "N/A")
|
||||||
|
.FirstOrDefault() ?? "N/A",
|
||||||
|
State = hrUserSettings
|
||||||
|
.Where(hr => hr.UserId == u.Id)
|
||||||
|
.Select(hr => hr.State != null ? hr.State.StateName : "N/A")
|
||||||
|
.FirstOrDefault() ?? "N/A"
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
// Log this data to inspect the response
|
||||||
|
Console.WriteLine(JsonConvert.SerializeObject(result)); // Debugging log
|
||||||
|
return Ok(result);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return BadRequest(new { message = ex.Message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[HttpPost("UpdateUserFlexiHours")]
|
||||||
|
public async Task<IActionResult> UpdateUserFlexiHours([FromBody] List<HrUserSettingModel> updates)
|
||||||
|
{
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
return BadRequest(ModelState);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var update in updates)
|
||||||
|
{
|
||||||
|
await UpdateOrInsertUserSettingAsync(update.UserId, flexiHourId: update.FlexiHourId);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _centralDbContext.SaveChangesAsync();
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return BadRequest(new { message = ex.Message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region State
|
||||||
|
[HttpGet("GetUserStates")]
|
||||||
|
public async Task<IActionResult> GetUserStates()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var users = await _centralDbContext.Users
|
||||||
|
.Include(u => u.Department)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var hrUserSettings = await _centralDbContext.Hrusersetting
|
||||||
|
.Include(h => h.State)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var result = users.Select(u =>
|
||||||
|
{
|
||||||
|
var hrSetting = hrUserSettings.FirstOrDefault(h => h.UserId == u.Id);
|
||||||
|
return new
|
||||||
|
{
|
||||||
|
u.Id,
|
||||||
|
u.FullName,
|
||||||
|
DepartmentName = u.Department != null ? u.Department.DepartmentName : "N/A",
|
||||||
|
State = hrSetting != null && hrSetting.State != null ? hrSetting.State.StateName : "N/A"
|
||||||
|
};
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return StatusCode(500, ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("UpdateUserStates")]
|
||||||
|
public async Task<IActionResult> UpdateUserStates([FromBody] List<HrUserSettingModel> updates)
|
||||||
|
{
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
return BadRequest(ModelState);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var update in updates)
|
||||||
|
{
|
||||||
|
await UpdateOrInsertUserSettingAsync(update.UserId, stateId: update.StateId);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
await _centralDbContext.SaveChangesAsync();
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return BadRequest(new { message = ex.Message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
#region Calendar
|
#region Calendar
|
||||||
[HttpGet("GetStatesName")]
|
[HttpGet("GetStatesName")]
|
||||||
public async Task<IActionResult> GetStatesName()
|
public async Task<IActionResult> GetStatesName()
|
||||||
@ -451,6 +631,47 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
return StatusCode(500, "An error occurred while saving overtime.");
|
return StatusCode(500, "An error occurred while saving overtime.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("GetUserStateAndHolidays/{userId}")]
|
||||||
|
public async Task<IActionResult> GetUserStateAndHolidaysAsync(int userId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var hrSettings = await _centralDbContext.Hrusersetting
|
||||||
|
.Include(h => h.State)
|
||||||
|
.ThenInclude(s => s.Weekends)
|
||||||
|
.Where(h => h.UserId == userId)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
if (hrSettings?.State == null)
|
||||||
|
{
|
||||||
|
return Ok(new { state = (object)null, publicHolidays = new List<object>() }); // Or handle no state differently
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch public holidays for the user's state and the current year (example)
|
||||||
|
var publicHolidays = await _centralDbContext.Holidays
|
||||||
|
.Where(ph => ph.StateId == hrSettings.StateId && ph.HolidayDate.Year == DateTime.Now.Year)
|
||||||
|
.Select(ph => new { Date = ph.HolidayDate.ToString("yyyy-MM-dd") })
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return Ok(new
|
||||||
|
{
|
||||||
|
state = new
|
||||||
|
{
|
||||||
|
stateId = hrSettings.StateId,
|
||||||
|
stateName = hrSettings.State?.StateName,
|
||||||
|
weekendDay = hrSettings.State?.Weekends?.Day,
|
||||||
|
weekendId = hrSettings.State?.WeekendId
|
||||||
|
},
|
||||||
|
publicHolidays
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error fetching user state and public holidays.");
|
||||||
|
return StatusCode(500, "An error occurred while fetching user state and public holidays.");
|
||||||
|
}
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Ot Records
|
#region Ot Records
|
||||||
@ -538,6 +759,22 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
.Where(o => o.UserId == userId && o.OtDate.Month == month && o.OtDate.Year == year)
|
.Where(o => o.UserId == userId && o.OtDate.Month == month && o.OtDate.Year == year)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
var hrUserSetting = _centralDbContext.Hrusersetting.FirstOrDefault(h => h.UserId == userId);
|
||||||
|
|
||||||
|
// StateId
|
||||||
|
var userStateId = hrUserSetting?.StateId ?? 0;
|
||||||
|
|
||||||
|
// WeekendId
|
||||||
|
var weekendId = _centralDbContext.States
|
||||||
|
.Where(s => s.StateId == userStateId)
|
||||||
|
.Select(s => s.WeekendId)
|
||||||
|
.FirstOrDefault() ?? 2; // Default Sat-Sun if null
|
||||||
|
|
||||||
|
// Public Holidays
|
||||||
|
var publicHolidays = _centralDbContext.Holidays
|
||||||
|
.Where(c => c.StateId == userStateId)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
// Step 1: Generate all days of the month
|
// Step 1: Generate all days of the month
|
||||||
var daysInMonth = DateTime.DaysInMonth(year, month);
|
var daysInMonth = DateTime.DaysInMonth(year, month);
|
||||||
var allDays = Enumerable.Range(1, daysInMonth)
|
var allDays = Enumerable.Range(1, daysInMonth)
|
||||||
@ -573,18 +810,188 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
byte[]? logoImage = null;
|
byte[]? logoImage = null;
|
||||||
var logoPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "images", "logo.jpg");
|
var logoPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "images", "logo.jpg");
|
||||||
|
|
||||||
if (System.IO.File.Exists(logoPath))
|
logoImage = System.IO.File.Exists(logoPath) ? System.IO.File.ReadAllBytes(logoPath) : null;
|
||||||
logoImage = System.IO.File.ReadAllBytes(logoPath);
|
|
||||||
|
var stream = _pdfService.GenerateOvertimeTablePdf(
|
||||||
|
mergedRecords,
|
||||||
|
departmentId,
|
||||||
|
fullName,
|
||||||
|
departmentName,
|
||||||
|
userStateId,
|
||||||
|
weekendId,
|
||||||
|
publicHolidays,
|
||||||
|
isAdminUser: IsAdmin(userId),
|
||||||
|
logoImage
|
||||||
|
);
|
||||||
|
|
||||||
var stream = _pdfService.GenerateOvertimeTablePdf(mergedRecords, departmentId, fullName, departmentName, logoImage);
|
|
||||||
|
|
||||||
return File(stream, "application/pdf", $"OvertimeRecords_{year}_{month}.pdf");
|
return File(stream, "application/pdf", $"OvertimeRecords_{year}_{month}.pdf");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsAdmin(int userId)
|
||||||
|
{
|
||||||
|
var userRoles = _centralDbContext.UserRoles
|
||||||
|
.Where(ur => ur.UserId == userId)
|
||||||
|
.Join(_centralDbContext.Roles, ur => ur.RoleId, r => r.Id, (ur, r) => r.Name)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return userRoles.Any(role => role.Contains("SuperAdmin") || role.Contains("SystemAdmin"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("GenerateOvertimeExcel")]
|
||||||
|
public IActionResult GenerateOvertimeExcel(int month, int year)
|
||||||
|
{
|
||||||
|
var userIdStr = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||||
|
if (string.IsNullOrEmpty(userIdStr) || !int.TryParse(userIdStr, out int userId))
|
||||||
|
return Unauthorized();
|
||||||
|
|
||||||
|
var user = _centralDbContext.Users
|
||||||
|
.Include(u => u.Department)
|
||||||
|
.FirstOrDefault(u => u.Id == userId);
|
||||||
|
|
||||||
|
if (user == null)
|
||||||
|
return NotFound("User not found.");
|
||||||
|
|
||||||
|
var fullName = user.FullName;
|
||||||
|
var departmentId = user.departmentId ?? 0;
|
||||||
|
var departmentName = user.Department?.DepartmentName ?? "N/A";
|
||||||
|
|
||||||
|
var records = _centralDbContext.Otregisters
|
||||||
|
.Include(o => o.Stations)
|
||||||
|
.Where(o => o.UserId == userId && o.OtDate.Month == month && o.OtDate.Year == year)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var hrUserSetting = _centralDbContext.Hrusersetting.FirstOrDefault(h => h.UserId == userId);
|
||||||
|
var userStateId = hrUserSetting?.StateId ?? 0;
|
||||||
|
|
||||||
|
var weekendId = _centralDbContext.States
|
||||||
|
.Where(s => s.StateId == userStateId)
|
||||||
|
.Select(s => s.WeekendId)
|
||||||
|
.FirstOrDefault() ?? 2;
|
||||||
|
|
||||||
|
var publicHolidays = _centralDbContext.Holidays
|
||||||
|
.Where(c => c.StateId == userStateId)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var daysInMonth = DateTime.DaysInMonth(year, month);
|
||||||
|
var allDays = Enumerable.Range(1, daysInMonth)
|
||||||
|
.Select(day => new DateTime(year, month, day))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var mergedRecords = new List<OtRegisterModel>();
|
||||||
|
foreach (var date in allDays)
|
||||||
|
{
|
||||||
|
var dayRecords = records
|
||||||
|
.Where(r => r.OtDate.Date == date.Date)
|
||||||
|
.OrderBy(r => r.OfficeFrom)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (dayRecords.Any())
|
||||||
|
{
|
||||||
|
mergedRecords.AddRange(dayRecords);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mergedRecords.Add(new OtRegisterModel
|
||||||
|
{
|
||||||
|
OtDate = date,
|
||||||
|
OtDays = date.DayOfWeek.ToString(),
|
||||||
|
OtDescription = "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var stream = _excelService.GenerateOvertimeExcel(
|
||||||
|
mergedRecords,
|
||||||
|
departmentId,
|
||||||
|
fullName,
|
||||||
|
departmentName,
|
||||||
|
userStateId,
|
||||||
|
weekendId,
|
||||||
|
publicHolidays,
|
||||||
|
isAdminUser: IsAdmin(userId),
|
||||||
|
logoImage: null
|
||||||
|
);
|
||||||
|
|
||||||
|
return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
|
$"OvertimeRecords_{year}_{month}.xlsx");
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("SubmitOvertime")]
|
||||||
|
public async Task<IActionResult> SubmitOvertime([FromForm] OvertimeSubmissionModel model)
|
||||||
|
{
|
||||||
|
if (model.File == null || model.File.Length == 0)
|
||||||
|
return BadRequest("No file uploaded.");
|
||||||
|
|
||||||
|
// Get userId from the login token
|
||||||
|
var userIdStr = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||||
|
if (string.IsNullOrEmpty(userIdStr) || !int.TryParse(userIdStr, out int userId))
|
||||||
|
return Unauthorized();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var uploadsFolder = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "Media", "Overtime");
|
||||||
|
if (!Directory.Exists(uploadsFolder))
|
||||||
|
Directory.CreateDirectory(uploadsFolder);
|
||||||
|
|
||||||
|
var uniqueFileName = $"{Guid.NewGuid()}_{model.File.FileName}";
|
||||||
|
var filePath = Path.Combine(uploadsFolder, uniqueFileName);
|
||||||
|
|
||||||
|
using (var fileStream = new FileStream(filePath, FileMode.Create))
|
||||||
|
{
|
||||||
|
await model.File.CopyToAsync(fileStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
var relativePath = Path.Combine("Media", "Overtime", uniqueFileName).Replace("\\", "/");
|
||||||
|
|
||||||
|
// Check if record exists for same month/year/user
|
||||||
|
var existingStatus = _centralDbContext.Otstatus.FirstOrDefault(x => x.UserId == userId && x.Month == model.Month && x.Year == model.Year);
|
||||||
|
|
||||||
|
if (existingStatus != null)
|
||||||
|
{
|
||||||
|
existingStatus.FilePath = relativePath;
|
||||||
|
existingStatus.SubmitDate = DateTime.Now;
|
||||||
|
_centralDbContext.Otstatus.Update(existingStatus);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var newStatus = new OtStatusModel
|
||||||
|
{
|
||||||
|
UserId = userId,
|
||||||
|
Month = model.Month,
|
||||||
|
Year = model.Year,
|
||||||
|
FilePath = relativePath,
|
||||||
|
SubmitDate = DateTime.Now,
|
||||||
|
HodStatus = "Pending",
|
||||||
|
HrStatus = "Pending"
|
||||||
|
};
|
||||||
|
_centralDbContext.Otstatus.Add(newStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _centralDbContext.SaveChangesAsync();
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to submit overtime.");
|
||||||
|
return StatusCode(500, "An error occurred while submitting overtime.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("CheckOvertimeSubmitted/{userId}/{month}/{year}")]
|
||||||
|
public async Task<IActionResult> CheckOvertimeSubmitted(string userId, int month, int year)
|
||||||
|
{
|
||||||
|
if (!int.TryParse(userId, out int parsedUserId))
|
||||||
|
return BadRequest("Invalid userId.");
|
||||||
|
|
||||||
|
var isSubmitted = await _centralDbContext.Otstatus
|
||||||
|
.AnyAsync(s => s.UserId == parsedUserId && s.Month == month && s.Year == year);
|
||||||
|
|
||||||
|
return Ok(isSubmitted);
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region Ot Edit
|
#region Ot Edit
|
||||||
[HttpGet("GetOvertimeRecordById/{id}")]
|
[HttpGet("GetOvertimeRecordById/{id}")]
|
||||||
public async Task<IActionResult> GetOvertimeRecordById(int id)
|
public async Task<IActionResult> GetOvertimeRecordById(int id)
|
||||||
@ -646,7 +1053,6 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
|
|
||||||
#region OtStatus
|
#region OtStatus
|
||||||
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -104,7 +104,9 @@ namespace PSTW_CentralSystem.DBContext
|
|||||||
public DbSet<StateModel> States { get; set; }
|
public DbSet<StateModel> States { get; set; }
|
||||||
public DbSet<WeekendModel> Weekends { get; set; }
|
public DbSet<WeekendModel> Weekends { get; set; }
|
||||||
public DbSet<OtRegisterModel> Otregisters { get; set; }
|
public DbSet<OtRegisterModel> Otregisters { get; set; }
|
||||||
public DbSet<OtStatusModel> OtStatus { get; set; }
|
public DbSet<OtStatusModel> Otstatus { get; set; }
|
||||||
|
public DbSet<HrUserSettingModel> Hrusersetting { get; set; }
|
||||||
|
public DbSet<FlexiHourModel> Flexihour { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="ClosedXML" Version="0.104.2" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.11" />
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.11" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="8.0.11" />
|
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="8.0.11" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.11" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.11" />
|
||||||
|
|||||||
@ -22,6 +22,8 @@ internal class Program
|
|||||||
builder.Services.AddControllersWithViews();
|
builder.Services.AddControllersWithViews();
|
||||||
builder.Services.AddRazorPages();
|
builder.Services.AddRazorPages();
|
||||||
builder.Services.AddScoped<OvertimePdfService>();
|
builder.Services.AddScoped<OvertimePdfService>();
|
||||||
|
builder.Services.AddTransient<OvertimeExcelService>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Log.Logger = new LoggerConfiguration()
|
Log.Logger = new LoggerConfiguration()
|
||||||
|
|||||||
@ -502,26 +502,6 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="sidebar-item">
|
|
||||||
<a class="sidebar-link has-arrow waves-effect waves-dark"
|
|
||||||
href="javascript:void(0)"
|
|
||||||
aria-expanded="false">
|
|
||||||
<i class="mdi mdi-receipt"></i><span class="hide-menu">HR Dashboard</span>
|
|
||||||
</a>
|
|
||||||
<ul aria-expanded="false" class="collapse first-level">
|
|
||||||
<li class="sidebar-item">
|
|
||||||
<a class="sidebar-link waves-effect waves-dark sidebar-link" asp-area="OTcalculate" asp-controller="HrDashboard" asp-action="OtApproval" aria-expanded="false">
|
|
||||||
<i class="mdi mdi-view-dashboard"></i><span class="hide-menu">OT Approval</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="sidebar-item">
|
|
||||||
<a class="sidebar-link waves-effect waves-dark sidebar-link" asp-area="OTcalculate" asp-controller="HrDashboard" asp-action="Settings" aria-expanded="false">
|
|
||||||
<i class="mdi mdi-view-dashboard"></i><span class="hide-menu">Settings</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="sidebar-item">
|
<li class="sidebar-item">
|
||||||
<a class="sidebar-link has-arrow waves-effect waves-dark"
|
<a class="sidebar-link has-arrow waves-effect waves-dark"
|
||||||
href="javascript:void(0)"
|
href="javascript:void(0)"
|
||||||
@ -575,6 +555,26 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li class="sidebar-item">
|
||||||
|
<a class="sidebar-link has-arrow waves-effect waves-dark"
|
||||||
|
href="javascript:void(0)"
|
||||||
|
aria-expanded="false">
|
||||||
|
<i class="mdi mdi-receipt"></i><span class="hide-menu">HR Dashboard</span>
|
||||||
|
</a>
|
||||||
|
<ul aria-expanded="false" class="collapse first-level">
|
||||||
|
<li class="sidebar-item">
|
||||||
|
<a class="sidebar-link waves-effect waves-dark sidebar-link" asp-area="OTcalculate" asp-controller="HrDashboard" asp-action="OtApproval" aria-expanded="false">
|
||||||
|
<i class="mdi mdi-view-dashboard"></i><span class="hide-menu">OT Approval</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="sidebar-item">
|
||||||
|
<a class="sidebar-link waves-effect waves-dark sidebar-link" asp-area="OTcalculate" asp-controller="HrDashboard" asp-action="Settings" aria-expanded="false">
|
||||||
|
<i class="mdi mdi-view-dashboard"></i><span class="hide-menu">Settings</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
<!-- <li class="sidebar-item">
|
<!-- <li class="sidebar-item">
|
||||||
<a class="sidebar-link waves-effect waves-dark sidebar-link"
|
<a class="sidebar-link waves-effect waves-dark sidebar-link"
|
||||||
href="charts.html"
|
href="charts.html"
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue
Block a user