Merge branch 'main' of http://192.168.0.251:3000/localnet/localhost-front
This commit is contained in:
commit
fe00b53e4f
@ -8,69 +8,41 @@
|
|||||||
|
|
||||||
/* 휴가 */
|
/* 휴가 */
|
||||||
.half-day-buttons {
|
.half-day-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
.half-day-buttons .btn.active {
|
.half-day-buttons .btn.active {
|
||||||
border: 2px solid black;
|
border: 2px solid black;
|
||||||
}
|
}
|
||||||
.fc-daygrid-day-frame {
|
|
||||||
min-height: 80px !important;
|
.fc-daygrid-day-events {
|
||||||
max-height: 120px !important;
|
max-height: 100px !important;
|
||||||
overflow: hidden !important;
|
overflow-y: auto !important;
|
||||||
padding-top: 25px !important;
|
}
|
||||||
}
|
|
||||||
.fc-daygrid-day-events {
|
.fc-event {
|
||||||
max-height: 100px !important;
|
border: none;
|
||||||
overflow-y: auto !important;
|
}
|
||||||
}
|
.fc-daygrid-event.half-day-am {
|
||||||
.fc-daygrid-event {
|
width: calc(50% - 4px) !important;
|
||||||
position: absolute !important;
|
}
|
||||||
height: 20px !important;
|
.fc-daygrid-event.half-day-pm {
|
||||||
width: 100% !important;
|
width: calc(50% - 4px) !important;
|
||||||
left: 0 !important;
|
margin-left: auto !important
|
||||||
margin: 2px 0 !important;
|
}
|
||||||
padding: 0 !important;
|
.fc-day-sun .fc-daygrid-day-number,
|
||||||
border-radius: 2px !important;
|
.fc-col-header-cell:first-child .fc-col-header-cell-cushion {
|
||||||
border: none !important;
|
color: #ff4500 !important;
|
||||||
}
|
}
|
||||||
.fc-daygrid-event-harness {
|
.fc-day-sat .fc-daygrid-day-number,
|
||||||
display: flex;
|
.fc-col-header-cell:last-child .fc-col-header-cell-cushion {
|
||||||
flex-direction: column;
|
color: #6076e0 !important;
|
||||||
align-items: flex-start;
|
}
|
||||||
justify-content: flex-start;
|
.fc-daygrid-day-number {
|
||||||
width: 100%;
|
margin-right: auto;
|
||||||
gap: 22px;
|
}
|
||||||
}
|
|
||||||
.fc-daygrid-event.half-day-am {
|
|
||||||
width: 45% !important;
|
|
||||||
left: 0 !important;
|
|
||||||
}
|
|
||||||
.fc-daygrid-event.half-day-pm {
|
|
||||||
width: 45% !important;
|
|
||||||
left: auto !important;
|
|
||||||
right: 0 !important;
|
|
||||||
}
|
|
||||||
.fc-daygrid-event.full-day {
|
|
||||||
width: 100% !important;
|
|
||||||
left: 0 !important;
|
|
||||||
}
|
|
||||||
.fc-day-sun .fc-daygrid-day-number,
|
|
||||||
.fc-col-header-cell:first-child .fc-col-header-cell-cushion {
|
|
||||||
color: #ff4500 !important;
|
|
||||||
}
|
|
||||||
.fc-day-sat .fc-daygrid-day-number,
|
|
||||||
.fc-col-header-cell:last-child .fc-col-header-cell-cushion {
|
|
||||||
color: #6076e0 !important;
|
|
||||||
}
|
|
||||||
.fc-daygrid-day-number {
|
|
||||||
position: absolute !important;
|
|
||||||
top: 0px !important;
|
|
||||||
left: 5px !important;
|
|
||||||
text-align: left !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.grayscaleImg {
|
.grayscaleImg {
|
||||||
filter: grayscale(100%);
|
filter: grayscale(100%);
|
||||||
|
|||||||
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>
|
||||||
@ -172,7 +172,6 @@ const userStore = useUserInfoStore();
|
|||||||
// 상태 관리
|
// 상태 관리
|
||||||
const user = ref(null);
|
const user = ref(null);
|
||||||
const projectList = ref([]);
|
const projectList = ref([]);
|
||||||
const filteredProjects = ref([]);
|
|
||||||
const selectedCategory = ref(null);
|
const selectedCategory = ref(null);
|
||||||
const searchText = ref('');
|
const searchText = ref('');
|
||||||
|
|
||||||
|
|||||||
@ -40,10 +40,11 @@
|
|||||||
@update:alert="pwhintResAlert = $event"
|
@update:alert="pwhintResAlert = $event"
|
||||||
:value="pwhintRes"
|
:value="pwhintRes"
|
||||||
/>
|
/>
|
||||||
<div class="d-flex mt-5">
|
<div class="d-flex gap-2 mt-7 mb-3">
|
||||||
<RouterLink type="button" class="btn btn-secondary me-2 w-50" to="/login">취소</RouterLink>
|
<BackBtn class=" w-50" @click="handleback"/>
|
||||||
<button type="button" @click="handleSubmit" class="btn btn-primary w-50">확인</button>
|
<SaveBtn class="w-50" @click="handleSubmit" />
|
||||||
</div>
|
</div>
|
||||||
|
<p v-if="userCheckMsg" class="invalid-feedback d-block mb-0">{{ userCheckMsg }}</p>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -73,7 +74,8 @@
|
|||||||
<span v-if="passwordcheckError" class="invalid-feedback d-block">{{ passwordcheckError }}</span>
|
<span v-if="passwordcheckError" class="invalid-feedback d-block">{{ passwordcheckError }}</span>
|
||||||
|
|
||||||
<div class="d-grid gap-2 mt-5 mb-5">
|
<div class="d-grid gap-2 mt-5 mb-5">
|
||||||
<button type="button" @click="handleNewPassword" class="btn btn-primary">확인</button>
|
<SaveBtn @click="handleNewPassword" />
|
||||||
|
<p v-if="pwErrMsg" class="invalid-feedback d-block mb-0">{{ pwErrMsg }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -86,6 +88,8 @@
|
|||||||
import { useToastStore } from '@s/toastStore';
|
import { useToastStore } from '@s/toastStore';
|
||||||
import UserFormInput from '@c/input/UserFormInput.vue';
|
import UserFormInput from '@c/input/UserFormInput.vue';
|
||||||
import FormSelect from '../input/FormSelect.vue';
|
import FormSelect from '../input/FormSelect.vue';
|
||||||
|
import BackBtn from '@c/button/BackBtn.vue';
|
||||||
|
import SaveBtn from '../button/SaveBtn.vue';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const toastStore = useToastStore();
|
const toastStore = useToastStore();
|
||||||
@ -94,6 +98,8 @@
|
|||||||
const birth = ref('');
|
const birth = ref('');
|
||||||
const pwhint = ref('');
|
const pwhint = ref('');
|
||||||
const pwhintRes = ref('');
|
const pwhintRes = ref('');
|
||||||
|
const userCheckMsg = ref("");
|
||||||
|
const pwErrMsg = ref("");
|
||||||
|
|
||||||
const idAlert = ref(false);
|
const idAlert = ref(false);
|
||||||
const birthAlert = ref(false);
|
const birthAlert = ref(false);
|
||||||
@ -108,15 +114,22 @@
|
|||||||
const passwordcheckAlert = ref(false);
|
const passwordcheckAlert = ref(false);
|
||||||
const passwordcheckErrorAlert = ref(false);
|
const passwordcheckErrorAlert = ref(false);
|
||||||
|
|
||||||
const { pwhintList } = commonApi();
|
const { pwhintList } = commonApi({
|
||||||
|
loadPwhint: true,
|
||||||
|
});
|
||||||
|
|
||||||
const handleIdChange = value => {
|
const handleIdChange = value => {
|
||||||
id.value = value;
|
id.value = value;
|
||||||
idAlert.value = false;
|
idAlert.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleback = () => {
|
||||||
|
router.push('/login');
|
||||||
|
}
|
||||||
|
|
||||||
// 아이디, 생년월일, 비밀번호 힌트, 답변이 일치하는 member 재설정 input 보이기
|
// 아이디, 생년월일, 비밀번호 힌트, 답변이 일치하는 member 재설정 input 보이기
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
|
userCheckMsg.value = '';
|
||||||
idAlert.value = id.value.trim() === '';
|
idAlert.value = id.value.trim() === '';
|
||||||
pwhintResAlert.value = pwhintRes.value.trim() === '';
|
pwhintResAlert.value = pwhintRes.value.trim() === '';
|
||||||
birthAlert.value = birth.value.trim() === '';
|
birthAlert.value = birth.value.trim() === '';
|
||||||
@ -135,7 +148,8 @@
|
|||||||
if (response.status === 200 && response.data.data === true) {
|
if (response.status === 200 && response.data.data === true) {
|
||||||
resetForm.value = true;
|
resetForm.value = true;
|
||||||
} else {
|
} else {
|
||||||
toastStore.onToast('일치하는 정보가 없습니다.', 'e');
|
userCheckMsg.value = '입력하신 정보와 일치하는 회원이 없습니다.';
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -151,9 +165,10 @@
|
|||||||
|
|
||||||
// 비밀번호 업데이트
|
// 비밀번호 업데이트
|
||||||
const handleNewPassword = async () => {
|
const handleNewPassword = async () => {
|
||||||
|
pwErrMsg.value = '';
|
||||||
passwordAlert.value = password.value.trim() === '';
|
passwordAlert.value = password.value.trim() === '';
|
||||||
passwordcheckAlert.value = passwordcheck.value.trim() === '';
|
passwordcheckAlert.value = passwordcheck.value.trim() === '';
|
||||||
|
checkPw();
|
||||||
if (passwordAlert.value || passwordcheckAlert.value || passwordcheckErrorAlert.value) {
|
if (passwordAlert.value || passwordcheckAlert.value || passwordcheckErrorAlert.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -164,7 +179,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (checkResponse.data.data === false) {
|
if (checkResponse.data.data === false) {
|
||||||
toastStore.onToast('기존 비밀번호와 동일한 비밀번호로 변경할 수 없습니다.', 'e');
|
pwErrMsg.value = '기존 비밀번호와 동일한 비밀번호로 변경할 수 없습니다.';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
<div class="d-grid gap-2 mt-7 mb-5">
|
<div class="d-grid gap-2 mt-7 mb-5">
|
||||||
<button type="submit" @click="handleSubmit" class="btn btn-primary">로그인</button>
|
<button type="submit" @click="handleSubmit" class="btn btn-primary">로그인</button>
|
||||||
|
<p v-if="errorMessage" class="invalid-feedback d-block mb-0">{{ errorMessage }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3 d-flex justify-content-around">
|
<div class="mb-3 d-flex justify-content-around">
|
||||||
@ -29,7 +30,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import $api from '@api';
|
import $api from '@api';
|
||||||
import router from '@/router';
|
import router from '@/router';
|
||||||
import { useRoute } from 'vue-router';
|
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import UserFormInput from '@c/input/UserFormInput.vue';
|
import UserFormInput from '@c/input/UserFormInput.vue';
|
||||||
import { useUserInfoStore } from '@/stores/useUserInfoStore';
|
import { useUserInfoStore } from '@/stores/useUserInfoStore';
|
||||||
@ -39,9 +39,9 @@
|
|||||||
const idAlert = ref(false);
|
const idAlert = ref(false);
|
||||||
const passwordAlert = ref(false);
|
const passwordAlert = ref(false);
|
||||||
const remember = ref(false);
|
const remember = ref(false);
|
||||||
|
const errorMessage = ref("");
|
||||||
|
|
||||||
const userStore = useUserInfoStore();
|
const userStore = useUserInfoStore();
|
||||||
const route = useRoute();
|
|
||||||
|
|
||||||
const handleIdChange = value => {
|
const handleIdChange = value => {
|
||||||
id.value = value;
|
id.value = value;
|
||||||
@ -53,7 +53,8 @@
|
|||||||
passwordAlert.value = false;
|
passwordAlert.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
|
errorMessage.value = '';
|
||||||
idAlert.value = id.value.trim() === '';
|
idAlert.value = id.value.trim() === '';
|
||||||
passwordAlert.value = password.value.trim() === '';
|
passwordAlert.value = password.value.trim() === '';
|
||||||
|
|
||||||
@ -61,16 +62,23 @@ const handleSubmit = async () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$api.post('user/login', {
|
$api.post('user/login', {
|
||||||
loginId: id.value,
|
loginId: id.value,
|
||||||
password: password.value,
|
password: password.value,
|
||||||
remember: remember.value,
|
remember: remember.value,
|
||||||
}, { headers: { 'X-Page-Route': route.path } })
|
}, { headers: { isLogin: true } })
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
userStore.userInfo();
|
userStore.userInfo();
|
||||||
router.push('/');
|
router.push('/');
|
||||||
}
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
if (error.response) {
|
||||||
|
error.config.isLoginRequest = true;
|
||||||
|
errorMessage.value = error.response.data.message;
|
||||||
|
console.clear();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -141,9 +141,11 @@
|
|||||||
:is-alert="phoneAlert"
|
:is-alert="phoneAlert"
|
||||||
@update:data="phone = $event"
|
@update:data="phone = $event"
|
||||||
@update:alert="phoneAlert = $event"
|
@update:alert="phoneAlert = $event"
|
||||||
|
@blur="checkPhoneDuplicate"
|
||||||
:maxlength="11"
|
:maxlength="11"
|
||||||
:value="phone"
|
:value="phone"
|
||||||
/>
|
/>
|
||||||
|
<span v-if="phoneError" class="invalid-feedback d-block">{{ phoneError }}</span>
|
||||||
|
|
||||||
<div class="d-flex mt-5">
|
<div class="d-flex mt-5">
|
||||||
<RouterLink type="button" class="btn btn-secondary me-2 w-50" to="/login">취소</RouterLink>
|
<RouterLink type="button" class="btn btn-secondary me-2 w-50" to="/login">취소</RouterLink>
|
||||||
@ -181,6 +183,7 @@
|
|||||||
const detailAddress = ref('');
|
const detailAddress = ref('');
|
||||||
const postcode = ref(''); // 우편번호
|
const postcode = ref(''); // 우편번호
|
||||||
const phone = ref('');
|
const phone = ref('');
|
||||||
|
const phoneError = ref('');
|
||||||
const color = ref(''); // 선택된 color
|
const color = ref(''); // 선택된 color
|
||||||
const mbti = ref(''); // 선택된 MBTI
|
const mbti = ref(''); // 선택된 MBTI
|
||||||
const pwhint = ref(''); // 선택된 pwhint
|
const pwhint = ref(''); // 선택된 pwhint
|
||||||
@ -196,6 +199,7 @@
|
|||||||
const birthAlert = ref(false);
|
const birthAlert = ref(false);
|
||||||
const addressAlert = ref(false);
|
const addressAlert = ref(false);
|
||||||
const phoneAlert = ref(false);
|
const phoneAlert = ref(false);
|
||||||
|
const phoneErrorAlert = ref(false);
|
||||||
|
|
||||||
const toastStore = useToastStore();
|
const toastStore = useToastStore();
|
||||||
|
|
||||||
@ -251,6 +255,19 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 전화번호 중복체크
|
||||||
|
const checkPhoneDuplicate = async () => {
|
||||||
|
const response = await $api.get(`/user/checkPhone?memberTel=${phone.value}`);
|
||||||
|
|
||||||
|
if (!response.data.data) {
|
||||||
|
phoneErrorAlert.value = true;
|
||||||
|
phoneError.value = '이미 사용 중인 전화번호입니다.';
|
||||||
|
} else {
|
||||||
|
phoneErrorAlert.value = false;
|
||||||
|
phoneError.value = '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 컬러, mbti, 비밀번호 힌트 목록 불러오기
|
// 컬러, mbti, 비밀번호 힌트 목록 불러오기
|
||||||
const { colorList, mbtiList, pwhintList } = commonApi({
|
const { colorList, mbtiList, pwhintList } = commonApi({
|
||||||
loadColor: true, colorType: 'YON',
|
loadColor: true, colorType: 'YON',
|
||||||
@ -298,7 +315,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (profilAlert.value || idAlert.value || idErrorAlert.value || passwordAlert.value || passwordcheckAlert.value ||
|
if (profilAlert.value || idAlert.value || idErrorAlert.value || passwordAlert.value || passwordcheckAlert.value ||
|
||||||
passwordcheckErrorAlert.value || pwhintResAlert.value || nameAlert.value || birthAlert.value || addressAlert.value || phoneAlert.value) {
|
passwordcheckErrorAlert.value || pwhintResAlert.value || nameAlert.value || birthAlert.value || addressAlert.value || phoneAlert.value || phoneErrorAlert.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,7 @@
|
|||||||
:class="{ 'grayscaleImg': isUserDisabled(user) }"
|
:class="{ 'grayscaleImg': isUserDisabled(user) }"
|
||||||
:src="`${baseUrl}upload/img/profile/${user.MEMBERPRF}`"
|
:src="`${baseUrl}upload/img/profile/${user.MEMBERPRF}`"
|
||||||
:style="`border-color: ${user.usercolor} !important;`"
|
:style="`border-color: ${user.usercolor} !important;`"
|
||||||
|
@error="$event.target.src = '/img/icons/icon.png'"
|
||||||
alt="user"
|
alt="user"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -38,17 +38,11 @@ const loadScript = src => {
|
|||||||
script.type = 'text/javascript';
|
script.type = 'text/javascript';
|
||||||
script.async = true;
|
script.async = true;
|
||||||
document.body.appendChild(script);
|
document.body.appendChild(script);
|
||||||
// script.onload = () => {
|
|
||||||
// console.log(`${src} loaded successfully.`);
|
|
||||||
// };
|
|
||||||
// script.onerror = () => {
|
|
||||||
// console.error(`Failed to load script: ${src}`);
|
|
||||||
// };
|
|
||||||
};
|
};
|
||||||
nextTick(async () => {
|
nextTick(async () => {
|
||||||
await wait(200);
|
await wait(200);
|
||||||
loadScript('/vendor/js/menu.js');
|
loadScript('/vendor/js/menu.js');
|
||||||
loadScript('/js/main.js');
|
// loadScript('/js/main.js');
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@ -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 -->
|
||||||
|
|||||||
@ -152,7 +152,7 @@
|
|||||||
<!-- User -->
|
<!-- User -->
|
||||||
<li class="nav-item navbar-dropdown dropdown-user dropdown">
|
<li class="nav-item navbar-dropdown dropdown-user dropdown">
|
||||||
<a class="nav-link dropdown-toggle hide-arrow p-0" href="javascript:void(0);" data-bs-toggle="dropdown">
|
<a class="nav-link dropdown-toggle hide-arrow p-0" href="javascript:void(0);" data-bs-toggle="dropdown">
|
||||||
<img v-if="user" :src="`http://localhost:10325/upload/img/profile/${user.profile}`" alt="Profile Image" class="w-px-40 h-auto rounded-circle"/>
|
<img v-if="user" :src="`${baseUrl}upload/img/profile/${user.profile}`" alt="Profile Image" class="w-px-40 h-auto rounded-circle" @error="$event.target.src = '/img/icons/icon.png'"/>
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu dropdown-menu-end">
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
<li>
|
<li>
|
||||||
@ -232,8 +232,10 @@ import { useUserInfoStore } from '@/stores/useUserInfoStore';
|
|||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { useThemeStore } from '@s/darkmode';
|
import { useThemeStore } from '@s/darkmode';
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
import $api from '@api';
|
||||||
|
|
||||||
const user = ref(null);
|
const user = ref(null);
|
||||||
|
const baseUrl = $api.defaults.baseURL.replace(/api\/$/, '');
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
const userStore = useUserInfoStore();
|
const userStore = useUserInfoStore();
|
||||||
|
|||||||
@ -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>
|
||||||
@ -123,30 +123,32 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 프로필 클릭 시 연차 내역 가져오기
|
// 프로필 클릭 시 연차 내역 가져오기
|
||||||
const handleProfileClick = async (user) => {
|
// 프로필 클릭 시 연차 내역 가져오기
|
||||||
try {
|
const handleProfileClick = async (user) => {
|
||||||
if (user.MEMBERSEQ === userStore.user.id) {
|
try {
|
||||||
const response = await axios.get(`vacation/history`);
|
if (user.MEMBERSEQ === userStore.user.id) {
|
||||||
if (response.status === 200 && response.data) {
|
const year = new Date().getFullYear(); // 현재 연도
|
||||||
myVacations.value = response.data.data.usedVacations || [];
|
// 연도 파라미터를 전달하여 전체 연도의 연차 내역을 조회
|
||||||
receivedVacations.value = response.data.data.receivedVacations || [];
|
const response = await axios.get(`vacation/history?year=${year}`);
|
||||||
isModalOpen.value = true;
|
if (response.status === 200 && response.data) {
|
||||||
// 모달을 열 때 기준 연도와 기준 월 갱신
|
myVacations.value = response.data.data.usedVacations || [];
|
||||||
modalYear.value = new Date().getFullYear();
|
receivedVacations.value = response.data.data.receivedVacations || [];
|
||||||
modalMonth.value = String(new Date().getMonth() + 1).padStart(2, "0");
|
isModalOpen.value = true;
|
||||||
isGrantModalOpen.value = false;
|
// 모달을 열 때 기준 연도 갱신
|
||||||
} else {
|
modalYear.value = year;
|
||||||
console.warn("❌ 연차 내역을 불러오지 못했습니다.");
|
isGrantModalOpen.value = false;
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
selectedUser.value = user;
|
console.warn("❌ 연차 내역을 불러오지 못했습니다.");
|
||||||
isGrantModalOpen.value = true;
|
|
||||||
isModalOpen.value = false;
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} else {
|
||||||
console.error("🚨 연차 데이터 불러오기 실패:", error);
|
selectedUser.value = user;
|
||||||
|
isGrantModalOpen.value = true;
|
||||||
|
isModalOpen.value = false;
|
||||||
}
|
}
|
||||||
};
|
} catch (error) {
|
||||||
|
console.error("🚨 연차 데이터 불러오기 실패:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const fetchUserList = async () => {
|
const fetchUserList = async () => {
|
||||||
try {
|
try {
|
||||||
@ -186,10 +188,11 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
// computed: modalYear와 일치하는 항목만 필터링
|
// computed: modalYear와 일치하는 항목만 필터링
|
||||||
const filteredMyVacations = computed(() => {
|
const filteredMyVacations = computed(() => {
|
||||||
const filtered = myVacations.value.filter(vac => {
|
const filtered = myVacations.value.filter(vac => {
|
||||||
console.log(vac)
|
// vac.date가 없으면 vac.LOCVACUDT를 사용하도록 함
|
||||||
const year = vac.date ? vac.date.split("T")[0].substring(0, 4) : null;
|
const dateStr = vac.date || vac.LOCVACUDT;
|
||||||
|
const year = dateStr ? dateStr.split("T")[0].substring(0, 4) : null;
|
||||||
console.log("vacation year:", year, "modalYear:", modalYear.value);
|
console.log("vacation year:", year, "modalYear:", modalYear.value);
|
||||||
return year === String(modalYear.value);
|
return year === String(modalYear.value);
|
||||||
});
|
});
|
||||||
@ -199,12 +202,10 @@
|
|||||||
|
|
||||||
const filteredReceivedVacations = computed(() => {
|
const filteredReceivedVacations = computed(() => {
|
||||||
return receivedVacations.value.filter(vac => {
|
return receivedVacations.value.filter(vac => {
|
||||||
console.log(
|
const dateStr = vac.date || vac.LOCVACUDT;
|
||||||
vac.date,
|
const year = dateStr ? dateStr.split("T")[0].substring(0, 4) : null;
|
||||||
vac.date ? vac.date.split("T")[0].substring(0, 4) : null,
|
console.log("vacation year:", year, "modalYear:", modalYear.value);
|
||||||
modalYear.value
|
return dateStr && year === String(modalYear.value);
|
||||||
);
|
|
||||||
return vac.date && vac.date.split("T")[0].substring(0, 4) === String(modalYear.value);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user