PSTW_CentralizeSystem/Areas/OTcalculate/Views/Overtime/OtRegister.cshtml
2025-04-08 17:21:11 +08:00

337 lines
15 KiB
Plaintext

@{
ViewData["Title"] = "Register Overtime";
Layout = "~/Views/Shared/_Layout.cshtml";
}
@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" id="dateInput" class="form-control" v-model="selectedDate"
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" id="officeFrom" class="form-control" v-model="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="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="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="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="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="outsideBreak"
v-on:input="calculateOTAndBreak" placeholder="e.g. 45">
</div>
</div>
<div class="mb-3">
<label for="airstationDropdown">Air Station</label>
<select id="airstationDropdown" class="form-control" v-model="selectedAirStation">
<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>
</div>
<div class="mb-3">
<label for="otDescription">Work Brief Description</label>
<textarea id="otDescription" class="form-control" v-model="otDescription"
placeholder="Describe the work done..."></textarea>
</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="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="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="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" 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="clearForm">Clear</button>
<button class="btn btn-success ms-3" v-on:click="addOvertime">Save</button>
</div>
</div>
</div>
</div>
@section Scripts {
@{
await Html.RenderPartialAsync("_ValidationScriptsPartial");
}
<script>
const app = Vue.createApp({
data() {
return {
selectedDate: "",
officeFrom: "",
officeTo: "",
officeBreak: 0,
outsideFrom: "",
outsideTo: "",
outsideBreak: 0,
selectedAirStation: "",
airstationList: [],
otDescription: "",
selectedDayType: "",
totalOTHours: "0 hr 0 min",
totalBreakHours: "0 hr 0 min",
uploadedFile: null,
currentUser: null,
userId: null,
};
},
async mounted() {
this.fetchStations();
await this.fetchUser();
},
methods: {
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;
} else {
console.error(`Failed to fetch user: ${response.statusText}`);
}
} catch (error) {
console.error("Error fetching user:", error);
}
},
calculateOTAndBreak() {
let officeOT = this.calculateTimeDifference(this.officeFrom, this.officeTo, this.officeBreak);
let outsideOT = this.calculateTimeDifference(this.outsideFrom, this.outsideTo, this.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.officeBreak || 0) + (this.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) {
this.uploadedFile = event.target.files[0];
},
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
},
async addOvertime() {
if (!this.selectedDate || !this.selectedDayType || !this.selectedAirStation) {
alert("Please fill in all required fields.");
return;
}
if (!this.uploadedFile || this.uploadedFile.type !== "application/pdf") {
alert("Please upload a valid PDF file.");
return;
}
if (!this.userId) {
console.error("User ID is not set!");
alert("User information is missing. Please try again.");
return;
}
console.log("Sending userId:", this.userId);
const reader = new FileReader();
reader.onload = async (event) => {
const base64String = event.target.result.split(',')[1];
// Generate a crypto key
const key = await crypto.subtle.generateKey(
{ name: "AES-GCM", length: 256 },
true,
["encrypt", "decrypt"]
);
// Export the key (for sending to backend if needed or for testing)
const exportedKey = await crypto.subtle.exportKey("jwk", key);
// Generate a random IV
const iv = crypto.getRandomValues(new Uint8Array(12));
// 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();
};
reader.onerror = () => {
console.error("Error reading file");
alert("Error reading the uploaded file.");
};
reader.readAsDataURL(this.uploadedFile);
},
clearForm() {
this.selectedDate = "";
this.officeFrom = "";
this.officeTo = "";
this.officeBreak = 0;
this.outsideFrom = "";
this.outsideTo = "";
this.outsideBreak = 0;
this.selectedAirStation = "";
this.otDescription = "";
this.selectedDayType = "";
this.totalOTHours = "0 hr 0 min";
this.totalBreakHours = "0 hr 0 min";
this.uploadedFile = null;
this.$refs.fileInput.value = '';
},
}
});
app.mount("#app");
</script>
}