Merge branch 'main' into board-ji
This commit is contained in:
commit
9564788ad8
@ -24,7 +24,7 @@
|
|||||||
class="form-control"
|
class="form-control"
|
||||||
:value="password"
|
:value="password"
|
||||||
autocomplete="new-password"
|
autocomplete="new-password"
|
||||||
maxlength="4"
|
maxlength="8"
|
||||||
placeholder="비밀번호 입력"
|
placeholder="비밀번호 입력"
|
||||||
@input="filterInput"
|
@input="filterInput"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -57,7 +57,7 @@
|
|||||||
autocomplete="new-password"
|
autocomplete="new-password"
|
||||||
v-model="password"
|
v-model="password"
|
||||||
placeholder="비밀번호"
|
placeholder="비밀번호"
|
||||||
maxlength="4"
|
maxlength="8"
|
||||||
@input="
|
@input="
|
||||||
password = password.replace(/\s/g, '');
|
password = password.replace(/\s/g, '');
|
||||||
clearAlert('password');
|
clearAlert('password');
|
||||||
|
|||||||
@ -90,9 +90,9 @@ import { useProjectStore } from '@/stores/useProjectStore';
|
|||||||
import CommuterBtn from '@c/commuters/CommuterBtn.vue';
|
import CommuterBtn from '@c/commuters/CommuterBtn.vue';
|
||||||
import CommuterProjectList from '@c/commuters/CommuterProjectList.vue';
|
import CommuterProjectList from '@c/commuters/CommuterProjectList.vue';
|
||||||
import BackBtn from '@c/button/BackBtn.vue';
|
import BackBtn from '@c/button/BackBtn.vue';
|
||||||
import flatpickr from 'flatpickr';
|
import { useDatePicker } from '@/stores/useDatePicker';
|
||||||
import monthSelectPlugin from 'flatpickr/dist/plugins/monthSelect/index';
|
|
||||||
import 'flatpickr/dist/plugins/monthSelect/style.css';
|
const datePickerStore = useDatePicker();
|
||||||
|
|
||||||
const baseUrl = $api.defaults.baseURL.replace(/api\/$/, '');
|
const baseUrl = $api.defaults.baseURL.replace(/api\/$/, '');
|
||||||
const user = ref({});
|
const user = ref({});
|
||||||
@ -115,7 +115,6 @@ const commuters = ref([]);
|
|||||||
const monthlyCommuters = ref([]);
|
const monthlyCommuters = ref([]);
|
||||||
|
|
||||||
const calendarDatepicker = ref(null);
|
const calendarDatepicker = ref(null);
|
||||||
let fpInstance = null;
|
|
||||||
|
|
||||||
// 출퇴근 컴포넌트 이벤트 핸들러
|
// 출퇴근 컴포넌트 이벤트 핸들러
|
||||||
const handleWorkTimeUpdate = () => {
|
const handleWorkTimeUpdate = () => {
|
||||||
@ -390,7 +389,6 @@ const selectedDateCommuters = computed(() => {
|
|||||||
commuter.COMMUTDAY === eventDate.value
|
commuter.COMMUTDAY === eventDate.value
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await fetchData();
|
await fetchData();
|
||||||
await userStore.userInfo();
|
await userStore.userInfo();
|
||||||
@ -407,65 +405,13 @@ onMounted(async () => {
|
|||||||
checkedInProject.value = storedProject;
|
checkedInProject.value = storedProject;
|
||||||
}
|
}
|
||||||
|
|
||||||
nextTick(() => {
|
datePickerStore.initDatePicker(
|
||||||
// 달력 데이트피커를 위한 input 요소 동적 생성
|
fullCalendarRef,
|
||||||
const datePickerInput = document.createElement('input');
|
async (year, month, options) => {
|
||||||
datePickerInput.type = 'text';
|
// 데이터 다시 불러오기
|
||||||
datePickerInput.style.display = 'none';
|
await fetchData();
|
||||||
document.body.appendChild(datePickerInput);
|
|
||||||
calendarDatepicker.value = datePickerInput;
|
|
||||||
|
|
||||||
// Flatpickr 초기화 (달 선택)
|
|
||||||
fpInstance = flatpickr(calendarDatepicker.value, {
|
|
||||||
dateFormat: "Y-m",
|
|
||||||
plugins: [
|
|
||||||
new monthSelectPlugin({
|
|
||||||
shorthand: true,
|
|
||||||
dateFormat: "Y-m",
|
|
||||||
altFormat: "F Y"
|
|
||||||
})
|
|
||||||
],
|
|
||||||
onOpen: function() {
|
|
||||||
document.querySelector('.flatpickr-input').style.visibility = 'hidden';
|
|
||||||
},
|
|
||||||
onChange: function(selectedDatesArr, dateStr) {
|
|
||||||
// 선택한 달의 첫날로 달력을 이동
|
|
||||||
fullCalendarRef.value.getApi().gotoDate(dateStr + "-01");
|
|
||||||
const [year, month] = dateStr.split("-");
|
|
||||||
lastRemainingYear.value = parseInt(year, 10);
|
|
||||||
lastRemainingMonth.value = month;
|
|
||||||
loadCalendarData(lastRemainingYear.value, lastRemainingMonth.value);
|
|
||||||
},
|
|
||||||
onClose: function() {
|
|
||||||
calendarDatepicker.value.style.display = "none";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// FullCalendar 년월월(.fc-toolbar-title) 클릭 시 데이트피커 열기
|
|
||||||
const titleEl = document.querySelector('.fc-toolbar-title');
|
|
||||||
if (titleEl) {
|
|
||||||
titleEl.style.cursor = 'pointer';
|
|
||||||
titleEl.addEventListener('click', () => {
|
|
||||||
const rect = titleEl.getBoundingClientRect();
|
|
||||||
const dpEl = calendarDatepicker.value;
|
|
||||||
|
|
||||||
dpEl.style.display = 'block';
|
|
||||||
dpEl.style.position = 'fixed';
|
|
||||||
dpEl.style.top = `${rect.bottom + window.scrollY}px`;
|
|
||||||
dpEl.style.left = `${rect.left + window.scrollX}px`;
|
|
||||||
dpEl.style.transform = 'translate(-50%, -50%)';
|
|
||||||
dpEl.style.zIndex = '9999';
|
|
||||||
dpEl.style.border = 'none';
|
|
||||||
dpEl.style.outline = 'none';
|
|
||||||
dpEl.style.backgroundColor = 'transparent';
|
|
||||||
// 제목의 중앙 아래에 위치하도록 계산
|
|
||||||
// 또는 CSS transform 사용
|
|
||||||
// dpEl.style.setProperty('--left-position', `${rect.left + window.scrollX}px`);
|
|
||||||
// dpEl.style.transform = 'translateX(-50%)';
|
|
||||||
fpInstance.open();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -1,23 +1,32 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="commuter-list">
|
<div class="commuter-list">
|
||||||
<div v-for="post in project" :key="post.PROJCTSEQ"
|
<div
|
||||||
|
v-for="post in sortedProjects"
|
||||||
|
:key="post.PROJCTSEQ"
|
||||||
class="border border-2 mt-3 card p-2"
|
class="border border-2 mt-3 card p-2"
|
||||||
:style="`border-color: ${post.projctcolor} !important; color: ${post.projctcolor} !important;`"
|
:style="`border-color: ${post.projctcolor} !important; color: ${post.projctcolor} !important;`"
|
||||||
@dragover="allowDrop($event)"
|
@dragover="allowDrop($event)"
|
||||||
@drop="handleDrop($event, post)">
|
@drop="handleDrop($event, post)"
|
||||||
|
>
|
||||||
<p class="mb-1">
|
<p class="mb-1">
|
||||||
{{ post.PROJCTNAM }}
|
{{ post.PROJCTNAM }}
|
||||||
</p>
|
</p>
|
||||||
<div class="row gx-2">
|
<div class="row gx-2">
|
||||||
<div v-for="commuter in commuters.filter(c => c.PROJCTNAM === post.PROJCTNAM)" :key="commuter.COMMUTCMT" class="col-4">
|
<div
|
||||||
|
v-for="commuter in commuters.filter(c => c.PROJCTNAM === post.PROJCTNAM)"
|
||||||
|
:key="commuter.COMMUTCMT"
|
||||||
|
class="col-4"
|
||||||
|
>
|
||||||
<div class="ratio ratio-1x1">
|
<div class="ratio ratio-1x1">
|
||||||
<img :src="`${baseUrl}upload/img/profile/${commuter.profile}`"
|
<img
|
||||||
|
:src="`${baseUrl}upload/img/profile/${commuter.profile}`"
|
||||||
alt="User Profile"
|
alt="User Profile"
|
||||||
class="rounded-circle"
|
class="rounded-circle"
|
||||||
:class="isCurrentUser(commuter) ? 'cursor-pointer' : ''"
|
:class="isCurrentUser(commuter) ? 'cursor-pointer' : ''"
|
||||||
:draggable="isCurrentUser(commuter)"
|
:draggable="isCurrentUser(commuter)"
|
||||||
@dragstart="isCurrentUser(commuter) ? dragStart($event, post) : null"
|
@dragstart="isCurrentUser(commuter) ? dragStart($event, post) : null"
|
||||||
@error="$event.target.src = '/img/icons/icon.png'">
|
@error="$event.target.src = '/img/icons/icon.png'"
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -26,7 +35,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { defineProps, defineEmits } from 'vue';
|
import { defineProps, defineEmits, computed } from 'vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
project: {
|
project: {
|
||||||
@ -57,6 +66,15 @@ const props = defineProps({
|
|||||||
|
|
||||||
const emit = defineEmits(['drop', 'update:selectedProject', 'update:checkedInProject']);
|
const emit = defineEmits(['drop', 'update:selectedProject', 'update:checkedInProject']);
|
||||||
|
|
||||||
|
// 프로젝트 참여인원 순 정렬
|
||||||
|
const sortedProjects = computed(() => {
|
||||||
|
const projectList = Array.isArray(props.project) ? props.project :
|
||||||
|
Object.values(props.project || {});
|
||||||
|
|
||||||
|
return projectList
|
||||||
|
.filter(item => item && typeof item === 'object')
|
||||||
|
.sort((a, b) => (b.participant_count || 0) - (a.participant_count || 0));
|
||||||
|
});
|
||||||
// 현재 사용자 확인
|
// 현재 사용자 확인
|
||||||
const isCurrentUser = (commuter) => {
|
const isCurrentUser = (commuter) => {
|
||||||
return props.user && commuter && commuter.MEMBERSEQ === props.user.id;
|
return props.user && commuter && commuter.MEMBERSEQ === props.user.id;
|
||||||
@ -79,4 +97,6 @@ const handleDrop = (event, targetProject) => {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
emit('drop', { event, targetProject });
|
emit('drop', { event, targetProject });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -94,7 +94,7 @@
|
|||||||
<template #title> 프로젝트 수정 </template>
|
<template #title> 프로젝트 수정 </template>
|
||||||
<template #body>
|
<template #body>
|
||||||
<FormInput
|
<FormInput
|
||||||
title="이름"
|
title="프로젝트명"
|
||||||
name="name"
|
name="name"
|
||||||
:is-essential="true"
|
:is-essential="true"
|
||||||
:is-alert="nameAlert"
|
:is-alert="nameAlert"
|
||||||
@ -292,25 +292,27 @@ const selectedUsers = ref({
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const startDateInput = ref(null);
|
const isKakaoMapLoaded = ref(false);
|
||||||
const endDateInput = ref(null);
|
|
||||||
|
|
||||||
// DOM 요소에 직접 접근하기 위한 변수들
|
const startDateInput = ref(null);
|
||||||
let startInputElement = null;
|
const endDateInput = ref(null);
|
||||||
let endInputElement = null;
|
|
||||||
|
// DOM 요소에 직접 접근하기 위한 변수들
|
||||||
|
let startInputElement = null;
|
||||||
|
let endInputElement = null;
|
||||||
|
|
||||||
|
|
||||||
const openStartDatePicker = () => {
|
const openStartDatePicker = () => {
|
||||||
if (startInputElement) {
|
if (startInputElement) {
|
||||||
startInputElement.showPicker();
|
startInputElement.showPicker();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const openEndDatePicker = () => {
|
const openEndDatePicker = () => {
|
||||||
if (endInputElement) {
|
if (endInputElement) {
|
||||||
endInputElement.showPicker();
|
endInputElement.showPicker();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// 사용자 목록 업데이트 핸들러
|
// 사용자 목록 업데이트 핸들러
|
||||||
@ -538,30 +540,19 @@ const handleUpdate = async () => {
|
|||||||
|
|
||||||
// 주소를 좌표로 변환하는 함수
|
// 주소를 좌표로 변환하는 함수
|
||||||
const convertAddressToCoordinates = () => {
|
const convertAddressToCoordinates = () => {
|
||||||
// kakao maps API가 로드되었는지 확인
|
if (!window.kakao || !window.kakao.maps) {
|
||||||
if (window.kakao && window.kakao.maps) {
|
return;
|
||||||
const geocoder = new window.kakao.maps.services.Geocoder();
|
|
||||||
geocoder.addressSearch(props.address, (result, status) => {
|
|
||||||
if (status === window.kakao.maps.services.Status.OK) {
|
|
||||||
coordinates.value = {
|
|
||||||
lat: parseFloat(result[0].y),
|
|
||||||
lng: parseFloat(result[0].x)
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
// 기본 좌표 설정 (본사)
|
|
||||||
coordinates.value = {
|
|
||||||
lat: 37.2108651707078,
|
|
||||||
lng: 127.089445559923
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// 기본 좌표로 설정
|
|
||||||
coordinates.value = {
|
|
||||||
lat: 37.2108651707078,
|
|
||||||
lng: 127.089445559923
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const geocoder = new window.kakao.maps.services.Geocoder();
|
||||||
|
geocoder.addressSearch(props.address, (result, status) => {
|
||||||
|
if (status === window.kakao.maps.services.Status.OK) {
|
||||||
|
coordinates.value = {
|
||||||
|
lat: parseFloat(result[0].y),
|
||||||
|
lng: parseFloat(result[0].x)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onLoadKakaoMap = (mapRef) => {
|
const onLoadKakaoMap = (mapRef) => {
|
||||||
@ -605,8 +596,6 @@ onMounted(async () => {
|
|||||||
await userStore.userInfo();
|
await userStore.userInfo();
|
||||||
user.value = userStore.user;
|
user.value = userStore.user;
|
||||||
|
|
||||||
convertAddressToCoordinates();
|
|
||||||
|
|
||||||
if (startDateInput.value) {
|
if (startDateInput.value) {
|
||||||
// FormInput 내부 input 찾기
|
// FormInput 내부 input 찾기
|
||||||
startInputElement = startDateInput.value.$el.querySelector('input[type="date"]');
|
startInputElement = startDateInput.value.$el.querySelector('input[type="date"]');
|
||||||
@ -615,6 +604,14 @@ onMounted(async () => {
|
|||||||
if (endDateInput.value) {
|
if (endDateInput.value) {
|
||||||
endInputElement = endDateInput.value.$el.querySelector('input[type="date"]');
|
endInputElement = endDateInput.value.$el.querySelector('input[type="date"]');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const checkKakaoMapsLoaded = () => {
|
||||||
|
if (window.kakao && window.kakao.maps && window.kakao.maps.services) {
|
||||||
|
convertAddressToCoordinates();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
checkKakaoMapsLoaded();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
<div v-for="post in projectStore.projectList" :key="post.PROJCTSEQ">
|
<div v-for="post in projectStore.projectList" :key="post.PROJCTSEQ">
|
||||||
<ProjectCard
|
<ProjectCard
|
||||||
:title="post.PROJCTNAM"
|
:title="post.PROJCTNAM"
|
||||||
:description="post.PROJCTDES"
|
:description="post.PROJCTDES ?? ''"
|
||||||
:strdate="post.PROJCTSTR"
|
:strdate="post.PROJCTSTR"
|
||||||
:enddate="post.PROJCTEND"
|
:enddate="post.PROJCTEND"
|
||||||
:address="post.PROJCTARR"
|
:address="post.PROJCTARR"
|
||||||
@ -37,7 +37,7 @@
|
|||||||
<template #title> 프로젝트 등록 </template>
|
<template #title> 프로젝트 등록 </template>
|
||||||
<template #body>
|
<template #body>
|
||||||
<FormInput
|
<FormInput
|
||||||
title="이름"
|
title="프로젝트명"
|
||||||
name="name"
|
name="name"
|
||||||
:is-essential="true"
|
:is-essential="true"
|
||||||
:is-alert="nameAlert"
|
:is-alert="nameAlert"
|
||||||
|
|||||||
@ -38,6 +38,7 @@
|
|||||||
@update:alert="passwordAlert = $event"
|
@update:alert="passwordAlert = $event"
|
||||||
:value="password"
|
:value="password"
|
||||||
/>
|
/>
|
||||||
|
<span v-if="passwordError" class="invalid-feedback d-block">{{ passwordError }}</span>
|
||||||
|
|
||||||
<UserFormInput
|
<UserFormInput
|
||||||
title="비밀번호 확인"
|
title="비밀번호 확인"
|
||||||
@ -180,6 +181,7 @@
|
|||||||
const id = ref('');
|
const id = ref('');
|
||||||
const idError = ref('');
|
const idError = ref('');
|
||||||
const password = ref('');
|
const password = ref('');
|
||||||
|
const passwordError = ref('');
|
||||||
const passwordcheck = ref('');
|
const passwordcheck = ref('');
|
||||||
const passwordcheckError = ref('');
|
const passwordcheckError = ref('');
|
||||||
const pwhintRes = ref('');
|
const pwhintRes = ref('');
|
||||||
@ -199,6 +201,7 @@
|
|||||||
const idAlert = ref(false);
|
const idAlert = ref(false);
|
||||||
const idErrorAlert = ref(false);
|
const idErrorAlert = ref(false);
|
||||||
const passwordAlert = ref(false);
|
const passwordAlert = ref(false);
|
||||||
|
const passwordErrorAlert = ref(false);
|
||||||
const passwordcheckAlert = ref(false);
|
const passwordcheckAlert = ref(false);
|
||||||
const passwordcheckErrorAlert = ref(false); // 비밀번호 확인 오류 메시지
|
const passwordcheckErrorAlert = ref(false); // 비밀번호 확인 오류 메시지
|
||||||
const pwhintResAlert = ref(false);
|
const pwhintResAlert = ref(false);
|
||||||
@ -328,11 +331,17 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
watch(password, (newValue) => {
|
||||||
|
if (newValue.length >= 4) {
|
||||||
|
passwordErrorAlert.value = false;
|
||||||
|
passwordError.value = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// 회원가입
|
// 회원가입
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
await checkColorDuplicate();
|
await checkColorDuplicate();
|
||||||
|
|
||||||
idAlert.value = id.value.trim() === '';
|
idAlert.value = id.value.trim() === '';
|
||||||
passwordAlert.value = password.value.trim() === '';
|
passwordAlert.value = password.value.trim() === '';
|
||||||
passwordcheckAlert.value = passwordcheck.value.trim() === '';
|
passwordcheckAlert.value = passwordcheck.value.trim() === '';
|
||||||
@ -342,6 +351,14 @@
|
|||||||
addressAlert.value = address.value.trim() === '';
|
addressAlert.value = address.value.trim() === '';
|
||||||
phoneAlert.value = phone.value.trim() === '';
|
phoneAlert.value = phone.value.trim() === '';
|
||||||
|
|
||||||
|
// 비밀번호 길이 체크 로직 추가
|
||||||
|
if (password.value.length < 4) {
|
||||||
|
passwordErrorAlert.value = true;
|
||||||
|
passwordError.value = '비밀번호는 4자리 이상이어야 합니다.';
|
||||||
|
} else {
|
||||||
|
passwordError.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
if (!/^\d+$/.test(phone.value)) {
|
if (!/^\d+$/.test(phone.value)) {
|
||||||
phoneAlert.value = true;
|
phoneAlert.value = true;
|
||||||
} else {
|
} else {
|
||||||
@ -362,6 +379,7 @@
|
|||||||
idAlert.value ||
|
idAlert.value ||
|
||||||
idErrorAlert.value ||
|
idErrorAlert.value ||
|
||||||
passwordAlert.value ||
|
passwordAlert.value ||
|
||||||
|
passwordcErrorAlert.value ||
|
||||||
passwordcheckAlert.value ||
|
passwordcheckAlert.value ||
|
||||||
passwordcheckErrorAlert.value ||
|
passwordcheckErrorAlert.value ||
|
||||||
pwhintResAlert.value ||
|
pwhintResAlert.value ||
|
||||||
|
|||||||
@ -126,7 +126,7 @@ watch(() => props.data.localVote.total_voted, () => {
|
|||||||
|
|
||||||
// 종료 체크 함수
|
// 종료 체크 함수
|
||||||
const checkVoteCompletion = () => {
|
const checkVoteCompletion = () => {
|
||||||
if (props.data.localVote.total_votable === props.data.localVote.total_voted && props.data.localVote.LOCVOTDDT == '') {
|
if (props.data.localVote.total_votable === props.data.localVote.total_voted && props.data.localVote.LOCVOTDDT == null) {
|
||||||
emit('voteEnded', { id: props.data.localVote.LOCVOTSEQ });
|
emit('voteEnded', { id: props.data.localVote.LOCVOTSEQ });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
95
src/stores/useDatePicker.js
Normal file
95
src/stores/useDatePicker.js
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
작성자 : 박지윤
|
||||||
|
작성일 : 2025-03-25
|
||||||
|
수정자 :
|
||||||
|
수정일 :
|
||||||
|
설명 : 달력 데이트 피커
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import flatpickr from 'flatpickr';
|
||||||
|
import monthSelectPlugin from 'flatpickr/dist/plugins/monthSelect/index';
|
||||||
|
import 'flatpickr/dist/flatpickr.min.css';
|
||||||
|
import 'flatpickr/dist/plugins/monthSelect/style.css';
|
||||||
|
|
||||||
|
export function useDatePicker() {
|
||||||
|
let fpInstance = null;
|
||||||
|
const calendarDatepicker = ref(null);
|
||||||
|
|
||||||
|
const initDatePicker = (fullCalendarRef, onDateChange, options = {}) => {
|
||||||
|
// input 요소 동적 생성
|
||||||
|
const datePickerInput = document.createElement('input');
|
||||||
|
datePickerInput.type = 'text';
|
||||||
|
datePickerInput.style.display = 'none';
|
||||||
|
document.body.appendChild(datePickerInput);
|
||||||
|
calendarDatepicker.value = datePickerInput;
|
||||||
|
|
||||||
|
// Flatpickr 초기화
|
||||||
|
fpInstance = flatpickr(calendarDatepicker.value, {
|
||||||
|
dateFormat: "Y-m",
|
||||||
|
plugins: [
|
||||||
|
new monthSelectPlugin({
|
||||||
|
shorthand: true,
|
||||||
|
dateFormat: "Y-m",
|
||||||
|
altFormat: "F Y"
|
||||||
|
})
|
||||||
|
],
|
||||||
|
onOpen: function() {
|
||||||
|
document.querySelector('.flatpickr-input').style.visibility = 'hidden';
|
||||||
|
},
|
||||||
|
onChange: function(selectedDatesArr, dateStr) {
|
||||||
|
// 선택한 달의 첫날로 달력을 이동
|
||||||
|
if (fullCalendarRef.value) {
|
||||||
|
fullCalendarRef.value.getApi().gotoDate(dateStr + "-01");
|
||||||
|
}
|
||||||
|
|
||||||
|
const [year, month] = dateStr.split("-");
|
||||||
|
|
||||||
|
// onDateChange가 함수인 경우에만 호출
|
||||||
|
if (typeof onDateChange === 'function') {
|
||||||
|
onDateChange(parseInt(year, 10), month, options);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClose: function() {
|
||||||
|
if (calendarDatepicker.value) {
|
||||||
|
calendarDatepicker.value.style.display = "none";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
|
||||||
|
// FullCalendar 년월월(.fc-toolbar-title) 클릭 시 데이트피커 열기
|
||||||
|
const titleEl = document.querySelector('.fc-toolbar-title');
|
||||||
|
if (titleEl) {
|
||||||
|
titleEl.style.cursor = 'pointer';
|
||||||
|
titleEl.addEventListener('click', () => {
|
||||||
|
const rect = titleEl.getBoundingClientRect();
|
||||||
|
const dpEl = calendarDatepicker.value;
|
||||||
|
|
||||||
|
dpEl.style.display = 'block';
|
||||||
|
dpEl.style.position = 'fixed';
|
||||||
|
dpEl.style.top = `${rect.bottom + window.scrollY}px`;
|
||||||
|
dpEl.style.left = `${rect.left + window.scrollX}px`;
|
||||||
|
dpEl.style.transform = 'translate(-50%, -50%)';
|
||||||
|
dpEl.style.zIndex = '9999';
|
||||||
|
dpEl.style.border = 'none';
|
||||||
|
dpEl.style.outline = 'none';
|
||||||
|
dpEl.style.backgroundColor = 'transparent';
|
||||||
|
|
||||||
|
fpInstance.open();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeDatePicker = () => {
|
||||||
|
if (fpInstance) {
|
||||||
|
fpInstance.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
initDatePicker,
|
||||||
|
closeDatePicker,
|
||||||
|
calendarDatepicker
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -30,6 +30,7 @@
|
|||||||
autocomplete="new-password"
|
autocomplete="new-password"
|
||||||
v-model="password"
|
v-model="password"
|
||||||
placeholder="비밀번호 입력"
|
placeholder="비밀번호 입력"
|
||||||
|
maxlength="8"
|
||||||
@input="
|
@input="
|
||||||
password = password.replace(/\s/g, '');
|
password = password.replace(/\s/g, '');
|
||||||
inputCheck();
|
inputCheck();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user