localhost-front/src/views/vacation/VacationManagement.vue

238 lines
6.8 KiB
Vue

<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="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">
<full-calendar
ref="fullCalendarRef"
:options="calendarOptions"
class="flatpickr-calendar-only"
/>
</div>
</div>
<div class="half-day-buttons">
<button class="btn btn-info" :class="{ active: halfDayType === 'AM' }" @click="toggleHalfDay('AM')">
오전반차
</button>
<button class="btn btn-warning" :class="{ active: halfDayType === 'PM' }" @click="toggleHalfDay('PM')">
오후반차
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import FullCalendar from "@fullcalendar/vue3";
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 axios from "@api";
const fullCalendarRef = ref(null);
const calendarEvents = ref([]); // FullCalendar의 이벤트 리스트
const selectedDates = ref(new Map());
const halfDayType = ref(null);
const employeeId = ref(1);
const calendarOptions = reactive({
plugins: [dayGridPlugin, interactionPlugin],
initialView: "dayGridMonth",
headerToolbar: {
left: "today",
center: "title",
right: "prev,next",
},
locale: "ko",
selectable: true,
dateClick: handleDateClick,
events: calendarEvents, // 이벤트 리스트를 직접 반영
});
/**
* 날짜 클릭 이벤트
*/
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);
} else {
selectedDates.value.delete(date);
}
halfDayType.value = null;
}
/**
* 오전/오후 반차 선택
*/
function toggleHalfDay(type) {
halfDayType.value = halfDayType.value === type ? null : type;
}
/**
* 백엔드에서 휴가 데이터를 가져와 FullCalendar에 반영
*/
async function fetchVacationData() {
try {
const response = await axios.get("vacation/list");
if (response.data.status === "OK") {
const vacationList = response.data.data;
console.log("📌 백엔드 응답 데이터:", vacationList);
if (!Array.isArray(vacationList)) {
throw new Error("vacationList is not an array.");
}
// 이벤트 리스트 변환
const events = vacationList.map((vac) => {
let dateStr = vac.LOCVACUDT.split("T")[0];
let className = "fc-daygrid-event";
let title = "연차";
if (vac.LOCVACTYP === "D") {
title = "오전반차";
className += " half-day-am";
} else if (vac.LOCVACTYP === "N") {
title = "오후반차";
className += " half-day-pm";
} else if (vac.LOCVACTYP === "F") {
title = "연차";
className += " full-day";
}
return {
title,
start: dateStr,
backgroundColor: getColorByEmployeeId(vac.MEMBERSEQ),
classNames: [className],
};
}).filter((event) => event !== null);
console.log("📌 변환된 이벤트:", events);
calendarEvents.value = events; // FullCalendar 이벤트 업데이트
}
} 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("휴가 저장에 실패했습니다.");
}
}
/**
* 사원 ID별 색상 반환
*/
function getColorByEmployeeId(employeeId) {
const colors = ["#ade3ff", "#ffade3", "#ade3ad", "#ffadad"];
return colors[employeeId % colors.length];
}
// 데이터 불러오기 호출
onMounted(() => {
fetchVacationData();
});
</script>
<style>
.vacation-management {
padding: 20px;
}
/* 버튼 스타일 */
.half-day-buttons {
display: flex;
justify-content: center;
gap: 10px;
margin-top: 20px;
}
.half-day-buttons .btn.active {
border: 2px solid black;
}
/* FullCalendar 이벤트 스타일 */
.fc-daygrid-event {
position: absolute !important;
height: 20px !important; /* 실선 두께 */
width: 90% !important;
left: 5% !important;
margin: 2px 0 !important;
padding: 0 !important;
border-radius: 2px !important;
background-color: inherit !important;
border: none !important; /* 기본 FullCalendar 테두리 제거 */
}
/* 여러 이벤트가 같은 날짜에 있을 때 정렬 */
.fc-daygrid-event-harness {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
gap: 3px;
}
/* 오전반차(왼쪽부터 중앙까지) */
.fc-daygrid-event.half-day-am {
width: 45% !important;
left: 0% !important;
background-color: #ffdd57 !important; /* 노란색 */
}
/* 오후반차(오른쪽부터 중앙까지) */
.fc-daygrid-event.half-day-pm {
width: 45% !important;
left: auto !important;
right: 0% !important;
background-color: #57a5ff !important; /* 파란색 */
}
/* 연차 (전체 너비) */
.fc-daygrid-event.full-day {
width: 100% !important;
left: 0 !important;
background-color: #ff85a2 !important; /* 연한 빨강 */
}
</style>