출퇴근

This commit is contained in:
yoon 2025-03-14 10:03:34 +09:00
parent 2ec81f274d
commit 1eba161060
5 changed files with 64 additions and 57 deletions

View File

@ -1,7 +1,6 @@
<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'"
@ -13,7 +12,6 @@
</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"
@ -29,7 +27,6 @@
import { ref, defineProps, defineEmits, onMounted, watch } from 'vue';
import $api from '@api';
import { useGeolocation } from '@vueuse/core';
import { useToastStore } from '@/stores/toastStore';
const props = defineProps({
userId: {
@ -47,7 +44,6 @@ const emit = defineEmits(['workTimeUpdated', 'leaveTimeUpdated']);
const workTime = ref(null);
const leaveTime = ref(null)
const userLocation = ref(null);
const toastStore = useToastStore();
//
const { coords, isSupported, error } = useGeolocation({
@ -74,12 +70,12 @@ const getAddress = (lat, lng) => {
//
const getLocation = async () => {
if (!isSupported.value) {
toastStore.onToast('브라우저가 위치 정보를 지원하지 않습니다.', 'e');
alert('브라우저가 위치 정보를 지원하지 않습니다.');
return null;
}
if (error.value) {
toastStore.onToast(`위치 정보를 가져오는데 실패했습니다: ${error.value.message}`, 'e');
alert(`위치 정보를 가져오는데 실패했습니다: ${error.value.message}`);
return null;
}
@ -93,7 +89,7 @@ const getLocation = async () => {
const address = await getAddress(coords.value.latitude, coords.value.longitude);
return address;
} catch (error) {
toastStore.onToast(error, 'e');
alert(error);
return null;
}
}
@ -135,7 +131,6 @@ const setWorkTime = async () => {
commutArr: address,
}).then(res => {
if (res.status === 200) {
toastStore.onToast('출근 완료.', 's');
todayCommuterInfo();
emit('workTimeUpdated', true);
@ -150,11 +145,6 @@ const setLeaveTime = () => {
commutLve: leaveTime.value || null,
}).then(res => {
if (res.status === 200) {
if (leaveTime.value) {
toastStore.onToast('퇴근 시간 초기화', 'e');
} else {
toastStore.onToast('퇴근 완료', 's');
}
todayCommuterInfo();
//
emit('leaveTimeUpdated', true);

View File

@ -86,7 +86,6 @@ import '@/assets/css/app-calendar.css';
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';
@ -96,7 +95,6 @@ const user = ref({});
const project = ref({});
const userStore = useUserInfoStore();
const projectStore = useProjectStore();
const toastStore = useToastStore();
const dayjs = inject('dayjs');
const fullCalendarRef = ref(null);
@ -134,19 +132,6 @@ const handleProjectDrop = ({ event, targetProject }) => {
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;
projectStore.setSelectedProject(targetProject);
@ -159,8 +144,6 @@ const handleProjectDrop = ({ event, targetProject }) => {
memberSeq: user.value.id,
}).then(res => {
if (res.status === 200) {
toastStore.onToast('출근 프로젝트가 변경 되었습니다.', 's');
todaysCommuter();
loadCommuters();
}
@ -317,15 +300,15 @@ const loadCommuters = async () => {
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}"]`);
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 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.onerror = () => { profileImg.src = '/img/icons/icon.png'; };

View File

@ -28,6 +28,7 @@
<script setup>
import { onMounted, ref, nextTick, computed } from 'vue';
import { useUserStore } from '@s/userList';
import { useProjectStore } from '@s/useProjectStore';
import $api from '@api';
const emit = defineEmits(['user-list-update']);
@ -106,7 +107,7 @@ const isUserDisabled = (user) => {
// / DB
// showOnlyActive true toggleDisable
const toggleDisable = async (index) => {
if (props.showOnlyActive) return; // showOnlyActive true
if (props.showOnlyActive) return;
const user = displayedUserList.value[index];
if (user) {
@ -125,6 +126,11 @@ const toggleDisable = async (index) => {
if (originalIndex !== -1) {
userList.value[originalIndex].PROJCTYON = newParticipationStatus ? '0' : '1';
}
//
const projectStore = useProjectStore();
await projectStore.getProjectList('', '', 'true');
await projectStore.getMemberProjects();
}
} else {
// userList

View File

@ -9,8 +9,13 @@
<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 projectStore.memberProjectList" :key="item.PROJCTSEQ" :value="item.PROJCTSEQ">
{{ item.PROJCTNAM }}
<!-- 내가 참여하고 있는 프로젝트 그룹 -->
<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 }}
</option>
</select>
@ -243,7 +248,7 @@
import { useProjectStore } from '@/stores/useProjectStore';
import { useRouter } from 'vue-router';
import { useThemeStore } from '@s/darkmode';
import { onMounted, ref, watch } from 'vue';
import { computed, onMounted, ref, watch } from 'vue';
import $api from '@api';
const baseUrl = import.meta.env.VITE_SERVER;
@ -256,11 +261,28 @@
const user = 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 = () => {
if (!selectedProject.value) return;
const selected = projectStore.memberProjectList.find(
//
const selected = projectStore.projectList.find(
project => project.PROJCTSEQ === selectedProject.value
);
@ -269,6 +291,13 @@
}
};
//
watch(() => projectStore.selectedProject, (newProject) => {
if (newProject) {
selectedProject.value = newProject.PROJCTSEQ;
}
});
// const { isDarkMode, switchToDarkMode, switchToLightMode } = useThemeStore();
@ -282,25 +311,23 @@
await userStore.userInfo();
user.value = userStore.user;
await projectStore.getProjectList('', '', 'true');
//
await projectStore.getMemberProjects();
// memberProjectList selectedProject
if (projectStore.selectedProject) {
selectedProject.value = projectStore.selectedProject.PROJCTSEQ;
//
const storedProject = projectStore.getSelectedProject();
if (storedProject) {
selectedProject.value = storedProject.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;
}
});
const handleLogout = async () => {
await authStore.logout();

View File

@ -35,7 +35,6 @@ export const useProjectStore = defineStore('project', () => {
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]);
}
@ -62,32 +61,34 @@ export const useProjectStore = defineStore('project', () => {
// 프로젝트 리스트가 변경될 때 자동으로 반응
watch(projectList, (newList) => {
// 선택된 프로젝트가 없고 목록이 있는 경우
if (!selectedProject.value && newList.length > 0) {
setSelectedProject(newList[0]);
// 사용자가 속한 프로젝트가 있는지 먼저 확인
if (memberProjectList.value.length > 0) {
setSelectedProject(memberProjectList.value[0]);
} else {
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);
const exists = newList.some(project => project.PROJCTSEQ === selectedProject.value.PROJCTSEQ);
if (!exists) {
setSelectedProject(newList[0]);
}
}
} else {
// 목록이 비어있으면 선택된 프로젝트를 null로 설정
setSelectedProject(null);
}
});
return {
projectList,
selectedProject,