Merge remote-tracking branch 'origin/main' into board-comment-2
This commit is contained in:
commit
08dabf4191
2
.env.dev
2
.env.dev
@ -1,6 +1,6 @@
|
||||
VITE_DOMAIN = https://192.168.0.251:5173/
|
||||
# VITE_LOGIN_URL = http://localhost:10325/ms/
|
||||
# VITE_FILE_URL = http://localhost:10325/ms/
|
||||
VITE_SERVER = httpS://192.168.0.251:10325/
|
||||
VITE_API_URL = https://192.168.0.251:10325/api/
|
||||
VITE_TEST_URL = https://192.168.0.251:10325/test/
|
||||
VITE_KAKAO_MAP_KEY=6f092e8f45ee81186bb6d8408f66a492
|
||||
@ -1,6 +1,6 @@
|
||||
VITE_DOMAIN = http://localhost:5173/
|
||||
# VITE_LOGIN_URL = http://localhost:10325/ms/
|
||||
# VITE_FILE_URL = http://localhost:10325/ms/
|
||||
VITE_SERVER = http://localhost:10325/
|
||||
VITE_API_URL = http://localhost:10325/api/
|
||||
VITE_TEST_URL = http://localhost:10325/test/
|
||||
VITE_KAKAO_MAP_KEY=6f092e8f45ee81186bb6d8408f66a492
|
||||
@ -3,22 +3,33 @@
|
||||
|
||||
/* 휴가 */
|
||||
|
||||
.fc-daygrid-day-events {
|
||||
max-height: 100px !important;
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
/* 이벤트 선 없게 */
|
||||
.fc-event {
|
||||
border: none;
|
||||
}
|
||||
/* 오전전반차 그래프 */
|
||||
/* 오전 반차 그래프 (왼쪽 절반) */
|
||||
.fc-daygrid-event.half-day-am {
|
||||
width: calc(50% - 4px) !important;
|
||||
width: 50% !important;
|
||||
height: 8px !important;
|
||||
border-radius: 2px !important;
|
||||
font-size: 0px !important;
|
||||
}
|
||||
/* 오후반차 그래프프 */
|
||||
/* 오후 반차 그래프 (오른쪽 절반) */
|
||||
.fc-daygrid-event.half-day-pm {
|
||||
width: calc(50% - 4px) !important;
|
||||
margin-left: auto !important
|
||||
width: 50% !important;
|
||||
height: 8px !important;
|
||||
margin-left: auto !important;
|
||||
border-radius: 2px !important;
|
||||
font-size: 0px !important;
|
||||
}
|
||||
/* 연차 그래프 (풀풀) */
|
||||
.fc-daygrid-event.full-day {
|
||||
width: 100% !important;
|
||||
height: 8px !important;
|
||||
margin-left: auto !important;
|
||||
border-radius: 2px !important;
|
||||
font-size: 0px !important;
|
||||
}
|
||||
/* 공휴일,일요일 색상 */
|
||||
.fc-day-sun .fc-daygrid-day-number,
|
||||
@ -39,8 +50,8 @@
|
||||
.flatpickr-calendar:after {
|
||||
display: none !important;
|
||||
}
|
||||
/* 기본 스타일은 그대로 두고, 데이트피커 인풋의 추가 스타일 정의 */
|
||||
.fc-toolbar-title {
|
||||
/* 기본 스타일은 그대로 두고, 데이트피커 인풋의 추가 스타일 정의 */
|
||||
.fc-toolbar-title {
|
||||
cursor: pointer;
|
||||
}
|
||||
/* 클릭 가능한 날짜 (오늘 + 미래) */
|
||||
@ -84,7 +95,6 @@ opacity: 0.6; /* 흐려 보이게 */
|
||||
|
||||
|
||||
/* 본인 모달 */
|
||||
|
||||
/* 닫기 버튼 */
|
||||
.close-btn {
|
||||
position: absolute;
|
||||
@ -109,7 +119,6 @@ opacity: 0.6; /* 흐려 보이게 */
|
||||
|
||||
|
||||
/* 선물하기 모달 */
|
||||
|
||||
/* 연차 개수 버튼 */
|
||||
.count-btn {
|
||||
font-size: 18px;
|
||||
@ -127,7 +136,6 @@ opacity: 0.6; /* 흐려 보이게 */
|
||||
background: #cccccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* 버튼 컨테이너 (우측 정렬) */
|
||||
.custom-button-container {
|
||||
display: flex;
|
||||
@ -141,18 +149,17 @@ opacity: 0.6; /* 흐려 보이게 */
|
||||
padding: 10px; /* 크기 조정 */
|
||||
cursor: pointer; /* 클릭 가능하도록 변경 */
|
||||
}
|
||||
|
||||
/* 아이콘 색상 변경 (기본) */
|
||||
.custom-button i {
|
||||
color: #282538; /* 기본 아이콘 색상 */
|
||||
font-size: 25px; /* 아이콘 크기 */
|
||||
}
|
||||
|
||||
/* 버튼 호버 효과 */
|
||||
.custom-button:hover i {
|
||||
color: #ff0800; /* 호버 시 아이콘 색상 변경 */
|
||||
}
|
||||
|
||||
|
||||
.grayscaleImg {
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
|
||||
@ -7,12 +7,6 @@
|
||||
//-----------------
|
||||
let menu, animate;
|
||||
|
||||
<<<<<<< HEAD
|
||||
var menu, animate;
|
||||
(function () {
|
||||
// Initialize menu
|
||||
//-----------------
|
||||
=======
|
||||
let layoutMenuEl = document.querySelectorAll('#layout-menu');
|
||||
layoutMenuEl.forEach(function (element) {
|
||||
menu = new Menu(element, {
|
||||
@ -23,7 +17,6 @@ var menu, animate;
|
||||
window.Helpers.scrollToActive((animate = false));
|
||||
window.Helpers.mainMenu = menu;
|
||||
});
|
||||
>>>>>>> board-comment
|
||||
|
||||
// Initialize menu togglers and bind click on each
|
||||
let menuToggler = document.querySelectorAll('.layout-menu-toggle');
|
||||
|
||||
@ -28,10 +28,11 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineEmits, ref, defineProps } from "vue";
|
||||
import { defineEmits, ref, defineProps, watch } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
isDisabled: Boolean
|
||||
isDisabled: Boolean,
|
||||
selectedDate: String // 날짜 선택 값을 props로 받음
|
||||
});
|
||||
|
||||
const emit = defineEmits(["toggleHalfDay", "addVacationRequests", "resetHalfDay"]);
|
||||
@ -39,15 +40,16 @@ const halfDayType = ref(null);
|
||||
|
||||
const toggleHalfDay = (type) => {
|
||||
halfDayType.value = type;
|
||||
|
||||
emit("toggleHalfDay", halfDayType.value);
|
||||
|
||||
// ✅ 버튼 클릭 후 1초 후 자동 비활성화
|
||||
setTimeout(() => {
|
||||
halfDayType.value = null;
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
// `selectedDate`가 변경되면 반차 선택 초기화
|
||||
watch(() => props.selectedDate, (newDate) => {
|
||||
if (newDate) {
|
||||
resetHalfDay();
|
||||
}
|
||||
});
|
||||
|
||||
// 날짜 선택 후 반차 버튼 상태 초기화
|
||||
const resetHalfDay = () => {
|
||||
halfDayType.value = null;
|
||||
@ -89,20 +91,20 @@ defineExpose({ resetHalfDay });
|
||||
|
||||
/* 선택된 (눌린) 버튼 */
|
||||
.btn.active {
|
||||
border: 3px solid #fff; /* 흰색 테두리 강조 */
|
||||
border: 3px solid #fff;
|
||||
box-shadow: 0px 4px 15px rgba(0, 0, 0, 0.3);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
/* AM 버튼 (선택된 상태) */
|
||||
.btn-warning.active {
|
||||
background-color: #ffca2c !important; /* 진한 노란색 */
|
||||
background-color: #ffca2c !important;
|
||||
color: black;
|
||||
}
|
||||
|
||||
/* PM 버튼 (선택된 상태) */
|
||||
.btn-info.active {
|
||||
background-color: #0b5ed7 !important; /* 진한 파란색 */
|
||||
background-color: #0b5ed7 !important;
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
<template>
|
||||
<div class="mb-4 row">
|
||||
<label :for="name" class="col-md-2 col-form-label">{{ title }}</label>
|
||||
<label :for="inputId" class="col-md-2 col-form-label">{{ title }}</label>
|
||||
<div class="col-md-10">
|
||||
<input
|
||||
class="form-control"
|
||||
type="file"
|
||||
:id="name"
|
||||
:id="inputId"
|
||||
ref="fileInput"
|
||||
@change="changeHandler"
|
||||
multiple
|
||||
/>
|
||||
@ -21,7 +22,7 @@ import { ref ,computed} from 'vue';
|
||||
import { fileMsg } from '@/common/msgEnum';
|
||||
|
||||
// Props
|
||||
const prop = defineProps({
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '라벨',
|
||||
@ -29,7 +30,7 @@ const prop = defineProps({
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: 'nameplz',
|
||||
default: 'fileInput',
|
||||
required: true,
|
||||
},
|
||||
isAlert: {
|
||||
@ -38,12 +39,13 @@ const prop = defineProps({
|
||||
required: false,
|
||||
},
|
||||
});
|
||||
const inputId = computed(() => props.name || 'defaultFileInput');
|
||||
|
||||
const emits = defineEmits(['update:data', 'update:isValid']);
|
||||
|
||||
const MAX_TOTAL_SIZE = 5 * 1024 * 1024; // 5MB
|
||||
const MAX_FILE_COUNT = 5; // 최대 파일 개수
|
||||
const ALLOWED_FILE_TYPES = ['image/jpeg', 'image/png', 'application/pdf']; // 허용된 파일 유형
|
||||
const ALLOWED_FILE_TYPES = []; // 모든 파일을 허용
|
||||
|
||||
const showError = ref(false);
|
||||
const fileMsgKey = ref(''); // 에러 메시지 키
|
||||
@ -51,9 +53,12 @@ const fileMsgKey = ref(''); // 에러 메시지 키
|
||||
const changeHandler = (event) => {
|
||||
const files = Array.from(event.target.files);
|
||||
const totalSize = files.reduce((sum, file) => sum + file.size, 0);
|
||||
const invalidFiles = files.filter(file => !ALLOWED_FILE_TYPES.includes(file.type));
|
||||
|
||||
// 파일 검증 로직
|
||||
// ALLOWED_FILE_TYPES가 비어있으면 모든 파일 허용
|
||||
const invalidFiles = ALLOWED_FILE_TYPES.length > 0
|
||||
? files.filter(file => !ALLOWED_FILE_TYPES.includes(file.type))
|
||||
: [];
|
||||
|
||||
if (totalSize > MAX_TOTAL_SIZE) {
|
||||
showError.value = true;
|
||||
fileMsgKey.value = 'FileMaxSizeMsg';
|
||||
|
||||
@ -310,6 +310,18 @@ const closeEditModal = () => {
|
||||
isEditModalOpen.value = false;
|
||||
};
|
||||
|
||||
// 변경된 내용 있는지 확인
|
||||
const hasChanges = computed(() => {
|
||||
return selectedProject.value.PROJCTNAM !== props.title ||
|
||||
selectedProject.value.PROJCTSTR !== props.strdate ||
|
||||
selectedProject.value.PROJCTEND !== props.enddate ||
|
||||
selectedProject.value.PROJCTZIP !== props.addressZip ||
|
||||
selectedProject.value.PROJCTARR !== props.address ||
|
||||
selectedProject.value.PROJCTDTL !== props.addressdtail ||
|
||||
selectedProject.value.PROJCTDES !== props.description ||
|
||||
selectedProject.value.PROJCTCOL !== props.projctCol;
|
||||
});
|
||||
|
||||
// 프로젝트 수정
|
||||
const handleUpdate = () => {
|
||||
nameAlert.value = selectedProject.value.PROJCTNAM.trim() === '';
|
||||
@ -318,6 +330,11 @@ const handleUpdate = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasChanges.value) {
|
||||
toastStore.onToast('변경된 내용이 없습니다.', 'e');
|
||||
return;
|
||||
}
|
||||
|
||||
$api.patch('project/update', {
|
||||
projctSeq: selectedProject.value.PROJCTSEQ,
|
||||
projctNam: selectedProject.value.PROJCTNAM,
|
||||
@ -336,7 +353,6 @@ const handleUpdate = () => {
|
||||
closeEditModal();
|
||||
// 상위 컴포넌트에 업데이트 알림
|
||||
emit('update');
|
||||
window.location.reload()
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<ul class="list-unstyled users-list d-flex align-items-center gap-1">
|
||||
<li
|
||||
v-for="(user, index) in userList"
|
||||
v-for="(user, index) in sortedUserList"
|
||||
:key="index"
|
||||
class="avatar pull-up"
|
||||
:class="{ 'opacity-100': isUserDisabled(user) }"
|
||||
@ -25,7 +25,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref, nextTick } from 'vue';
|
||||
import { onMounted, ref, nextTick, computed } from 'vue';
|
||||
import { useUserStore } from '@s/userList';
|
||||
import $api from '@api';
|
||||
|
||||
@ -41,6 +41,19 @@ const props = defineProps({
|
||||
}
|
||||
});
|
||||
|
||||
// 활성화된 회원을 앞으로 정렬하는 computed 속성
|
||||
const sortedUserList = computed(() => {
|
||||
return [...userList.value].sort((a, b) => {
|
||||
const aDisabled = isUserDisabled(a);
|
||||
const bDisabled = isUserDisabled(b);
|
||||
|
||||
// 활성화된 사용자가 먼저 오도록 정렬
|
||||
if (!aDisabled && bDisabled) return -1;
|
||||
if (aDisabled && !bDisabled) return 1;
|
||||
return 0;
|
||||
});
|
||||
});
|
||||
|
||||
// 사용자의 프로젝트 참여 상태 확인
|
||||
const fetchProjectParticipation = async () => {
|
||||
if (props.projctSeq) {
|
||||
@ -79,27 +92,33 @@ const isUserDisabled = (user) => {
|
||||
|
||||
// 클릭 시 활성화/비활성화 및 DB 업데이트
|
||||
const toggleDisable = async (index) => {
|
||||
const user = userList.value[index];
|
||||
const user = sortedUserList.value[index];
|
||||
if (user) {
|
||||
const newParticipationStatus = props.projctSeq
|
||||
? 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';
|
||||
// 원래 userList에서 해당 사용자를 찾아 업데이트
|
||||
const originalIndex = userList.value.findIndex(u => u.MEMBERSEQ === user.MEMBERSEQ);
|
||||
if (originalIndex !== -1) {
|
||||
userList.value[originalIndex].PROJCTYON = newParticipationStatus ? '0' : '1';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
user.disabled = newParticipationStatus;
|
||||
// 원래 userList에서 해당 사용자를 찾아 업데이트
|
||||
const originalIndex = userList.value.findIndex(u => u.MEMBERSEQ === user.MEMBERSEQ);
|
||||
if (originalIndex !== -1) {
|
||||
userList.value[originalIndex].disabled = newParticipationStatus;
|
||||
emitUserListUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// emit
|
||||
|
||||
@ -32,17 +32,17 @@
|
||||
|
||||
window.isDarkStyle = window.Helpers.isDarkStyle();
|
||||
|
||||
const loadScript = src => {
|
||||
const loadScript = src => {
|
||||
const script = document.createElement('script');
|
||||
script.src = src;
|
||||
script.type = 'text/javascript';
|
||||
script.async = true;
|
||||
document.body.appendChild(script);
|
||||
};
|
||||
nextTick(async () => {
|
||||
};
|
||||
nextTick(async () => {
|
||||
await wait(200);
|
||||
loadScript('/vendor/js/menu.js');
|
||||
// loadScript('/js/main.js');
|
||||
});
|
||||
loadScript('/js/main.js');
|
||||
});
|
||||
</script>
|
||||
<style></style>
|
||||
|
||||
@ -241,7 +241,7 @@
|
||||
|
||||
const user = ref(null);
|
||||
//const baseUrl = $api.defaults.baseURL.replace(/api\/$/, '');
|
||||
const baseUrl = import.meta.env.BASE_URL;
|
||||
const baseUrl = import.meta.env.VITE_SERVER;
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const userStore = useUserInfoStore();
|
||||
|
||||
@ -7,9 +7,11 @@
|
||||
<div class="sidebar-content">
|
||||
<div class="sidebar-actions text-center my-3">
|
||||
<HalfDayButtons
|
||||
ref="halfDayButtonsRef"
|
||||
@toggleHalfDay="toggleHalfDay"
|
||||
@addVacationRequests="saveVacationChanges"
|
||||
:isDisabled="!hasChanges"
|
||||
:selectedDate="selectedDate"
|
||||
/>
|
||||
</div>
|
||||
<ProfileList
|
||||
@ -58,28 +60,31 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, onMounted, nextTick, computed, watch } from "vue";
|
||||
import axios from "@api";
|
||||
import FullCalendar from "@fullcalendar/vue3";
|
||||
import dayGridPlugin from "@fullcalendar/daygrid";
|
||||
import interactionPlugin from "@fullcalendar/interaction";
|
||||
// Flatpickr 및 MonthSelect 플러그인 임포트
|
||||
import flatpickr from "flatpickr";
|
||||
import monthSelectPlugin from "flatpickr/dist/plugins/monthSelect/index";
|
||||
import "flatpickr/dist/flatpickr.min.css";
|
||||
import "flatpickr/dist/plugins/monthSelect/style.css";
|
||||
<script setup>
|
||||
import { reactive, ref, onMounted, nextTick, computed, watch, onBeforeUnmount } from "vue";
|
||||
import axios from "@api";
|
||||
import FullCalendar from "@fullcalendar/vue3";
|
||||
import dayGridPlugin from "@fullcalendar/daygrid";
|
||||
import interactionPlugin from "@fullcalendar/interaction";
|
||||
// Flatpickr 및 MonthSelect 플러그인 임포트
|
||||
import flatpickr from "flatpickr";
|
||||
import monthSelectPlugin from "flatpickr/dist/plugins/monthSelect/index";
|
||||
import "flatpickr/dist/flatpickr.min.css";
|
||||
import "flatpickr/dist/plugins/monthSelect/style.css";
|
||||
|
||||
import "@/assets/css/app-calendar.css";
|
||||
import "bootstrap-icons/font/bootstrap-icons.css";
|
||||
import HalfDayButtons from "@c/button/HalfDayButtons.vue";
|
||||
import ProfileList from "@c/vacation/ProfileList.vue";
|
||||
import VacationModal from "@c/modal/VacationModal.vue";
|
||||
import VacationGrantModal from "@c/modal/VacationGrantModal.vue";
|
||||
import { useUserStore } from "@s/userList";
|
||||
import { useUserInfoStore } from "@s/useUserInfoStore";
|
||||
import { fetchHolidays } from "@c/calendar/holiday.js";
|
||||
import { useToastStore } from '@s/toastStore';
|
||||
import "@/assets/css/app-calendar.css";
|
||||
import "bootstrap-icons/font/bootstrap-icons.css";
|
||||
import HalfDayButtons from "@c/button/HalfDayButtons.vue";
|
||||
import ProfileList from "@c/vacation/ProfileList.vue";
|
||||
import VacationModal from "@c/modal/VacationModal.vue";
|
||||
import VacationGrantModal from "@c/modal/VacationGrantModal.vue";
|
||||
import { useUserStore } from "@s/userList";
|
||||
import { useUserInfoStore } from "@s/useUserInfoStore";
|
||||
import { fetchHolidays } from "@c/calendar/holiday.js";
|
||||
import { useToastStore } from '@s/toastStore';
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const toastStore = useToastStore();
|
||||
const userStore = useUserInfoStore();
|
||||
@ -90,7 +95,7 @@
|
||||
const receivedVacations = ref([]);
|
||||
const isModalOpen = ref(false);
|
||||
const remainingVacationData = ref({});
|
||||
|
||||
const selectedDate = ref(null);
|
||||
const lastRemainingYear = ref(new Date().getFullYear());
|
||||
const lastRemainingMonth = ref(String(new Date().getMonth() + 1).padStart(2, "0"));
|
||||
const isGrantModalOpen = ref(false);
|
||||
@ -106,6 +111,39 @@
|
||||
const fetchedEvents = ref([]);
|
||||
const halfDayButtonsRef = ref(null);
|
||||
|
||||
// 페이지 이동 시 변경 사항 확인
|
||||
router.beforeEach((to, from, next) => {
|
||||
if (hasChanges.value) {
|
||||
const answer = window.confirm("저장하지 않은 변경 사항이 있습니다. 이동하시겠습니까?");
|
||||
if (!answer) {
|
||||
return next(false); // 이동 취소
|
||||
}
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener("beforeunload", preventUnsavedChanges);
|
||||
});
|
||||
|
||||
function preventUnsavedChanges(event) {
|
||||
if (hasChanges.value) {
|
||||
event.preventDefault();
|
||||
event.returnValue = ""; // 대부분의 브라우저에서 경고 메시지 표시됨
|
||||
}
|
||||
}
|
||||
|
||||
// `selectedDates` 변경 시 반차 버튼 초기화
|
||||
watch(
|
||||
() => Array.from(selectedDates.value.keys()), // 선택된 날짜 리스트 감시
|
||||
(newKeys) => {
|
||||
if (halfDayButtonsRef.value) {
|
||||
halfDayButtonsRef.value.resetHalfDay();
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
// 데이트피커 인풋 ref
|
||||
const calendarDatepicker = ref(null);
|
||||
let fpInstance = null;
|
||||
@ -192,6 +230,7 @@ function handleDateClick(info) {
|
||||
await fetchRemainingVacation();
|
||||
const currentYear = new Date().getFullYear();
|
||||
await fetchVacationHistory(currentYear);
|
||||
window.addEventListener("beforeunload", preventUnsavedChanges);
|
||||
|
||||
// Flatpickr 초기화 (달 선택 모드)
|
||||
fpInstance = flatpickr(calendarDatepicker.value, {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user