메인 이벤트 달력

This commit is contained in:
nevermoregb 2025-03-31 11:22:26 +09:00
parent a540feb851
commit c1274cf9a0
5 changed files with 486 additions and 286 deletions

View File

@ -76,7 +76,7 @@ const common = {
formatDateTime(dateObj) { formatDateTime(dateObj) {
const date = new Date(dateObj); const date = new Date(dateObj);
const dateCheck = date.getTime(); const dateCheck = date.getTime();
if (isNaN(dateCheck)) return dateStr; if (isNaN(dateCheck)) return dateObj;
const zeroFormat = num => (num < 10 ? `0${num}` : num); const zeroFormat = num => (num < 10 ? `0${num}` : num);
return { return {

View 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>

View File

@ -15,30 +15,18 @@
{{ user.name }} {{ user.name }}
</p> </p>
<CommuterBtn <CommuterBtn :userId="user.id" :checkedInProject="checkedInProject || {}" ref="workTimeComponentRef" />
:userId="user.id"
:checkedInProject="checkedInProject || {}"
@workTimeUpdated="handleWorkTimeUpdate"
@leaveTimeUpdated="handleLeaveTimeUpdate"
ref="workTimeComponentRef"
/>
<MainEventList <MainEventList
:categoryList="categoryList" :categoryList="categoryList"
:baseUrl="baseUrl" :baseUrl="baseUrl"
:birthdayList="birthdayList" :birthdayList="birthdayList"
:vacationList="vacationList" :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>
</div> </div>
@ -60,41 +48,24 @@
</div> </div>
</div> </div>
<CenterModal :display="isModalOpen" @close="closeModal"> <EventModal
<template #title> v-if="showModal"
{{ eventDate }} :position="modalPosition"
</template> :selected-date="selectedDate"
<template #body> :base-url="baseUrl"
<div v-if="selectedDateCommuters.length > 0"> :date-events="currentDateEvents"
<div v-for="(commuter, index) in selectedDateCommuters" :key="index"> @select="handleEventSelect"
<div class="d-flex align-items-center my-2"> @delete="handleEventDelete"
<img @insert="handleEventInsert"
:src="`${baseUrl}upload/img/profile/${commuter.profile}`" @close="handleCloseModal"
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>
</template> </template>
<script setup> <script setup>
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';
import CenterModal from '@c/modal/CenterModal.vue'; import { inject, onMounted, reactive, ref, watch } from 'vue';
import { computed, inject, onMounted, reactive, ref, watch } from 'vue';
import $api from '@api'; import $api from '@api';
import 'flatpickr/dist/flatpickr.min.css'; import 'flatpickr/dist/flatpickr.min.css';
import '@/assets/css/app-calendar.css'; import '@/assets/css/app-calendar.css';
@ -102,13 +73,12 @@
import { useUserInfoStore } from '@/stores/useUserInfoStore'; import { useUserInfoStore } from '@/stores/useUserInfoStore';
import { useProjectStore } from '@/stores/useProjectStore'; import { useProjectStore } from '@/stores/useProjectStore';
import CommuterBtn from '@c/commuters/CommuterBtn.vue'; import CommuterBtn from '@c/commuters/CommuterBtn.vue';
import CommuterProjectList from '@c/commuters/CommuterProjectList.vue';
import MainEventList from '@c/main/MainEventList.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 baseUrl = import.meta.env.VITE_DOMAIN;
const user = ref({}); const user = ref({});
const project = ref({});
const userStore = useUserInfoStore(); const userStore = useUserInfoStore();
const projectStore = useProjectStore(); const projectStore = useProjectStore();
@ -116,39 +86,45 @@
const fullCalendarRef = ref(null); const fullCalendarRef = ref(null);
const workTimeComponentRef = ref(null); const workTimeComponentRef = ref(null);
const calendarEvents = ref([]); const calendarEvents = ref([]);
const eventDate = ref('');
const selectedProject = ref(null); const selectedProject = ref(null);
const checkedInProject = ref(null); const checkedInProject = ref(null);
//
const isModalOpen = ref(false);
const commuters = ref([]); 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 $common = inject('common');
const toastStore = useToastStore();
// //
const handleWorkTimeUpdate = () => { const pressTimer = ref(null);
todaysCommuter(); const longPressDelay = 500; // 0.5
loadCommuters();
};
const handleLeaveTimeUpdate = () => { // //
todaysCommuter(); // const handleWorkTimeUpdate = () => {
}; // todaysCommuter();
// //loadCommuters();
// };
// // const handleLeaveTimeUpdate = () => {
const todaysCommuter = async () => { // todaysCommuter();
const res = await $api.get(`commuters/todays`); // };
if (res.status === 200) {
commuters.value = res.data.data; // //
} // const todaysCommuter = async () => {
}; // const res = await $api.get(`commuters/todays`);
// if (res.status === 200) {
// commuters.value = res.data.data;
// }
// };
/************* category ***************/ /************* category ***************/
// //
const categoryList = ref([]); const categoryList = ref([]);
const fetchCategoryList = async () => { const fetchCategoryList = async () => {
@ -159,8 +135,19 @@
/************* init ***************/ /************* init ***************/
const monthBirthdayList = ref([]); const monthBirthdayList = ref([]);
const monthVacationList = ref([]); const monthVacationList = ref([]);
const monthBirthdayPartyList = ref([]);
const monthDinnerList = ref([]);
const monthTeaTimeList = ref([]);
const monthWorkShopList = ref([]);
const birthdayList = ref([]); const birthdayList = ref([]);
const vacationList = ref([]); const vacationList = ref([]);
const birthdayPartyList = ref([]);
const dinnerList = ref([]);
const teaTimeList = ref([]);
const workShopList = ref([]);
const currentDateEvents = ref([]);
// , , // , ,
const fetchEventList = async param => { const fetchEventList = async param => {
@ -187,31 +174,35 @@
}); });
} }
// //
if (res?.birthdayPartyList?.length) { monthBirthdayPartyList.value = [];
res.birthdayPartyList.forEach(party => { monthDinnerList.value = [];
addEvent(party.EVENTDT, 'birthdayParty', '생일파티'); monthTeaTimeList.value = [];
}); monthTeaTimeList.value = [];
}
// if (res?.eventList?.length) {
if (res?.dinnerList?.length) { res.eventList.forEach(item => {
res.dinnerList.forEach(dinner => { switch (item.CMNCODVAL) {
addEvent(dinner.EVENTDT, 'dinner', '회식'); case 300203:
}); monthBirthdayPartyList.value = [...monthBirthdayPartyList.value, item];
} addEvent($common.dateFormatter(item.LOCEVTTME, 'YMD'), 'birthdayParty', '생일파티');
break;
// case 300204:
if (res?.teaTimeList?.length) { monthDinnerList.value = [...monthDinnerList.value, item];
res.teaTimeList.forEach(tea => { addEvent($common.dateFormatter(item.LOCEVTTME, 'YMD'), 'dinner', '회식');
addEvent(tea.EVENTDT, 'teaTime', '티타임'); break;
});
}
// case 300205:
if (res?.workshopList?.length) { monthTeaTimeList.value = [...monthTeaTimeList.value, item];
res.workshopList.forEach(workshop => { addEvent($common.dateFormatter(item.LOCEVTTME, 'YMD'), 'teaTime', '티타임');
addEvent(workshop.EVENTDT, 'workshop', '워크샵'); break;
case 300206:
monthWorkShopList.value = [...monthWorkShopList.value, item];
addEvent($common.dateFormatter(item.LOCEVTTME, 'YMD'), 'workshop', '워크샵');
break;
}
}); });
} }
}; };
@ -221,11 +212,12 @@
// //
if (type === 'birthday') { if (type === 'birthday') {
const calendarApi = fullCalendarRef.value?.getApi(); const calendarApi = fullCalendarRef.value?.getApi();
if (calendarApi) { if (calendarApi) {
const dateArr = calendarApi.currentData.viewTitle.split(' '); const calendarDate = calendarApi.currentData.currentDate;
const calendarYear = parseInt(dateArr[0].replace(/\D/g, ''), 10); const { year } = $common.formatDateTime(new Date(calendarDate));
const birthDate = $common.dateFormatter(date, 'MD'); const birthDate = $common.dateFormatter(date, 'MD');
date = `${calendarYear}-${birthDate}`; date = `${year}-${birthDate}`;
} }
} }
@ -256,6 +248,26 @@
if (monthVacationList.value) { if (monthVacationList.value) {
vacationList.value = $common.filterTargetByDate(monthVacationList.value, 'LOCVACUDT', month, day); 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(); const calendarApi = fullCalendarRef.value?.getApi();
if (!calendarApi) return; if (!calendarApi) return;
// , const date = calendarApi.currentData.currentDate;
const date = calendarApi.currentData.viewTitle; const { year, month } = $common.formatDateTime(new Date(date));
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);
try { try {
// //
const holidayEvents = await fetchHolidays(currentYear, String(currentMonth).padStart(2, '0')); const holidayEvents = await fetchHolidays(year, month);
calendarEvents.value = [...holidayEvents]; // calendarEvents.value = [...holidayEvents]; //
// //
const param = new URLSearchParams(); const param = new URLSearchParams();
param.append('year', currentYear); param.append('year', year);
param.append('month', currentMonth); param.append('month', month);
param.append('day', '1'); // param.append('day', '1'); //
await fetchEventList(param); await fetchEventList(param);
} catch (error) { } catch (error) {
console.error('공휴일 정보 로딩 실패:', error); console.error('공휴일 정보 로딩 실패:', error);
@ -318,81 +324,6 @@
return !isWeekend && !isHoliday; 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 getCellClassNames = arg => {
const cellDate = dayjs(arg.date); const cellDate = dayjs(arg.date);
@ -406,58 +337,106 @@
return classes; return classes;
}; };
// //
const loadCommuters = async () => { const handleDateClick = info => {
const calendarApi = fullCalendarRef.value?.getApi(); const { month, day } = $common.formatDateTime(new Date(info.dateStr));
if (!calendarApi) return; useFilterEventList(month, day);
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 res = await $api.get('commuters/month', { //
params: { const handleMouseDown = (date, jsEvent) => {
year: currentYear, if (showModal.value) showModal.value = false;
month: currentMonth,
},
});
if (res.status === 200) {
//
monthlyCommuters.value = res.data.data;
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 => { pressTimer.value = setTimeout(() => {
const date = commuter.COMMUTDAY; modalPosition.value = {
const dateCell = x: jsEvent.clientX,
document.querySelector(`.fc-day[data-date="${date}"]`) || y: jsEvent.clientY,
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';
};
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 => { const handleEventContent = item => {
if (!item.event) return null; if (!item.event) return null;
@ -518,6 +497,17 @@
selectable: true, selectable: true,
selectAllow: selectInfo => isSelectableDate(selectInfo.start), selectAllow: selectInfo => isSelectableDate(selectInfo.start),
dateClick: handleDateClick, 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, dayCellClassNames: getCellClassNames,
unselectAuto: true, unselectAuto: true,
droppable: false, 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 () => { onMounted(async () => {
await fetchData();
await userStore.userInfo(); await userStore.userInfo();
user.value = userStore.user; user.value = userStore.user;
await projectStore.getProjectList('', '', 'true');
project.value = projectStore.projectList;
await todaysCommuter();
// //
const storedProject = projectStore.getSelectedProject(); const storedProject = projectStore.getSelectedProject();
@ -598,6 +558,13 @@
await fetchCategoryList(); await fetchCategoryList();
await fetchEventList(param); await fetchEventList(param);
useFilterEventList(month, day); 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> </script>
<style> <style>
@ -605,7 +572,7 @@
background-color: transparent; background-color: transparent;
} }
.event-popover { .event-modal {
padding: 8px; padding: 8px;
border: 1px solid #ddd; border: 1px solid #ddd;
} }
@ -614,4 +581,9 @@
transform: scale(1.1); transform: scale(1.1);
transition: transform 0.2s; transition: transform 0.2s;
} }
/* 이벤트 모달 노출 시 텍스트 선택 방지 */
.fc-daygrid-day {
user-select: none;
}
</style> </style>

View File

@ -2,10 +2,17 @@
<div class=""> <div class="">
<template v-for="category in categoryList" :key="category.CMNCODVAL"> <template v-for="category in categoryList" :key="category.CMNCODVAL">
<div <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" 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="col-3 mx-0 px-0">
<div class="ratio ratio-1x1"> <div class="ratio ratio-1x1">
<img <img
@ -22,6 +29,38 @@
<template v-if="category.CMNCODVAL === 300202"> <template v-if="category.CMNCODVAL === 300202">
<MainMemberProfile :members="vacationList" :baseUrl="baseUrl" /> <MainMemberProfile :members="vacationList" :baseUrl="baseUrl" />
</template> </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> </div>
</div> </div>
@ -67,30 +106,17 @@
vacationList: { vacationList: {
type: Array, 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> </script>

View File

@ -5,8 +5,9 @@
<div class="ratio ratio-1x1 mb-0 profile-list"> <div class="ratio ratio-1x1 mb-0 profile-list">
<img <img
:src="`${baseUrl}upload/img/profile/${member.MEMBERPRF}`" :src="`${baseUrl}upload/img/profile/${member.MEMBERPRF}`"
:style="`border-color: ${member.usercolor} !important;`"
alt="User Profile" alt="User Profile"
class="rounded-circle" class="rounded-circle border border-2"
@error="$event.target.src = '/img/icons/icon.png'" @error="$event.target.src = '/img/icons/icon.png'"
/> />
</div> </div>