This commit is contained in:
dyhj625 2025-02-18 14:46:20 +09:00
parent 2c28645488
commit 8c29873731
3 changed files with 142 additions and 107 deletions

View File

@ -5,7 +5,7 @@
<div class="d-flex justify-content-start align-items-top"> <div class="d-flex justify-content-start align-items-top">
<!-- 프로필섹션 --> <!-- 프로필섹션 -->
<div class="avatar-wrapper"> <div class="avatar-wrapper">
<div class="avatar me-4"> <div v-if="!unknown" class="avatar me-4">
<img src="/img/avatars/11.png" alt="Avatar" class="rounded-circle"> <img src="/img/avatars/11.png" alt="Avatar" class="rounded-circle">
</div> </div>
</div> </div>
@ -13,8 +13,9 @@
<div class="w-100"> <div class="w-100">
<textarea <textarea
class="form-control" class="form-control"
placeholder="주제에 대한 생각을 자유롭게 댓글로 표현해 주세요. &#13;&#10;여러분의 다양한 의견을 기다립니다." placeholder="댓글 달기"
rows="3" rows="3"
v-model="comment"
></textarea> ></textarea>
</div> </div>
</div> </div>
@ -23,7 +24,7 @@
<div class="d-flex justify-content-between flex-wrap mt-4"> <div class="d-flex justify-content-between flex-wrap mt-4">
<div class="d-flex flex-wrap align-items-center"> <div class="d-flex flex-wrap align-items-center">
<!-- 익명 체크박스 (익명게시판일 경우에만)--> <!-- 익명 체크박스 (익명게시판일 경우에만)-->
<div class="form-check form-check-inline mb-0 me-4"> <div v-if="unknown" class="form-check form-check-inline mb-0 me-4">
<input <input
class="form-check-input" class="form-check-input"
type="checkbox" type="checkbox"
@ -40,15 +41,16 @@
type="password" type="password"
id="basic-default-password" id="basic-default-password"
class="form-control flex-grow-1" class="form-control flex-grow-1"
placeholder="" v-model="password"
/> />
</div> </div>
</div> </div>
<!-- 답변 쓰기 버튼 --> <!-- 답변 쓰기 버튼 -->
<div class="ms-auto mt-3 mt-md-0"> <div class="ms-auto mt-3 mt-md-0">
<button class="btn btn-primary"> <button class="btn btn-primary" @click="handleCommentSubmit">
<i class="icon-base bx bx-check"></i> <!-- <i class="icon-base bx bx-check"></i> -->
확인
</button> </button>
</div> </div>
</div> </div>
@ -57,6 +59,40 @@
</template> </template>
<script setup> <script setup>
import { ref } from 'vue'; import { ref, defineEmits, defineProps, computed, watch } from 'vue';
const props = defineProps({
unknown: {
type: Boolean,
default: true,
},
parentId: {
type: Number,
default: 0
}
});
const comment = ref('');
const password = ref('');
const isCheck = ref(false); const isCheck = ref(false);
const emit = defineEmits(['submitComment']);
watch(() => props.unknown, (newVal) => {
if (!newVal) {
isCheck.value = false;
}
});
function handleCommentSubmit() {
emit('submitComment', {
comment: comment.value,
password: password.value,
});
comment.value = '';
password.value = '';
}
</script> </script>

View File

