휴가저장,리스트

This commit is contained in:
dyhj625 2025-02-07 14:40:25 +09:00
parent 792fde6eac
commit 05dca89be0
2 changed files with 307 additions and 167 deletions

File diff suppressed because one or more lines are too long

View File

@ -1,14 +1,11 @@
<template>
<div class="vacation-management">
<div class="container-xxl flex-grow-1 container-p-y">
<div class="save-button-container">
<button class="btn btn-success" @click="addVacationRequests"> 저장</button>
</div>
<div class="container flex-grow-1">
<div class="card app-calendar-wrapper">
<div class="row g-0">
<div class="col app-calendar-content">
<div class="card shadow-none border-0">
<div class="card-body pb-0">
<div class="card-body">
<full-calendar
ref="fullCalendarRef"
:options="calendarOptions"
@ -17,14 +14,28 @@
</div>
</div>
<div class="half-day-buttons">
<button class="btn btn-info" :class="{ active: halfDayType === 'AM' }" @click="toggleHalfDay('AM')">
오전반차
<button
class="btn btn-info"
:class="{ active: halfDayType === 'AM' }"
@click="toggleHalfDay('AM')"
>
<i class="bi bi-sun"></i>
</button>
<button class="btn btn-warning" :class="{ active: halfDayType === 'PM' }" @click="toggleHalfDay('PM')">
오후반차
<button
class="btn btn-warning"
:class="{ active: halfDayType === 'PM' }"
@click="toggleHalfDay('PM')"
>
<i class="bi bi-moon"></i>
</button>
<div class="save-button-container">
<button class="btn btn-success" @click="addVacationRequests">
</button>
</div>
</div>
<br />
</div>
</div>
</div>
</div>
@ -37,15 +48,22 @@ import dayGridPlugin from "@fullcalendar/daygrid";
import interactionPlugin from "@fullcalendar/interaction";
import "flatpickr/dist/flatpickr.min.css";
import "@/assets/css/app-calendar.css";
import { reactive, ref, onMounted } from "vue";
import { reactive, ref, onMounted, nextTick } from "vue";
import axios from "@api";
import "bootstrap-icons/font/bootstrap-icons.css";
// FullCalendar
const fullCalendarRef = ref(null);
const calendarEvents = ref([]); // FullCalendar
const selectedDates = ref(new Map());
const calendarEvents = ref([]); // FullCalendar (API + )
const fetchedEvents = ref([]); // API (, )
const selectedDates = ref(new Map()); //
const halfDayType = ref(null);
const employeeId = ref(1);
// (YYYY-MM-DD ) ( )
const holidayDates = ref(new Set());
// FullCalendar (events calendarEvents )
const calendarOptions = reactive({
plugins: [dayGridPlugin, interactionPlugin],
initialView: "dayGridMonth",
@ -55,52 +73,96 @@ const calendarOptions = reactive({
right: "prev,next",
},
locale: "ko",
selectable: true,
selectable: false,
dateClick: handleDateClick,
events: calendarEvents, //
datesSet: handleMonthChange,
events: calendarEvents,
});
/**
* 날짜 클릭 이벤트
* API 이벤트(fetchedEvents) 사용자가 선택한 날짜(selectedDates) 병합하여
* calendarEvents를 업데이트하는 함수
* - 선택 이벤트는 display: "background" 옵션을 사용하여 배경으로 표시
* - 선택된 타입에 따라 클래스(selected-am, selected-pm, selected-full) 부여함
*/
function handleDateClick(info) {
const date = info.dateStr;
if (!selectedDates.value.has(date)) {
const type = halfDayType.value ? (halfDayType.value === "AM" ? "D" : "N") : "F";
selectedDates.value.set(date, type);
function updateCalendarEvents() {
const selectedEvents = Array.from(selectedDates.value).map(([date, type]) => {
let className = "";
let title = "";
if (type === "D") {
className = "selected-am"; // :
title = "오전반차 (선택)";
} else if (type === "N") {
className = "selected-pm"; // :
title = "오후반차 (선택)";
} else {
selectedDates.value.delete(date);
className = "selected-full"; //
title = "연차 (선택)";
}
halfDayType.value = null;
return {
title,
start: date,
backgroundColor: "rgba(0, 128, 0, 0.3)",
display: "background",
classNames: [className],
};
});
calendarEvents.value = [...fetchedEvents.value, ...selectedEvents];
}
/**
* 오전/오후 반차 선택
* 날짜 클릭 이벤트
* - 주말(, ) 공휴일은 클릭되지 않음
* - 클릭 해당 날짜를 selectedDates에 추가 또는 제거한 updateCalendarEvents() 호출
*/
function handleDateClick(info) {
const clickedDateStr = info.dateStr;
const clickedDate = info.date;
// (:6, :0)
if (clickedDate.getDay() === 0 || clickedDate.getDay() === 6) {
return;
}
//
if (holidayDates.value.has(clickedDateStr)) {
return;
}
if (!selectedDates.value.has(clickedDateStr)) {
const type = halfDayType.value
? halfDayType.value === "AM"
? "D"
: "N"
: "F";
selectedDates.value.set(clickedDateStr, type);
} else {
selectedDates.value.delete(clickedDateStr);
}
halfDayType.value = null;
updateCalendarEvents();
}
/**
* 오전/오후 반차 버튼 토글
*/
function toggleHalfDay(type) {
halfDayType.value = halfDayType.value === type ? null : type;
}
/**
* 백엔드에서 휴가 데이터를 가져와 FullCalendar에 반영
* 백엔드에서 휴가 데이터를 가져와 이벤트로 변환
*/
async function fetchVacationData() {
async function fetchVacationData(year, month) {
try {
const response = await axios.get("vacation/list");
if (response.data.status === "OK") {
const vacationList = response.data.data;
console.log(`📌 휴가 데이터 요청: ${year}-${month}`);
const response = await axios.get(`vacation/list/${year}/${month}`);
if (response.status == 200) {
const vacationList = response.data;
console.log("📌 백엔드 응답 데이터:", vacationList);
if (!Array.isArray(vacationList)) {
throw new Error("vacationList is not an array.");
}
//
const events = vacationList.map((vac) => {
const events = vacationList
.map((vac) => {
let dateStr = vac.LOCVACUDT.split("T")[0];
let className = "fc-daygrid-event";
let backgroundColor = getColorByEmployeeId(vac.MEMBERSEQ);
let title = "연차";
if (vac.LOCVACTYP === "D") {
title = "오전반차";
@ -112,51 +174,22 @@ async function fetchVacationData() {
title = "연차";
className += " full-day";
}
return {
title,
start: dateStr,
backgroundColor: getColorByEmployeeId(vac.MEMBERSEQ),
backgroundColor,
classNames: [className],
};
}).filter((event) => event !== null);
console.log("📌 변환된 이벤트:", events);
calendarEvents.value = events; // FullCalendar
})
.filter((event) => event !== null);
return events;
} else {
console.warn("📌 휴가 데이터를 불러오지 못함");
return [];
}
} catch (error) {
console.error("Error fetching vacation data:", error);
}
}
/**
* 휴가 요청 추가
*/
async function addVacationRequests() {
if (selectedDates.value.size === 0) {
alert("휴가를 선택해주세요.");
return;
}
const vacationRequests = Array.from(selectedDates.value).map(([date, type]) => ({
date,
type,
employeeId: employeeId.value,
}));
try {
const response = await axios.post("vacation", vacationRequests);
if (response.data && response.data.status === "OK") {
alert("휴가가 저장되었습니다.");
fetchVacationData(); //
selectedDates.value.clear();
} else {
alert("휴가 저장 중 오류가 발생했습니다.");
}
} catch (error) {
console.error(error);
alert("휴가 저장에 실패했습니다.");
return [];
}
}
@ -168,17 +201,103 @@ function getColorByEmployeeId(employeeId) {
return colors[employeeId % colors.length];
}
//
/**
* 휴가 요청 추가 (선택된 날짜를 백엔드로 전송)
*/
async function addVacationRequests() {
if (selectedDates.value.size === 0) {
alert("휴가를 선택해주세요.");
return;
}
const vacationRequests = Array.from(selectedDates.value).map(([date, type]) => ({
date,
type,
employeeId: employeeId.value,
}));
try {
const response = await axios.post("vacation", vacationRequests);
if (response.data && response.data.status === "OK") {
alert("휴가가 저장되었습니다.");
//
const currentDate = fullCalendarRef.value.getApi().getDate();
const year = currentDate.getFullYear();
const month = String(currentDate.getMonth() + 1).padStart(2, "0");
loadCalendarData(year, month);
selectedDates.value.clear();
updateCalendarEvents();
} else {
alert("휴가 저장 중 오류가 발생했습니다.");
}
} catch (error) {
console.error(error);
alert("휴가 저장에 실패했습니다.");
}
}
/**
* 공휴일 데이터 요청 이벤트 변환
*/
async function fetchHolidays(year, month) {
try {
console.log(`📌 공휴일 요청: ${year}-${month}`);
const response = await axios.get(`vacation/${year}/${month}`);
console.log("📌 공휴일 API 응답:", response.data);
const holidayEvents = response.data.map((holiday) => ({
title: holiday.name,
start: holiday.date, // "YYYY-MM-DD"
backgroundColor: "#ff6666",
classNames: ["holiday-event"],
}));
return holidayEvents;
} catch (error) {
console.error("공휴일 정보를 불러오지 못했습니다.", error);
return [];
}
}
/**
* 달력 변경 호출 (FullCalendar의 datesSet 옵션)
*/
function handleMonthChange(viewInfo) {
const currentDate = viewInfo.view.currentStart;
const year = currentDate.getFullYear();
const month = String(currentDate.getMonth() + 1).padStart(2, "0");
console.log(`📌 월 변경 감지: ${year}-${month}`);
loadCalendarData(year, month);
}
/**
* 지정한 월의 데이터를 로드 (휴가, 공휴일 데이터를 병렬 요청)
*/
async function loadCalendarData(year, month) {
console.log(`📌 ${year}-${month} 데이터 로드 시작`);
fetchedEvents.value = [];
const [vacationEvents, holidayEvents] = await Promise.all([
fetchVacationData(year, month),
fetchHolidays(year, month),
]);
console.log("📌 변환된 휴가 이벤트:", vacationEvents);
console.log("📌 변환된 공휴일 이벤트:", holidayEvents);
// Set
holidayDates.value = new Set(holidayEvents.map((event) => event.start));
fetchedEvents.value = [...vacationEvents, ...holidayEvents];
updateCalendarEvents();
await nextTick();
fullCalendarRef.value.getApi().refetchEvents();
console.log("📌 FullCalendar 데이터 업데이트 완료");
}
//
onMounted(() => {
fetchVacationData();
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, "0");
console.log(`📌 초기 로드: ${year}-${month}`);
loadCalendarData(year, month);
});
</script>
<style>
.vacation-management {
padding: 20px;
}
/* 버튼 스타일 */
.half-day-buttons {
display: flex;
@ -186,22 +305,33 @@ onMounted(() => {
gap: 10px;
margin-top: 20px;
}
.half-day-buttons .btn.active {
border: 2px solid black;
}
/* FullCalendar 이벤트 스타일 */
/* 날짜 칸 높이 고정 */
.fc-daygrid-day-frame {
min-height: 80px !important;
max-height: 120px !important;
overflow: hidden !important;
padding-top: 25px !important;
}
/* 날짜 칸 내부의 이벤트 목록 */
.fc-daygrid-day-events {
max-height: 100px !important;
overflow-y: auto !important;
}
.fc-daygrid-event {
position: absolute !important;
height: 20px !important; /* 실선 두께 */
width: 90% !important;
left: 5% !important;
height: 20px !important;
width: 100% !important;
left: 0 !important;
margin: 2px 0 !important;
padding: 0 !important;
border-radius: 2px !important;
background-color: inherit !important;
border: none !important; /* 기본 FullCalendar 테두리 제거 */
border: none !important;
}
/* 여러 이벤트가 같은 날짜에 있을 때 정렬 */
@ -210,28 +340,38 @@ onMounted(() => {
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
gap: 3px;
width: 100%;
gap: 22px;
}
/* 오전반차(왼쪽부터 중앙까지) */
/* 기본 휴가/반차 이벤트 (API에서 받은 이벤트) */
.fc-daygrid-event.half-day-am {
width: 45% !important;
left: 0% !important;
background-color: #ffdd57 !important; /* 노란색 */
left: 0 !important;
}
/* 오후반차(오른쪽부터 중앙까지) */
.fc-daygrid-event.half-day-pm {
width: 45% !important;
left: auto !important;
right: 0% !important;
background-color: #57a5ff !important; /* 파란색 */
right: 0 !important;
}
/* 연차 (전체 너비) */
.fc-daygrid-event.full-day {
width: 100% !important;
left: 0 !important;
background-color: #ff85a2 !important; /* 연한 빨강 */
}
/* 요일별 날짜 칸 스타일 */
.fc-day-sun .fc-daygrid-day-number,
.fc-col-header-cell:first-child .fc-col-header-cell-cushion {
color: #ff4500 !important;
}
.fc-day-sat .fc-daygrid-day-number,
.fc-col-header-cell:last-child .fc-col-header-cell-cushion {
color: #324fde !important;
}
.fc-daygrid-day-number {
position: absolute !important;
top: 0px !important;
left: 5px !important;
text-align: left !important;
}
</style>