수정, 등록 나누고 삭제 추가

This commit is contained in:
yoon 2025-02-27 13:28:52 +09:00
parent 0c1e7bc5dd
commit 581709430f
4 changed files with 290 additions and 218 deletions

View File

@ -1,5 +1,5 @@
<template>
<div class="mb-2 row" >
<div class="mb-2 row">
<div class="d-flex">
<label :for="name" class="col-md-2 col-form-label">
{{ title }}
@ -28,7 +28,6 @@
placeholder="기본주소"
readonly
/>
</div>
<div>
@ -87,7 +86,11 @@ const props = defineProps({
},
modelValue: {
type: Object,
default: () => ({}),
default: () => ({
postcode: '',
address: '',
detailAddress: ''
}),
required: false
}
});
@ -95,13 +98,13 @@ const props = defineProps({
// watch
watch(() => props.modelValue, (newValue) => {
if (newValue) {
postcode.value = newValue.PROJCTZIP || '';
address.value = newValue.PROJCTARR || '';
detailAddress.value = newValue.PROJCTDTL || '';
postcode.value = newValue.postcode || '';
address.value = newValue.address || '';
detailAddress.value = newValue.detailAddress || '';
}
}, { immediate: true });
const emits = defineEmits(['update:data', 'update:alert']);
const emits = defineEmits(['update:data', 'update:alert', 'update:modelValue']);
//
const openAddressSearch = () => {
@ -136,6 +139,7 @@ const emitAddressData = () => {
detailAddress: detailAddress.value,
};
emits('update:data', fullAddress);
emits('update:modelValue', fullAddress); // modelValue
};
// isAlert false

View File

@ -16,11 +16,11 @@
:min="min"
@focusout="$emit('focusout', modelValue)"
/>
<div class="invalid-feedback" :class="isAlert ? 'display-block' : ''">
<div class="invalid-feedback" :class="isAlert ? 'd-block' : ''">
{{ title }} 확인해주세요.
</div>
<!-- 카테고리 중복 -->
<div class="invalid-feedback" :class="isCateAlert ? 'display-block' : ''">
<div class="invalid-feedback" :class="isCateAlert ? 'd-block' : ''">
카테고리 중복입니다.
</div>
</div>

View File

@ -3,8 +3,13 @@
<div class="row g-0">
<div class="card-body">
<!-- 제목 -->
<h5 class="card-title">
<h5 class="card-title d-flex justify-content-between">
{{ title }}
<div>
<EditBtn @click.stop="openEditModal" />
<DeleteBtn @click.stop="handleDelete" class="ms-1"/>
</div>
</h5>
<!-- 날짜 -->
<div class="d-flex flex-column flex-sm-row align-items-center pb-2">
@ -27,20 +32,14 @@
<!-- 주소 -->
<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>
<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 w-100 map text-end"
>
<button type="button" class="btn-close popover-close" @click.stop="isPopoverVisible = !isPopoverVisible"></button>
<div v-if="isPopoverVisible" class="position-absolute w-100 map text-end">
<button type="button" class="btn-close popover-close" @click.stop="isPopoverVisible = !isPopoverVisible"></button>
<div class="card">
<div class="card-body p-1">
<KakaoMap
@ -49,7 +48,7 @@
:lng="coordinates.lng"
:draggable="false"
class="w-100 h-px-200"
>
>
<KakaoMapMarker
:lat="coordinates.lat"
:lng="coordinates.lng"
@ -59,39 +58,122 @@
</div>
</div>
</div>
<button type="button" class="btn ms-auto text-white" :style="`background-color: ${projctCol} !important;`" @click.stop="openModal">log</button>
<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 class="ms-4 mt-2 border p-3" v-if="logData">
<p class="mb-1">{{ logData.createDate }}</p>
<strong>[{{ logData.creator }}] 프로젝트 등록</strong>
</div>
<div class="log-item" v-if="logData?.updateDate">
<div class="ms-4 mt-2 border p-3">
<p class="mb-1">{{ logData.updateDate }}</p>
<strong>[{{ logData.updater }}] 프로젝트 수정</strong>
<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>
<BackButton @click="closeModal" />
<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"
: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 } from 'vue';
import { defineProps, onMounted, ref, computed, 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';
//
const toastStore = useToastStore();
const userStore = useUserInfoStore();
// Props
const props = defineProps({
@ -120,39 +202,144 @@ const props = defineProps({
type: String,
required: true,
},
addressZip: {
type: String,
required: true,
},
projctSeq: {
type: Number,
required: false
},
projctCol: {
type: Number,
required: false
},
projctColor: {
type: String,
required: false
},
});
defineEmits(['click']);
// Emit
const emit = defineEmits(['update']);
//
const isModalOpen = ref(false);
const logData = ref(null);
const logData = ref([]);
//
const isPopoverVisible = ref(false);
const mapIconRef = ref(null);
const coordinates = ref(null);
const fetchLogData = async () => {
const response = await $api.get(`project/log/${props.projctSeq}`);
logData.value = response.data.data.length > 0 ? response.data.data[0] : {};
//
const isEditModalOpen = ref(false);
const originalColor = ref('');
const nameAlert = ref(false);
const user = ref(null);
//
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 fetchLogData();
await getLogData();
isModalOpen.value = true;
};
//
const closeModal = () => {
isModalOpen.value = false;
};
//
const openEditModal = () => {
isEditModalOpen.value = true;
originalColor.value = props.projctCol;
// ( )
if (!user.value) {
userStore.userInfo().then(() => {
user.value = userStore.user;
});
}
};
//
const closeEditModal = () => {
isEditModalOpen.value = false;
};
//
const handleUpdate = () => {
nameAlert.value = selectedProject.value.PROJCTNAM.trim() === '';
if (nameAlert.value) {
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?.name,
originalColor: originalColor.value === selectedProject.value.PROJCTCOL ? null : originalColor.value,
}).then(res => {
if (res.status === 200) {
toastStore.onToast('수정이 완료 되었습니다.', 's');
closeEditModal();
//
emit('update');
window.location.reload()
}
});
};
//
const convertAddressToCoordinates = () => {
@ -174,11 +361,27 @@ const convertAddressToCoordinates = () => {
});
};
//
onMounted(() => {
//
const handleDelete = () => {
$api.patch('project/delete', {
projctSeq: props.projctSeq,
projctCol: props.projctCol,
})
.then(res => {
if (res.status === 200) {
toastStore.onToast('삭제가 완료되었습니다.', 's');
location.reload()
}
})
};
//
onMounted(async () => {
convertAddressToCoordinates();
//
await userStore.userInfo();
user.value = userStore.user;
});
</script>

View File

@ -11,7 +11,7 @@
<p class="text-muted mt-4">등록된 프로젝트가 없습니다.</p>
</div>
<div v-for="post in projectStore.projectList" :key="post.PROJCTSEQ" @click="openEditModal(post)" class="cursor-pointer">
<div v-for="post in projectStore.projectList" :key="post.PROJCTSEQ">
<ProjectCard
:title="post.PROJCTNAM"
:description="post.PROJCTDES"
@ -19,8 +19,11 @@
:enddate="post.PROJCTEND"
:address="post.PROJCTARR"
:addressdtail="post.PROJCTDTL"
:addressZip="post.PROJCTZIP"
:projctSeq="post.PROJCTSEQ"
:projctCol="post.projctcolor"
:projctCol="post.PROJCTCOL"
:projctColor="post.projctcolor"
@update="getProjectList"
/>
</div>
</div>
@ -36,8 +39,8 @@
:is-essential="true"
:is-alert="nameAlert"
:modelValue="name"
@update:modelValue="name = $event"
@update:alert="nameAlert = $event"
@update:modelValue="name = $event"
/>
<FormSelect
@ -80,6 +83,7 @@
:isEssential="true"
:is-row="true"
:is-alert="addressAlert"
:modelValue="addressData"
@update:data="handleAddressUpdate"
@update:alert="addressAlert = $event"
/>
@ -90,70 +94,6 @@
</template>
</CenterModal>
</form>
<!-- 수정 모달 -->
<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"
: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="selectedProject"
@update:data="updateAddress"
/>
</template>
<template #footer>
<BackButton @click="closeEditModal" />
<SaveButton @click="handleUpdate" />
</template>
</CenterModal>
</template>
<script setup>
@ -193,29 +133,17 @@
const isCreateModalOpen = ref(false);
const name = ref('');
const color = ref('');
const address = ref('');
const detailAddress = ref('');
const postcode = ref('');
const startDay = ref(today);
const endDay = ref('');
const description = ref('');
const nameAlert = ref(false);
const addressAlert = ref(false);
//
const isEditModalOpen = ref(false);
const originalColor = ref('');
const selectedProject = ref({
PROJCTSEQ: '',
PROJCTNAM: '',
PROJCTSTR: '',
PROJCTEND: '',
PROJCTZIP: '',
PROJCTARR: '',
PROJCTDTL: '',
PROJCTDES: '',
PROJCTCOL: '',
projctcolor: '',
const addressData = ref({
postcode: '',
address: '',
detailAddress: ''
});
// API
@ -261,9 +189,11 @@
const formReset = () => {
name.value = '';
color.value = '';
address.value = '';
detailAddress.value = '';
postcode.value = '';
addressData.value = {
postcode: '',
address: '',
detailAddress: ''
};
startDay.value = today;
endDay.value = '';
description.value = '';
@ -272,30 +202,43 @@
}
// ::
const handleAddressUpdate = addressData => {
address.value = addressData.address;
detailAddress.value = addressData.detailAddress;
postcode.value = addressData.postcode;
};
const handleAddressUpdate = (data) => {
addressData.value = data;
};
//
watch([startDay, endDay], () => {
if (startDay.value && endDay.value) {
const start = new Date(startDay.value);
const end = new Date(endDay.value);
//
if (end < start) {
endDay.value = startDay.value;
}
}
});
//
const handleCreate = async () => {
nameAlert.value = name.value.trim() === '';
addressAlert.value = address.value.trim() === '';
addressAlert.value = addressData.value.address.trim() === '';
if (nameAlert.value || addressAlert.value) {
return;
}
$api.post('project/insert', {
projctNam: name.value,
projctCol: color.value,
projctStr: startDay.value,
projctEnd: endDay.value || null,
projctDes: description.value || null,
projctArr: address.value,
projctDtl: detailAddress.value,
projctZip: postcode.value,
projctArr: addressData.value.address,
projctDtl: addressData.value.detailAddress,
projctZip: addressData.value.postcode,
projctCmb: user.value.name,
}).then(res => {
if (res.status === 200) {
@ -306,84 +249,6 @@
});
};
//
const openEditModal = post => {
isEditModalOpen.value = true;
selectedProject.value = { ...post };
originalColor.value = post.PROJCTCOL;
};
const closeEditModal = () => {
isEditModalOpen.value = false;
};
// +
const allColors = computed(() => {
const existingColor = { value: selectedProject.value.PROJCTCOL, label: selectedProject.value.projctcolor };
return [existingColor, ...colorList.value];
});
//
const hasChanges = computed(() => {
const original = projectStore.projectList.find(p => p.PROJCTSEQ === selectedProject.value.PROJCTSEQ);
if (!original) return false;
return (
original.PROJCTNAM !== selectedProject.value.PROJCTNAM ||
original.PROJCTCOL !== selectedProject.value.PROJCTCOL ||
original.PROJCTARR !== selectedProject.value.PROJCTARR ||
original.PROJCTDTL !== selectedProject.value.PROJCTDTL ||
original.PROJCTZIP !== selectedProject.value.PROJCTZIP ||
original.PROJCTSTR !== selectedProject.value.PROJCTSTR ||
original.PROJCTEND !== selectedProject.value.PROJCTEND ||
original.PROJCTDES !== selectedProject.value.PROJCTDES
);
});
// ::
const updateAddress = addressData => {
selectedProject.value = {
...selectedProject.value,
PROJCTZIP: addressData.postcode,
PROJCTARR: addressData.address,
PROJCTDTL: addressData.detailAddress,
};
};
//
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.name,
originalColor: originalColor.value === selectedProject.value.PROJCTCOL ? null : originalColor.value,
}).then(res => {
if (res.status === 200) {
toastStore.onToast('수정이 완료 되었습니다.', 's');
closeEditModal();
location.reload();
}
});
};
onMounted(async () => {
await getProjectList();
await userStore.userInfo();