메인 이벤트 달력
This commit is contained in:
parent
a540feb851
commit
c1274cf9a0
@ -76,7 +76,7 @@ const common = {
|
||||
formatDateTime(dateObj) {
|
||||
const date = new Date(dateObj);
|
||||
const dateCheck = date.getTime();
|
||||
if (isNaN(dateCheck)) return dateStr;
|
||||
if (isNaN(dateCheck)) return dateObj;
|
||||
|
||||
const zeroFormat = num => (num < 10 ? `0${num}` : num);
|
||||
return {
|
||||
|
||||
201
src/components/main/EventModal.vue
Normal file
201
src/components/main/EventModal.vue
Normal file
@ -0,0 +1,201 @@
|
||||
<template>
|
||||
<div class="event-modal position-fixed bg-white shadow rounded" :style="modalStyle">
|
||||
<!-- 이벤트 선택 화면 -->
|
||||
<div v-if="!selectedEventType" class="d-flex flex-wrap gap-2 p-2">
|
||||
<div v-for="event in eventTypes" :key="event.code" class="event-icon-wrapper position-relative">
|
||||
<img
|
||||
:src="`${baseUrl}img/main-category-img/main-${event.code}.png`"
|
||||
class="event-icon-select"
|
||||
style="width: 25px; height: 25px; cursor: pointer"
|
||||
@click="handleEventClick(event)"
|
||||
/>
|
||||
<!-- X 표시 수정 -->
|
||||
<span v-if="isEventExists(event.type)" class="cancel-mark"> × </span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 입력 폼 화면 -->
|
||||
<div v-else class="p-2" style="min-width: 200px">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<small class="text-muted">{{ getEventTitle(selectedEventType) }}</small>
|
||||
<button class="btn-close btn-close-sm" style="font-size: 8px" @click="resetForm"></button>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control form-control-sm py-1"
|
||||
style="height: 25px; font-size: 12px"
|
||||
placeholder="장소"
|
||||
v-model="eventPlace"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<input type="time" class="form-control form-control-sm py-1" style="height: 25px; font-size: 12px" v-model="eventTime" />
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<button class="btn btn-primary btn-sm py-1" style="font-size: 12px; height: 25px; line-height: 1" @click="handleSubmit">
|
||||
등록
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
position: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({ x: 0, y: 0 }),
|
||||
},
|
||||
selectedDate: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
baseUrl: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
dateEvents: {
|
||||
type: Array,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['select', 'delete', 'insert']);
|
||||
|
||||
// 폼 관련 상태
|
||||
const selectedEventType = ref(null);
|
||||
const eventPlace = ref('');
|
||||
const eventTime = ref('');
|
||||
|
||||
const eventTypes = [
|
||||
{ type: 'birthdayParty', code: '300203', title: '생일파티' },
|
||||
{ type: 'dinner', code: '300204', title: '회식' },
|
||||
{ type: 'teaTime', code: '300205', title: '티타임' },
|
||||
{ type: 'workshop', code: '300206', title: '워크샵' },
|
||||
];
|
||||
|
||||
const getEventTitle = type => {
|
||||
return eventTypes.find(event => event.type === type)?.title || '';
|
||||
};
|
||||
|
||||
const isEventExists = type => {
|
||||
return props.dateEvents?.some(event => event.type === type);
|
||||
};
|
||||
|
||||
const handleEventClick = event => {
|
||||
if (isEventExists(event.type)) {
|
||||
if (confirm('이벤트를 취소하시겠습니까?')) {
|
||||
emit('delete', {
|
||||
date: props.selectedDate,
|
||||
code: event.code,
|
||||
title: event.title,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
selectedEventType.value = event.code;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (!eventPlace.value || !eventTime.value) {
|
||||
alert('장소와 시간을 모두 입력해주세요');
|
||||
return;
|
||||
}
|
||||
|
||||
emit('insert', {
|
||||
date: props.selectedDate,
|
||||
code: selectedEventType.value,
|
||||
title: getEventTitle(selectedEventType.value),
|
||||
place: eventPlace.value,
|
||||
time: eventTime.value,
|
||||
});
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
selectedEventType.value = null;
|
||||
eventPlace.value = '';
|
||||
eventTime.value = '';
|
||||
};
|
||||
|
||||
// 모달 스타일 계산을 computed로 변경
|
||||
const modalStyle = computed(() => {
|
||||
const modalWidth = 200; // 모달의 예상 너비
|
||||
const modalHeight = 150; // 모달의 예상 높이
|
||||
const viewportWidth = window.innerWidth;
|
||||
const viewportHeight = window.innerHeight;
|
||||
|
||||
let x = props.position?.x || 0;
|
||||
let y = props.position?.y || 0;
|
||||
|
||||
// 모달이 뷰포트를 벗어나지 않도록 조정
|
||||
if (x + modalWidth > viewportWidth) {
|
||||
x = viewportWidth - modalWidth - 10;
|
||||
}
|
||||
if (x < 0) {
|
||||
x = 10;
|
||||
}
|
||||
if (y + modalHeight > viewportHeight) {
|
||||
y = viewportHeight - modalHeight - 10;
|
||||
}
|
||||
if (y < 0) {
|
||||
y = 10;
|
||||
}
|
||||
|
||||
return {
|
||||
left: `${x}px`,
|
||||
top: `${y}px`,
|
||||
zIndex: 1050,
|
||||
maxWidth: '90vw', // 뷰포트 너비의 90%를 넘지 않도록
|
||||
maxHeight: '90vh', // 뷰포트 높이의 90%를 넘지 않도록
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.event-icon-wrapper {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.event-icon-select {
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.event-icon-select:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.cancel-mark {
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
right: -8px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.event-modal {
|
||||
min-width: 120px;
|
||||
max-width: 300px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* 작은 화면에서의 스타일 */
|
||||
@media (max-width: 576px) {
|
||||
.event-modal {
|
||||
min-width: 100px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -15,30 +15,18 @@
|
||||
{{ user.name }}
|
||||
</p>
|
||||
|
||||
<CommuterBtn
|
||||
:userId="user.id"
|
||||
:checkedInProject="checkedInProject || {}"
|
||||
@workTimeUpdated="handleWorkTimeUpdate"
|
||||
@leaveTimeUpdated="handleLeaveTimeUpdate"
|
||||
ref="workTimeComponentRef"
|
||||
/>
|
||||
<CommuterBtn :userId="user.id" :checkedInProject="checkedInProject || {}" ref="workTimeComponentRef" />
|
||||
|
||||
<MainEventList
|
||||
:categoryList="categoryList"
|
||||
:baseUrl="baseUrl"
|
||||
:birthdayList="birthdayList"
|
||||
:vacationList="vacationList"
|
||||
:birthdayPartyList="birthdayPartyList"
|
||||
:dinnerList="dinnerList"
|
||||
:teaTimeList="teaTimeList"
|
||||
:workShopList="workShopList"
|
||||
/>
|
||||
<!-- <CommuterProjectList
|
||||
:categoryList="categoryList"
|
||||
:project="project"
|
||||
:commuters="commuters"
|
||||
:baseUrl="baseUrl"
|
||||
:user="user"
|
||||
:selectedProject="selectedProject"
|
||||
:checkedInProject="checkedInProject"
|
||||
@drop="handleProjectDrop"
|
||||
/> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -60,41 +48,24 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CenterModal :display="isModalOpen" @close="closeModal">
|
||||
<template #title>
|
||||
{{ eventDate }}
|
||||
</template>
|
||||
<template #body>
|
||||
<div v-if="selectedDateCommuters.length > 0">
|
||||
<div v-for="(commuter, index) in selectedDateCommuters" :key="index">
|
||||
<div class="d-flex align-items-center my-2">
|
||||
<img
|
||||
:src="`${baseUrl}upload/img/profile/${commuter.profile}`"
|
||||
class="rounded-circle me-2 w-px-50 h-px-50"
|
||||
@error="$event.target.src = '/img/icons/icon.png'"
|
||||
/>
|
||||
|
||||
<span class="text-white fw-bold rounded py-1 px-3" :style="`background: ${commuter.projctcolor} !important;`">{{
|
||||
commuter.memberName
|
||||
}}</span>
|
||||
|
||||
<div class="ms-auto text-start fw-bold">{{ commuter.COMMUTCMT }} ~ {{ commuter.COMMUTLVE || '00:00:00' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<BackBtn @click="closeModal" />
|
||||
</template>
|
||||
</CenterModal>
|
||||
<EventModal
|
||||
v-if="showModal"
|
||||
:position="modalPosition"
|
||||
:selected-date="selectedDate"
|
||||
:base-url="baseUrl"
|
||||
:date-events="currentDateEvents"
|
||||
@select="handleEventSelect"
|
||||
@delete="handleEventDelete"
|
||||
@insert="handleEventInsert"
|
||||
@close="handleCloseModal"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import FullCalendar from '@fullcalendar/vue3';
|
||||
import dayGridPlugin from '@fullcalendar/daygrid';
|
||||
import interactionPlugin from '@fullcalendar/interaction';
|
||||
import CenterModal from '@c/modal/CenterModal.vue';
|
||||
import { computed, inject, onMounted, reactive, ref, watch } from 'vue';
|
||||
import { inject, onMounted, reactive, ref, watch } from 'vue';
|
||||
import $api from '@api';
|
||||
import 'flatpickr/dist/flatpickr.min.css';
|
||||
import '@/assets/css/app-calendar.css';
|
||||
@ -102,13 +73,12 @@
|
||||
import { useUserInfoStore } from '@/stores/useUserInfoStore';
|
||||
import { useProjectStore } from '@/stores/useProjectStore';
|
||||
import CommuterBtn from '@c/commuters/CommuterBtn.vue';
|
||||
import CommuterProjectList from '@c/commuters/CommuterProjectList.vue';
|
||||
import MainEventList from '@c/main/MainEventList.vue';
|
||||
import BackBtn from '@c/button/BackBtn.vue';
|
||||
import EventModal from '@c/main/EventModal.vue';
|
||||
import { useToastStore } from '@s/toastStore';
|
||||
|
||||
const baseUrl = import.meta.env.VITE_DOMAIN;
|
||||
const user = ref({});
|
||||
const project = ref({});
|
||||
const userStore = useUserInfoStore();
|
||||
const projectStore = useProjectStore();
|
||||
|
||||
@ -116,39 +86,45 @@
|
||||
const fullCalendarRef = ref(null);
|
||||
const workTimeComponentRef = ref(null);
|
||||
const calendarEvents = ref([]);
|
||||
const eventDate = ref('');
|
||||
|
||||
const selectedProject = ref(null);
|
||||
const checkedInProject = ref(null);
|
||||
|
||||
// 모달 상태
|
||||
const isModalOpen = ref(false);
|
||||
|
||||
const commuters = ref([]);
|
||||
const monthlyCommuters = ref([]);
|
||||
|
||||
// 이벤트 모달 관련
|
||||
const showModal = ref(false);
|
||||
const modalPosition = ref({ x: 0, y: 0 });
|
||||
const selectedDate = ref('');
|
||||
|
||||
// 공통 함수
|
||||
const $common = inject('common');
|
||||
const toastStore = useToastStore();
|
||||
|
||||
// 출퇴근 컴포넌트 이벤트 핸들러
|
||||
const handleWorkTimeUpdate = () => {
|
||||
todaysCommuter();
|
||||
loadCommuters();
|
||||
};
|
||||
// 롱프레스 관련 변수 추가
|
||||
const pressTimer = ref(null);
|
||||
const longPressDelay = 500; // 0.5초
|
||||
|
||||
const handleLeaveTimeUpdate = () => {
|
||||
todaysCommuter();
|
||||
};
|
||||
// // 출퇴근 컴포넌트 이벤트 핸들러
|
||||
// const handleWorkTimeUpdate = () => {
|
||||
// todaysCommuter();
|
||||
// //loadCommuters();
|
||||
// };
|
||||
|
||||
// 오늘 출근 모든 사용자 조회
|
||||
const todaysCommuter = async () => {
|
||||
const res = await $api.get(`commuters/todays`);
|
||||
if (res.status === 200) {
|
||||
commuters.value = res.data.data;
|
||||
}
|
||||
};
|
||||
// const handleLeaveTimeUpdate = () => {
|
||||
// todaysCommuter();
|
||||
// };
|
||||
|
||||
// // 오늘 출근 모든 사용자 조회
|
||||
// const todaysCommuter = async () => {
|
||||
// const res = await $api.get(`commuters/todays`);
|
||||
// if (res.status === 200) {
|
||||
// commuters.value = res.data.data;
|
||||
// }
|
||||
// };
|
||||
|
||||
/************* category ***************/
|
||||
|
||||
// 이벤트 카테고리 데이터 로딩
|
||||
const categoryList = ref([]);
|
||||
const fetchCategoryList = async () => {
|
||||
@ -159,8 +135,19 @@
|
||||
/************* init ***************/
|
||||
const monthBirthdayList = ref([]);
|
||||
const monthVacationList = ref([]);
|
||||
const monthBirthdayPartyList = ref([]);
|
||||
const monthDinnerList = ref([]);
|
||||
const monthTeaTimeList = ref([]);
|
||||
const monthWorkShopList = ref([]);
|
||||
|
||||
const birthdayList = ref([]);
|
||||
const vacationList = ref([]);
|
||||
const birthdayPartyList = ref([]);
|
||||
const dinnerList = ref([]);
|
||||
const teaTimeList = ref([]);
|
||||
const workShopList = ref([]);
|
||||
|
||||
const currentDateEvents = ref([]);
|
||||
|
||||
// 생일자, 휴가자, 이벤트 일정 조회
|
||||
const fetchEventList = async param => {
|
||||
@ -187,31 +174,35 @@
|
||||
});
|
||||
}
|
||||
|
||||
// 생일파티
|
||||
if (res?.birthdayPartyList?.length) {
|
||||
res.birthdayPartyList.forEach(party => {
|
||||
addEvent(party.EVENTDT, 'birthdayParty', '생일파티');
|
||||
});
|
||||
}
|
||||
// 초기화
|
||||
monthBirthdayPartyList.value = [];
|
||||
monthDinnerList.value = [];
|
||||
monthTeaTimeList.value = [];
|
||||
monthTeaTimeList.value = [];
|
||||
|
||||
// 회식
|
||||
if (res?.dinnerList?.length) {
|
||||
res.dinnerList.forEach(dinner => {
|
||||
addEvent(dinner.EVENTDT, 'dinner', '회식');
|
||||
});
|
||||
}
|
||||
if (res?.eventList?.length) {
|
||||
res.eventList.forEach(item => {
|
||||
switch (item.CMNCODVAL) {
|
||||
case 300203:
|
||||
monthBirthdayPartyList.value = [...monthBirthdayPartyList.value, item];
|
||||
addEvent($common.dateFormatter(item.LOCEVTTME, 'YMD'), 'birthdayParty', '생일파티');
|
||||
break;
|
||||
|
||||
// 티타임
|
||||
if (res?.teaTimeList?.length) {
|
||||
res.teaTimeList.forEach(tea => {
|
||||
addEvent(tea.EVENTDT, 'teaTime', '티타임');
|
||||
});
|
||||
}
|
||||
case 300204:
|
||||
monthDinnerList.value = [...monthDinnerList.value, item];
|
||||
addEvent($common.dateFormatter(item.LOCEVTTME, 'YMD'), 'dinner', '회식');
|
||||
break;
|
||||
|
||||
// 워크샵
|
||||
if (res?.workshopList?.length) {
|
||||
res.workshopList.forEach(workshop => {
|
||||
addEvent(workshop.EVENTDT, 'workshop', '워크샵');
|
||||
case 300205:
|
||||
monthTeaTimeList.value = [...monthTeaTimeList.value, item];
|
||||
addEvent($common.dateFormatter(item.LOCEVTTME, 'YMD'), 'teaTime', '티타임');
|
||||
break;
|
||||
|
||||
case 300206:
|
||||
monthWorkShopList.value = [...monthWorkShopList.value, item];
|
||||
addEvent($common.dateFormatter(item.LOCEVTTME, 'YMD'), 'workshop', '워크샵');
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -221,11 +212,12 @@
|
||||
// 생일의 경우 달력의 현재 년도로 변경하여 처리
|
||||
if (type === 'birthday') {
|
||||
const calendarApi = fullCalendarRef.value?.getApi();
|
||||
|
||||
if (calendarApi) {
|
||||
const dateArr = calendarApi.currentData.viewTitle.split(' ');
|
||||
const calendarYear = parseInt(dateArr[0].replace(/\D/g, ''), 10);
|
||||
const calendarDate = calendarApi.currentData.currentDate;
|
||||
const { year } = $common.formatDateTime(new Date(calendarDate));
|
||||
const birthDate = $common.dateFormatter(date, 'MD');
|
||||
date = `${calendarYear}-${birthDate}`;
|
||||
date = `${year}-${birthDate}`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -256,6 +248,26 @@
|
||||
if (monthVacationList.value) {
|
||||
vacationList.value = $common.filterTargetByDate(monthVacationList.value, 'LOCVACUDT', month, day);
|
||||
}
|
||||
|
||||
// 생일파티
|
||||
if (monthBirthdayPartyList.value) {
|
||||
birthdayPartyList.value = $common.filterTargetByDate(monthBirthdayPartyList.value, 'LOCEVTTME', month, day);
|
||||
}
|
||||
|
||||
// 회식
|
||||
if (monthDinnerList.value) {
|
||||
dinnerList.value = $common.filterTargetByDate(monthDinnerList.value, 'LOCEVTTME', month, day);
|
||||
}
|
||||
|
||||
// 티타임
|
||||
if (monthTeaTimeList.value) {
|
||||
teaTimeList.value = $common.filterTargetByDate(monthTeaTimeList.value, 'LOCEVTTME', month, day);
|
||||
}
|
||||
|
||||
// 워크샵
|
||||
if (monthWorkShopList.value) {
|
||||
workShopList.value = $common.filterTargetByDate(monthWorkShopList.value, 'LOCEVTTME', month, day);
|
||||
}
|
||||
};
|
||||
|
||||
// 캘린더 데이터 가져오기
|
||||
@ -264,26 +276,20 @@
|
||||
const calendarApi = fullCalendarRef.value?.getApi();
|
||||
if (!calendarApi) return;
|
||||
|
||||
// 현재 표시된 달력의 연도, 월 추출
|
||||
const date = calendarApi.currentData.viewTitle;
|
||||
const dateArr = date.split(' ');
|
||||
let currentYear = dateArr[0].trim();
|
||||
let currentMonth = dateArr[1].trim();
|
||||
const regex = /\D/g;
|
||||
// 숫자가 아닌 문자 제거 후 정수로 변환
|
||||
currentYear = parseInt(currentYear.replace(regex, ''), 10);
|
||||
currentMonth = parseInt(currentMonth.replace(regex, ''), 10);
|
||||
const date = calendarApi.currentData.currentDate;
|
||||
const { year, month } = $common.formatDateTime(new Date(date));
|
||||
|
||||
try {
|
||||
// 현재 표시 중인 월의 공휴일 정보 가져오기
|
||||
const holidayEvents = await fetchHolidays(currentYear, String(currentMonth).padStart(2, '0'));
|
||||
const holidayEvents = await fetchHolidays(year, month);
|
||||
calendarEvents.value = [...holidayEvents]; // 공휴일 정보로 초기화
|
||||
|
||||
// 이벤트 데이터 가져오기
|
||||
const param = new URLSearchParams();
|
||||
param.append('year', currentYear);
|
||||
param.append('month', currentMonth);
|
||||
param.append('year', year);
|
||||
param.append('month', month);
|
||||
param.append('day', '1'); // 해당 월의 첫날
|
||||
|
||||
await fetchEventList(param);
|
||||
} catch (error) {
|
||||
console.error('공휴일 정보 로딩 실패:', error);
|
||||
@ -318,81 +324,6 @@
|
||||
return !isWeekend && !isHoliday;
|
||||
};
|
||||
|
||||
// 이벤트 타입 정의
|
||||
const eventTypes = [
|
||||
{ type: 'birthdayParty', code: '300203', title: '생일파티' },
|
||||
{ type: 'dinner', code: '300204', title: '회식' },
|
||||
{ type: 'teaTime', code: '300205', title: '티타임' },
|
||||
{ type: 'workshop', code: '300206', title: '워크샵' },
|
||||
];
|
||||
|
||||
// 날짜 클릭 이벤트 함수 수정
|
||||
const handleDateClick = info => {
|
||||
// const clickedDate = dayjs(info.dateStr);
|
||||
const { month, day } = $common.formatDateTime(new Date(info.dateStr));
|
||||
|
||||
// 클릭한 날짜의 이벤트 리스트 필터링
|
||||
useFilterEventList(month, day);
|
||||
|
||||
// 팝오버 HTML 생성
|
||||
const popoverContent = `
|
||||
<div class="d-flex flex-wrap gap-2 p-2">
|
||||
${eventTypes
|
||||
.map(
|
||||
event => `
|
||||
<img
|
||||
src="${baseUrl}img/main-category-img/main-${event.code}.png"
|
||||
class="event-icon-select"
|
||||
data-event-type="${event.type}"
|
||||
data-event-title="${event.title}"
|
||||
data-date="${info.dateStr}"
|
||||
style="width: 25px; height: 25px; cursor: pointer;"
|
||||
/>
|
||||
`,
|
||||
)
|
||||
.join('')}
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 기존 팝오버 제거
|
||||
const existingPopovers = document.querySelectorAll('.event-popover');
|
||||
existingPopovers.forEach(popover => popover.remove());
|
||||
|
||||
// 새 팝오버 생성
|
||||
const popover = document.createElement('div');
|
||||
popover.className = 'event-popover position-absolute bg-white shadow rounded z-3';
|
||||
popover.innerHTML = popoverContent;
|
||||
popover.style.top = `${info.jsEvent.pageY}px`;
|
||||
popover.style.left = `${info.jsEvent.pageX}px`;
|
||||
|
||||
// 아이콘 클릭 이벤트 추가
|
||||
popover.addEventListener('click', e => {
|
||||
const target = e.target;
|
||||
if (target.classList.contains('event-icon-select')) {
|
||||
const eventType = target.dataset.eventType;
|
||||
const eventTitle = target.dataset.eventTitle;
|
||||
const date = target.dataset.date;
|
||||
|
||||
// 이벤트 추가
|
||||
addEvent(date, eventType, eventTitle);
|
||||
|
||||
// 팝오버 닫기
|
||||
popover.remove();
|
||||
}
|
||||
});
|
||||
|
||||
// 문서에 팝오버 추가
|
||||
document.body.appendChild(popover);
|
||||
|
||||
// 팝오버 외부 클릭 시 닫기
|
||||
document.addEventListener('click', e => {
|
||||
if (!e.target.closest('.event-popover') && !e.target.closest('.fc-daygrid-day')) {
|
||||
const popovers = document.querySelectorAll('.event-popover');
|
||||
popovers.forEach(p => p.remove());
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 날짜 셀 클래스 추가 함수
|
||||
const getCellClassNames = arg => {
|
||||
const cellDate = dayjs(arg.date);
|
||||
@ -406,58 +337,106 @@
|
||||
return classes;
|
||||
};
|
||||
|
||||
// 현재 달의 모든 출근 정보 조회
|
||||
const loadCommuters = async () => {
|
||||
const calendarApi = fullCalendarRef.value?.getApi();
|
||||
if (!calendarApi) return;
|
||||
const date = calendarApi.currentData.viewTitle;
|
||||
const dateArr = date.split(' ');
|
||||
let currentYear = dateArr[0].trim();
|
||||
let currentMonth = dateArr[1].trim();
|
||||
const regex = /\D/g;
|
||||
currentYear = parseInt(currentYear.replace(regex, ''), 10);
|
||||
currentMonth = parseInt(currentMonth.replace(regex, ''), 10);
|
||||
// 날짜 클릭 이벤트 핸들러
|
||||
const handleDateClick = info => {
|
||||
const { month, day } = $common.formatDateTime(new Date(info.dateStr));
|
||||
useFilterEventList(month, day);
|
||||
};
|
||||
|
||||
const res = await $api.get('commuters/month', {
|
||||
params: {
|
||||
year: currentYear,
|
||||
month: currentMonth,
|
||||
},
|
||||
});
|
||||
if (res.status === 200) {
|
||||
// 월별 출근 정보 저장
|
||||
monthlyCommuters.value = res.data.data;
|
||||
// 이벤트 모달 핸들러
|
||||
const handleMouseDown = (date, jsEvent) => {
|
||||
if (showModal.value) showModal.value = false;
|
||||
|
||||
document.querySelectorAll('.fc-daygrid-day-events img.rounded-circle').forEach(img => {
|
||||
img.remove();
|
||||
});
|
||||
// 해당 날짜의 이벤트 필터링
|
||||
const dateEvents = calendarEvents.value.filter(
|
||||
event => $common.dateFormatter(event.start, 'YMD') === $common.dateFormatter(date, 'YMD'),
|
||||
);
|
||||
|
||||
monthlyCommuters.value.forEach(commuter => {
|
||||
const date = commuter.COMMUTDAY;
|
||||
const dateCell =
|
||||
document.querySelector(`.fc-day[data-date="${date}"]`) ||
|
||||
document.querySelector(`.fc-daygrid-day[data-date="${date}"]`);
|
||||
if (dateCell) {
|
||||
const dayEvents = dateCell.querySelector('.fc-daygrid-day-events');
|
||||
if (dayEvents) {
|
||||
dayEvents.classList.add('text-center');
|
||||
// 프로필 이미지 생성
|
||||
const profileImg = document.createElement('img');
|
||||
profileImg.src = `${baseUrl}upload/img/profile/${commuter.profile}`;
|
||||
profileImg.className = 'rounded-circle w-px-20 h-px-20 mx-1 mb-1 position-relative z-5 m-auto';
|
||||
profileImg.style.border = `2px solid ${commuter.projctcolor}`;
|
||||
profileImg.onerror = () => {
|
||||
profileImg.src = '/img/icons/icon.png';
|
||||
};
|
||||
pressTimer.value = setTimeout(() => {
|
||||
modalPosition.value = {
|
||||
x: jsEvent.clientX,
|
||||
y: jsEvent.clientY,
|
||||
};
|
||||
|
||||
dayEvents.appendChild(profileImg);
|
||||
}
|
||||
}
|
||||
});
|
||||
selectedDate.value = date;
|
||||
currentDateEvents.value = dateEvents;
|
||||
showModal.value = true;
|
||||
pressTimer.value = null;
|
||||
}, longPressDelay);
|
||||
};
|
||||
|
||||
// 이벤트 모달 외부 클릭 시 닫힘
|
||||
const handleMouseUp = () => {
|
||||
if (pressTimer.value) {
|
||||
clearTimeout(pressTimer.value);
|
||||
pressTimer.value = null;
|
||||
}
|
||||
};
|
||||
|
||||
// 이벤트 아이콘 표시 함수
|
||||
// 이벤트 삭제 api
|
||||
const toggleEvent = async (date, code, title) => {
|
||||
const { data } = await $api.post('main/toggleEvent', {
|
||||
date: date,
|
||||
code: code,
|
||||
title: title,
|
||||
});
|
||||
|
||||
if (data?.code === 200) toastStore.onToast(data.message);
|
||||
|
||||
const { year, month, day } = $common.formatDateTime(new Date(date));
|
||||
const param = new URLSearchParams();
|
||||
param.append('year', year);
|
||||
param.append('month', month);
|
||||
param.append('day', day);
|
||||
|
||||
await fetchEventList(param);
|
||||
useFilterEventList(month, year);
|
||||
};
|
||||
|
||||
// 이벤트 추가 api
|
||||
const insertEvent = async (date, code, title, place, time) => {
|
||||
const dateTime = $common.dateFormatter(`${date} ${time}`);
|
||||
const { data } = await $api.post('main/inserEvent', {
|
||||
date: dateTime,
|
||||
code: code,
|
||||
title: title,
|
||||
place: place,
|
||||
});
|
||||
|
||||
if (data?.code === 200) toastStore.onToast(data.message);
|
||||
|
||||
const { year, month, day } = $common.formatDateTime(new Date(date));
|
||||
const param = new URLSearchParams();
|
||||
param.append('year', year);
|
||||
param.append('month', month);
|
||||
param.append('day', day);
|
||||
|
||||
await fetchEventList(param);
|
||||
useFilterEventList(month, year);
|
||||
};
|
||||
|
||||
// 이벤트 선택 핸들러
|
||||
const handleEventSelect = data => {
|
||||
toggleEvent(data.date, data.code, data.title);
|
||||
showModal.value = false;
|
||||
};
|
||||
|
||||
const handleEventInsert = data => {
|
||||
insertEvent(data.date, data.code, data.title, data.place, data.time);
|
||||
showModal.value = false;
|
||||
};
|
||||
|
||||
const handleEventDelete = data => {
|
||||
toggleEvent(data.date, data.code, data.title);
|
||||
showModal.value = false;
|
||||
};
|
||||
|
||||
// 이벤트 모달 닫기
|
||||
const handleCloseModal = () => {
|
||||
showModal.value = false;
|
||||
};
|
||||
|
||||
// 달력 이벤트 아이콘 표시 함수
|
||||
const handleEventContent = item => {
|
||||
if (!item.event) return null;
|
||||
|
||||
@ -518,6 +497,17 @@
|
||||
selectable: true,
|
||||
selectAllow: selectInfo => isSelectableDate(selectInfo.start),
|
||||
dateClick: handleDateClick,
|
||||
dayCellDidMount: arg => {
|
||||
const dateCell = arg.el;
|
||||
|
||||
// 마우스 홀드시 이벤트 모달
|
||||
dateCell.addEventListener('mousedown', e => {
|
||||
const date = $common.dateFormatter(arg.date, 'YMD');
|
||||
handleMouseDown(date, e);
|
||||
});
|
||||
dateCell.addEventListener('mouseup', handleMouseUp);
|
||||
dateCell.addEventListener('mouseleave', handleMouseUp);
|
||||
},
|
||||
dayCellClassNames: getCellClassNames,
|
||||
unselectAuto: true,
|
||||
droppable: false,
|
||||
@ -546,39 +536,9 @@
|
||||
},
|
||||
);
|
||||
|
||||
// selectedProject 변경 감지
|
||||
watch(
|
||||
() => projectStore.selectedProject,
|
||||
newProject => {
|
||||
if (newProject) {
|
||||
selectedProject.value = newProject.PROJCTSEQ;
|
||||
checkedInProject.value = newProject;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// 모달 열기
|
||||
const openModal = () => {
|
||||
isModalOpen.value = true;
|
||||
};
|
||||
|
||||
// 모달 닫기
|
||||
const closeModal = () => {
|
||||
isModalOpen.value = false;
|
||||
};
|
||||
|
||||
const selectedDateCommuters = computed(() => {
|
||||
return monthlyCommuters.value.filter(commuter => commuter.COMMUTDAY === eventDate.value);
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
await fetchData();
|
||||
await userStore.userInfo();
|
||||
user.value = userStore.user;
|
||||
await projectStore.getProjectList('', '', 'true');
|
||||
project.value = projectStore.projectList;
|
||||
|
||||
await todaysCommuter();
|
||||
|
||||
// 저장된 선택 프로젝트 가져오기
|
||||
const storedProject = projectStore.getSelectedProject();
|
||||
@ -598,6 +558,13 @@
|
||||
await fetchCategoryList();
|
||||
await fetchEventList(param);
|
||||
useFilterEventList(month, day);
|
||||
|
||||
// 이벤트모달 외부 클릭 감지
|
||||
// document.addEventListener('click', e => {
|
||||
// if (showModal.value && !e.target.closest('.event-modal') && !e.target.closest('.fc-daygrid-day')) {
|
||||
// showModal.value = false;
|
||||
// }
|
||||
// });
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
@ -605,7 +572,7 @@
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.event-popover {
|
||||
.event-modal {
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
@ -614,4 +581,9 @@
|
||||
transform: scale(1.1);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
/* 이벤트 모달 노출 시 텍스트 선택 방지 */
|
||||
.fc-daygrid-day {
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -2,10 +2,17 @@
|
||||
<div class="">
|
||||
<template v-for="category in categoryList" :key="category.CMNCODVAL">
|
||||
<div
|
||||
v-if="(category.CMNCODVAL === 300201 && birthdayList?.length) || (category.CMNCODVAL === 300202 && vacationList?.length)"
|
||||
v-if="
|
||||
(category.CMNCODVAL === 300201 && birthdayList?.length) ||
|
||||
(category.CMNCODVAL === 300202 && vacationList?.length) ||
|
||||
(category.CMNCODVAL === 300203 && birthdayPartyList?.length) ||
|
||||
(category.CMNCODVAL === 300204 && dinnerList?.length) ||
|
||||
(category.CMNCODVAL === 300205 && teaTimeList?.length) ||
|
||||
(category.CMNCODVAL === 300206 && workShopList?.length)
|
||||
"
|
||||
class="border border-2 mt-3 card p-2"
|
||||
>
|
||||
<div class="row g-2">
|
||||
<div class="row g-2 position-relative">
|
||||
<div class="col-3 mx-0 px-0">
|
||||
<div class="ratio ratio-1x1">
|
||||
<img
|
||||
@ -22,6 +29,38 @@
|
||||
<template v-if="category.CMNCODVAL === 300202">
|
||||
<MainMemberProfile :members="vacationList" :baseUrl="baseUrl" />
|
||||
</template>
|
||||
<template v-if="category.CMNCODVAL === 300203">
|
||||
<div>
|
||||
{{ birthdayPartyList[0].LOCEVTPLC }}
|
||||
</div>
|
||||
<div>
|
||||
{{ $common.dateFormatter(birthdayPartyList[0].LOCEVTTME) }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="category.CMNCODVAL === 300204">
|
||||
<div>
|
||||
{{ dinnerList[0].LOCEVTPLC }}
|
||||
</div>
|
||||
<div>
|
||||
{{ $common.dateFormatter(dinnerList[0].LOCEVTTME) }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="category.CMNCODVAL === 300205">
|
||||
<div>
|
||||
{{ teaTimeList[0].LOCEVTPLC }}
|
||||
</div>
|
||||
<div>
|
||||
{{ $common.dateFormatter(teaTimeList[0].LOCEVTTME) }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="category.CMNCODVAL === 300206">
|
||||
<div>
|
||||
{{ workShopList[0].LOCEVTPLC }}
|
||||
</div>
|
||||
<div>
|
||||
{{ $common.dateFormatter(workShopList[0].LOCEVTTME) }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -67,30 +106,17 @@
|
||||
vacationList: {
|
||||
type: Array,
|
||||
},
|
||||
birthdayPartyList: {
|
||||
type: Array,
|
||||
},
|
||||
dinnerList: {
|
||||
type: Array,
|
||||
},
|
||||
teaTimeList: {
|
||||
type: Array,
|
||||
},
|
||||
workShopList: {
|
||||
type: Array,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['drop', 'update:selectedProject', 'update:checkedInProject']);
|
||||
|
||||
// 현재 사용자 확인
|
||||
const isCurrentUser = commuter => {
|
||||
return props.user && commuter && commuter.MEMBERSEQ === props.user.id;
|
||||
};
|
||||
|
||||
// 드래그 시작 이벤트 핸들러
|
||||
const dragStart = (event, project) => {
|
||||
// 드래그 데이터 설정
|
||||
event.dataTransfer.setData('application/json', JSON.stringify(project));
|
||||
event.dataTransfer.effectAllowed = 'copy';
|
||||
};
|
||||
|
||||
// 드래그 오버 드롭 허용
|
||||
const allowDrop = event => {
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
// 드롭
|
||||
const handleDrop = (event, targetProject) => {
|
||||
event.preventDefault();
|
||||
emit('drop', { event, targetProject });
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -5,8 +5,9 @@
|
||||
<div class="ratio ratio-1x1 mb-0 profile-list">
|
||||
<img
|
||||
:src="`${baseUrl}upload/img/profile/${member.MEMBERPRF}`"
|
||||
:style="`border-color: ${member.usercolor} !important;`"
|
||||
alt="User Profile"
|
||||
class="rounded-circle"
|
||||
class="rounded-circle border border-2"
|
||||
@error="$event.target.src = '/img/icons/icon.png'"
|
||||
/>
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user