250328_work
This commit is contained in:
parent
c4ff151581
commit
a540feb851
@ -47,11 +47,11 @@ const common = {
|
||||
*
|
||||
* @param {string} dateStr
|
||||
* @return
|
||||
* 1. Date type 인 경우 예시 '25-02-24 12:02'
|
||||
* 1. Date type 인 경우 예시 '2025-02-24 12:02'
|
||||
* 2. Date type 이 아닌 경우 입력값 리턴
|
||||
*
|
||||
*/
|
||||
dateFormatter(dateStr) {
|
||||
dateFormatter(dateStr, type = null) {
|
||||
const date = new Date(dateStr);
|
||||
const dateCheck = date.getTime();
|
||||
|
||||
@ -59,13 +59,26 @@ const common = {
|
||||
return dateStr;
|
||||
} else {
|
||||
const { year, month, day, hours, minutes } = this.formatDateTime(date);
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
||||
let callback = '';
|
||||
|
||||
if (type == 'YMD') {
|
||||
callback = `${year}-${month}-${day}`;
|
||||
} else if (type == 'MD') {
|
||||
callback = `${month}-${day}`;
|
||||
} else {
|
||||
callback = `${year}-${month}-${day} ${hours}:${minutes}`;
|
||||
}
|
||||
|
||||
return callback;
|
||||
}
|
||||
},
|
||||
|
||||
formatDateTime(date) {
|
||||
const zeroFormat = num => (num < 10 ? `0${num}` : num);
|
||||
formatDateTime(dateObj) {
|
||||
const date = new Date(dateObj);
|
||||
const dateCheck = date.getTime();
|
||||
if (isNaN(dateCheck)) return dateStr;
|
||||
|
||||
const zeroFormat = num => (num < 10 ? `0${num}` : num);
|
||||
return {
|
||||
year: date.getFullYear(),
|
||||
month: zeroFormat(date.getMonth() + 1),
|
||||
|
||||
@ -140,34 +140,6 @@
|
||||
todaysCommuter();
|
||||
};
|
||||
|
||||
// 프로젝트 드롭 이벤트 핸들러 (ProjectList 컴포넌트에서 전달받음)
|
||||
const handleProjectDrop = ({ event, targetProject }) => {
|
||||
// 드래그된 프로젝트 데이터 가져오기
|
||||
const draggedProjectData = JSON.parse(event.dataTransfer.getData('application/json'));
|
||||
|
||||
// 드래그한 프로젝트와 드롭한 프로젝트가 같으면 아무 동작 안 함
|
||||
if (draggedProjectData.PROJCTSEQ === targetProject.PROJCTSEQ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 선택된 프로젝트 변경
|
||||
checkedInProject.value = targetProject;
|
||||
projectStore.setSelectedProject(targetProject);
|
||||
|
||||
// select 값도 변경
|
||||
selectedProject.value = targetProject.PROJCTSEQ;
|
||||
|
||||
$api.patch('commuters/update', {
|
||||
projctSeq: targetProject.PROJCTSEQ,
|
||||
memberSeq: user.value.id,
|
||||
}).then(res => {
|
||||
if (res.status === 200) {
|
||||
todaysCommuter();
|
||||
loadCommuters();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 오늘 출근 모든 사용자 조회
|
||||
const todaysCommuter = async () => {
|
||||
const res = await $api.get(`commuters/todays`);
|
||||
@ -195,14 +167,85 @@
|
||||
const { data } = await $api.get(`main/eventList?${param}`);
|
||||
const res = data?.data;
|
||||
|
||||
// 기존의 공휴일 이벤트는 유지
|
||||
const holidayEvents = calendarEvents.value.filter(event => event.classNames?.includes('holiday-event'));
|
||||
calendarEvents.value = [...holidayEvents];
|
||||
|
||||
// 생일자
|
||||
if (res?.memberBirthdayList?.length) monthBirthdayList.value = [...res.memberBirthdayList];
|
||||
if (res?.memberBirthdayList?.length) {
|
||||
monthBirthdayList.value = [...res.memberBirthdayList];
|
||||
res.memberBirthdayList.forEach(member => {
|
||||
addEvent($common.dateFormatter(member.MEMBERBTH, 'YMD'), 'birthday', `${member.MEMBERNAM}`);
|
||||
});
|
||||
}
|
||||
|
||||
// 휴가자
|
||||
if (res?.memberVacationList?.length) monthVacationList.value = [...res.memberVacationList];
|
||||
if (res?.memberVacationList?.length) {
|
||||
monthVacationList.value = [...res.memberVacationList];
|
||||
res.memberVacationList.forEach(member => {
|
||||
addEvent($common.dateFormatter(member.LOCVACUDT, 'YMD'), 'vacation', `${member.MEMBERNAM}`);
|
||||
});
|
||||
}
|
||||
|
||||
// 생일파티
|
||||
if (res?.birthdayPartyList?.length) {
|
||||
res.birthdayPartyList.forEach(party => {
|
||||
addEvent(party.EVENTDT, 'birthdayParty', '생일파티');
|
||||
});
|
||||
}
|
||||
|
||||
// 회식
|
||||
if (res?.dinnerList?.length) {
|
||||
res.dinnerList.forEach(dinner => {
|
||||
addEvent(dinner.EVENTDT, 'dinner', '회식');
|
||||
});
|
||||
}
|
||||
|
||||
// 티타임
|
||||
if (res?.teaTimeList?.length) {
|
||||
res.teaTimeList.forEach(tea => {
|
||||
addEvent(tea.EVENTDT, 'teaTime', '티타임');
|
||||
});
|
||||
}
|
||||
|
||||
// 워크샵
|
||||
if (res?.workshopList?.length) {
|
||||
res.workshopList.forEach(workshop => {
|
||||
addEvent(workshop.EVENTDT, 'workshop', '워크샵');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 해당일 기준 이벤트 리스트
|
||||
// 달력에 이벤트 데이터 추가
|
||||
const addEvent = (date, type, title) => {
|
||||
// 생일의 경우 달력의 현재 년도로 변경하여 처리
|
||||
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 birthDate = $common.dateFormatter(date, 'MD');
|
||||
date = `${calendarYear}-${birthDate}`;
|
||||
}
|
||||
}
|
||||
|
||||
// 같은 날짜와 타입의 이벤트가 이미 있는지 확인
|
||||
const existingEvent = calendarEvents.value.find(
|
||||
event => $common.dateFormatter(event.start, 'MD') === $common.dateFormatter(date, 'MD') && event.type == type,
|
||||
);
|
||||
|
||||
// 없는 경우에만 추가
|
||||
if (!existingEvent) {
|
||||
calendarEvents.value.push({
|
||||
start: date,
|
||||
type: type,
|
||||
title: title,
|
||||
classNames: [`${type}-event`],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 해당일 기준 이벤트 리스트 필터링
|
||||
const useFilterEventList = (month, day) => {
|
||||
// 생일자
|
||||
if (monthBirthdayList.value) {
|
||||
@ -234,13 +277,14 @@
|
||||
try {
|
||||
// 현재 표시 중인 월의 공휴일 정보 가져오기
|
||||
const holidayEvents = await fetchHolidays(currentYear, String(currentMonth).padStart(2, '0'));
|
||||
// 기존 이벤트에서 공휴일 이벤트를 제외한 이벤트만 필터링
|
||||
const existingEvents = calendarEvents.value.filter(event => !event.classNames?.includes('holiday-event'));
|
||||
// 필터링된 이벤트와 새로 가져온 공휴일 이벤트 병합
|
||||
calendarEvents.value = [...existingEvents, ...holidayEvents];
|
||||
calendarEvents.value = [...holidayEvents]; // 공휴일 정보로 초기화
|
||||
|
||||
// 출근 정보
|
||||
await loadCommuters();
|
||||
// 이벤트 데이터 가져오기
|
||||
const param = new URLSearchParams();
|
||||
param.append('year', currentYear);
|
||||
param.append('month', currentMonth);
|
||||
param.append('day', '1'); // 해당 월의 첫날
|
||||
await fetchEventList(param);
|
||||
} catch (error) {
|
||||
console.error('공휴일 정보 로딩 실패:', error);
|
||||
}
|
||||
@ -274,47 +318,81 @@
|
||||
return !isWeekend && !isHoliday;
|
||||
};
|
||||
|
||||
// 날짜 클릭 이벤트 함수
|
||||
let todayElement = null;
|
||||
// 이벤트 타입 정의
|
||||
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.date).format('YYYY-MM-DD');
|
||||
// const clickedDate = dayjs(info.dateStr);
|
||||
const { month, day } = $common.formatDateTime(new Date(info.dateStr));
|
||||
|
||||
// 클릭한 날짜에 월별 출근 정보가 있는지 확인
|
||||
const dateCommuters = monthlyCommuters.value.filter(commuter => commuter.COMMUTDAY === clickedDate);
|
||||
// 클릭한 날짜의 이벤트 리스트 필터링
|
||||
useFilterEventList(month, day);
|
||||
|
||||
// 출근 기록이 있는 경우에만 모달 열기
|
||||
if (dateCommuters.length > 0) {
|
||||
eventDate.value = clickedDate;
|
||||
isModalOpen.value = true;
|
||||
}
|
||||
// 팝오버 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>
|
||||
`;
|
||||
|
||||
if (isSelectableDate(info.date)) {
|
||||
const isToday = dayjs(info.date).isSame(dayjs(), 'day');
|
||||
// 기존 팝오버 제거
|
||||
const existingPopovers = document.querySelectorAll('.event-popover');
|
||||
existingPopovers.forEach(popover => popover.remove());
|
||||
|
||||
if (isToday) {
|
||||
// 오늘 날짜 클릭 시 클래스 제거하고 요소 저장
|
||||
todayElement = info.dayEl;
|
||||
todayElement.classList.remove('fc-day-today');
|
||||
} else if (todayElement) {
|
||||
// 다른 날짜 클릭 시 저장된 오늘 요소에 클래스 다시 추가
|
||||
todayElement.classList.add('fc-day-today');
|
||||
todayElement = null;
|
||||
// 새 팝오버 생성
|
||||
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());
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 바깥 클릭 시 todayElement 클래스 복구
|
||||
document.addEventListener(
|
||||
'click',
|
||||
event => {
|
||||
if (todayElement && !event.target.closest('.fc-daygrid-day')) {
|
||||
todayElement.classList.add('fc-day-today');
|
||||
todayElement = null;
|
||||
}
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
// 날짜 셀 클래스 추가 함수
|
||||
const getCellClassNames = arg => {
|
||||
const cellDate = dayjs(arg.date);
|
||||
@ -379,6 +457,50 @@
|
||||
}
|
||||
};
|
||||
|
||||
// 이벤트 아이콘 표시 함수
|
||||
const handleEventContent = item => {
|
||||
if (!item.event) return null;
|
||||
|
||||
// 공휴일인 경우 텍스트로 표시
|
||||
if (item.event.classNames?.includes('holiday-event')) {
|
||||
return {
|
||||
html: `<div class="holiday-text" style="color: white;">${item.event.title}</div>`,
|
||||
};
|
||||
}
|
||||
|
||||
// 현재 이벤트의 타입만 확인
|
||||
const eventType = item.event.extendedProps.type;
|
||||
if (!eventType) return null;
|
||||
|
||||
let iconCode = '';
|
||||
switch (eventType) {
|
||||
case 'birthday':
|
||||
iconCode = '300201';
|
||||
break;
|
||||
case 'vacation':
|
||||
iconCode = '300202';
|
||||
break;
|
||||
case 'birthdayParty':
|
||||
iconCode = '300203';
|
||||
break;
|
||||
case 'dinner':
|
||||
iconCode = '300204';
|
||||
break;
|
||||
case 'teaTime':
|
||||
iconCode = '300205';
|
||||
break;
|
||||
case 'workshop':
|
||||
iconCode = '300206';
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
html: `<img src="${baseUrl}img/main-category-img/main-${iconCode}.png" class="calendar-event-icon" style="width: 20px; height: 20px; margin: 2px;" />`,
|
||||
};
|
||||
};
|
||||
|
||||
// 캘린더 옵션 설정
|
||||
const calendarOptions = reactive({
|
||||
plugins: [dayGridPlugin, interactionPlugin],
|
||||
@ -392,19 +514,14 @@
|
||||
events: calendarEvents,
|
||||
eventOrder: 'sortIdx',
|
||||
contentHeight: 'auto',
|
||||
// eventContent: calendarCommuter,
|
||||
// 날짜 선택 관련 옵션 수정
|
||||
eventContent: handleEventContent,
|
||||
selectable: true,
|
||||
selectAllow: selectInfo => isSelectableDate(selectInfo.start),
|
||||
dateClick: handleDateClick,
|
||||
dayCellClassNames: getCellClassNames,
|
||||
|
||||
// 날짜 클릭 비활성화를 위한 추가 설정
|
||||
unselectAuto: true,
|
||||
droppable: false,
|
||||
eventDisplay: 'block',
|
||||
|
||||
// 커스텀 버튼 정의
|
||||
customButtons: {
|
||||
prev: {
|
||||
text: 'PREV',
|
||||
@ -470,9 +587,6 @@
|
||||
checkedInProject.value = storedProject;
|
||||
}
|
||||
|
||||
// 이벤트 카테고리 호출
|
||||
await fetchCategoryList();
|
||||
|
||||
// 오늘 기준 데이터 호출
|
||||
const { year, month, day } = $common.getToday();
|
||||
const param = new URLSearchParams();
|
||||
@ -480,7 +594,24 @@
|
||||
param.append('month', month);
|
||||
param.append('day', day);
|
||||
|
||||
// 이벤트 카테고리 호출
|
||||
await fetchCategoryList();
|
||||
await fetchEventList(param);
|
||||
useFilterEventList(month, day);
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
.fc-h-event {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.event-popover {
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.event-icon-select:hover {
|
||||
transform: scale(1.1);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
<template>
|
||||
<div class="">
|
||||
<template v-for="category in categoryList" :key="category.CMNCODVAL">
|
||||
<div class="border border-2 mt-3 card p-2">
|
||||
<div
|
||||
v-if="(category.CMNCODVAL === 300201 && birthdayList?.length) || (category.CMNCODVAL === 300202 && vacationList?.length)"
|
||||
class="border border-2 mt-3 card p-2"
|
||||
>
|
||||
<div class="row g-2">
|
||||
<div class="col-3 mx-0 px-0">
|
||||
<div class="ratio ratio-1x1">
|
||||
@ -14,36 +17,10 @@
|
||||
</div>
|
||||
<div class="col-9 mx-0 px-0">
|
||||
<template v-if="category.CMNCODVAL === 300201">
|
||||
<div class="ms-2">
|
||||
<ul class="row gx-1 mb-0 list-inline d-flex align-items-center">
|
||||
<li class="col-4 me-0" v-for="member in birthdayList" :key="member.MEMBERSEQ">
|
||||
<div class="ratio ratio-1x1 mb-0 profile-list">
|
||||
<img
|
||||
:src="`${baseUrl}upload/img/profile/${member.MEMBERPRF}`"
|
||||
alt="User Profile"
|
||||
class="rounded-circle"
|
||||
@error="$event.target.src = '/img/icons/icon.png'"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<MainMemberProfile :members="birthdayList" :baseUrl="baseUrl" />
|
||||
</template>
|
||||
<template v-if="category.CMNCODVAL === 300202">
|
||||
<div class="ms-2">
|
||||
<ul class="row gx-1 mb-0 list-inline d-flex align-items-center">
|
||||
<li class="col-4 me-0" v-for="member in vacationList" :key="member.MEMBERSEQ">
|
||||
<div class="ratio ratio-1x1 mb-0 profile-list">
|
||||
<img
|
||||
:src="`${baseUrl}upload/img/profile/${member.MEMBERPRF}`"
|
||||
alt="User Profile"
|
||||
class="rounded-circle"
|
||||
@error="$event.target.src = '/img/icons/icon.png'"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<MainMemberProfile :members="vacationList" :baseUrl="baseUrl" />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@ -54,6 +31,7 @@
|
||||
|
||||
<script setup>
|
||||
import { defineEmits } from 'vue';
|
||||
import MainMemberProfile from '@c/main/MainMemberProfile.vue';
|
||||
|
||||
const props = defineProps({
|
||||
project: {
|
||||
|
||||
31
src/components/main/MainMemberProfile.vue
Normal file
31
src/components/main/MainMemberProfile.vue
Normal file
@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<div class="ms-2">
|
||||
<ul class="row gx-1 mb-0 list-inline d-flex align-items-center">
|
||||
<li class="col-4 me-0" v-for="(member, index) in members" :key="index">
|
||||
<div class="ratio ratio-1x1 mb-0 profile-list">
|
||||
<img
|
||||
:src="`${baseUrl}upload/img/profile/${member.MEMBERPRF}`"
|
||||
alt="User Profile"
|
||||
class="rounded-circle"
|
||||
@error="$event.target.src = '/img/icons/icon.png'"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
members: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
baseUrl: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<MainCalendar />
|
||||
<MainEventCalendar />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import MainCalendar from '@/components/main/MainEventCalendar.vue';
|
||||
import MainEventCalendar from '@/components/main/MainEventCalendar.vue';
|
||||
</script>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user