diff --git a/src/components/commuters/CommuteCalendar.vue b/src/components/commuters/CommuteCalendar.vue
index 9898eaa..688a129 100644
--- a/src/components/commuters/CommuteCalendar.vue
+++ b/src/components/commuters/CommuteCalendar.vue
@@ -5,27 +5,41 @@
![Profile Image]()
-
+
{{ user.name }}
-
출근시간
-
-
+
{{ post.PROJCTNAM }}
+
+
![User Profile]()
+
@@ -50,26 +64,12 @@
- Add Event
+ 상세보기
-
-
+
- 추가
+
@@ -81,38 +81,123 @@ import interactionPlugin from '@fullcalendar/interaction';
import CenterModal from '@c/modal/CenterModal.vue';
import { inject, onMounted, reactive, ref, watch } from 'vue';
import $api from '@api';
-import { isEmpty } from '@/common/utils';
-import FormInput from '../input/FormInput.vue';
import 'flatpickr/dist/flatpickr.min.css';
import '@/assets/css/app-calendar.css';
import { fetchHolidays } from '@c/calendar/holiday';
import { useUserInfoStore } from '@/stores/useUserInfoStore';
import { useProjectStore } from '@/stores/useProjectStore';
+import BackBtn from '@c/button/BackBtn.vue';
+import { useToastStore } from '@/stores/toastStore';
const baseUrl = $api.defaults.baseURL.replace(/api\/$/, '');
const user = ref({});
const project = ref({});
const userStore = useUserInfoStore();
const projectStore = useProjectStore();
+const toastStore = useToastStore();
const dayjs = inject('dayjs');
const fullCalendarRef = ref(null);
const calendarEvents = ref([]);
const isModalVisible = ref(false);
-const eventAlert = ref(false);
-const eventDateAlert = ref(false);
-const eventTitle = ref('');
const eventDate = ref('');
-const selectedDate = ref(null);
+const workTime = ref(null);
+const selectedProject = ref(null);
+const checkedInProject = ref(null);
-// 날짜 선택 핸들러
-const handleDateSelect = (selectedDates) => {
- if (selectedDates.length > 0) {
- // 선택된 첫 번째 날짜를 YYYY-MM-DD 형식으로 변환
- const selectedDate = dayjs(selectedDates[0]).format('YYYY-MM-DD');
- eventDate.value = selectedDate;
- showModal(); // 모달 표시
+const draggedProject = ref(null);
+
+// 드래그 시작 이벤트 핸들러
+const dragStart = (event, project) => {
+ draggedProject.value = project;
+ // 드래그 데이터 설정
+ event.dataTransfer.setData('application/json', JSON.stringify(project));
+ event.dataTransfer.effectAllowed = 'copy';
+};
+
+// 드래그 오버 드롭 허용
+const allowDrop = (event) => {
+ event.preventDefault();
+};
+
+// 드롭
+const handleDrop = (event, targetProject) => {
+ event.preventDefault();
+
+ // 드래그한 프로젝트와 드롭한 프로젝트가 같으면 아무 동작 안 함
+ if (draggedProject.value.PROJCTSEQ === targetProject.PROJCTSEQ) {
+ return;
+ }
+
+ // 선택된 프로젝트 변경
+ checkedInProject.value = targetProject;
+ projectStore.setSelectedProject(targetProject);
+
+ // select 값도 변경
+ selectedProject.value = targetProject.PROJCTSEQ;
+
+ // 프로필 테두리 색상 업데이트
+ addProfileToCalendar();
+};
+
+// 출근 시간
+const setWorkTime = () => {
+ // 이미 출근 시간이 설정된 경우 중복 실행 방지
+ if (workTime.value) return;
+
+ const now = new Date();
+ workTime.value = now.toLocaleTimeString('ko-KR', { hour12: false });
+
+ // 현재 선택된 프로젝트 가져오기
+ const currentProject = projectStore.selectedProject || projectStore.getSelectedProject();
+ if (currentProject) {
+ checkedInProject.value = currentProject;
+
+ // 캘린더에 오늘 날짜에 프로필 이미지 추가하기
+ addProfileToCalendar();
+ }
+
+ $api.post('commuters/insert', {
+ memberSeq: user.value.id,
+ projctSeq: checkedInProject.value.PROJCTSEQ,
+ commutCmt: workTime.value,
+ commutLvt: null,
+ commutArr: null,
+ }).then(res => {
+ if (res.status === 200) {
+ toastStore.onToast('출근 등록되었습니다.', 's');
+ }
+ });
+};
+
+// 캘린더의 오늘 날짜에 프로필 이미지 추가
+const addProfileToCalendar = () => {
+ const calendarApi = fullCalendarRef.value?.getApi();
+ if (!calendarApi || !user.value) return;
+
+ // 오늘 날짜 셀 찾기
+ const today = dayjs().format('YYYY-MM-DD');
+ const todayCell = document.querySelector(`.fc-day[data-date="${today}"]`) || document.querySelector(`.fc-daygrid-day[data-date="${today}"]`);
+
+ if (todayCell) {
+ const dayFrame = todayCell.querySelector('.fc-daygrid-day-events');
+
+ if (dayFrame) {
+ const existingProfileImg = dayFrame.querySelector('.profile-img');
+ if (existingProfileImg) {
+ dayFrame.removeChild(existingProfileImg);
+ }
+
+ const profileImg = document.createElement('img');
+ profileImg.src = `${baseUrl}upload/img/profile/${user.value.profile}`;
+ profileImg.className = 'profile-img rounded-circle w-px-20 h-px-20';
+ profileImg.style.border = `2px solid ${checkedInProject.value.projctcolor}`;
+
+ profileImg.onerror = () => { profileImg.src = '/img/icons/icon.png'; };
+
+ dayFrame.appendChild(profileImg);
+ }
}
};
@@ -139,6 +224,11 @@ const fetchData = async () => {
const existingEvents = calendarEvents.value.filter(event => !event.classNames?.includes('holiday-event'));
// 필터링된 이벤트와 새로 가져온 공휴일 이벤트 병합
calendarEvents.value = [...existingEvents, ...holidayEvents];
+
+ // 출근 시간이 있으면 프로필 이미지 다시 추가
+ if (workTime.value && checkedInProject.value) {
+ setTimeout(addProfileToCalendar, 100); // 달력이 렌더링된 후 실행
+ }
} catch (error) {
console.error('공휴일 정보 로딩 실패:', error);
}
@@ -160,72 +250,108 @@ const moveCalendar = async (value = 0) => {
await fetchData();
};
-// 모달 표시 함수
-const showModal = () => {
- isModalVisible.value = true;
+// 날짜 선택 가능 여부를 확인하는 공통 함수
+const isSelectableDate = (date) => {
+ const checkDate = dayjs(date);
+ const today = dayjs().startOf('day');
+ const isWeekend = checkDate.day() === 0 || checkDate.day() === 6;
+ // 공휴일 체크
+ const isHoliday = calendarEvents.value.some(event =>
+ event.classNames?.includes('holiday-event') &&
+ dayjs(event.start).format('YYYY-MM-DD') === checkDate.format('YYYY-MM-DD')
+ );
+
+ return !checkDate.isBefore(today) && !isWeekend && !isHoliday;
};
-// 모달 닫기 함수
-const closeModal = () => {
- isModalVisible.value = false;
- // 입력 필드 초기화
- eventTitle.value = '';
- eventDate.value = '';
-};
-// 이벤트 추가 함수
-const addEvent = () => {
- // 이벤트 유효성 검사
- if (!checkEvent()) {
- // 유효성 검사 통과 시 이벤트 추가
- calendarEvents.value.push({
- title: eventTitle.value,
- start: eventDate.value,
- backgroundColor: '#4CAF50' // 일반 이벤트 색상
- });
- closeModal(); // 모달 닫기
+// 날짜 클릭 이벤트 함수
+let todayElement = null;
+const handleDateClick = (info) => {
+ if (isSelectableDate(info.date)) {
+ const isToday = dayjs(info.date).isSame(dayjs(), 'day');
+
+ if (isToday) {
+ // 오늘 날짜 클릭 시 클래스 제거하고 요소 저장
+ todayElement = info.dayEl;
+ todayElement.classList.remove('fc-day-today');
+ } else if (todayElement) {
+ // 다른 날짜 클릭 시 저장된 오늘 요소에 클래스 다시 추가
+ todayElement.classList.add('fc-day-today');
+ todayElement = null;
+ }
+
+ eventDate.value = dayjs(info.date).format('YYYY-MM-DD');
+ showModal();
}
};
-// 이벤트 유효성 검사 함수
-const checkEvent = () => {
- // 제목과 날짜가 비어있는지 확인
- eventAlert.value = isEmpty(eventTitle.value);
- eventDateAlert.value = isEmpty(eventDate.value);
- // 하나라도 비어있으면 true 반환 (유효성 검사 실패)
- return eventAlert.value || eventDateAlert.value;
+// 바깥 클릭 시 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);
+ const today = dayjs().startOf('day');
+ const classes = [];
+
+ // 선택 불가능한 날짜(과거, 주말, 공휴일)에 동일한 클래스 추가
+ if (!isSelectableDate(cellDate) || cellDate.isBefore(today)) {
+ classes.push('fc-day-sat-sun');
+ }
+
+ return classes;
};
// 캘린더 옵션 설정
const calendarOptions = reactive({
- plugins: [dayGridPlugin, interactionPlugin], // 사용할 플러그인
- initialView: 'dayGridMonth', // 초기 뷰 (월간)
- headerToolbar: { // 상단 툴바 구성
- left: 'today', // 왼쪽: 오늘 버튼
- center: 'title', // 중앙: 제목(연월)
- right: 'prev,next', // 오른쪽: 이전/다음 버튼
+ plugins: [dayGridPlugin, interactionPlugin],
+ initialView: 'dayGridMonth',
+ headerToolbar: {
+ left: 'today',
+ center: 'title',
+ right: 'prev,next',
+ },
+ locale: 'kr',
+ events: calendarEvents,
+ eventOrder: 'sortIdx',
+ // 날짜 선택 관련 옵션 수정
+ selectable: true,
+ selectAllow: (selectInfo) => isSelectableDate(selectInfo.start),
+ dateClick: handleDateClick,
+ dayCellClassNames: getCellClassNames,
+
+ // 날짜 클릭 비활성화를 위한 추가 설정
+ unselectAuto: true,
+ droppable: false,
+ eventDisplay: 'block',
+
+ // 캘린더 렌더링 완료 이벤트
+ datesSet: () => {
+ // 캘린더가 렌더링 된 후 프로필 이미지 다시 추가 (월 변경 시)
+ if (workTime.value && checkedInProject.value) {
+ setTimeout(addProfileToCalendar, 100);
+ }
},
- locale: 'kr', // 한국어 지역화
- events: calendarEvents, // 표시할 이벤트 데이터
- eventOrder: 'sortIdx', // 이벤트 정렬 기준
- selectable: true, // 날짜 선택 가능 여부
- dateClick: handleDateSelect, // 날짜 클릭 이벤트 핸들러
- droppable: false, // 드래그 앤 드롭 비활성화
- eventDisplay: 'block', // 이벤트 표시 방식
// 커스텀 버튼 정의
customButtons: {
prev: {
- text: 'PREV', // 이전 버튼 텍스트
- click: () => moveCalendar(1), // 클릭 시 이전 달로 이동
+ text: 'PREV',
+ click: () => moveCalendar(1),
},
today: {
- text: 'TODAY', // 오늘 버튼 텍스트
- click: () => moveCalendar(3), // 클릭 시 오늘로 이동
+ text: 'TODAY',
+ click: () => moveCalendar(3),
},
next: {
- text: 'NEXT', // 다음 버튼 텍스트
- click: () => moveCalendar(2), // 클릭 시 다음 달로 이동
+ text: 'NEXT',
+ click: () => moveCalendar(2),
},
},
});
@@ -235,13 +361,44 @@ watch(() => fullCalendarRef.value?.getApi().currentData.viewTitle, async () => {
await fetchData();
});
+// 모달 표시 함수
+const showModal = () => {
+ isModalVisible.value = true;
+};
+
+// 모달 닫기 함수
+const closeModal = () => {
+ isModalVisible.value = false;
+};
+
+// selectedProject 변경 감지
+watch(() => projectStore.selectedProject, (newProject) => {
+ if (newProject) {
+ selectedProject.value = newProject.PROJCTSEQ;
+ checkedInProject.value = newProject;
+ addProfileToCalendar();
+ }
+});
-console.log(project)
onMounted(async () => {
await fetchData();
await userStore.userInfo();
user.value = userStore.user;
await projectStore.getProjectList();
project.value = projectStore.projectList;
+
+ // 저장된 선택 프로젝트 가져오기
+ const storedProject = projectStore.getSelectedProject();
+ if (storedProject) {
+ selectedProject.value = storedProject;
+ }
+
+ // 캘린더가 완전히 로드된 후 프로필 추가를 위한 지연 설정
+ setTimeout(() => {
+ // 이미 출근 시간이 있는 경우 (페이지 새로고침 등) 프로필 이미지 표시
+ if (workTime.value && checkedInProject.value) {
+ addProfileToCalendar();
+ }
+ }, 500);
});
diff --git a/src/layouts/TheTop.vue b/src/layouts/TheTop.vue
index 4c1d281..9f1121d 100644
--- a/src/layouts/TheTop.vue
+++ b/src/layouts/TheTop.vue
@@ -8,6 +8,12 @@
+
+
@@ -234,36 +240,79 @@
diff --git a/src/stores/useProjectStore.js b/src/stores/useProjectStore.js
index 3770caf..62da6a7 100644
--- a/src/stores/useProjectStore.js
+++ b/src/stores/useProjectStore.js
@@ -6,11 +6,12 @@
설명 : 프로젝트 목록
*/
import { defineStore } from 'pinia';
-import { ref } from 'vue';
+import { ref, watch } from 'vue';
import $api from '@api';
export const useProjectStore = defineStore('project', () => {
const projectList = ref([]);
+ const selectedProject = ref(null);
const getProjectList = async (searchText = '', selectedYear = '') => {
const res = await $api.get('project/select', {
@@ -22,6 +23,36 @@ export const useProjectStore = defineStore('project', () => {
projectList.value = res.data.data.projectList;
};
+ const setSelectedProject = (project) => {
+ selectedProject.value = { ...project };
- return { projectList, getProjectList };
+ if (project) {
+ localStorage.setItem('selectedProject', JSON.stringify(project));
+ }
+ };
+
+ const getSelectedProject = () => {
+ if (!selectedProject.value) {
+ const storedProject = localStorage.getItem('selectedProject');
+ if (storedProject) {
+ selectedProject.value = JSON.parse(storedProject);
+ }
+ }
+ return selectedProject.value;
+ };
+
+ // 프로젝트 리스트가 변경될 때 자동으로 반응
+ watch(projectList, (newList) => {
+ if (!selectedProject.value && newList.length > 0) {
+ setSelectedProject(newList[0]);
+ }
+ });
+
+ return {
+ projectList,
+ selectedProject,
+ getProjectList,
+ setSelectedProject,
+ getSelectedProject
+ };
});