휴가 css 수정

This commit is contained in:
dyhj625 2025-03-06 15:02:15 +09:00
parent cd1c707b12
commit 6904902755
5 changed files with 565 additions and 517 deletions

View File

@ -113,18 +113,17 @@ opacity: 0.6; /* 흐려 보이게 */
background: none !important;
box-shadow: none !important;
display: flex;
align-items: center;
align-items: flex-end;
justify-content: center;
width: 100%;
height: 100%;
padding-bottom: 20px;
}
/* 모달 본문 스타일 */
.vac-modal-content {
background: #fff;
border-radius: 8px;
box-shadow: 0px -4px 10px rgba(0, 0, 0, 0.1); /* 위쪽 그림자만 적용 */
padding: 20px;
max-width: 500px;
width: 100%;
position: relative;

View File

@ -14,6 +14,7 @@
<div class="content-backdrop fade"></div>
</div>
</div>
<TheChat />
</div>
<!-- Overlay -->
@ -27,6 +28,7 @@
import TheTop from './TheTop.vue';
import TheFooter from './TheFooter.vue';
import TheMenu from './TheMenu.vue';
import TheChat from './TheChat.vue';
import { nextTick } from 'vue';
import { wait } from '@/common/utils';
@ -45,4 +47,13 @@
loadScript('/js/main.js');
});
</script>
<style></style>
<style>
/* ✅ 중앙 콘텐츠 자동 조정 */
.layout-page {
flex-grow: 1;
min-width: 0; /* flexbox 내에서 올바른 크기 계산 */
margin-right: 350px; /* 채팅 사이드바의 너비만큼 밀리도록 설정 */
}
</style>

43
src/layouts/TheChat.vue Normal file
View File

@ -0,0 +1,43 @@
<template>
<!-- Chat Sidebar -->
<aside id="chat-sidebar" class="chat-sidebar">
</aside>
</template>
<script setup>
import { ref } from "vue";
//
const messages = ref([
{ user: "사용자1", text: "안녕하세요!" },
{ user: "사용자2", text: "안녕하세요. 반갑습니다." }
]);
const newMessage = ref("");
//
const sendMessage = () => {
if (newMessage.value.trim() !== "") {
messages.value.push({ user: "나", text: newMessage.value });
newMessage.value = "";
}
};
</script>
<style scoped>
/* ✅ 채팅 사이드바 고정 */
.chat-sidebar {
width: 350px;
height: 100vh;
position: fixed;
right: 0;
top: 0;
background: #fff;
border-left: 1px solid #ddd;
box-shadow: -2px 0 10px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
z-index: 1000;
}
</style>

View File

@ -265,4 +265,5 @@
router.push('/login');
};
</script>
<style></style>
<style scoped>
</style>

View File

