-
This commit is contained in:
parent
95db83ab3e
commit
2c9d8bc4da
@ -15,6 +15,10 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Controllers
|
|||||||
{
|
{
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
public IActionResult EditOvertime()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,7 +28,6 @@ namespace PSTW_CentralSystem.Areas.OTcalculate.Models
|
|||||||
public string OtDescription { get; set; }
|
public string OtDescription { get; set; }
|
||||||
public string OtDays { get; set; }
|
public string OtDays { get; set; }
|
||||||
public required string PDFBase64 { get; set; }
|
public required string PDFBase64 { get; set; }
|
||||||
public required string IvBase64 { get; set; }
|
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
public int UserId { get; set; }
|
public int UserId { get; set; }
|
||||||
|
|||||||
6
Areas/OTcalculate/Views/Overtime/EditOvertime.cshtml
Normal file
6
Areas/OTcalculate/Views/Overtime/EditOvertime.cshtml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
@{
|
||||||
|
ViewData["Title"] = "Records Overtime";
|
||||||
|
Layout = "~/Views/Shared/_Layout.cshtml";
|
||||||
|
}
|
||||||
|
|
||||||
|
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
@ -1,5 +1,298 @@
|
|||||||
@{
|
@{
|
||||||
ViewData["Title"] = "All Records";
|
ViewData["Title"] = "My Overtime Records";
|
||||||
Layout = "~/Views/Shared/_Layout.cshtml";
|
Layout = "~/Views/Shared/_Layout.cshtml";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.table-container {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 15px;
|
||||||
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
||||||
|
padding: 25px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table th, .table td {
|
||||||
|
vertical-align: middle;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-green {
|
||||||
|
background-color: #d9ead3 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-blue {
|
||||||
|
background-color: #cfe2f3 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-orange {
|
||||||
|
background-color: #fce5cd !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.form-control {
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
select.form-control {
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
border-radius: 10px !important;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-primary td {
|
||||||
|
background-color: #e6f0ff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-responsive {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
<div id="app" style="max-width: 1300px; margin: auto; font-size: 13px;">
|
||||||
|
<div class="mb-3 d-flex flex-wrap">
|
||||||
|
<div class="me-2 mb-2">
|
||||||
|
<label>Month</label>
|
||||||
|
<select class="form-control form-control-sm" v-model="selectedMonth">
|
||||||
|
<option v-for="(m, index) in months" :value="index + 1">{{ m }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<label>Year</label>
|
||||||
|
<select class="form-control form-control-sm" v-model="selectedYear">
|
||||||
|
<option v-for="y in years" :value="y">{{ y }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="print-section" class="table-container table-responsive">
|
||||||
|
<table class="table table-bordered table-sm table-striped text-center align-middle">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="header-green" rowspan="2">Date</th>
|
||||||
|
<th class="header-blue" colspan="3">Office<br><small>(8:30 - 17:30)</small></th>
|
||||||
|
<th class="header-blue" colspan="3">Outside<br><small>(17:30 - 8:30)</small></th>
|
||||||
|
<th class="header-orange" rowspan="2">Total OT Hours</th>
|
||||||
|
<th class="header-orange" rowspan="2">Break Hours (min)</th>
|
||||||
|
<th class="header-orange" rowspan="2">Net OT</th>
|
||||||
|
<th class="header-orange" rowspan="2">Station</th>
|
||||||
|
<th class="header-blue" rowspan="2">Description</th>
|
||||||
|
<th class="header-blue" rowspan="2">File</th>
|
||||||
|
<th class="header-green" rowspan="2">Action</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th class="header-blue">From</th>
|
||||||
|
<th class="header-blue">To</th>
|
||||||
|
<th class="header-blue">Break</th>
|
||||||
|
<th class="header-blue">From</th>
|
||||||
|
<th class="header-blue">To</th>
|
||||||
|
<th class="header-blue">Break</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="(record, index) in filteredRecords" :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>{{ calcTotalHours(record).toFixed(2) }}</td>
|
||||||
|
<td>{{ calcBreakTotal(record) }}</td>
|
||||||
|
<td>{{ formatHourMinute(calcNetHours(record)) }}</td>
|
||||||
|
<td>{{ record.stationName || 'N/A' }}</td>
|
||||||
|
<td>{{ record.otDescription }}</td>
|
||||||
|
<td>
|
||||||
|
<span v-if="record.pdfBase64">
|
||||||
|
<button class="btn btn-light border rounded-circle" title="View PDF" v-on:click="viewPdf(record.pdfBase64)">
|
||||||
|
<i class="bi bi-file-earmark-pdf-fill fs-5"></i>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-light border rounded-circle me-1" title="Edit Record" v-on:click="editRecord(index)">
|
||||||
|
<i class="bi bi-pencil-fill text-warning fs-5"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-light border rounded-circle" title="Delete Record" v-on:click="deleteRecord(index)">
|
||||||
|
<i class="bi bi-trash-fill text-danger fs-5"></i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
<tr v-if="filteredRecords.length === 0">
|
||||||
|
<td colspan="14">No records found for selected month and year.</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="table-primary fw-bold">
|
||||||
|
<td>TOTAL</td>
|
||||||
|
<td colspan="6"></td>
|
||||||
|
<td>{{ totalHours.toFixed(2) }}</td>
|
||||||
|
<td>{{ totalBreak }}</td>
|
||||||
|
<td>{{ formatHourMinute(totalNetTime) }}</td>
|
||||||
|
<td colspan="4"></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-3 d-flex flex-wrap gap-2">
|
||||||
|
<button class="btn btn-primary btn-sm" v-on:click="printTable">
|
||||||
|
<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-success btn-sm" v-on:click="submitRecords">
|
||||||
|
<i class="bi bi-send"></i> Submit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue@3"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.2/html2pdf.bundle.min.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const app = Vue.createApp({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
otRecords: [],
|
||||||
|
userId: null,
|
||||||
|
selectedMonth: new Date().getMonth() + 1,
|
||||||
|
selectedYear: new Date().getFullYear(),
|
||||||
|
months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
|
||||||
|
years: Array.from({ length: 10 }, (_, i) => new Date().getFullYear() - 5 + i)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
filteredRecords() {
|
||||||
|
return this.otRecords
|
||||||
|
.filter(record => {
|
||||||
|
const date = new Date(record.otDate);
|
||||||
|
return date.getMonth() + 1 === this.selectedMonth && date.getFullYear() === this.selectedYear;
|
||||||
|
})
|
||||||
|
.sort((a, b) => new Date(a.otDate) - new Date(b.otDate));
|
||||||
|
},
|
||||||
|
totalHours() {
|
||||||
|
return this.filteredRecords.reduce((sum, r) => sum + this.calcTotalHours(r), 0);
|
||||||
|
},
|
||||||
|
totalBreak() {
|
||||||
|
return this.filteredRecords.reduce((sum, r) => sum + this.calcBreakTotal(r), 0);
|
||||||
|
},
|
||||||
|
netHours() {
|
||||||
|
return this.totalHours - (this.totalBreak / 60);
|
||||||
|
},
|
||||||
|
totalNetTime() {
|
||||||
|
const totalMinutes = (this.totalHours * 60) - this.totalBreak;
|
||||||
|
const hours = Math.floor(totalMinutes / 60);
|
||||||
|
const minutes = Math.round(totalMinutes % 60);
|
||||||
|
return { hours, minutes };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
await this.initUserAndRecords();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async initUserAndRecords() {
|
||||||
|
await this.fetchUser();
|
||||||
|
if (this.userId) {
|
||||||
|
await this.fetchOtRecords();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async fetchUser() {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/IdentityAPI/GetUserInformation', { method: 'POST' });
|
||||||
|
const data = await res.json();
|
||||||
|
this.userId = data.userInfo?.id;
|
||||||
|
} catch (err) { console.error("User fetch error", err); }
|
||||||
|
},
|
||||||
|
async fetchOtRecords() {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/OvertimeAPI/GetUserOvertimeRecords/${this.userId}`);
|
||||||
|
this.otRecords = await res.json();
|
||||||
|
} catch (err) { console.error("Records fetch error", err); }
|
||||||
|
},
|
||||||
|
formatDate(d) {
|
||||||
|
return new Date(d).toLocaleDateString();
|
||||||
|
},
|
||||||
|
formatTime(t) {
|
||||||
|
return t ? t.slice(0, 5) : "-";
|
||||||
|
},
|
||||||
|
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 totalHours = this.calcTotalHours(r);
|
||||||
|
const breakMinutes = this.calcBreakTotal(r);
|
||||||
|
const netMinutes = (totalHours * 60) - breakMinutes;
|
||||||
|
const hours = Math.floor(netMinutes / 60);
|
||||||
|
const minutes = Math.round(netMinutes % 60);
|
||||||
|
return { hours, minutes };
|
||||||
|
},
|
||||||
|
formatHourMinute(timeObj) {
|
||||||
|
if (!timeObj) return '-';
|
||||||
|
return `${timeObj.hours} hr ${timeObj.minutes} min`;
|
||||||
|
},
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
editRecord(index) {
|
||||||
|
|
||||||
|
},
|
||||||
|
async deleteRecord(index) {
|
||||||
|
const record = this.filteredRecords[index];
|
||||||
|
const confirmed = confirm("Are you sure you want to delete this record?");
|
||||||
|
if (!confirmed) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/OvertimeAPI/DeleteOvertimeRecord/${record.overtimeId}`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
// Remove from local state after successful backend deletion
|
||||||
|
this.otRecords.splice(this.otRecords.indexOf(record), 1);
|
||||||
|
} else {
|
||||||
|
const errorText = await res.text();
|
||||||
|
alert("Failed to delete: " + errorText);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Delete failed", err);
|
||||||
|
alert("Error deleting record.");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
printTable() {
|
||||||
|
window.print();
|
||||||
|
},
|
||||||
|
downloadPdf() {
|
||||||
|
const element = document.getElementById("print-section");
|
||||||
|
html2pdf().from(element).save("OT_Records.pdf");
|
||||||
|
},
|
||||||
|
submitRecords() {
|
||||||
|
alert("Submitting records...");
|
||||||
|
},
|
||||||
|
viewPdf(base64) {
|
||||||
|
const byteArray = Uint8Array.from(atob(base64), c => c.charCodeAt(0));
|
||||||
|
const blob = new Blob([byteArray], { type: 'application/pdf' });
|
||||||
|
window.open(URL.createObjectURL(blob));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.mount("#app");
|
||||||
|
</script>
|
||||||
|
}
|
||||||
|
|||||||
@ -62,6 +62,7 @@
|
|||||||
{{ station.stationName || 'Unnamed Station' }}
|
{{ station.stationName || 'Unnamed Station' }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
<small class="text-danger">*Only for PSTW AIR</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@ -222,7 +223,7 @@
|
|||||||
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`; // Ensure valid HH:mm:ss format
|
||||||
},
|
},
|
||||||
async addOvertime() {
|
async addOvertime() {
|
||||||
if (!this.selectedDate || !this.selectedDayType || !this.selectedAirStation) {
|
if (!this.selectedDate || !this.selectedDayType) {
|
||||||
alert("Please fill in all required fields.");
|
alert("Please fill in all required fields.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -241,70 +242,44 @@
|
|||||||
console.log("Sending userId:", this.userId);
|
console.log("Sending userId:", this.userId);
|
||||||
|
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = async (event) => {
|
reader.onload = async (event) => {
|
||||||
const base64String = event.target.result.split(',')[1];
|
const base64String = event.target.result.split(',')[1];
|
||||||
|
|
||||||
// Generate a crypto key
|
const payload = {
|
||||||
const key = await crypto.subtle.generateKey(
|
otDate: this.selectedDate,
|
||||||
{ name: "AES-GCM", length: 256 },
|
officeFrom: this.formatTime(this.officeFrom) || null,
|
||||||
true,
|
officeTo: this.formatTime(this.officeTo) || null,
|
||||||
["encrypt", "decrypt"]
|
officeBreak: this.officeBreak || 0,
|
||||||
);
|
outsideFrom: this.formatTime(this.outsideFrom) || null,
|
||||||
|
outsideTo: this.formatTime(this.outsideTo) || null,
|
||||||
|
outsideBreak: this.outsideBreak || 0,
|
||||||
|
stationId: this.selectedAirStation || null,
|
||||||
|
otDescription: this.otDescription,
|
||||||
|
otDays: this.selectedDayType,
|
||||||
|
pdfBase64: base64String,
|
||||||
|
userId: this.userId,
|
||||||
|
};
|
||||||
|
|
||||||
// Export the key (for sending to backend if needed or for testing)
|
try {
|
||||||
const exportedKey = await crypto.subtle.exportKey("jwk", key);
|
const response = await fetch(`${window.location.origin}/OvertimeAPI/AddOvertime`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
|
||||||
// Generate a random IV
|
if (!response.ok) {
|
||||||
const iv = crypto.getRandomValues(new Uint8Array(12));
|
const errorText = await response.text();
|
||||||
|
throw new Error(`HTTP error! status: ${response.status} - ${errorText}`);
|
||||||
// Encode data
|
}
|
||||||
const encoded = new TextEncoder().encode(base64String);
|
|
||||||
|
|
||||||
// Encrypt the data
|
|
||||||
const encrypted = await crypto.subtle.encrypt(
|
|
||||||
{ name: "AES-GCM", iv: iv },
|
|
||||||
key,
|
|
||||||
encoded
|
|
||||||
);
|
|
||||||
|
|
||||||
// Convert encrypted data to base64
|
|
||||||
const encryptedBase64 = btoa(String.fromCharCode(...new Uint8Array(encrypted)));
|
|
||||||
const ivBase64 = btoa(String.fromCharCode(...iv));
|
|
||||||
|
|
||||||
const payload = {
|
|
||||||
otDate: this.selectedDate,
|
|
||||||
officeFrom: this.formatTime(this.officeFrom) || null,
|
|
||||||
officeTo: this.formatTime(this.officeTo) || null,
|
|
||||||
officeBreak: this.officeBreak || 0,
|
|
||||||
outsideFrom: this.formatTime(this.outsideFrom) || null,
|
|
||||||
outsideTo: this.formatTime(this.outsideTo) || null,
|
|
||||||
outsideBreak: this.outsideBreak || 0,
|
|
||||||
stationId: this.selectedAirStation,
|
|
||||||
otDescription: this.otDescription,
|
|
||||||
otDays: this.selectedDayType,
|
|
||||||
pdfBase64: encryptedBase64,
|
|
||||||
ivBase64: ivBase64, // store this too
|
|
||||||
userId: this.userId,
|
|
||||||
encryptionKey: exportedKey.k, // only if you're doing a simple demo; ideally don't send this from frontend
|
|
||||||
};
|
|
||||||
|
|
||||||
// Send to backend
|
|
||||||
const response = await fetch(`${window.location.origin}/OvertimeAPI/AddOvertime`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify(payload),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorText = await response.text();
|
|
||||||
throw new Error(`HTTP error! status: ${response.status} - ${errorText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
alert(result.message);
|
|
||||||
this.clearForm();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
alert(result.message);
|
||||||
|
this.clearForm();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error adding overtime:", error);
|
||||||
|
alert("Failed to save overtime. Please check the console for errors.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
reader.onerror = () => {
|
reader.onerror = () => {
|
||||||
console.error("Error reading file");
|
console.error("Error reading file");
|
||||||
|
|||||||
@ -18,6 +18,7 @@ using System.Diagnostics;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using static System.Collections.Specialized.BitVector32;
|
using static System.Collections.Specialized.BitVector32;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
using Microsoft.VisualStudio.Web.CodeGenerators.Mvc.Templates.BlazorIdentity.Pages;
|
||||||
|
|
||||||
|
|
||||||
namespace PSTW_CentralSystem.Controllers.API
|
namespace PSTW_CentralSystem.Controllers.API
|
||||||
@ -424,6 +425,65 @@ namespace PSTW_CentralSystem.Controllers.API
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Ot Records
|
||||||
|
[HttpGet("GetUserOvertimeRecords/{userId}")]
|
||||||
|
public IActionResult GetUserOvertimeRecords(int userId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var records = _centralDbContext.Otregisters
|
||||||
|
.Where(o => o.UserId == userId)
|
||||||
|
.Select(o => new
|
||||||
|
{
|
||||||
|
o.OvertimeId,
|
||||||
|
o.OtDate,
|
||||||
|
o.OfficeFrom,
|
||||||
|
o.OfficeTo,
|
||||||
|
o.OfficeBreak,
|
||||||
|
o.OutsideFrom,
|
||||||
|
o.OutsideTo,
|
||||||
|
o.OutsideBreak,
|
||||||
|
o.StationId,
|
||||||
|
StationName = o.Stations != null ? o.Stations.StationName : "N/A",
|
||||||
|
o.OtDescription,
|
||||||
|
o.OtDays,
|
||||||
|
o.PDFBase64,
|
||||||
|
o.UserId
|
||||||
|
})
|
||||||
|
.OrderByDescending(o => o.OtDate)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return Ok(records);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to fetch OT records.");
|
||||||
|
return StatusCode(500, "Error retrieving OT records.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[HttpDelete("DeleteOvertimeRecord/{id}")]
|
||||||
|
public IActionResult DeleteOvertimeRecord(int id)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var record = _centralDbContext.Otregisters.FirstOrDefault(o => o.OvertimeId == id);
|
||||||
|
if (record == null)
|
||||||
|
return NotFound("Overtime record not found.");
|
||||||
|
|
||||||
|
_centralDbContext.Otregisters.Remove(record);
|
||||||
|
_centralDbContext.SaveChanges();
|
||||||
|
|
||||||
|
return Ok(new { message = "Record deleted successfully." });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to delete OT record.");
|
||||||
|
return StatusCode(500, "Error deleting OT record.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -42,6 +42,7 @@
|
|||||||
<link rel="stylesheet" href="~/assets/libs/quill/dist/quill.snow.css" />
|
<link rel="stylesheet" href="~/assets/libs/quill/dist/quill.snow.css" />
|
||||||
<link href="~/dist/css/style.min.css" rel="stylesheet" />
|
<link href="~/dist/css/style.min.css" rel="stylesheet" />
|
||||||
<link href="~/lib/printjs/print.min.css" rel="stylesheet" />
|
<link href="~/lib/printjs/print.min.css" rel="stylesheet" />
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css">
|
||||||
<!-- DataTables CSS-->
|
<!-- DataTables CSS-->
|
||||||
<link href="~/lib/datatables/datatables.css" rel="stylesheet" />
|
<link href="~/lib/datatables/datatables.css" rel="stylesheet" />
|
||||||
<!-- Vue Js -->
|
<!-- Vue Js -->
|
||||||
@ -52,6 +53,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||||
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
||||||
<!--[if lt IE 9]>
|
<!--[if lt IE 9]>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user