출퇴근
This commit is contained in:
parent
10aaae307e
commit
a772a2b4e6
129
src/components/commuters/CommuterBtn.vue
Normal file
129
src/components/commuters/CommuterBtn.vue
Normal file
@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<div class="row g-0">
|
||||
<div class="col-6 pe-1">
|
||||
<p class="mb-1">출근시간</p>
|
||||
<button
|
||||
class="btn border-3 w-100 py-0 h-px-50"
|
||||
:class="workTime ? 'p-0 btn-primary pe-none' : 'btn-outline-primary'"
|
||||
@click="setWorkTime"
|
||||
>
|
||||
<i v-if="!workTime" class="bx bx-run fs-2"></i>
|
||||
<span v-if="workTime" class="ql-size-12px">{{ workTime }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="col-6 ps-1">
|
||||
<p class="mb-1">퇴근시간</p>
|
||||
<button
|
||||
class="btn btn-outline-secondary border-3 w-100 py-0 h-px-50"
|
||||
@click="setLeaveTime"
|
||||
>
|
||||
<i v-if="!leaveTime" class='bx bxs-door-open fs-2'></i>
|
||||
<span v-if="leaveTime" class="ql-size-12px">{{ leaveTime }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, defineProps, defineEmits, onMounted, watch } from 'vue';
|
||||
import $api from '@api';
|
||||
import { useToastStore } from '@/stores/toastStore';
|
||||
|
||||
const props = defineProps({
|
||||
userId: {
|
||||
type: [Number],
|
||||
required: false
|
||||
},
|
||||
checkedInProject: {
|
||||
type: Object,
|
||||
required: false
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['workTimeUpdated', 'leaveTimeUpdated']);
|
||||
|
||||
const workTime = ref(null);
|
||||
const leaveTime = ref(null);
|
||||
const toastStore = useToastStore();
|
||||
|
||||
// 오늘 사용자의 출근 정보 조회
|
||||
const todayCommuterInfo = async () => {
|
||||
if (!props.userId) return;
|
||||
|
||||
const res = await $api.get(`commuters/today/${props.userId}`);
|
||||
if (res.status === 200) {
|
||||
const commuterInfo = res.data.data[0];
|
||||
|
||||
if (commuterInfo) {
|
||||
workTime.value = commuterInfo.COMMUTCMT;
|
||||
leaveTime.value = commuterInfo.COMMUTLVE;
|
||||
|
||||
// 부모 컴포넌트에 상태 업데이트 알림
|
||||
emit('workTimeUpdated', workTime.value);
|
||||
emit('leaveTimeUpdated', leaveTime.value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 출근 시간
|
||||
const setWorkTime = () => {
|
||||
// 이미 출근 시간이 설정된 경우 중복 실행 방지
|
||||
if (workTime.value) return;
|
||||
|
||||
$api.post('commuters/insert', {
|
||||
memberSeq: props.userId,
|
||||
projctSeq: props.checkedInProject.PROJCTSEQ,
|
||||
commutLvt: null,
|
||||
commutArr: null,
|
||||
}).then(res => {
|
||||
if (res.status === 200) {
|
||||
toastStore.onToast('출근 완료.', 's');
|
||||
todayCommuterInfo();
|
||||
// 부모 컴포넌트에 업데이트 이벤트 발생
|
||||
emit('workTimeUpdated', true);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 퇴근
|
||||
const setLeaveTime = () => {
|
||||
$api.patch('commuters/updateLve', {
|
||||
memberSeq: props.userId,
|
||||
commutLve: leaveTime.value || null,
|
||||
}).then(res => {
|
||||
if (res.status === 200) {
|
||||
if (leaveTime.value) {
|
||||
toastStore.onToast('퇴근 시간 초기화', 'e');
|
||||
} else {
|
||||
toastStore.onToast('퇴근 완료', 's');
|
||||
}
|
||||
todayCommuterInfo();
|
||||
// 부모 컴포넌트에 업데이트 이벤트 발생
|
||||
emit('leaveTimeUpdated', true);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// props 변경 감지
|
||||
watch(() => props.userId, async () => {
|
||||
if (props.userId) {
|
||||
await todayCommuterInfo();
|
||||
}
|
||||
});
|
||||
|
||||
watch(() => props.checkedInProject, () => {
|
||||
// 프로젝트가 변경되면 필요한 처리 수행
|
||||
}, { deep: true });
|
||||
|
||||
onMounted(async () => {
|
||||
await todayCommuterInfo();
|
||||
});
|
||||
|
||||
// 외부에서 접근할 메서드 노출
|
||||
defineExpose({
|
||||
todayCommuterInfo,
|
||||
workTime,
|
||||
leaveTime
|
||||
});
|
||||
</script>
|
||||
@ -3,57 +3,35 @@
|
||||
<div class="card app-calendar-wrapper">
|
||||
<div class="row g-0">
|
||||
<div class="col-3 border-end text-center">
|
||||
<div class="card-body pb-0">
|
||||
<div class="card-body">
|
||||
<img v-if="user" :src="`${baseUrl}upload/img/profile/${user.profile}`" alt="Profile Image" class="w-px-50 h-px-50 rounded-circle" @error="$event.target.src = '/img/icons/icon.png'"/>
|
||||
<p class="mt-2 fw-bold">
|
||||
{{ user.name }}
|
||||
</p>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="col-6 pe-1">
|
||||
<p class="mb-1">출근시간</p>
|
||||
<button class="btn border-3 w-100 py-0 h-px-50" :class="workTime ? 'p-0 btn-primary pe-none' : 'btn-outline-primary'" @click="setWorkTime" >
|
||||
<i v-if="!workTime" class="bx bx-run fs-2"></i>
|
||||
<span v-if="workTime" class="ql-size-12px">{{ workTime }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<CommuterBtn
|
||||
:userId="user.id"
|
||||
:checkedInProject="checkedInProject || {}"
|
||||
@workTimeUpdated="handleWorkTimeUpdate"
|
||||
@leaveTimeUpdated="handleLeaveTimeUpdate"
|
||||
ref="workTimeComponentRef"
|
||||
/>
|
||||
|
||||
<div class="col-6 ps-1">
|
||||
<p class="mb-1">퇴근시간</p>
|
||||
<button class="btn btn-outline-secondary border-3 w-100 py-0 h-px-50" @click="setLeaveTime">
|
||||
<i v-if="!leaveTime" class='bx bxs-door-open fs-2'></i>
|
||||
<span v-if="leaveTime" class="ql-size-12px">{{ leaveTime }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-for="post in project" :key="post.PROJCTSEQ"
|
||||
class="border border-2 mt-3 card p-2"
|
||||
:style="`border-color: ${post.projctcolor} !important; color: ${post.projctcolor} !important;`"
|
||||
@dragover="allowDrop($event)"
|
||||
@drop="handleDrop($event, post)">
|
||||
<p class="mb-1">
|
||||
{{ post.PROJCTNAM }}
|
||||
</p>
|
||||
<div class="row gx-2">
|
||||
<div v-for="commuter in commuters.filter(c => c.PROJCTNAM === post.PROJCTNAM)" :key="commuter.COMMUTCMT" class="col-4">
|
||||
<div class="ratio ratio-1x1">
|
||||
<img :src="`${baseUrl}upload/img/profile/${commuter.profile}`"
|
||||
alt="User Profile"
|
||||
class="rounded-circle"
|
||||
:draggable="isCurrentUser(commuter)"
|
||||
@dragstart="isCurrentUser(commuter) ? dragStart($event, post) : null"
|
||||
@error="$event.target.src = '/img/icons/icon.png'">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<CommuterProjectList
|
||||
:project="project"
|
||||
:commuters="commuters"
|
||||
:baseUrl="baseUrl"
|
||||
:user="user"
|
||||
:selectedProject="selectedProject"
|
||||
:checkedInProject="checkedInProject"
|
||||
@drop="handleProjectDrop"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col app-calendar-content">
|
||||
<div class="card shadow-none border-0">
|
||||
<div class="card-body pb-0">
|
||||
<div class="card-body">
|
||||
<full-calendar
|
||||
ref="fullCalendarRef"
|
||||
:events="calendarEvents"
|
||||
@ -69,7 +47,31 @@
|
||||
</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>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@ -77,7 +79,7 @@ import FullCalendar from '@fullcalendar/vue3';
|
||||
import dayGridPlugin from '@fullcalendar/daygrid';
|
||||
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 'flatpickr/dist/flatpickr.min.css';
|
||||
import '@/assets/css/app-calendar.css';
|
||||
@ -85,6 +87,9 @@ import { fetchHolidays } from '@c/calendar/holiday';
|
||||
import { useUserInfoStore } from '@/stores/useUserInfoStore';
|
||||
import { useProjectStore } from '@/stores/useProjectStore';
|
||||
import { useToastStore } from '@/stores/toastStore';
|
||||
import CommuterBtn from '@c/commuters/CommuterBtn.vue';
|
||||
import CommuterProjectList from '@c/commuters/CommuterProjectList.vue';
|
||||
import BackBtn from '@c/button/BackBtn.vue';
|
||||
|
||||
const baseUrl = $api.defaults.baseURL.replace(/api\/$/, '');
|
||||
const user = ref({});
|
||||
@ -95,43 +100,37 @@ const toastStore = useToastStore();
|
||||
|
||||
const dayjs = inject('dayjs');
|
||||
const fullCalendarRef = ref(null);
|
||||
const workTimeComponentRef = ref(null);
|
||||
const calendarEvents = ref([]);
|
||||
const eventDate = ref('');
|
||||
|
||||
const workTime = ref(null);
|
||||
const leaveTime = ref(null);
|
||||
const selectedProject = ref(null);
|
||||
const checkedInProject = ref(null);
|
||||
|
||||
const draggedProject = ref(null);
|
||||
// 모달 상태
|
||||
const isModalOpen = ref(false);
|
||||
|
||||
const commuters = ref([]);
|
||||
const monthlyCommuters = ref([]);
|
||||
|
||||
|
||||
// 현재 사용자 확인
|
||||
const isCurrentUser = (commuter) => {
|
||||
return user.value && commuter && commuter.MEMBERSEQ === user.value.id;
|
||||
// 출퇴근 컴포넌트 이벤트 핸들러
|
||||
const handleWorkTimeUpdate = () => {
|
||||
todaysCommuter();
|
||||
loadCommuters();
|
||||
};
|
||||
|
||||
// 드래그 시작 이벤트 핸들러
|
||||
const dragStart = (event, project) => {
|
||||
draggedProject.value = project;
|
||||
// 드래그 데이터 설정
|
||||
event.dataTransfer.setData('application/json', JSON.stringify(project));
|
||||
event.dataTransfer.effectAllowed = 'copy';
|
||||
const handleLeaveTimeUpdate = () => {
|
||||
todaysCommuter();
|
||||
};
|
||||
|
||||
// 드래그 오버 드롭 허용
|
||||
const allowDrop = (event) => {
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
// 드롭
|
||||
const handleDrop = (event, targetProject) => {
|
||||
event.preventDefault();
|
||||
// 프로젝트 드롭 이벤트 핸들러 (ProjectList 컴포넌트에서 전달받음)
|
||||
const handleProjectDrop = ({ event, targetProject }) => {
|
||||
// 드래그된 프로젝트 데이터 가져오기
|
||||
const draggedProjectData = JSON.parse(event.dataTransfer.getData('application/json'));
|
||||
|
||||
// 드래그한 프로젝트와 드롭한 프로젝트가 같으면 아무 동작 안 함
|
||||
if (draggedProject.value.PROJCTSEQ === targetProject.PROJCTSEQ) {
|
||||
if (draggedProjectData.PROJCTSEQ === targetProject.PROJCTSEQ) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -168,23 +167,6 @@ const handleDrop = (event, targetProject) => {
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// 오늘 사용자의 출근 정보 조회
|
||||
const todayCommuterInfo = async () => {
|
||||
if (!user.value || !user.value.id) return;
|
||||
|
||||
const res = await $api.get(`commuters/today/${user.value.id}`);
|
||||
if (res.status === 200 ) {
|
||||
const commuterInfo = res.data.data[0];
|
||||
|
||||
if (commuterInfo) {
|
||||
workTime.value = commuterInfo.COMMUTCMT;
|
||||
leaveTime.value = commuterInfo.COMMUTLVE;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 오늘 출근 모든 사용자 조회
|
||||
const todaysCommuter = async () => {
|
||||
const res = await $api.get(`commuters/todays`);
|
||||
@ -193,75 +175,6 @@ const todaysCommuter = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 출근 시간
|
||||
const setWorkTime = () => {
|
||||
// 이미 출근 시간이 설정된 경우 중복 실행 방지
|
||||
if (workTime.value) return;
|
||||
|
||||
// 현재 선택된 프로젝트 가져오기
|
||||
const currentProject = projectStore.selectedProject || projectStore.getSelectedProject();
|
||||
if (currentProject) {
|
||||
checkedInProject.value = currentProject;
|
||||
}
|
||||
|
||||
return;
|
||||
$api.post('commuters/insert', {
|
||||
memberSeq: user.value.id,
|
||||
projctSeq: checkedInProject.value.PROJCTSEQ,
|
||||
commutLvt: null,
|
||||
commutArr: null,
|
||||
}).then(res => {
|
||||
if (res.status === 200) {
|
||||
toastStore.onToast('출근 완료.', 's');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 퇴근
|
||||
const setLeaveTime = () => {
|
||||
|
||||
$api.patch('commuters/updateLve', {
|
||||
memberSeq: user.value.id,
|
||||
commutLve: leaveTime.value || null,
|
||||
}).then(res => {
|
||||
if (res.status === 200) {
|
||||
if (leaveTime.value) {
|
||||
toastStore.onToast('퇴근 시간 초기화', 'e');
|
||||
} else {
|
||||
toastStore.onToast('퇴근 완료', 's');
|
||||
}
|
||||
todayCommuterInfo();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 캘린더의 오늘 날짜에 프로필 이미지 추가
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 캘린더 데이터 가져오기
|
||||
const fetchData = async () => {
|
||||
// FullCalendar API 인스턴스 가져오기
|
||||
@ -326,20 +239,31 @@ const isSelectableDate = (date) => {
|
||||
// 날짜 클릭 이벤트 함수
|
||||
let todayElement = null;
|
||||
const handleDateClick = (info) => {
|
||||
const clickedDate = dayjs(info.date).format('YYYY-MM-DD');
|
||||
|
||||
// 클릭한 날짜에 월별 출근 정보가 있는지 확인
|
||||
const dateCommuters = monthlyCommuters.value.filter(commuter =>
|
||||
commuter.COMMUTDAY === clickedDate
|
||||
);
|
||||
|
||||
// 출근 기록이 있는 경우에만 모달 열기
|
||||
if (dateCommuters.length > 0) {
|
||||
eventDate.value = clickedDate;
|
||||
isModalOpen.value = true;
|
||||
}
|
||||
|
||||
if (isSelectableDate(info.date)) {
|
||||
const isToday = dayjs(info.date).isSame(dayjs(), 'day');
|
||||
|
||||
if (isToday) {
|
||||
// 오늘 날짜 클릭 시 클래스 제거하고 요소 저장
|
||||
todayElement = info.dayEl;
|
||||
todayElement.classList.remove('fc-day-today');
|
||||
// 오늘 날짜 클릭 시 클래스 제거하고 요소 저장
|
||||
todayElement = info.dayEl;
|
||||
todayElement.classList.remove('fc-day-today');
|
||||
} else if (todayElement) {
|
||||
// 다른 날짜 클릭 시 저장된 오늘 요소에 클래스 다시 추가
|
||||
todayElement.classList.add('fc-day-today');
|
||||
todayElement = null;
|
||||
// 다른 날짜 클릭 시 저장된 오늘 요소에 클래스 다시 추가
|
||||
todayElement.classList.add('fc-day-today');
|
||||
todayElement = null;
|
||||
}
|
||||
|
||||
eventDate.value = dayjs(info.date).format('YYYY-MM-DD');
|
||||
}
|
||||
};
|
||||
|
||||
@ -364,6 +288,7 @@ const getCellClassNames = (arg) => {
|
||||
return classes;
|
||||
};
|
||||
|
||||
// 현재 달의 모든 출근 정보 조회
|
||||
const loadCommuters = async () => {
|
||||
const calendarApi = fullCalendarRef.value?.getApi();
|
||||
if (!calendarApi) return;
|
||||
@ -374,6 +299,7 @@ const loadCommuters = async () => {
|
||||
const regex = /\D/g;
|
||||
currentYear = parseInt(currentYear.replace(regex, ''), 10);
|
||||
currentMonth = parseInt(currentMonth.replace(regex, ''), 10);
|
||||
|
||||
const res = await $api.get('commuters/month', {
|
||||
params: {
|
||||
year: currentYear,
|
||||
@ -381,13 +307,14 @@ const loadCommuters = async () => {
|
||||
}
|
||||
});
|
||||
if (res.status === 200) {
|
||||
const commuters = res.data.data;
|
||||
// 월별 출근 정보 저장
|
||||
monthlyCommuters.value = res.data.data;
|
||||
|
||||
document.querySelectorAll('.fc-daygrid-day-events img.rounded-circle').forEach(img => {
|
||||
img.remove();
|
||||
});
|
||||
|
||||
commuters.forEach(commuter => {
|
||||
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}"]`);
|
||||
@ -395,7 +322,6 @@ const loadCommuters = async () => {
|
||||
if (dateCell) {
|
||||
const dayEvents = dateCell.querySelector('.fc-daygrid-day-events');
|
||||
if (dayEvents) {
|
||||
|
||||
// 프로필 이미지 생성
|
||||
const profileImg = document.createElement('img');
|
||||
profileImg.src = `${baseUrl}upload/img/profile/${commuter.profile}`;
|
||||
@ -467,6 +393,22 @@ watch(() => projectStore.selectedProject, (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();
|
||||
@ -474,14 +416,13 @@ onMounted(async () => {
|
||||
await projectStore.getProjectList('', '', 'true');
|
||||
project.value = projectStore.projectList;
|
||||
|
||||
await todayCommuterInfo();
|
||||
await todaysCommuter();
|
||||
|
||||
// 저장된 선택 프로젝트 가져오기
|
||||
const storedProject = projectStore.getSelectedProject();
|
||||
if (storedProject) {
|
||||
selectedProject.value = storedProject;
|
||||
selectedProject.value = storedProject.PROJCTSEQ;
|
||||
checkedInProject.value = storedProject;
|
||||
}
|
||||
|
||||
});
|
||||
</script>
|
||||
82
src/components/commuters/CommuterProjectList.vue
Normal file
82
src/components/commuters/CommuterProjectList.vue
Normal file
@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<div style="max-height: 590px; overflow-y: auto;">
|
||||
<div v-for="post in project" :key="post.PROJCTSEQ"
|
||||
class="border border-2 mt-3 card p-2"
|
||||
:style="`border-color: ${post.projctcolor} !important; color: ${post.projctcolor} !important;`"
|
||||
@dragover="allowDrop($event)"
|
||||
@drop="handleDrop($event, post)">
|
||||
<p class="mb-1">
|
||||
{{ post.PROJCTNAM }}
|
||||
</p>
|
||||
<div class="row gx-2">
|
||||
<div v-for="commuter in commuters.filter(c => c.PROJCTNAM === post.PROJCTNAM)" :key="commuter.COMMUTCMT" class="col-4">
|
||||
<div class="ratio ratio-1x1">
|
||||
<img :src="`${baseUrl}upload/img/profile/${commuter.profile}`"
|
||||
alt="User Profile"
|
||||
class="rounded-circle"
|
||||
:class="isCurrentUser(commuter) ? 'cursor-pointer' : ''"
|
||||
:draggable="isCurrentUser(commuter)"
|
||||
@dragstart="isCurrentUser(commuter) ? dragStart($event, post) : null"
|
||||
@error="$event.target.src = '/img/icons/icon.png'">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
project: {
|
||||
type: Object,
|
||||
required: false
|
||||
},
|
||||
commuters: {
|
||||
type: Array,
|
||||
required: false
|
||||
},
|
||||
baseUrl: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
user: {
|
||||
type: Object,
|
||||
required: false
|
||||
},
|
||||
selectedProject: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
checkedInProject: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
});
|
||||
|
||||
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>
|
||||
@ -177,10 +177,12 @@ import ArrInput from '@c/input/ArrInput.vue';
|
||||
import { useToastStore } from '@s/toastStore';
|
||||
import { useUserInfoStore } from '@/stores/useUserInfoStore';
|
||||
import commonApi from '@/common/commonApi';
|
||||
import { useProjectStore } from '@/stores/useProjectStore';
|
||||
|
||||
// 스토어
|
||||
const toastStore = useToastStore();
|
||||
const userStore = useUserInfoStore();
|
||||
const projectStore = useProjectStore();
|
||||
|
||||
// Props 정의
|
||||
const props = defineProps({
|
||||
@ -448,7 +450,8 @@ const handleDelete = () => {
|
||||
.then(res => {
|
||||
if (res.status === 200) {
|
||||
toastStore.onToast('삭제가 완료되었습니다.', 's');
|
||||
location.reload()
|
||||
projectStore.getProjectList();
|
||||
projectStore.getMemberProjects();
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
@ -247,6 +247,8 @@
|
||||
toastStore.onToast('프로젝트가 등록되었습니다.', 's');
|
||||
closeCreateModal();
|
||||
getProjectList();
|
||||
projectStore.getMemberProjects();
|
||||
formReset();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
<div class="navbar-nav-right d-flex align-items-center" id="navbar-collapse">
|
||||
<ul class="navbar-nav flex-row align-items-center ms-auto">
|
||||
<select class="form-select py-1" id="name" v-model="selectedProject" @change="updateSelectedProject">
|
||||
<option v-for="item in memberProject" :key="item.PROJCTSEQ" :value="item.PROJCTSEQ">
|
||||
<option v-for="item in projectStore.memberProjectList" :key="item.PROJCTSEQ" :value="item.PROJCTSEQ">
|
||||
{{ item.PROJCTNAM }}
|
||||
</option>
|
||||
</select>
|
||||
@ -254,14 +254,13 @@
|
||||
const router = useRouter();
|
||||
|
||||
const user = ref(null);
|
||||
const memberProject = ref({});
|
||||
const selectedProject = ref(null);
|
||||
|
||||
// 프로젝트 선택 변경 시 스토어에 저장
|
||||
const updateSelectedProject = () => {
|
||||
if (!selectedProject.value) return;
|
||||
|
||||
const selected = memberProject.value.find(
|
||||
const selected = projectStore.memberProjectList.find(
|
||||
project => project.PROJCTSEQ === selectedProject.value
|
||||
);
|
||||
|
||||
@ -270,23 +269,6 @@
|
||||
}
|
||||
};
|
||||
|
||||
const getMemberProjects = async () => {
|
||||
const res = await $api.get(`project/${user.value.id}`);
|
||||
memberProject.value = res.data.data;
|
||||
projectStore.projectList = memberProject.value; // 스토어에도 저장
|
||||
|
||||
// 이전에 선택된 프로젝트가 있으면 불러오기
|
||||
const storedProject = projectStore.getSelectedProject();
|
||||
if (storedProject) {
|
||||
selectedProject.value = storedProject.PROJCTSEQ;
|
||||
} else if (memberProject.value.length > 0) {
|
||||
// 없으면 첫 번째 프로젝트 선택
|
||||
selectedProject.value = memberProject.value[0].PROJCTSEQ;
|
||||
updateSelectedProject();
|
||||
}
|
||||
|
||||
console.log(memberProject.value);
|
||||
};
|
||||
|
||||
// const { isDarkMode, switchToDarkMode, switchToLightMode } = useThemeStore();
|
||||
|
||||
@ -300,16 +282,26 @@
|
||||
await userStore.userInfo();
|
||||
user.value = userStore.user;
|
||||
|
||||
await getMemberProjects();
|
||||
await projectStore.getMemberProjects();
|
||||
|
||||
// memberProjectList가 로드된 후 selectedProject 업데이트
|
||||
if (projectStore.selectedProject) {
|
||||
selectedProject.value = projectStore.selectedProject.PROJCTSEQ;
|
||||
} else if (projectStore.memberProjectList.length > 0) {
|
||||
selectedProject.value = projectStore.memberProjectList[0].PROJCTSEQ;
|
||||
projectStore.setSelectedProject(projectStore.memberProjectList[0]);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// projectStore.selectedProject 변경 감지
|
||||
watch(() => projectStore.selectedProject, (newProject) => {
|
||||
if (newProject) {
|
||||
selectedProject.value = newProject.PROJCTSEQ; // select 값 강제 변경
|
||||
selectedProject.value = newProject.PROJCTSEQ;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const handleLogout = async () => {
|
||||
await authStore.logout();
|
||||
router.push('/login');
|
||||
|
||||
@ -8,11 +8,15 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref, watch } from 'vue';
|
||||
import $api from '@api';
|
||||
import { useUserInfoStore } from '@/stores/useUserInfoStore';
|
||||
|
||||
export const useProjectStore = defineStore('project', () => {
|
||||
const projectList = ref([]);
|
||||
const memberProjectList = ref([]);
|
||||
const selectedProject = ref(null);
|
||||
const userStore = useUserInfoStore();
|
||||
|
||||
// 전체 프로젝트 가져오기
|
||||
const getProjectList = async (searchText = '', selectedYear = '', excludeEnded = '') => {
|
||||
const res = await $api.get('project/select', {
|
||||
params: {
|
||||
@ -24,12 +28,25 @@ export const useProjectStore = defineStore('project', () => {
|
||||
projectList.value = res.data.data.projectList;
|
||||
};
|
||||
|
||||
// 사용자가 속한 프로젝트 목록 가져오기
|
||||
const getMemberProjects = async () => {
|
||||
if (!userStore.user) return; // 로그인한 사용자 확인
|
||||
|
||||
const res = await $api.get(`project/${userStore.user.id}`);
|
||||
memberProjectList.value = res.data.data;
|
||||
|
||||
// 로그인 직후 자동으로 프로젝트 선택 (watch와 별개로 직접 처리)
|
||||
if (memberProjectList.value.length > 0 && !selectedProject.value) {
|
||||
setSelectedProject(memberProjectList.value[0]);
|
||||
}
|
||||
};
|
||||
|
||||
const setSelectedProject = (project) => {
|
||||
selectedProject.value = { ...project };
|
||||
|
||||
selectedProject.value = project ? { ...project } : null;
|
||||
if (project) {
|
||||
localStorage.setItem('selectedProject', JSON.stringify(project));
|
||||
} else {
|
||||
localStorage.removeItem('selectedProject');
|
||||
}
|
||||
};
|
||||
|
||||
@ -45,15 +62,38 @@ export const useProjectStore = defineStore('project', () => {
|
||||
|
||||
// 프로젝트 리스트가 변경될 때 자동으로 반응
|
||||
watch(projectList, (newList) => {
|
||||
|
||||
if (!selectedProject.value && newList.length > 0) {
|
||||
setSelectedProject(newList[0]);
|
||||
}
|
||||
});
|
||||
|
||||
watch(memberProjectList, (newList) => {
|
||||
if (newList.length > 0) {
|
||||
// 현재 선택된 프로젝트가 없거나 목록에 없는 경우 첫 번째 항목 선택
|
||||
if (!selectedProject.value) {
|
||||
setSelectedProject(newList[0]);
|
||||
} else {
|
||||
// 선택된 프로젝트가 있는 경우 목록에 있는지 확인
|
||||
const exists = newList.some(project =>
|
||||
project.PROJCTSEQ === selectedProject.value.PROJCTSEQ);
|
||||
|
||||
if (!exists) {
|
||||
setSelectedProject(newList[0]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 목록이 비어있으면 선택된 프로젝트를 null로 설정
|
||||
setSelectedProject(null);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
projectList,
|
||||
selectedProject,
|
||||
getProjectList,
|
||||
memberProjectList,
|
||||
getMemberProjects,
|
||||
setSelectedProject,
|
||||
getSelectedProject
|
||||
};
|
||||
|
||||
@ -3,5 +3,5 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import CommuteCalendar from '@c/commuters/CommuteCalendar.vue';
|
||||
import CommuteCalendar from '@c/commuters/CommuterCalendar.vue';
|
||||
</script>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user