@ -63,45 +63,46 @@
<script setup>
import { reactive, ref, onMounted, nextTick, computed, watch, onBeforeUnmount } from "vue";
import axios from "@api";
import "bootstrap-icons/font/bootstrap-icons.css";
//
import FullCalendar from "@fullcalendar/vue3";
import dayGridPlugin from "@fullcalendar/daygrid";
import interactionPlugin from "@fullcalendar/interaction";
// Flatpickr MonthSelect
import "@/assets/css/app-calendar.css";
// Flatpickr, MonthSelect
import flatpickr from "flatpickr";
import monthSelectPlugin from "flatpickr/dist/plugins/monthSelect/index";
import "flatpickr/dist/flatpickr.min.css";
import "flatpickr/dist/plugins/monthSelect/style.css";
import "@/assets/css/app-calendar.css";
import "bootstrap-icons/font/bootstrap-icons.css";
//
import HalfDayButtons from "@c/button/HalfDayButtons.vue";
import ProfileList from "@c/vacation/ProfileList.vue";
import VacationModal from "@c/modal/VacationModal.vue";
import VacationGrantModal from "@c/modal/VacationGrantModal.vue";
import { fetchHolidays } from "@c/calendar/holiday.js";
//
import { useUserStore } from "@s/userList";
import { useUserInfoStore } from "@s/useUserInfoStore";
import { fetchHolidays } from "@c/calendar/holiday.js";
import { useToastStore } from '@s/toastStore';
//
import { useRouter } from "vue-router";
const router = useRouter();
//
const toastStore = useToastStore();
const userStore = useUserInfoStore();
const userListStore = useUserStore();
//
const userList = ref([]);
const userColors = ref({});
const myVacations = ref([]); //
//
const myVacations = ref([]);
const receivedVacations = ref([]);
const isModalOpen = ref(false);
const remainingVacationData = ref({});
const selectedDate = ref(null);
const lastRemainingYear = ref(new Date().getFullYear());
const lastRemainingMonth = ref(String(new Date().getMonth() + 1).padStart(2, "0"));
//
const isModalOpen = ref(false);
const isGrantModalOpen = ref(false);
const selectedUser = ref(null);
// FullCalendar
// FullCalendar
const fullCalendarRef = ref(null);
const calendarEvents = ref([]);
const selectedDates = ref(new Map());
@ -110,45 +111,15 @@ const router = useRouter();
const holidayDates = ref(new Set());
const fetchedEvents = ref([]);
const halfDayButtonsRef = ref(null);
//
router.beforeEach((to, from, next) => {
if (hasChanges.value) {
const answer = window.confirm("저장하지 않은 변경 사항이 있습니다. 이동하시겠습니까?");
if (!answer) {
return next(false); //
}
}
next();
});
onBeforeUnmount(() => {
window.removeEventListener("beforeunload", preventUnsavedChanges);
});
function preventUnsavedChanges(event) {
if (hasChanges.value) {
event.preventDefault();
event.returnValue = ""; //
}
}
// `selectedDates`
watch(
() => Array.from(selectedDates.value.keys()), //
(newKeys) => {
if (halfDayButtonsRef.value) {
halfDayButtonsRef.value.resetHalfDay();
}
},
{ deep: true }
);
const selectedDate = ref(null);
const selectedUser = ref(null);
const lastRemainingYear = ref(new Date().getFullYear());
const lastRemainingMonth = ref(String(new Date().getMonth() + 1).padStart(2, "0"));
// ref
const calendarDatepicker = ref(null);
let fpInstance = null;
/** 변경사항 여부 확인 */
/* 변경사항 여부 확인 */
const hasChanges = computed(() => {
return (
selectedDates.value.size > 0 ||
@ -156,60 +127,8 @@ const hasChanges = computed(() => {
);
});
/** selectedDates가 변경될 때 버튼 상태 즉시 업데이트 */
watch(
() => Array.from(selectedDates.value.keys()), // keys() Array
(newKeys) => {
},
{ deep: true }
);
function handleDateClick(info) {
const clickedDateStr = info.dateStr;
const clickedDate = info.date;
const todayStr = new Date().toISOString().split("T")[0];
if (
clickedDate.getDay() === 0 ||
clickedDate.getDay() === 6 ||
holidayDates.value.has(clickedDateStr) ||
clickedDateStr < todayStr
) {
return;
}
const isMyVacation = myVacations.value.some(vac => {
const vacDate = vac.date ? String(vac.date).substring(0, 10) : "";
return vacDate === clickedDateStr && !vac.receiverId;
});
if (isMyVacation) {
if (selectedDates.value.get(clickedDateStr) === "delete") {
selectedDates.value.delete(clickedDateStr);
} else {
selectedDates.value.set(clickedDateStr, "delete");
}
updateCalendarEvents();
return;
}
if (selectedDates.value.has(clickedDateStr)) {
selectedDates.value.delete(clickedDateStr);
updateCalendarEvents();
return;
}
const type = halfDayType.value
? (halfDayType.value === "AM" ? "700101" : "700102")
: "700103";
selectedDates.value.set(clickedDateStr, type);
halfDayType.value = null;
updateCalendarEvents();
//
if (halfDayButtonsRef.value) {
halfDayButtonsRef.value.resetHalfDay();
}
}
/* 캘린더 설정 */
// ,
const calendarOptions = reactive({
plugins: [dayGridPlugin, interactionPlugin],
initialView: "dayGridMonth",
@ -224,98 +143,112 @@ function handleDateClick(info) {
datesSet: handleMonthChange,
events: calendarEvents,
});
onMounted(async () => {
await userStore.userInfo();
await fetchRemainingVacation();
const currentYear = new Date().getFullYear();
await fetchVacationHistory(currentYear);
window.addEventListener("beforeunload", preventUnsavedChanges);
// Flatpickr ( )
fpInstance = flatpickr(calendarDatepicker.value, {
dateFormat: "Y-m",
plugins: [
new monthSelectPlugin({
shorthand: true,
dateFormat: "Y-m",
altFormat: "F Y"
})
],
onChange: function(selectedDatesArr, dateStr) {
//
fullCalendarRef.value.getApi().gotoDate(dateStr + "-01");
const [year, month] = dateStr.split("-");
lastRemainingYear.value = parseInt(year, 10);
lastRemainingMonth.value = month;
loadCalendarData(lastRemainingYear.value, lastRemainingMonth.value);
},
onClose: function() {
calendarDatepicker.value.style.display = "none";
//
function handleMonthChange(viewInfo) {
const currentDate = viewInfo.view.currentStart;
const year = currentDate.getFullYear();
const month = String(currentDate.getMonth() + 1).padStart(2, "0");
loadCalendarData(year, month);
}
});
// FullCalendar (.fc-toolbar-title)
nextTick(() => {
const titleEl = document.querySelector('.fc-toolbar-title');
if (titleEl) {
titleEl.style.cursor = 'pointer';
titleEl.addEventListener('click', () => {
//
const dpEl = calendarDatepicker.value;
dpEl.style.display = 'block';
dpEl.style.position = 'fixed';
dpEl.style.top = '22%';
dpEl.style.left = '66%';
dpEl.style.transform = 'translate(-50%, -50%)';
dpEl.style.zIndex = '9999';
dpEl.style.border = 'none';
dpEl.style.outline = 'none';
dpEl.style.backgroundColor = 'transparent';
fpInstance.open();
});
//
function handleDateClick(info) {
const clickedDateStr = info.dateStr;
const clickedDate = info.date;
const todayStr = new Date().toISOString().split("T")[0];
if (
clickedDate.getDay() === 0 ||
clickedDate.getDay() === 6 ||
holidayDates.value.has(clickedDateStr) ||
clickedDateStr < todayStr
){
return;
}
const isMyVacation = myVacations.value.some(vac => {
const vacDate = vac.date ? String(vac.date).substring(0, 10) : "";
return vacDate === clickedDateStr && !vac.receiverId;
});
})
// API ( )
async function fetchVacationHistory(year) {
try {
const response = await axios.get(`vacation/history?year=${year}`);
if (response.status === 200 && response.data) {
myVacations.value = response.data.data.usedVacations || [];
receivedVacations.value = response.data.data.receivedVacations || [];
if (isMyVacation) {
if (selectedDates.value.get(clickedDateStr) === "delete") {
selectedDates.value.delete(clickedDateStr);
} else {
console.warn("❌ 연차 내역을 불러오지 못했습니다.");
myVacations.value = [];
receivedVacations.value = [];
selectedDates.value.set(clickedDateStr, "delete");
}
} catch (error) {
console.error("🚨 연차 데이터 불러오기 실패:", error);
updateCalendarEvents();
return;
}
if (selectedDates.value.has(clickedDateStr)) {
selectedDates.value.delete(clickedDateStr);
updateCalendarEvents();
return;
}
const type = halfDayType.value
? (halfDayType.value === "AM" ? "700101" : "700102")
: "700103";
selectedDates.value.set(clickedDateStr, type);
halfDayType.value = null;
updateCalendarEvents();
//
if (halfDayButtonsRef.value) {
halfDayButtonsRef.value.resetHalfDay();
}
}
//
function markClickableDates() {
nextTick(() => {
const todayStr = new Date().toISOString().split("T")[0]; // YYYY-MM-DD
const todayObj = new Date(todayStr);
watch(lastRemainingYear, async (newYear, oldYear) => {
await fetchVacationHistory(newYear);
document.querySelectorAll(".fc-daygrid-day").forEach((cell) => {
const dateStr = cell.getAttribute("data-date");
if (!dateStr) return; //
const dateObj = new Date(dateStr);
// (, )
if (dateObj.getDay() === 0 || dateObj.getDay() === 6 || holidayDates.value.has(dateStr)) {
cell.classList.remove("clickable");
cell.classList.add("fc-day-sat-sun");
}
// ( )
else if (dateObj < todayObj) {
cell.classList.remove("clickable");
cell.classList.add("past"); //
}
// & ( )
else {
cell.classList.add("clickable");
cell.classList.remove("past", "fc-day-sat-sun");
}
});
});
const fetchRemainingVacation = async () => {
try {
const response = await axios.get("vacation/remaining");
if (response.status === 200) {
remainingVacationData.value = response.data.data.reduce((acc, vacation) => {
acc[vacation.employeeId] = vacation.remainingQuota;
return acc;
}, {});
}
} catch (error) {
console.error("🚨 남은 연차 데이터를 불러오지 못했습니다:", error);
//
async function loadCalendarData(year, month) {
if (lastRemainingYear.value !== year || lastRemainingMonth.value !== month) {
await fetchRemainingVacation();
lastRemainingYear.value = year;
lastRemainingMonth.value = month;
}
fetchedEvents.value = [];
const [vacationEvents, holidayEvents] = await Promise.all([
fetchVacationData(year, month),
fetchHolidays(year, month),
]);
holidayDates.value = new Set(holidayEvents.map((event) => event.start));
fetchedEvents.value = [...vacationEvents, ...holidayEvents];
updateCalendarEvents();
await nextTick();
fullCalendarRef.value.getApi().refetchEvents();
}
};
/* 프로필 구역 */
//
const handleProfileClick = async (user) => {
try {
//
if (isModalOpen.value && user.MEMBERSEQ === userStore.user.id) {
return;
}
if (isGrantModalOpen.value && selectedUser.value?.MEMBERSEQ === user.MEMBERSEQ) {
return;
}
isModalOpen.value = false;
isGrantModalOpen.value = false;
if (user.MEMBERSEQ === userStore.user.id) {
@ -330,7 +263,7 @@ function handleDateClick(info) {
console.error("🚨 연차 데이터 불러오기 실패:", error);
}
};
//
const fetchUserList = async () => {
try {
await userListStore.fetchUserList();
@ -341,33 +274,27 @@ function handleDateClick(info) {
}
userColors.value = {};
userList.value.forEach((user) => {
userColors.value[user.MEMBERSEQ] = user.usercolor || "#FFFFFF";
userColors.value[user.MEMBERSEQ] = user.usercolor;
});
} catch (error) {
console.error("📌 사용자 목록 불러오기 오류:", error);
}
};
const fetchVacationCodes = async () => {
//
const fetchRemainingVacation = async () => {
try {
const response = await axios.get("vacation/codes");
if (response.status === 200 && response.data) {
vacationCodeMap.value = response.data.data.reduce((acc, item) => {
acc[item.code] = item.name;
const response = await axios.get("vacation/remaining");
if (response.status === 200) {
remainingVacationData.value = response.data.data.reduce((acc, vacation) => {
acc[vacation.employeeId] = vacation.remainingQuota;
return acc;
}, {});
} else {
console.warn("❌ 공통 코드 데이터를 불러오지 못했습니다.");
}
} catch (error) {
console.error("🚨 공통 코드 API 호출 실패:", error);
console.error("🚨 남은 연차 데이터를 불러오지 못했습니다:", error);
}
};
const getVacationType = (typeCode) => {
return vacationCodeMap.value[typeCode] || "기타";
};
//
const filteredMyVacations = computed(() => {
return myVacations.value.filter(vac => {
const dateStr = vac.date;
@ -375,7 +302,7 @@ function handleDateClick(info) {
return year === String(lastRemainingYear.value);
});
});
//
const filteredReceivedVacations = computed(() => {
return receivedVacations.value.filter(vac => {
const dateStr = vac.date;
@ -384,76 +311,8 @@ function handleDateClick(info) {
});
});
function updateCalendarEvents() {
const selectedEvents = Array.from(selectedDates.value)
.filter(([date, type]) => type !== "delete")
.map(([date, type]) => ({
start: date,
backgroundColor: "rgb(113 212 243 / 76%)",
textColor: "#fff",
display: "background",
classNames: [getVacationTypeClass(type), "selected-event"]
}));
const filteredFetchedEvents = fetchedEvents.value.filter(event => {
if (event.saved && selectedDates.value.get(event.start) === "delete") {
if (event.memberSeq === userStore.user.id) {
return false;
}
}
return true;
});
calendarEvents.value = [...filteredFetchedEvents, ...selectedEvents];
}
const getVacationTypeClass = (type) => {
if (type === "700101") return "half-day-am";
if (type === "700102") return "half-day-pm";
return "full-day";
};
function toggleHalfDay(type) {
halfDayType.value = halfDayType.value === type ? null : type;
}
//
async function fetchVacationData(year, month) {
try {
const response = await axios.get(`vacation/list/${year}/${month}`);
if (response.status === 200) {
const vacationList = response.data;
//
const filteredVacations = vacationList.filter(vac =>
userColors.value[vac.MEMBERSEQ] && userColors.value[vac.MEMBERSEQ] !== "#FFFFFF"
);
const events = filteredVacations.map(vac => {
let dateStr = vac.LOCVACUDT ? vac.LOCVACUDT.split("T")[0] : "";
let backgroundColor = userColors.value[vac.MEMBERSEQ];
return {
title: getVacationType(vac.LOCVACTYP),
start: dateStr,
backgroundColor,
classNames: [getVacationTypeClass(vac.LOCVACTYP)],
saved: true,
memberSeq: vac.MEMBERSEQ,
};
}).filter(event => event.start);
return events;
} else {
console.warn("📌 휴가 데이터를 불러오지 못함");
return [];
}
} catch (error) {
console.error("Error fetching vacation data:", error);
return [];
}
}
/* 휴가 변경사항 저장 */
async function saveVacationChanges() {
if (!hasChanges.value) return;
const selectedDatesArray = Array.from(selectedDates.value);
@ -481,6 +340,7 @@ async function fetchVacationData(year, month) {
});
if (response.data && response.data.status === "OK") {
toastStore.onToast('휴가 변경 사항이 저장되었습니다.', 's');
await fetchVacationHistory(lastRemainingYear.value);
await fetchRemainingVacation();
if (isModalOpen.value) {
await fetchVacationHistory(lastRemainingYear.value);
@ -498,72 +358,161 @@ async function fetchVacationData(year, month) {
}
}
function handleMonthChange(viewInfo) {
const currentDate = viewInfo.view.currentStart;
const year = currentDate.getFullYear();
const month = String(currentDate.getMonth() + 1).padStart(2, "0");
loadCalendarData(year, month);
/* 휴가 조회 */
//
async function fetchVacationHistory(year) {
try {
const response = await axios.get(`vacation/history?year=${year}`);
if (response.status === 200 && response.data) {
myVacations.value = response.data.data.usedVacations || [];
receivedVacations.value = response.data.data.receivedVacations || [];
} else {
console.warn("❌ 연차 내역을 불러오지 못했습니다.");
myVacations.value = [];
receivedVacations.value = [];
}
async function loadCalendarData(year, month) {
if (lastRemainingYear.value !== year || lastRemainingMonth.value !== month) {
await fetchRemainingVacation();
lastRemainingYear.value = year;
lastRemainingMonth.value = month;
} catch (error) {
console.error("🚨 연차 데이터 불러오기 실패:", error);
}
fetchedEvents.value = [];
const [vacationEvents, holidayEvents] = await Promise.all([
fetchVacationData(year, month),
fetchHolidays(year, month),
]);
holidayDates.value = new Set(holidayEvents.map((event) => event.start));
fetchedEvents.value = [...vacationEvents, ...holidayEvents];
updateCalendarEvents();
await nextTick();
fullCalendarRef.value.getApi().refetchEvents();
}
/** 오늘 이후의 날짜만 클릭 가능하도록 설정 */
function markClickableDates() {
nextTick(() => {
const todayStr = new Date().toISOString().split("T")[0]; // YYYY-MM-DD
const todayObj = new Date(todayStr);
document.querySelectorAll(".fc-daygrid-day").forEach((cell) => {
const dateStr = cell.getAttribute("data-date");
if (!dateStr) return; //
const dateObj = new Date(dateStr);
// (, )
if (dateObj.getDay() === 0 || dateObj.getDay() === 6 || holidayDates.value.has(dateStr)) {
cell.classList.remove("clickable");
cell.classList.add("fc-day-sat-sun");
//
async function fetchVacationData(year, month) {
try {
const response = await axios.get(`vacation/list/${year}/${month}`);
if (response.status === 200) {
const vacationList = response.data;
//
const filteredVacations = vacationList.filter(vac =>
userColors.value[vac.MEMBERSEQ] && userColors.value[vac.MEMBERSEQ]
);
const events = filteredVacations.map(vac => {
let dateStr = vac.LOCVACUDT ? vac.LOCVACUDT.split("T")[0] : "";
let backgroundColor = userColors.value[vac.MEMBERSEQ];
return {
title: getVacationType(vac.LOCVACTYP),
start: dateStr,
backgroundColor,
classNames: [getVacationTypeClass(vac.LOCVACTYP)],
saved: true,
memberSeq: vac.MEMBERSEQ,
};
}).filter(event => event.start);
return events;
} else {
console.warn("📌 휴가 데이터를 불러오지 못함");
return [];
}
// ( )
else if (dateObj < todayObj) {
cell.classList.remove("clickable");
cell.classList.add("past"); //
} catch (error) {
console.error("Error fetching vacation data:", error);
return [];
}
// & ( )
else {
cell.classList.add("clickable");
cell.classList.remove("past", "fc-day-sat-sun");
}
//
function updateCalendarEvents() {
const selectedEvents = Array.from(selectedDates.value)
.filter(([date, type]) => type !== "delete")
.map(([date, type]) => ({
start: date,
backgroundColor: "rgb(113 212 243 / 76%)",
textColor: "#fff",
display: "background",
classNames: [getVacationTypeClass(type), "selected-event"]
}));
const filteredFetchedEvents = fetchedEvents.value.filter(event => {
if (event.saved && selectedDates.value.get(event.start) === "delete") {
if (event.memberSeq === userStore.user.id) {
return false;
}
}
return true;
});
});
calendarEvents.value = [...filteredFetchedEvents, ...selectedEvents];
}
//
const getVacationTypeClass = (type) => {
if (type === "700101") return "half-day-am";
if (type === "700102") return "half-day-pm";
return "full-day";
};
//
const fetchVacationCodes = async () => {
try {
const response = await axios.get("vacation/codes");
if (response.status === 200 && response.data) {
vacationCodeMap.value = response.data.data.reduce((acc, item) => {
acc[item.code] = item.name;
return acc;
}, {});
} else {
console.warn("❌ 공통 코드 데이터를 불러오지 못했습니다.");
}
} catch (error) {
console.error("🚨 공통 코드 API 호출 실패:", error);
}
};
const getVacationType = (typeCode) => {
return vacationCodeMap.value[typeCode] || "기타";
};
/* 버튼 */
//
function toggleHalfDay(type) {
halfDayType.value = halfDayType.value === type ? null : type;
}
/** ✅ onMounted 및 달력 변경 시 실행 */
onMounted(() => {
markClickableDates();
/* 페이지 이동 시 변경 사항 확인 */
router.beforeEach((to, from, next) => {
if (hasChanges.value) {
const answer = window.confirm("저장하지 않은 변경 사항이 있습니다. 이동하시겠습니까?");
if (!answer) {
return next(false);
}
}
next();
});
onBeforeUnmount(() => {
window.removeEventListener("beforeunload", preventUnsavedChanges);
});
function preventUnsavedChanges(event) {
if (hasChanges.value) {
event.preventDefault();
event.returnValue = "";
}
}
/* watch */
watch(lastRemainingYear, async (newYear, oldYear) => {
await fetchVacationHistory(newYear);
});
// `selectedDates`
watch(
() => Array.from(selectedDates.value.keys()), //
(newKeys) => {
if (halfDayButtonsRef.value) {
halfDayButtonsRef.value.resetHalfDay();
}
},
{ deep: true }
);
// selectedDates
watch(
() => Array.from(selectedDates.value.keys()),
(newKeys) => {
},
{ deep: true }
);
watch([holidayDates, lastRemainingYear, lastRemainingMonth], () => {
markClickableDates();
});
/* onMounted */
// onMounted
onMounted(() => {
markClickableDates();
});
// onMounted
onMounted(async () => {
await userStore.userInfo();
await fetchUserList();
await fetchVacationCodes();
const today = new Date();
@ -571,13 +520,58 @@ watch([holidayDates, lastRemainingYear, lastRemainingMonth], () => {
const month = String(today.getMonth() + 1).padStart(2, "0");
await fetchVacationData(year, month);
await loadCalendarData(year, month);
await fetchRemainingVacation();
const currentYear = new Date().getFullYear();
await fetchVacationHistory(currentYear);
window.addEventListener("beforeunload", preventUnsavedChanges);
// Flatpickr ( )
fpInstance = flatpickr(calendarDatepicker.value, {
dateFormat: "Y-m",
plugins: [
new monthSelectPlugin({
shorthand: true,
dateFormat: "Y-m",
altFormat: "F Y"
})
],
onChange: function(selectedDatesArr, dateStr) {
//
fullCalendarRef.value.getApi().gotoDate(dateStr + "-01");
const [year, month] = dateStr.split("-");
lastRemainingYear.value = parseInt(year, 10);
lastRemainingMonth.value = month;
loadCalendarData(lastRemainingYear.value, lastRemainingMonth.value);
},
onClose: function() {
calendarDatepicker.value.style.display = "none";
}
});
// FullCalendar (.fc-toolbar-title)
nextTick(() => {
const titleEl = document.querySelector('.fc-toolbar-title');
if (titleEl) {
titleEl.style.cursor = 'pointer';
titleEl.addEventListener('click', () => {
const dpEl = calendarDatepicker.value;
dpEl.style.display = 'block';
dpEl.style.position = 'fixed';
dpEl.style.top = '22%';
dpEl.style.left = '66%';
dpEl.style.transform = 'translate(-50%, -50%)';
dpEl.style.zIndex = '9999';
dpEl.style.border = 'none';
dpEl.style.outline = 'none';
dpEl.style.backgroundColor = 'transparent';
fpInstance.open();
});
}
});
})
</script>
<style>
/* 모달 본문 스크롤 */
.modal-body {
max-height: 130px;
max-height: 140px;
overflow-y: auto;
}
</style>