This commit is contained in:
khj0414 2025-03-10 13:00:38 +09:00
commit 16b95d546c
10 changed files with 541 additions and 483 deletions

View File

@ -3,6 +3,9 @@
/* 휴가 */
.fc-daygrid-event {
pointer-events: none !important;
}
/* 이벤트 선 없게 */
.fc-event {
border: none;

View File

@ -16,7 +16,7 @@
@updateReaction="handleUpdateReaction"
/>
<!-- 댓글 비밀번호 입력창 (익명일 경우) -->
<div v-if="currentPasswordCommentId === comment.commentId && unknown" class="mt-3 w-25 ms-auto">
<div v-if="currentPasswordCommentId === comment.commentId && unknown && comment.author == '익명'" class="mt-3 w-25 ms-auto">
<div class="input-group">
<input
type="password"
@ -47,49 +47,21 @@
<!-- <template v-if="isDeleted">
<p class="m-0 text-muted">댓글이 삭제되었습니다.</p>
</template> -->
<PlusButton v-if="isPlusButton" @click="toggleComment" class="mt-6"/>
<BoardCommentArea v-if="isComment" :unknown="unknown" @submitComment="submitComment"/>
<PlusButton v-if="isPlusButton" @click="toggleComment" class="mt-6" />
<BoardCommentArea v-if="isComment" :unknown="unknown" @submitComment="submitComment" :commnetId="comment.commentId" />
<!-- 대댓글 -->
<ul v-if="comment.children && comment.children.length" class="list-unstyled">
<li
v-for="child in comment.children"
:key="child.commentId"
class="mt-8 pt-6 ps-10 border-top"
>
<BoardComment
:comment="child"
:unknown="child.author === '익명'"
:isPlusButton="false"
:isLike="true"
:isCommentProfile="true"
:isCommentAuthor="child.isCommentAuthor"
:isCommentPassword="isCommentPassword"
:currentPasswordCommentId="currentPasswordCommentId"
:passwordCommentAlert="passwordCommentAlert"
:password="password"
@editClick="handleReplyEditClick"
@deleteClick="$emit('deleteClick', child)"
@submitEdit="(comment, editedContent) => $emit('submitEdit', comment, editedContent)"
@cancelEdit="$emit('cancelEdit', child)"
@submitComment="submitComment"
@updateReaction="handleUpdateReaction"
@submitPassword="$emit('submitPassword', child, password)"
@update:password="$emit('update:password', $event)"
/>
</li>
</ul>
<slot name="reply"></slot>
</div>
</template>
<script setup>
import { defineProps, defineEmits, ref, computed, watch } from 'vue';
import BoardProfile from './BoardProfile.vue';
import BoardCommentArea from './BoardCommentArea.vue';
import PlusButton from '../button/PlusBtn.vue';
import SaveBtn from '../button/SaveBtn.vue';
import { defineProps, defineEmits, ref, computed, watch } from 'vue';
import BoardProfile from './BoardProfile.vue';
import BoardCommentArea from './BoardCommentArea.vue';
import PlusButton from '../button/PlusBtn.vue';
import SaveBtn from '../button/SaveBtn.vue';
const props = defineProps({
const props = defineProps({
comment: {
type: Object,
required: true,
@ -112,11 +84,11 @@ const props = defineProps({
},
isEditTextarea: {
type: Boolean,
default: false
default: false,
},
isDeleted: {
type: Boolean,
default: false
default: false,
},
isCommentPassword: {
type: Boolean,
@ -124,74 +96,80 @@ const props = defineProps({
},
passwordCommentAlert: {
type: String,
default: ''
default: '',
},
currentPasswordCommentId: {
type: Number
type: Number,
},
password:{
type: String
password: {
type: String,
},
});
});
// emits
const emit = defineEmits(['submitComment', 'updateReaction', 'editClick', 'deleteClick', 'submitPassword', 'submitEdit', 'cancelEdit', 'update:password']);
// emits
const emit = defineEmits([
'submitComment',
'updateReaction',
'editClick',
'deleteClick',
'submitPassword',
'submitEdit',
'cancelEdit',
'update:password',
]);
const localEditedContent = ref(props.comment.content);
const localEditedContent = ref(props.comment.content);
//
const isComment = ref(false);
const toggleComment = () => {
//
const isComment = ref(false);
const toggleComment = () => {
isComment.value = !isComment.value;
};
};
//
const submitComment = (newComment) => {
//
const submitComment = newComment => {
emit('submitComment', { parentId: props.comment.commentId, ...newComment, LOCBRDTYP: newComment.LOCBRDTYP });
isComment.value = false;
};
};
// ,
const handleUpdateReaction = (reactionData) => {
// ,
const handleUpdateReaction = reactionData => {
emit('updateReaction', {
boardId: props.comment.boardId,
commentId: props.comment.commentId || reactionData.commentId,
...reactionData,
});
};
};
//
const logPasswordAndEmit = () => {
//
const logPasswordAndEmit = () => {
emit('submitPassword', props.comment, props.password);
};
};
watch(() => props.comment.isEditTextarea, (newVal) => {
watch(
() => props.comment.isEditTextarea,
newVal => {
if (newVal) {
localEditedContent.value = props.comment.content;
}
});
},
);
// watch(() => props.comment.isDeleted, () => {
// console.log("BoardComment - isDeleted :", newVal);
// watch(() => props.comment.isDeleted, () => {
// console.log("BoardComment - isDeleted :", newVal);
// if (newVal) {
// localEditedContent.value = " ."; // UI
// props.comment.isEditTextarea = false;
// }
// });
// if (newVal) {
// localEditedContent.value = " ."; // UI
// props.comment.isEditTextarea = false;
// }
// });
//
const submitEdit = () => {
//
const submitEdit = () => {
emit('submitEdit', props.comment, localEditedContent.value);
};
};
const handleEditClick = () => {
const handleEditClick = () => {
emit('editClick', props.comment);
}
const handleReplyEditClick = (comment) => {
emit('editClick', comment);
}
};
</script>

View File

@ -11,7 +11,7 @@
</div> -->
<!-- 텍스트박스 -->
<div class="w-100">
<textarea class="form-control" placeholder="댓글 달기" rows="3" v-model="comment"></textarea>
<textarea class="form-control" placeholder="댓글 달기" rows="3" :maxlength="maxLength" v-model="comment"></textarea>
<span v-if="commentAlert" class="invalid-feedback d-block text-start ms-2">{{ commentAlert }}</span>
<span v-else class="invalid-feedback d-block text-start ms-2">{{ textAlert }}</span>
</div>
@ -22,8 +22,8 @@
<div class="d-flex flex-wrap align-items-center">
<!-- 익명 체크박스 (익명게시판일 경우에만)-->
<div v-if="unknown" class="form-check form-check-inline mb-0 me-4">
<input class="form-check-input" type="checkbox" id="inlineCheckbox1" v-model="isCheck" />
<label class="form-check-label" for="inlineCheckbox1">익명</label>
<input class="form-check-input" type="checkbox" :id="`checkboxAnnonymous${commnetId}`" v-model="isCheck" />
<label class="form-check-label" :for="`checkboxAnnonymous${commnetId}`">익명</label>
</div>
<!-- 비밀번호 입력 필드 (익명이 선택된 경우에만 표시) -->
@ -51,10 +51,10 @@
</template>
<script setup>
import { ref, defineEmits, defineProps, watch, inject } from 'vue';
import SaveBtn from '../button/SaveBtn.vue';
import { ref, defineEmits, defineProps, watch, inject } from 'vue';
import SaveBtn from '../button/SaveBtn.vue';
const props = defineProps({
const props = defineProps({
unknown: {
type: Boolean,
default: true,
@ -71,18 +71,25 @@ const props = defineProps({
type: String,
default: '',
},
});
maxLength: {
type: Number,
default: 500,
},
commnetId: {
type: Number,
},
});
const $common = inject('common');
const comment = ref('');
const password = ref('');
const isCheck = ref(false);
const textAlert = ref('');
const passwordAlert2 = ref('');
const $common = inject('common');
const comment = ref('');
const password = ref('');
const isCheck = ref(false);
const textAlert = ref('');
const passwordAlert2 = ref('');
const emit = defineEmits(['submitComment']);
const emit = defineEmits(['submitComment']);
const handleCommentSubmit = () => {
const handleCommentSubmit = () => {
if (!$common.isNotEmpty(comment.value)) {
textAlert.value = '댓글을 입력하세요';
return false;
@ -107,22 +114,21 @@ const handleCommentSubmit = () => {
//
resetCommentForm();
};
};
//
const resetCommentForm = () => {
//
const resetCommentForm = () => {
comment.value = '';
password.value = '';
isCheck.value = false;
};
};
watch(
watch(
() => props.passwordAlert,
() => {
if (!props.passwordAlert) {
resetCommentForm();
}
}
);
},
);
</script>

View File

@ -1,10 +1,6 @@
<template>
<ul class="list-unstyled mt-10">
<li
v-for="comment in comments"
:key="comment.commentId"
class="mt-6 border-bottom pb-6"
>
<li v-for="comment in comments" :key="comment.commentId" class="mt-6 border-bottom pb-6">
<BoardComment
:unknown="unknown"
:comment="comment"
@ -21,22 +17,50 @@
@submitComment="submitComment"
@submitEdit="handleSubmitEdit"
@cancelEdit="handleCancelEdit"
@updateReaction="(reactionData) => handleUpdateReaction(reactionData, comment.commentId, comment.boardId)"
@updateReaction="reactionData => handleUpdateReaction(reactionData, comment.commentId, comment.boardId)"
@update:password="updatePassword"
>
<!-- 대댓글 -->
<template #reply>
<ul v-if="comment.children && comment.children.length" class="list-unstyled">
<li v-for="(child, index) in comment.children" :key="child.commentId" class="mt-8 pt-6 ps-10 border-top">
<BoardComment
:comment="child"
:unknown="child.author === '익명'"
:isPlusButton="false"
:isLike="true"
:isCommentProfile="true"
:isCommentAuthor="child.isCommentAuthor"
:isCommentPassword="isCommentPassword"
:currentPasswordCommentId="currentPasswordCommentId"
:passwordCommentAlert="passwordCommentAlert"
:password="password"
@editClick="handleReplyEditClick"
@deleteClick="$emit('deleteClick', child)"
@submitEdit="(comment, editedContent) => $emit('submitEdit', comment, editedContent)"
@cancelEdit="$emit('cancelEdit', child)"
@submitComment="submitComment"
@updateReaction="handleUpdateReaction"
@submitPassword="$emit('submitPassword', child, password)"
@update:password="$emit('update:password', $event)"
/>
</li>
</ul>
</template>
</BoardComment>
</li>
</ul>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue';
import BoardComment from './BoardComment.vue'
import { defineProps, defineEmits } from 'vue';
import BoardComment from './BoardComment.vue';
const props = defineProps({
const props = defineProps({
comments: {
type: Array,
required: true,
default: () => []
default: () => [],
},
unknown: {
type: Boolean,
@ -60,23 +84,35 @@ const props = defineProps({
},
passwordCommentAlert: {
type: String,
default: ''
default: '',
},
currentPasswordCommentId: {
type: Number
type: Number,
},
password:{
type: String
password: {
type: String,
},
});
index: {
type: Number,
},
});
const emit = defineEmits(['submitComment', 'updateReaction', 'editClick', 'deleteClick', 'submitPassword', 'clearPassword','submitEdit', 'update:password']);
const emit = defineEmits([
'submitComment',
'updateReaction',
'editClick',
'deleteClick',
'submitPassword',
'clearPassword',
'submitEdit',
'update:password',
]);
const submitComment = (replyData) => {
const submitComment = replyData => {
emit('submitComment', replyData);
};
};
const handleUpdateReaction = (reactionData, commentId, boardId) => {
const handleUpdateReaction = (reactionData, commentId, boardId) => {
const updatedReactionData = {
...reactionData,
commentId: commentId || reactionData.commentId,
@ -84,41 +120,45 @@ const handleUpdateReaction = (reactionData, commentId, boardId) => {
};
emit('updateReaction', updatedReactionData);
}
};
const submitPassword = (comment, password) => {
const submitPassword = (comment, password) => {
emit('submitPassword', comment, password);
};
};
const handleEditClick = (comment) => {
const handleEditClick = comment => {
if (comment.parentId) {
emit('editClick', comment); //
} else {
emit('editClick', comment); //
}
};
};
const handleSubmitEdit = (comment, editedContent) => {
emit("submitEdit", comment, editedContent);
};
const handleSubmitEdit = (comment, editedContent) => {
emit('submitEdit', comment, editedContent);
};
const handleDeleteClick = (comment) => {
const handleDeleteClick = comment => {
if (comment.parentId) {
emit('deleteClick', comment); //
} else {
emit('deleteClick', comment); //
}
};
};
const handleCancelEdit = (comment) => {
const handleCancelEdit = comment => {
if (comment.parentId) {
emit('cancelEdit', comment); //
} else {
emit('cancelEdit', comment); //
}
};
};
const updatePassword = (newPassword) => {
const updatePassword = newPassword => {
emit('update:password', newPassword);
};
};
const handleReplyEditClick = comment => {
emit('editClick', comment);
};
</script>

View File

@ -40,7 +40,7 @@
const defaultProfile = '/img/icons/icon.png';
// (Vue )
const baseUrl = 'http://localhost:10325/'; // API URL
const baseUrl = import.meta.env.VITE_SERVER; // API URL
// Props
const props = defineProps({

View File

@ -1,35 +1,35 @@
<template v-if="isRecommend">
<button class="btn btn-label-primary btn-icon" :class="{'clicked': likeClicked, 'big': bigBtn}" @click="handleLike">
<button class="btn btn-label-primary btn-icon" :class="{ clicked: likeClicked, big: bigBtn }" @click="handleLike">
<i class="fa-regular fa-thumbs-up"></i> <span class="num">{{ likeCount }}</span>
</button>
<button class="btn btn-label-danger btn-icon" :class="{'clicked': dislikeClicked, 'big': bigBtn}" @click="handleDislike">
<button class="btn btn-label-danger btn-icon" :class="{ clicked: dislikeClicked, big: bigBtn }" @click="handleDislike">
<i class="fa-regular fa-thumbs-down"></i> <span class="num">{{ dislikeCount }}</span>
</button>
</template>
<script setup>
import { ref, computed } from 'vue';
import { ref, computed } from 'vue';
const props = defineProps({
const props = defineProps({
comment: {
type: Object,
default: () => ({}),
},
likeClicked : {
type : Boolean,
default : false,
likeClicked: {
type: Boolean,
default: false,
},
dislikeClicked : {
type : Boolean,
default : false,
dislikeClicked: {
type: Boolean,
default: false,
},
bigBtn : {
type :Boolean,
default : false,
bigBtn: {
type: Boolean,
default: false,
},
isRecommend: {
type:Boolean,
default:true,
type: Boolean,
default: true,
},
boardId: {
type: Number,
@ -47,78 +47,77 @@ const props = defineProps({
type: Number,
default: 0,
},
});
});
const emit = defineEmits(['updateReaction']);
const emit = defineEmits(['updateReaction']);
const likeClicked = ref(props.likeClicked);
const dislikeClicked = ref(props.dislikeClicked);
const likeCount = computed(() => props.comment?.likeCount ?? props.likeCount);
const dislikeCount = computed(() => props.comment?.dislikeCount ?? props.dislikeCount);
const likeClicked = ref(props.likeClicked);
const dislikeClicked = ref(props.dislikeClicked);
const likeCount = computed(() => props.comment?.likeCount ?? props.likeCount);
const dislikeCount = computed(() => props.comment?.dislikeCount ?? props.dislikeCount);
const handleLike = () => {
const handleLike = () => {
const isLike = !likeClicked.value;
const isDislike = false;
emit('updateReaction', { boardId: props.boardId, commentId: props.commentId, isLike, isDislike });
likeClicked.value = isLike;
dislikeClicked.value = false;
};
};
const handleDislike = () => {
const handleDislike = () => {
const isDislike = !dislikeClicked.value;
const isLike = false;
emit('updateReaction', { boardId: props.boardId, commentId: props.commentId, isLike, isDislike });
dislikeClicked.value = isDislike;
likeClicked.value = false;
};
};
</script>
<style scoped>
.btn + .btn {
.btn + .btn {
margin-left: 5px;
}
}
.num {
.num {
margin-left: 5px;
}
}
.btn-label-danger.clicked {
.btn-label-danger.clicked {
background-color: #e6381a;
}
}
.btn-label-danger.clicked i,
.btn-label-danger.clicked span {
.btn-label-danger.clicked i,
.btn-label-danger.clicked span {
color: #fff;
}
}
.btn-label-primary.clicked {
.btn-label-primary.clicked {
background-color: #5f61e6;
}
}
.btn-label-primary.clicked i,
.btn-label-primary.clicked span {
color : #fff;
}
.btn-label-primary.clicked i,
.btn-label-primary.clicked span {
color: #fff;
}
.btn {
.btn {
width: 55px;
height: 30px;
}
/* height: 30px; */
}
.btn.big {
.btn.big {
width: 70px;
height: 70px;
font-size: 18px;
}
}
@media screen and (max-width:450px) {
@media screen and (max-width: 450px) {
.btn {
width: 50px;
height: 20px;
font-size: 12px;
}
}
}
</style>

View File

@ -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">
@ -26,8 +26,8 @@
</ol>
</div>
<!-- 연차 데이터 없음 -->
<p v-else class="text-sm-center mt-10 text-gray">
🚫 사용한 연차가 없습니다.
<p v-else class="text-sm-center mt-10 text-gray vac-modal-title">
🚫 연차 내역이 없습니다.
</p>
</div>
</div>
@ -58,19 +58,15 @@ const emit = defineEmits(["close"]);
// (,)
let globalCounter = 0;
const usedVacations = computed(() => {
const result = [];
props.myVacations.forEach((v) => {
return props.myVacations.flatMap((v) => {
const count = v.used_quota || 1;
for (let i = 0; i < count; i++) {
result.push({
return Array.from({ length: count }, (_, i) => ({
...v,
category: "used",
code: v.LOCVACTYP,
_expandIndex: globalCounter++,
}));
});
}
});
return result;
});
//

View File

@ -69,12 +69,18 @@ nextTick(() => {
});
const sortedUserList = computed(() => {
if (!employeeId.value) return userList.value;
const myProfile = userList.value.find(user => user.MEMBERSEQ === employeeId.value);
const otherUsers = userList.value.filter(user => user.MEMBERSEQ !== employeeId.value);
return myProfile ? [myProfile, ...otherUsers] : userList.value;
if (!employeeId.value) return [];
//
const nonAdminUsers = userList.value.filter(user => user.MEMBERROL !== "ROLE_ADMIN");
const myProfile = nonAdminUsers.find(user => user.MEMBERSEQ === employeeId.value);
const otherUsers = nonAdminUsers.filter(user => user.MEMBERSEQ !== employeeId.value);
return myProfile ? [myProfile, ...otherUsers] : otherUsers;
});
const getUserProfileImage = (profilePath) =>
profilePath && profilePath.trim() ? `${baseUrl}upload/img/profile/${profilePath}` : defaultProfile;
@ -94,11 +100,11 @@ if (windowWidth.value >= 1650) {
if (totalUsers <= 15) return "30px";
return "20px";
} else if (windowWidth.value >= 1024) {
if (totalUsers <= 10) return "35px";
if (totalUsers <= 10) return "40px";
if (totalUsers <= 15) return "30px";
return "20px";
} else {
return "20px";
return "30px";
}
});

View File

@ -92,6 +92,7 @@
:unknown="unknown"
:commentAlert="commentAlert"
:passwordAlert="passwordAlert"
:maxLength="500"
@submitComment="handleCommentSubmit"
/>
</div>
@ -237,7 +238,6 @@
const data = response.data.data;
profileName.value = data.author || '익명';
console.log(data.author);
authorId.value = data.authorId;
boardTitle.value = data.title || '제목 없음';
boardContent.value = data.content || '';
@ -580,7 +580,7 @@
LOCBRDPWD: password.value,
LOCBRDSEQ: currentBoardId.value,
});
console.log('response: ', response);
if (response.data.code === 200 && response.data.data === true) {
password.value = '';
isPassword.value = false;

View File

@ -142,7 +142,7 @@ const calendarOptions = reactive({
datesSet: handleMonthChange,
events: calendarEvents,
});
//
//
function handleMonthChange(viewInfo) {
const currentDate = viewInfo.view.currentStart;
const year = currentDate.getFullYear();
@ -154,16 +154,17 @@ function handleDateClick(info) {
const clickedDateStr = info.dateStr;
const clickedDate = info.date;
const todayStr = new Date().toISOString().split("T")[0];
if (
clickedDate.getDay() === 0 ||
clickedDate.getDay() === 6 ||
holidayDates.value.has(clickedDateStr) ||
clickedDateStr < todayStr
){
) {
return;
}
const isMyVacation = myVacations.value.some(vac => {
const vacDate = vac.date ? String(vac.date).substring(0, 10) : "";
const vacDate = vac.date ? vac.date.substring(0, 10) : "";
return vacDate === clickedDateStr && !vac.receiverId;
});
if (isMyVacation) {
@ -186,7 +187,6 @@ function handleDateClick(info) {
selectedDates.value.set(clickedDateStr, type);
halfDayType.value = null;
updateCalendarEvents();
//
if (halfDayButtonsRef.value) {
halfDayButtonsRef.value.resetHalfDay();
}
@ -266,15 +266,24 @@ const handleProfileClick = async (user) => {
const fetchUserList = async () => {
try {
await userListStore.fetchUserList();
userList.value = userListStore.userList;
// "ROLE_ADMIN"
const filteredUsers = userListStore.userList.filter(user => user.MEMBERROL !== "ROLE_ADMIN");
// userList
userList.value = [...filteredUsers];
if (!userList.value.length) {
console.warn("📌 사용자 목록이 비어 있음!");
return;
}
//
userColors.value = {};
userList.value.forEach((user) => {
userColors.value[user.MEMBERSEQ] = user.usercolor;
});
} catch (error) {
console.error("📌 사용자 목록 불러오기 오류:", error);
}
@ -310,47 +319,57 @@ const filteredReceivedVacations = computed(() => {
});
});
/* 휴가 변경사항 저장 */
/* 휴가 저장 */
//
async function saveVacationChanges() {
if (!hasChanges.value) return;
const selectedDatesArray = Array.from(selectedDates.value);
const vacationsToAdd = selectedDatesArray
.filter(([date, type]) => type !== "delete")
.filter(([date, type]) =>
!myVacations.value.some(vac => vac.date && vac.date.startsWith(date)) ||
myVacations.value.some(vac => vac.date && vac.date.startsWith(date) && vac.receiverId)
)
.map(([date, type]) => ({ date, type }));
const vacationsToDelete = myVacations.value
const vacationChangesByYear = selectedDatesArray.reduce((acc, [date, type]) => {
const year = date.split("-")[0]; // YYYY-MM-DD YYYY
if (!acc[year]) acc[year] = { add: [], delete: [] };
if (type !== "delete") {
acc[year].add.push({ date, type });
} else {
acc[year].delete.push(date);
}
return acc;
}, {});
try {
for (const year of Object.keys(vacationChangesByYear)) {
const vacationsToAdd = vacationChangesByYear[year].add;
// id
const vacationsToDeleteForYear = myVacations.value
.filter(vac => {
if (!vac.date) return false;
const date = vac.date.split("T")[0];
return selectedDates.value.get(date) === "delete" && !vac.receiverId;
})
.map(vac => {
const id = vac.id;
return typeof id === "number" ? Number(id) : id;
const vacDate = vac.date.split("T")[0];
return vacationChangesByYear[year].delete.includes(vacDate);
});
try {
const vacationIdsToDelete = vacationsToDeleteForYear.map(vac => vac.id);
if (vacationsToAdd.length > 0 || vacationIdsToDelete.length > 0) {
const response = await axios.post("vacation/batchUpdate", {
add: vacationsToAdd,
delete: vacationsToDelete
delete: vacationIdsToDelete,
});
if (response.data && response.data.status === "OK") {
toastStore.onToast('휴가 변경 사항이 저장되었습니다.', 's');
await fetchVacationHistory(lastRemainingYear.value);
await fetchRemainingVacation();
if (isModalOpen.value) {
await fetchVacationHistory(lastRemainingYear.value);
toastStore.onToast(`휴가 변경 사항이 저장되었습니다.`, 's');
// : myVacations ID
myVacations.value = myVacations.value.filter(vac => !vacationIdsToDelete.includes(vac.id));
//
const updatedVacations = await fetchVacationHistory(year);
if (updatedVacations) {
myVacations.value = updatedVacations; //
}
const currentDate = fullCalendarRef.value.getApi().getDate();
await loadCalendarData(currentDate.getFullYear(), currentDate.getMonth() + 1);
} else {
toastStore.onToast(`휴가 변경 중 오류가 발생했습니다.`, 'e');
}
}
}
await fetchRemainingVacation();
selectedDates.value.clear();
updateCalendarEvents();
} else {
toastStore.onToast('휴가 저장 중 오류가 발생했습니다.', 'e');
}
//
const currentDate = fullCalendarRef.value.getApi().getDate();
await loadCalendarData(currentDate.getFullYear(), currentDate.getMonth() + 1);
} catch (error) {
console.error("🚨 휴가 변경 저장 실패:", error);
toastStore.onToast('휴가 저장 요청에 실패했습니다.', 'e');
@ -363,15 +382,17 @@ async function fetchVacationHistory(year) {
try {
const response = await axios.get(`vacation/history?year=${year}`);
if (response.status === 200 && response.data) {
myVacations.value = response.data.data.usedVacations || [];
receivedVacations.value = response.data.data.receivedVacations || [];
} else {
console.warn("❌ 연차 내역을 불러오지 못했습니다.");
myVacations.value = [];
receivedVacations.value = [];
const newVacations = response.data.data.usedVacations || [];
const uniqueVacations = Array.from(
new Map([...myVacations.value, ...newVacations].map(v => [`${v.date}-${v.type}`, v]))
.values()
);
myVacations.value = uniqueVacations;
}
} catch (error) {
console.error("🚨 연차 데이터 불러오기 실패:", error);
console.error(`🚨 ${year}년 휴가 데이터 불러오기 실패:`, error);
}
}
//
@ -462,16 +483,20 @@ function toggleHalfDay(type) {
/* 페이지 이동 시 변경 사항 확인 */
router.beforeEach((to, from, next) => {
if (hasChanges.value) {
console.log('휴가!!!!!');
const answer = window.confirm("저장하지 않은 변경 사항이 있습니다. 이동하시겠습니까?");
if (!answer) {
return next(false);
}
}
selectedDates.value.clear();
next();
});
onBeforeUnmount(() => {
window.removeEventListener("beforeunload", preventUnsavedChanges);
});
/* 새로고침 또는 페이지 종료 시 알림 */
function preventUnsavedChanges(event) {
if (hasChanges.value) {
event.preventDefault();
@ -480,8 +505,10 @@ function preventUnsavedChanges(event) {
}
/* watch */
watch(lastRemainingYear, async (newYear, oldYear) => {
watch(() => lastRemainingYear.value, async (newYear, oldYear) => {
if (newYear !== oldYear) {
await fetchVacationHistory(newYear);
}
});
// `selectedDates`
watch(
@ -533,6 +560,9 @@ onMounted(async () => {
altFormat: "F Y"
})
],
onOpen: function() {
document.querySelector('.flatpickr-input').style.visibility = 'hidden';
},
onChange: function(selectedDatesArr, dateStr) {
//
fullCalendarRef.value.getApi().gotoDate(dateStr + "-01");