휴가관리 수정
This commit is contained in:
parent
e07593c13b
commit
ac71a9aa1f
@ -3,22 +3,33 @@
|
|||||||
|
|
||||||
/* 휴가 */
|
/* 휴가 */
|
||||||
|
|
||||||
.fc-daygrid-day-events {
|
|
||||||
max-height: 100px !important;
|
|
||||||
overflow-y: auto !important;
|
|
||||||
}
|
|
||||||
/* 이벤트 선 없게 */
|
/* 이벤트 선 없게 */
|
||||||
.fc-event {
|
.fc-event {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
/* 오전전반차 그래프 */
|
/* 오전 반차 그래프 (왼쪽 절반) */
|
||||||
.fc-daygrid-event.half-day-am {
|
.fc-daygrid-event.half-day-am {
|
||||||
width: calc(50% - 4px) !important;
|
width: 50% !important;
|
||||||
|
height: 8px !important;
|
||||||
|
border-radius: 2px !important;
|
||||||
|
font-size: 0px !important;
|
||||||
}
|
}
|
||||||
/* 오후반차 그래프프 */
|
/* 오후 반차 그래프 (오른쪽 절반) */
|
||||||
.fc-daygrid-event.half-day-pm {
|
.fc-daygrid-event.half-day-pm {
|
||||||
width: calc(50% - 4px) !important;
|
width: 50% !important;
|
||||||
margin-left: auto !important
|
height: 8px !important;
|
||||||
|
margin-left: auto !important;
|
||||||
|
border-radius: 2px !important;
|
||||||
|
font-size: 0px !important;
|
||||||
|
}
|
||||||
|
/* 연차 그래프 (풀풀) */
|
||||||
|
.fc-daygrid-event.full-day {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 8px !important;
|
||||||
|
margin-left: auto !important;
|
||||||
|
border-radius: 2px !important;
|
||||||
|
font-size: 0px !important;
|
||||||
}
|
}
|
||||||
/* 공휴일,일요일 색상 */
|
/* 공휴일,일요일 색상 */
|
||||||
.fc-day-sun .fc-daygrid-day-number,
|
.fc-day-sun .fc-daygrid-day-number,
|
||||||
@ -39,8 +50,8 @@
|
|||||||
.flatpickr-calendar:after {
|
.flatpickr-calendar:after {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
/* 기본 스타일은 그대로 두고, 데이트피커 인풋의 추가 스타일 정의 */
|
/* 기본 스타일은 그대로 두고, 데이트피커 인풋의 추가 스타일 정의 */
|
||||||
.fc-toolbar-title {
|
.fc-toolbar-title {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
/* 클릭 가능한 날짜 (오늘 + 미래) */
|
/* 클릭 가능한 날짜 (오늘 + 미래) */
|
||||||
@ -84,7 +95,6 @@ opacity: 0.6; /* 흐려 보이게 */
|
|||||||
|
|
||||||
|
|
||||||
/* 본인 모달 */
|
/* 본인 모달 */
|
||||||
|
|
||||||
/* 닫기 버튼 */
|
/* 닫기 버튼 */
|
||||||
.close-btn {
|
.close-btn {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -109,7 +119,6 @@ opacity: 0.6; /* 흐려 보이게 */
|
|||||||
|
|
||||||
|
|
||||||
/* 선물하기 모달 */
|
/* 선물하기 모달 */
|
||||||
|
|
||||||
/* 연차 개수 버튼 */
|
/* 연차 개수 버튼 */
|
||||||
.count-btn {
|
.count-btn {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
@ -127,7 +136,6 @@ opacity: 0.6; /* 흐려 보이게 */
|
|||||||
background: #cccccc;
|
background: #cccccc;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 버튼 컨테이너 (우측 정렬) */
|
/* 버튼 컨테이너 (우측 정렬) */
|
||||||
.custom-button-container {
|
.custom-button-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -141,18 +149,17 @@ opacity: 0.6; /* 흐려 보이게 */
|
|||||||
padding: 10px; /* 크기 조정 */
|
padding: 10px; /* 크기 조정 */
|
||||||
cursor: pointer; /* 클릭 가능하도록 변경 */
|
cursor: pointer; /* 클릭 가능하도록 변경 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 아이콘 색상 변경 (기본) */
|
/* 아이콘 색상 변경 (기본) */
|
||||||
.custom-button i {
|
.custom-button i {
|
||||||
color: #282538; /* 기본 아이콘 색상 */
|
color: #282538; /* 기본 아이콘 색상 */
|
||||||
font-size: 25px; /* 아이콘 크기 */
|
font-size: 25px; /* 아이콘 크기 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 버튼 호버 효과 */
|
/* 버튼 호버 효과 */
|
||||||
.custom-button:hover i {
|
.custom-button:hover i {
|
||||||
color: #ff0800; /* 호버 시 아이콘 색상 변경 */
|
color: #ff0800; /* 호버 시 아이콘 색상 변경 */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.grayscaleImg {
|
.grayscaleImg {
|
||||||
filter: grayscale(100%);
|
filter: grayscale(100%);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,10 +28,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { defineEmits, ref, defineProps } from "vue";
|
import { defineEmits, ref, defineProps, watch } from "vue";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
isDisabled: Boolean
|
isDisabled: Boolean,
|
||||||
|
selectedDate: String // 날짜 선택 값을 props로 받음
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(["toggleHalfDay", "addVacationRequests", "resetHalfDay"]);
|
const emit = defineEmits(["toggleHalfDay", "addVacationRequests", "resetHalfDay"]);
|
||||||
@ -39,15 +40,16 @@ const halfDayType = ref(null);
|
|||||||
|
|
||||||
const toggleHalfDay = (type) => {
|
const toggleHalfDay = (type) => {
|
||||||
halfDayType.value = type;
|
halfDayType.value = type;
|
||||||
|
|
||||||
emit("toggleHalfDay", halfDayType.value);
|
emit("toggleHalfDay", halfDayType.value);
|
||||||
|
|
||||||
// ✅ 버튼 클릭 후 1초 후 자동 비활성화
|
|
||||||
setTimeout(() => {
|
|
||||||
halfDayType.value = null;
|
|
||||||
}, 1000);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// `selectedDate`가 변경되면 반차 선택 초기화
|
||||||
|
watch(() => props.selectedDate, (newDate) => {
|
||||||
|
if (newDate) {
|
||||||
|
resetHalfDay();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 날짜 선택 후 반차 버튼 상태 초기화
|
// 날짜 선택 후 반차 버튼 상태 초기화
|
||||||
const resetHalfDay = () => {
|
const resetHalfDay = () => {
|
||||||
halfDayType.value = null;
|
halfDayType.value = null;
|
||||||
@ -89,20 +91,20 @@ defineExpose({ resetHalfDay });
|
|||||||
|
|
||||||
/* 선택된 (눌린) 버튼 */
|
/* 선택된 (눌린) 버튼 */
|
||||||
.btn.active {
|
.btn.active {
|
||||||
border: 3px solid #fff; /* 흰색 테두리 강조 */
|
border: 3px solid #fff;
|
||||||
box-shadow: 0px 4px 15px rgba(0, 0, 0, 0.3);
|
box-shadow: 0px 4px 15px rgba(0, 0, 0, 0.3);
|
||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* AM 버튼 (선택된 상태) */
|
/* AM 버튼 (선택된 상태) */
|
||||||
.btn-warning.active {
|
.btn-warning.active {
|
||||||
background-color: #ffca2c !important; /* 진한 노란색 */
|
background-color: #ffca2c !important;
|
||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* PM 버튼 (선택된 상태) */
|
/* PM 버튼 (선택된 상태) */
|
||||||
.btn-info.active {
|
.btn-info.active {
|
||||||
background-color: #0b5ed7 !important; /* 진한 파란색 */
|
background-color: #0b5ed7 !important;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mb-4 row">
|
<div class="mb-4 row">
|
||||||
<label :for="name" class="col-md-2 col-form-label">{{ title }}</label>
|
<label :for="inputId" class="col-md-2 col-form-label">{{ title }}</label>
|
||||||
<div class="col-md-10">
|
<div class="col-md-10">
|
||||||
<input
|
<input
|
||||||
class="form-control"
|
class="form-control"
|
||||||
type="file"
|
type="file"
|
||||||
:id="name"
|
:id="inputId"
|
||||||
|
ref="fileInput"
|
||||||
@change="changeHandler"
|
@change="changeHandler"
|
||||||
multiple
|
multiple
|
||||||
/>
|
/>
|
||||||
@ -21,7 +22,7 @@ import { ref ,computed} from 'vue';
|
|||||||
import { fileMsg } from '@/common/msgEnum';
|
import { fileMsg } from '@/common/msgEnum';
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
const prop = defineProps({
|
const props = defineProps({
|
||||||
title: {
|
title: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '라벨',
|
default: '라벨',
|
||||||
@ -29,7 +30,7 @@ const prop = defineProps({
|
|||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'nameplz',
|
default: 'fileInput',
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
isAlert: {
|
isAlert: {
|
||||||
@ -38,12 +39,13 @@ const prop = defineProps({
|
|||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
const inputId = computed(() => props.name || 'defaultFileInput');
|
||||||
|
|
||||||
const emits = defineEmits(['update:data', 'update:isValid']);
|
const emits = defineEmits(['update:data', 'update:isValid']);
|
||||||
|
|
||||||
const MAX_TOTAL_SIZE = 5 * 1024 * 1024; // 5MB
|
const MAX_TOTAL_SIZE = 5 * 1024 * 1024; // 5MB
|
||||||
const MAX_FILE_COUNT = 5; // 최대 파일 개수
|
const MAX_FILE_COUNT = 5; // 최대 파일 개수
|
||||||
const ALLOWED_FILE_TYPES = ['image/jpeg', 'image/png', 'application/pdf']; // 허용된 파일 유형
|
const ALLOWED_FILE_TYPES = []; // 모든 파일을 허용
|
||||||
|
|
||||||
const showError = ref(false);
|
const showError = ref(false);
|
||||||
const fileMsgKey = ref(''); // 에러 메시지 키
|
const fileMsgKey = ref(''); // 에러 메시지 키
|
||||||
@ -51,9 +53,12 @@ const fileMsgKey = ref(''); // 에러 메시지 키
|
|||||||
const changeHandler = (event) => {
|
const changeHandler = (event) => {
|
||||||
const files = Array.from(event.target.files);
|
const files = Array.from(event.target.files);
|
||||||
const totalSize = files.reduce((sum, file) => sum + file.size, 0);
|
const totalSize = files.reduce((sum, file) => sum + file.size, 0);
|
||||||
const invalidFiles = files.filter(file => !ALLOWED_FILE_TYPES.includes(file.type));
|
|
||||||
|
|
||||||
// 파일 검증 로직
|
// ALLOWED_FILE_TYPES가 비어있으면 모든 파일 허용
|
||||||
|
const invalidFiles = ALLOWED_FILE_TYPES.length > 0
|
||||||
|
? files.filter(file => !ALLOWED_FILE_TYPES.includes(file.type))
|
||||||
|
: [];
|
||||||
|
|
||||||
if (totalSize > MAX_TOTAL_SIZE) {
|
if (totalSize > MAX_TOTAL_SIZE) {
|
||||||
showError.value = true;
|
showError.value = true;
|
||||||
fileMsgKey.value = 'FileMaxSizeMsg';
|
fileMsgKey.value = 'FileMaxSizeMsg';
|
||||||
|
|||||||
@ -7,9 +7,11 @@
|
|||||||
<div class="sidebar-content">
|
<div class="sidebar-content">
|
||||||
<div class="sidebar-actions text-center my-3">
|
<div class="sidebar-actions text-center my-3">
|
||||||
<HalfDayButtons
|
<HalfDayButtons
|
||||||
@toggleHalfDay="toggleHalfDay"
|
ref="halfDayButtonsRef"
|
||||||
@addVacationRequests="saveVacationChanges"
|
@toggleHalfDay="toggleHalfDay"
|
||||||
:isDisabled="!hasChanges"
|
@addVacationRequests="saveVacationChanges"
|
||||||
|
:isDisabled="!hasChanges"
|
||||||
|
:selectedDate="selectedDate"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ProfileList
|
<ProfileList
|
||||||
@ -58,28 +60,31 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { reactive, ref, onMounted, nextTick, computed, watch } from "vue";
|
import { reactive, ref, onMounted, nextTick, computed, watch, onBeforeUnmount } from "vue";
|
||||||
import axios from "@api";
|
import axios from "@api";
|
||||||
import FullCalendar from "@fullcalendar/vue3";
|
import FullCalendar from "@fullcalendar/vue3";
|
||||||
import dayGridPlugin from "@fullcalendar/daygrid";
|
import dayGridPlugin from "@fullcalendar/daygrid";
|
||||||
import interactionPlugin from "@fullcalendar/interaction";
|
import interactionPlugin from "@fullcalendar/interaction";
|
||||||
// Flatpickr 및 MonthSelect 플러그인 임포트
|
// Flatpickr 및 MonthSelect 플러그인 임포트
|
||||||
import flatpickr from "flatpickr";
|
import flatpickr from "flatpickr";
|
||||||
import monthSelectPlugin from "flatpickr/dist/plugins/monthSelect/index";
|
import monthSelectPlugin from "flatpickr/dist/plugins/monthSelect/index";
|
||||||
import "flatpickr/dist/flatpickr.min.css";
|
import "flatpickr/dist/flatpickr.min.css";
|
||||||
import "flatpickr/dist/plugins/monthSelect/style.css";
|
import "flatpickr/dist/plugins/monthSelect/style.css";
|
||||||
|
|
||||||
import "@/assets/css/app-calendar.css";
|
import "@/assets/css/app-calendar.css";
|
||||||
import "bootstrap-icons/font/bootstrap-icons.css";
|
import "bootstrap-icons/font/bootstrap-icons.css";
|
||||||
import HalfDayButtons from "@c/button/HalfDayButtons.vue";
|
import HalfDayButtons from "@c/button/HalfDayButtons.vue";
|
||||||
import ProfileList from "@c/vacation/ProfileList.vue";
|
import ProfileList from "@c/vacation/ProfileList.vue";
|
||||||
import VacationModal from "@c/modal/VacationModal.vue";
|
import VacationModal from "@c/modal/VacationModal.vue";
|
||||||
import VacationGrantModal from "@c/modal/VacationGrantModal.vue";
|
import VacationGrantModal from "@c/modal/VacationGrantModal.vue";
|
||||||
import { useUserStore } from "@s/userList";
|
import { useUserStore } from "@s/userList";
|
||||||
import { useUserInfoStore } from "@s/useUserInfoStore";
|
import { useUserInfoStore } from "@s/useUserInfoStore";
|
||||||
import { fetchHolidays } from "@c/calendar/holiday.js";
|
import { fetchHolidays } from "@c/calendar/holiday.js";
|
||||||
import { useToastStore } from '@s/toastStore';
|
import { useToastStore } from '@s/toastStore';
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const toastStore = useToastStore();
|
const toastStore = useToastStore();
|
||||||
const userStore = useUserInfoStore();
|
const userStore = useUserInfoStore();
|
||||||
@ -90,7 +95,7 @@
|
|||||||
const receivedVacations = ref([]);
|
const receivedVacations = ref([]);
|
||||||
const isModalOpen = ref(false);
|
const isModalOpen = ref(false);
|
||||||
const remainingVacationData = ref({});
|
const remainingVacationData = ref({});
|
||||||
|
const selectedDate = ref(null);
|
||||||
const lastRemainingYear = ref(new Date().getFullYear());
|
const lastRemainingYear = ref(new Date().getFullYear());
|
||||||
const lastRemainingMonth = ref(String(new Date().getMonth() + 1).padStart(2, "0"));
|
const lastRemainingMonth = ref(String(new Date().getMonth() + 1).padStart(2, "0"));
|
||||||
const isGrantModalOpen = ref(false);
|
const isGrantModalOpen = ref(false);
|
||||||
@ -106,6 +111,39 @@
|
|||||||
const fetchedEvents = ref([]);
|
const fetchedEvents = ref([]);
|
||||||
const halfDayButtonsRef = ref(null);
|
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 }
|
||||||
|
);
|
||||||
|
|
||||||
// 데이트피커 인풋 ref
|
// 데이트피커 인풋 ref
|
||||||
const calendarDatepicker = ref(null);
|
const calendarDatepicker = ref(null);
|
||||||
let fpInstance = null;
|
let fpInstance = null;
|
||||||
@ -192,6 +230,7 @@ function handleDateClick(info) {
|
|||||||
await fetchRemainingVacation();
|
await fetchRemainingVacation();
|
||||||
const currentYear = new Date().getFullYear();
|
const currentYear = new Date().getFullYear();
|
||||||
await fetchVacationHistory(currentYear);
|
await fetchVacationHistory(currentYear);
|
||||||
|
window.addEventListener("beforeunload", preventUnsavedChanges);
|
||||||
|
|
||||||
// Flatpickr 초기화 (달 선택 모드)
|
// Flatpickr 초기화 (달 선택 모드)
|
||||||
fpInstance = flatpickr(calendarDatepicker.value, {
|
fpInstance = flatpickr(calendarDatepicker.value, {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user