This commit is contained in:
dyhj625 2025-02-18 10:33:47 +09:00
commit 2b57883edd
14 changed files with 547 additions and 136 deletions

View File

@ -80,4 +80,10 @@
.grayscaleImg { .grayscaleImg {
filter: grayscale(100%); filter: grayscale(100%);
} }
/* scrollbar 안보이게 */
.scrollbar-none {
scrollbar-width: none;
}

View File

@ -1,6 +1,6 @@
<template> <template>
<ul class="cate-list list-unstyled d-flex flex-wrap mb-0"> <ul class="cate-list list-unstyled d-flex flex-wrap mb-0">
<li v-for="category in lists" :key="category.value" class="mt-2 mx-1"> <li v-for="category in lists" :key="category.value" class="mt-2 me-2">
<button <button
type="button" type="button"
class="btn" class="btn"
@ -39,9 +39,7 @@ const selectCategory = (cate) => {
<style scoped> <style scoped>
.cate-list {
margin-left: -0.25rem;
}
@media (max-width: 768px) { @media (max-width: 768px) {
.cate-list { .cate-list {

View File

@ -1,14 +1,16 @@
<template> <template>
<div class="mb-2"> <div class="mb-2 row" >
<div class="d-flex align-items-center"> <div class="d-flex">
<label :for="name" class="col-md-2 col-form-label"> <label :for="name" class="col-md-2 col-form-label">
{{ title }} {{ title }}
<span :class="isEssential ? 'link-danger' : 'd-none'">*</span> <span :class="isEssential ? 'link-danger' : 'd-none'">*</span>
</label> </label>
<button type="button" class="btn btn-sm btn-primary" @click="openAddressSearch">주소찾기</button> <div class="align-content-center col-md-10 text-end ms-auto">
<button type="button" class="btn btn-sm btn-primary" :class="isRow ? '' : 'ms-auto'" @click="openAddressSearch">주소찾기</button>
</div>
</div> </div>
<div class="col-md-12"> <div :class="isRow ? 'col-md-10 ms-auto' : 'col-md-12'">
<div class="d-flex mb-3"> <div class="d-flex mb-3">
<input <input
:id="name" :id="name"
@ -48,6 +50,10 @@
<script setup> <script setup>
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
const postcode = ref('');
const address = ref('');
const detailAddress = ref('');
const props = defineProps({ const props = defineProps({
title: { title: {
type: String, type: String,
@ -69,19 +75,33 @@ const props = defineProps({
default: 30, default: 30,
required: false, required: false,
}, },
isRow: {
type: Boolean,
default: false,
required: false,
},
isAlert: { isAlert: {
type: Boolean, type: Boolean,
default: false, default: false,
required: false, required: false,
}, },
modelValue: {
type: Object,
default: () => ({}),
required: false
}
}); });
const emits = defineEmits(['update:data', 'update:alert']); // watch
watch(() => props.modelValue, (newValue) => {
if (newValue) {
postcode.value = newValue.PROJCTZIP || '';
address.value = newValue.PROJCTARR || '';
detailAddress.value = newValue.PROJCTDTL || '';
}
}, { immediate: true });
// const emits = defineEmits(['update:data', 'update:alert']);
const postcode = ref('');
const address = ref('');
const detailAddress = ref('');
// //
const openAddressSearch = () => { const openAddressSearch = () => {

View File

@ -1,63 +1,126 @@
<template> <template>
<div class="card mb-3 shadow-sm border"> <div class="card mb-3 shadow-sm border">
<div class="row g-0"> <div class="row g-0">
<!-- 게시물 내용 섹션 --> <div class="card-body">
<div> <!-- 제목 -->
<div class="card-body"> <h5 class="card-title">
<!-- 제목 --> {{ title }}
<h5 class="card-title"> </h5>
{{ title }} <!-- 날짜 -->
<span class="text-muted me-3" v-if="attachment"> <div class="d-flex flex-column flex-sm-row align-items-center pb-2">
<i class="fa-solid fa-paperclip"></i> <i class="bx bx-calendar"></i>
</span> <div class="ms-2">날짜</div>
</h5> <div class="ms-12">{{ strdate }} ~ {{ enddate }}</div>
<!-- 본문 --> </div>
<div class="card-text line-clamp-2 my-5">{{ content }}</div> <!-- 참여자 -->
<!-- 날짜 --> <div class="d-flex flex-column flex-sm-row align-items-center pb-2">
<div class="d-flex flex-column flex-sm-row justify-content-between align-items-start"> <i class="bx bxs-user"></i>
<small class="text-muted">{{ formattedDate }}</small> <div class="ms-2">참여자</div>
</div> <UserList :projctSeq="projctSeq" 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">
<i class="bx bxs-map"></i>
<div class="ms-2">주소</div>
<div class="ms-12">{{ address }}</div>
<button type="button" class="btn ms-auto text-white" :style="`background-color: ${projctCol} !important;`" @click.stop="openModal">log</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<CenterModal :display="isModalOpen" @close="closeModal">
<template #title> Log </template>
<template #body>
<div class="ms-4 mt-2" v-if="logData">
<p class="mb-1">{{ logData.createDate }}</p>
<strong class="">[{{ logData.creator }}] 프로젝트 등록</strong>
</div>
<div class="log-item" v-if="logData?.updateDate">
<div class="d-flex align-items-center">
<i class="bx bx-edit me-2"></i>
<strong>수정 정보</strong>
</div>
<div class="ms-4 mt-2">
<p class="mb-1">{{ logData.updateDate }}</p>
<p class="mb-0 text-muted">[{{ logData.updater }}] 프로젝트 수정</p>
</div>
</div>
</template>
<template #footer>
<button type="button" class="btn btn-secondary" @click="closeModal">닫기</button>
</template>
</CenterModal>
</template> </template>
<script setup> <script setup>
import { computed } from 'vue'; import { defineProps, ref } from 'vue';
import { defineProps } from 'vue'; import UserList from '@c/user/UserList.vue';
import CenterModal from '../modal/CenterModal.vue';
import $api from '@api';
// Props // Props
const props = defineProps({ const props = defineProps({
category: {
type: String,
required: false,
},
title: { title: {
type: String, type: String,
required: true, required: true,
}, },
content: { strdate: {
type: String, type: String,
required: true, required: true,
}, },
date: { enddate: {
type: String,
required: true,
default: "",
},
description: {
type: String,
required: false,
},
address: {
type: String, type: String,
required: true, required: true,
}, },
attachment: { projctSeq: {
type: Boolean, type: Number,
default: false, required: false
} },
projctCol: {
type: String,
required: false
},
}); });
// formattedDate computed defineEmits(['click']);
const formattedDate = computed(() => {
const date = new Date(props.date); const isModalOpen = ref(false);
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String( const logData = ref(null);
date.getDate()
).padStart(2, "0")} ${String(date.getHours()).padStart(2, "0")}:${String( const fetchLogData = async () => {
date.getMinutes() try {
).padStart(2, "0")}`; const response = await $api.get(`project/log/${props.projctSeq}`);
}); logData.value = response.data.data.length > 0 ? response.data.data[0] : {};
} catch (error) {
console.error('로그 정보 조회 실패:', error);
}
};
const openModal = async () => {
await fetchLogData();
isModalOpen.value = true;
};
const closeModal = () => {
isModalOpen.value = false;
};
</script> </script>

View File

@ -1,49 +1,181 @@
<template> <template>
<div class="mt-4"> <div class="mt-4">
<div v-if="posts.length === 0" class="text-center"> <div v-if="projectList.length === 0" class="text-center">
<p class="text-muted mt-4">게시물이 없습니다.</p> <p class="text-muted mt-4">게시물이 없습니다.</p>
</div> </div>
<div v-for="post in posts" :key="post.id" @click="goDetail(post.id)"> <div v-for="post in projectList" :key="post.PROJCTSEQ" @click="openModal(post)" class="cursor-pointer">
<ProjectCard <ProjectCard
:title="post.title" :title="post.PROJCTNAM"
:content="post.content" :description="post.PROJCTDES"
:date="post.date" :strdate="post.PROJCTSTR"
:attachment="post.attachment || false" :enddate="post.PROJCTEND"
:address="post.PROJCTARR + ' ' + post.PROJCTDTL"
:projctSeq="post.PROJCTSEQ"
:projctCol="post.projctcolor"
/> />
</div> </div>
<CenterModal :display="isModalOpen" @close="closeModal">
<template #title> 프로젝트 수정 </template>
<template #body>
<FormInput
title="이름"
name="name"
:is-essential="true"
:is-alert="nameAlert"
v-model="selectedProject.PROJCTNAM"
/>
<FormSelect
title="컬러"
name="color"
:is-essential="true"
:is-label="true"
:is-common="true"
:data="allColors"
v-model="selectedProject.projctcolor"
/>
<FormInput
title="시작일"
type="date"
name="startDay"
v-model="selectedProject.PROJCTSTR"
:is-essential="true"
/>
<FormInput
title="종료일"
type="date"
name="endDay"
v-model="selectedProject.PROJCTEND"
/>
<FormInput
title="설명"
name="description"
v-model="selectedProject.PROJCTDES"
/>
<ArrInput
title="주소"
name="address"
:is-essential="true"
:is-row="true"
v-model="selectedProject"
@update:data="updateAddress"
/>
</template>
<template #footer>
<button class="btn btn-secondary" @click="closeModal">Close</button>
<button class="btn btn-primary" @click="handleSubmit">Save</button>
</template>
</CenterModal>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, defineEmits } from 'vue'; import { computed, ref } from 'vue';
import ProjectCard from './ProjectCard.vue'; import ProjectCard from './ProjectCard.vue';
import { onMounted } from 'vue';
import $api from '@api';
import CenterModal from '@c/modal/CenterModal.vue';
import FormInput from '@c/input/FormInput.vue';
import FormSelect from '@c/input/FormSelect.vue';
import commonApi from '@/common/commonApi';
import ArrInput from '../input/ArrInput.vue';
import { useUserInfoStore } from '@/stores/useUserInfoStore';
// const projectList = ref([]);
const posts = ref([ const isModalOpen = ref(false);
{
id: 1,
title: 'Vue 3 Composition API 소개',
content: 'Composition API를 사용하여 Vue 3에서 효율적으로 개발하는 방법을 알아봅니다. Composition API를 사용하여 Vue 3에서 효율적으로 개발하는 방법을 알아봅니다. Composition API를 사용하여 Vue 3에서 효율적으로 개발하는 방법을 알아봅니다. Composition API를 사용하여 Vue 3에서 효율적으로 개발하는 방법을 알아봅니다. Composition API를 사용하여 Vue 3에서 효율적으로 개발하는 방법을 알아봅니다. Composition API를 사용하여 Vue 3에서 효율적으로 개발하는 방법을 알아봅니다.',
date: '2025-02-10',
comments: 4,
attachment: true
},
{
id: 2,
title: 'Spring Boot로 REST API 만들기',
content: 'Spring Boot를 사용하여 간단한 RESTful API를 구현하는 방법을 다룹니다.',
date: '2025-02-09',
comments: 2,
attachment: false
}
]);
const emit = defineEmits(['click']); const userStore = useUserInfoStore();
const user = ref(null);
// const nameAlert = ref(false);
const goDetail = (id) => { const selectedProject = ref({
emit('click', id); PROJCTNAM: '',
projctcolor: '',
PROJCTSTR: '',
PROJCTEND: '',
PROJCTZIP: '',
PROJCTARR: '',
PROJCTDTL: '',
PROJCTDES: '',
PROJCTCOL: '',
});
const { colorList } = commonApi({
loadColor: true,
colorType: 'YNP',
});
onMounted(async () => {
getProjectList();
await userStore.userInfo(); //
user.value = userStore.user;
});
//
const getProjectList = () => {
$api.get('project/select').then(res => {
projectList.value = res.data.data.projectList;
console.log(projectList.value);
});
}; };
const openModal = (post) => {
isModalOpen.value = true;
selectedProject.value = { ...post };
};
const closeModal = () => {
isModalOpen.value = false;
};
// +
const allColors = computed(() => {
//
const existingColor = { value: selectedProject.value.PROJCTCOL, label: selectedProject.value.projctcolor };
// colorList
return [existingColor, ...colorList.value];
});
const updateAddress = (addressData) => {
selectedProject.value = {
...selectedProject.value,
PROJCTZIP: addressData.postcode,
PROJCTARR: addressData.address,
PROJCTDTL: addressData.detailAddress
};
};
console.log(projectList.PROJCTSEQ)
const handleSubmit = () => {
$api.patch('project/update', {
projctSeq: projectList.PROJCTSEQ,
projctNam: selectedProject.value.PROJCTNAM,
projctCol: selectedProject.value.projctcolor,
projctArr: selectedProject.value.PROJCTARR,
projctDtl: selectedProject.value.PROJCTDTL,
projctZip: selectedProject.value.PROJCTZIP,
projctStr: selectedProject.value.PROJCTSTR,
projctEnd: selectedProject.value.PROJCTEND,
projctDes: selectedProject.value.PROJCTDES,
projctUmb: user.value.name,
}).then(res => {
if (res.status === 200) {
toastStore.onToast('수정이 완료 되었습니다.', 's');
closeModal();
location.reload();
}
})
};
</script> </script>

View File

@ -1,10 +1,10 @@
<template> <template>
<div @click="closeModal" class="modal fade" :class="{ 'show': display, 'display-block': display , 'modal-back' : display }" id="modalCenter" tabindex="-1" aria-modal="true" role="dialog"> <div @click="closeModal" class="modal fade scrollbar-none" :class="{ 'show': display, 'display-block': display , 'modal-back' : display }" id="modalCenter" tabindex="-1" aria-modal="true" role="dialog">
<div @click.stop class="modal-dialog modal-dialog-centered" role="document"> <div @click.stop class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="modalCenterTitle"> <h5 class="modal-title m-auto fw-bold" id="modalCenterTitle">
<slot name="title">Modal Title</slot> <slot name="title">Modal Title</slot>
</h5> </h5>
<button type="button" class="btn-close" @click="closeModal" aria-label="Close"></button> <button type="button" class="btn-close" @click="closeModal" aria-label="Close"></button>

View File

@ -1,19 +1,163 @@
<template> <template>
<SearchBar /> <SearchBar />
<CategoryBtn :lists="yearCategory" /> <div class="d-flex align-items-center">
<CategoryBtn :lists="yearCategory" v-model:selectedCategory="selectedCategory" />
<WriteBtn class="mt-2 ms-auto" @click="openModal" />
<CenterModal :display="isModalOpen" @close="closeModal">
<template #title> 프로젝트 등록 </template>
<template #body>
<FormInput
title="이름"
name="name"
:is-essential="true"
:is-alert="nameAlert"
@update:modelValue="name = $event"
/>
<FormSelect
title="컬러"
name="color"
:is-essential="true"
:is-label="true"
:is-common="true"
:data="colorList"
@update:data="color = $event"
/>
<FormInput
title="시작 일"
type="date"
name="startDay"
v-model="startDay"
:is-essential="true"
/>
<FormInput
title="종료 일"
name="endDay"
:type="'date'"
@update:modelValue="endDay = $event"
/>
<FormInput
title="설명"
name="description"
@update:modelValue="description = $event"
/>
<ArrInput
title="주소"
name="address"
:isEssential="true"
:is-row="true"
:is-alert="addressAlert"
@update:data="handleAddressUpdate"
@update:alert="addressAlert = $event"
/>
</template>
<template #footer>
<button class="btn btn-secondary" @click="closeModal">Close</button>
<button class="btn btn-primary" @click="handleSubmit">Save</button>
</template>
</CenterModal>
</div>
<ProjectCardList :category="selectedCategory" /> <ProjectCardList :category="selectedCategory" />
</template> </template>
<script setup> <script setup>
import SearchBar from '@c/search/SearchBar.vue'; import SearchBar from '@c/search/SearchBar.vue';
import ProjectCardList from '@c/list/ProjectCardList.vue'; import ProjectCardList from '@c/list/ProjectCardList.vue';
import CategoryBtn from '@c/category/CategoryBtn.vue'; import CategoryBtn from '@c/category/CategoryBtn.vue';
import commonApi from '@/common/commonApi' import commonApi from '@/common/commonApi';
import { ref } from 'vue'; import { inject, onMounted, ref } from 'vue';
import WriteBtn from '@c/button/WriteBtn.vue';
import CenterModal from '@c/modal/CenterModal.vue';
import FormSelect from '@c/input/FormSelect.vue';
import FormInput from '@c/input/FormInput.vue';
import ArrInput from '@c/input/ArrInput.vue';
import { useToastStore } from '@s/toastStore';
import $api from '@api';
import { useUserInfoStore } from '@/stores/useUserInfoStore';
const selectedCategory = ref(null); const dayjs = inject('dayjs');
const { yearCategory } = commonApi(); const today = dayjs().format('YYYY-MM-DD');
const toastStore = useToastStore();
const userStore = useUserInfoStore();
const user = ref(null);
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 isModalOpen = ref(false);
const nameAlert = ref(false);
const addressAlert = ref(false);
const openModal = () => {
isModalOpen.value = true;
};
const closeModal = () => {
isModalOpen.value = false;
};
const selectedCategory = ref(null);
const { yearCategory, colorList } = commonApi({
loadColor: true,
colorType: 'YNP',
loadYearCategory: true,
});
//
const handleAddressUpdate = addressData => {
address.value = addressData.address;
detailAddress.value = addressData.detailAddress;
postcode.value = addressData.postcode;
};
onMounted(async () => {
await userStore.userInfo(); //
user.value = userStore.user;
});
const handleSubmit = async () => {
nameAlert.value = name.value.trim() === '';
addressAlert.value = address.value.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,
projctCmb: user.value.name,
})
.then(res => {
if (res.status === 200) {
toastStore.onToast('프로젝트가 등록되었습니다.', 's');
closeModal();
location.reload();
}
})
};
</script> </script>

View File

@ -1,5 +1,4 @@
<template> <template>
<form @submit.prevent="handleSubmit">
<div class="col-xl-12"> <div class="col-xl-12">
<UserFormInput title="아이디" name="id" :is-alert="idAlert" :useInputGroup="true" @update:data="handleIdChange" :value="id" /> <UserFormInput title="아이디" name="id" :is-alert="idAlert" :useInputGroup="true" @update:data="handleIdChange" :value="id" />
@ -13,7 +12,7 @@
/> />
<div class="d-grid gap-2 mt-7 mb-5"> <div class="d-grid gap-2 mt-7 mb-5">
<button type="submit" class="btn btn-primary">로그인</button> <button type="submit" @click="handleSubmit" class="btn btn-primary">로그인</button>
</div> </div>
<div class="mb-3 d-flex justify-content-around"> <div class="mb-3 d-flex justify-content-around">
@ -25,7 +24,6 @@
<RouterLink class="text-dark fw-bold" to="/pw">비밀번호 찾기</RouterLink> <RouterLink class="text-dark fw-bold" to="/pw">비밀번호 찾기</RouterLink>
</div> </div>
</div> </div>
</form>
</template> </template>
<script setup> <script setup>
@ -34,7 +32,7 @@
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { ref } from 'vue'; import { ref } from 'vue';
import UserFormInput from '@c/input/UserFormInput.vue'; import UserFormInput from '@c/input/UserFormInput.vue';
import { useUserStore } from '@s/useUserStore'; import { useUserInfoStore } from '@/stores/useUserInfoStore';
const id = ref(''); const id = ref('');
const password = ref(''); const password = ref('');
@ -42,7 +40,7 @@
const passwordAlert = ref(false); const passwordAlert = ref(false);
const remember = ref(false); const remember = ref(false);
const userStore = useUserStore(); const userStore = useUserInfoStore();
const route = useRoute(); const route = useRoute();
const handleIdChange = value => { const handleIdChange = value => {
@ -55,21 +53,21 @@
passwordAlert.value = false; passwordAlert.value = false;
}; };
const handleSubmit = async () => { const handleSubmit = async () => {
$api.post( idAlert.value = id.value.trim() === '';
'user/login', passwordAlert.value = password.value.trim() === '';
{
loginId: id.value,
password: password.value,
remember: remember.value,
},
{ headers: { 'X-Page-Route': route.path } },
).then(res => {
if (res.status === 200) {
// const sessionCookie = res.data.data;
// document.cookie = `JSESSIONID=${sessionCookie};path=/;expires=-1;`;
// document.cookie = `JSESSIONID=${sessionCookie};path=/;HttpOnly=true;samesite=lax`;
if (idAlert.value || passwordAlert.value) {
return;
}
$api.post('user/login', {
loginId: id.value,
password: password.value,
remember: remember.value,
}, { headers: { 'X-Page-Route': route.path } })
.then(res => {
if (res.status === 200) {
userStore.userInfo(); userStore.userInfo();
router.push('/'); router.push('/');
} }

View File

@ -252,7 +252,11 @@
}; };
// , mbti, // , mbti,
const { colorList, mbtiList, pwhintList } = commonApi(); const { colorList, mbtiList, pwhintList } = commonApi({
loadColor: true, colorType: 'YON',
loadMbti: true,
loadPwhint: true,
});
// //
const handleAddressUpdate = (addressData) => { const handleAddressUpdate = (addressData) => {

View File

@ -4,23 +4,22 @@
v-for="(user, index) in userList" v-for="(user, index) in userList"
:key="index" :key="index"
class="avatar pull-up" class="avatar pull-up"
:class="{ 'opacity-100': user.disabled }" :class="{ 'opacity-100': isUserDisabled(user) }"
@click="toggleDisable(index)" @click="toggleDisable(index)"
data-bs-toggle="tooltip" data-bs-toggle="tooltip"
data-popup="tooltip-custom" data-popup="tooltip-custom"
data-bs-placement="top" data-bs-placement="top"
:aria-label="user.MEMBERSEQ" :aria-label="user.MEMBERSEQ"
:data-bs-original-title="getTooltipTitle(user)" :data-bs-original-title="getTooltipTitle(user)"
> >
<img <img
class="rounded-circle user-avatar border border-3" class="rounded-circle user-avatar border border-3"
:class="{ 'grayscaleImg': user.disabled }" :class="{ 'grayscaleImg': isUserDisabled(user) }"
:src="`${baseUrl}upload/img/profile/${user.MEMBERPRF}`" :src="`${baseUrl}upload/img/profile/${user.MEMBERPRF}`"
:style="`border-color: ${user.usercolor} !important;`" :style="`border-color: ${user.usercolor} !important;`"
alt="user" alt="user"
/> />
</li> </li>
</ul> </ul>
</template> </template>
@ -29,38 +28,83 @@ import { onMounted, ref, nextTick } from 'vue';
import { useUserStore } from '@s/userList'; import { useUserStore } from '@s/userList';
import $api from '@api'; import $api from '@api';
const emit = defineEmits(); const emit = defineEmits(['user-list-update']);
const userStore = useUserStore(); const userStore = useUserStore();
const userList = ref([]); const userList = ref([]);
const baseUrl = $api.defaults.baseURL.replace(/api\/$/, ''); const baseUrl = $api.defaults.baseURL.replace(/api\/$/, '');
const props = defineProps({
projctSeq: {
type: Number,
required: false,
}
});
//
const fetchProjectParticipation = async () => {
if (props.projctSeq) {
const response = await $api.get(`project/members/${props.projctSeq}`);
if (response.status === 200) {
const projectMembers = response.data.data;
userList.value = userList.value.map(user => ({
...user,
PROJCTYON: projectMembers.find(pm => pm.MEMBERSEQ === user.MEMBERSEQ)?.PROJCTYON ?? '1'
}));
}
}
};
// //
onMounted(async () => { onMounted(async () => {
await userStore.fetchUserList(); await userStore.fetchUserList();
userList.value = userStore.userList; userList.value = userStore.userList;
if (props.projctSeq) {
await fetchProjectParticipation();
}
nextTick(() => { nextTick(() => {
const tooltips = document.querySelectorAll('[data-bs-toggle="tooltip"]'); const tooltips = document.querySelectorAll('[data-bs-toggle="tooltip"]');
tooltips.forEach((tooltip) => { tooltips.forEach((tooltip) => {
new bootstrap.Tooltip(tooltip); new bootstrap.Tooltip(tooltip);
}); });
}); });
}); });
// / //
const toggleDisable = (index) => { const isUserDisabled = (user) => {
return props.projctSeq ? user.PROJCTYON === '0' : user.disabled;
};
// / DB
const toggleDisable = async (index) => {
const user = userList.value[index]; const user = userList.value[index];
if (user) { if (user) {
user.disabled = !user.disabled; const newParticipationStatus = props.projctSeq
emitUserListUpdate(); ? user.PROJCTYON === '1'
: !user.disabled;
if (props.projctSeq) {
const response = await $api.patch('project/updateYon', {
memberSeq: user.MEMBERSEQ,
projctSeq: props.projctSeq,
projctYon: newParticipationStatus ? '0' : '1'
});
if (response.status === 200) {
user.PROJCTYON = newParticipationStatus ? '0' : '1';
}
} else {
user.disabled = newParticipationStatus;
emitUserListUpdate();
}
} }
}; };
// emit // emit
const emitUserListUpdate = () => { const emitUserListUpdate = () => {
const activeUsers = userList.value.filter((user) => !user.disabled); const activeUsers = userList.value.filter(user => !isUserDisabled(user));
const disabledUsers = userList.value.filter((user) => user.disabled); const disabledUsers = userList.value.filter(user => isUserDisabled(user));
emit('user-list-update', { activeUsers, disabledUsers }); emit('user-list-update', { activeUsers, disabledUsers });
}; };
@ -68,6 +112,3 @@ const getTooltipTitle = (user) => {
return user.MEMBERSEQ === userStore.userInfo.id ? '나' : user.MEMBERNAM; return user.MEMBERSEQ === userStore.userInfo.id ? '나' : user.MEMBERNAM;
}; };
</script> </script>
<style scoped>
</style>

View File

@ -37,7 +37,7 @@
remainingVacationData: Object, remainingVacationData: Object,
}); });
const userStore = useUserStore(); const userStore = useUserInfoStore();
const userListStore = useUserListStore(); const userListStore = useUserListStore();
const userList = ref([]); const userList = ref([]);

View File

@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<ul class="alphabet-list list-unstyled d-flex flex-wrap mb-0"> <ul class="alphabet-list list-unstyled d-flex flex-wrap mb-0">
<li v-for="char in koreanChars" :key="char" class="mt-2 mx-1"> <li v-for="char in koreanChars" :key="char" class="mt-2 me-2">
<button <button
type="button" type="button"
class="btn" class="btn"
@ -13,7 +13,7 @@
</li> </li>
</ul> </ul>
<ul class="alphabet-list list-unstyled d-flex flex-wrap mb-0"> <ul class="alphabet-list list-unstyled d-flex flex-wrap mb-0">
<li v-for="char in englishChars" :key="char" class="mt-2 mx-1"> <li v-for="char in englishChars" :key="char" class="mt-2 me-2">
<button <button
type="button" type="button"
class="btn" class="btn"
@ -43,9 +43,6 @@ const selectAlphabet = (alphabet) => {
</script> </script>
<style scoped> <style scoped>
.alphabet-list {
margin-left: -0.25rem;
}
@media (max-width: 768px) { @media (max-width: 768px) {
.alphabet-list { .alphabet-list {

View File

@ -228,7 +228,7 @@
</template> </template>
<script setup> <script setup>
import { useAuthStore } from '@s/useAuthStore'; import { useAuthStore } from '@s/useAuthStore';
import { useUserStore } from '@s/useUserStore'; import { useUserInfoStore } from '@/stores/useUserInfoStore';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useThemeStore } from '@s/darkmode'; import { useThemeStore } from '@s/darkmode';
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
@ -236,7 +236,7 @@ import { onMounted, ref } from 'vue';
const user = ref(null); const user = ref(null);
const authStore = useAuthStore(); const authStore = useAuthStore();
const userStore = useUserStore(); const userStore = useUserInfoStore();
const router = useRouter(); const router = useRouter();
const { isDarkMode, switchToDarkMode, switchToLightMode } = useThemeStore(); const { isDarkMode, switchToDarkMode, switchToLightMode } = useThemeStore();

View File

@ -1,8 +1,16 @@
/*
작성자 : 박지윤
작성일 : 2025-02-04
수정자 :
수정일 :
설명 : 로그인 사용자 정보
*/
import { ref } from 'vue'; import { ref } from 'vue';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import $api from "@api"; import $api from "@api";
export const useUserStore = defineStore('userInfo', () => { export const useUserInfoStore = defineStore('userInfo', () => {
const user = ref(null); const user = ref(null);
// 사용자 정보 가져오기 // 사용자 정보 가져오기