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 $api from '@api';
const commonApi = (options = {}) => {
const colorList = ref([]);
const mbtiList = ref([]);
const pwhintList = ref([]);
const yearCategory = 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 params = type ? { type } : {};
const response = await $api.get(`${path}/${endpoint}`, {
@ -27,9 +41,9 @@ const commonApi = (options = {}) => {
}));
};
const commonApi = (options = {}) => {
onMounted(async () => {
// 요청할 데이터가 옵션으로 전달 -> 그에 맞게 호출
// color 옵션에 type 포함
if (options.loadColor) {
await CommonCode("user", "color", colorList, options.colorType);
}
@ -39,7 +53,15 @@ const commonApi = (options = {}) => {
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;

View File

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

View File

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

View File

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

View File

@ -7,7 +7,6 @@
<h5 class="card-title fw-bold">
{{ title }}
</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">
<EditBtn @click.stop="openEditModal" />
<DeleteBtn v-if="isProjectCreator" @click.stop="handleDelete" class="ms-1"/>
@ -131,20 +130,17 @@
</div>
<!-- 시작일 -->
<div class="date-picker-wrapper" @click="focusStartDateInput">
<FormInput
title="시작일"
type="date"
name="startDay"
:is-essential="true"
:is-alert="startDayAlert"
:modelValue="selectedProject.PROJCTSTR"
@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
title="종료일"
type="date"
@ -153,8 +149,6 @@
:modelValue="selectedProject.PROJCTEND"
@update:modelValue="selectedProject.PROJCTEND = $event"
/>
<input ref="endDateInput" type="date" v-model="selectedProject.PROJCTEND" class="hidden-end-input">
</div>
<FormInput
title="설명"
@ -199,7 +193,7 @@ 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 commonApi, { refreshColorList } from '@/common/commonApi';
import { useProjectStore } from '@/stores/useProjectStore';
//
@ -207,31 +201,6 @@ const toastStore = useToastStore();
const userStore = useUserInfoStore();
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
const props = defineProps({
title: {
@ -250,6 +219,7 @@ const props = defineProps({
description: {
type: String,
required: false,
default: "",
},
address: {
type: String,
@ -282,6 +252,10 @@ const props = defineProps({
resetUserSelection: {
type: Boolean,
default: false
},
searchParams: {
type: Object,
default: () => ({ text: '', year: null })
}
});
@ -302,6 +276,7 @@ const coordinates = ref(null);
const isEditModalOpen = ref(false);
const originalColor = ref('');
const nameAlert = ref(false);
const startDayAlert = ref(false);
const user = ref(null);
const editUserListRef = ref(null);
@ -312,6 +287,7 @@ const selectedUsers = ref({
disabledUsers: []
});
//
const handleEditUserListUpdate = (userLists) => {
selectedUsers.value = userLists;
@ -356,12 +332,23 @@ const { colorList } = commonApi({
colorType: 'YNP',
});
// +
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 => {
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 basicChanges = selectedProject.value.PROJCTNAM !== props.title ||
selectedProject.value.PROJCTSTR !== props.strdate ||
selectedProject.value.PROJCTEND !== props.enddate ||
@ -446,8 +440,8 @@ const hasChanges = computed(() => {
selectedProject.value.PROJCTDES !== props.description ||
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;
});
@ -473,8 +467,9 @@ watch(() => props.resetUserSelection, () => {
//
const handleUpdate = async () => {
nameAlert.value = selectedProject.value.PROJCTNAM.trim() === '';
startDayAlert.value = selectedProject.value.PROJCTSTR.trim() === '';
if (nameAlert.value) {
if (nameAlert.value || startDayAlert.value) {
return;
}
@ -504,21 +499,23 @@ const handleUpdate = async () => {
toastStore.onToast('수정이 완료 되었습니다.', 's');
//
await projectStore.getProjectList();
await projectStore.getProjectList(props.searchParams.text, props.searchParams.year, 'false');
await projectStore.getMemberProjects();
await refreshColorList('YNP');
await editUserListRef.value.fetchProjectParticipation();
await userListRef.value.fetchProjectParticipation();
closeEditModal();
emit('update');
emit('update', props.searchParams);
}
};
//
const convertAddressToCoordinates = () => {
// kakao maps API
if (window.kakao && window.kakao.maps) {
const geocoder = new window.kakao.maps.services.Geocoder();
geocoder.addressSearch(props.address, (result, status) => {
if (status === window.kakao.maps.services.Status.OK) {
coordinates.value = {
@ -533,6 +530,13 @@ const convertAddressToCoordinates = () => {
};
}
});
} else {
//
coordinates.value = {
lat: 37.2108651707078,
lng: 127.089445559923
};
}
};
const onLoadKakaoMap = (mapRef) => {
@ -581,13 +585,4 @@ onMounted(async () => {
</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"
:projctCreatorId="post.PROJCTCMB"
:resetUserSelection="resetUserSelection"
@update="getProjectList"
:searchParams="{ text: searchText, year: selectedYear }"
@update="handleProjectUpdate"
/>
</div>
</div>
@ -52,6 +53,7 @@
:is-label="true"
:is-common="true"
:is-color="true"
:value="color"
:data="colorList"
@update:data="color = $event"
/>
@ -69,19 +71,16 @@
</div>
</div>
<div class="date-picker-wrapper" @click="focusStartDateInput">
<FormInput
title="시작 일"
name="startDay"
:type="'date'"
:is-alert="startDayAlert"
:is-essential="true"
:modelValue="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
title="종료 일"
name="endDay"
@ -90,8 +89,6 @@
:min="startDay"
@update:modelValue="endDay = $event"
/>
<input ref="endDateInput" type="date" v-model="endDay" class="hidden-end-input">
</div>
<FormInput
title="설명"
@ -113,7 +110,7 @@
</template>
<template #footer>
<BackButton type="reset" @click="closeCreateModal" />
<SaveButton :disabled="!color" @click="handleCreate" />
<SaveButton @click="handleCreate" />
</template>
</CenterModal>
</form>
@ -130,7 +127,7 @@
import FormInput from '@c/input/FormInput.vue';
import ArrInput from '@c/input/ArrInput.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 { useUserInfoStore } from '@/stores/useUserInfoStore';
import { useProjectStore } from '@/stores/useProjectStore';
@ -159,38 +156,15 @@
//
const isCreateModalOpen = ref(false);
const name = ref('');
const color = ref('');
const color = ref('0');
const startDay = ref(today);
const endDay = ref('');
const description = ref('');
const nameAlert = 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({
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;
};
@ -251,8 +231,9 @@
};
const formReset = () => {
name.value = '';
color.value = colorList.value.length > 0 ? colorList.value[0].value : '';
color.value = colorList.value[0].value;
addressData.value = {
postcode: '',
address: '',
@ -262,6 +243,8 @@
endDay.value = '';
description.value = '';
nameAlert.value = false;
addressAlert.value = false;
startDayAlert.value = false;
selectedUsers.value = {
activeUsers: [],
@ -271,6 +254,7 @@
if (userListRef.value) {
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 () => {
nameAlert.value = name.value.trim() === '';
startDayAlert.value = startDay.value.trim() === '';
addressAlert.value = addressData.value.address.trim() === '';
if (nameAlert.value || addressAlert.value) {
if (nameAlert.value || startDayAlert.value || addressAlert.value) {
return;
}
@ -314,10 +315,15 @@
if (response.status === 200) {
toastStore.onToast('프로젝트가 등록되었습니다.', 's');
closeCreateModal();
getProjectList();
projectStore.getMemberProjects();
colorList.value = colorList.value.filter(c => c.value !== color.value);
formReset();
await getProjectList();
await projectStore.getMemberProjects();
closeCreateModal();
resetUserSelection.value = !resetUserSelection.value;
}
};
@ -326,5 +332,7 @@
await getProjectList();
await userStore.userInfo();
user.value = userStore.user;
});
</script>

View File

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

View File

@ -54,6 +54,10 @@ const props = defineProps({
}
});
//
const originalDisabledUsers = ref([]);
const resetSelection = async () => {
//
if (props.projctSeq) {
@ -107,11 +111,16 @@ const fetchProjectParticipation = async () => {
...user,
PROJCTYON: projectMembers.find(pm => pm.MEMBERSEQ === user.MEMBERSEQ)?.PROJCTYON ?? '1'
}));
//
originalDisabledUsers.value = userList.value
.filter(user => user.PROJCTYON === '0')
.map(user => user.MEMBERSEQ);
emitUserListUpdate();
}
}
};
//
const fetchUserProjectPeriods = async () => {
if (props.projctSeq) {
@ -223,9 +232,29 @@ const getTooltipTitle = (user) => {
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>

View File

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