출퇴근

This commit is contained in:
yoon 2025-03-13 11:14:38 +09:00
parent 10aaae307e
commit a772a2b4e6
8 changed files with 377 additions and 188 deletions

View 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>

View File

@ -3,57 +3,35 @@
<div class="card app-calendar-wrapper"> <div class="card app-calendar-wrapper">
<div class="row g-0"> <div class="row g-0">
<div class="col-3 border-end text-center"> <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'"/> <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"> <p class="mt-2 fw-bold">
{{ user.name }} {{ user.name }}
</p> </p>
<div class="row g-0"> <CommuterBtn
<div class="col-6 pe-1"> :userId="user.id"
<p class="mb-1">출근시간</p> :checkedInProject="checkedInProject || {}"
<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" > @workTimeUpdated="handleWorkTimeUpdate"
<i v-if="!workTime" class="bx bx-run fs-2"></i> @leaveTimeUpdated="handleLeaveTimeUpdate"
<span v-if="workTime" class="ql-size-12px">{{ workTime }}</span> ref="workTimeComponentRef"
</button> />
</div>
<div class="col-6 ps-1"> <CommuterProjectList
<p class="mb-1">퇴근시간</p> :project="project"
<button class="btn btn-outline-secondary border-3 w-100 py-0 h-px-50" @click="setLeaveTime"> :commuters="commuters"
<i v-if="!leaveTime" class='bx bxs-door-open fs-2'></i> :baseUrl="baseUrl"
<span v-if="leaveTime" class="ql-size-12px">{{ leaveTime }}</span> :user="user"
</button> :selectedProject="selectedProject"
</div> :checkedInProject="checkedInProject"
@drop="handleProjectDrop"
<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>
</div> </div>
</div> </div>
<div class="col app-calendar-content"> <div class="col app-calendar-content">
<div class="card shadow-none border-0"> <div class="card shadow-none border-0">
<div class="card-body pb-0"> <div class="card-body">
<full-calendar <full-calendar
ref="fullCalendarRef" ref="fullCalendarRef"
:events="calendarEvents" :events="calendarEvents"
@ -69,7 +47,31 @@
</div> </div>
</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> </template>
<script setup> <script setup>
@ -77,7 +79,7 @@ import FullCalendar from '@fullcalendar/vue3';
import dayGridPlugin from '@fullcalendar/daygrid'; import dayGridPlugin from '@fullcalendar/daygrid';
import interactionPlugin from '@fullcalendar/interaction'; import interactionPlugin from '@fullcalendar/interaction';
import CenterModal from '@c/modal/CenterModal.vue'; 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 $api from '@api';
import 'flatpickr/dist/flatpickr.min.css'; import 'flatpickr/dist/flatpickr.min.css';
import '@/assets/css/app-calendar.css'; import '@/assets/css/app-calendar.css';
@ -85,6 +87,9 @@ 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 { 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 baseUrl = $api.defaults.baseURL.replace(/api\/$/, '');
const user = ref({}); const user = ref({});
@ -95,43 +100,37 @@ const toastStore = useToastStore();
const dayjs = inject('dayjs'); const dayjs = inject('dayjs');
const fullCalendarRef = ref(null); const fullCalendarRef = ref(null);
const workTimeComponentRef = ref(null);
const calendarEvents = ref([]); const calendarEvents = ref([]);
const eventDate = ref(''); const eventDate = ref('');
const workTime = ref(null);
const leaveTime = ref(null);
const selectedProject = ref(null); const selectedProject = ref(null);
const checkedInProject = ref(null); const checkedInProject = ref(null);
const draggedProject = ref(null); //
const isModalOpen = ref(false);
const commuters = ref([]); const commuters = ref([]);
const monthlyCommuters = ref([]);
// //
const isCurrentUser = (commuter) => { const handleWorkTimeUpdate = () => {
return user.value && commuter && commuter.MEMBERSEQ === user.value.id; todaysCommuter();
loadCommuters();
}; };
// const handleLeaveTimeUpdate = () => {
const dragStart = (event, project) => { todaysCommuter();
draggedProject.value = project;
//
event.dataTransfer.setData('application/json', JSON.stringify(project));
event.dataTransfer.effectAllowed = 'copy';
}; };
// // (ProjectList )
const allowDrop = (event) => { const handleProjectDrop = ({ event, targetProject }) => {
event.preventDefault(); //
}; const draggedProjectData = JSON.parse(event.dataTransfer.getData('application/json'));
//
const handleDrop = (event, targetProject) => {
event.preventDefault();
// //
if (draggedProject.value.PROJCTSEQ === targetProject.PROJCTSEQ) { if (draggedProjectData.PROJCTSEQ === targetProject.PROJCTSEQ) {
return; 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 todaysCommuter = async () => {
const res = await $api.get(`commuters/todays`); 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 () => { const fetchData = async () => {
// FullCalendar API // FullCalendar API
@ -326,20 +239,31 @@ const isSelectableDate = (date) => {
// //
let todayElement = null; let todayElement = null;
const handleDateClick = (info) => { 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)) { if (isSelectableDate(info.date)) {
const isToday = dayjs(info.date).isSame(dayjs(), 'day'); const isToday = dayjs(info.date).isSame(dayjs(), 'day');
if (isToday) { if (isToday) {
// //
todayElement = info.dayEl; todayElement = info.dayEl;
todayElement.classList.remove('fc-day-today'); todayElement.classList.remove('fc-day-today');
} else if (todayElement) { } else if (todayElement) {
// //
todayElement.classList.add('fc-day-today'); todayElement.classList.add('fc-day-today');
todayElement = null; todayElement = null;
} }
eventDate.value = dayjs(info.date).format('YYYY-MM-DD');
} }
}; };
@ -364,6 +288,7 @@ const getCellClassNames = (arg) => {
return classes; return classes;
}; };
//
const loadCommuters = async () => { const loadCommuters = async () => {
const calendarApi = fullCalendarRef.value?.getApi(); const calendarApi = fullCalendarRef.value?.getApi();
if (!calendarApi) return; if (!calendarApi) return;
@ -374,6 +299,7 @@ const loadCommuters = async () => {
const regex = /\D/g; const regex = /\D/g;
currentYear = parseInt(currentYear.replace(regex, ''), 10); currentYear = parseInt(currentYear.replace(regex, ''), 10);
currentMonth = parseInt(currentMonth.replace(regex, ''), 10); currentMonth = parseInt(currentMonth.replace(regex, ''), 10);
const res = await $api.get('commuters/month', { const res = await $api.get('commuters/month', {
params: { params: {
year: currentYear, year: currentYear,
@ -381,13 +307,14 @@ const loadCommuters = async () => {
} }
}); });
if (res.status === 200) { 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 => { document.querySelectorAll('.fc-daygrid-day-events img.rounded-circle').forEach(img => {
img.remove(); img.remove();
}); });
commuters.forEach(commuter => { monthlyCommuters.value.forEach(commuter => {
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}"]`);
@ -395,7 +322,6 @@ const loadCommuters = async () => {
if (dateCell) { if (dateCell) {
const dayEvents = dateCell.querySelector('.fc-daygrid-day-events'); const dayEvents = dateCell.querySelector('.fc-daygrid-day-events');
if (dayEvents) { if (dayEvents) {
// //
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}`;
@ -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 () => { onMounted(async () => {
await fetchData(); await fetchData();
await userStore.userInfo(); await userStore.userInfo();
@ -474,14 +416,13 @@ onMounted(async () => {
await projectStore.getProjectList('', '', 'true'); await projectStore.getProjectList('', '', 'true');
project.value = projectStore.projectList; project.value = projectStore.projectList;
await todayCommuterInfo();
await todaysCommuter(); await todaysCommuter();
// //
const storedProject = projectStore.getSelectedProject(); const storedProject = projectStore.getSelectedProject();
if (storedProject) { if (storedProject) {
selectedProject.value = storedProject; selectedProject.value = storedProject.PROJCTSEQ;
checkedInProject.value = storedProject;
} }
}); });
</script> </script>

View 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>

View File

@ -177,10 +177,12 @@ import ArrInput from '@c/input/ArrInput.vue';
import { useToastStore } from '@s/toastStore'; import { useToastStore } from '@s/toastStore';
import { useUserInfoStore } from '@/stores/useUserInfoStore'; import { useUserInfoStore } from '@/stores/useUserInfoStore';
import commonApi from '@/common/commonApi'; import commonApi from '@/common/commonApi';
import { useProjectStore } from '@/stores/useProjectStore';
// //
const toastStore = useToastStore(); const toastStore = useToastStore();
const userStore = useUserInfoStore(); const userStore = useUserInfoStore();
const projectStore = useProjectStore();
// Props // Props
const props = defineProps({ const props = defineProps({
@ -448,7 +450,8 @@ const handleDelete = () => {
.then(res => { .then(res => {
if (res.status === 200) { if (res.status === 200) {
toastStore.onToast('삭제가 완료되었습니다.', 's'); toastStore.onToast('삭제가 완료되었습니다.', 's');
location.reload() projectStore.getProjectList();
projectStore.getMemberProjects();
} }
}) })
}; };

View File

@ -247,6 +247,8 @@
toastStore.onToast('프로젝트가 등록되었습니다.', 's'); toastStore.onToast('프로젝트가 등록되었습니다.', 's');
closeCreateModal(); closeCreateModal();
getProjectList(); getProjectList();
projectStore.getMemberProjects();
formReset();
} }
}); });
}; };

View File

@ -9,7 +9,7 @@
<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 memberProject" :key="item.PROJCTSEQ" :value="item.PROJCTSEQ"> <option v-for="item in projectStore.memberProjectList" :key="item.PROJCTSEQ" :value="item.PROJCTSEQ">
{{ item.PROJCTNAM }} {{ item.PROJCTNAM }}
</option> </option>
</select> </select>
@ -254,14 +254,13 @@
const router = useRouter(); const router = useRouter();
const user = ref(null); const user = ref(null);
const memberProject = ref({});
const selectedProject = ref(null); const selectedProject = ref(null);
// //
const updateSelectedProject = () => { const updateSelectedProject = () => {
if (!selectedProject.value) return; if (!selectedProject.value) return;
const selected = memberProject.value.find( const selected = projectStore.memberProjectList.find(
project => project.PROJCTSEQ === selectedProject.value 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(); // const { isDarkMode, switchToDarkMode, switchToLightMode } = useThemeStore();
@ -300,16 +282,26 @@
await userStore.userInfo(); await userStore.userInfo();
user.value = userStore.user; 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) => { watch(() => projectStore.selectedProject, (newProject) => {
if (newProject) { if (newProject) {
selectedProject.value = newProject.PROJCTSEQ; // select selectedProject.value = newProject.PROJCTSEQ;
} }
}); });
const handleLogout = async () => { const handleLogout = async () => {
await authStore.logout(); await authStore.logout();
router.push('/login'); router.push('/login');

View File

@ -8,11 +8,15 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
import $api from '@api'; import $api from '@api';
import { useUserInfoStore } from '@/stores/useUserInfoStore';
export const useProjectStore = defineStore('project', () => { export const useProjectStore = defineStore('project', () => {
const projectList = ref([]); const projectList = ref([]);
const memberProjectList = ref([]);
const selectedProject = ref(null); const selectedProject = ref(null);
const userStore = useUserInfoStore();
// 전체 프로젝트 가져오기
const getProjectList = async (searchText = '', selectedYear = '', excludeEnded = '') => { const getProjectList = async (searchText = '', selectedYear = '', excludeEnded = '') => {
const res = await $api.get('project/select', { const res = await $api.get('project/select', {
params: { params: {
@ -24,12 +28,25 @@ export const useProjectStore = defineStore('project', () => {
projectList.value = res.data.data.projectList; 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) => { const setSelectedProject = (project) => {
selectedProject.value = { ...project }; selectedProject.value = project ? { ...project } : null;
if (project) { if (project) {
localStorage.setItem('selectedProject', JSON.stringify(project)); localStorage.setItem('selectedProject', JSON.stringify(project));
} else {
localStorage.removeItem('selectedProject');
} }
}; };
@ -45,15 +62,38 @@ export const useProjectStore = defineStore('project', () => {
// 프로젝트 리스트가 변경될 때 자동으로 반응 // 프로젝트 리스트가 변경될 때 자동으로 반응
watch(projectList, (newList) => { watch(projectList, (newList) => {
if (!selectedProject.value && newList.length > 0) { if (!selectedProject.value && newList.length > 0) {
setSelectedProject(newList[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 { return {
projectList, projectList,
selectedProject, selectedProject,
getProjectList, getProjectList,
memberProjectList,
getMemberProjects,
setSelectedProject, setSelectedProject,
getSelectedProject getSelectedProject
}; };

View File

@ -3,5 +3,5 @@
</template> </template>
<script setup> <script setup>
import CommuteCalendar from '@c/commuters/CommuteCalendar.vue'; import CommuteCalendar from '@c/commuters/CommuterCalendar.vue';
</script> </script>