버튼,휴가관리 추가가
This commit is contained in:
parent
af4b18130a
commit
76981b956a
@ -3,13 +3,12 @@
|
||||
<li>
|
||||
<BoardProfile profileName=곤데리 :showDetail="false" :author="true" />
|
||||
<div class="mt-2">저도 궁금합니다.</div>
|
||||
<button type="button" class="btn btn-text-primary" @click="toggleComment">답변달기</button>
|
||||
<PlusButton @click="toggleComment"/>
|
||||
<BoardComentArea v-if="comment" />
|
||||
<ul class="list-unstyled twoDepth">
|
||||
<li>
|
||||
<BoardProfile profileName=곤데리2 :showDetail="false" />
|
||||
<div class="mt-2">저도 궁금합니다.</div>
|
||||
<button type="button" class="btn btn-text-primary" @click="toggleComment">답변달기</button>
|
||||
<BoardComentArea v-if="comment" />
|
||||
</li>
|
||||
</ul>
|
||||
@ -17,13 +16,13 @@
|
||||
<li>
|
||||
<BoardProfile profileName=곤데리 :showDetail="false" />
|
||||
<div class="mt-2">저도 궁금합니다.</div>
|
||||
<button type="button" class="btn btn-text-primary" @click="toggleComment">답변달기</button>
|
||||
<PlusButton @click="toggleComment"/>
|
||||
<BoardComentArea v-if="comment" />
|
||||
</li>
|
||||
<li>
|
||||
<BoardProfile :showDetail="false" :unknown="false" />
|
||||
<BoardProfile profileName=곤데리 :showDetail="false" />
|
||||
<div class="mt-2">저도 궁금합니다.</div>
|
||||
<button type="button" class="btn btn-text-primary" @click="toggleComment">답변달기</button>
|
||||
<PlusButton @click="toggleComment"/>
|
||||
<BoardComentArea v-if="comment" />
|
||||
</li>
|
||||
</ul>
|
||||
@ -36,6 +35,7 @@ import BoardProfile from './BoardProfile.vue';
|
||||
import BoardComentArea from './BoardComentArea.vue';
|
||||
import { ref, computed } from 'vue';
|
||||
import Pagination from '../pagination/Pagination.vue';
|
||||
import PlusButton from '../button/PlusButton.vue';
|
||||
|
||||
const comment = ref(false);
|
||||
|
||||
@ -63,4 +63,4 @@ const toggleComment = () => {
|
||||
.btn-text-primary:focus {
|
||||
background-color: transparent
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@ -24,12 +24,8 @@
|
||||
</div>
|
||||
<div class="ms-auto btn-area">
|
||||
<template v-if="showDetail">
|
||||
<button class="btn btn-label-primary btn-icon">
|
||||
<i class='bx bx-edit-alt'></i>
|
||||
</button>
|
||||
<button class="btn btn-label-primary btn-icon">
|
||||
<i class='bx bx-trash' ></i>
|
||||
</button>
|
||||
<EditButton />
|
||||
<DeleteButton />
|
||||
</template>
|
||||
<template v-else>
|
||||
<template v-if="author">
|
||||
@ -47,6 +43,8 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import DeleteButton from '../button/DeleteButton.vue';
|
||||
import EditButton from '../button/EditButton.vue';
|
||||
import BoardRecommendBtn from './BoardRecommendBtn.vue';
|
||||
|
||||
defineProps({
|
||||
@ -85,7 +83,7 @@ defineProps({
|
||||
}
|
||||
|
||||
@media screen and (max-width:450px) {
|
||||
.btn-area {
|
||||
.btn-area {
|
||||
margin-top: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
@ -94,4 +92,4 @@ defineProps({
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
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>
|
||||
</RouterLink>
|
||||
</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' : ''">
|
||||
<RouterLink class="menu-link" to="/sample"> <i class="bi "></i>
|
||||
<i class="menu-icon tf-icons bx bx-calendar"></i>
|
||||
|
||||
@ -32,6 +32,10 @@ const routes = [
|
||||
// component: () => import('@v/user/TheLogin.vue'),
|
||||
// meta: { layout: 'NoLayout' },
|
||||
// },
|
||||
{
|
||||
path: '/vacation',
|
||||
component: () => import('@v/vacation/VacationManagement.vue'),
|
||||
},
|
||||
{
|
||||
path: '/sample',
|
||||
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