휴가관리 수정정
This commit is contained in:
parent
09e79cb690
commit
efbeee855a
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="card-body d-flex justify-content-center">
|
||||
<div class="card-body d-flex justify-content-center m-n5">
|
||||
<ul class="list-unstyled d-flex flex-wrap align-items-center gap-2 mb-0 mt-2">
|
||||
<li
|
||||
v-for="(user, index) in sortedUserList"
|
||||
|
||||
@ -5,12 +5,16 @@
|
||||
<!-- Sidebar: 사이드바 영역 -->
|
||||
<div class="col-3 app-calendar-sidebar border-end" id="app-calendar-sidebar">
|
||||
<div class="sidebar-content">
|
||||
<!-- 사원 프로필 리스트 -->
|
||||
<div class="sidebar-actions text-center my-3">
|
||||
<HalfDayButtons
|
||||
@toggleHalfDay="toggleHalfDay"
|
||||
@addVacationRequests="saveVacationChanges"
|
||||
/>
|
||||
</div>
|
||||
<ProfileList
|
||||
@profileClick="handleProfileClick"
|
||||
:remainingVacationData="remainingVacationData"
|
||||
/>
|
||||
<!-- 모달들은 화면 오버레이로 동작하므로 사이드바 내부에 두어도 무방 -->
|
||||
<VacationModal
|
||||
v-if="isModalOpen"
|
||||
:isOpen="isModalOpen"
|
||||
@ -28,24 +32,23 @@
|
||||
@updateVacation="fetchRemainingVacation"
|
||||
/>
|
||||
</div>
|
||||
<div class="sidebar-actions text-center my-3">
|
||||
<!-- 액션 버튼 -->
|
||||
<HalfDayButtons
|
||||
@toggleHalfDay="toggleHalfDay"
|
||||
@addVacationRequests="saveVacationChanges"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content: 캘린더 영역 -->
|
||||
<div class="col app-calendar-content">
|
||||
<div class="card shadow-none border-0">
|
||||
<div class="card-body pb-0">
|
||||
<div class="card-body pb-0" style="position: relative;">
|
||||
<full-calendar
|
||||
ref="fullCalendarRef"
|
||||
:options="calendarOptions"
|
||||
class="flatpickr-calendar-only"
|
||||
/>
|
||||
<!-- 숨겨진 데이트피커 인풋 -->
|
||||
<input
|
||||
ref="calendarDatepicker"
|
||||
type="text"
|
||||
style="display: none; position: absolute;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -60,7 +63,12 @@
|
||||
import FullCalendar from "@fullcalendar/vue3";
|
||||
import dayGridPlugin from "@fullcalendar/daygrid";
|
||||
import interactionPlugin from "@fullcalendar/interaction";
|
||||
// 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";
|
||||
@ -75,11 +83,11 @@
|
||||
const userListStore = useUserStore();
|
||||
const userList = ref([]);
|
||||
const userColors = ref({});
|
||||
const myVacations = ref([]); // 전체 "사용한 연차" 목록 (로그인한 사원의 휴가만)
|
||||
const receivedVacations = ref([]); // 전체 "받은 연차" 목록
|
||||
const myVacations = ref([]); // 로그인한 사원의 휴가
|
||||
const receivedVacations = ref([]);
|
||||
const isModalOpen = ref(false);
|
||||
const remainingVacationData = ref({});
|
||||
const modalYear = ref(new Date().getFullYear());
|
||||
|
||||
const lastRemainingYear = ref(new Date().getFullYear());
|
||||
const lastRemainingMonth = ref(String(new Date().getMonth() + 1).padStart(2, "0"));
|
||||
const isGrantModalOpen = ref(false);
|
||||
@ -94,8 +102,9 @@
|
||||
const holidayDates = ref(new Set());
|
||||
const fetchedEvents = ref([]);
|
||||
|
||||
// 추가: 토글 상태 변수 (필요 시 사용 가능)
|
||||
const toggledDates = ref(new Set());
|
||||
// 데이트피커 인풋 ref
|
||||
const calendarDatepicker = ref(null);
|
||||
let fpInstance = null;
|
||||
|
||||
const calendarOptions = reactive({
|
||||
plugins: [dayGridPlugin, interactionPlugin],
|
||||
@ -115,9 +124,51 @@
|
||||
onMounted(async () => {
|
||||
await userStore.userInfo();
|
||||
await fetchRemainingVacation();
|
||||
// 초기 vacation history도 가져오기
|
||||
const currentYear = new Date().getFullYear();
|
||||
await fetchVacationHistory(currentYear);
|
||||
|
||||
// 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 rect = titleEl.getBoundingClientRect();
|
||||
const dpEl = calendarDatepicker.value;
|
||||
dpEl.style.display = 'none';
|
||||
dpEl.style.position = 'absolute';
|
||||
dpEl.style.top = (rect.bottom + window.scrollY -120) + 'px';
|
||||
// dpEl.style.left = (rect.left) + 'px';
|
||||
dpEl.style.width = '0px'; // 원하는 크기로 조정
|
||||
dpEl.style.fontSize = '0.9em';
|
||||
fpInstance.open();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 연차 내역 API (초기 호출용)
|
||||
@ -137,7 +188,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// lastRemainingYear 값이 변경될 때마다 해당 연도의 연차 내역 재조회
|
||||
watch(lastRemainingYear, async (newYear, oldYear) => {
|
||||
await fetchVacationHistory(newYear);
|
||||
});
|
||||
@ -156,27 +206,18 @@
|
||||
}
|
||||
};
|
||||
|
||||
// 프로필 클릭 시 연차 내역 가져오기
|
||||
const handleProfileClick = async (user) => {
|
||||
try {
|
||||
if (isModalOpen.value) {
|
||||
// 열린 모달이 있으면 모두 닫음 후 새 모달 열기
|
||||
isModalOpen.value = false;
|
||||
return;
|
||||
}
|
||||
if (isGrantModalOpen.value) {
|
||||
isGrantModalOpen.value = false;
|
||||
return;
|
||||
}
|
||||
if (user.MEMBERSEQ === userStore.user.id) {
|
||||
const year = new Date().getFullYear();
|
||||
await fetchVacationHistory(year);
|
||||
const displayedYear = lastRemainingYear.value;
|
||||
await fetchVacationHistory(displayedYear);
|
||||
isModalOpen.value = true;
|
||||
lastRemainingYear.value = year;
|
||||
isGrantModalOpen.value = false;
|
||||
} else {
|
||||
selectedUser.value = user;
|
||||
isGrantModalOpen.value = true;
|
||||
isModalOpen.value = false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("🚨 연차 데이터 불러오기 실패:", error);
|
||||
@ -220,29 +261,23 @@
|
||||
return vacationCodeMap.value[typeCode] || "기타";
|
||||
};
|
||||
|
||||
// computed: lastRemainingYear과 일치하는 항목만 필터링
|
||||
const filteredMyVacations = computed(() => {
|
||||
const filtered = myVacations.value.filter(vac => {
|
||||
const dateStr = vac.date || vac.LOCVACUDT;
|
||||
return myVacations.value.filter(vac => {
|
||||
const dateStr = vac.date;
|
||||
const year = dateStr ? dateStr.split("T")[0].substring(0, 4) : null;
|
||||
console.log("vacation year:", year, "lastRemainingYear:", lastRemainingYear.value);
|
||||
return year === String(lastRemainingYear.value);
|
||||
});
|
||||
console.log("filteredMyVacations:", filtered);
|
||||
return filtered;
|
||||
});
|
||||
|
||||
const filteredReceivedVacations = computed(() => {
|
||||
return receivedVacations.value.filter(vac => {
|
||||
const dateStr = vac.date || vac.LOCVACUDT;
|
||||
const dateStr = vac.date;
|
||||
const year = dateStr ? dateStr.split("T")[0].substring(0, 4) : null;
|
||||
console.log("vacation year:", year, "lastRemainingYear:", lastRemainingYear.value);
|
||||
return dateStr && year === String(lastRemainingYear.value);
|
||||
});
|
||||
});
|
||||
|
||||
function updateCalendarEvents() {
|
||||
// selectedDates에서 "delete"가 아닌 경우에 대해 이벤트 생성
|
||||
const selectedEvents = Array.from(selectedDates.value)
|
||||
.filter(([date, type]) => type !== "delete")
|
||||
.map(([date, type]) => ({
|
||||
@ -254,8 +289,6 @@
|
||||
classNames: [getVacationTypeClass(type), "selected-event"]
|
||||
}));
|
||||
|
||||
// fetchedEvents에서, 만약 해당 날짜가 "delete" 상태라면
|
||||
// 로그인한 사용자의(내) 이벤트만 제거하고, 다른 사용자의 이벤트는 그대로 둠.
|
||||
const filteredFetchedEvents = fetchedEvents.value.filter(event => {
|
||||
if (event.saved && selectedDates.value.get(event.start) === "delete") {
|
||||
if (event.memberSeq === userStore.user.id) {
|
||||
@ -287,15 +320,12 @@
|
||||
) {
|
||||
return;
|
||||
}
|
||||
// 내 휴가 여부: LOCVACUDT의 앞 10자가 clickedDateStr과 같고, LOCVACRMM가 없거나 빈 문자열인 경우
|
||||
const isMyVacation = myVacations.value.some(vac => {
|
||||
const vacDate = vac.date ? String(vac.date).substring(0, 10) : "";
|
||||
return vacDate === clickedDateStr &&
|
||||
(!vac.LOCVACRMM || String(vac.LOCVACRMM).trim() === "");
|
||||
return vacDate === clickedDateStr && !vac.receiverId;
|
||||
});
|
||||
|
||||
if (isMyVacation) {
|
||||
// 내 휴가인 경우: selectedDates에 "delete" 상태를 토글 (내 이벤트만 영향을 줌)
|
||||
if (selectedDates.value.get(clickedDateStr) === "delete") {
|
||||
selectedDates.value.delete(clickedDateStr);
|
||||
} else {
|
||||
@ -305,9 +335,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// 내 휴가가 없는 날짜: 일반 선택/토글 처리
|
||||
if (selectedDates.value.has(clickedDateStr)) {
|
||||
console.log("일반 날짜 토글 off: 기존 선택 해제");
|
||||
selectedDates.value.delete(clickedDateStr);
|
||||
updateCalendarEvents();
|
||||
return;
|
||||
@ -315,7 +343,6 @@
|
||||
const type = halfDayType.value
|
||||
? (halfDayType.value === "AM" ? "700101" : "700102")
|
||||
: "700103";
|
||||
console.log("일반 날짜 토글 on: 선택 및 타입", type);
|
||||
selectedDates.value.set(clickedDateStr, type);
|
||||
halfDayType.value = null;
|
||||
updateCalendarEvents();
|
||||
@ -331,7 +358,6 @@
|
||||
if (response.status === 200) {
|
||||
const vacationList = response.data;
|
||||
if (lastRemainingYear.value !== year) {
|
||||
// 로그인한 사원의 휴가만 저장
|
||||
myVacations.value = vacationList.filter(
|
||||
(vac) => vac.MEMBERSEQ === userStore.user.id
|
||||
);
|
||||
@ -348,7 +374,7 @@
|
||||
backgroundColor,
|
||||
classNames: [getVacationTypeClass(vac.LOCVACTYP)],
|
||||
saved: true,
|
||||
memberSeq: vac.MEMBERSEQ, // 이벤트 소유자 정보
|
||||
memberSeq: vac.MEMBERSEQ,
|
||||
};
|
||||
})
|
||||
.filter((event) => event.start);
|
||||
@ -368,18 +394,18 @@
|
||||
const vacationsToAdd = selectedDatesArray
|
||||
.filter(([date, type]) => type !== "delete")
|
||||
.filter(([date, type]) =>
|
||||
!myVacations.value.some(vac => vac.LOCVACUDT && vac.LOCVACUDT.startsWith(date)) ||
|
||||
myVacations.value.some(vac => vac.LOCVACUDT && vac.LOCVACUDT.startsWith(date) && vac.LOCVACRMM)
|
||||
!myVacations.value.some(vac => vac.date && vac.date.startsWith(date)) ||
|
||||
myVacations.value.some(vac => vac.date && vac.date.startsWith(date) && vac.receiverId)
|
||||
)
|
||||
.map(([date, type]) => ({ date, type }));
|
||||
const vacationsToDelete = myVacations.value
|
||||
.filter(vac => {
|
||||
if (!vac.LOCVACUDT) return false;
|
||||
const date = vac.LOCVACUDT.split("T")[0];
|
||||
return selectedDates.value.get(date) === "delete" && !vac.LOCVACRMM;
|
||||
if (!vac.date) return false;
|
||||
const date = vac.date.split("T")[0];
|
||||
return selectedDates.value.get(date) === "delete" && !vac.receiverId;
|
||||
})
|
||||
.map(vac => {
|
||||
const id = vac.LOCVACSEQ;
|
||||
const id = vac.id;
|
||||
return typeof id === "number" ? Number(id) : id;
|
||||
});
|
||||
try {
|
||||
@ -390,6 +416,9 @@
|
||||
if (response.data && response.data.status === "OK") {
|
||||
alert("✅ 휴가 변경 사항이 저장되었습니다.");
|
||||
await fetchRemainingVacation();
|
||||
if (isModalOpen.value) {
|
||||
await fetchVacationHistory(lastRemainingYear.value);
|
||||
}
|
||||
const currentDate = fullCalendarRef.value.getApi().getDate();
|
||||
await loadCalendarData(currentDate.getFullYear(), currentDate.getMonth() + 1);
|
||||
selectedDates.value.clear();
|
||||
@ -440,5 +469,10 @@
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* 스타일 정의 */
|
||||
/* 기본 스타일은 그대로 두고, 데이트피커 인풋의 추가 스타일 정의 */
|
||||
.fc-toolbar-title {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 데이트피커 인풋은 Flatpickr에서 동적으로 스타일 적용됨 */
|
||||
</style>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user