Merge branch 'main' into board-ji

This commit is contained in:
dyhj625 2025-03-28 16:33:49 +09:00
commit 5c5a2c63ef
9 changed files with 181 additions and 146 deletions

View File

@ -539,7 +539,7 @@
.map { .map {
top: -160px; top: -160px;
left: -5px; left: 90px;
} }
@keyframes sparkle { @keyframes sparkle {

View File

@ -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>

View File

@ -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,

View File

@ -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();
}); });

View 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>

View File

@ -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>

View File

@ -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 {

View File

@ -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>

View File

@ -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">
<!-- 리스트 갯수 선택 --> <!-- 리스트 갯수 선택 -->