Merge branch 'main' into board-ji
This commit is contained in:
commit
5c5a2c63ef
@ -539,7 +539,7 @@
|
|||||||
|
|
||||||
.map {
|
.map {
|
||||||
top: -160px;
|
top: -160px;
|
||||||
left: -5px;
|
left: 90px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes sparkle {
|
@keyframes sparkle {
|
||||||
|
|||||||
@ -63,8 +63,8 @@
|
|||||||
|
|
||||||
<span class="fw-bold">{{ commuter.memberName }}</span>
|
<span class="fw-bold">{{ commuter.memberName }}</span>
|
||||||
|
|
||||||
<div class="ms-auto text-start fw-bold">
|
<div class="ms-auto text-start fw-bold d-flex flex-column align-items-end">
|
||||||
<div class="d-flex gap-1 align-items-center ">
|
<div class="d-flex gap-1 align-items-center">
|
||||||
출근 :
|
출근 :
|
||||||
<div class="text-white rounded px-2" :style="`background: ${commuter.projctcolor} !important;`">
|
<div class="text-white rounded px-2" :style="`background: ${commuter.projctcolor} !important;`">
|
||||||
{{ commuter.PROJCTNAM }}
|
{{ commuter.PROJCTNAM }}
|
||||||
@ -431,9 +431,3 @@ onMounted(async () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
|
||||||
.fc-daygrid-day[data-has-commuters="true"] {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@ -14,7 +14,6 @@
|
|||||||
:value="computedValue"
|
:value="computedValue"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:maxLength="maxlength"
|
:maxLength="maxlength"
|
||||||
:minLength="minlength"
|
|
||||||
:placeholder="title"
|
:placeholder="title"
|
||||||
@blur="$emit('blur')"
|
@blur="$emit('blur')"
|
||||||
/>
|
/>
|
||||||
@ -30,7 +29,6 @@
|
|||||||
:value="computedValue"
|
:value="computedValue"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:maxLength="maxlength"
|
:maxLength="maxlength"
|
||||||
:minLength="minlength"
|
|
||||||
:placeholder="title"
|
:placeholder="title"
|
||||||
@blur="$emit('blur')"
|
@blur="$emit('blur')"
|
||||||
@click="handleDateClick"
|
@click="handleDateClick"
|
||||||
@ -81,11 +79,6 @@
|
|||||||
default: 30,
|
default: 30,
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
minlength: {
|
|
||||||
type: Number,
|
|
||||||
default: 4,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
isAlert: {
|
isAlert: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
|
|||||||
@ -32,36 +32,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- 주소 -->
|
<!-- 주소 -->
|
||||||
<div class="d-flex flex-sm-row align-items-center pb-2">
|
<div class="d-flex flex-sm-row align-items-center pb-2">
|
||||||
<div class="d-flex" @click.stop="isPopoverVisible = !isPopoverVisible">
|
<MapPopover
|
||||||
<i class="bx bxs-map cursor-pointer" ref="mapIconRef"></i>
|
:address="address"
|
||||||
<div class="ms-2">주소</div>
|
:ref="mapIconRef"
|
||||||
</div>
|
>
|
||||||
<div class="ms-12 position-relative">
|
<template #trigger>
|
||||||
{{ address }} {{ addressdtail }}
|
<div class="d-flex align-items-center cursor-pointer">
|
||||||
<!-- 팝오버 -->
|
<i class="bx bxs-map"></i>
|
||||||
<div v-if="isPopoverVisible" class="position-absolute map ">
|
<div class="ms-2">주소</div>
|
||||||
<button type="button" class="btn-close popover-close" @click.stop="isPopoverVisible = !isPopoverVisible"></button>
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body p-1">
|
|
||||||
<KakaoMap
|
|
||||||
v-if="coordinates"
|
|
||||||
:lat="coordinates.lat"
|
|
||||||
:lng="coordinates.lng"
|
|
||||||
class="w-px-250 h-px-200"
|
|
||||||
@onLoadKakaoMap="onLoadKakaoMap"
|
|
||||||
>
|
|
||||||
<KakaoMapMarker
|
|
||||||
:lat="coordinates.lat"
|
|
||||||
:lng="coordinates.lng"
|
|
||||||
/>
|
|
||||||
</KakaoMap>
|
|
||||||
<div class="position-absolute top-50 translate-middle-y end-0 me-3 z-1 d-flex flex-column gap-1">
|
|
||||||
<button class="btn-secondary border-none" @click="zoomOut">+</button>
|
|
||||||
<button class="btn-secondary border-none" @click="zoomIn">-</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
|
</MapPopover>
|
||||||
|
<div class="ms-12">
|
||||||
|
{{ address }} {{ addressdtail }}
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn ms-auto text-white" :style="`background-color: ${projctColor} !important;`" @click.stop="openModal"><i class='bx bx-child'></i></button>
|
<button type="button" class="btn ms-auto text-white" :style="`background-color: ${projctColor} !important;`" @click.stop="openModal"><i class='bx bx-child'></i></button>
|
||||||
</div>
|
</div>
|
||||||
@ -195,6 +178,7 @@ import DeleteBtn from '../button/DeleteBtn.vue';
|
|||||||
import FormInput from '@c/input/FormInput.vue';
|
import FormInput from '@c/input/FormInput.vue';
|
||||||
import FormSelect from '@c/input/FormSelect.vue';
|
import FormSelect from '@c/input/FormSelect.vue';
|
||||||
import ArrInput from '@c/input/ArrInput.vue';
|
import ArrInput from '@c/input/ArrInput.vue';
|
||||||
|
import MapPopover from '@c/map/MapPopover.vue';
|
||||||
import { useToastStore } from '@s/toastStore';
|
import { useToastStore } from '@s/toastStore';
|
||||||
import { useUserInfoStore } from '@/stores/useUserInfoStore';
|
import { useUserInfoStore } from '@/stores/useUserInfoStore';
|
||||||
import commonApi, { refreshColorList } from '@/common/commonApi';
|
import commonApi, { refreshColorList } from '@/common/commonApi';
|
||||||
@ -270,11 +254,7 @@ const emit = defineEmits(['update']);
|
|||||||
const isModalOpen = ref(false);
|
const isModalOpen = ref(false);
|
||||||
const logData = ref([]);
|
const logData = ref([]);
|
||||||
|
|
||||||
// 주소 팝오버 상태
|
|
||||||
const isPopoverVisible = ref(false);
|
|
||||||
const map = ref();
|
|
||||||
const mapIconRef = ref(null);
|
const mapIconRef = ref(null);
|
||||||
const coordinates = ref(null);
|
|
||||||
|
|
||||||
// 수정 모달 상태
|
// 수정 모달 상태
|
||||||
const isEditModalOpen = ref(false);
|
const isEditModalOpen = ref(false);
|
||||||
@ -292,8 +272,6 @@ const selectedUsers = ref({
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const isKakaoMapLoaded = ref(false);
|
|
||||||
|
|
||||||
const startDateInput = ref(null);
|
const startDateInput = ref(null);
|
||||||
const endDateInput = ref(null);
|
const endDateInput = ref(null);
|
||||||
|
|
||||||
@ -538,43 +516,6 @@ const handleUpdate = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 주소를 좌표로 변환하는 함수
|
|
||||||
const convertAddressToCoordinates = () => {
|
|
||||||
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)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const onLoadKakaoMap = (mapRef) => {
|
|
||||||
map.value = mapRef;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 지도 확대
|
|
||||||
const zoomIn = () => {
|
|
||||||
if (map.value) {
|
|
||||||
const level = map.value.getLevel();
|
|
||||||
map.value.setLevel(level + 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 지도 축소
|
|
||||||
const zoomOut = () => {
|
|
||||||
if (map.value) {
|
|
||||||
const level = map.value.getLevel();
|
|
||||||
map.value.setLevel(level - 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 프로젝트 삭제
|
// 프로젝트 삭제
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
$api.patch('project/delete', {
|
$api.patch('project/delete', {
|
||||||
@ -605,13 +546,6 @@ onMounted(async () => {
|
|||||||
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();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
109
src/components/map/MapPopover.vue
Normal file
109
src/components/map/MapPopover.vue
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<template>
|
||||||
|
<div class="position-relative">
|
||||||
|
<div @click="togglePopover">
|
||||||
|
<slot name="trigger">
|
||||||
|
<i class="bx bxs-map"></i>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="isVisible"
|
||||||
|
class="position-absolute map z-3"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn-close popover-close"
|
||||||
|
@click="togglePopover"
|
||||||
|
></button>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body p-1">
|
||||||
|
<KakaoMap
|
||||||
|
v-if="coordinates"
|
||||||
|
:lat="coordinates.lat"
|
||||||
|
:lng="coordinates.lng"
|
||||||
|
class="w-px-250 h-px-200"
|
||||||
|
@onLoadKakaoMap="onLoadKakaoMap"
|
||||||
|
>
|
||||||
|
<KakaoMapMarker
|
||||||
|
:lat="coordinates.lat"
|
||||||
|
:lng="coordinates.lng"
|
||||||
|
/>
|
||||||
|
</KakaoMap>
|
||||||
|
|
||||||
|
<div class="position-absolute top-50 translate-middle-y end-0 me-3 z-1 d-flex flex-column gap-1">
|
||||||
|
<button class="btn-secondary border-none" @click="zoomOut">+</button>
|
||||||
|
<button class="btn-secondary border-none" @click="zoomIn">-</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
import { KakaoMap, KakaoMapMarker } from 'vue3-kakao-maps';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
address: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const isVisible = ref(false);
|
||||||
|
const coordinates = ref(null);
|
||||||
|
const map = ref(null);
|
||||||
|
|
||||||
|
// 주소를 좌표로 변환하는 함수
|
||||||
|
const convertAddressToCoordinates = () => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!window.kakao || !window.kakao.maps) {
|
||||||
|
reject(new Error('Kakao Maps not loaded'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const geocoder = new window.kakao.maps.services.Geocoder();
|
||||||
|
geocoder.addressSearch(props.address, (result, status) => {
|
||||||
|
if (status === window.kakao.maps.services.Status.OK) {
|
||||||
|
resolve({
|
||||||
|
lat: parseFloat(result[0].y),
|
||||||
|
lng: parseFloat(result[0].x)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
reject(new Error('Address conversion failed'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const togglePopover = () => {
|
||||||
|
isVisible.value = !isVisible.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onLoadKakaoMap = (mapRef) => {
|
||||||
|
map.value = mapRef;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 지도 확대
|
||||||
|
const zoomIn = () => {
|
||||||
|
if (map.value) {
|
||||||
|
const level = map.value.getLevel();
|
||||||
|
map.value.setLevel(level + 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 지도 축소
|
||||||
|
const zoomOut = () => {
|
||||||
|
if (map.value) {
|
||||||
|
const level = map.value.getLevel();
|
||||||
|
map.value.setLevel(level - 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 컴포넌트 마운트 시 좌표 변환
|
||||||
|
onMounted(async () => {
|
||||||
|
coordinates.value = await convertAddressToCoordinates();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@ -1,17 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<form @submit.prevent="search">
|
<form @submit.prevent="search">
|
||||||
<div class="input-group mb-3 d-flex">
|
<div class="input-group mb-3 d-flex">
|
||||||
<input
|
<input type="text" class="form-control" placeholder="Search" v-model="searchQuery" @input="preventLeadingSpace" />
|
||||||
type="text"
|
<button type="submit" class="btn btn-primary">
|
||||||
class="form-control"
|
|
||||||
placeholder="Search"
|
|
||||||
v-model="searchQuery"
|
|
||||||
@input="preventLeadingSpace"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
class="btn btn-primary"
|
|
||||||
>
|
|
||||||
<i class="bx bx-search bx-md"></i>
|
<i class="bx bx-search bx-md"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -19,44 +10,54 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from "vue";
|
import { ref, watch } from 'vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
maxlength: {
|
maxlength: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 30,
|
default: 30,
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
});
|
initKeyword: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const emits = defineEmits(["update:data"]);
|
const emits = defineEmits(['update:data']);
|
||||||
const searchQuery = ref("");
|
const searchQuery = ref('');
|
||||||
|
|
||||||
// 검색 실행 함수 (버튼 클릭 or 엔터 공통)
|
watch(
|
||||||
const search = () => {
|
() => props.initKeyword,
|
||||||
const trimmedQuery = searchQuery.value.trimStart();
|
(newVal, oldVal) => {
|
||||||
if (trimmedQuery === "") {
|
searchQuery.value = newVal;
|
||||||
emits("update:data", "");
|
},
|
||||||
return;
|
);
|
||||||
}
|
|
||||||
if (trimmedQuery.length < 2 ) {
|
|
||||||
alert("검색어는 최소 2글자 이상 입력해주세요.");
|
|
||||||
searchQuery.value = '';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 길이 제한 처리
|
// 검색 실행 함수 (버튼 클릭 or 엔터 공통)
|
||||||
if (trimmedQuery.length > props.maxlength) {
|
const search = () => {
|
||||||
searchQuery.value = trimmedQuery.slice(0, props.maxlength);
|
const trimmedQuery = searchQuery.value.trimStart();
|
||||||
} else {
|
if (trimmedQuery === '') {
|
||||||
searchQuery.value = trimmedQuery;
|
emits('update:data', '');
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
if (trimmedQuery.length < 2) {
|
||||||
|
alert('검색어는 최소 2글자 이상 입력해주세요.');
|
||||||
|
searchQuery.value = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
emits("update:data", searchQuery.value);
|
// 길이 제한 처리
|
||||||
};
|
if (trimmedQuery.length > props.maxlength) {
|
||||||
|
searchQuery.value = trimmedQuery.slice(0, props.maxlength);
|
||||||
|
} else {
|
||||||
|
searchQuery.value = trimmedQuery;
|
||||||
|
}
|
||||||
|
|
||||||
// 좌측 공백 제거
|
emits('update:data', searchQuery.value);
|
||||||
const preventLeadingSpace = () => {
|
};
|
||||||
searchQuery.value = searchQuery.value.trimStart();
|
|
||||||
};
|
// 좌측 공백 제거
|
||||||
|
const preventLeadingSpace = () => {
|
||||||
|
searchQuery.value = searchQuery.value.trimStart();
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -332,9 +332,12 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
watch(password, (newValue) => {
|
watch(password, (newValue) => {
|
||||||
if (newValue.length >= 4) {
|
if (newValue && newValue.length >= 4) {
|
||||||
passwordErrorAlert.value = false;
|
passwordErrorAlert.value = false;
|
||||||
passwordError.value = '';
|
passwordError.value = '';
|
||||||
|
} else if (newValue && newValue.length < 4) {
|
||||||
|
passwordErrorAlert.value = true;
|
||||||
|
passwordError.value = '비밀번호는 4자리 이상이어야 합니다.';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -342,6 +345,7 @@
|
|||||||
// 회원가입
|
// 회원가입
|
||||||
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() === '';
|
||||||
@ -351,8 +355,8 @@
|
|||||||
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) {
|
if (password.value && password.value.length < 4) {
|
||||||
passwordErrorAlert.value = true;
|
passwordErrorAlert.value = true;
|
||||||
passwordError.value = '비밀번호는 4자리 이상이어야 합니다.';
|
passwordError.value = '비밀번호는 4자리 이상이어야 합니다.';
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="card mb-6" :class="{'ps-none opacity-50': data.localVote.LOCVOTDDT && (topVoters.length == 1 || data.localVote.LOCVOTRES || voteResult == 0)}">
|
<div class="card mb-6" :class="{'disabled-class': data.localVote.LOCVOTDDT && (topVoters.length == 1 || data.localVote.LOCVOTRES || voteResult == 0)}">
|
||||||
<div class="card-body" v-if="!data.localVote.LOCVOTDEL" >
|
<div class="card-body" v-if="!data.localVote.LOCVOTDEL" >
|
||||||
<h5 class="card-title mb-1">
|
<h5 class="card-title mb-1">
|
||||||
<div class="list-unstyled users-list d-flex align-items-center gap-1">
|
<div class="list-unstyled users-list d-flex align-items-center gap-1">
|
||||||
@ -59,7 +59,7 @@
|
|||||||
:data="data.voteMembers"/>
|
:data="data.voteMembers"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="card-body">
|
<div v-else class="card-body disabled-class">
|
||||||
<h5>{{ data.localVote.LOCVOTTTL }}</h5>
|
<h5>{{ data.localVote.LOCVOTTTL }}</h5>
|
||||||
삭제된 투표입니다.
|
삭제된 투표입니다.
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
<div class="card-header d-flex flex-column">
|
<div class="card-header d-flex flex-column">
|
||||||
<!-- 검색창 -->
|
<!-- 검색창 -->
|
||||||
<div class="mb-3 w-100">
|
<div class="mb-3 w-100">
|
||||||
<search-bar @update:data="search" @keyup.enter="searchOnEnter" class="flex-grow-1" />
|
<search-bar @update:data="search" @keyup.enter="searchOnEnter" :initKeyword="searchText" class="flex-grow-1" />
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-items-center" style="gap: 15px">
|
<div class="d-flex align-items-center" style="gap: 15px">
|
||||||
<!-- 리스트 갯수 선택 -->
|
<!-- 리스트 갯수 선택 -->
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user