@ -1,49 +1,41 @@
<template> <template>
<div v-if="isOpen" class="modal-dialog" @click.self="closeModal"> <div v-if="isOpen" class="modal-dialog" @click.self="closeModal">
<div class="modal-content modal-scroll"> <div class="modal-content modal-scroll">
<h5 class="modal-title">📅 연차 사용 내역</h5> <h5 class="modal-title">📅 연차 사용 내역</h5>
<button class="close-btn" @click="closeModal"></button> <button class="close-btn" @click="closeModal"></button>
<!-- 연차 사용 받은 연차 리스트 --> <!-- 연차 사용 받은 연차 리스트 -->
<div class="modal-body" v-if="mergedVacations.length > 0"> <div class="modal-body" v-if="mergedVacations.length > 0">
<ol class="vacation-list"> <ol class="vacation-list">
<li <li v-for="(vacation, index) in mergedVacations" :key="index" class="vacation-item">
v-for="(vacation, index) in mergedVacations" <span v-if="vacation.type === 'used'" class="vacation-index">
:key="index" {{ getVacationIndex(index) }})
class="vacation-item" </span>
> <span :class="vacation.type === 'used' ? 'minus-symbol' : 'plus-symbol'">
<span v-if="vacation.type === 'used'" class="vacation-index"> {{ vacation.type === 'used' ? '-' : '+' }}
{{ totalUsedVacationCount - usedVacations.findIndex(v => v.date === vacation.date) }}) </span>
</span> <span
<span :class="vacation.type === 'used' ? 'minus-symbol' : 'plus-symbol'"> :style="{ color: userColors[vacation.senderId || vacation.receiverId] || '#000' }"
{{ vacation.type === 'used' ? '-' : '+' }} class="vacation-date"
</span> >
<span {{ formatDate(vacation.date) }}
:style="{ color: userColors[vacation.senderId || vacation.receiverId] || '#000' }" </span>
class="vacation-date" </li>
> </ol>
{{ formatDate(vacation.date) }} </div>
<span class="vacation-type">
({{ vacation.halfDay ? '반차' : '풀 연차' }})
<span v-if="vacation.senderId"> (보낸 연차)</span>
</span>
</span>
</li>
</ol>
</div>
<!-- 연차 데이터 없음 --> <!-- 연차 데이터 없음 -->
<p v-if="mergedVacations.length === 0" class="no-data"> <p v-if="mergedVacations.length === 0" class="no-data">
🚫 사용한 연차가 없습니다. 🚫 사용한 연차가 없습니다.
</p> </p>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { defineProps, defineEmits, computed } from "vue"; import { defineProps, defineEmits, computed } from "vue";
const props = defineProps({ const props = defineProps({
isOpen: Boolean, isOpen: Boolean,
myVacations: { myVacations: {
type: Array, type: Array,
@ -57,50 +49,57 @@
type: Object, type: Object,
default: () => ({}), default: () => ({}),
}, },
}); });
const emit = defineEmits(["close"]); const emit = defineEmits(["close"]);
// // +
const totalUsedVacationCount = computed(() => props.myVacations.length); const usedVacations = computed(() =>
props.myVacations.map(v => ({ ...v, type: "used" }))
);
// + const receivedVacations = computed(() =>
const usedVacations = computed(() => props.receivedVacations.map(v => ({ ...v, type: "received" }))
props.myVacations.map(v => ({ ...v, type: "used" })) );
//
const mergedVacations = computed(() => {
return [...usedVacations.value, ...receivedVacations.value].sort(
(a, b) => new Date(b.date) - new Date(a.date)
); );
});
const receivedVacations = computed(() => // ( )
props.receivedVacations.map(v => ({ ...v, type: "received" })) const getVacationIndex = (index) => {
); let count = 0;
for (let i = 0; i <= index; i++) {
const v = mergedVacations.value[i];
count += v.used_quota; //
}
return count;
};
// // (YYYY-MM-DD)
const mergedVacations = computed(() => { const formatDate = (dateString) => {
return [...usedVacations.value, ...receivedVacations.value].sort( const date = new Date(dateString);
(a, b) => new Date(b.date) - new Date(a.date) return date.toISOString().split("T")[0]; // YYYY-MM-DD
); };
});
// (YYYY-MM-DD) const closeModal = () => {
const formatDate = (dateString) => { emit("close");
const date = new Date(dateString); };
return date.toISOString().split("T")[0]; // YYYY-MM-DD </script>
};
const closeModal = () => { <style scoped>
emit("close"); /* 모달 스타일 */
}; .modal-dialog {
</script>
<style scoped>
/* 모달 스타일 */
.modal-dialog {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
/* 스크롤 가능한 모달 */ /* 스크롤 가능한 모달 */
.modal-content { .modal-content {
max-height: 60vh; max-height: 60vh;
overflow-y: auto; overflow-y: auto;
padding: 20px; padding: 20px;
@ -108,10 +107,10 @@
background: white; background: white;
border-radius: 8px; border-radius: 8px;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1); box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
} }
/* 닫기 버튼 */ /* 닫기 버튼 */
.close-btn { .close-btn {
position: absolute; position: absolute;
top: 10px; top: 10px;
right: 10px; right: 10px;
@ -120,17 +119,17 @@
font-size: 18px; font-size: 18px;
cursor: pointer; cursor: pointer;
font-weight: bold; font-weight: bold;
} }
/* 리스트 기본 스타일 */ /* 리스트 기본 스타일 */
.vacation-list { .vacation-list {
list-style-type: none; list-style-type: none;
padding-left: 0; padding-left: 0;
margin-top: 15px; margin-top: 15px;
} }
/* 리스트 아이템 */ /* 리스트 아이템 */
.vacation-item { .vacation-item {
display: flex; display: flex;
align-items: center; align-items: center;
font-size: 16px; font-size: 16px;
@ -139,49 +138,49 @@
padding: 5px 10px; padding: 5px 10px;
border-radius: 5px; border-radius: 5px;
background: #f9f9f9; background: #f9f9f9;
} }
/* 인덱스 (연차 사용 개수) */ /* 인덱스 (연차 사용 개수) */
.vacation-index { .vacation-index {
font-weight: bold; font-weight: bold;
font-size: 16px; font-size: 16px;
margin-right: 8px; margin-right: 8px;
color: #333; color: #333;
} }
/* "-" 빨간색 */ /* "-" 빨간색 */
.minus-symbol { .minus-symbol {
color: red; color: red;
font-weight: bold; font-weight: bold;
margin-right: 8px; margin-right: 8px;
} }
/* "+" 파란색 */ /* "+" 파란색 */
plus-symbol { .plus-symbol {
color: blue; color: blue;
font-weight: bold; font-weight: bold;
margin-right: 8px; margin-right: 8px;
} }
/* 날짜 스타일 */ /* 날짜 스타일 */
.vacation-date { .vacation-date {
font-size: 16px; font-size: 16px;
color: #333; color: #333;
} }
/* 연차 유형 스타일 */ /* 연차 유형 스타일 */
.vacation-type { .vacation-type {
font-size: 14px; font-size: 14px;
font-weight: normal; font-weight: normal;
color: gray; color: gray;
margin-left: 5px; margin-left: 5px;
} }
/* 연차 데이터 없음 */ /* 연차 데이터 없음 */
.no-data { .no-data {
text-align: center; text-align: center;
font-size: 14px; font-size: 14px;
color: gray; color: gray;
margin-top: 10px; margin-top: 10px;
} }
</style> </style>

View File

@ -221,12 +221,12 @@ const handleUpdateReaction = async ({ boardId, commentId, isLike, isDislike }) =
}; };
// //
const fetchComments = async (pageNum = 1) => { const fetchComments = async (page = 1) => {
try { try {
const response = await axios.get(`board/${currentBoardId.value}/comments`, { const response = await axios.get(`board/${currentBoardId.value}/comments`, {
params: { params: {
LOCBRDSEQ: currentBoardId.value, LOCBRDSEQ: currentBoardId.value,
pageNum: pageNum page
} }
}); });
console.log("목록 API 응답 데이터:", response.data); console.log("목록 API 응답 데이터:", response.data);