528 lines
24 KiB
Plaintext
528 lines
24 KiB
Plaintext
@{
|
|
ViewData["Title"] = "Calendar Update";
|
|
Layout = "~/Views/Shared/_Layout.cshtml";
|
|
}
|
|
|
|
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
|
<div class="container">
|
|
<div class="row justify-content-center">
|
|
<div class="col-6 col-md-6 col-lg-3">
|
|
<div class="card card-hover">
|
|
<a asp-area="OTcalculate" asp-controller="HrDashboard" asp-action="Rate">
|
|
<div class="box bg-success text-center">
|
|
<h1 class="font-light text-white">
|
|
<i class="mdi mdi-currency-usd"></i>
|
|
</h1>
|
|
<h6 class="text-white">Salary</h6>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-6 col-md-6 col-lg-3">
|
|
<div class="card card-hover">
|
|
<a asp-area="OTcalculate" asp-controller="HrDashboard" asp-action="Calendar">
|
|
<div class="box bg-purple text-center">
|
|
<h1 class="font-light text-white">
|
|
<i class="mdi mdi-calendar"></i>
|
|
</h1>
|
|
<h6 class="text-white">Calendar</h6>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-6 col-md-6 col-lg-3">
|
|
<div class="card card-hover">
|
|
<a asp-area="OTcalculate" asp-controller="HrDashboard" asp-action="HrUserSetting">
|
|
<div class="box bg-megna text-center">
|
|
<h1 class="font-light text-white">
|
|
<i class="mdi mdi-account-settings"></i>
|
|
</h1>
|
|
<h6 class="text-white">User Setting</h6>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div id="app">
|
|
<div class="container mt-3">
|
|
|
|
@* Tab Page *@
|
|
<ul class="nav nav-tabs">
|
|
<li class="nav-item">
|
|
<a class="nav-link"
|
|
:class="{ 'bg-purple text-white': activeTab === 'holiday', 'bg-light text-dark': activeTab !== 'holiday' }"
|
|
style="border: 1px solid #ddd;"
|
|
v-on:click="changeTab('holiday')">
|
|
Update Holiday
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link"
|
|
:class="{ 'bg-purple text-white': activeTab === 'weekend', 'bg-light text-dark': activeTab !== 'weekend' }"
|
|
style="border: 1px solid #ddd;"
|
|
v-on:click="changeTab('weekend')">
|
|
Update Weekend
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
|
|
<div class="tab-content mt-3">
|
|
|
|
@* Update Holiday *@
|
|
<div v-if="activeTab === 'holiday'" class="card shadow-sm">
|
|
<div class="card-body">
|
|
<label>Date:</label>
|
|
<input type="date" class="form-control" v-model="selectedDate">
|
|
</div>
|
|
<div class="card-body">
|
|
<label>State:</label>
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<div class="form-check">
|
|
<input type="checkbox" class="form-check-input" v-model="selectAllChecked">
|
|
<label class="form-check-label"><strong>Select All</strong></label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4" v-for="state in stateList" :key="state.stateId">
|
|
<div class="form-check">
|
|
<input type="checkbox" class="form-check-input" :value="state.stateId" v-model="selectedStates">
|
|
<label class="form-check-label">{{ state.stateName }}</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card-body">
|
|
<label>Holiday:</label>
|
|
<input type="text" class="form-control" v-model="holidayName" placeholder="Enter Holiday Name">
|
|
</div>
|
|
|
|
<div class="card-body text-center">
|
|
<button class="btn btn-danger" v-on:click="clearForm">Clear</button>
|
|
<button class="btn btn-success ms-3" v-on:click="updateHoliday">Save Holiday</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="activeTab === 'holiday'" class="card shadow-sm">
|
|
<div class="card-body">
|
|
<label for="stateDropdown">Select State:</label>
|
|
<select id="stateDropdown" class="form-control" v-model="selectedState">
|
|
<option v-for="state in stateList" :key="state.stateId" :value="state.stateId">
|
|
{{ state.stateName }}
|
|
</option>
|
|
</select>
|
|
</div>
|
|
<div class="card-body mt-3 border-top pt-4">
|
|
<h6 class="text-uppercase fw-bold text-secondary mb-3 small">Holiday Schedule</h6>
|
|
|
|
<div v-if="filteredHolidays.length > 0" class="table-responsive bg-white rounded shadow-sm">
|
|
<table class="table table-hover table-borderless align-middle mb-0">
|
|
<thead class="table-light text-muted small text-uppercase">
|
|
<tr>
|
|
<th class="ps-4 py-3 rounded-start">Date</th>
|
|
<th class="py-3">Holiday Name</th>
|
|
<th class="text-end pe-4 py-3 rounded-end">Action</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="holiday in filteredHolidays" :key="holiday.holidayId" class="border-bottom">
|
|
<td class="ps-4 fw-semibold text-primary">
|
|
<i class="mdi mdi-calendar-check me-2"></i>{{ formatDate(holiday.holidayDate) }}
|
|
</td>
|
|
<td class="fw-bold text-dark">{{ holiday.holidayName }}</td>
|
|
<td class="text-end pe-4">
|
|
<button class="btn btn-sm btn-outline-danger px-3 rounded-pill" v-on:click="deleteHoliday(holiday.holidayId)">
|
|
<i class="mdi mdi-trash-can-outline me-1"></i> Remove
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div v-else class="text-center py-5 bg-light rounded shadow-sm border border-light">
|
|
<i class="mdi mdi-folder-open text-muted opacity-25 display-4"></i>
|
|
<h6 class="text-muted mt-3 fw-bold">No Data Available</h6>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@* Update Weekend *@
|
|
<div v-if="activeTab === 'weekend'" class="card shadow-sm">
|
|
<div class="card-body">
|
|
<label>State:</label>
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<div class="form-check">
|
|
<input type="checkbox" class="form-check-input" v-model="selectAllChecked">
|
|
<label class="form-check-label"><strong>Select All</strong></label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4" v-for="state in stateList" :key="state.stateId">
|
|
<div class="form-check">
|
|
<input type="checkbox" class="form-check-input" :value="state.stateId" v-model="selectedStates">
|
|
<label class="form-check-label">{{ state.stateName }}</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card-body">
|
|
<label for="dayDropdown">Days:</label>
|
|
<select id="dayDropdown" class="form-control" v-model="selectedDay">
|
|
<option value="" disabled selected>Select Day</option>
|
|
<option v-for="weekend in dayList" :key="weekend.weekendId" :value="weekend.day">
|
|
{{ weekend.day }}
|
|
</option>
|
|
</select>
|
|
</div>
|
|
|
|
|
|
<div class="card-body text-center">
|
|
<button class="btn btn-danger" v-on:click="clearWeekend">Clear</button>
|
|
<button class="btn btn-success ms-3" v-on:click="updateWeekend">Save Weekend</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="activeTab === 'weekend'" class="card shadow-sm border-0 mt-3">
|
|
<div class="card-body bg-white rounded border pt-4 pb-5">
|
|
<h6 class="text-uppercase fw-bold text-secondary mb-4 text-center">
|
|
<i class="mdi mdi-calendar-weekend text-purple me-1"></i> Assigned Weekends
|
|
</h6>
|
|
|
|
<div v-if="Object.keys(groupedWeekends).length === 0" class="text-center py-5 bg-light rounded">
|
|
<i class="mdi mdi-folder-open text-muted opacity-25 display-4"></i>
|
|
<h6 class="text-muted mt-3 fw-bold">No Data Available</h6>
|
|
<p class="text-secondary small mb-0">Assign states to a weekend above.</p>
|
|
</div>
|
|
|
|
<div class="row g-4 justify-content-center">
|
|
<div class="col-md-6" v-for="(states, day) in groupedWeekends" :key="day">
|
|
<div class="card border border-light-subtle shadow-sm h-100">
|
|
|
|
<div class="card-header bg-light border-bottom-0 py-3 d-flex justify-content-between align-items-center">
|
|
<h6 class="fw-bold mb-0 text-dark">
|
|
<i class="mdi mdi-clock-outline me-2 text-primary"></i>{{ day }}
|
|
</h6>
|
|
<span class="badge bg-primary rounded-pill">{{ states.length }}</span>
|
|
</div>
|
|
|
|
<div class="card-body pt-3 pb-4">
|
|
<div class="d-flex flex-wrap gap-2">
|
|
<span class="badge border text-secondary bg-white px-3 py-2 shadow-sm" style="font-size: 0.85rem;" v-for="state in states" :key="state">
|
|
<i class="mdi mdi-map-marker-outline me-1"></i> {{ state }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
@* <div class="card-body pt-3 pb-4">
|
|
<div class="d-flex flex-wrap gap-2">
|
|
<span class="badge border text-secondary bg-white px-3 py-2 shadow-sm d-flex align-items-center" style="font-size: 0.85rem;" v-for="state in states" :key="state">
|
|
|
|
<img :src="getStateFlag(state)" alt="Flag" class="me-2" style="width: 20px; height: 14px; object-fit: cover; border-radius: 2px; border: 1px solid #ddd;">
|
|
|
|
{{ state }}
|
|
</span>
|
|
</div>
|
|
</div> *@
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@section Scripts {
|
|
<script src="https://cdn.jsdelivr.net/npm/vue@3.3.4/dist/vue.global.js"></script>
|
|
|
|
<script>
|
|
const app = Vue.createApp({
|
|
data() {
|
|
return {
|
|
activeTab: 'holiday',
|
|
stateList: [],
|
|
selectedDate: '',
|
|
selectedStates: [],
|
|
holidayName: '',
|
|
selectedState: null,
|
|
holidayList: [],
|
|
selectedDay: "",
|
|
weekendList: [],
|
|
stateWeekends: [],
|
|
};
|
|
},
|
|
|
|
mounted() {
|
|
this.fetchStates();
|
|
this.fetchHolidays();
|
|
this.fetchWeekends();
|
|
this.fetchStateWeekends();
|
|
|
|
},
|
|
|
|
computed: {
|
|
filteredHolidays() {
|
|
return this.selectedState
|
|
? this.holidayList
|
|
.filter(h => h.stateId === this.selectedState)
|
|
.sort((a, b) => new Date(a.holidayDate) - new Date(b.holidayDate))
|
|
: [];
|
|
},
|
|
groupedWeekends() {
|
|
const grouped = {};
|
|
this.stateWeekends.forEach(weekend => {
|
|
if (!grouped[weekend.day]) {
|
|
grouped[weekend.day] = [];
|
|
}
|
|
grouped[weekend.day].push(weekend.stateName);
|
|
});
|
|
return grouped;
|
|
},
|
|
|
|
selectAllChecked: {
|
|
|
|
get() {
|
|
return this.stateList.length > 0 && this.selectedStates.length === this.stateList.length;
|
|
},
|
|
|
|
set(value) {
|
|
if (value) {
|
|
// If checked, select all states
|
|
this.selectedStates = this.stateList.map(state => state.stateId);
|
|
} else {
|
|
// If unchecked, clear all states
|
|
this.selectedStates = [];
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
methods: {
|
|
changeTab(tab) {
|
|
this.activeTab = tab;
|
|
},
|
|
|
|
async fetchStates() {
|
|
try {
|
|
const response = await fetch("/OvertimeAPI/GetStatesName");
|
|
if (!response.ok) throw new Error("Failed to fetch states");
|
|
|
|
const data = await response.json();
|
|
this.stateList = data.map(state => ({
|
|
stateId: state.stateId,
|
|
stateName: state.stateName
|
|
}));
|
|
|
|
if (this.stateList.length > 0) {
|
|
this.selectedState = this.stateList[0].stateId;
|
|
}
|
|
} catch (error) {
|
|
console.error("Error fetching states:", error);
|
|
alert("Failed to load state list.");
|
|
}
|
|
},
|
|
|
|
async fetchHolidays() {
|
|
try {
|
|
const response = await fetch("/OvertimeAPI/GetAllHolidays");
|
|
if (!response.ok) throw new Error("Failed to fetch holidays");
|
|
|
|
this.holidayList = await response.json();
|
|
} catch (error) {
|
|
console.error("Error fetching holidays:", error);
|
|
alert("Failed to load holiday list.");
|
|
}
|
|
},
|
|
|
|
formatDate(date) {
|
|
if (!date) return '';
|
|
const d = new Date(date);
|
|
|
|
const day = String(d.getDate()).padStart(2, '0');
|
|
const month = String(d.getMonth() + 1).padStart(2, '0');
|
|
const year = d.getFullYear();
|
|
|
|
return `${day}/${month}/${year}`;
|
|
},
|
|
|
|
async updateHoliday() {
|
|
try {
|
|
if (!this.selectedDate || this.selectedStates.length === 0 || !this.holidayName) {
|
|
alert("Please fill in all fields.");
|
|
return;
|
|
}
|
|
|
|
const payload = this.selectedStates.map(stateId => ({
|
|
holidayDate: this.selectedDate,
|
|
stateId: stateId,
|
|
holidayName: this.holidayName
|
|
}));
|
|
|
|
const response = await fetch("/OvertimeAPI/UpdateHoliday", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(payload)
|
|
});
|
|
|
|
if (response.ok) {
|
|
alert("Holiday updated successfully!");
|
|
this.clearForm();
|
|
this.fetchHolidays();
|
|
} else {
|
|
alert("Failed to update holiday.");
|
|
}
|
|
} catch (error) {
|
|
console.error("Error updating holiday:", error);
|
|
alert("An error occurred while updating holiday.");
|
|
}
|
|
},
|
|
|
|
async deleteHoliday(holidayId) {
|
|
if (!confirm("Are you sure you want to delete this holiday?")) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`/OvertimeAPI/DeleteHoliday/${holidayId}`, {
|
|
method: "DELETE"
|
|
});
|
|
|
|
if (response.ok) {
|
|
alert("Holiday deleted successfully!");
|
|
this.fetchHolidays();
|
|
} else {
|
|
alert("Failed to delete holiday.");
|
|
}
|
|
} catch (error) {
|
|
console.error("Error deleting holiday:", error);
|
|
alert("An error occurred while deleting the holiday.");
|
|
}
|
|
},
|
|
|
|
async fetchWeekends() {
|
|
try {
|
|
const response = await fetch("/OvertimeAPI/GetWeekendDay");
|
|
if (!response.ok) throw new Error("Failed to fetch weekends");
|
|
|
|
this.dayList = await response.json();
|
|
} catch (error) {
|
|
console.error("Error fetching weekends:", error);
|
|
alert("Failed to load weekend list.");
|
|
}
|
|
},
|
|
|
|
async fetchStateWeekends() {
|
|
try {
|
|
const response = await fetch("/OvertimeAPI/GetStateWeekends");
|
|
if (!response.ok) throw new Error("Failed to fetch state weekends");
|
|
|
|
this.stateWeekends = await response.json();
|
|
console.log("Fetched state weekends:", this.stateWeekends);
|
|
} catch (error) {
|
|
console.error("Error fetching state weekends:", error);
|
|
alert("Failed to load state weekend data.");
|
|
}
|
|
},
|
|
|
|
async updateWeekend() {
|
|
try {
|
|
if (this.selectedStates.length === 0 || !this.selectedDay) {
|
|
alert("Please select at least one state and a day.");
|
|
return;
|
|
}
|
|
|
|
const selectedDayObject = this.dayList.find(day => day.day === this.selectedDay);
|
|
if (!selectedDayObject) {
|
|
alert("Selected day not found in the list.");
|
|
return;
|
|
}
|
|
|
|
const payload = this.selectedStates.map(stateId => {
|
|
const state = this.stateList.find(s => s.stateId === stateId);
|
|
return {
|
|
StateId: stateId,
|
|
StateName: state.stateName,
|
|
WeekendId: selectedDayObject.weekendId
|
|
};
|
|
});
|
|
|
|
console.log("Payload being sent to API:", payload);
|
|
|
|
const response = await fetch("/OvertimeAPI/UpdateWeekend", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(payload)
|
|
});
|
|
|
|
if (response.ok) {
|
|
alert("Weekend updated successfully!");
|
|
this.clearWeekend();
|
|
await this.fetchStateWeekends();
|
|
} else {
|
|
alert("Failed to update weekend.");
|
|
}
|
|
} catch (error) {
|
|
console.error("Error updating weekend:", error);
|
|
alert("An error occurred while updating weekend.");
|
|
}
|
|
},
|
|
|
|
// getStateFlag(stateName) {
|
|
// if (!stateName) return '';
|
|
|
|
// const name = stateName.toLowerCase();
|
|
|
|
// Dictionary mapping state names to Wikimedia Commons flag URLs
|
|
// const flags = {
|
|
// 'johor': 'https:upload.wikimedia.org/wikipedia/commons/5/5a/Flag_of_Johor.svg',
|
|
// 'kedah': 'https:upload.wikimedia.org/wikipedia/commons/c/cc/Flag_of_Kedah.svg',
|
|
// 'kelantan': 'https:upload.wikimedia.org/wikipedia/commons/6/61/Flag_of_Kelantan.svg',
|
|
// 'melaka': 'https:upload.wikimedia.org/wikipedia/commons/0/09/Flag_of_Malacca.svg',
|
|
// 'malacca': 'https:upload.wikimedia.org/wikipedia/commons/0/09/Flag_of_Malacca.svg',
|
|
// 'negeri sembilan': 'https:upload.wikimedia.org/wikipedia/commons/d/db/Flag_of_Negeri_Sembilan.svg',
|
|
// 'pahang': 'https:upload.wikimedia.org/wikipedia/commons/a/aa/Flag_of_Pahang.svg',
|
|
// 'penang': 'https:upload.wikimedia.org/wikipedia/commons/d/d4/Flag_of_Penang_%28Malaysia%29.svg',
|
|
// 'pulau pinang': 'https:upload.wikimedia.org/wikipedia/commons/d/d4/Flag_of_Penang_%28Malaysia%29.svg',
|
|
// 'perak': 'https:upload.wikimedia.org/wikipedia/commons/8/87/Flag_of_Perak.svg',
|
|
// 'perlis': 'https:upload.wikimedia.org/wikipedia/commons/a/aa/Flag_of_Perlis.svg',
|
|
// 'sabah': 'https:upload.wikimedia.org/wikipedia/commons/b/b5/Flag_of_Sabah.svg',
|
|
// 'sarawak': 'https:upload.wikimedia.org/wikipedia/commons/7/7e/Flag_of_Sarawak.svg',
|
|
// 'selangor': 'https:upload.wikimedia.org/wikipedia/commons/0/0c/Flag_of_Selangor.svg',
|
|
// 'terengganu': 'https:upload.wikimedia.org/wikipedia/commons/6/6b/Flag_of_Terengganu.svg',
|
|
// 'kuala lumpur': 'https:upload.wikimedia.org/wikipedia/commons/6/64/Flag_of_Kuala_Lumpur%2C_Malaysia.svg',
|
|
// 'labuan': 'https:upload.wikimedia.org/wikipedia/commons/6/69/Flag_of_Labuan.svg',
|
|
// 'putrajaya': 'https:upload.wikimedia.org/wikipedia/commons/9/9f/Flag_of_Putrajaya.svg'
|
|
// };
|
|
|
|
// for (let key in flags) {
|
|
// if (name.includes(key)) {
|
|
// return flags[key];
|
|
// }
|
|
// }
|
|
|
|
// return 'https:flagcdn.com/w40/my.png';
|
|
// },
|
|
|
|
clearForm() {
|
|
this.selectedDate = '';
|
|
this.selectedStates = [];
|
|
this.holidayName = '';
|
|
this.selectedDay = '';
|
|
},
|
|
|
|
clearWeekend() {
|
|
this.selectedDay = "";
|
|
this.selectedStates = [];
|
|
}
|
|
}
|
|
});
|
|
|
|
app.mount('#app');
|
|
</script>
|
|
}
|