workMainViewPublish

This commit is contained in:
nevermoregb 2025-03-18 12:52:12 +09:00
parent 6613fed587
commit 2be48fe901
3 changed files with 425 additions and 1187 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -0,0 +1,423 @@
<template>
<div class="container-xxl flex-grow-1 container-p-y pb-0">
<div class="card app-calendar-wrapper">
<div class="row g-0">
<div class="col-3 border-end text-center">
<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>
<CommuterBtn
:userId="user.id"
:checkedInProject="checkedInProject || {}"
@workTimeUpdated="handleWorkTimeUpdate"
@leaveTimeUpdated="handleLeaveTimeUpdate"
ref="workTimeComponentRef"
/>
<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">
<full-calendar
ref="fullCalendarRef"
:events="calendarEvents"
:options="calendarOptions"
defaultView="dayGridMonth"
class="flatpickr-calendar-only"
>
</full-calendar>
</div>
</div>
</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>
<script setup>
import FullCalendar from '@fullcalendar/vue3';
import dayGridPlugin from '@fullcalendar/daygrid';
import interactionPlugin from '@fullcalendar/interaction';
import CenterModal from '@c/modal/CenterModal.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';
import { fetchHolidays } from '@c/calendar/holiday';
import { useUserInfoStore } from '@/stores/useUserInfoStore';
import { useProjectStore } from '@/stores/useProjectStore';
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({});
const project = ref({});
const userStore = useUserInfoStore();
const projectStore = useProjectStore();
const dayjs = inject('dayjs');
const fullCalendarRef = ref(null);
const workTimeComponentRef = ref(null);
const calendarEvents = ref([]);
const eventDate = ref('');
const selectedProject = ref(null);
const checkedInProject = ref(null);
//
const isModalOpen = ref(false);
const commuters = ref([]);
const monthlyCommuters = ref([]);
//
const handleWorkTimeUpdate = () => {
todaysCommuter();
loadCommuters();
};
const handleLeaveTimeUpdate = () => {
todaysCommuter();
};
// (ProjectList )
const handleProjectDrop = ({ event, targetProject }) => {
//
const draggedProjectData = JSON.parse(event.dataTransfer.getData('application/json'));
//
if (draggedProjectData.PROJCTSEQ === targetProject.PROJCTSEQ) {
return;
}
//
checkedInProject.value = targetProject;
projectStore.setSelectedProject(targetProject);
// select
selectedProject.value = targetProject.PROJCTSEQ;
$api.patch('commuters/update', {
projctSeq: targetProject.PROJCTSEQ,
memberSeq: user.value.id,
}).then(res => {
if (res.status === 200) {
todaysCommuter();
loadCommuters();
}
});
};
//
const todaysCommuter = async () => {
const res = await $api.get(`commuters/todays`);
if (res.status === 200) {
commuters.value = res.data.data;
}
};
//
const fetchData = async () => {
// FullCalendar API
const calendarApi = fullCalendarRef.value?.getApi();
if (!calendarApi) return;
// ,
const date = calendarApi.currentData.viewTitle;
const dateArr = date.split(' ');
let currentYear = dateArr[0].trim();
let currentMonth = dateArr[1].trim();
const regex = /\D/g;
//
currentYear = parseInt(currentYear.replace(regex, ''), 10);
currentMonth = parseInt(currentMonth.replace(regex, ''), 10);
try {
//
const holidayEvents = await fetchHolidays(currentYear, String(currentMonth).padStart(2, '0'));
//
const existingEvents = calendarEvents.value.filter(event => !event.classNames?.includes('holiday-event'));
//
calendarEvents.value = [...existingEvents, ...holidayEvents];
//
await loadCommuters();
} catch (error) {
console.error('공휴일 정보 로딩 실패:', error);
}
};
// (, , )
const moveCalendar = async (value = 0) => {
const calendarApi = fullCalendarRef.value?.getApi();
if (value === 1) {
calendarApi.prev(); //
} else if (value === 2) {
calendarApi.next(); //
} else if (value === 3) {
calendarApi.today(); //
}
await fetchData();
};
//
const isSelectableDate = date => {
const checkDate = dayjs(date);
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 !isWeekend && !isHoliday;
};
//
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');
} else if (todayElement) {
//
todayElement.classList.add('fc-day-today');
todayElement = null;
}
}
};
// 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 classes = [];
// (, , )
if (!isSelectableDate(cellDate)) {
classes.push('fc-day-sat-sun');
}
return classes;
};
//
const loadCommuters = async () => {
const calendarApi = fullCalendarRef.value?.getApi();
if (!calendarApi) return;
const date = calendarApi.currentData.viewTitle;
const dateArr = date.split(' ');
let currentYear = dateArr[0].trim();
let currentMonth = dateArr[1].trim();
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,
month: currentMonth,
},
});
if (res.status === 200) {
//
monthlyCommuters.value = res.data.data;
document.querySelectorAll('.fc-daygrid-day-events img.rounded-circle').forEach(img => {
img.remove();
});
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}"]`);
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 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';
};
dayEvents.appendChild(profileImg);
}
}
});
}
};
//
const calendarOptions = reactive({
plugins: [dayGridPlugin, interactionPlugin],
initialView: 'dayGridMonth',
headerToolbar: {
left: 'today',
center: 'title',
right: 'prev,next',
},
locale: 'kr',
events: calendarEvents,
eventOrder: 'sortIdx',
contentHeight: 'auto',
// eventContent: calendarCommuter,
//
selectable: true,
selectAllow: selectInfo => isSelectableDate(selectInfo.start),
dateClick: handleDateClick,
dayCellClassNames: getCellClassNames,
//
unselectAuto: true,
droppable: false,
eventDisplay: 'block',
//
customButtons: {
prev: {
text: 'PREV',
click: () => moveCalendar(1),
},
today: {
text: 'TODAY',
click: () => moveCalendar(3),
},
next: {
text: 'NEXT',
click: () => moveCalendar(2),
},
},
});
// ( )
watch(
() => fullCalendarRef.value?.getApi().currentData.viewTitle,
async () => {
await fetchData();
},
);
// selectedProject
watch(
() => projectStore.selectedProject,
newProject => {
if (newProject) {
selectedProject.value = newProject.PROJCTSEQ;
checkedInProject.value = 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();
user.value = userStore.user;
await projectStore.getProjectList('', '', 'true');
project.value = projectStore.projectList;
await todaysCommuter();
//
const storedProject = projectStore.getSelectedProject();
if (storedProject) {
selectedProject.value = storedProject.PROJCTSEQ;
checkedInProject.value = storedProject;
}
});
</script>

File diff suppressed because it is too large Load Diff