This commit is contained in:
Naz 2025-04-15 12:07:40 +08:00
parent 872eb2363a
commit 1400532680
4 changed files with 389 additions and 5 deletions

View File

@ -2,5 +2,344 @@
ViewData["Title"] = "Edit Overtime"; ViewData["Title"] = "Edit Overtime";
Layout = "~/Views/Shared/_Layout.cshtml"; Layout = "~/Views/Shared/_Layout.cshtml";
} }
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<div id="app" class="container mt-4 d-flex justify-content-center">
<div class="card shadow-sm" style="width: 1100px;">
<div class="card-body">
<div class="row">
<div class="col-md-7">
<div class="mb-3">
<label class="form-label" for="dateInput">Date</label>
<input type="date" class="form-control" v-model="editForm.otDate"
v-on:input="calculateOTAndBreak">
</div>
<h6 class="fw-bold">OFFICE HOURS</h6>
<div class="row mb-3">
<div class="col-4">
<label for="officeFrom">From</label>
<input type="time" class="form-control" v-model="editForm.officeFrom"
v-on:input="calculateOTAndBreak">
</div>
<div class="col-4">
<label for="officeTo">To</label>
<input type="time" id="officeTo" class="form-control" v-model="editForm.officeTo"
v-on:input="calculateOTAndBreak">
</div>
<div class="col-4">
<label for="officeBreak">Break Hours (Minutes)</label>
<input type="number" id="officeBreak" class="form-control" v-model="editForm.officeBreak"
v-on:input="calculateOTAndBreak" placeholder="e.g. 30">
</div>
</div>
<h6 class="fw-bold text-danger">OUTSIDE OFFICE HOURS</h6>
<div class="row mb-2">
<div class="col-4">
<label for="outsideFrom">From</label>
<input type="time" id="outsideFrom" class="form-control" v-model="editForm.outsideFrom"
v-on:input="calculateOTAndBreak">
</div>
<div class="col-4">
<label for="outsideTo">To</label>
<input type="time" id="outsideTo" class="form-control" v-model="editForm.outsideTo"
v-on:input="calculateOTAndBreak">
</div>
<div class="col-4">
<label for="outsideBreak">Break Hours (Minutes)</label>
<input type="number" id="outsideBreak" class="form-control" v-model="editForm.outsideBreak"
v-on:input="calculateOTAndBreak" placeholder="e.g. 45">
</div>
</div>
<div class="mb-3" v-if="isPSTWAIR">
<label for="airstationDropdown">Air Station</label>
<select id="airstationDropdown" class="form-control" v-model="editForm.stationId">
<option value="" disabled selected>Select Station</option>
<option v-for="station in airstationList" :key="station.stationId" :value="station.stationId">
{{ station.stationName || 'Unnamed Station' }}
</option>
</select>
<small class="text-danger">*Only for PSTW AIR</small>
</div>
<div class="mb-3">
<label for="otDescription">Work Brief Description</label>
<textarea id="otDescription" class="form-control"
v-model="editForm.otDescription"
v-on:input="limitCharCount"
placeholder="Describe the work done..."></textarea>
<small class="text-muted">
{{ charCount }} / 150 characters
</small>
</div>
</div>
<div class="col-md-5">
<label class="mb-2">Day</label>
<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 class="mb-3 mt-3">
<label for="fileUpload">Upload File:</label>
<input type="file" id="fileUpload" class="form-control" accept="application/pdf"
v-on:change="handleFileUpload" ref="fileInput">
<small class="text-danger">*upload pdf file only</small>
</div>
<div class="mb-3 d-flex flex-column align-items-center">
<label for="totalOTHours">Total OT Hours</label>
<input type="text" id="totalOTHours" class="form-control text-center" v-model="totalOTHours"
style="width: 200px;" readonly>
</div>
<div class="mb-3 d-flex flex-column align-items-center">
<label for="totalBreakHours">Total Break Hours</label>
<input type="text" id="totalBreakHours" class="form-control text-center" v-model="totalBreakHours"
style="width: 200px;" readonly>
</div>
</div>
</div>
<div class="d-flex justify-content-end mt-3">
<button class="btn btn-danger" v-on:click="goBack">Cancel</button>
<button class="btn btn-success ms-3" v-on:click="updateRecord">Update</button>
</div>
</div>
</div>
</div>
@section Scripts {
@{
await Html.RenderPartialAsync("_ValidationScriptsPartial");
}
<script>
const app = Vue.createApp({
data() {
return {
editForm: {
overtimeId: null,
otDate: "",
officeFrom: "",
officeTo: "",
officeBreak: 0,
outsideFrom: "",
outsideTo: "",
outsideBreak: 0,
stationId: "",
otDescription: "",
otDays: "",
pdfBase64: "",
userId: null,
},
airstationList: [],
totalOTHours: "0 hr 0 min",
totalBreakHours: "0 hr 0 min",
uploadedFile: null,
currentUser: null,
isPSTWAIR: false,
};
},
computed: {
charCount() {
return this.editForm.otDescription.length;
}
},
async mounted() {
const urlParams = new URLSearchParams(window.location.search);
const overtimeId = urlParams.get('overtimeId');
if (overtimeId) {
await this.fetchOvertimeRecord(overtimeId);
}
await this.fetchUser();
if (this.isPSTWAIR) {
await this.fetchStations();
}
},
methods: {
async fetchOvertimeRecord(id) {
try {
const res = await fetch(`/OvertimeAPI/GetOvertimeRecordById/${id}`);
if (res.ok) {
const data = await res.json();
this.populateForm(data); // Fill form fields with data
} else {
alert("Failed to fetch overtime record.");
}
} catch (err) {
console.error("Fetch error:", err);
}
},
populateForm(record) {
this.editForm = {
...record,
otDate: record.otDate.slice(0, 10),
selectedDayType: record.otDays,
};
this.calculateOTAndBreak();
},
async fetchStations() {
try {
const response = await fetch(`/OvertimeAPI/GetStationsByDepartment`);
if (!response.ok) throw new Error("Failed to fetch stations");
this.airstationList = await response.json();
} catch (error) {
console.error("Error fetching stations:", error);
}
},
async fetchUser() {
try {
const response = await fetch(`/IdentityAPI/GetUserInformation/`, { method: 'POST' });
if (response.ok) {
const data = await response.json();
this.currentUser = data?.userInfo || null;
this.userId = this.currentUser?.id || null;
console.log("Fetched User:", this.currentUser);
console.log("Dept ID:", this.currentUser?.departmentId);
if (this.currentUser?.department?.departmentId === 2) {
this.isPSTWAIR = true;
console.log("User is PSTW AIR");
}
} else {
console.error(`Failed to fetch user: ${response.statusText}`);
}
} catch (error) {
console.error("Error fetching user:", error);
}
},
limitCharCount(event) {
if (this.editForm.otDescription && this.editForm.otDescription.length > 150) {
this.editForm.otDescription = this.editForm.otDescription.substring(0, 150);
event.preventDefault();
}
},
calculateOTAndBreak() {
let officeOT = this.calculateTimeDifference(
this.editForm.officeFrom,
this.editForm.officeTo,
this.editForm.officeBreak
);
let outsideOT = this.calculateTimeDifference(
this.editForm.outsideFrom,
this.editForm.outsideTo,
this.editForm.outsideBreak
);
let totalOTMinutes = officeOT.minutes + outsideOT.minutes;
let totalOTHours = officeOT.hours + outsideOT.hours + Math.floor(totalOTMinutes / 60);
totalOTMinutes = totalOTMinutes % 60;
this.totalOTHours = `${totalOTHours} hr ${totalOTMinutes} min`;
let totalBreakMinutes = (this.editForm.officeBreak || 0) + (this.editForm.outsideBreak || 0);
let totalBreakHours = Math.floor(totalBreakMinutes / 60);
totalBreakMinutes = totalBreakMinutes % 60;
this.totalBreakHours = `${totalBreakHours} hr ${totalBreakMinutes} min`;
},
calculateTimeDifference(startTime, endTime, breakMinutes) {
if (!startTime || !endTime) {
return { hours: 0, minutes: 0 };
}
const start = this.parseTime(startTime);
const end = this.parseTime(endTime);
let diffMinutes = (end.hours * 60 + end.minutes) - (start.hours * 60 + start.minutes);
if (diffMinutes < 0) {
diffMinutes += 24 * 60;
}
diffMinutes -= breakMinutes || 0;
const hours = Math.floor(diffMinutes / 60);
const minutes = diffMinutes % 60;
return { hours, minutes };
},
parseTime(timeString) {
const [hours, minutes] = timeString.split(':').map(Number);
return { hours, minutes };
},
handleFileUpload(event) {
const file = event.target.files[0];
if (file && file.type === "application/pdf") {
const reader = new FileReader();
reader.onload = () => {
this.editForm.pdfBase64 = reader.result.split(',')[1]; // Remove data URI prefix
};
reader.readAsDataURL(file);
} else {
alert("Please upload a valid PDF file.");
}
},
formatTime(timeString) {
if (!timeString) return null;
const [hours, minutes] = timeString.split(':');
return `${hours.padStart(2, '0')}:${minutes.padStart(2, '0')}:00`; //HH:mm:ss format
},
async updateRecord() {
try {
const payload = {
...this.editForm,
otDays: this.editForm.selectedDayType, // ensure correct day value
officeFrom: this.formatTime(this.editForm.officeFrom),
officeTo: this.formatTime(this.editForm.officeTo),
outsideFrom: this.formatTime(this.editForm.outsideFrom),
outsideTo: this.formatTime(this.editForm.outsideTo)
};
const res = await fetch('/OvertimeAPI/UpdateOvertimeRecord', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (res.ok) {
alert("Record updated successfully.");
window.location.href = "/OTcalculate/Overtime/OtRecords";
} else {
const errorText = await res.text();
console.error("Server returned:", errorText);
alert("Failed to update record.");
}
} catch (err) {
console.error("Update error:", err);
}
},
goBack() {
window.location.href = "/OTcalculate/Overtime/OtRecords";
},
}
});
app.mount("#app");
</script>
}

View File

@ -297,7 +297,7 @@
}, },
editRecord(index) { editRecord(index) {
const record = this.filteredRecords[index]; const record = this.filteredRecords[index];
window.location.href = `/OTcalculate/Overtime/EditOvertime?id=${record.overtimeId}`; window.location.href = `/OTcalculate/Overtime/EditOvertime?overtimeId=${record.overtimeId}`;
}, },
async deleteRecord(index) { async deleteRecord(index) {
const record = this.filteredRecords[index]; const record = this.filteredRecords[index];
@ -322,7 +322,7 @@
const blobUrl = URL.createObjectURL(blob); const blobUrl = URL.createObjectURL(blob);
const printWindow = window.open(blobUrl, '_blank'); const printWindow = window.open(blobUrl, '_blank');
// Automatically trigger print after window loads // Trigger print after window loads
printWindow.onload = () => { printWindow.onload = () => {
printWindow.focus(); printWindow.focus();
printWindow.print(); printWindow.print();

View File

@ -100,7 +100,8 @@
<div class="mb-3 mt-3"> <div class="mb-3 mt-3">
<label for="fileUpload">Upload File:</label> <label for="fileUpload">Upload File:</label>
<input type="file" id="fileUpload" class="form-control" v-on:change="handleFileUpload" ref="fileInput"> <input type="file" id="fileUpload" class="form-control" accept="application/pdf"
v-on:change="handleFileUpload" ref="fileInput">
<small class="text-danger">*upload pdf file only</small> <small class="text-danger">*upload pdf file only</small>
</div> </div>

View File

@ -561,7 +561,6 @@ 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();
// Optional: load logo image as byte array
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");
@ -576,5 +575,50 @@ namespace PSTW_CentralSystem.Controllers.API
#endregion #endregion
[HttpGet("GetOvertimeRecordById/{id}")]
public async Task<IActionResult> GetOvertimeRecordById(int id)
{
var record = await _centralDbContext.Otregisters.FindAsync(id);
if (record == null)
return NotFound();
return Ok(record);
}
[HttpPut("UpdateOvertimeRecord")]
public IActionResult UpdateOvertimeRecord([FromBody] OtRegisterModel model)
{
try
{
var existing = _centralDbContext.Otregisters.FirstOrDefault(o => o.OvertimeId == model.OvertimeId);
if (existing == null)
return NotFound("Overtime record not found.");
// Update fields
existing.OtDate = model.OtDate;
existing.OfficeFrom = model.OfficeFrom;
existing.OfficeTo = model.OfficeTo;
existing.OfficeBreak = model.OfficeBreak;
existing.OutsideFrom = model.OutsideFrom;
existing.OutsideTo = model.OutsideTo;
existing.OutsideBreak = model.OutsideBreak;
existing.StationId = model.StationId;
existing.OtDescription = model.OtDescription;
existing.OtDays = model.OtDays;
existing.PDFBase64 = model.PDFBase64;
existing.UserId = model.UserId;
_centralDbContext.SaveChanges();
return Ok(new { message = "Record updated successfully." });
}
catch (Exception ex)
{
_logger.LogError(ex, "Error updating overtime record.");
return StatusCode(500, "Failed to update record.");
}
}
} }
} }