출퇴근
This commit is contained in:
parent
2ec81f274d
commit
1eba161060
@ -1,7 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="row g-0">
|
<div class="row g-0">
|
||||||
<div class="col-6 pe-1">
|
<div class="col-6 pe-1">
|
||||||
<p class="mb-1">출근시간</p>
|
|
||||||
<button
|
<button
|
||||||
class="btn border-3 w-100 py-0 h-px-50"
|
class="btn border-3 w-100 py-0 h-px-50"
|
||||||
:class="workTime ? 'p-0 btn-primary pe-none' : 'btn-outline-primary'"
|
:class="workTime ? 'p-0 btn-primary pe-none' : 'btn-outline-primary'"
|
||||||
@ -13,7 +12,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-6 ps-1">
|
<div class="col-6 ps-1">
|
||||||
<p class="mb-1">퇴근시간</p>
|
|
||||||
<button
|
<button
|
||||||
class="btn btn-outline-secondary border-3 w-100 py-0 h-px-50"
|
class="btn btn-outline-secondary border-3 w-100 py-0 h-px-50"
|
||||||
@click="setLeaveTime"
|
@click="setLeaveTime"
|
||||||
@ -29,7 +27,6 @@
|
|||||||
import { ref, defineProps, defineEmits, onMounted, watch } from 'vue';
|
import { ref, defineProps, defineEmits, onMounted, watch } from 'vue';
|
||||||
import $api from '@api';
|
import $api from '@api';
|
||||||
import { useGeolocation } from '@vueuse/core';
|
import { useGeolocation } from '@vueuse/core';
|
||||||
import { useToastStore } from '@/stores/toastStore';
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
userId: {
|
userId: {
|
||||||
@ -47,7 +44,6 @@ const emit = defineEmits(['workTimeUpdated', 'leaveTimeUpdated']);
|
|||||||
const workTime = ref(null);
|
const workTime = ref(null);
|
||||||
const leaveTime = ref(null)
|
const leaveTime = ref(null)
|
||||||
const userLocation = ref(null);
|
const userLocation = ref(null);
|
||||||
const toastStore = useToastStore();
|
|
||||||
|
|
||||||
// 위치 정보 가져오기 설정
|
// 위치 정보 가져오기 설정
|
||||||
const { coords, isSupported, error } = useGeolocation({
|
const { coords, isSupported, error } = useGeolocation({
|
||||||
@ -74,12 +70,12 @@ const getAddress = (lat, lng) => {
|
|||||||
// 위치 정보 가져오기 함수
|
// 위치 정보 가져오기 함수
|
||||||
const getLocation = async () => {
|
const getLocation = async () => {
|
||||||
if (!isSupported.value) {
|
if (!isSupported.value) {
|
||||||
toastStore.onToast('브라우저가 위치 정보를 지원하지 않습니다.', 'e');
|
alert('브라우저가 위치 정보를 지원하지 않습니다.');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error.value) {
|
if (error.value) {
|
||||||
toastStore.onToast(`위치 정보를 가져오는데 실패했습니다: ${error.value.message}`, 'e');
|
alert(`위치 정보를 가져오는데 실패했습니다: ${error.value.message}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +89,7 @@ const getLocation = async () => {
|
|||||||
const address = await getAddress(coords.value.latitude, coords.value.longitude);
|
const address = await getAddress(coords.value.latitude, coords.value.longitude);
|
||||||
return address;
|
return address;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toastStore.onToast(error, 'e');
|
alert(error);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -135,7 +131,6 @@ const setWorkTime = async () => {
|
|||||||
commutArr: address,
|
commutArr: address,
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
toastStore.onToast('출근 완료.', 's');
|
|
||||||
todayCommuterInfo();
|
todayCommuterInfo();
|
||||||
|
|
||||||
emit('workTimeUpdated', true);
|
emit('workTimeUpdated', true);
|
||||||
@ -150,11 +145,6 @@ const setLeaveTime = () => {
|
|||||||
commutLve: leaveTime.value || null,
|
commutLve: leaveTime.value || null,
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
if (leaveTime.value) {
|
|
||||||
toastStore.onToast('퇴근 시간 초기화', 'e');
|
|
||||||
} else {
|
|
||||||
toastStore.onToast('퇴근 완료', 's');
|
|
||||||
}
|
|
||||||
todayCommuterInfo();
|
todayCommuterInfo();
|
||||||
// 부모 컴포넌트에 업데이트 이벤트 발생
|
// 부모 컴포넌트에 업데이트 이벤트 발생
|
||||||
emit('leaveTimeUpdated', true);
|
emit('leaveTimeUpdated', true);
|
||||||
|
|||||||
@ -86,7 +86,6 @@ import '@/assets/css/app-calendar.css';
|
|||||||
import { fetchHolidays } from '@c/calendar/holiday';
|
import { fetchHolidays } from '@c/calendar/holiday';
|
||||||
import { useUserInfoStore } from '@/stores/useUserInfoStore';
|
import { useUserInfoStore } from '@/stores/useUserInfoStore';
|
||||||
import { useProjectStore } from '@/stores/useProjectStore';
|
import { useProjectStore } from '@/stores/useProjectStore';
|
||||||
import { useToastStore } from '@/stores/toastStore';
|
|
||||||
import CommuterBtn from '@c/commuters/CommuterBtn.vue';
|
import CommuterBtn from '@c/commuters/CommuterBtn.vue';
|
||||||
import CommuterProjectList from '@c/commuters/CommuterProjectList.vue';
|
import CommuterProjectList from '@c/commuters/CommuterProjectList.vue';
|
||||||
import BackBtn from '@c/button/BackBtn.vue';
|
import BackBtn from '@c/button/BackBtn.vue';
|
||||||
@ -96,7 +95,6 @@ const user = ref({});
|
|||||||
const project = ref({});
|
const project = ref({});
|
||||||
const userStore = useUserInfoStore();
|
const userStore = useUserInfoStore();
|
||||||
const projectStore = useProjectStore();
|
const projectStore = useProjectStore();
|
||||||
const toastStore = useToastStore();
|
|
||||||
|
|
||||||
const dayjs = inject('dayjs');
|
const dayjs = inject('dayjs');
|
||||||
const fullCalendarRef = ref(null);
|
const fullCalendarRef = ref(null);
|
||||||
@ -134,19 +132,6 @@ const handleProjectDrop = ({ event, targetProject }) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// select값에 드롭한 프로젝트가 없는 경우
|
|
||||||
if (!selectedProject.value) {
|
|
||||||
$api.patch('project/updateYon', {
|
|
||||||
projctSeq: targetProject.PROJCTSEQ,
|
|
||||||
memberSeq: user.value.id,
|
|
||||||
projctYon: '1'
|
|
||||||
}).then(res => {
|
|
||||||
if (res.status === 200) {
|
|
||||||
projectStore.getProjectList();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 선택된 프로젝트 변경
|
// 선택된 프로젝트 변경
|
||||||
checkedInProject.value = targetProject;
|
checkedInProject.value = targetProject;
|
||||||
projectStore.setSelectedProject(targetProject);
|
projectStore.setSelectedProject(targetProject);
|
||||||
@ -159,8 +144,6 @@ const handleProjectDrop = ({ event, targetProject }) => {
|
|||||||
memberSeq: user.value.id,
|
memberSeq: user.value.id,
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
toastStore.onToast('출근 프로젝트가 변경 되었습니다.', 's');
|
|
||||||
|
|
||||||
todaysCommuter();
|
todaysCommuter();
|
||||||
loadCommuters();
|
loadCommuters();
|
||||||
}
|
}
|
||||||
@ -318,14 +301,14 @@ const loadCommuters = async () => {
|
|||||||
const date = commuter.COMMUTDAY;
|
const date = commuter.COMMUTDAY;
|
||||||
const dateCell = document.querySelector(`.fc-day[data-date="${date}"]`) ||
|
const dateCell = document.querySelector(`.fc-day[data-date="${date}"]`) ||
|
||||||
document.querySelector(`.fc-daygrid-day[data-date="${date}"]`);
|
document.querySelector(`.fc-daygrid-day[data-date="${date}"]`);
|
||||||
|
|
||||||
if (dateCell) {
|
if (dateCell) {
|
||||||
const dayEvents = dateCell.querySelector('.fc-daygrid-day-events');
|
const dayEvents = dateCell.querySelector('.fc-daygrid-day-events');
|
||||||
if (dayEvents) {
|
if (dayEvents) {
|
||||||
|
dayEvents.classList.add('text-center');
|
||||||
// 프로필 이미지 생성
|
// 프로필 이미지 생성
|
||||||
const profileImg = document.createElement('img');
|
const profileImg = document.createElement('img');
|
||||||
profileImg.src = `${baseUrl}upload/img/profile/${commuter.profile}`;
|
profileImg.src = `${baseUrl}upload/img/profile/${commuter.profile}`;
|
||||||
profileImg.className = 'rounded-circle w-px-20 h-px-20 ms-1 position-relative z-5';
|
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.style.border = `2px solid ${commuter.projctcolor}`;
|
||||||
profileImg.onerror = () => { profileImg.src = '/img/icons/icon.png'; };
|
profileImg.onerror = () => { profileImg.src = '/img/icons/icon.png'; };
|
||||||
|
|
||||||
|
|||||||
@ -28,6 +28,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted, ref, nextTick, computed } from 'vue';
|
import { onMounted, ref, nextTick, computed } from 'vue';
|
||||||
import { useUserStore } from '@s/userList';
|
import { useUserStore } from '@s/userList';
|
||||||
|
import { useProjectStore } from '@s/useProjectStore';
|
||||||
import $api from '@api';
|
import $api from '@api';
|
||||||
|
|
||||||
const emit = defineEmits(['user-list-update']);
|
const emit = defineEmits(['user-list-update']);
|
||||||
@ -106,7 +107,7 @@ const isUserDisabled = (user) => {
|
|||||||
// 클릭 시 활성화/비활성화 및 DB 업데이트
|
// 클릭 시 활성화/비활성화 및 DB 업데이트
|
||||||
// showOnlyActive가 true일 때는 toggleDisable 함수가 실행되지 않음
|
// showOnlyActive가 true일 때는 toggleDisable 함수가 실행되지 않음
|
||||||
const toggleDisable = async (index) => {
|
const toggleDisable = async (index) => {
|
||||||
if (props.showOnlyActive) return; // showOnlyActive가 true이면 함수 실행 중지
|
if (props.showOnlyActive) return;
|
||||||
|
|
||||||
const user = displayedUserList.value[index];
|
const user = displayedUserList.value[index];
|
||||||
if (user) {
|
if (user) {
|
||||||
@ -125,6 +126,11 @@ const toggleDisable = async (index) => {
|
|||||||
if (originalIndex !== -1) {
|
if (originalIndex !== -1) {
|
||||||
userList.value[originalIndex].PROJCTYON = newParticipationStatus ? '0' : '1';
|
userList.value[originalIndex].PROJCTYON = newParticipationStatus ? '0' : '1';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 변경 후 프로젝트 목록 새로고침
|
||||||
|
const projectStore = useProjectStore();
|
||||||
|
await projectStore.getProjectList('', '', 'true');
|
||||||
|
await projectStore.getMemberProjects();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 원래 userList에서 해당 사용자를 찾아 업데이트
|
// 원래 userList에서 해당 사용자를 찾아 업데이트
|
||||||
|
|||||||
@ -9,7 +9,12 @@
|
|||||||
<div class="navbar-nav-right d-flex align-items-center" id="navbar-collapse">
|
<div class="navbar-nav-right d-flex align-items-center" id="navbar-collapse">
|
||||||
<ul class="navbar-nav flex-row align-items-center ms-auto">
|
<ul class="navbar-nav flex-row align-items-center ms-auto">
|
||||||
<select class="form-select py-1" id="name" v-model="selectedProject" @change="updateSelectedProject">
|
<select class="form-select py-1" id="name" v-model="selectedProject" @change="updateSelectedProject">
|
||||||
<option v-for="item in projectStore.memberProjectList" :key="item.PROJCTSEQ" :value="item.PROJCTSEQ">
|
<!-- 내가 참여하고 있는 프로젝트 그룹 -->
|
||||||
|
<option v-for="item in myProjects" :key="item.PROJCTSEQ" :value="item.PROJCTSEQ">
|
||||||
|
{{ item.PROJCTNAM }}
|
||||||
|
</option>
|
||||||
|
<!-- 전체 프로젝트 그룹 -->
|
||||||
|
<option v-for="item in otherProjects" :key="item.PROJCTSEQ" :value="item.PROJCTSEQ">
|
||||||
{{ item.PROJCTNAM }}
|
{{ item.PROJCTNAM }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
@ -243,7 +248,7 @@
|
|||||||
import { useProjectStore } from '@/stores/useProjectStore';
|
import { useProjectStore } from '@/stores/useProjectStore';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { useThemeStore } from '@s/darkmode';
|
import { useThemeStore } from '@s/darkmode';
|
||||||
import { onMounted, ref, watch } from 'vue';
|
import { computed, onMounted, ref, watch } from 'vue';
|
||||||
import $api from '@api';
|
import $api from '@api';
|
||||||
|
|
||||||
const baseUrl = import.meta.env.VITE_SERVER;
|
const baseUrl = import.meta.env.VITE_SERVER;
|
||||||
@ -256,11 +261,28 @@
|
|||||||
const user = ref(null);
|
const user = ref(null);
|
||||||
const selectedProject = ref(null);
|
const selectedProject = ref(null);
|
||||||
|
|
||||||
|
// 내가 참여하고 있는 프로젝트 목록
|
||||||
|
const myProjects = computed(() => {
|
||||||
|
return projectStore.memberProjectList || [];
|
||||||
|
});
|
||||||
|
|
||||||
|
// 내가 참여하고 있지 않은 프로젝트 목록
|
||||||
|
const otherProjects = computed(() => {
|
||||||
|
if (!projectStore.projectList || !projectStore.memberProjectList) return [];
|
||||||
|
|
||||||
|
// 내 프로젝트 ID 목록
|
||||||
|
const myProjectIds = projectStore.memberProjectList.map(p => p.PROJCTSEQ);
|
||||||
|
|
||||||
|
// 내 프로젝트가 아닌 프로젝트만 필터링
|
||||||
|
return projectStore.projectList.filter(p => !myProjectIds.includes(p.PROJCTSEQ));
|
||||||
|
});
|
||||||
|
|
||||||
// 프로젝트 선택 변경 시 스토어에 저장
|
// 프로젝트 선택 변경 시 스토어에 저장
|
||||||
const updateSelectedProject = () => {
|
const updateSelectedProject = () => {
|
||||||
if (!selectedProject.value) return;
|
if (!selectedProject.value) return;
|
||||||
|
|
||||||
const selected = projectStore.memberProjectList.find(
|
// 전체 프로젝트 리스트에서 선택된 프로젝트 찾기
|
||||||
|
const selected = projectStore.projectList.find(
|
||||||
project => project.PROJCTSEQ === selectedProject.value
|
project => project.PROJCTSEQ === selectedProject.value
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -269,6 +291,13 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 선택된 프로젝트 변경 감지
|
||||||
|
watch(() => projectStore.selectedProject, (newProject) => {
|
||||||
|
if (newProject) {
|
||||||
|
selectedProject.value = newProject.PROJCTSEQ;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// const { isDarkMode, switchToDarkMode, switchToLightMode } = useThemeStore();
|
// const { isDarkMode, switchToDarkMode, switchToLightMode } = useThemeStore();
|
||||||
|
|
||||||
@ -282,25 +311,23 @@
|
|||||||
await userStore.userInfo();
|
await userStore.userInfo();
|
||||||
user.value = userStore.user;
|
user.value = userStore.user;
|
||||||
|
|
||||||
|
await projectStore.getProjectList('', '', 'true');
|
||||||
|
|
||||||
|
// 사용자가 참여하고 있는 프로젝트 목록
|
||||||
await projectStore.getMemberProjects();
|
await projectStore.getMemberProjects();
|
||||||
|
|
||||||
// memberProjectList가 로드된 후 selectedProject 업데이트
|
// 저장된 선택 프로젝트
|
||||||
if (projectStore.selectedProject) {
|
const storedProject = projectStore.getSelectedProject();
|
||||||
selectedProject.value = projectStore.selectedProject.PROJCTSEQ;
|
if (storedProject) {
|
||||||
|
selectedProject.value = storedProject.PROJCTSEQ;
|
||||||
} else if (projectStore.memberProjectList.length > 0) {
|
} else if (projectStore.memberProjectList.length > 0) {
|
||||||
|
// 저장된 선택 프로젝트가 없으면 첫 번째 참여 프로젝트 선택
|
||||||
selectedProject.value = projectStore.memberProjectList[0].PROJCTSEQ;
|
selectedProject.value = projectStore.memberProjectList[0].PROJCTSEQ;
|
||||||
projectStore.setSelectedProject(projectStore.memberProjectList[0]);
|
projectStore.setSelectedProject(projectStore.memberProjectList[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// projectStore.selectedProject 변경 감지
|
|
||||||
watch(() => projectStore.selectedProject, (newProject) => {
|
|
||||||
if (newProject) {
|
|
||||||
selectedProject.value = newProject.PROJCTSEQ;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
await authStore.logout();
|
await authStore.logout();
|
||||||
|
|||||||
@ -35,7 +35,6 @@ export const useProjectStore = defineStore('project', () => {
|
|||||||
const res = await $api.get(`project/${userStore.user.id}`);
|
const res = await $api.get(`project/${userStore.user.id}`);
|
||||||
memberProjectList.value = res.data.data;
|
memberProjectList.value = res.data.data;
|
||||||
|
|
||||||
// 로그인 직후 자동으로 프로젝트 선택 (watch와 별개로 직접 처리)
|
|
||||||
if (memberProjectList.value.length > 0 && !selectedProject.value) {
|
if (memberProjectList.value.length > 0 && !selectedProject.value) {
|
||||||
setSelectedProject(memberProjectList.value[0]);
|
setSelectedProject(memberProjectList.value[0]);
|
||||||
}
|
}
|
||||||
@ -62,32 +61,34 @@ export const useProjectStore = defineStore('project', () => {
|
|||||||
|
|
||||||
// 프로젝트 리스트가 변경될 때 자동으로 반응
|
// 프로젝트 리스트가 변경될 때 자동으로 반응
|
||||||
watch(projectList, (newList) => {
|
watch(projectList, (newList) => {
|
||||||
|
// 선택된 프로젝트가 없고 목록이 있는 경우
|
||||||
if (!selectedProject.value && newList.length > 0) {
|
if (!selectedProject.value && newList.length > 0) {
|
||||||
|
// 사용자가 속한 프로젝트가 있는지 먼저 확인
|
||||||
|
if (memberProjectList.value.length > 0) {
|
||||||
|
setSelectedProject(memberProjectList.value[0]);
|
||||||
|
} else {
|
||||||
setSelectedProject(newList[0]);
|
setSelectedProject(newList[0]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(memberProjectList, (newList) => {
|
watch(memberProjectList, (newList) => {
|
||||||
if (newList.length > 0) {
|
if (newList.length > 0) {
|
||||||
// 현재 선택된 프로젝트가 없거나 목록에 없는 경우 첫 번째 항목 선택
|
// 현재 선택된 프로젝트가 없는 경우 첫 번째 항목 선택
|
||||||
if (!selectedProject.value) {
|
if (!selectedProject.value) {
|
||||||
setSelectedProject(newList[0]);
|
setSelectedProject(newList[0]);
|
||||||
} else {
|
} else {
|
||||||
// 선택된 프로젝트가 있는 경우 목록에 있는지 확인
|
// 선택된 프로젝트가 있는 경우 목록에 있는지 확인
|
||||||
const exists = newList.some(project =>
|
const exists = newList.some(project => project.PROJCTSEQ === selectedProject.value.PROJCTSEQ);
|
||||||
project.PROJCTSEQ === selectedProject.value.PROJCTSEQ);
|
|
||||||
|
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
setSelectedProject(newList[0]);
|
setSelectedProject(newList[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// 목록이 비어있으면 선택된 프로젝트를 null로 설정
|
|
||||||
setSelectedProject(null);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
projectList,
|
projectList,
|
||||||
selectedProject,
|
selectedProject,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user