Merge branch 'main' of http://192.168.0.251:3000/localhost/localhost-front
All checks were successful
LocalNet_front/pipeline/head This commit looks good

This commit is contained in:
nevermoregb 2025-03-24 09:47:35 +09:00
commit a72eb1f81a
9 changed files with 209 additions and 156 deletions

View File

@ -8,14 +8,28 @@
import { ref, onMounted } from "vue"; import { ref, onMounted } from "vue";
import $api from '@api'; import $api from '@api';
const commonApi = (options = {}) => {
const colorList = ref([]); const colorList = ref([]);
const mbtiList = ref([]); const mbtiList = ref([]);
const pwhintList = ref([]); const pwhintList = ref([]);
const yearCategory = ref([]); const yearCategory = ref([]);
const cateList = ref([]); const cateList = ref([]);
// type 파라미터를 추가로 받도록 수정 const refreshColorList = async (type = 'YNP') => {
const response = await $api.get(`user/color`, {
params: { type }
});
if (response.data && response.data.data) {
colorList.value = response.data.data.map(item => ({
label: item.CMNCODNAM,
value: item.CMNCODVAL,
}));
}
return colorList.value;
};
// CommonCode 함수를 외부에서도 접근할 수 있게 변경
const CommonCode = async (path, endpoint, targetList, type = null) => { const CommonCode = async (path, endpoint, targetList, type = null) => {
const params = type ? { type } : {}; const params = type ? { type } : {};
const response = await $api.get(`${path}/${endpoint}`, { const response = await $api.get(`${path}/${endpoint}`, {
@ -27,9 +41,9 @@ const commonApi = (options = {}) => {
})); }));
}; };
const commonApi = (options = {}) => {
onMounted(async () => { onMounted(async () => {
// 요청할 데이터가 옵션으로 전달 -> 그에 맞게 호출 // 요청할 데이터가 옵션으로 전달 -> 그에 맞게 호출
// color 옵션에 type 포함
if (options.loadColor) { if (options.loadColor) {
await CommonCode("user", "color", colorList, options.colorType); await CommonCode("user", "color", colorList, options.colorType);
} }
@ -39,7 +53,15 @@ const commonApi = (options = {}) => {
if (options.loadCateList) await CommonCode("worddict", "getWordCategory", cateList); if (options.loadCateList) await CommonCode("worddict", "getWordCategory", cateList);
}); });
return { colorList, mbtiList, pwhintList, yearCategory, cateList }; return {
colorList,
mbtiList,
pwhintList,
yearCategory,
cateList,
refreshColorList
};
}; };
export { refreshColorList };
export default commonApi; export default commonApi;

View File

@ -62,6 +62,7 @@ const getAddress = (lat, lng) => {
resolve(address); resolve(address);
} else { } else {
reject('주소를 가져올 수 없습니다.'); reject('주소를 가져올 수 없습니다.');
return;
} }
}); });
}); });

View File

