Merge branch 'vacation'
This commit is contained in:
commit
e955ac144a
@ -157,7 +157,7 @@
|
||||
.fc-toolbar-title {
|
||||
cursor: pointer;
|
||||
}
|
||||
/* 클릭 가능한 날짜 (오늘 + 미래) */
|
||||
/* 클릭 가능한 날짜 */
|
||||
.fc-daygrid-day.clickable {
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
@ -362,6 +362,28 @@
|
||||
background-color: #0b5ed7 !important;
|
||||
color: white;
|
||||
}
|
||||
/* 풀 연차 버튼 스타일 */
|
||||
.vac-btn-primary {
|
||||
color: #fff;
|
||||
background-color: #28a745; /* 녹색 */
|
||||
border-color: #28a745;
|
||||
box-shadow: 0 0.125rem 0.25rem 0 rgba(40, 167, 69, 0.4);
|
||||
font-size: 28px;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
/* 풀 연차 버튼 활성화 스타일 */
|
||||
.vac-btn-primary.active {
|
||||
background-color: #218838 !important;
|
||||
color: #fff;
|
||||
border: 3px solid #91d091 !important;
|
||||
box-shadow: 0px 4px 15px rgba(0, 0, 0, 0.3);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
/* 풀 연차 버튼이 눌렸을 때 효과 */
|
||||
.vac-btn-primary:active {
|
||||
transform: scale(0.9);
|
||||
box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
/* 버튼 기본 */
|
||||
.vac-btn-success {
|
||||
transition: all 0.2s ease-in-out;
|
||||
|
||||
@ -1,26 +1,40 @@
|
||||
<template>
|
||||
<div class="row gx-2 mb-4">
|
||||
<div class="col-4">
|
||||
<div class="col-3">
|
||||
<div class="ratio ratio-1x1">
|
||||
<!-- 오전 반차 버튼 -->
|
||||
<button class="vac-btn vac-btn-warning rounded-circle d-flex align-items-center justify-content-center" :class="{ active: halfDayType === 'AM' }"
|
||||
<button class="vac-btn vac-btn-warning rounded-circle d-flex align-items-center justify-content-center"
|
||||
:class="{ active: halfDayType === 'AM' }"
|
||||
@click="toggleHalfDay('AM')">
|
||||
<i class="bi bi-sun"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div class="col-3">
|
||||
<div class="ratio ratio-1x1">
|
||||
<!-- 오후 반차 버튼 -->
|
||||
<button class="vac-btn vac-btn-info rounded-circle d-flex align-items-center justify-content-center" :class="{ active: halfDayType === 'PM' }"
|
||||
<button class="vac-btn vac-btn-info rounded-circle d-flex align-items-center justify-content-center"
|
||||
:class="{ active: halfDayType === 'PM' }"
|
||||
@click="toggleHalfDay('PM')">
|
||||
<i class="bi bi-moon"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div class="col-3">
|
||||
<div class="ratio ratio-1x1">
|
||||
<button class="vac-btn-success rounded-circle d-flex align-items-center justify-content-center" @click="addVacationRequests"
|
||||
<!-- 풀 연차 버튼 -->
|
||||
<button class="vac-btn vac-btn-primary rounded-circle d-flex align-items-center justify-content-center"
|
||||
:class="{ active: halfDayType === 'FULL' }"
|
||||
@click="toggleHalfDay('FULL')">
|
||||
<i class="bi bi-calendar"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<div class="ratio ratio-1x1">
|
||||
<!-- 저장 버튼 -->
|
||||
<button class="vac-btn-success rounded-circle d-flex align-items-center justify-content-center"
|
||||
@click="addVacationRequests"
|
||||
:class="{ active: !isDisabled, disabled: isDisabled }">
|
||||
✔
|
||||
</button>
|
||||
@ -30,11 +44,10 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineEmits, ref, defineProps, watch } from "vue";
|
||||
import { defineEmits, ref, defineProps } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
isDisabled: Boolean,
|
||||
selectedDate: String // 날짜 선택 값을 props로 받음
|
||||
isDisabled: Boolean
|
||||
});
|
||||
|
||||
const emit = defineEmits(["toggleHalfDay", "addVacationRequests", "resetHalfDay"]);
|
||||
@ -45,14 +58,7 @@ halfDayType.value = halfDayType.value === type ? null : type;
|
||||
emit("toggleHalfDay", halfDayType.value);
|
||||
};
|
||||
|
||||
// `selectedDate`가 변경되면 반차 선택 초기화
|
||||
watch(() => props.selectedDate, (newDate) => {
|
||||
if (newDate) {
|
||||
resetHalfDay();
|
||||
}
|
||||
});
|
||||
|
||||
// 날짜 선택 후 반차 버튼 상태 초기화
|
||||
// 날짜 클릭 후 버튼 상태 자동 초기화
|
||||
const resetHalfDay = () => {
|
||||
halfDayType.value = null;
|
||||
emit("resetHalfDay");
|
||||
@ -64,4 +70,3 @@ emit("addVacationRequests");
|
||||
|
||||
defineExpose({ resetHalfDay });
|
||||
</script>
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div v-if="isOpen" class="vac-modal-dialog" @click.self="closeModal">
|
||||
<div class="vac-modal-content p-5 modal-scroll">
|
||||
<h5 class="vac-modal-title">📅 내 연차 내역</h5>
|
||||
<h5 class="vac-modal-title">📅 내 연차 (누적 개수)</h5>
|
||||
<button class="close-btn" @click="closeModal">✖</button>
|
||||
<!-- 연차 목록 -->
|
||||
<div class="vac-modal-body" v-if="mergedVacations.length > 0">
|
||||
@ -11,9 +11,6 @@
|
||||
:key="vac._expandIndex"
|
||||
class="vacation-item"
|
||||
>
|
||||
<span v-if="vac.category === 'used'" class="fw-bold text-dark me-2">
|
||||
{{ usedVacationIndexMap[vac._expandIndex] }})
|
||||
</span>
|
||||
<span :class="vac.category === 'used' ? 'fw-bold text-danger me-2' : 'fw-bold text-primary me-2'">
|
||||
{{ vac.category === 'used' ? '-' : '+' }}
|
||||
</span>
|
||||
@ -22,6 +19,9 @@
|
||||
>
|
||||
{{ formatDate(vac.date) }}
|
||||
</span>
|
||||
<span v-if="vac.category === 'used'" class="fw-bold text-dark ms-1">
|
||||
( {{ usedVacationIndexMap[vac._expandIndex] }} )
|
||||
</span>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
import { useAuthStore } from '@s/useAuthStore';
|
||||
import { useUserInfoStore } from '@s/useUserInfoStore';
|
||||
|
||||
@ -6,7 +6,7 @@ import { useUserInfoStore } from '@s/useUserInfoStore';
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: "Home",
|
||||
name: 'Home',
|
||||
component: () => import('@v/MainView.vue'),
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
@ -18,23 +18,23 @@ const routes = [
|
||||
{
|
||||
path: '',
|
||||
name: 'BoardList',
|
||||
component: () => import('@v/board/BoardList.vue')
|
||||
component: () => import('@v/board/BoardList.vue'),
|
||||
},
|
||||
{
|
||||
path: 'write',
|
||||
component: () => import('@v/board/BoardWrite.vue')
|
||||
component: () => import('@v/board/BoardWrite.vue'),
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
name: 'BoardDetail',
|
||||
component: () => import('@v/board/BoardView.vue')
|
||||
component: () => import('@v/board/BoardView.vue'),
|
||||
},
|
||||
{
|
||||
path: 'edit/:id',
|
||||
name: 'BoardEdit',
|
||||
component: () => import('@v/board/BoardEdit.vue')
|
||||
}
|
||||
]
|
||||
component: () => import('@v/board/BoardEdit.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/wordDict',
|
||||
@ -71,14 +71,13 @@ const routes = [
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: () => import('@v/voteboard/voteBoardList.vue')
|
||||
component: () => import('@v/voteboard/voteBoardList.vue'),
|
||||
},
|
||||
{
|
||||
path: 'write',
|
||||
component: () => import('@v/voteboard/voteboardWrite.vue')
|
||||
component: () => import('@v/voteboard/voteboardWrite.vue'),
|
||||
},
|
||||
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/projectlist',
|
||||
@ -93,20 +92,32 @@ const routes = [
|
||||
{
|
||||
path: '/authorization',
|
||||
component: () => import('@v/admin/TheAuthorization.vue'),
|
||||
meta: { requiresAuth: true }
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{ path: "/error/400", name: "Error400", component: () => import('@v/error/Error400.vue'), meta: {layout: 'NoLayout'} },
|
||||
{ path: "/error/500", name: "Error500", component: () => import('@v/error/Error500.vue'), meta: {layout: 'NoLayout'} },
|
||||
{
|
||||
path: "/:anything(.*)",
|
||||
name: "Error404", component: () => import('@v/error/Error404.vue'), meta: {layout: 'NoLayout'}
|
||||
path: '/error/400',
|
||||
name: 'Error400',
|
||||
component: () => import('@v/error/Error400.vue'),
|
||||
meta: { layout: 'NoLayout' },
|
||||
},
|
||||
{
|
||||
path: '/error/500',
|
||||
name: 'Error500',
|
||||
component: () => import('@v/error/Error500.vue'),
|
||||
meta: { layout: 'NoLayout' },
|
||||
},
|
||||
{
|
||||
path: '/:anything(.*)',
|
||||
name: 'Error404',
|
||||
component: () => import('@v/error/Error404.vue'),
|
||||
meta: { layout: 'NoLayout' },
|
||||
},
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: routes,
|
||||
})
|
||||
});
|
||||
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
const authStore = useAuthStore();
|
||||
@ -120,9 +131,9 @@ router.beforeEach(async (to, from, next) => {
|
||||
return next({ name: 'Login', query: { redirect: to.fullPath } });
|
||||
}
|
||||
|
||||
// Authorization 페이지는 ID가 1이 아니면 접근 차단
|
||||
if (to.path === "/authorization" && userId !== allowedUserId) {
|
||||
return next("/");
|
||||
// Authorization 페이지는 ID가 26이 아니면 접근 차단
|
||||
if (to.path === '/authorization' && userId !== allowedUserId) {
|
||||
return next('/');
|
||||
}
|
||||
|
||||
// 비로그인 사용자만 접근 가능한 페이지인데 로그인된 경우 → 홈으로 이동
|
||||
@ -148,7 +159,7 @@ axios.interceptors.response.use(
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export default router
|
||||
export default router;
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
<h5>{{ user.name }}</h5>
|
||||
</div>
|
||||
<!-- 권한 토글 버튼 -->
|
||||
<label class="switch">
|
||||
<label class="switch me-0">
|
||||
<input type="checkbox" :checked="user.isAdmin" @change="toggleAdmin(user)" />
|
||||
<span class="slider round"></span>
|
||||
</label>
|
||||
@ -32,7 +32,7 @@ const users = ref([]);
|
||||
const toastStore = useToastStore();
|
||||
const baseUrl = axios.defaults.baseURL.replace(/api\/$/, "");
|
||||
const defaultProfile = "/img/icons/icon.png";
|
||||
|
||||
const allowedUserId = 1; // 특정 ID (변경필요!!)
|
||||
// 사용자 목록 가져오기
|
||||
async function fetchUsers() {
|
||||
try {
|
||||
@ -43,14 +43,17 @@ async function fetchUsers() {
|
||||
throw new Error("올바른 데이터 형식이 아닙니다.");
|
||||
}
|
||||
|
||||
// 데이터 매핑 (올바른 형식으로 변환)
|
||||
users.value = response.data.data.map(user => ({
|
||||
// MEMBERSEQ가 1이 아닌 회원만 필터링하여 데이터 매핑
|
||||
users.value = response.data.data
|
||||
.filter(user => user.MEMBERSEQ !== allowedUserId) // MEMBERSEQ가 1이면 제외
|
||||
.map(user => ({
|
||||
id: user.MEMBERSEQ,
|
||||
name: user.MEMBERNAM,
|
||||
photo: user.MEMBERPRF ? `${baseUrl}upload/img/profile/${user.MEMBERPRF}` : defaultProfile,
|
||||
color: user.MEMBERCOL,
|
||||
isAdmin: user.MEMBERROL === 'ROLE_ADMIN',
|
||||
}));
|
||||
|
||||
} catch (error) {
|
||||
toastStore.onToast('사용자 목록을 불러오지 못했습니다.', 'e');
|
||||
}
|
||||
|
||||
@ -235,8 +235,8 @@
|
||||
// 공지사항 데이터 로드
|
||||
const fetchNoticePosts = async () => {
|
||||
try {
|
||||
const { data } = await axios.get('board/notices', {
|
||||
params: { searchKeyword: searchText.value },
|
||||
const { data } = await axios.get("board/notices", {
|
||||
params: { searchKeyword: searchText.value }
|
||||
});
|
||||
|
||||
if (data?.data) {
|
||||
|
||||
@ -106,6 +106,7 @@ const isGrantModalOpen = ref(false);
|
||||
const fullCalendarRef = ref(null);
|
||||
const calendarEvents = ref([]);
|
||||
const selectedDates = ref(new Map());
|
||||
|
||||
const halfDayType = ref(null);
|
||||
const vacationCodeMap = ref({});
|
||||
const holidayDates = ref(new Set());
|
||||
@ -118,7 +119,6 @@ const lastRemainingMonth = ref(String(new Date().getMonth() + 1).padStart(2, "0"
|
||||
// 데이트피커 인풋 ref
|
||||
const calendarDatepicker = ref(null);
|
||||
let fpInstance = null;
|
||||
|
||||
/* 변경사항 여부 확인 */
|
||||
const hasChanges = computed(() => {
|
||||
return (
|
||||
@ -173,40 +173,53 @@ function handleDateClick(info) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isMyVacation = myVacations.value.some(vac => {
|
||||
const vacDate = vac.date ? vac.date.substring(0, 10) : "";
|
||||
return vacDate === clickedDateStr && !vac.receiverId;
|
||||
});
|
||||
// 기존 값 확인
|
||||
const currentValue = selectedDates.value.get(clickedDateStr);
|
||||
|
||||
const isMyVacation = myVacations.value.some(vac => vac.date.substring(0, 10) === clickedDateStr && !vac.receiverId);
|
||||
|
||||
// 이미 활성화된 날짜를 한 번 더 클릭하면 비활성화
|
||||
if (currentValue && currentValue !== "delete") {
|
||||
console.log("🛑 활성화된 날짜 비활성화:", clickedDateStr);
|
||||
selectedDates.value.delete(clickedDateStr);
|
||||
updateCalendarEvents();
|
||||
return;
|
||||
}
|
||||
|
||||
// 버튼을 누르지 않았을 때 - 삭제 모드
|
||||
if (!halfDayType.value) {
|
||||
if (isMyVacation) {
|
||||
if (selectedDates.value.get(clickedDateStr) === "delete") {
|
||||
if (currentValue === "delete") {
|
||||
selectedDates.value.delete(clickedDateStr);
|
||||
} else {
|
||||
selectedDates.value.set(clickedDateStr, "delete");
|
||||
}
|
||||
} else {
|
||||
selectedDates.value.set(clickedDateStr, "700103");
|
||||
}
|
||||
updateCalendarEvents();
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedDates.value.has(clickedDateStr)) {
|
||||
selectedDates.value.delete(clickedDateStr);
|
||||
updateCalendarEvents();
|
||||
return;
|
||||
// 버튼을 눌렀을 때 - 기존 휴가 삭제 후 새로운 값 추가
|
||||
if (isMyVacation) {
|
||||
console.log("🗑 기존 휴가 삭제 후 새로운 상태 추가:", clickedDateStr);
|
||||
selectedDates.value.set(clickedDateStr, "delete");
|
||||
}
|
||||
const type = halfDayType.value
|
||||
? (halfDayType.value === "AM" ? "700101" : "700102")
|
||||
: "700103";
|
||||
|
||||
const type = halfDayType.value === "AM" ? "700101" :
|
||||
halfDayType.value === "PM" ? "700102" :
|
||||
"700103"; // 풀연차
|
||||
|
||||
selectedDates.value.set(clickedDateStr, type);
|
||||
|
||||
if (halfDayType.value) {
|
||||
// 버튼을 한 번 사용 후 자동 해제 (일회성)
|
||||
halfDayType.value = null;
|
||||
}
|
||||
updateCalendarEvents();
|
||||
|
||||
if (halfDayButtonsRef.value) {
|
||||
halfDayButtonsRef.value.resetHalfDay();
|
||||
}
|
||||
|
||||
updateCalendarEvents();
|
||||
}
|
||||
|
||||
function markClickableDates() {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user