-
This commit is contained in:
parent
695af0f339
commit
be8084ca85
@ -18,5 +18,9 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Controllers
|
||||
{
|
||||
return View();
|
||||
}
|
||||
public IActionResult OtReview()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
||||
int departmentId,
|
||||
string userFullName,
|
||||
string departmentName,
|
||||
byte[]? logoImage = null // Optional logo image
|
||||
byte[]? logoImage = null
|
||||
)
|
||||
{
|
||||
records = records
|
||||
@ -31,7 +31,6 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
||||
page.Size(PageSizes.A4.Landscape());
|
||||
page.Margin(30);
|
||||
|
||||
// Header section with logo and user info
|
||||
page.Content().Column(column =>
|
||||
{
|
||||
column.Item().Row(row =>
|
||||
@ -103,7 +102,6 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
||||
AddHeaderCell("Description", "#e3f2fd");
|
||||
});
|
||||
|
||||
// Data Rows
|
||||
// Data Rows
|
||||
double totalOTSum = 0;
|
||||
int totalBreakSum = 0;
|
||||
@ -112,7 +110,6 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
||||
|
||||
if (!records.Any())
|
||||
{
|
||||
// Show message row if no records
|
||||
uint colspan = (uint)(departmentId == 2 ? 13 : 12);
|
||||
|
||||
table.Cell().ColumnSpan(colspan)
|
||||
@ -124,7 +121,6 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
||||
.FontColor(Colors.Grey.Darken2)
|
||||
.Italic();
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
foreach (var r in records)
|
||||
@ -165,7 +161,6 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
||||
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);
|
||||
|
||||
@ -196,7 +191,6 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
||||
return stream;
|
||||
}
|
||||
|
||||
|
||||
private TimeSpan CalculateTotalOT(OtRegisterModel r)
|
||||
{
|
||||
TimeSpan office = (r.OfficeTo ?? TimeSpan.Zero) - (r.OfficeFrom ?? TimeSpan.Zero);
|
||||
|
||||
@ -2,16 +2,67 @@
|
||||
ViewData["Title"] = "Overtime Approval";
|
||||
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">
|
||||
|
||||
<h6 class="text-white">Rate</h6>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="container mt-4" id="hod-approval-app">
|
||||
<table class="table table-bordered table-striped mt-3">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Submission Date</th>
|
||||
<th>Month/Year</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="submission in submittedRecords" :key="submission.userId + '-' + submission.monthYear">
|
||||
<td><a :href="'/OTcalculate/HodDashboard/OtReview/' + submission.userId + '/' + getMonthYearParts(submission.monthYear).month + '/' + getMonthYearParts(submission.monthYear).year">{{ submission.userName }}</a></td>
|
||||
<td>{{ formatDate(submission.submissionDate) }}</td>
|
||||
<td>{{ submission.monthYear }}</td>
|
||||
<td><button class="btn btn-info btn-sm">Review</button></td>
|
||||
</tr>
|
||||
<tr v-if="!submittedRecords.length">
|
||||
<td colspan="4">No overtime records submitted for review.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue@3"></script>
|
||||
<script>
|
||||
const hodApprovalApp = Vue.createApp({
|
||||
data() {
|
||||
return {
|
||||
submittedRecords: []
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.fetchSubmittedRecords();
|
||||
},
|
||||
methods: {
|
||||
async fetchSubmittedRecords() {
|
||||
try {
|
||||
const res = await fetch('/OvertimeAPI/GetSubmittedOvertimeRecords');
|
||||
if (res.ok) {
|
||||
this.submittedRecords = await res.json();
|
||||
} else {
|
||||
console.error("Failed to fetch submitted records:", await res.text());
|
||||
alert("Error fetching submitted records.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching submitted records:", error);
|
||||
alert("An unexpected error occurred.");
|
||||
}
|
||||
},
|
||||
formatDate(dateString) {
|
||||
if (!dateString) return '-';
|
||||
return new Date(dateString).toLocaleDateString();
|
||||
},
|
||||
getMonthYearParts(monthYearString) {
|
||||
const parts = monthYearString.split('/');
|
||||
return { month: parts[0], year: parts[1] };
|
||||
}
|
||||
}
|
||||
}).mount("#hod-approval-app");
|
||||
</script>
|
||||
}
|
||||
169
Areas/OTcalculate/Views/HodDashboard/OtReview.cshtml
Normal file
169
Areas/OTcalculate/Views/HodDashboard/OtReview.cshtml
Normal file
@ -0,0 +1,169 @@
|
||||
@{
|
||||
ViewData["Title"] = "Overtime Review";
|
||||
Layout = "~/Views/Shared/_Layout.cshtml";
|
||||
}
|
||||
|
||||
<div id="ot-review-app" style="max-width: 1300px; margin: auto; font-size: 13px;">
|
||||
<h2>Overtime Records for {{ reviewedUserName }} - {{ formatMonthYear(selectedMonth, selectedYear) }}</h2>
|
||||
<div id="print-section" class="table-container table-responsive">
|
||||
<table class="table table-bordered table-sm table-striped text-center align-middle">
|
||||
<thead>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(record, index) in otRecords" :key="record.overtimeId">
|
||||
<td>{{ formatDate(record.otDate) }}</td>
|
||||
<td>{{ formatTime(record.officeFrom) }}</td>
|
||||
<td>{{ formatTime(record.officeTo) }}</td>
|
||||
<td>{{ record.officeBreak }} min</td>
|
||||
<td>{{ formatTime(record.outsideFrom) }}</td>
|
||||
<td>{{ formatTime(record.outsideTo) }}</td>
|
||||
<td>{{ record.outsideBreak }} min</td>
|
||||
<td>{{ formatHourMinute(calcTotalTime(record)) }}</td>
|
||||
<td>{{ calcBreakTotal(record) }}</td>
|
||||
<td>{{ formatHourMinute(calcNetHours(record)) }}</td>
|
||||
<td v-if="isPSTWAIR">{{ record.stationName || 'N/A' }}</td>
|
||||
<td>{{ record.otDays}}</td>
|
||||
<td class="wrap-text">
|
||||
<div class="description-preview" v-on:click ="toggleDescription(index)" :class="{ expanded: expandedDescriptions[index] }">
|
||||
{{ record.otDescription }}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="record.filePath">
|
||||
<a :href="record.filePath" target="_blank" class="btn btn-light border rounded-circle" title="View PDF">
|
||||
<i class="bi bi-file-earmark-pdf-fill fs-5"></i>
|
||||
</a>
|
||||
</span>
|
||||
<span v-else>-</span>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-success btn-sm me-1">Approve</button>
|
||||
<button class="btn btn-danger btn-sm">Reject</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="otRecords.length === 0">
|
||||
<td :colspan="isPSTWAIR ? 14 : 13">No overtime records found for this user and period.</td>
|
||||
</tr>
|
||||
<tr class="table-primary fw-bold">
|
||||
<td>TOTAL</td>
|
||||
<td colspan="6"></td>
|
||||
<td>{{ formatHourMinute(totalHours) }}</td>
|
||||
<td>{{ formatHourMinute(totalBreak) }}</td>
|
||||
<td>{{ formatHourMinute(totalNetTime) }}</td>
|
||||
<td v-if="isPSTWAIR"></td>
|
||||
<td colspan="4"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue@3"></script>
|
||||
<script>
|
||||
const otReviewApp = Vue.createApp({
|
||||
data() {
|
||||
return {
|
||||
userId: null,
|
||||
selectedMonth: null,
|
||||
selectedYear: null,
|
||||
otRecords: [],
|
||||
isPSTWAIR: false, // You might need to fetch this based on the reviewed user
|
||||
expandedDescriptions: {},
|
||||
reviewedUserName: '' // To display the name of the user being reviewed
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
totalHours() { /* ... your totalHours calculation ... */ },
|
||||
totalBreak() { /* ... your totalBreak calculation ... */ },
|
||||
totalNetTime() { /* ... your totalNetTime calculation ... */ }
|
||||
},
|
||||
mounted() {
|
||||
this.getParamsFromUrl();
|
||||
this.fetchOtRecords();
|
||||
this.fetchUserInfo(); // To get the name and department of the reviewed user
|
||||
},
|
||||
methods: {
|
||||
getParamsFromUrl() {
|
||||
const pathSegments = window.location.pathname.split('/');
|
||||
this.userId = parseInt(pathSegments[4]); // Assuming the URL structure is /OTcalculate/HodDashboard/OtReview/{userId}/{month}/{year}
|
||||
this.selectedMonth = parseInt(pathSegments[5]);
|
||||
this.selectedYear = parseInt(pathSegments[6]);
|
||||
},
|
||||
async fetchUserInfo() {
|
||||
try {
|
||||
const res = await fetch(`/IdentityAPI/GetUserInformationById/${this.userId}`, { method: 'POST' }); // Assuming you have an endpoint to get user info by ID
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
this.reviewedUserName = data.userInfo?.fullName;
|
||||
const department = data.userInfo?.department;
|
||||
const deptName = department?.departmentName?.toUpperCase?.() || '';
|
||||
this.isPSTWAIR = deptName.includes("PSTW") && deptName.includes("AIR") && department?.departmentId === 2;
|
||||
} else {
|
||||
console.error("Failed to fetch user info:", await res.text());
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching user info:", error);
|
||||
}
|
||||
},
|
||||
async fetchOtRecords() {
|
||||
try {
|
||||
const res = await fetch(`/OvertimeAPI/GetSubmittedUserOvertimeRecords/<span class="math-inline">\{this\.userId\}/</span>{this.selectedMonth}/${this.selectedYear}`);
|
||||
if (res.ok) {
|
||||
this.otRecords = await res.json();
|
||||
} else {
|
||||
console.error("Failed to fetch OT records:", await res.text());
|
||||
alert("Error fetching overtime records.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching OT records:", error);
|
||||
alert("An unexpected error occurred.");
|
||||
}
|
||||
},
|
||||
toggleDescription(index) {
|
||||
this.expandedDescriptions[index] = !this.expandedDescriptions[index];
|
||||
},
|
||||
formatDate(d) {
|
||||
return new Date(d).toLocaleDateString();
|
||||
},
|
||||
formatTime(t) {
|
||||
return t ? t.slice(0, 5) : "-";
|
||||
},
|
||||
getTimeDiff(from, to) {
|
||||
if (!from || !to) return 0;
|
||||
const [fh, fm] = from.split(":").map(Number);
|
||||
const [th, tm] = to.split(":").map(Number);
|
||||
return ((th * 60 + tm) - (fh * 60 + fm)) / 60;
|
||||
},
|
||||
calcTotalTime(r) {
|
||||
const totalMinutes = this.calcTotalHours(r) * 60;
|
||||
return {
|
||||
hours: Math.floor(totalMinutes / 60),
|
||||
minutes: Math.round(totalMinutes % 60)
|
||||
};
|
||||
},
|
||||
calcTotalHours(r) {
|
||||
return this.getTimeDiff(r.officeFrom, r.officeTo) + this.getTimeDiff(r.outsideFrom, r.outsideTo);
|
||||
},
|
||||
calcBreakTotal(r) {
|
||||
return (r.officeBreak || 0) + (r.outsideBreak || 0);
|
||||
},
|
||||
calcNetHours(r) {
|
||||
const totalMinutes = (this.calcTotalHours(r) * 60) - this.calcBreakTotal(r);
|
||||
return {
|
||||
hours: Math.floor(totalMinutes / 60),
|
||||
minutes: Math.round(totalMinutes % 60)
|
||||
};
|
||||
},
|
||||
formatHourMinute(timeObj) {
|
||||
return timeObj ? `${timeObj.hours} h ${timeObj.minutes} m` : '-';
|
||||
},
|
||||
formatMonthYear(month, year) {
|
||||
const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
||||
return `${monthNames[month - 1]} ${year}`;
|
||||
}
|
||||
// Add methods for Approve and Reject actions here
|
||||
}
|
||||
}).mount("#ot-review-app");
|
||||
</script>
|
||||
}
|
||||
@ -353,13 +353,34 @@
|
||||
},
|
||||
async submitRecords() {
|
||||
try {
|
||||
const res = await fetch('/OvertimeAPI/SaveOvertimeRecordsWithPdf', {
|
||||
const recordsToSubmit = this.filteredRecords.map(record => ({
|
||||
overtimeId: record.overtimeId, // Make sure to include the ID for updates
|
||||
otDate: record.otDate,
|
||||
officeFrom: record.officeFrom,
|
||||
officeTo: record.officeTo,
|
||||
officeBreak: record.officeBreak,
|
||||
outsideFrom: record.outsideFrom,
|
||||
outsideTo: record.outsideTo,
|
||||
outsideBreak: record.outsideBreak,
|
||||
stationId: record.stationId,
|
||||
otDescription: record.otDescription,
|
||||
otDays: record.otDays,
|
||||
filePath: record.filePath, // Include existing file path
|
||||
userId: this.userId
|
||||
// Add other relevant fields if necessary
|
||||
}));
|
||||
|
||||
const res = await fetch('/OvertimeAPI/SubmitOvertimeRecords', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(this.otRecords)
|
||||
body: JSON.stringify(recordsToSubmit)
|
||||
});
|
||||
if (res.ok) alert("Overtime records submitted successfully.");
|
||||
else alert("Submission failed.");
|
||||
if (res.ok) {
|
||||
alert("Overtime records submitted for review.");
|
||||
// Optionally, clear the local records or redirect the user
|
||||
} else {
|
||||
alert("Submission failed: " + await res.text());
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Submission error:", err);
|
||||
alert("An error occurred during submission.");
|
||||
|
||||
@ -251,7 +251,7 @@
|
||||
formatTime(timeString) {
|
||||
if (!timeString) return null;
|
||||
const [hours, minutes] = timeString.split(':');
|
||||
return `${hours.padStart(2, '0')}:${minutes.padStart(2, '0')}:00`; // Ensure valid HH:mm:ss format
|
||||
return `${hours.padStart(2, '0')}:${minutes.padStart(2, '0')}:00`; // HH:mm:ss format
|
||||
},
|
||||
async addOvertime() {
|
||||
if (this.isPSTWAIR && !this.selectedAirStation) {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue
Block a user