@ -390,7 +390,7 @@ onMounted(async () => {
await userStore.userInfo(); await userStore.userInfo();
user.value = userStore.user; user.value = userStore.user;
await projectStore.getProjectList('', '', 'true'); await projectStore.getProjectList('', '', 'true');
project.value = projectStore.projectList; project.value = projectStore.activeProjectList;
await todaysCommuter(); await todaysCommuter();

View File

@ -129,10 +129,11 @@ watch(selectData, (newValue) => {
const selected = computed(() => { const selected = computed(() => {
//
const selectedItem = props.data.find(item => const selectedItem = props.data.find(item =>
props.isCommon ? item.value === selectData.value : props.data.indexOf(item) === selectData.value props.isCommon ? item.value === selectData.value : props.data.indexOf(item) === selectData.value
); );
return selectedItem ? selectedItem.label : null; return selectedItem ? selectedItem.label : null;
}); });
</script> </script>

View File

@ -7,7 +7,6 @@
<h5 class="card-title fw-bold"> <h5 class="card-title fw-bold">
{{ title }} {{ title }}
</h5> </h5>
<p v-if="isProjectExpired" class="btn-icon btn-danger rounded-2 pe-none"><i class='bx bx-power-off'></i></p>
<div v-if="!isProjectExpired" class="d-flex gap-1"> <div v-if="!isProjectExpired" class="d-flex gap-1">
<EditBtn @click.stop="openEditModal" /> <EditBtn @click.stop="openEditModal" />
<DeleteBtn v-if="isProjectCreator" @click.stop="handleDelete" class="ms-1"/> <DeleteBtn v-if="isProjectCreator" @click.stop="handleDelete" class="ms-1"/>
@ -131,20 +130,17 @@
</div> </div>
<!-- 시작일 --> <!-- 시작일 -->
<div class="date-picker-wrapper" @click="focusStartDateInput">
<FormInput <FormInput
title="시작일" title="시작일"
type="date" type="date"
name="startDay" name="startDay"
:is-essential="true" :is-essential="true"
:is-alert="startDayAlert"
:modelValue="selectedProject.PROJCTSTR" :modelValue="selectedProject.PROJCTSTR"
@update:modelValue="selectedProject.PROJCTSTR = $event" @update:modelValue="selectedProject.PROJCTSTR = $event"
/> />
<input ref="startDateInput" type="date" v-model="selectedProject.PROJCTSTR" class="hidden-start-input">
</div>
<!-- 종료일 --> <!-- 종료일 -->
<div class="date-picker-wrapper" @click="focusEndDateInput">
<FormInput <FormInput
title="종료일" title="종료일"
type="date" type="date"
@ -153,8 +149,6 @@
:modelValue="selectedProject.PROJCTEND" :modelValue="selectedProject.PROJCTEND"
@update:modelValue="selectedProject.PROJCTEND = $event" @update:modelValue="selectedProject.PROJCTEND = $event"
/> />
<input ref="endDateInput" type="date" v-model="selectedProject.PROJCTEND" class="hidden-end-input">
</div>
<FormInput <FormInput
title="설명" title="설명"
@ -199,7 +193,7 @@ import FormSelect from '@c/input/FormSelect.vue';
import ArrInput from '@c/input/ArrInput.vue'; import ArrInput from '@c/input/ArrInput.vue';
import { useToastStore } from '@s/toastStore'; import { useToastStore } from '@s/toastStore';
import { useUserInfoStore } from '@/stores/useUserInfoStore'; import { useUserInfoStore } from '@/stores/useUserInfoStore';
import commonApi from '@/common/commonApi'; import commonApi, { refreshColorList } from '@/common/commonApi';
import { useProjectStore } from '@/stores/useProjectStore'; import { useProjectStore } from '@/stores/useProjectStore';
// //
@ -207,31 +201,6 @@ const toastStore = useToastStore();
const userStore = useUserInfoStore(); const userStore = useUserInfoStore();
const projectStore = useProjectStore(); const projectStore = useProjectStore();
const startDateInput = ref(null);
const endDateInput = ref(null);
const focusStartDateInput = () => {
nextTick(() => {
if (startDateInput.value) {
startDateInput.value.focus();
setTimeout(() => {
startDateInput.value.showPicker?.();
}, 50);
}
});
};
const focusEndDateInput = () => {
nextTick(() => {
if (endDateInput.value) {
endDateInput.value.focus();
setTimeout(() => {
endDateInput.value.showPicker?.();
}, 50);
}
});
};
// Props // Props
const props = defineProps({ const props = defineProps({
title: { title: {
@ -250,6 +219,7 @@ const props = defineProps({
description: { description: {
type: String, type: String,
required: false, required: false,
default: "",
}, },
address: { address: {
type: String, type: String,
@ -282,6 +252,10 @@ const props = defineProps({
resetUserSelection: { resetUserSelection: {
type: Boolean, type: Boolean,
default: false default: false
},
searchParams: {
type: Object,
default: () => ({ text: '', year: null })
} }
}); });
@ -302,6 +276,7 @@ const coordinates = ref(null);
const isEditModalOpen = ref(false); const isEditModalOpen = ref(false);
const originalColor = ref(''); const originalColor = ref('');
const nameAlert = ref(false); const nameAlert = ref(false);
const startDayAlert = ref(false);
const user = ref(null); const user = ref(null);
const editUserListRef = ref(null); const editUserListRef = ref(null);
@ -312,6 +287,7 @@ const selectedUsers = ref({
disabledUsers: [] disabledUsers: []
}); });
// //
const handleEditUserListUpdate = (userLists) => { const handleEditUserListUpdate = (userLists) => {
selectedUsers.value = userLists; selectedUsers.value = userLists;
@ -356,12 +332,23 @@ const { colorList } = commonApi({
colorType: 'YNP', colorType: 'YNP',
}); });
// + // +
const allColors = computed(() => { const allColors = computed(() => {
const existingColor = { value: selectedProject.value.PROJCTCOL, label: selectedProject.value.projctcolor }; // ( )
return [existingColor, ...colorList.value]; const existingColor = {
value: props.projctCol, //
label: props.projctColor //
};
//
const otherColors = colorList.value.filter(color => color.value !== existingColor.value);
//
return [existingColor, ...otherColors];
}); });
// :: // ::
const updateAddress = addressData => { const updateAddress = addressData => {
selectedProject.value = { selectedProject.value = {
@ -434,9 +421,16 @@ const closeEditModal = () => {
} }
}; };
// selectedUsers
watch(() => selectedUsers.value.activeUsers, (newVal, oldVal) => {
}, { deep: true });
watch(() => selectedUsers.value.disabledUsers, (newVal, oldVal) => {
}, { deep: true });
// //
const hasChanges = computed(() => { const hasChanges = computed(() => {
// //
const basicChanges = selectedProject.value.PROJCTNAM !== props.title || const basicChanges = selectedProject.value.PROJCTNAM !== props.title ||
selectedProject.value.PROJCTSTR !== props.strdate || selectedProject.value.PROJCTSTR !== props.strdate ||
selectedProject.value.PROJCTEND !== props.enddate || selectedProject.value.PROJCTEND !== props.enddate ||
@ -446,8 +440,8 @@ const hasChanges = computed(() => {
selectedProject.value.PROJCTDES !== props.description || selectedProject.value.PROJCTDES !== props.description ||
selectedProject.value.PROJCTCOL !== props.projctCol; selectedProject.value.PROJCTCOL !== props.projctCol;
const userChanges = selectedUsers.value.activeUsers.length > 0 || //
selectedUsers.value.disabledUsers.length > 0; const userChanges = editUserListRef.value?.hasUserChanges() || false;
return basicChanges || userChanges; return basicChanges || userChanges;
}); });
@ -473,8 +467,9 @@ watch(() => props.resetUserSelection, () => {
// //
const handleUpdate = async () => { const handleUpdate = async () => {
nameAlert.value = selectedProject.value.PROJCTNAM.trim() === ''; nameAlert.value = selectedProject.value.PROJCTNAM.trim() === '';
startDayAlert.value = selectedProject.value.PROJCTSTR.trim() === '';
if (nameAlert.value) { if (nameAlert.value || startDayAlert.value) {
return; return;
} }
@ -504,21 +499,23 @@ const handleUpdate = async () => {
toastStore.onToast('수정이 완료 되었습니다.', 's'); toastStore.onToast('수정이 완료 되었습니다.', 's');
// //
await projectStore.getProjectList(); await projectStore.getProjectList(props.searchParams.text, props.searchParams.year, 'false');
await projectStore.getMemberProjects(); await projectStore.getMemberProjects();
await refreshColorList('YNP');
await editUserListRef.value.fetchProjectParticipation(); await editUserListRef.value.fetchProjectParticipation();
await userListRef.value.fetchProjectParticipation(); await userListRef.value.fetchProjectParticipation();
closeEditModal(); closeEditModal();
emit('update'); emit('update', props.searchParams);
} }
}; };
// //
const convertAddressToCoordinates = () => { const convertAddressToCoordinates = () => {
// kakao maps API
if (window.kakao && window.kakao.maps) {
const geocoder = new window.kakao.maps.services.Geocoder(); const geocoder = new window.kakao.maps.services.Geocoder();
geocoder.addressSearch(props.address, (result, status) => { geocoder.addressSearch(props.address, (result, status) => {
if (status === window.kakao.maps.services.Status.OK) { if (status === window.kakao.maps.services.Status.OK) {
coordinates.value = { coordinates.value = {
@ -533,6 +530,13 @@ const convertAddressToCoordinates = () => {
}; };
} }
}); });
} else {
//
coordinates.value = {
lat: 37.2108651707078,
lng: 127.089445559923
};
}
}; };
const onLoadKakaoMap = (mapRef) => { const onLoadKakaoMap = (mapRef) => {
@ -581,13 +585,4 @@ onMounted(async () => {
</script> </script>
<style>
.hidden-date-input {
position: absolute;
top: 100%;
left: 0;
width: 100%;
height: 40px;
opacity: 0;
}
</style>

View File

@ -25,7 +25,8 @@
:projctColor="post.projctcolor" :projctColor="post.projctcolor"
:projctCreatorId="post.PROJCTCMB" :projctCreatorId="post.PROJCTCMB"
:resetUserSelection="resetUserSelection" :resetUserSelection="resetUserSelection"
@update="getProjectList" :searchParams="{ text: searchText, year: selectedYear }"
@update="handleProjectUpdate"
/> />
</div> </div>
</div> </div>
@ -52,6 +53,7 @@
:is-label="true" :is-label="true"
:is-common="true" :is-common="true"
:is-color="true" :is-color="true"
:value="color"
:data="colorList" :data="colorList"
@update:data="color = $event" @update:data="color = $event"
/> />
@ -69,19 +71,16 @@
</div> </div>
</div> </div>
<div class="date-picker-wrapper" @click="focusStartDateInput">
<FormInput <FormInput
title="시작 일" title="시작 일"
name="startDay" name="startDay"
:type="'date'" :type="'date'"
:is-alert="startDayAlert"
:is-essential="true" :is-essential="true"
:modelValue="startDay" :modelValue="startDay"
v-model="startDay" v-model="startDay"
/> />
<input ref="startDateInput" type="date" v-model="startDay" class="hidden-start-input">
</div>
<div class="date-picker-wrapper" @click="focusEndDateInput">
<FormInput <FormInput
title="종료 일" title="종료 일"
name="endDay" name="endDay"
@ -90,8 +89,6 @@
:min="startDay" :min="startDay"
@update:modelValue="endDay = $event" @update:modelValue="endDay = $event"
/> />
<input ref="endDateInput" type="date" v-model="endDay" class="hidden-end-input">
</div>
<FormInput <FormInput
title="설명" title="설명"
@ -113,7 +110,7 @@
</template> </template>
<template #footer> <template #footer>
<BackButton type="reset" @click="closeCreateModal" /> <BackButton type="reset" @click="closeCreateModal" />
<SaveButton :disabled="!color" @click="handleCreate" /> <SaveButton @click="handleCreate" />
</template> </template>
</CenterModal> </CenterModal>
</form> </form>
@ -130,7 +127,7 @@
import FormInput from '@c/input/FormInput.vue'; import FormInput from '@c/input/FormInput.vue';
import ArrInput from '@c/input/ArrInput.vue'; import ArrInput from '@c/input/ArrInput.vue';
import UserList from '@c/user/UserList.vue'; import UserList from '@c/user/UserList.vue';
import commonApi from '@/common/commonApi'; import commonApi, { refreshColorList } from '@/common/commonApi';
import { useToastStore } from '@s/toastStore'; import { useToastStore } from '@s/toastStore';
import { useUserInfoStore } from '@/stores/useUserInfoStore'; import { useUserInfoStore } from '@/stores/useUserInfoStore';
import { useProjectStore } from '@/stores/useProjectStore'; import { useProjectStore } from '@/stores/useProjectStore';
@ -159,38 +156,15 @@
// //
const isCreateModalOpen = ref(false); const isCreateModalOpen = ref(false);
const name = ref(''); const name = ref('');
const color = ref(''); const color = ref('0');
const startDay = ref(today); const startDay = ref(today);
const endDay = ref(''); const endDay = ref('');
const description = ref(''); const description = ref('');
const nameAlert = ref(false); const nameAlert = ref(false);
const addressAlert = ref(false); const addressAlert = ref(false);
const startDayAlert = ref(false);
const startDateInput = ref(null);
const endDateInput = ref(null);
const focusStartDateInput = () => {
nextTick(() => {
if (startDateInput.value) {
startDateInput.value.focus();
setTimeout(() => {
startDateInput.value.showPicker?.();
}, 50);
}
});
};
const focusEndDateInput = () => {
nextTick(() => {
if (endDateInput.value) {
endDateInput.value.focus();
setTimeout(() => {
endDateInput.value.showPicker?.();
}, 50);
}
});
};
const addressData = ref({ const addressData = ref({
postcode: '', postcode: '',
@ -241,7 +215,13 @@
}); });
// //
const openCreateModal = () => { const openCreateModal = async () => {
const updatedColors = await refreshColorList('YNP');
if (updatedColors && updatedColors.length > 0) {
color.value = updatedColors[0].value;
}
isCreateModalOpen.value = true; isCreateModalOpen.value = true;
}; };
@ -251,8 +231,9 @@
}; };
const formReset = () => { const formReset = () => {
name.value = ''; name.value = '';
color.value = colorList.value.length > 0 ? colorList.value[0].value : ''; color.value = colorList.value[0].value;
addressData.value = { addressData.value = {
postcode: '', postcode: '',
address: '', address: '',
@ -262,6 +243,8 @@
endDay.value = ''; endDay.value = '';
description.value = ''; description.value = '';
nameAlert.value = false; nameAlert.value = false;
addressAlert.value = false;
startDayAlert.value = false;
selectedUsers.value = { selectedUsers.value = {
activeUsers: [], activeUsers: [],
@ -271,6 +254,7 @@
if (userListRef.value) { if (userListRef.value) {
userListRef.value.resetSelection(); userListRef.value.resetSelection();
} }
}; };
@ -286,13 +270,30 @@
} }
}); });
const handleProjectUpdate = async (params) => {
if (params) {
await projectStore.getProjectList(params.text, params.year, 'false');
} else {
await projectStore.getProjectList(searchText.value, selectedYear.value, 'false');
}
await projectStore.getMemberProjects();
//
const updatedColors = await refreshColorList('YNP');
// ()
if (updatedColors && updatedColors.length > 0) {
color.value = updatedColors[0].value;
}
};
// //
const handleCreate = async () => { const handleCreate = async () => {
nameAlert.value = name.value.trim() === ''; nameAlert.value = name.value.trim() === '';
startDayAlert.value = startDay.value.trim() === '';
addressAlert.value = addressData.value.address.trim() === ''; addressAlert.value = addressData.value.address.trim() === '';
if (nameAlert.value || addressAlert.value) { if (nameAlert.value || startDayAlert.value || addressAlert.value) {
return; return;
} }
@ -314,10 +315,15 @@
if (response.status === 200) { if (response.status === 200) {
toastStore.onToast('프로젝트가 등록되었습니다.', 's'); toastStore.onToast('프로젝트가 등록되었습니다.', 's');
closeCreateModal();
getProjectList(); colorList.value = colorList.value.filter(c => c.value !== color.value);
projectStore.getMemberProjects();
formReset(); formReset();
await getProjectList();
await projectStore.getMemberProjects();
closeCreateModal();
resetUserSelection.value = !resetUserSelection.value; resetUserSelection.value = !resetUserSelection.value;
} }
}; };
@ -326,5 +332,7 @@
await getProjectList(); await getProjectList();
await userStore.userInfo(); await userStore.userInfo();
user.value = userStore.user; user.value = userStore.user;
}); });
</script> </script>

View File

@ -32,7 +32,7 @@
<script setup> <script setup>
import $api from '@api'; import $api from '@api';
import router from '@/router'; import router from '@/router';
import { nextTick, ref } from 'vue'; import { ref } from 'vue';
import UserFormInput from '@c/input/UserFormInput.vue'; import UserFormInput from '@c/input/UserFormInput.vue';
import { useUserInfoStore } from '@/stores/useUserInfoStore'; import { useUserInfoStore } from '@/stores/useUserInfoStore';
@ -79,8 +79,7 @@
} }
// //
userStore.userInfo(); await userStore.userInfo();
await nextTick();
router.push('/'); router.push('/');
}) })
}; };

View File

@ -54,6 +54,10 @@ const props = defineProps({
} }
}); });
//
const originalDisabledUsers = ref([]);
const resetSelection = async () => { const resetSelection = async () => {
// //
if (props.projctSeq) { if (props.projctSeq) {
@ -107,11 +111,16 @@ const fetchProjectParticipation = async () => {
...user, ...user,
PROJCTYON: projectMembers.find(pm => pm.MEMBERSEQ === user.MEMBERSEQ)?.PROJCTYON ?? '1' PROJCTYON: projectMembers.find(pm => pm.MEMBERSEQ === user.MEMBERSEQ)?.PROJCTYON ?? '1'
})); }));
//
originalDisabledUsers.value = userList.value
.filter(user => user.PROJCTYON === '0')
.map(user => user.MEMBERSEQ);
emitUserListUpdate(); emitUserListUpdate();
} }
} }
}; };
// //
const fetchUserProjectPeriods = async () => { const fetchUserProjectPeriods = async () => {
if (props.projctSeq) { if (props.projctSeq) {
@ -223,9 +232,29 @@ const getTooltipTitle = (user) => {
return userName; return userName;
}; };
defineExpose({
resetSelection, fetchProjectParticipation
});
const hasUserChanges = () => {
if (!props.projctSeq) return false;
const currentDisabledUserIds = userList.value
.filter(user => user.PROJCTYON === '0')
.map(user => user.MEMBERSEQ);
//
if (currentDisabledUserIds.length !== originalDisabledUsers.value.length) {
return true;
}
// ID
return currentDisabledUserIds.some(id => !originalDisabledUsers.value.includes(id)) ||
originalDisabledUsers.value.some(id => !currentDisabledUserIds.includes(id));
};
// expose
defineExpose({
resetSelection,
fetchProjectParticipation,
hasUserChanges
});
</script> </script>

View File

@ -9,7 +9,6 @@ import { defineStore } from 'pinia';
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
import $api from '@api'; import $api from '@api';
import { useUserInfoStore } from '@/stores/useUserInfoStore'; import { useUserInfoStore } from '@/stores/useUserInfoStore';
import { useAuthStore } from '@/stores/useAuthStore';
export const useProjectStore = defineStore('project', () => { export const useProjectStore = defineStore('project', () => {
const projectList = ref([]); // 모든 프로젝트 (종료된 프로젝트 포함) const projectList = ref([]); // 모든 프로젝트 (종료된 프로젝트 포함)
@ -18,7 +17,6 @@ export const useProjectStore = defineStore('project', () => {
const activeMemberProjectList = ref([]); // 사용자가 속한 진행 중인 프로젝트 const activeMemberProjectList = ref([]); // 사용자가 속한 진행 중인 프로젝트
const selectedProject = ref(null); const selectedProject = ref(null);
const userStore = useUserInfoStore(); const userStore = useUserInfoStore();
const authStore = useAuthStore();
// 전체 프로젝트 가져오기 (종료된 프로젝트 포함 여부에 따라 다른 배열에 저장) // 전체 프로젝트 가져오기 (종료된 프로젝트 포함 여부에 따라 다른 배열에 저장)
const getProjectList = async (searchText = '', selectedYear = '', excludeEnded = 'false') => { const getProjectList = async (searchText = '', selectedYear = '', excludeEnded = 'false') => {