238 lines
6.8 KiB
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>
|