PSTW_CentralizeSystem/Areas/OTcalculate/Views/HodDashboard/OtReview.cshtml
2025-04-18 10:50:23 +08:00

169 lines
8.6 KiB
Plaintext

@{
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>
}