출퇴근 현황 추가
This commit is contained in:
parent
8b45d5f048
commit
2aa2c97ae5
202
src/components/commuters/Calendar.vue
Normal file
202
src/components/commuters/Calendar.vue
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container-xxl flex-grow-1 container-p-y">
|
||||||
|
<div class="card app-calendar-wrapper">
|
||||||
|
<div class="row g-0">
|
||||||
|
<div class="col app-calendar-sidebar border-end text-center" id="app-calendar-sidebar">
|
||||||
|
<div class="card-body pb-0">
|
||||||
|
<img v-if="user" :src="`http://localhost:10325/upload/img/profile/${user.profile}`" alt="Profile Image" class="w-px-50 h-auto rounded-circle"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col app-calendar-content">
|
||||||
|
<div class="card shadow-none border-0">
|
||||||
|
<div class="card-body pb-0">
|
||||||
|
<full-calendar
|
||||||
|
ref="fullCalendarRef"
|
||||||
|
:events="calendarEvents"
|
||||||
|
:options="calendarOptions"
|
||||||
|
defaultView="dayGridMonth"
|
||||||
|
class="flatpickr-calendar-only"
|
||||||
|
>
|
||||||
|
</full-calendar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<center-modal :display="isModalVisible" @close="isModalVisible = $event">
|
||||||
|
<template #title> Add Event </template>
|
||||||
|
<template #body>
|
||||||
|
<FormInput
|
||||||
|
title="이벤트 제목"
|
||||||
|
name="event"
|
||||||
|
:is-essential="true"
|
||||||
|
:is-alert="eventAlert"
|
||||||
|
@update:data="eventTitle = $event"
|
||||||
|
/>
|
||||||
|
<FormInput
|
||||||
|
title="이벤트 날짜"
|
||||||
|
type="date"
|
||||||
|
name="eventDate"
|
||||||
|
:is-essential="true"
|
||||||
|
:is-alert="eventDateAlert"
|
||||||
|
@update:data="eventDate = $event"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<button @click="addEvent">추가</button>
|
||||||
|
</template>
|
||||||
|
</center-modal>
|
||||||
|
</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 { inject, onMounted, reactive, ref, watch } from 'vue';
|
||||||
|
import $api from '@api';
|
||||||
|
import { isEmpty } from '@/common/utils';
|
||||||
|
import FormInput from '../input/FormInput.vue';
|
||||||
|
import 'flatpickr/dist/flatpickr.min.css';
|
||||||
|
import '@/assets/css/app-calendar.css';
|
||||||
|
import { useThemeStore } from '@s/darkmode';
|
||||||
|
import { fetchHolidays } from '@c/calendar/holiday';
|
||||||
|
import { useUserInfoStore } from '@/stores/useUserInfoStore';
|
||||||
|
|
||||||
|
const user = ref(null);
|
||||||
|
|
||||||
|
const userStore = useUserInfoStore();
|
||||||
|
|
||||||
|
const themeStore = useThemeStore();
|
||||||
|
const dayjs = inject('dayjs');
|
||||||
|
const fullCalendarRef = ref(null);
|
||||||
|
const calendarEvents = ref([]);
|
||||||
|
const isModalVisible = ref(false);
|
||||||
|
const eventAlert = ref(false);
|
||||||
|
const eventDateAlert = ref(false);
|
||||||
|
const eventTitle = ref('');
|
||||||
|
const eventDate = ref('');
|
||||||
|
const selectedDate = ref(null);
|
||||||
|
|
||||||
|
|
||||||
|
// 날짜 선택 핸들러
|
||||||
|
const handleDateSelect = (selectedDates) => {
|
||||||
|
if (selectedDates.length > 0) {
|
||||||
|
const selectedDate = dayjs(selectedDates[0]).format('YYYY-MM-DD');
|
||||||
|
eventDate.value = selectedDate;
|
||||||
|
showModal();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 달력 데이터 가져오기
|
||||||
|
const fetchData = 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);
|
||||||
|
|
||||||
|
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];
|
||||||
|
} 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 showModal = () => {
|
||||||
|
isModalVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
isModalVisible.value = false;
|
||||||
|
eventTitle.value = '';
|
||||||
|
eventDate.value = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const addEvent = () => {
|
||||||
|
if (!checkEvent()) {
|
||||||
|
calendarEvents.value.push({
|
||||||
|
title: eventTitle.value,
|
||||||
|
start: eventDate.value,
|
||||||
|
backgroundColor: '#4CAF50' // 일반 이벤트 색상
|
||||||
|
});
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkEvent = () => {
|
||||||
|
eventAlert.value = isEmpty(eventTitle.value);
|
||||||
|
eventDateAlert.value = isEmpty(eventDate.value);
|
||||||
|
return eventAlert.value || eventDateAlert.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const calendarOptions = reactive({
|
||||||
|
plugins: [dayGridPlugin, interactionPlugin],
|
||||||
|
initialView: 'dayGridMonth',
|
||||||
|
headerToolbar: {
|
||||||
|
left: 'today',
|
||||||
|
center: 'title',
|
||||||
|
right: 'prev,next',
|
||||||
|
},
|
||||||
|
locale: 'kr',
|
||||||
|
events: calendarEvents,
|
||||||
|
eventOrder: 'sortIdx',
|
||||||
|
selectable: true,
|
||||||
|
dateClick: handleDateSelect,
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await fetchData();
|
||||||
|
await userStore.userInfo();
|
||||||
|
user.value = userStore.user;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@ -67,12 +67,18 @@
|
|||||||
<div class="text-truncate">Project</div>
|
<div class="text-truncate">Project</div>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</li>
|
</li>
|
||||||
<li class="menu-item" :class="$route.path.includes('/sample') ? 'active' : ''">
|
<li class="menu-item" :class="$route.path.includes('/commuters') ? 'active' : ''">
|
||||||
|
<RouterLink class="menu-link" to="/commuters">
|
||||||
|
<i class="menu-icon icon-base bx bx-buildings"></i>
|
||||||
|
<div class="text-truncate">Commuters</div>
|
||||||
|
</RouterLink>
|
||||||
|
</li>
|
||||||
|
<!-- <li class="menu-item" :class="$route.path.includes('/sample') ? 'active' : ''">
|
||||||
<RouterLink class="menu-link" to="/sample"> <i class="bi "></i>
|
<RouterLink class="menu-link" to="/sample"> <i class="bi "></i>
|
||||||
<i class="menu-icon tf-icons bx bx-calendar"></i>
|
<i class="menu-icon tf-icons bx bx-calendar"></i>
|
||||||
<div class="text-truncate">Sample</div>
|
<div class="text-truncate">Sample</div>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</li>
|
</li> -->
|
||||||
</ul>
|
</ul>
|
||||||
</aside>
|
</aside>
|
||||||
<!-- / Menu -->
|
<!-- / Menu -->
|
||||||
|
|||||||
@ -80,6 +80,10 @@ const routes = [
|
|||||||
path: '/projectlist',
|
path: '/projectlist',
|
||||||
component: () => import('@v/projectlist/TheProjectList.vue'),
|
component: () => import('@v/projectlist/TheProjectList.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/commuters',
|
||||||
|
component: () => import('@v/commuters/TheCommuters.vue'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/sample',
|
path: '/sample',
|
||||||
component: () => import('@c/calendar/SampleCalendar.vue'),
|
component: () => import('@c/calendar/SampleCalendar.vue'),
|
||||||
|
|||||||
9
src/views/commuters/TheCommuters.vue
Normal file
9
src/views/commuters/TheCommuters.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<template>
|
||||||
|
<Calendar />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import Calendar from '@c/commuters/Calendar.vue';
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
Loading…
Reference in New Issue
Block a user