470 lines
15 KiB
Vue
470 lines
15 KiB
Vue
<template>
|
|
<div class="card mb-3 shadow-sm border" :class="isProjectExpired ? 'end-project' : ''">
|
|
<div class="row g-0">
|
|
<div class="card-body">
|
|
<!-- 제목 -->
|
|
<div class="d-flex justify-content-between ">
|
|
<h5 class="card-title fw-bold">
|
|
{{ title }}
|
|
</h5>
|
|
<p v-if="isProjectExpired" class="btn-icon btn-danger rounded-2"><i class='bx bx-power-off'></i></p>
|
|
<div v-if="!isProjectExpired" class="d-flex gap-1">
|
|
<EditBtn @click.stop="openEditModal" />
|
|
<DeleteBtn v-if="isProjectCreator" @click.stop="handleDelete" class="ms-1"/>
|
|
</div>
|
|
</div>
|
|
<!-- 날짜 -->
|
|
<div class="d-flex flex-column flex-sm-row align-items-center pb-2">
|
|
<i class="bx bx-calendar"></i>
|
|
<div class="ms-2">날짜</div>
|
|
<div class="ms-12">{{ strdate }} ~ {{ enddate }}</div>
|
|
</div>
|
|
<!-- 참여자 -->
|
|
<div class="d-flex flex-column flex-sm-row align-items-center pb-2">
|
|
<i class="bx bxs-user"></i>
|
|
<div class="ms-2">참여자</div>
|
|
<UserList :projctSeq="projctSeq" :showOnlyActive="isProjectExpired" class="ms-8 mb-0" />
|
|
</div>
|
|
<!-- 설명 -->
|
|
<div class="d-flex flex-column flex-sm-row align-items-center pb-2">
|
|
<i class="bx bx-detail"></i>
|
|
<div class="ms-2">설명</div>
|
|
<div class="ms-12">{{ description }}</div>
|
|
</div>
|
|
<!-- 주소 -->
|
|
<div class="d-flex flex-column flex-sm-row align-items-center pb-2">
|
|
<div class="d-flex" @click.stop="isPopoverVisible = !isPopoverVisible">
|
|
<i class="bx bxs-map cursor-pointer" ref="mapIconRef"></i>
|
|
<div class="ms-2">주소</div>
|
|
</div>
|
|
<div class="ms-12 position-relative">
|
|
{{ address }} {{ addressdtail }}
|
|
<!-- 팝오버 -->
|
|
<div v-if="isPopoverVisible" class="position-absolute map ">
|
|
<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>
|
|
<button type="button" class="btn ms-auto text-white" :style="`background-color: ${projctColor} !important;`" @click.stop="openModal">log</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 로그 모달 -->
|
|
<CenterModal :display="isModalOpen" @close="closeModal">
|
|
<template #title> Log </template>
|
|
<template #body>
|
|
<div v-if="logData.length > 0">
|
|
<div
|
|
v-for="(log, index) in logData"
|
|
:key="index"
|
|
class="ms-4 mt-2 border p-3"
|
|
>
|
|
<p class="mb-1">{{ log.logDate }}</p>
|
|
<strong>{{ log.logMessage }}</strong>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<template #footer>
|
|
<BackBtn @click="closeModal" />
|
|
</template>
|
|
</CenterModal>
|
|
|
|
<!-- 수정 모달 -->
|
|
<CenterModal :display="isEditModalOpen" @close="closeEditModal">
|
|
<template #title> 프로젝트 수정 </template>
|
|
<template #body>
|
|
<FormInput
|
|
title="이름"
|
|
name="name"
|
|
:is-essential="true"
|
|
:is-alert="nameAlert"
|
|
:modelValue="selectedProject.PROJCTNAM"
|
|
@update:modelValue="selectedProject.PROJCTNAM = $event"
|
|
@update:alert="nameAlert = $event"
|
|
/>
|
|
|
|
<FormSelect
|
|
title="컬러"
|
|
name="color"
|
|
:is-essential="true"
|
|
:is-label="true"
|
|
:is-common="true"
|
|
:data="allColors"
|
|
:value="selectedProject.PROJCTCOL"
|
|
@update:data="selectedProject.PROJCTCOL = $event"
|
|
/>
|
|
|
|
<FormInput
|
|
title="시작일"
|
|
type="date"
|
|
name="startDay"
|
|
:is-essential="true"
|
|
:modelValue="selectedProject.PROJCTSTR"
|
|
@update:modelValue="selectedProject.PROJCTSTR = $event"
|
|
/>
|
|
|
|
<FormInput
|
|
title="종료일"
|
|
type="date"
|
|
name="endDay"
|
|
:min="todays"
|
|
:modelValue="selectedProject.PROJCTEND"
|
|
@update:modelValue="selectedProject.PROJCTEND = $event"
|
|
/>
|
|
|
|
<FormInput
|
|
title="설명"
|
|
name="description"
|
|
:modelValue="selectedProject.PROJCTDES"
|
|
@update:modelValue="selectedProject.PROJCTDES = $event"
|
|
/>
|
|
|
|
<ArrInput
|
|
title="주소"
|
|
name="address"
|
|
:is-essential="true"
|
|
:is-row="true"
|
|
:modelValue="{
|
|
address: selectedProject.PROJCTARR,
|
|
detailAddress: selectedProject.PROJCTDTL,
|
|
postcode: selectedProject.PROJCTZIP
|
|
}"
|
|
@update:data="updateAddress"
|
|
/>
|
|
</template>
|
|
<template #footer>
|
|
<BackButton @click="closeEditModal" />
|
|
<SaveButton @click="handleUpdate" />
|
|
</template>
|
|
</CenterModal>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { defineProps, onMounted, ref, computed, watch, inject } from 'vue';
|
|
import UserList from '@c/user/UserList.vue';
|
|
import CenterModal from '@c/modal/CenterModal.vue';
|
|
import $api from '@api';
|
|
import { KakaoMap, KakaoMapMarker } from 'vue3-kakao-maps';
|
|
import BackBtn from '@c/button/BackBtn.vue';
|
|
import BackButton from '@c/button/BackBtn.vue';
|
|
import SaveButton from '@c/button/SaveBtn.vue';
|
|
import EditBtn from '../button/EditBtn.vue';
|
|
import DeleteBtn from '../button/DeleteBtn.vue';
|
|
import FormInput from '@c/input/FormInput.vue';
|
|
import FormSelect from '@c/input/FormSelect.vue';
|
|
import ArrInput from '@c/input/ArrInput.vue';
|
|
import { useToastStore } from '@s/toastStore';
|
|
import { useUserInfoStore } from '@/stores/useUserInfoStore';
|
|
import commonApi from '@/common/commonApi';
|
|
import { useProjectStore } from '@/stores/useProjectStore';
|
|
|
|
// 스토어
|
|
const toastStore = useToastStore();
|
|
const userStore = useUserInfoStore();
|
|
const projectStore = useProjectStore();
|
|
|
|
// Props 정의
|
|
const props = defineProps({
|
|
title: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
strdate: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
enddate: {
|
|
type: String,
|
|
required: true,
|
|
default: "",
|
|
},
|
|
description: {
|
|
type: String,
|
|
required: false,
|
|
},
|
|
address: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
addressdtail: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
addressZip: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
projctSeq: {
|
|
type: Number,
|
|
required: false
|
|
},
|
|
projctCol: {
|
|
type: Number,
|
|
required: false
|
|
},
|
|
projctColor: {
|
|
type: String,
|
|
required: false
|
|
},
|
|
projctCreatorId: {
|
|
type: Number,
|
|
required: false
|
|
}
|
|
});
|
|
|
|
// Emit 정의
|
|
const emit = defineEmits(['update']);
|
|
|
|
// 로그 모달 상태
|
|
const isModalOpen = ref(false);
|
|
const logData = ref([]);
|
|
|
|
// 주소 팝오버 상태
|
|
const isPopoverVisible = ref(false);
|
|
const map = ref();
|
|
const mapIconRef = ref(null);
|
|
const coordinates = ref(null);
|
|
|
|
// 수정 모달 상태
|
|
const isEditModalOpen = ref(false);
|
|
const originalColor = ref('');
|
|
const nameAlert = ref(false);
|
|
const user = ref(null);
|
|
|
|
const isProjectCreator = computed(() => {
|
|
return user.value?.id === props.projctCreatorId;
|
|
});
|
|
|
|
// dayjs 인스턴스 가져오기
|
|
const dayjs = inject('dayjs');
|
|
|
|
// 오늘 날짜를 YYYY-MM-DD 형식으로 변환
|
|
const todays = dayjs().format('YYYY-MM-DD');
|
|
|
|
// 프로젝트 만료 여부 체크 (종료일이 지났는지)
|
|
const isProjectExpired = computed(() => {
|
|
if (!props.enddate) return false;
|
|
|
|
const today = new Date();
|
|
today.setHours(0, 0, 0, 0); // 오늘 날짜의 시작 시간으로 설정
|
|
|
|
const endDate = new Date(props.enddate);
|
|
endDate.setHours(0, 0, 0, 0); // 종료일의 시작 시간으로 설정
|
|
|
|
return endDate < today;
|
|
});
|
|
|
|
// 수정할 프로젝트 데이터
|
|
const selectedProject = ref({
|
|
PROJCTSEQ: props.projctSeq,
|
|
PROJCTNAM: props.title,
|
|
PROJCTSTR: props.strdate,
|
|
PROJCTEND: props.enddate,
|
|
PROJCTZIP: props.addressZip,
|
|
PROJCTARR: props.address,
|
|
PROJCTDTL: props.addressdtail,
|
|
PROJCTDES: props.description,
|
|
PROJCTCOL: props.projctCol,
|
|
projctcolor: props.projctColor,
|
|
});
|
|
|
|
// 컬러 목록 가져오기
|
|
const { colorList } = commonApi({
|
|
loadColor: true,
|
|
colorType: 'YNP',
|
|
});
|
|
|
|
// 기존 컬러 + 사용 가능 한 컬러
|
|
const allColors = computed(() => {
|
|
const existingColor = { value: selectedProject.value.PROJCTCOL, label: selectedProject.value.projctcolor };
|
|
return [existingColor, ...colorList.value];
|
|
});
|
|
|
|
// 수정 :: 주소
|
|
const updateAddress = addressData => {
|
|
selectedProject.value = {
|
|
...selectedProject.value,
|
|
PROJCTZIP: addressData.postcode,
|
|
PROJCTARR: addressData.address,
|
|
PROJCTDTL: addressData.detailAddress,
|
|
};
|
|
};
|
|
|
|
|
|
// 로그 데이터 가져오기
|
|
const getLogData = async () => {
|
|
const res = await $api.get(`project/log/${props.projctSeq}`);
|
|
|
|
logData.value = res.data.data;
|
|
};
|
|
|
|
// 로그 모달 열기
|
|
const openModal = async () => {
|
|
await getLogData();
|
|
isModalOpen.value = true;
|
|
};
|
|
|
|
// 로그 모달 닫기
|
|
const closeModal = () => {
|
|
isModalOpen.value = false;
|
|
};
|
|
|
|
// 수정 모달 열기
|
|
const openEditModal = () => {
|
|
isEditModalOpen.value = true;
|
|
originalColor.value = props.projctCol;
|
|
};
|
|
|
|
// 수정 모달 닫기
|
|
const closeEditModal = () => {
|
|
selectedProject.value = {
|
|
PROJCTSEQ: props.projctSeq,
|
|
PROJCTNAM: props.title,
|
|
PROJCTSTR: props.strdate,
|
|
PROJCTEND: props.enddate,
|
|
PROJCTZIP: props.addressZip,
|
|
PROJCTARR: props.address,
|
|
PROJCTDTL: props.addressdtail,
|
|
PROJCTDES: props.description,
|
|
PROJCTCOL: props.projctCol,
|
|
projctcolor: props.projctColor,
|
|
};
|
|
|
|
isEditModalOpen.value = false;
|
|
};
|
|
|
|
// 변경된 내용 있는지 확인
|
|
const hasChanges = computed(() => {
|
|
return selectedProject.value.PROJCTNAM !== props.title ||
|
|
selectedProject.value.PROJCTSTR !== props.strdate ||
|
|
selectedProject.value.PROJCTEND !== props.enddate ||
|
|
selectedProject.value.PROJCTZIP !== props.addressZip ||
|
|
selectedProject.value.PROJCTARR !== props.address ||
|
|
selectedProject.value.PROJCTDTL !== props.addressdtail ||
|
|
selectedProject.value.PROJCTDES !== props.description ||
|
|
selectedProject.value.PROJCTCOL !== props.projctCol;
|
|
});
|
|
|
|
// 프로젝트 수정
|
|
const handleUpdate = () => {
|
|
nameAlert.value = selectedProject.value.PROJCTNAM.trim() === '';
|
|
|
|
if (nameAlert.value) {
|
|
return;
|
|
}
|
|
|
|
if (!hasChanges.value) {
|
|
toastStore.onToast('변경된 내용이 없습니다.', 'e');
|
|
return;
|
|
}
|
|
|
|
$api.patch('project/update', {
|
|
projctSeq: selectedProject.value.PROJCTSEQ,
|
|
projctNam: selectedProject.value.PROJCTNAM,
|
|
projctCol: selectedProject.value.PROJCTCOL,
|
|
projctArr: selectedProject.value.PROJCTARR,
|
|
projctDtl: selectedProject.value.PROJCTDTL,
|
|
projctZip: selectedProject.value.PROJCTZIP,
|
|
projctStr: selectedProject.value.PROJCTSTR,
|
|
projctEnd: selectedProject.value.PROJCTEND || null,
|
|
projctDes: selectedProject.value.PROJCTDES || null,
|
|
projctUmb: user.value?.id,
|
|
originalColor: originalColor.value === selectedProject.value.PROJCTCOL ? null : originalColor.value,
|
|
}).then(res => {
|
|
if (res.status === 200) {
|
|
toastStore.onToast('수정이 완료 되었습니다.', 's');
|
|
closeEditModal();
|
|
// 상위 컴포넌트에 업데이트 알림
|
|
emit('update');
|
|
}
|
|
});
|
|
};
|
|
|
|
// 주소를 좌표로 변환하는 함수
|
|
const convertAddressToCoordinates = () => {
|
|
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
|
|
};
|
|
}
|
|
});
|
|
};
|
|
|
|
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 = () => {
|
|
$api.patch('project/delete', {
|
|
projctSeq: props.projctSeq,
|
|
projctCol: props.projctCol,
|
|
})
|
|
.then(res => {
|
|
if (res.status === 200) {
|
|
toastStore.onToast('삭제가 완료되었습니다.', 's');
|
|
projectStore.getProjectList();
|
|
projectStore.getMemberProjects();
|
|
}
|
|
})
|
|
};
|
|
|
|
// 컴포넌트 마운트 시 실행
|
|
onMounted(async () => {
|
|
// 사용자 정보 가져오기
|
|
await userStore.userInfo();
|
|
user.value = userStore.user;
|
|
|
|
convertAddressToCoordinates();
|
|
});
|
|
|
|
|
|
</script>
|