메인 이벤트 달력
This commit is contained in:
parent
a540feb851
commit
c1274cf9a0
@ -76,7 +76,7 @@ const common = {
|
|||||||
formatDateTime(dateObj) {
|
formatDateTime(dateObj) {
|
||||||
const date = new Date(dateObj);
|
const date = new Date(dateObj);
|
||||||
const dateCheck = date.getTime();
|
const dateCheck = date.getTime();
|
||||||
if (isNaN(dateCheck)) return dateStr;
|
if (isNaN(dateCheck)) return dateObj;
|
||||||
|
|
||||||
const zeroFormat = num => (num < 10 ? `0${num}` : num);
|
const zeroFormat = num => (num < 10 ? `0${num}` : num);
|
||||||
return {
|
return {
|
||||||
|
|||||||
201
src/components/main/EventModal.vue
Normal file
201
src/components/main/EventModal.vue
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
<template>
|
||||||
|
<div class="event-modal position-fixed bg-white shadow rounded" :style="modalStyle">
|
||||||
|
<!-- 이벤트 선택 화면 -->
|
||||||
|
<div v-if="!selectedEventType" class="d-flex flex-wrap gap-2 p-2">
|
||||||
|
<div v-for="event in eventTypes" :key="event.code" class="event-icon-wrapper position-relative">
|
||||||
|
<img
|
||||||
|
:src="`${baseUrl}img/main-category-img/main-${event.code}.png`"
|
||||||
|
class="event-icon-select"
|
||||||
|
style="width: 25px; height: 25px; cursor: pointer"
|
||||||
|
@click="handleEventClick(event)"
|
||||||
|
/>
|
||||||
|
<!-- X 표시 수정 -->
|
||||||
|
<span v-if="isEventExists(event.type)" class="cancel-mark"> × </span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 입력 폼 화면 -->
|
||||||
|
<div v-else class="p-2" style="min-width: 200px">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<small class="text-muted">{{ getEventTitle(selectedEventType) }}</small>
|
||||||
|
<button class="btn-close btn-close-sm" style="font-size: 8px" @click="resetForm"></button>
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control form-control-sm py-1"
|
||||||
|
style="height: 25px; font-size: 12px"
|
||||||
|
placeholder="장소"
|
||||||
|
v-model="eventPlace"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<input type="time" class="form-control form-control-sm py-1" style="height: 25px; font-size: 12px" v-model="eventTime" />
|
||||||
|
</div>
|
||||||
|
<div class="text-end">
|
||||||
|
<button class="btn btn-primary btn-sm py-1" style="font-size: 12px; height: 25px; line-height: 1" @click="handleSubmit">
|
||||||
|
등록
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
position: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
default: () => ({ x: 0, y: 0 }),
|
||||||
|
},
|
||||||
|
selectedDate: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
baseUrl: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
dateEvents: {
|
||||||
|
type: Array,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['select', 'delete', 'insert']);
|
||||||
|
|
||||||
|
// 폼 관련 상태
|
||||||
|
const selectedEventType = ref(null);
|
||||||
|
const eventPlace = ref('');
|
||||||
|
const eventTime = ref('');
|
||||||
|
|
||||||
|
const eventTypes = [
|
||||||
|
{ type: 'birthdayParty', code: '300203', title: '생일파티' },
|
||||||
|
{ type: 'dinner', code: '300204', title: '회식' },
|
||||||
|
{ type: 'teaTime', code: '300205', title: '티타임' },
|
||||||
|
{ type: 'workshop', code: '300206', title: '워크샵' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const getEventTitle = type => {
|
||||||
|
return eventTypes.find(event => event.type === type)?.title || '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const isEventExists = type => {
|
||||||
|
return props.dateEvents?.some(event => event.type === type);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEventClick = event => {
|
||||||
|
if (isEventExists(event.type)) {
|
||||||
|
if (confirm('이벤트를 취소하시겠습니까?')) {
|
||||||
|
emit('delete', {
|
||||||
|
date: props.selectedDate,
|
||||||
|
code: event.code,
|
||||||
|
title: event.title,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
selectedEventType.value = event.code;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
if (!eventPlace.value || !eventTime.value) {
|
||||||
|
alert('장소와 시간을 모두 입력해주세요');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('insert', {
|
||||||
|
date: props.selectedDate,
|
||||||
|
code: selectedEventType.value,
|
||||||
|
title: getEventTitle(selectedEventType.value),
|
||||||
|
place: eventPlace.value,
|
||||||
|
time: eventTime.value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
selectedEventType.value = null;
|
||||||
|
eventPlace.value = '';
|
||||||
|
eventTime.value = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 모달 스타일 계산을 computed로 변경
|
||||||
|
const modalStyle = computed(() => {
|
||||||
|
const modalWidth = 200; // 모달의 예상 너비
|
||||||
|
const modalHeight = 150; // 모달의 예상 높이
|
||||||
|
const viewportWidth = window.innerWidth;
|
||||||
|
const viewportHeight = window.innerHeight;
|
||||||
|
|
||||||
|
let x = props.position?.x || 0;
|
||||||
|
let y = props.position?.y || 0;
|
||||||
|
|
||||||
|
// 모달이 뷰포트를 벗어나지 않도록 조정
|
||||||
|
if (x + modalWidth > viewportWidth) {
|
||||||
|
x = viewportWidth - modalWidth - 10;
|
||||||
|
}
|
||||||
|
if (x < 0) {
|
||||||
|
x = 10;
|
||||||
|
}
|
||||||
|
if (y + modalHeight > viewportHeight) {
|
||||||
|
y = viewportHeight - modalHeight - 10;
|
||||||
|
}
|
||||||
|
if (y < 0) {
|
||||||
|
y = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
left: `${x}px`,
|
||||||
|
top: `${y}px`,
|
||||||
|
zIndex: 1050,
|
||||||
|
maxWidth: '90vw', // 뷰포트 너비의 90%를 넘지 않도록
|
||||||
|
maxHeight: '90vh', // 뷰포트 높이의 90%를 넘지 않도록
|
||||||
|
};
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.event-icon-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-icon-select {
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-icon-select:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-mark {
|
||||||
|
position: absolute;
|
||||||
|
top: -8px;
|
||||||
|
right: -8px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
background-color: #dc3545;
|
||||||
|
color: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-modal {
|
||||||
|
min-width: 120px;
|
||||||
|
max-width: 300px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 작은 화면에서의 스타일 */
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
.event-modal {
|
||||||
|
min-width: 100px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -15,30 +15,18 @@
|
|||||||
{{ user.name }}
|
{{ user.name }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<CommuterBtn
|
<CommuterBtn :userId="user.id" :checkedInProject="checkedInProject || {}" ref="workTimeComponentRef" />
|
||||||
:userId="user.id"
|
|
||||||
:checkedInProject="checkedInProject || {}"
|
|
||||||
@workTimeUpdated="handleWorkTimeUpdate"
|
|
||||||
@leaveTimeUpdated="handleLeaveTimeUpdate"
|
|
||||||
ref="workTimeComponentRef"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<MainEventList
|
<MainEventList
|
||||||
:categoryList="categoryList"
|
:categoryList="categoryList"
|
||||||
:baseUrl="baseUrl"
|
:baseUrl="baseUrl"
|
||||||
:birthdayList="birthdayList"
|
:birthdayList="birthdayList"
|
||||||
:vacationList="vacationList"
|
:vacationList="vacationList"
|
||||||
|
:birthdayPartyList="birthdayPartyList"
|
||||||
|
:dinnerList="dinnerList"
|
||||||
|
:teaTimeList="teaTimeList"
|
||||||
|
:workShopList="workShopList"
|
||||||
/>
|
/>
|
||||||
<!-- <CommuterProjectList
|
|
||||||
:categoryList="categoryList"
|
|
||||||
:project="project"
|
|
||||||
:commuters="commuters"
|
|
||||||
:baseUrl="baseUrl"
|
|
||||||
:user="user"
|
|
||||||
:selectedProject="selectedProject"
|
|
||||||
:checkedInProject="checkedInProject"
|
|
||||||
@drop="handleProjectDrop"
|
|
||||||
/> -->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -60,41 +48,24 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CenterModal :display="isModalOpen" @close="closeModal">
|
<EventModal
|
||||||
<template #title>
|
v-if="showModal"
|
||||||
{{ eventDate }}
|
:position="modalPosition"
|
||||||
</template>
|
:selected-date="selectedDate"
|
||||||
<template #body>
|
:base-url="baseUrl"
|
||||||
<div v-if="selectedDateCommuters.length > 0">
|
:date-events="currentDateEvents"
|
||||||
<div v-for="(commuter, index) in selectedDateCommuters" :key="index">
|
@select="handleEventSelect"
|
||||||
<div class="d-flex align-items-center my-2">
|
@delete="handleEventDelete"
|
||||||
<img
|
@insert="handleEventInsert"
|
||||||
:src="`${baseUrl}upload/img/profile/${commuter.profile}`"
|
@close="handleCloseModal"
|
||||||
class="rounded-circle me-2 w-px-50 h-px-50"
|
/>
|
||||||
@error="$event.target.src = '/img/icons/icon.png'"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<span class="text-white fw-bold rounded py-1 px-3" :style="`background: ${commuter.projctcolor} !important;`">{{
|
|
||||||
commuter.memberName
|
|
||||||
}}</span>
|
|
||||||
|
|
||||||
<div class="ms-auto text-start fw-bold">{{ commuter.COMMUTCMT }} ~ {{ commuter.COMMUTLVE || '00:00:00' }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #footer>
|
|
||||||
<BackBtn @click="closeModal" />
|
|
||||||
</template>
|
|
||||||
</CenterModal>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import FullCalendar from '@fullcalendar/vue3';
|
import FullCalendar from '@fullcalendar/vue3';
|
||||||
import dayGridPlugin from '@fullcalendar/daygrid';
|
import dayGridPlugin from '@fullcalendar/daygrid';
|
||||||
import interactionPlugin from '@fullcalendar/interaction';
|
import interactionPlugin from '@fullcalendar/interaction';
|
||||||
import CenterModal from '@c/modal/CenterModal.vue';
|
import { inject, onMounted, reactive, ref, watch } from 'vue';
|
||||||
import { computed, inject, onMounted, reactive, ref, watch } from 'vue';
|
|
||||||
import $api from '@api';
|
import $api from '@api';
|
||||||
import 'flatpickr/dist/flatpickr.min.css';
|
import 'flatpickr/dist/flatpickr.min.css';
|
||||||
import '@/assets/css/app-calendar.css';
|
import '@/assets/css/app-calendar.css';
|
||||||
@ -102,13 +73,12 @@
|
|||||||
import { useUserInfoStore } from '@/stores/useUserInfoStore';
|
import { useUserInfoStore } from '@/stores/useUserInfoStore';
|
||||||
import { useProjectStore } from '@/stores/useProjectStore';
|
import { useProjectStore } from '@/stores/useProjectStore';
|
||||||
import CommuterBtn from '@c/commuters/CommuterBtn.vue';
|
import CommuterBtn from '@c/commuters/CommuterBtn.vue';
|
||||||
import CommuterProjectList from '@c/commuters/CommuterProjectList.vue';
|
|
||||||
import MainEventList from '@c/main/MainEventList.vue';
|
import MainEventList from '@c/main/MainEventList.vue';
|
||||||
import BackBtn from '@c/button/BackBtn.vue';
|
import EventModal from '@c/main/EventModal.vue';
|
||||||
|
import { useToastStore } from '@s/toastStore';
|
||||||
|
|
||||||
const baseUrl = import.meta.env.VITE_DOMAIN;
|
const baseUrl = import.meta.env.VITE_DOMAIN;
|
||||||
const user = ref({});
|
const user = ref({});
|
||||||
const project = ref({});
|
|
||||||
const userStore = useUserInfoStore();
|
const userStore = useUserInfoStore();
|
||||||
const projectStore = useProjectStore();
|
const projectStore = useProjectStore();
|
||||||
|
|
||||||
@ -116,39 +86,45 @@
|
|||||||
const fullCalendarRef = ref(null);
|
const fullCalendarRef = ref(null);
|
||||||
const workTimeComponentRef = ref(null);
|
const workTimeComponentRef = ref(null);
|
||||||
const calendarEvents = ref([]);
|
const calendarEvents = ref([]);
|
||||||
const eventDate = ref('');
|
|
||||||
|
|
||||||
const selectedProject = ref(null);
|
const selectedProject = ref(null);
|
||||||
const checkedInProject = ref(null);
|
const checkedInProject = ref(null);
|
||||||
|
|
||||||
// 모달 상태
|
|
||||||
const isModalOpen = ref(false);
|
|
||||||
|
|
||||||
const commuters = ref([]);
|
const commuters = ref([]);
|
||||||
const monthlyCommuters = ref([]);
|
|
||||||
|
// 이벤트 모달 관련
|
||||||
|
const showModal = ref(false);
|
||||||
|
const modalPosition = ref({ x: 0, y: 0 });
|
||||||
|
const selectedDate = ref('');
|
||||||
|
|
||||||
// 공통 함수
|
// 공통 함수
|
||||||
const $common = inject('common');
|
const $common = inject('common');
|
||||||
|
const toastStore = useToastStore();
|
||||||
|
|
||||||
// 출퇴근 컴포넌트 이벤트 핸들러
|
// 롱프레스 관련 변수 추가
|
||||||
const handleWorkTimeUpdate = () => {
|
const pressTimer = ref(null);
|
||||||
todaysCommuter();
|
const longPressDelay = 500; // 0.5초
|
||||||
loadCommuters();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleLeaveTimeUpdate = () => {
|
// // 출퇴근 컴포넌트 이벤트 핸들러
|
||||||
todaysCommuter();
|
// const handleWorkTimeUpdate = () => {
|
||||||
};
|
// todaysCommuter();
|
||||||
|
// //loadCommuters();
|
||||||
|
// };
|
||||||
|
|
||||||
// 오늘 출근 모든 사용자 조회
|
// const handleLeaveTimeUpdate = () => {
|
||||||
const todaysCommuter = async () => {
|
// todaysCommuter();
|
||||||
const res = await $api.get(`commuters/todays`);
|
// };
|
||||||
if (res.status === 200) {
|
|
||||||
commuters.value = res.data.data;
|
// // 오늘 출근 모든 사용자 조회
|
||||||
}
|
// const todaysCommuter = async () => {
|
||||||
};
|
// const res = await $api.get(`commuters/todays`);
|
||||||
|
// if (res.status === 200) {
|
||||||
|
// commuters.value = res.data.data;
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
/************* category ***************/
|
/************* category ***************/
|
||||||
|
|
||||||
// 이벤트 카테고리 데이터 로딩
|
// 이벤트 카테고리 데이터 로딩
|
||||||
const categoryList = ref([]);
|
const categoryList = ref([]);
|
||||||
const fetchCategoryList = async () => {
|
const fetchCategoryList = async () => {
|
||||||
@ -159,8 +135,19 @@
|
|||||||
/************* init ***************/
|
/************* init ***************/
|
||||||
const monthBirthdayList = ref([]);
|
const monthBirthdayList = ref([]);
|
||||||
const monthVacationList = ref([]);
|
const monthVacationList = ref([]);
|
||||||
|
const monthBirthdayPartyList = ref([]);
|
||||||
|
const monthDinnerList = ref([]);
|
||||||
|
const monthTeaTimeList = ref([]);
|
||||||
|
const monthWorkShopList = ref([]);
|
||||||
|
|
||||||
const birthdayList = ref([]);
|
const birthdayList = ref([]);
|
||||||
const vacationList = ref([]);
|
const vacationList = ref([]);
|
||||||
|
const birthdayPartyList = ref([]);
|
||||||
|
const dinnerList = ref([]);
|
||||||
|
const teaTimeList = ref([]);
|
||||||
|
const workShopList = ref([]);
|
||||||
|
|
||||||
|
const currentDateEvents = ref([]);
|
||||||
|
|
||||||
// 생일자, 휴가자, 이벤트 일정 조회
|
// 생일자, 휴가자, 이벤트 일정 조회
|
||||||
const fetchEventList = async param => {
|
const fetchEventList = async param => {
|
||||||
@ -187,31 +174,35 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 생일파티
|
// 초기화
|
||||||
if (res?.birthdayPartyList?.length) {
|
monthBirthdayPartyList.value = [];
|
||||||
res.birthdayPartyList.forEach(party => {
|
monthDinnerList.value = [];
|
||||||
addEvent(party.EVENTDT, 'birthdayParty', '생일파티');
|
monthTeaTimeList.value = [];
|
||||||
});
|
monthTeaTimeList.value = [];
|
||||||
}
|
|
||||||
|
|
||||||
// 회식
|
if (res?.eventList?.length) {
|
||||||
if (res?.dinnerList?.length) {
|
res.eventList.forEach(item => {
|
||||||
res.dinnerList.forEach(dinner => {
|
switch (item.CMNCODVAL) {
|
||||||
addEvent(dinner.EVENTDT, 'dinner', '회식');
|
case 300203:
|
||||||
});
|
monthBirthdayPartyList.value = [...monthBirthdayPartyList.value, item];
|
||||||
}
|
addEvent($common.dateFormatter(item.LOCEVTTME, 'YMD'), 'birthdayParty', '생일파티');
|
||||||
|
break;
|
||||||
|
|
||||||
// 티타임
|
case 300204:
|
||||||
if (res?.teaTimeList?.length) {
|
monthDinnerList.value = [...monthDinnerList.value, item];
|
||||||
res.teaTimeList.forEach(tea => {
|
addEvent($common.dateFormatter(item.LOCEVTTME, 'YMD'), 'dinner', '회식');
|
||||||
addEvent(tea.EVENTDT, 'teaTime', '티타임');
|
break;
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 워크샵
|
case 300205:
|
||||||
if (res?.workshopList?.length) {
|
monthTeaTimeList.value = [...monthTeaTimeList.value, item];
|
||||||
res.workshopList.forEach(workshop => {
|
addEvent($common.dateFormatter(item.LOCEVTTME, 'YMD'), 'teaTime', '티타임');
|
||||||
addEvent(workshop.EVENTDT, 'workshop', '워크샵');
|
break;
|
||||||
|
|
||||||
|
case 300206:
|
||||||
|
monthWorkShopList.value = [...monthWorkShopList.value, item];
|
||||||
|
addEvent($common.dateFormatter(item.LOCEVTTME, 'YMD'), 'workshop', '워크샵');
|
||||||
|
break;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -221,11 +212,12 @@
|
|||||||
// 생일의 경우 달력의 현재 년도로 변경하여 처리
|
// 생일의 경우 달력의 현재 년도로 변경하여 처리
|
||||||
if (type === 'birthday') {
|
if (type === 'birthday') {
|
||||||
const calendarApi = fullCalendarRef.value?.getApi();
|
const calendarApi = fullCalendarRef.value?.getApi();
|
||||||
|
|
||||||
if (calendarApi) {
|
if (calendarApi) {
|
||||||
const dateArr = calendarApi.currentData.viewTitle.split(' ');
|
const calendarDate = calendarApi.currentData.currentDate;
|
||||||
const calendarYear = parseInt(dateArr[0].replace(/\D/g, ''), 10);
|
const { year } = $common.formatDateTime(new Date(calendarDate));
|
||||||
const birthDate = $common.dateFormatter(date, 'MD');
|
const birthDate = $common.dateFormatter(date, 'MD');
|
||||||
date = `${calendarYear}-${birthDate}`;
|
date = `${year}-${birthDate}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,6 +248,26 @@
|
|||||||
if (monthVacationList.value) {
|
if (monthVacationList.value) {
|
||||||
vacationList.value = $common.filterTargetByDate(monthVacationList.value, 'LOCVACUDT', month, day);
|
vacationList.value = $common.filterTargetByDate(monthVacationList.value, 'LOCVACUDT', month, day);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 생일파티
|
||||||
|
if (monthBirthdayPartyList.value) {
|
||||||
|
birthdayPartyList.value = $common.filterTargetByDate(monthBirthdayPartyList.value, 'LOCEVTTME', month, day);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 회식
|
||||||
|
if (monthDinnerList.value) {
|
||||||
|
dinnerList.value = $common.filterTargetByDate(monthDinnerList.value, 'LOCEVTTME', month, day);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 티타임
|
||||||
|
if (monthTeaTimeList.value) {
|
||||||
|
teaTimeList.value = $common.filterTargetByDate(monthTeaTimeList.value, 'LOCEVTTME', month, day);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 워크샵
|
||||||
|
if (monthWorkShopList.value) {
|
||||||
|
workShopList.value = $common.filterTargetByDate(monthWorkShopList.value, 'LOCEVTTME', month, day);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 캘린더 데이터 가져오기
|
// 캘린더 데이터 가져오기
|
||||||
@ -264,26 +276,20 @@
|
|||||||
const calendarApi = fullCalendarRef.value?.getApi();
|
const calendarApi = fullCalendarRef.value?.getApi();
|
||||||
if (!calendarApi) return;
|
if (!calendarApi) return;
|
||||||
|
|
||||||
// 현재 표시된 달력의 연도, 월 추출
|
const date = calendarApi.currentData.currentDate;
|
||||||
const date = calendarApi.currentData.viewTitle;
|
const { year, month } = $common.formatDateTime(new Date(date));
|
||||||
const dateArr = date.split(' ');
|
|
||||||
let currentYear = dateArr[0].trim();
|
|
||||||
let currentMonth = dateArr[1].trim();
|
|
||||||
const regex = /\D/g;
|
|
||||||
// 숫자가 아닌 문자 제거 후 정수로 변환
|
|
||||||
currentYear = parseInt(currentYear.replace(regex, ''), 10);
|
|
||||||
currentMonth = parseInt(currentMonth.replace(regex, ''), 10);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 현재 표시 중인 월의 공휴일 정보 가져오기
|
// 현재 표시 중인 월의 공휴일 정보 가져오기
|
||||||
const holidayEvents = await fetchHolidays(currentYear, String(currentMonth).padStart(2, '0'));
|
const holidayEvents = await fetchHolidays(year, month);
|
||||||
calendarEvents.value = [...holidayEvents]; // 공휴일 정보로 초기화
|
calendarEvents.value = [...holidayEvents]; // 공휴일 정보로 초기화
|
||||||
|
|
||||||
// 이벤트 데이터 가져오기
|
// 이벤트 데이터 가져오기
|
||||||
const param = new URLSearchParams();
|
const param = new URLSearchParams();
|
||||||
param.append('year', currentYear);
|
param.append('year', year);
|
||||||
param.append('month', currentMonth);
|
param.append('month', month);
|
||||||
param.append('day', '1'); // 해당 월의 첫날
|
param.append('day', '1'); // 해당 월의 첫날
|
||||||
|
|
||||||
await fetchEventList(param);
|
await fetchEventList(param);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('공휴일 정보 로딩 실패:', error);
|
console.error('공휴일 정보 로딩 실패:', error);
|
||||||
@ -318,81 +324,6 @@
|
|||||||
return !isWeekend && !isHoliday;
|
return !isWeekend && !isHoliday;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 이벤트 타입 정의
|
|
||||||
const eventTypes = [
|
|
||||||
{ type: 'birthdayParty', code: '300203', title: '생일파티' },
|
|
||||||
{ type: 'dinner', code: '300204', title: '회식' },
|
|
||||||
{ type: 'teaTime', code: '300205', title: '티타임' },
|
|
||||||
{ type: 'workshop', code: '300206', title: '워크샵' },
|
|
||||||
];
|
|
||||||
|
|
||||||
// 날짜 클릭 이벤트 함수 수정
|
|
||||||
const handleDateClick = info => {
|
|
||||||
// const clickedDate = dayjs(info.dateStr);
|
|
||||||
const { month, day } = $common.formatDateTime(new Date(info.dateStr));
|
|
||||||
|
|
||||||
// 클릭한 날짜의 이벤트 리스트 필터링
|
|
||||||
useFilterEventList(month, day);
|
|
||||||
|
|
||||||
// 팝오버 HTML 생성
|
|
||||||
const popoverContent = `
|
|
||||||
<div class="d-flex flex-wrap gap-2 p-2">
|
|
||||||
${eventTypes
|
|
||||||
.map(
|
|
||||||
event => `
|
|
||||||
<img
|
|
||||||
src="${baseUrl}img/main-category-img/main-${event.code}.png"
|
|
||||||
class="event-icon-select"
|
|
||||||
data-event-type="${event.type}"
|
|
||||||
data-event-title="${event.title}"
|
|
||||||
data-date="${info.dateStr}"
|
|
||||||
style="width: 25px; height: 25px; cursor: pointer;"
|
|
||||||
/>
|
|
||||||
`,
|
|
||||||
)
|
|
||||||
.join('')}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
// 기존 팝오버 제거
|
|
||||||
const existingPopovers = document.querySelectorAll('.event-popover');
|
|
||||||
existingPopovers.forEach(popover => popover.remove());
|
|
||||||
|
|
||||||
// 새 팝오버 생성
|
|
||||||
const popover = document.createElement('div');
|
|
||||||
popover.className = 'event-popover position-absolute bg-white shadow rounded z-3';
|
|
||||||
popover.innerHTML = popoverContent;
|
|
||||||
popover.style.top = `${info.jsEvent.pageY}px`;
|
|
||||||
popover.style.left = `${info.jsEvent.pageX}px`;
|
|
||||||
|
|
||||||
// 아이콘 클릭 이벤트 추가
|
|
||||||
popover.addEventListener('click', e => {
|
|
||||||
const target = e.target;
|
|
||||||
if (target.classList.contains('event-icon-select')) {
|
|
||||||
const eventType = target.dataset.eventType;
|
|
||||||
const eventTitle = target.dataset.eventTitle;
|
|
||||||
const date = target.dataset.date;
|
|
||||||
|
|
||||||
// 이벤트 추가
|
|
||||||
addEvent(date, eventType, eventTitle);
|
|
||||||
|
|
||||||
// 팝오버 닫기
|
|
||||||
popover.remove();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 문서에 팝오버 추가
|
|
||||||
document.body.appendChild(popover);
|
|
||||||
|
|
||||||
// 팝오버 외부 클릭 시 닫기
|
|
||||||
document.addEventListener('click', e => {
|
|
||||||
if (!e.target.closest('.event-popover') && !e.target.closest('.fc-daygrid-day')) {
|
|
||||||
const popovers = document.querySelectorAll('.event-popover');
|
|
||||||
popovers.forEach(p => p.remove());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 날짜 셀 클래스 추가 함수
|
// 날짜 셀 클래스 추가 함수
|
||||||
const getCellClassNames = arg => {
|
const getCellClassNames = arg => {
|
||||||
const cellDate = dayjs(arg.date);
|
const cellDate = dayjs(arg.date);
|
||||||
@ -406,58 +337,106 @@
|
|||||||
return classes;
|
return classes;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 현재 달의 모든 출근 정보 조회
|
// 날짜 클릭 이벤트 핸들러
|
||||||
const loadCommuters = async () => {
|
const handleDateClick = info => {
|
||||||
const calendarApi = fullCalendarRef.value?.getApi();
|
const { month, day } = $common.formatDateTime(new Date(info.dateStr));
|
||||||
if (!calendarApi) return;
|
useFilterEventList(month, day);
|
||||||
const date = calendarApi.currentData.viewTitle;
|
};
|
||||||
const dateArr = date.split(' ');
|
|
||||||
let currentYear = dateArr[0].trim();
|
|
||||||
let currentMonth = dateArr[1].trim();
|
|
||||||
const regex = /\D/g;
|
|
||||||
currentYear = parseInt(currentYear.replace(regex, ''), 10);
|
|
||||||
currentMonth = parseInt(currentMonth.replace(regex, ''), 10);
|
|
||||||
|
|
||||||
const res = await $api.get('commuters/month', {
|
// 이벤트 모달 핸들러
|
||||||
params: {
|
const handleMouseDown = (date, jsEvent) => {
|
||||||
year: currentYear,
|
if (showModal.value) showModal.value = false;
|
||||||
month: currentMonth,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (res.status === 200) {
|
|
||||||
// 월별 출근 정보 저장
|
|
||||||
monthlyCommuters.value = res.data.data;
|
|
||||||
|
|
||||||
document.querySelectorAll('.fc-daygrid-day-events img.rounded-circle').forEach(img => {
|
// 해당 날짜의 이벤트 필터링
|
||||||
img.remove();
|
const dateEvents = calendarEvents.value.filter(
|
||||||
});
|
event => $common.dateFormatter(event.start, 'YMD') === $common.dateFormatter(date, 'YMD'),
|
||||||
|
);
|
||||||
|
|
||||||
monthlyCommuters.value.forEach(commuter => {
|
pressTimer.value = setTimeout(() => {
|
||||||
const date = commuter.COMMUTDAY;
|
modalPosition.value = {
|
||||||
const dateCell =
|
x: jsEvent.clientX,
|
||||||
document.querySelector(`.fc-day[data-date="${date}"]`) ||
|
y: jsEvent.clientY,
|
||||||
document.querySelector(`.fc-daygrid-day[data-date="${date}"]`);
|
};
|
||||||
if (dateCell) {
|
|
||||||
const dayEvents = dateCell.querySelector('.fc-daygrid-day-events');
|
|
||||||
if (dayEvents) {
|
|
||||||
dayEvents.classList.add('text-center');
|
|
||||||
// 프로필 이미지 생성
|
|
||||||
const profileImg = document.createElement('img');
|
|
||||||
profileImg.src = `${baseUrl}upload/img/profile/${commuter.profile}`;
|
|
||||||
profileImg.className = 'rounded-circle w-px-20 h-px-20 mx-1 mb-1 position-relative z-5 m-auto';
|
|
||||||
profileImg.style.border = `2px solid ${commuter.projctcolor}`;
|
|
||||||
profileImg.onerror = () => {
|
|
||||||
profileImg.src = '/img/icons/icon.png';
|
|
||||||
};
|
|
||||||
|
|
||||||
dayEvents.appendChild(profileImg);
|
selectedDate.value = date;
|
||||||
}
|
currentDateEvents.value = dateEvents;
|
||||||
}
|
showModal.value = true;
|
||||||
});
|
pressTimer.value = null;
|
||||||
|
}, longPressDelay);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 이벤트 모달 외부 클릭 시 닫힘
|
||||||
|
const handleMouseUp = () => {
|
||||||
|
if (pressTimer.value) {
|
||||||
|
clearTimeout(pressTimer.value);
|
||||||
|
pressTimer.value = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 이벤트 아이콘 표시 함수
|
// 이벤트 삭제 api
|
||||||
|
const toggleEvent = async (date, code, title) => {
|
||||||
|
const { data } = await $api.post('main/toggleEvent', {
|
||||||
|
date: date,
|
||||||
|
code: code,
|
||||||
|
title: title,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data?.code === 200) toastStore.onToast(data.message);
|
||||||
|
|
||||||
|
const { year, month, day } = $common.formatDateTime(new Date(date));
|
||||||
|
const param = new URLSearchParams();
|
||||||
|
param.append('year', year);
|
||||||
|
param.append('month', month);
|
||||||
|
param.append('day', day);
|
||||||
|
|
||||||
|
await fetchEventList(param);
|
||||||
|
useFilterEventList(month, year);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 이벤트 추가 api
|
||||||
|
const insertEvent = async (date, code, title, place, time) => {
|
||||||
|
const dateTime = $common.dateFormatter(`${date} ${time}`);
|
||||||
|
const { data } = await $api.post('main/inserEvent', {
|
||||||
|
date: dateTime,
|
||||||
|
code: code,
|
||||||
|
title: title,
|
||||||
|
place: place,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data?.code === 200) toastStore.onToast(data.message);
|
||||||
|
|
||||||
|
const { year, month, day } = $common.formatDateTime(new Date(date));
|
||||||
|
const param = new URLSearchParams();
|
||||||
|
param.append('year', year);
|
||||||
|
param.append('month', month);
|
||||||
|
param.append('day', day);
|
||||||
|
|
||||||
|
await fetchEventList(param);
|
||||||
|
useFilterEventList(month, year);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 이벤트 선택 핸들러
|
||||||
|
const handleEventSelect = data => {
|
||||||
|
toggleEvent(data.date, data.code, data.title);
|
||||||
|
showModal.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEventInsert = data => {
|
||||||
|
insertEvent(data.date, data.code, data.title, data.place, data.time);
|
||||||
|
showModal.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEventDelete = data => {
|
||||||
|
toggleEvent(data.date, data.code, data.title);
|
||||||
|
showModal.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 이벤트 모달 닫기
|
||||||
|
const handleCloseModal = () => {
|
||||||
|
showModal.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 달력 이벤트 아이콘 표시 함수
|
||||||
const handleEventContent = item => {
|
const handleEventContent = item => {
|
||||||
if (!item.event) return null;
|
if (!item.event) return null;
|
||||||
|
|
||||||
@ -518,6 +497,17 @@
|
|||||||
selectable: true,
|
selectable: true,
|
||||||
selectAllow: selectInfo => isSelectableDate(selectInfo.start),
|
selectAllow: selectInfo => isSelectableDate(selectInfo.start),
|
||||||
dateClick: handleDateClick,
|
dateClick: handleDateClick,
|
||||||
|
dayCellDidMount: arg => {
|
||||||
|
const dateCell = arg.el;
|
||||||
|
|
||||||
|
// 마우스 홀드시 이벤트 모달
|
||||||
|
dateCell.addEventListener('mousedown', e => {
|
||||||
|
const date = $common.dateFormatter(arg.date, 'YMD');
|
||||||
|
handleMouseDown(date, e);
|
||||||
|
});
|
||||||
|
dateCell.addEventListener('mouseup', handleMouseUp);
|
||||||
|
dateCell.addEventListener('mouseleave', handleMouseUp);
|
||||||
|
},
|
||||||
dayCellClassNames: getCellClassNames,
|
dayCellClassNames: getCellClassNames,
|
||||||
unselectAuto: true,
|
unselectAuto: true,
|
||||||
droppable: false,
|
droppable: false,
|
||||||
@ -546,39 +536,9 @@
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// selectedProject 변경 감지
|
|
||||||
watch(
|
|
||||||
() => projectStore.selectedProject,
|
|
||||||
newProject => {
|
|
||||||
if (newProject) {
|
|
||||||
selectedProject.value = newProject.PROJCTSEQ;
|
|
||||||
checkedInProject.value = newProject;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// 모달 열기
|
|
||||||
const openModal = () => {
|
|
||||||
isModalOpen.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 모달 닫기
|
|
||||||
const closeModal = () => {
|
|
||||||
isModalOpen.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const selectedDateCommuters = computed(() => {
|
|
||||||
return monthlyCommuters.value.filter(commuter => commuter.COMMUTDAY === eventDate.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await fetchData();
|
|
||||||
await userStore.userInfo();
|
await userStore.userInfo();
|
||||||
user.value = userStore.user;
|
user.value = userStore.user;
|
||||||
await projectStore.getProjectList('', '', 'true');
|
|
||||||
project.value = projectStore.projectList;
|
|
||||||
|
|
||||||
await todaysCommuter();
|
|
||||||
|
|
||||||
// 저장된 선택 프로젝트 가져오기
|
// 저장된 선택 프로젝트 가져오기
|
||||||
const storedProject = projectStore.getSelectedProject();
|
const storedProject = projectStore.getSelectedProject();
|
||||||
@ -598,6 +558,13 @@
|
|||||||
await fetchCategoryList();
|
await fetchCategoryList();
|
||||||
await fetchEventList(param);
|
await fetchEventList(param);
|
||||||
useFilterEventList(month, day);
|
useFilterEventList(month, day);
|
||||||
|
|
||||||
|
// 이벤트모달 외부 클릭 감지
|
||||||
|
// document.addEventListener('click', e => {
|
||||||
|
// if (showModal.value && !e.target.closest('.event-modal') && !e.target.closest('.fc-daygrid-day')) {
|
||||||
|
// showModal.value = false;
|
||||||
|
// }
|
||||||
|
// });
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
@ -605,7 +572,7 @@
|
|||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.event-popover {
|
.event-modal {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
}
|
}
|
||||||
@ -614,4 +581,9 @@
|
|||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
transition: transform 0.2s;
|
transition: transform 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 이벤트 모달 노출 시 텍스트 선택 방지 */
|
||||||
|
.fc-daygrid-day {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -2,10 +2,17 @@
|
|||||||
<div class="">
|
<div class="">
|
||||||
<template v-for="category in categoryList" :key="category.CMNCODVAL">
|
<template v-for="category in categoryList" :key="category.CMNCODVAL">
|
||||||
<div
|
<div
|
||||||
v-if="(category.CMNCODVAL === 300201 && birthdayList?.length) || (category.CMNCODVAL === 300202 && vacationList?.length)"
|
v-if="
|
||||||
|
(category.CMNCODVAL === 300201 && birthdayList?.length) ||
|
||||||
|
(category.CMNCODVAL === 300202 && vacationList?.length) ||
|
||||||
|
(category.CMNCODVAL === 300203 && birthdayPartyList?.length) ||
|
||||||
|
(category.CMNCODVAL === 300204 && dinnerList?.length) ||
|
||||||
|
(category.CMNCODVAL === 300205 && teaTimeList?.length) ||
|
||||||
|
(category.CMNCODVAL === 300206 && workShopList?.length)
|
||||||
|
"
|
||||||
class="border border-2 mt-3 card p-2"
|
class="border border-2 mt-3 card p-2"
|
||||||
>
|
>
|
||||||
<div class="row g-2">
|
<div class="row g-2 position-relative">
|
||||||
<div class="col-3 mx-0 px-0">
|
<div class="col-3 mx-0 px-0">
|
||||||
<div class="ratio ratio-1x1">
|
<div class="ratio ratio-1x1">
|
||||||
<img
|
<img
|
||||||
@ -22,6 +29,38 @@
|
|||||||
<template v-if="category.CMNCODVAL === 300202">
|
<template v-if="category.CMNCODVAL === 300202">
|
||||||
<MainMemberProfile :members="vacationList" :baseUrl="baseUrl" />
|
<MainMemberProfile :members="vacationList" :baseUrl="baseUrl" />
|
||||||
</template>
|
</template>
|
||||||
|
<template v-if="category.CMNCODVAL === 300203">
|
||||||
|
<div>
|
||||||
|
{{ birthdayPartyList[0].LOCEVTPLC }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ $common.dateFormatter(birthdayPartyList[0].LOCEVTTME) }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-if="category.CMNCODVAL === 300204">
|
||||||
|
<div>
|
||||||
|
{{ dinnerList[0].LOCEVTPLC }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ $common.dateFormatter(dinnerList[0].LOCEVTTME) }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-if="category.CMNCODVAL === 300205">
|
||||||
|
<div>
|
||||||
|
{{ teaTimeList[0].LOCEVTPLC }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ $common.dateFormatter(teaTimeList[0].LOCEVTTME) }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-if="category.CMNCODVAL === 300206">
|
||||||
|
<div>
|
||||||
|
{{ workShopList[0].LOCEVTPLC }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ $common.dateFormatter(workShopList[0].LOCEVTTME) }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -67,30 +106,17 @@
|
|||||||
vacationList: {
|
vacationList: {
|
||||||
type: Array,
|
type: Array,
|
||||||
},
|
},
|
||||||
|
birthdayPartyList: {
|
||||||
|
type: Array,
|
||||||
|
},
|
||||||
|
dinnerList: {
|
||||||
|
type: Array,
|
||||||
|
},
|
||||||
|
teaTimeList: {
|
||||||
|
type: Array,
|
||||||
|
},
|
||||||
|
workShopList: {
|
||||||
|
type: Array,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['drop', 'update:selectedProject', 'update:checkedInProject']);
|
|
||||||
|
|
||||||
// 현재 사용자 확인
|
|
||||||
const isCurrentUser = commuter => {
|
|
||||||
return props.user && commuter && commuter.MEMBERSEQ === props.user.id;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 드래그 시작 이벤트 핸들러
|
|
||||||
const dragStart = (event, project) => {
|
|
||||||
// 드래그 데이터 설정
|
|
||||||
event.dataTransfer.setData('application/json', JSON.stringify(project));
|
|
||||||
event.dataTransfer.effectAllowed = 'copy';
|
|
||||||
};
|
|
||||||
|
|
||||||
// 드래그 오버 드롭 허용
|
|
||||||
const allowDrop = event => {
|
|
||||||
event.preventDefault();
|
|
||||||
};
|
|
||||||
|
|
||||||
// 드롭
|
|
||||||
const handleDrop = (event, targetProject) => {
|
|
||||||
event.preventDefault();
|
|
||||||
emit('drop', { event, targetProject });
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -5,8 +5,9 @@
|
|||||||
<div class="ratio ratio-1x1 mb-0 profile-list">
|
<div class="ratio ratio-1x1 mb-0 profile-list">
|
||||||
<img
|
<img
|
||||||
:src="`${baseUrl}upload/img/profile/${member.MEMBERPRF}`"
|
:src="`${baseUrl}upload/img/profile/${member.MEMBERPRF}`"
|
||||||
|
:style="`border-color: ${member.usercolor} !important;`"
|
||||||
alt="User Profile"
|
alt="User Profile"
|
||||||
class="rounded-circle"
|
class="rounded-circle border border-2"
|
||||||
@error="$event.target.src = '/img/icons/icon.png'"
|
@error="$event.target.src = '/img/icons/icon.png'"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user