휴가관리 수정

This commit is contained in:
dyhj625 2025-02-28 13:03:52 +09:00
parent e07593c13b
commit ac71a9aa1f
4 changed files with 112 additions and 59 deletions

View File

@ -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%);
} }

View File

@ -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;
} }

View File

@ -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';

View File

@ -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, {