휴가,버튼컴포넌트추가가
This commit is contained in:
parent
32fd6897f2
commit
71f6abc61f
@ -6,8 +6,8 @@
|
|||||||
"@/*" : ["src/*"],
|
"@/*" : ["src/*"],
|
||||||
"@a/*": ["src/assets/*"],
|
"@a/*": ["src/assets/*"],
|
||||||
"@c/*": ["src/components/*"],
|
"@c/*": ["src/components/*"],
|
||||||
"@v/*": ["src/view/*"],
|
"@v/*": ["src/views/*"],
|
||||||
"@l/*": ["src/layout/*"],
|
"@l/*": ["src/layouts/*"],
|
||||||
"@s/*": ["src/stores/*"],
|
"@s/*": ["src/stores/*"],
|
||||||
"@p/*": ["src/common/plugin/*"],
|
"@p/*": ["src/common/plugin/*"],
|
||||||
"@api": ["./src/common/axios-interceptor.js"]
|
"@api": ["./src/common/axios-interceptor.js"]
|
||||||
|
|||||||
@ -3,13 +3,12 @@
|
|||||||
<li>
|
<li>
|
||||||
<BoardProfile profileName=곤데리 :showDetail="false" :author="true" />
|
<BoardProfile profileName=곤데리 :showDetail="false" :author="true" />
|
||||||
<div class="mt-2">저도 궁금합니다.</div>
|
<div class="mt-2">저도 궁금합니다.</div>
|
||||||
<button type="button" class="btn btn-text-primary" @click="toggleComment">답변달기</button>
|
<PlusButton @click="toggleComment"/>
|
||||||
<BoardComentArea v-if="comment" />
|
<BoardComentArea v-if="comment" />
|
||||||
<ul class="list-unstyled twoDepth">
|
<ul class="list-unstyled twoDepth">
|
||||||
<li>
|
<li>
|
||||||
<BoardProfile profileName=곤데리2 :showDetail="false" />
|
<BoardProfile profileName=곤데리2 :showDetail="false" />
|
||||||
<div class="mt-2">저도 궁금합니다.</div>
|
<div class="mt-2">저도 궁금합니다.</div>
|
||||||
<button type="button" class="btn btn-text-primary" @click="toggleComment">답변달기</button>
|
|
||||||
<BoardComentArea v-if="comment" />
|
<BoardComentArea v-if="comment" />
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -17,13 +16,13 @@
|
|||||||
<li>
|
<li>
|
||||||
<BoardProfile profileName=곤데리 :showDetail="false" />
|
<BoardProfile profileName=곤데리 :showDetail="false" />
|
||||||
<div class="mt-2">저도 궁금합니다.</div>
|
<div class="mt-2">저도 궁금합니다.</div>
|
||||||
<button type="button" class="btn btn-text-primary" @click="toggleComment">답변달기</button>
|
<PlusButton @click="toggleComment"/>
|
||||||
<BoardComentArea v-if="comment" />
|
<BoardComentArea v-if="comment" />
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<BoardProfile :showDetail="false" :unknown="false" />
|
<BoardProfile profileName=곤데리 :showDetail="false" />
|
||||||
<div class="mt-2">저도 궁금합니다.</div>
|
<div class="mt-2">저도 궁금합니다.</div>
|
||||||
<button type="button" class="btn btn-text-primary" @click="toggleComment">답변달기</button>
|
<PlusButton @click="toggleComment"/>
|
||||||
<BoardComentArea v-if="comment" />
|
<BoardComentArea v-if="comment" />
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -36,6 +35,7 @@ import BoardProfile from './BoardProfile.vue';
|
|||||||
import BoardComentArea from './BoardComentArea.vue';
|
import BoardComentArea from './BoardComentArea.vue';
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
import Pagination from '../pagination/Pagination.vue';
|
import Pagination from '../pagination/Pagination.vue';
|
||||||
|
import PlusButton from '../button/PlusButton.vue';
|
||||||
|
|
||||||
const comment = ref(false);
|
const comment = ref(false);
|
||||||
|
|
||||||
|
|||||||
@ -24,12 +24,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="ms-auto btn-area">
|
<div class="ms-auto btn-area">
|
||||||
<template v-if="showDetail">
|
<template v-if="showDetail">
|
||||||
<button class="btn btn-label-primary btn-icon">
|
<EditButton />
|
||||||
<i class='bx bx-edit-alt'></i>
|
<DeleteButton />
|
||||||
</button>
|
|
||||||
<button class="btn btn-label-primary btn-icon">
|
|
||||||
<i class='bx bx-trash' ></i>
|
|
||||||
</button>
|
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<template v-if="author">
|
<template v-if="author">
|
||||||
@ -47,6 +43,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import DeleteButton from '../button/DeleteButton.vue';
|
||||||
|
import EditButton from '../button/EditButton.vue';
|
||||||
import BoardRecommendBtn from './BoardRecommendBtn.vue';
|
import BoardRecommendBtn from './BoardRecommendBtn.vue';
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
|
|||||||
13
src/components/button/DeleteButton.vue
Normal file
13
src/components/button/DeleteButton.vue
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<template>
|
||||||
|
<button class="btn btn-label-primary btn-icon">
|
||||||
|
<i class='bx bx-trash' ></i>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'DeleteButton',
|
||||||
|
methods: {
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
13
src/components/button/EditButton.vue
Normal file
13
src/components/button/EditButton.vue
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<template>
|
||||||
|
<button class="btn btn-label-primary btn-icon">
|
||||||
|
<i class="bx bx-edit-alt"></i>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'EditButton',
|
||||||
|
methods: {
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
13
src/components/button/PlusButton.vue
Normal file
13
src/components/button/PlusButton.vue
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<template>
|
||||||
|
<button class="btn btn-label-primary btn-icon">
|
||||||
|
<i class="icon-base bx bx-plus"></i>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'PlusButton',
|
||||||
|
methods: {
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@ -43,6 +43,12 @@
|
|||||||
<div class="text-truncate">Board</div>
|
<div class="text-truncate">Board</div>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="menu-item" :class="$route.path.includes('/vacation') ? 'active' : ''">
|
||||||
|
<RouterLink class="menu-link" to="/vacation">
|
||||||
|
<i class="menu-icon tf-icons bx bx-calendar"></i>
|
||||||
|
<div class="text-truncate">vacation</div>
|
||||||
|
</RouterLink>
|
||||||
|
</li>
|
||||||
<li class="menu-item" :class="$route.path.includes('/sample') ? 'active' : ''">
|
<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>
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import BoardWrite from '@v/board/BoardWrite.vue';
|
|||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
component: () => import('@v/MainView.vue'),
|
component: () => import('@/views/vacation/MainView.vue'),
|
||||||
// meta: { requiresAuth: true }
|
// meta: { requiresAuth: true }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -26,6 +26,10 @@ const routes = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/vacation',
|
||||||
|
component: () => import('@v/vacation/VacationManagement.vue'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/sample',
|
path: '/sample',
|
||||||
component: () => import('@c/calendar/SampleCalendar.vue'),
|
component: () => import('@c/calendar/SampleCalendar.vue'),
|
||||||
|
|||||||
20
src/stores/calendarStore.js
Normal file
20
src/stores/calendarStore.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { ref } from 'vue';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const events = ref([]);
|
||||||
|
|
||||||
|
const fetchEvents = async () => {
|
||||||
|
const response = await axios.get('/api/calendar/events');
|
||||||
|
events.value = response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const addEvent = async (event) => {
|
||||||
|
await axios.post('/api/calendar/event', event);
|
||||||
|
fetchEvents();
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
events,
|
||||||
|
fetchEvents,
|
||||||
|
addEvent,
|
||||||
|
};
|
||||||
55
src/views/AddEventModal.vue
Normal file
55
src/views/AddEventModal.vue
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<template>
|
||||||
|
<div class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<h3>휴가 추가</h3>
|
||||||
|
<input type="text" v-model="title" placeholder="제목" />
|
||||||
|
<input type="date" v-model="date" />
|
||||||
|
<button @click="addEvent">추가</button>
|
||||||
|
<button @click="$emit('close')">닫기</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import calendarStore from '@s/calendarStore';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
title: '',
|
||||||
|
date: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
addEvent() {
|
||||||
|
if (this.title && this.date) {
|
||||||
|
calendarStore.addEvent({ title: this.title, start: this.date });
|
||||||
|
this.$emit('close');
|
||||||
|
} else {
|
||||||
|
alert('모든 필드를 입력해주세요.');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.modal-content {
|
||||||
|
background: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
width: 300px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
56
src/views/vacation/ProfileList.vue
Normal file
56
src/views/vacation/ProfileList.vue
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<template>
|
||||||
|
<div class="profile-list">
|
||||||
|
<div v-for="profile in profiles" :key="profile.id" class="profile">
|
||||||
|
<img :src="profile.avatar" alt="프로필 사진" class="avatar" />
|
||||||
|
<div class="info">
|
||||||
|
<p class="name">{{ profile.name }}</p>
|
||||||
|
<p class="vacation-count">남은 휴가: {{ profile.remainingVacations }}일</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
profiles: [
|
||||||
|
{ id: 1, name: '김철수', avatar: '/avatars/user1.png', remainingVacations: 15 },
|
||||||
|
{ id: 2, name: '박영희', avatar: '/avatars/user2.png', remainingVacations: 11 },
|
||||||
|
{ id: 3, name: '이민호', avatar: '/avatars/user3.png', remainingVacations: 10 },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.profile-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.profile {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.avatar {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
.info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.name {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.vacation-count {
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
181
src/views/vacation/VacationManagement.vue
Normal file
181
src/views/vacation/VacationManagement.vue
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
<template>
|
||||||
|
<div class="vacation-management">
|
||||||
|
<div class="container-xxl flex-grow-1 container-p-y">
|
||||||
|
<div class="save-button-container">
|
||||||
|
<button class="btn btn-success" @click="addVacationRequest">✔</button>
|
||||||
|
</div>
|
||||||
|
<div class="card app-calendar-wrapper">
|
||||||
|
<div class="row g-0">
|
||||||
|
<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 class="half-day-buttons">
|
||||||
|
<button class="btn btn-info" :class="{ active: halfDayType === 'AM' }" @click="toggleHalfDay('AM')">☀️ 오전반차</button>
|
||||||
|
<button class="btn btn-warning" :class="{ active: halfDayType === 'PM' }" @click="toggleHalfDay('PM')">🌙 오후반차</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import FullCalendar from '@fullcalendar/vue3';
|
||||||
|
import dayGridPlugin from '@fullcalendar/daygrid';
|
||||||
|
import interactionPlugin from '@fullcalendar/interaction';
|
||||||
|
import 'flatpickr/dist/flatpickr.min.css';
|
||||||
|
import '@/assets/css/app-calendar.css';
|
||||||
|
import { reactive, ref } from 'vue';
|
||||||
|
|
||||||
|
const fullCalendarRef = ref(null);
|
||||||
|
const calendarEvents = ref([]);
|
||||||
|
const selectedDates = ref([]);
|
||||||
|
const halfDayType = ref(null); // 오전/오후 반차 선택
|
||||||
|
|
||||||
|
const calendarOptions = reactive({
|
||||||
|
plugins: [dayGridPlugin, interactionPlugin],
|
||||||
|
initialView: 'dayGridMonth',
|
||||||
|
headerToolbar: {
|
||||||
|
left: 'today',
|
||||||
|
center: 'title',
|
||||||
|
right: 'prev,next',
|
||||||
|
},
|
||||||
|
locale: 'ko',
|
||||||
|
selectable: true,
|
||||||
|
dateClick: handleDateClick,
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleDateClick(info) {
|
||||||
|
const date = info.dateStr;
|
||||||
|
const dayElement = info.dayEl;
|
||||||
|
|
||||||
|
if (!selectedDates.value.includes(date)) {
|
||||||
|
selectedDates.value.push(date);
|
||||||
|
if (halfDayType.value === 'AM') {
|
||||||
|
dayElement.style.backgroundImage = 'linear-gradient(to bottom, #ade3ff 50%, transparent 50%)';
|
||||||
|
} else if (halfDayType.value === 'PM') {
|
||||||
|
dayElement.style.backgroundImage = 'linear-gradient(to top, #ade3ff 50%, transparent 50%)';
|
||||||
|
} else {
|
||||||
|
dayElement.style.backgroundColor = '#ade3ff';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
selectedDates.value = selectedDates.value.filter((d) => d !== date);
|
||||||
|
dayElement.style.backgroundColor = '';
|
||||||
|
dayElement.style.backgroundImage = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
halfDayType.value = null; // 날짜 클릭 후 반차 선택 초기화
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleHalfDay(type) {
|
||||||
|
halfDayType.value = halfDayType.value === type ? null : type;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addVacationRequest() {
|
||||||
|
if (selectedDates.value.length === 0) {
|
||||||
|
alert('Please select at least one date.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newEvents = selectedDates.value.map((date) => ({
|
||||||
|
title: halfDayType.value ? `${halfDayType.value} Half Day Vacation` : 'Vacation',
|
||||||
|
start: date,
|
||||||
|
allDay: true,
|
||||||
|
}));
|
||||||
|
|
||||||
|
calendarEvents.value = [...calendarEvents.value, ...newEvents];
|
||||||
|
alert(`Vacation added for dates: ${selectedDates.value.join(', ')} as ${halfDayType.value || 'Full Day'}`);
|
||||||
|
selectedDates.value = [];
|
||||||
|
halfDayType.value = null;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.vacation-management {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-button-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 900px; /* 탑바 아래로 간격 조정 */
|
||||||
|
right: 400px;
|
||||||
|
z-index: 1050; /* 탑바보다 높은 값 */
|
||||||
|
background-color: white;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.save-button-container {
|
||||||
|
top: 70px; /* 모바일에서 탑바 아래 간격 조정 */
|
||||||
|
right: 5px;
|
||||||
|
left: 5px;
|
||||||
|
width: calc(100% - 10px); /* 모바일 화면에 맞게 크기 조정 */
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.half-day-buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-day-sun .fc-col-header-cell-cushion,
|
||||||
|
.fc-day-sun a {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-day-sat .fc-col-header-cell-cushion,
|
||||||
|
.fc-day-sat a {
|
||||||
|
color: blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-calendar-only input.flatpickr-input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pt-2.px-3 {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flatpickr-calendar {
|
||||||
|
position: relative !important;
|
||||||
|
display: block !important;
|
||||||
|
width: 100% !important;
|
||||||
|
height: auto !important;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-info {
|
||||||
|
background-color: #17a2b8;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-warning {
|
||||||
|
background-color: #ffc107;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.active {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in New Issue
Block a user