169 lines
8.6 KiB
Plaintext
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>
|
|
} |