jichoi / calendar star
박정하 박정하 07-08
250708 박정하 휴가 현황, 휴가 신청 추가
@3beb56e8146272c1565935674a00020adf29a7b8
 
client/resources/api/cmmnCode.js (added)
+++ client/resources/api/cmmnCode.js
@@ -0,0 +1,6 @@
+import apiClient from "./index";
+
+// 조회 - 목록
+export const findCodeListProc = data => {
+  return apiClient.post('/system/cmmnCode/findCodeList.json', data);
+}(파일 끝에 줄바꿈 문자 없음)
client/resources/api/index.js
--- client/resources/api/index.js
+++ client/resources/api/index.js
@@ -54,7 +54,7 @@
             window.history.back();
             return Promise.reject(error);
         }
-        
+
         // 리프레시 토큰 요청
         if (originalReq.url.includes('/refresh/tknReissue.json')) {
             return Promise.reject(error);
 
client/resources/api/sanctns.js (added)
+++ client/resources/api/sanctns.js
@@ -0,0 +1,6 @@
+import apiClient from "./index";
+
+// 조회 - 목록
+export const findSanctnsProc = data => {
+  return apiClient.get('/sanctn/findSanctns.json', data);
+}(파일 끝에 줄바꿈 문자 없음)
 
client/resources/api/user.js (added)
+++ client/resources/api/user.js
@@ -0,0 +1,6 @@
+import apiClient from "./index";
+
+// 조회 - 목록
+export const findUsersProc = data => {
+  return apiClient.get('/user/users.json', { params: data });
+}(파일 끝에 줄바꿈 문자 없음)
 
client/resources/api/vcatn.js (added)
+++ client/resources/api/vcatn.js
@@ -0,0 +1,36 @@
+import apiClient from "./index";
+
+// 등록
+export const saveVcatnProc = data => {
+  return apiClient.post('/vcatn/saveVcatn.json', data);
+}
+
+// 조회 - 현황
+export const findVcatnsSummary = () => {
+  return apiClient.get('/vcatn/findVcatnsSummary.json');
+}
+
+// 조회 - 목록
+export const findMyVcatnsProc = data => {
+  return apiClient.get('/vcatn/findMyVcatns.json', { params: data });
+}
+
+// 조회 - 상세
+export const findVcatnProc = data => {
+  return apiClient.get(`/vcatn/${data}/findVcatn.json`);
+}
+
+// 조회 - 이전 승인자
+export const findLastVcatnProc = () => {
+  return apiClient.get('/vcatn/findLastVcatn.json');
+}
+
+// 삭제
+export const deleteVcatnProc = data => {
+  return apiClient.delete(`/vcatn/${data}/deleteVcatn.json`);
+}
+
+// 수정
+export const updateVcatnProc = data => {
+  return apiClient.post('/vcatn/updateVcatn.json', data);
+}(파일 끝에 줄바꿈 문자 없음)
client/resources/js/cmmnPlugin.js
--- client/resources/js/cmmnPlugin.js
+++ client/resources/js/cmmnPlugin.js
@@ -1,3 +1,6 @@
+import store from '../../views/pages/AppStore'
+import { findCodeListProc } from '../api/cmmnCode'
+
 export default {
   install(Vue) {
     // 빈값체크
@@ -12,5 +15,108 @@
         return false;
       }
     }
+
+    // 게시물 권한 체크
+    Vue.config.globalProperties.$registerChk = (register) => {
+      // 사용자 권한 확인
+      const roles = store.state.roles;
+      if (roles != null && roles.length > 0) {
+        for (let role of roles) {
+          if (role.authority === 'ROLE_ADMIN') {
+            return true; // 관리자인 경우 true 반환
+          }
+        }
+      }
+
+      // 관리자가 아닌 경우 작성자 확인
+      const userId = store.state.userId;
+      if (register === userId) {
+        return true; // 작성자인 경우 true 반환
+      } else {
+        return false; // 작성자가 아닌 경우 false 반환
+      }
+    }
+
+    // 공통코드 조회
+    Vue.config.globalProperties.$findCodeList = async function (serachRequest) {
+      try {
+        const response = await findCodeListProc(serachRequest);
+        const result = response.data.data;
+        return result.codeList;
+      } catch (error) {
+        if (error.response) {
+          alert(error.response.data.message);
+        } else {
+          alert("에러가 발생했습니다.");
+        }
+        console.error(error.message);
+        throw error;
+      }
+    }
+
+    // 날짜 형식 변경
+    Vue.config.globalProperties.$formattedDate = (day, hour, min) => {
+      let beginHour = hour ? hour.toString().padStart(2, '0') : '00';
+      let beginMnt = min ? min.toString().padStart(2, '0') : '00';
+
+      return day.split(' ')[0] + " " + beginHour + ":" + beginMnt
+    }
+
+    // 공통코드 조회 (부모 코드로 자식 코드 조회)
+    Vue.config.globalProperties.$findChildCodes = async (upperCode) => {
+      try {
+        const searchRequest = {
+          searchType: 'upperCd',
+          searchText: upperCode,
+        };
+
+        const codes = await Vue.config.globalProperties.$findCodeList(searchRequest);
+        return Array.isArray(codes) ? codes : [];
+      } catch (error) {
+        console.error(`코드 조회 실패 (${upperCode}):`, error);
+        return [];
+      }
+    };
+
+    // 기본 코드 목록 초기화
+    Vue.config.globalProperties.$defaultCodes = async () => {
+      const codeGroups = {
+        vcatnKndCodeList: [], // 휴가 종류 (연차/반차)
+        bsrpCodeList: [],     // 출장 종류 (해외/국내)
+        sanctnCodeList: [],   // 결재 구분 (결재/대결/전결)
+        confmCodeList: [],    // 상태 코드 (대기/결재대기/승인/반려)
+        clsfCodeList: [],     // 직급 코드 (사원/주임/대리)
+        rspofcCodeList: [],   // 직책 코드 (사원/주임/대리)
+      };
+
+      // 휴가 종류 - depth 2 조회
+      const vcatnKndCodes = await Vue.config.globalProperties.$findChildCodes('sanctn_mby_vcatn');
+      for (const code of vcatnKndCodes) {
+        const childCodes = await Vue.config.globalProperties.$findChildCodes(code.code);
+        codeGroups.vcatnKndCodeList.push(...childCodes);
+      }
+
+      // 출장 종류 - depth 1 조회
+      const bsrpCodes = await Vue.config.globalProperties.$findChildCodes('sanctn_mby_bsrp');
+      codeGroups.bsrpCodeList.push(...bsrpCodes);
+
+      // 결재 구분 - depth 1 조회
+      const sanctnCodes = await Vue.config.globalProperties.$findChildCodes('sanctn_code');
+      codeGroups.sanctnCodeList.push(...sanctnCodes);
+
+      // 상태 코드 - depth 1 조회
+      const confmCodes = await Vue.config.globalProperties.$findChildCodes('confm_code');
+      codeGroups.confmCodeList.push(...confmCodes);
+
+      // 직급 코드 - depth 1 조회
+      const clsfCodes = await Vue.config.globalProperties.$findChildCodes('clsf_code');
+      codeGroups.clsfCodeList.push(...clsfCodes);
+
+      // 직책 코드 - depth 1 조회
+      const rspofcCodes = await Vue.config.globalProperties.$findChildCodes('rspofc_code');
+      codeGroups.rspofcCodeList.push(...rspofcCodes);
+
+      return codeGroups;
+    };
   },
 }
(파일 끝에 줄바꿈 문자 없음)
client/views/component/Popup/HrPopup.vue
--- client/views/component/Popup/HrPopup.vue
+++ client/views/component/Popup/HrPopup.vue
@@ -1,121 +1,157 @@
 <template>
-    <div  class="popup-overlay" @click.self="$emit('close')">
-                <div class="popup-content">
-                  <div class="card">
-                    <div class="card-body">
-                      <h2 class="card-title">직원 목록</h2>
-                      <div class="sch-form-wrap">
-                        <div class="input-group">
-                          <div class="sch-input">
-              <input type="text" class="form-control" placeholder="직원명">
-              <button class="ico-sch"><SearchOutlined /></button>
-            </div>
-                        </div>
-                      </div>
-
-                      <!-- Table  -->
-                      <div class="tbl-wrap">
-                        <table id="myTable" class="tbl data">
-                          <!-- 동적으로 <th> 생성 -->
-                          <thead>
-                            <tr>
-                              <th>직책 </th>
-                              <th>직무</th>
-                              <th>부서</th>
-                              <th>이름</th>
-                              <th>아이디</th>
-                              <th>선택</th>
-                            </tr>
-                          </thead>
-                          <!-- 동적으로 <td> 생성 -->
-                          <tbody>
-                            <tr v-for="(item, index) in popuplistData" :key="index">
-                                <td>{{ item.position }}</td>
-      <td>{{ item.role }}</td>
-      <td>{{ item.department }}</td>
-      <td>{{ item.name }}</td>
-      <td>{{ item.id }}</td>
-      <td>
-        <button
-          type="button"
-          class="btn sm secondary"
-          @click="selectPerson(item)"
-        >
-          선택
-        </button>
-      </td>
-                            </tr>
-                          </tbody>
-                        </table>
-
-                      </div>
-                      <div class="pagination">
-                        <ul>
-                          <!-- 왼쪽 화살표 (이전 페이지) -->
-                          <li class="arrow" :class="{ disabled: currentPage === 1 }"
-                            @click="changePage(currentPage - 1)">
-                            &lt;
-                          </li>
-
-                          <!-- 페이지 번호 -->
-                          <li v-for="page in totalPages" :key="page" :class="{ active: currentPage === page }"
-                            @click="changePage(page)">
-                            {{ page }}
-                          </li>
-
-                          <!-- 오른쪽 화살표 (다음 페이지) -->
-                          <li class="arrow" :class="{ disabled: currentPage === totalPages }"
-                            @click="changePage(currentPage + 1)">
-                            &gt;
-                          </li>
-                        </ul>
-                      </div>
-                      <!-- End Table -->
-                    </div>
-                  </div>
-                  <button @click="$emit('close')" class="close-btn">
-                    <CloseCircleFilled />
-                  </button>
-                </div>
+  <div class="popup-overlay" @click.self="$emit('close')">
+    <div class="popup-content">
+      <div class="card">
+        <div class="card-body">
+          <h2 class="card-title">직원 목록</h2>
+          <div class="sch-form-wrap">
+            <div class="input-group">
+              <div class="sch-input">
+                <input type="text" class="form-control" placeholder="직원명" v-model="request.searchText" @keyup.enter="findDatas">
+                <button type="button" class="ico-sch" @click="findDatas">
+                  <SearchOutlined />
+                </button>
               </div>
+            </div>
+          </div>
+          <div class="tbl-wrap">
+            <table id="myTable" class="tbl data">
+              <thead>
+                <tr>
+                  <th>직급</th>
+                  <th>직책</th>
+                  <th>부서</th>
+                  <th>이름</th>
+                  <th>선택</th>
+                </tr>
+              </thead>
+              <tbody>
+                <tr v-for="(item, idx) in users" :key="idx">
+                  <td>{{ getCodeName(item.clsf, 'clsfCodeList') }}</td>
+                  <td>{{ getCodeName(item.rspofc, 'rspofcCodeList') }}</td>
+                  <td>{{ item.userDeptInfo.deptVO.deptNm }}</td>
+                  <td>{{ item.userNm }}</td>
+                  <td>
+                    <button type="button" class="btn sm secondary" @click="selectPerson(item)" :disabled="isUserSelected(item.userId)">선택</button>
+                  </td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+          <Pagenation :search="request" @onChange="fnChangeCurrentPage" />
+        </div>
+      </div>
+      <button @click="$emit('close')" class="close-btn">
+        <CloseCircleFilled />
+      </button>
+    </div>
+  </div>
 </template>
 <script>
 import { SearchOutlined, CloseCircleFilled } from '@ant-design/icons-vue';
+import Pagenation from '../Pagenation.vue';
+// API
+import { findUsersProc } from '../../../resources/api/user';
 
 export default {
-    data() {
-        return {
-            popuplistData: [
-            {
-          position: '과장',
-          role: '인사관리',
-          department: '총무부',
-          name: '김철수',
-          id: 'admin',
-        },
-        {
-          position: '대리',
-          role: '회계',
-          department: '재무부',
-          name: '이영희',
-          id: 'admin1',
-        },
-      ],
-        }
-    },
-    components: {
-    SearchOutlined, CloseCircleFilled
+  components: {
+    SearchOutlined, CloseCircleFilled,
+    Pagenation
   },
+
+  props: {
+    sanctns: {
+      type: Array,
+      default: () => [],
+    }
+  },
+
+  data() {
+    return {
+      users: [],
+
+      request: {
+        searchType: 'nm', // 검색조건 사용자 이름 고정
+        searchText: null, // 검색어
+        searchSttus: 1, // 회원상태 승인 고정
+        searchUseAt: 'Y', // 사용여부 사용 고정
+      },
+
+      cmmnCodes: {},
+    }
+  },
+
+  computed: {
+    selectedUserIds() {
+      return new Set(this.sanctns.map(sanctn => sanctn.confmerId));
+    }
+  },
+
+  async created() {
+    this.cmmnCodes = await this.$defaultCodes();
+  },
+
+  mounted() {
+    this.findDatas(); // 목록 조회
+  },
+
   methods: {
+    // 목록 조회
+    async findDatas() {
+      try {
+        const response = await findUsersProc(this.request);
+        const result = response.data.data;
+
+        this.users = result.users;
+        this.request = result.search;
+      } catch (error) {
+        if (error.response) {
+          alert(error.response.data.message);
+        } else {
+          alert("에러가 발생했습니다.");
+        }
+        console.error(error.message);
+      }
+    },
+
+    // 공통코드명 조회
+    getCodeName(code, codeListName) {
+      if (!code || !this.cmmnCodes[codeListName]) return '';
+      const codeItem = this.cmmnCodes[codeListName].find(item => item.code === code);
+      return codeItem ? codeItem.codeNm : null;
+    },
+
+    // 사용자 검증
+    isUserSelected(userId) {
+      return this.selectedUserIds.has(userId);
+    },
+
+    // 승인자 선택
     selectPerson(item) {
-    this.$emit('select', item); // 부모에게 데이터 전달
-  },
-    
+      const data = {
+        confmerId: item.userId,
+        clsf: item.clsf,
+        sanctnOrdr: this.sanctns.length + 1,
+        sanctnSe: this.cmmnCodes.sanctnCodeList[0].code,
+
+        clsfNm: this.getCodeName(item.clsf, 'clsfCodeList'),
+        userNm: item.userNm,
+      };
+      this.$emit('onSelected', data);
+    },
+
+    // 페이지 이동
+    fnChangeCurrentPage(currentPage) {
+      this.request.currentPage = Number(currentPage);
+      this.$nextTick(() => {
+        this.findDatas();
+      });
+    },
   }
 }
 </script>
 <style scoped>
 .popup-content {
-    width: 50%;
+  width: 50%;
 }
 </style>
(파일 끝에 줄바꿈 문자 없음)
 
client/views/component/Sanctn/SanctnList.vue (added)
+++ client/views/component/Sanctn/SanctnList.vue
@@ -0,0 +1,193 @@
+<template>
+  <div>
+    <div v-for="(sanctns, idx) of sanctns" :key="sanctns.sanctnId || idx" class="draggable-item-wrapper">
+      <div class="drop-zone" @dragover.prevent="handleDragEnter($event, idx)" @dragenter.prevent="handleDragEnter($event, idx)" @dragleave="handleDragLeave($event, idx)" @drop.prevent="handleDrop($event, idx)" :class="{
+        'drop-active': dropTarget === idx,
+        'drop-visible': draggedIndex !== null && shouldShowDropZone(idx),
+        'drop-hidden': draggedIndex !== null && !shouldShowDropZone(idx)
+      }">
+        <div class="drop-indicator">여기에 놓기</div>
+      </div>
+      <div class="d-flex addapproval draggable-item" draggable="true" @dragstart="handleDragStart(idx, $event)" @dragend="handleDragEnd" :class="{ 'being-dragged': draggedIndex === idx }">
+        <select class="form-select" v-model="sanctns.sanctnSe" style="width: 110px;" @mousedown.stop>
+          <option v-for="(item, idx) of cmmnCodes.sanctnCodeList" :key="idx" :value="item.code"> {{ item.codeNm }} </option>
+        </select>
+        <div class="d-flex align-items-center border-x">
+          <p>{{ sanctns.userNm }} {{ formatClsf(sanctns.clsfNm) }} ({{ sanctns.sanctnOrdr }})</p>
+          <button type="button" @click="$emit('delSanctn', idx)" @mousedown.stop>
+            <CloseOutlined />
+          </button>
+        </div>
+      </div>
+    </div>
+    <div class="drop-zone" @dragover.prevent="handleDragEnter($event, sanctns.length)" @dragenter.prevent="handleDragEnter($event, sanctns.length)" @dragleave="handleDragLeave($event, sanctns.length)" @drop.prevent="handleDrop($event, sanctns.length)" :class="{
+      'drop-active': dropTarget === sanctns.length,
+      'drop-visible': draggedIndex !== null && shouldShowLastDropZone(),
+      'drop-hidden': draggedIndex !== null && !shouldShowLastDropZone()
+    }">
+      <div class="drop-indicator">여기에 놓기</div>
+    </div>
+  </div>
+</template>
+<script>
+import { CloseOutlined } from '@ant-design/icons-vue';
+
+export default {
+  name: 'SanctnList',
+
+  components: { CloseOutlined },
+
+  props: {
+    sanctns: {
+      type: Array,
+      default: () => [],
+    }
+  },
+
+  data() {
+    return {
+      cmmnCodes: {}, // 결재 코드 목록
+      draggedIndex: null,
+      dropTarget: null,
+    }
+  },
+
+  async created() {
+    this.cmmnCodes = await this.$defaultCodes(); // 코드 목록 초기화
+  },
+
+  methods: {
+    formatClsf(code) {
+      const clsfCode = this.cmmnCodes?.clsfCodeList?.find(item => item.code === code);
+      return clsfCode?.codeNm || code;
+    },
+
+    shouldShowDropZone(index) {
+      if (this.draggedIndex === null) return true;
+      if (index === this.draggedIndex || index === this.draggedIndex + 1) return false;
+      return true;
+    },
+
+    shouldShowLastDropZone() {
+      if (this.draggedIndex === null) return true;
+      return this.draggedIndex !== this.sanctns.length - 1;
+    },
+
+    handleDragStart(index, event) {
+      this.draggedIndex = index;
+      event.dataTransfer.effectAllowed = 'move';
+      event.dataTransfer.setData('text/plain', index.toString());
+    },
+
+    handleDragEnter(event, dropIndex) {
+      if (this.draggedIndex !== null) {
+        this.dropTarget = dropIndex;
+      }
+    },
+
+    handleDragLeave(event, dropIndex) {
+      const rect = event.currentTarget.getBoundingClientRect();
+      const x = event.clientX;
+      const y = event.clientY;
+
+      if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom) {
+        if (this.dropTarget === dropIndex) {
+          this.dropTarget = null;
+        }
+      }
+    },
+
+    handleDrop(event, dropIndex) {
+      if (this.draggedIndex !== null && this.draggedIndex !== dropIndex) {
+        let finalDropIndex = dropIndex;
+
+        if (this.draggedIndex < dropIndex) {
+          finalDropIndex = dropIndex - 1;
+        }
+
+        const newSanctns = [...this.sanctns];
+        const draggedItem = newSanctns.splice(this.draggedIndex, 1)[0];
+        newSanctns.splice(finalDropIndex, 0, draggedItem);
+
+        newSanctns.forEach((item, index) => {
+          item.sanctnOrdr = index + 1;
+        });
+
+        this.$emit('update:sanctns', newSanctns);
+      }
+
+      this.dropTarget = null;
+    },
+
+    handleDragEnd() {
+      this.draggedIndex = null;
+      this.dropTarget = null;
+    }
+  }
+};
+</script>
+<style scoped>
+.draggable-item-wrapper {
+  position: relative;
+}
+
+.draggable-item {
+  transition: all 0.3s ease;
+  border: 2px solid transparent;
+  background: white;
+  border-radius: 8px;
+  padding: 8px;
+}
+
+.being-dragged {
+  opacity: 0.5;
+  box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
+}
+
+.drop-zone {
+  height: 0;
+  margin: 0;
+  padding: 0;
+  border: 2px dashed transparent;
+  border-radius: 8px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  transition: all 0.2s ease;
+  opacity: 0;
+  overflow: hidden;
+  line-height: 0;
+  font-size: 0;
+}
+
+.drop-zone.drop-visible {
+  height: 40px;
+  margin: 8px 0;
+  opacity: 1;
+  border-color: #ddd;
+  font-size: 14px;
+  line-height: normal;
+}
+
+.drop-zone.drop-hidden {
+  height: 0;
+  margin: 0;
+  padding: 0;
+  opacity: 0;
+  pointer-events: none;
+  line-height: 0;
+  font-size: 0;
+}
+
+.drop-zone.drop-active {
+  border-color: #007bff !important;
+  background-color: #e3f2fd;
+}
+
+.drop-indicator {
+  color: #007bff;
+  font-weight: bold;
+  font-size: inherit;
+  pointer-events: none;
+}
+</style>(파일 끝에 줄바꿈 문자 없음)
 
client/views/component/editor/EditorComponent.vue (added)
+++ client/views/component/editor/EditorComponent.vue
@@ -0,0 +1,160 @@
+<template>
+  <ckeditor v-if="editor && config" :model-value="editorContent" :editor="editor" :config="config" @ready="onReady" @update:model-value="updateContents" />
+</template>
+<script>
+/**
+ * This configuration was generated using the CKEditor 5 Builder. You can modify it anytime using this link:
+ * https://ckeditor.com/ckeditor-5/builder/#installation/NoNgNARATAdAzPCkCMAGKIAcmCsrdwghzICcUqqyyALESDnKXFKW6djTdkhANYB7JKjDBkYEZIlhkAXUggAZmxwBjVRFlA==
+ */
+import { Ckeditor } from '@ckeditor/ckeditor5-vue';
+import {
+  ClassicEditor,
+  Alignment,
+  AutoLink,
+  BlockQuote,
+  Bold,
+  Essentials,
+  FontBackgroundColor,
+  FontColor,
+  FontFamily,
+  FontSize,
+  HorizontalLine,
+  Indent,
+  IndentBlock,
+  Italic,
+  Link,
+  Paragraph,
+  RemoveFormat,
+  Strikethrough,
+  Subscript,
+  Superscript,
+  Table,
+  TableCaption,
+  TableCellProperties,
+  TableColumnResize,
+  TableProperties,
+  TableToolbar,
+  TextPartLanguage,
+  Underline,
+} from 'ckeditor5';
+import translations from 'ckeditor5/translations/ko.js';
+import 'ckeditor5/ckeditor5.css';
+const LICENSE_KEY = 'GPL';
+export default {
+  name: 'EditorComponent',
+  components: {
+    Ckeditor
+  },
+  props: {
+    contents: {
+      type: String,
+      default: ''
+    }
+  },
+  emits: ['update:contents'],
+  data() {
+    return {
+      isLayoutReady: false,
+      editor: ClassicEditor,
+      editorInstance: null,
+    };
+  },
+  computed: {
+    editorContent() {
+      return this.contents || '';
+    },
+    config() {
+      if (!this.isLayoutReady) {
+        return null;
+      }
+      return {
+        toolbar: {
+          items: [
+            'fontSize',
+            'fontFamily',
+            'fontColor',
+            'fontBackgroundColor',
+            '|',
+            'bold',
+            'italic',
+            'underline',
+            'strikethrough',
+            'subscript',
+            'superscript',
+            'removeFormat',
+            '|',
+            'horizontalLine',
+            'link',
+            'insertTable',
+            'blockQuote',
+            '|',
+            'alignment',
+            '|',
+            'outdent',
+            'indent'
+          ],
+          shouldNotGroupWhenFull: true
+        },
+        plugins: [
+          Alignment,
+          AutoLink,
+          BlockQuote,
+          Bold,
+          Essentials,
+          FontBackgroundColor,
+          FontColor,
+          FontFamily,
+          FontSize,
+          HorizontalLine,
+          Indent,
+          IndentBlock,
+          Italic,
+          Link,
+          Paragraph,
+          RemoveFormat,
+          Strikethrough,
+          Subscript,
+          Superscript,
+          Table,
+          TableCaption,
+          TableCellProperties,
+          TableColumnResize,
+          TableProperties,
+          TableToolbar,
+          TextPartLanguage,
+          Underline,
+        ],
+        fontFamily: {
+          supportAllValues: true
+        },
+        fontSize: {
+          options: [10, 12, 14, 'default', 18, 20, 22],
+          supportAllValues: true
+        },
+        language: 'ko',
+        licenseKey: LICENSE_KEY,
+        link: {
+          addTargetToExternalLinks: true,
+          defaultProtocol: 'https://',
+        },
+        placeholder: '내용을 입력하세요.',
+        table: {
+          contentToolbar: ['tableColumn', 'tableRow', 'mergeTableCells', 'tableProperties', 'tableCellProperties']
+        },
+        translations: [translations],
+      };
+    }
+  },
+  mounted() {
+    this.isLayoutReady = true;
+  },
+  methods: {
+    onReady(editor) {
+      this.editorInstance = editor;
+    },
+    updateContents(data) {
+      this.$emit('update:contents', data);
+    }
+  }
+};
+</script>(파일 끝에 줄바꿈 문자 없음)
 
client/views/component/editor/ViewerComponent.vue (added)
+++ client/views/component/editor/ViewerComponent.vue
@@ -0,0 +1,15 @@
+<template>
+  <div class="ck-content" v-html="content"></div>
+</template>
+<script>
+import 'ckeditor5/ckeditor5.css';
+export default {
+  name: 'ViewerComponent',
+  props: {
+    content: {
+      type: String,
+      default: ''
+    }
+  }
+};
+</script>(파일 끝에 줄바꿈 문자 없음)
client/views/layout/Menu.vue
--- client/views/layout/Menu.vue
+++ client/views/layout/Menu.vue
@@ -35,10 +35,10 @@
         <router-link to="/system-management.page" class="nav-link " active-class="active">
           <span>시스템관리</span></router-link>
       </li>
-    
+
 
     </ul><!-- End Profile Dropdown Items -->
-   
+
 </template>
 
 <script>
@@ -48,7 +48,7 @@
     return {
       isLoggedIn: false,
       userName: '',
-      logo: "/client/resources/img/logo.png", 
+      logo: "/client/resources/img/logo.png",
     };
   },
   methods: {
client/views/pages/Manager/approval/approvalList.vue
--- client/views/pages/Manager/approval/approvalList.vue
+++ client/views/pages/Manager/approval/approvalList.vue
@@ -9,16 +9,16 @@
             <select name="" id="" class="form-select">
               <option :value="currentYear">{{ currentYear }}년</option>
               <option value="all">전체</option>
-              <option v-for="year in remainingYears" :key="year" :value="year" v-if="year !== currentYear">
-                {{ year }}년
-              </option>
+              <template v-for="year in remainingYears" :key="year">
+                <option :value="year" v-if="year !== currentYear"> {{ year }}년</option>
+              </template>
             </select>
             <select name="" id="" class="form-select">
               <option :value="currentMonth">{{ currentMonth }}월</option>
               <option value="all">전체</option>
-              <option v-for="month in remainingMonths" :key="month" :value="month" v-if="month !== currentMonth">
-                {{ month }}월
-              </option>
+              <template v-for="month in remainingMonths" :key="month">
+                <option :value="month" v-if="month !== currentMonth"> {{ month }}월</option>
+              </template>
             </select>
             <div class="sch-input">
               <input type="text" class="form-control" placeholder="신청자명">
@@ -28,68 +28,52 @@
             </div>
           </div>
         </div>
-
-        <!-- Table  -->
         <div class="tbl-wrap">
           <table id="myTable" class="tbl data">
-            <!-- 동적으로 <th> 생성 -->
+            <colgroup>
+              <col style="width: 13%;">
+              <col style="width: 13%;">
+              <col style="width: 13%;">
+              <col style="width: 48%;">
+              <col style="width: 13%;">
+            </colgroup>
             <thead>
               <tr>
-                <th>구분 </th>
+                <th>구분</th>
                 <th>결재구분</th>
                 <th>신청자</th>
                 <th>기간</th>
                 <th>신청일</th>
               </tr>
             </thead>
-            <!-- 동적으로 <td> 생성 -->
             <tbody>
-              <tr v-for="(item, index) in listData" :key="index" :class="{ 'expired': isPastPeriod(item.period) }" @click="goToPage(item)">
-                <td>{{ item.type }}</td>
-                <td>{{ item.approvalType }}</td>
-                <td>{{ item.applicant }}</td>
-                <td>{{ item.period }}</td>
-                <td>{{ item.requestDate }}</td>
+              <tr v-for="(item, idx) in waitListData" :key="idx" :class="{ 'expired': true }">
+                <td>{{ getCodeName('type', item.iemInfo.vcatnKnd) }}</td>
+                <td>{{ getCodeName('cate', item.sanctnSe) }}</td>
+                <td>{{ item.registerInfo.userNm }}</td>
+                <td>{{ $formattedDate(item.iemInfo.bgnde, item.iemInfo.beginHour, item.iemInfo.beginMnt) }} ~ {{ $formattedDate(item.iemInfo.bgnde, item.iemInfo.beginHour, item.iemInfo.beginMnt) }}</td>
+                <td>{{ item.registerInfo.userNm }}</td>
               </tr>
             </tbody>
           </table>
-
         </div>
-        <div class="pagination">
-          <ul>
-            <!-- 왼쪽 화살표 (이전 페이지) -->
-            <li class="arrow" :class="{ disabled: currentPage === 1 }" @click="changePage(currentPage - 1)">
-              &lt;
-            </li>
-
-            <!-- 페이지 번호 -->
-            <li v-for="page in totalPages" :key="page" :class="{ active: currentPage === page }"
-              @click="changePage(page)">
-              {{ page }}
-            </li>
-
-            <!-- 오른쪽 화살표 (다음 페이지) -->
-            <li class="arrow" :class="{ disabled: currentPage === totalPages }" @click="changePage(currentPage + 1)">
-              &gt;
-            </li>
-          </ul>
-        </div>
+        <Pagenation :search="waitRequest" @onChange="(currentPage) => fnChangeCurrentPage(currentPage, 'wait')" />
         <div class="sch-form-wrap title-wrap">
           <h3><img :src="h3icon" alt="">승인 이력</h3>
           <div class="input-group">
             <select name="" id="" class="form-select">
               <option :value="currentYear">{{ currentYear }}년</option>
               <option value="all">전체</option>
-              <option v-for="year in remainingYears" :key="year" :value="year" v-if="year !== currentYear">
-                {{ year }}년
-              </option>
+              <template v-for="year in remainingYears" :key="year">
+                <option :value="year" v-if="year !== currentYear"> {{ year }}년 </option>
+              </template>
             </select>
             <select name="" id="" class="form-select">
               <option :value="currentMonth">{{ currentMonth }}월</option>
               <option value="all">전체</option>
-              <option v-for="month in remainingMonths" :key="month" :value="month" v-if="month !== currentMonth">
-                {{ month }}월
-              </option>
+              <template v-for="month in remainingMonths" :key="month">
+                <option :value="month" v-if="month !== currentMonth"> {{ month }}월 </option>
+              </template>
             </select>
             <select name="" id="" class="form-select">
               <option value="all">상태</option>
@@ -104,14 +88,19 @@
             </div>
           </div>
         </div>
-
-        <!-- Table  -->
         <div class="tbl-wrap">
           <table id="myTable" class="tbl data">
-            <!-- 동적으로 <th> 생성 -->
+            <colgroup>
+              <col style="width: 13%;">
+              <col style="width: 13%;">
+              <col style="width: 13%;">
+              <col style="width: 35%;">
+              <col style="width: 13%;">
+              <col style="width: 13%;">
+            </colgroup>
             <thead>
               <tr>
-                <th>구분 </th>
+                <th>구분</th>
                 <th>결재구분</th>
                 <th>신청자</th>
                 <th>기간</th>
@@ -119,53 +108,57 @@
                 <th>상태</th>
               </tr>
             </thead>
-            <!-- 동적으로 <td> 생성 -->
             <tbody>
-              <tr v-for="(item, index) in filteredList" :key="index" :class="{ 'expired': isPastPeriod(item.period) }">
-                <td>{{ item.type }}</td>
-                <td>{{ item.approvalType }}</td>
-                <td>{{ item.applicant }}</td>
-                <td>{{ item.period }}</td>
-                <td>{{ item.requestDate }}</td>
-                <td :class="getStatusClass(item.status)">{{ item.status }}</td>
+              <tr v-for="(item, index) in confmListData" :key="index" :class="{ 'expired': true }">
+                <td>{{ getCodeName('type', item.iemInfo.vcatnKnd) }}</td>
+                <td>{{ getCodeName('cate', item.sanctnSe) }}</td>
+                <td>{{ item.registerInfo.userNm }}</td>
+                <td>{{ $formattedDate(item.iemInfo.bgnde, item.iemInfo.beginHour, item.iemInfo.beginMnt) }} ~ {{ $formattedDate(item.iemInfo.bgnde, item.iemInfo.beginHour, item.iemInfo.beginMnt) }}</td>
+                <td>{{ item.registerInfo.userNm }}</td>
+                <td :class="getStatusClass(item.status)">{{ getCodeName('confm', item.confmAt) }}</td>
               </tr>
             </tbody>
           </table>
-
         </div>
-        <div class="pagination">
-          <ul>
-            <!-- 왼쪽 화살표 (이전 페이지) -->
-            <li class="arrow" :class="{ disabled: currentPage === 1 }" @click="changePage(currentPage - 1)">
-              &lt;
-            </li>
-
-            <!-- 페이지 번호 -->
-            <li v-for="page in totalPages" :key="page" :class="{ active: currentPage === page }"
-              @click="changePage(page)">
-              {{ page }}
-            </li>
-
-            <!-- 오른쪽 화살표 (다음 페이지) -->
-            <li class="arrow" :class="{ disabled: currentPage === totalPages }" @click="changePage(currentPage + 1)">
-              &gt;
-            </li>
-          </ul>
-        </div>
+        <Pagenation :search="confmRequest" @onChange="(currentPage) => fnChangeCurrentPage(currentPage, 'wait')" />
       </div>
     </div>
   </div>
 </template>
-
 <script>
-import { ref } from 'vue';
 import { SearchOutlined } from '@ant-design/icons-vue';
+import Pagenation from '../../../component/Pagenation.vue';
+// API
+import { findSanctnsProc } from '../../../../resources/api/sanctns';
+
 const currentYear = new Date().getFullYear();
 const currentMonth = new Date().getMonth() + 1;
+
 export default {
+  components: {
+    SearchOutlined,
+    Pagenation,
+  },
+
   data() {
     return {
-      selectedStatus: 'all',
+      photoicon: "/client/resources/img/photo_icon.png",
+      h3icon: "/client/resources/img/h3icon.png",
+
+      // 목록 조회용
+      waitListData: [],
+      waitRequest: {
+        sanctnDe: null, // 결재일
+        sanctnIem: null, // 결재항목 : 출장 구분 or 휴가 종류 (휴가/출장 구분 코드)
+        sanctnMbyId: null, // 결재 주체 아이디 : 휴가/출장 아이디
+      },
+      confmListData: [],
+      confmRequest: {
+        sanctnDe: null, // 결재일
+        sanctnIem: null, // 결재항목 : 출장 구분 or 휴가 종류 (휴가/출장 구분 코드)
+        sanctnMbyId: null, // 결재 주체 아이디 : 휴가/출장 아이디
+      },
+
       currentMonth,
       selectedMonth: currentMonth,
       remainingMonths: Array.from({ length: 12 }, (_, i) => i + 1),
@@ -174,82 +167,157 @@
       remainingYears: Array.from({ length: 10 }, (_, i) => currentYear - i),
       currentPage: 1,
       totalPages: 3,
-      photoicon: "/client/resources/img/photo_icon.png",
-      h3icon: "/client/resources/img/h3icon.png",
       // 데이터 초기화
       years: [2023, 2024, 2025], // 연도 목록
       months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // 월 목록
       selectedYear: '',
       selectedMonth: '',
-      listData: [
-        {
-          type: '연차',
-          approvalType: '결재',
-          applicant: '홍길동',
-          period: '2025-05-10 ~ 2025-15-03',
-          requestDate: '2025-04-25',
-        }, {
-          type: '출장-복명',
-          approvalType: '전결',
-          applicant: '홍길동',
-          period: '2025-05-01 ~ 2025-05-03',
-          requestDate: '2025-04-25',
-        }, {
-          type: '출장-품의',
-          approvalType: '전결',
-          applicant: '홍길동',
-          period: '2025-05-01 ~ 2025-05-03',
-          requestDate: '2025-04-25',
-        }
-      ],
-      listData2: [
-        {
-          type: '연차',
-          approvalType: '결재',
-          applicant: '홍길동',
-          period: '2025-05-10 ~ 2025-15-03',
-          requestDate: '2025-04-25',
-          status: '대기'
-        }, {
-          type: '반차',
-          approvalType: '전결',
-          applicant: '홍길동',
-          period: '2025-05-01 ~ 2025-05-03',
-          requestDate: '2025-04-25',
-          status: '승인'
-        }],
-      filteredData: [],
+
+      // 공통코드 목록
+      sanctnCodes: [], // 결재 종류 코드 목록
+      sanctnMbyCodes: [], // 결재 항목 코드 목록
+      sanctnConfmCodes: [], // 결재 상태 코드 목록
     };
   },
-  computed: {
-    filteredList() {
-      if (this.selectedStatus === 'all') {
-        return this.listData2;
-      }
-      return this.listData2.filter((item) => item.status === this.selectedStatus);
-    },
+
+  computed: {},
+
+  created() {
+    this.initializeSanctnCodes();
+
+    console.log("sanctnCodes: ", this.sanctnCodes);
+    console.log("sanctnMbyCodes: ", this.sanctnMbyCodes);
   },
-  components: {
-    SearchOutlined
+
+  mounted() {
+    this.findList('wait'); // 목록 조회
+    this.findList('confm'); // 목록 조회
   },
+
   methods: {
-    goToPage(item) {
-    const type = item.type;
-    if (type === '연차' || type === '반차') {
-      this.$router.push({name: 'Hyuga'});
-    } else if (type === '출장-품의') {
-      this.$router.push({name: 'ChuljangPumui'});
-    } else if (type === '출장-복명') {
-      this.$router.push({name: 'ChuljangBokmyeong'});
-    } else {
-      alert('이동할 수 없는 항목입니다.');
-    }
-  },
-    changePage(page) {
-      if (page < 1 || page > this.totalPages) return;
-      this.currentPage = page;
-      this.$emit('page-changed', page); // 필요 시 부모에 알림
+    // 결재 관련 코드 초기화
+    async initializeSanctnCodes() {
+      try {
+        this.sanctnCodes = [];
+        this.sanctnMbyCodes = [];
+
+        // 순차적으로 코드 조회 및 추가
+        const vcatnCodes = await this.findCodeByDepth2('sanctn_mby_vcatn'); // 휴가 종류
+        this.sanctnCodes.push(...vcatnCodes);
+
+        const bsrpCodes = await this.findCodeByDepth1('sanctn_mby_bsrp'); // 출장 종류
+        this.sanctnCodes.push(...bsrpCodes);
+
+        const sanctnCodes = await this.findCodeByDepth1('sanctn_code'); // 결재 구분
+        this.sanctnMbyCodes.push(...sanctnCodes);
+
+        const confmCodes = await this.findCodeByDepth1('confm_code'); // 상태 코드
+        this.sanctnConfmCodes.push(...confmCodes);
+      } catch (error) {
+        console.error('코드 조회 중 오류 발생:', error);
+      }
     },
+
+    // 코드 목록 조회 (depth 1)
+    async findCodeByDepth1(upperCode) {
+      try {
+        const searchRequest = {
+          searchType: 'upperCd',
+          searchText: upperCode,
+        };
+
+        const codes = await this.$findCodeList(searchRequest);
+        return Array.isArray(codes) ? codes : [];
+
+      } catch (error) {
+        console.error(`코드 조회 실패 (${upperCode}):`, error);
+        return [];
+      }
+    },
+
+    // 코드 목록 조회 (depth 2)
+    async findCodeByDepth2(upperCode) {
+      try {
+        const parentCodes = await this.findCodeByDepth1(upperCode);
+        const childCodes = [];
+
+        for (const code of parentCodes) {
+          const result = await this.findCodeByDepth1(code.code);
+          childCodes.push(...result);
+        }
+
+        return childCodes;
+
+      } catch (error) {
+        console.error(`하위 코드 조회 실패 (${upperCode}):`, error);
+        return [];
+      }
+    },
+
+    // 코드 매핑
+    getCodeName(type, code) {
+      let codeList;
+
+      if (type === 'type') {
+        codeList = this.sanctnCodes;
+      } else if (type === 'cate') {
+        codeList = this.sanctnMbyCodes;
+      } else if (type === 'confm') {
+        const foundCode = this.sanctnConfmCodes?.find(item => item.codeValue === code);
+        return foundCode ? foundCode.codeNm : code;
+      } else {
+        return code; // 알 수 없는 타입이면 원본 반환
+      }
+
+      const foundCode = codeList?.find(item => item.code === code);
+      return foundCode ? foundCode.codeNm : code;
+    },
+
+    // 목록 조회
+    async findList(type) {
+      const vm = this;
+
+      try {
+        let request = {};
+        if (type === 'wait') {
+          request = vm.waitRequest;
+        } else if (type == 'confm') {
+          request = vm.confmRequest;
+        }
+
+        const response = await findSanctnsProc(request);
+        const result = response.data.data;
+
+        if (type === 'wait') {
+          vm.waitListData = result.lists;
+          vm.waitRequest = result.search;
+        } else if (type == 'confm') {
+          vm.confmListData = result.lists;
+          vm.confmRequest = result.search;
+        }
+      } catch (error) {
+        if (error.response) {
+          alert(error.response.data.message);
+        } else {
+          alert("에러가 발생했습니다.");
+        }
+        console.error(error.message);
+      };
+    },
+
+    // 페이지 이동
+    fnChangeCurrentPage(currentPage, type) {
+      if (type === 'wait') {
+        this.waitRequest.currentPage = Number(currentPage);
+      } else if (type === 'confm') {
+        this.confmRequest.currentPage = Number(currentPage);
+      }
+
+      this.$nextTick(() => {
+        this.findList(type);
+      });
+    },
+
     async onClickSubmit() {
       // `useMutation` 훅을 사용하여 mutation 함수 가져오기
       const { mutate, onDone, onError } = useMutation(mygql);
@@ -277,27 +345,11 @@
       if (status === '대기') return 'status-pending';
       return '';
     },
-    isPastPeriod(period) {
-      // 예: '2025-05-01 ~ 2025-05-03' → 종료일 추출
-      const endDateStr = period.split('~')[1]?.trim();
-      if (!endDateStr) return false;
-
-      const endDate = new Date(endDateStr);
-      const today = new Date();
-
-      // 현재 날짜보다 과거면 true
-      return endDate < today;
-    }
-  },
-  created() {
-  },
-  mounted() {
-
-
   },
 };
 </script>
-
 <style scoped>
-tr{cursor: pointer;}
+tr {
+  cursor: pointer;
+}
 </style>
(파일 끝에 줄바꿈 문자 없음)
client/views/pages/Manager/attendance/HyugaDetail.vue
--- client/views/pages/Manager/attendance/HyugaDetail.vue
+++ client/views/pages/Manager/attendance/HyugaDetail.vue
@@ -1,153 +1,266 @@
 <template>
-<div class="card ">
-      <div class="card-body">
-          <h2 class="card-title">휴가 현황</h2>
-       
+  <div class="card">
+    <div class="card-body">
+      <h2 class="card-title">휴가 현황</h2>
       <div class="form-card">
         <h1>휴가신청서</h1>
-        <div class="approval-box tbl-wrap tbl2">
-         <table class="tbl data">
-          <tbody>
-            <tr class="thead">
-              <td rowspan="2" class="th">승인자</td>
-              <td>과장</td>
-              <td>소장</td>
-            </tr>
-            <tr>
-              <td><p class="name">홍길동</p><p class="date">2025/05/09</p></td>
-              <td><p class="name">홍길동</p><p class="date">2025/05/09</p></td>
-            </tr>
-          </tbody>
-
-         </table>
+        <div v-if="detailData.sanctnList && detailData.sanctnList.length > 0" class="approval-box tbl-wrap tbl2">
+          <table class="tbl data">
+            <tbody>
+              <tr class="thead">
+                <td rowspan="2" class="th">승인자</td>
+                <template v-for="(item, idx) of detailData.sanctnList" :key="idx">
+                  <td>{{ fnFindNm(item.clsf) }}</td>
+                </template>
+              </tr>
+              <tr>
+                <td v-for="(item, idx) of detailData.sanctnList" :key="idx">
+                  <template v-if="item.confmAt === 'A'">
+                    <p v-if="item.sanctnSe === 'sanctn_agncy'">(대결)</p>
+                    <p class="name">{{ item.confmerInfo?.userNm || '' }}</p>
+                    <small class="date">{{ new Date(item.sanctnDe).toISOString().split('T')[0] }}</small>
+                  </template>
+                  <template v-else-if="item.confmAt === 'R'">
+                    <p class="name">반려</p>
+                  </template>
+                </td>
+              </tr>
+            </tbody>
+          </table>
         </div>
-          <form class="row g-3 needs-validation detail"  :class="{ 'was-validated': formSubmitted }" @submit.prevent="handleRegister" novalidate>
-                <div class="col-12 ">
-                  <div class="col-12 border-x">
-                    <label for="youremail" class="form-label ">유형<p class="require"><img :src="require" alt=""></p></label>
-                    <input  v-model="email" type="text" name="username" class="form-control"  readonly >
-                  </div>
-    
-                  <div class="col-12 border-x">
-                    <label for="yourPassword" class="form-label">신청자</label>
-                    <input  v-model="password" type="password" name="password" class="form-control"  readonly   placeholder="주식회사 테이큰 소프트"> 
-                  </div>
-               </div>
-               <div class="col-12">
-                  <div class="col-12 border-x">
-                    <label for="youremail" class="form-label">부서</label>
-                    <input  v-model="email" type="text" name="username" class="form-control"  readonly  placeholder="과장">
-                  </div>
-    
-                  <div class="col-12 border-x">
-                    <label for="yourPassword" class="form-label">직급</label>
-                    <input  v-model="password" type="password" name="password" class="form-control"  readonly  placeholder="팀장"> 
-                  </div>
-               </div>
-               <div class="col-12">
-                  <label for="yourName" class="form-label">기간</label>
-                  <input  v-model="name" type="text" name="name" class="form-control"  readonly>
+        <form class="row g-3 needs-validation detail" novalidate>
+          <div class="col-12">
+            <div class="col-12 border-x">
+              <label for="vcatnType" class="form-label">유형</label>
+              <p>{{ formattedVcatnKnd }}</p>
             </div>
-            <div class="col-12 hyuga">
-                  <label for="yourName" class="form-label">세부사항</label>
-                  <input v-model="name" type="text" name="name" class="form-control textarea"  readonly>
+            <div class="col-12 border-x">
+              <label for="applicant" class="form-label">신청자</label>
+              <p>{{ detailData.registerInfo?.userNm || '' }}</p>
             </div>
-            <div class="col-12 ">
-                  <label for="yourName" class="form-label">신청일</label>
-                  <input v-model="name" type="text" name="name" class="form-control "  readonly>
+          </div>
+          <div class="col-12">
+            <div class="col-12 border-x">
+              <label for="department" class="form-label">부서</label>
+              <p>{{ detailData.registerInfo?.userDeptInfo?.deptVO?.deptNm || '' }}</p>
             </div>
-            <div class="col-12 border-x return" >
-                  <label for="yourName" class="form-label ">반려사유</label>
-                  <input  v-model="name" type="text" name="name" class="form-control"  readonly   >
+            <div class="col-12 border-x">
+              <label for="position" class="form-label">직급</label>
+              <p>{{ formattedClsf }}</p>
             </div>
-           
-                
-              </form>
+          </div>
+          <div class="col-12">
+            <label for="period" class="form-label">기간</label>
+            <p>{{ formattedPeriod }}</p>
+          </div>
+          <div class="col-12 hyuga">
+            <label for="details" class="form-label">세부사항</label>
+            <ViewerComponent :content="detailData.detailCn || ''" />
+          </div>
+          <div class="col-12">
+            <label for="requestDate" class="form-label">신청일</label>
+            <p>{{ detailData.rgsde || '' }}</p>
+          </div>
+          <div class="col-12">
+            <label for="status" class="form-label">상태</label>
+            <p>{{ formattedStatus }}</p>
+          </div>
+          <div v-if="detailData.confmAt === 'R'" class="col-12 border-x return">
+            <label for="rejectReason" class="form-label">반려사유</label>
+            <template v-for="(item, idx) of detailData.sanctnList" :key="idx">
+              <p v-if="item.confmAt === 'R'">{{ item.returnResn || '' }}</p>
+            </template>
+          </div>
+        </form>
       </div>
-            <div class="buttons">
-              <button class="btn  btn-red"  type="button">신청취소</button>
-                <button class="btn secondary"  type="button">재신청</button>
-                <button class="btn secondary"  type="button">수정</button>
-                <button class="btn tertiary "  type="button">목록</button>
-              </div>
-              <ReturnPopup v-if="showPopup" @close="showPopup = false"/>
+      <div class="buttons">
+        <button v-if="detailData.confmAt === 'W' || detailData.confmAt === 'R'" class="btn btn-red" type="button" @click="deleteData">신청취소</button>
+        <button v-if="detailData.confmAt === 'W'" class="btn secondary" type="button" @click="fnMoveTo('edit', pageId)">수정</button>
+        <button v-if="detailData.confmAt === 'R'" class="btn secondary" type="button" @click="reSave">재신청</button>
+        <button class="btn tertiary" type="button" @click="fnMoveTo('list')">목록</button>
       </div>
+      <ReturnPopup v-if="showPopup" @close="showPopup = false" />
     </div>
+  </div>
 </template>
-
 <script>
 import ReturnPopup from '../../../component/Popup/ReturnPopup.vue';
+import ViewerComponent from '../../../component/editor/ViewerComponent.vue';
+// API
+import { findVcatnProc, deleteVcatnProc } from '../../../../resources/api/vcatn';
+
 export default {
+  components: { ReturnPopup, ViewerComponent },
+
   data() {
-    const today = new Date().toISOString().split('T')[0];
     return {
+      require: "/client/resources/img/require.png",
+
+      pageId: null,
+      isRegister: false,
       showPopup: false,
-      startDate: today,
-      startTime: "09:00", // 기본 시작 시간 09:00
-      endDate: today,
-      endTime: "18:00", // 기본 종료 시간 18:00
-      category: "", 
-      dayCount: 1, 
-      reason: "", // 사유
-      listData: [
-  {
-    type: '연차',
-    approvalType: '결재',
-    applicant: '홍길동',
-    period: '2025-05-10 ~ 2025-15-03',
-    requestDate: '2025-04-25',
-    status: '대기'
-  },   {
-    type: '반차',
-    approvalType: '전결',
-    applicant: '홍길동',
-    period: '2025-05-01 ~ 2025-05-03',
-    requestDate: '2025-04-25',
-    status: '승인'
-  }],
+
+      // 휴가 신청서 정보
+      detailData: {
+        registerInfo: {},
+        sanctnList: [],
+        confmAt: '',
+        vcatnKnd: '',
+        clsf: '',
+        bgnde: '',
+        endde: '',
+        detailCn: '',
+        rgsde: ''
+      },
+
+      cmmnCodes: {
+        vcatnKndCodeList: [],
+        clsfCodeList: [],
+        confmCodeList: []
+      }
     };
   },
-  components: {
-    ReturnPopup
-  },
+
   computed: {
-  
+    formattedVcatnKnd() {
+      if (!this.cmmnCodes.vcatnKndCodeList || this.cmmnCodes.vcatnKndCodeList.length === 0) return this.detailData.vcatnKnd;
+      const vcatnKnd = this.cmmnCodes.vcatnKndCodeList.find(v => v.code === this.detailData.vcatnKnd);
+      return vcatnKnd ? vcatnKnd.codeNm : this.detailData.vcatnKnd;
+    },
+
+    formattedClsf() {
+      if (!this.cmmnCodes.clsfCodeList || this.cmmnCodes.clsfCodeList.length === 0) return this.detailData.clsf;
+      const clsf = this.cmmnCodes.clsfCodeList.find(v => v.code === this.detailData.clsf);
+      return clsf ? clsf.codeNm : this.detailData.clsf;
+    },
+
+    formattedStatus() {
+      if (!this.cmmnCodes.confmCodeList || this.cmmnCodes.confmCodeList.length === 0) return this.detailData.confmAt;
+      const confmAt = this.cmmnCodes.confmCodeList.find(v => v.codeValue === this.detailData.confmAt);
+      return confmAt ? confmAt.codeNm : this.detailData.confmAt;
+    },
+
+    formattedPeriod() {
+      if (!this.detailData.bgnde || !this.detailData.endde) return '';
+
+      const formattedBgnde = this.detailData.bgnde.split(' ')[0];
+      const formattedBeginHour = this.detailData.beginHour?.toString().padStart(2, '0') || '00';
+      const formattedBeginMnt = this.detailData.beginMnt?.toString().padStart(2, '0') || '00';
+      const startDay = `${formattedBgnde} ${formattedBeginHour}:${formattedBeginMnt}`;
+
+      const formattedEndde = this.detailData.endde.split(' ')[0];
+      const formattedEndHour = this.detailData.endHour?.toString().padStart(2, '0') || '00';
+      const formattedEndMnt = this.detailData.endMnt?.toString().padStart(2, '0') || '00';
+      const endDay = `${formattedEndde} ${formattedEndHour}:${formattedEndMnt}`;
+
+      const dayCount = this.calculateDayCount();
+      return `${startDay} ~ ${endDay} (${dayCount}일)`;
+    },
   },
-  methods: {
-   
-    calculateDayCount() {
-    const start = new Date(`${this.startDate}T${this.startTime}:00`);
-    const end = new Date(`${this.endDate}T${this.endTime}:00`);
-    
-    let totalHours = (end - start) / (1000 * 60 * 60);  // 밀리초를 시간 단위로 변환
-    
-    if (this.startDate !== this.endDate) {
-      // 시작일과 종료일이 다른경우
-      const startDateObj = new Date(this.startDate);
-      const endDateObj = new Date(this.endDate);
-      const daysDifference = (endDateObj - startDateObj) / (1000 * 60 * 60 * 24); // 두 날짜 사이의 차이를 일수로 계산
-      if (this.startTime !== "09:00" || this.endTime !== "18:00") {
-        this.dayCount = daysDifference + 0.5; // 시간 조건이 기준에서 벗어날 경우
-      } else {
-        this.dayCount = Math.ceil(daysDifference + 1); // 시간 조건이 기준에 맞을 경우
-      }
-    } else {
-      // 시작일과 종료일이 같은 경우
-      if (this.startTime !== "09:00" || this.endTime !== "18:00") {
-        this.dayCount = 0.5; // 시작 시간 또는 종료 시간이 기준과 다를 경우 0.5
-      } else {
-        this.dayCount = 1; // 기준 시간(09:00~18:00)이 맞으면 1일로 간주
-      }
+
+  async created() {
+    this.pageId = this.$route.query.id;
+    if (this.$isEmpty(this.pageId)) {
+      alert("게시물이 존재하지 않습니다.");
+      this.fnMoveTo('list');
     }
 
-    this.validateForm(); // dayCount 변경 후 폼 재검증
+    // 코드 목록 초기화
+    this.cmmnCodes = await this.$defaultCodes();
   },
-    
-    
-    
-  },
+
   mounted() {
+    this.findData(); // 상세 조회
   },
-  
+
+  methods: {
+    // 상세 조회
+    async findData() {
+      try {
+        const response = await findVcatnProc(this.pageId);
+        const result = response.data.data;
+
+        this.detailData = result.vo;
+        this.isRegister = this.$registerChk(result.vo.register);
+      } catch (error) {
+        if (error.response) {
+          alert(error.response.data.message);
+        } else {
+          alert("에러가 발생했습니다.");
+        }
+        console.error(error.message);
+        this.fnMoveTo('list');
+      }
+    },
+
+    fnFindNm(code) {
+      if (this.$isEmpty(code)) {
+        return code;
+      }
+
+      const clsf = this.cmmnCodes.clsfCodeList.find(v => v.code === code);
+      return clsf ? clsf.codeNm : code;
+    },
+
+    // 일수 계산
+    calculateDayCount() {
+      if (!this.detailData.bgnde || !this.detailData.endde) return 0;
+
+      let dayCnt = 1; // 기본값
+
+      // 반차인 경우
+      if (this.detailData.vcatnKnd === 'MORNING_HALF' || this.detailData.vcatnKnd === 'AFTERNOON_HALF') {
+        dayCnt = 0.5;
+      }
+
+      const startDate = new Date(this.detailData.bgnde);
+      const endDate = new Date(this.detailData.endde);
+      const timeDiff = endDate.getTime() - startDate.getTime();
+      const dayDiff = Math.floor(timeDiff / (1000 * 60 * 60 * 24)) + 1;
+
+      return dayCnt * Math.max(0, dayDiff);
+    },
+
+    // 삭제
+    async deleteData() {
+      try {
+        const response = await deleteVcatnProc(this.pageId);
+
+        this.fnMoveTo('list');
+      } catch (error) {
+        if (error.response) {
+          alert(error.response.data.message);
+        } else {
+          alert("에러가 발생했습니다.");
+        }
+        console.error(error.message);
+        this.fnMoveTo('list');
+      }
+    },
+
+    // 재신청
+    reSave() {
+      this.$router.push({ name: 'hyugaInsert', query: { id: this.pageId } });
+    },
+
+    // 페이지 이동
+    fnMoveTo(type, id) {
+      const routes = {
+        'list': { name: 'hyugaStatue' },
+        'view': { name: 'HyugaDetail', query: { id } },
+        'edit': { name: 'hyugaInsert', query: this.$isEmpty(id) ? {} : { id } },
+      };
+
+      if (routes[type]) {
+        if (!this.$isEmpty(this.pageId) && type === 'list') {
+          this.$router.push({ name: 'HyugaDetail', query: { id: this.pageId } });
+        }
+        this.$router.push(routes[type]);
+      } else {
+        alert("올바르지 않은 경로를 요청하여 목록으로 이동합니다.");
+        this.$router.push(routes['list']);
+      }
+    },
+  }
 };
-</script>
+</script>
(파일 끝에 줄바꿈 문자 없음)
client/views/pages/Manager/attendance/HyugaInsert.vue
--- client/views/pages/Manager/attendance/HyugaInsert.vue
+++ client/views/pages/Manager/attendance/HyugaInsert.vue
@@ -1,227 +1,466 @@
 <template>
   <div class="card">
-
     <div class="card-body">
       <h2 class="card-title">휴가 신청</h2>
       <p class="require"><img :src="require" alt=""> 필수입력</p>
-      <!-- Multi Columns Form -->
-      <form class="row g-3  needs-validation" @submit.prevent="handleSubmit">
+      <div class="row g-3 needs-validation">
         <div class="col-12">
           <label for="inputName5" class="form-label">
-            <p>유형
-            <p class="require"><img :src="require" alt=""></p>
-            </p>
+            <p>유형<span class="require"><img :src="require" alt=""></span></p>
           </label>
-          <select id="category" class="form-select" style="max-width: 200px;">
-            <option value="연차">연차</option>
-            <option value="반차">반차</option>
-            <option value="병가">병가</option>
-            <option value="경조">경조</option>
-            <option value="무급">무급</option>
-            <option value="공가">공가</option>
+          <select class="form-select" style="max-width: 200px;" v-model="editData.vcatnKnd" @change="fnOnchangeVcatnKnd">
+            <option value="" disabled hidden>유형 선택</option>
+            <option v-for="(item, idx) of cmmnCodes.vcatnKndCodeList" :key="idx" :value="item.code">{{ item.codeNm }}</option>
           </select>
+          <template v-if="showSubTypeSelect">
+            <select class="form-select mt-2" style="max-width: 200px;" v-model="editData.vcatnSubKnd" @change="fnOnchangeVcatnSubKnd">
+              <option value="" disabled hidden>세부 유형 선택</option>
+              <option v-for="(item, idx) of subTypes" :key="idx" :value="item.code">{{ item.codeNm }}</option>
+            </select>
+          </template>
         </div>
-
         <div class="col-12">
-          <label for="startDate" class="form-label">
-            <p>시작일
-            <p class="require"><img :src="require" alt=""></p>
-            </p>
+          <label for="bgnde" class="form-label">
+            <p>시작일<span class="require"><img :src="require" alt=""></span></p>
           </label>
           <div class="d-flex gap-1">
-            <input type="date" class="form-control" id="startDate" v-model="startDate" />
-            <!-- 시간 선택을 위한 select 사용 -->
-            <select class="form-control" id="startTime" v-model="startTime">
-              <option value="09:00">09:00</option>
-              <option value="10:00">10:00</option>
-              <option value="11:00">11:00</option>
-              <option value="12:00">12:00</option>
-              <option value="13:00">13:00</option>
-              <option value="14:00">14:00</option>
-              <option value="15:00">15:00</option>
-              <option value="16:00">16:00</option>
-              <option value="17:00">17:00</option>
-              <option value="18:00">18:00</option>
-            </select>
+            <input type="date" class="form-control" id="bgnde" v-model="editData.bgnde" @keydown="preventKeyboard" />
+            <input type="text" class="form-control" placeholder="시" v-model="editData.beginHour" readonly />
+            <input type="text" class="form-control" placeholder="분" v-model="editData.beginMnt" readonly />
           </div>
         </div>
-
         <div class="col-12">
-          <label for="endDate" class="form-label">
-            <p>종료일
-            <p class="require"><img :src="require" alt=""></p>
-            </p>
+          <label for="endde" class="form-label">
+            <p>종료일<span class="require"><img :src="require" alt=""></span></p>
           </label>
           <div class="d-flex gap-1">
-            <input type="date" class="form-control" id="endDate" v-model="endDate" />
-            <!-- 종료 시간을 위한 select 사용 -->
-            <select class="form-control" id="endTime" v-model="endTime">
-              <option value="09:00">09:00</option>
-              <option value="10:00">10:00</option>
-              <option value="11:00">11:00</option>
-              <option value="12:00">12:00</option>
-              <option value="13:00">13:00</option>
-              <option value="14:00">14:00</option>
-              <option value="15:00">15:00</option>
-              <option value="16:00">16:00</option>
-              <option value="17:00">17:00</option>
-              <option value="18:00">18:00</option>
-            </select>
+            <input type="date" class="form-control" id="endde" v-model="editData.endde" :readonly="dayCnt === 0.5" @keydown="preventKeyboard" />
+            <input type="text" class="form-control" placeholder="시" v-model="editData.endHour" readonly />
+            <input type="text" class="form-control" placeholder="분" v-model="editData.endMnt" readonly />
           </div>
         </div>
-
         <div class="col-12">
-          <label for="dayCount" class="form-label">사용 휴가일</label>
-          <input type="text" class="form-control" id="dayCount" v-model="dayCount" readonly />
+          <label for="totalDays" class="form-label">사용 휴가일</label>
+          <input type="text" class="form-control" id="totalDays" v-model="totalDays" readonly />
         </div>
-
         <div class="col-12">
           <label for="member" class="form-label">
-            승인자
-            <button type="button" title="추가" @click="showPopup = true">
+            <span>승인자<span class="require"><img :src="require" alt=""></span></span>
+            <button type="button" title="추가" @click="isOpenModal = true">
               <PlusCircleFilled />
             </button>
           </label>
-          <HrPopup v-if="showPopup" @close="showPopup = false" @select="addApproval" />
-          <!-- 반복 렌더링되는 addapproval 항목 -->
+          <HrPopup v-if="isOpenModal" :sanctns="editData.sanctnList" @onSelected="fnAddSanctn" @close="isOpenModal = false" />
           <div class="approval-container">
-            <div v-for="(approval, index) in approvals" :key="index" class="d-flex gap-2 addapproval mb-2">
-              <select class="form-select" v-model="approval.category" style="width: 110px;">
-                <option value="결재">결재</option>
-                <option value="전결">전결</option>
-                <option value="대결">대결</option>
-              </select>
-
-              <form class="d-flex align-items-center border-x">
-                <input type="text" class="form-control" v-model="approval.name" style="max-width: 150px;" />
-                <button type="button" @click="removeApproval(index)" class="delete-button">
-                  <CloseOutlined />
-                </button>
-              </form>
-            </div>
+            <SanctnList v-model:sanctns="editData.sanctnList" @delSanctn="fnDelSanctn" />
           </div>
         </div>
         <div class="col-12 border-x hyuga">
           <label for="prvonsh" class="form-label">세부사항</label>
-          <input type="text" class="form-control textarea" id="reason" v-model="reason" />
+          <div>
+            <EditorComponent v-model:contents="editData.detailCn" />
+          </div>
         </div>
-
-
-      </form><!-- End Multi Columns Form -->
+      </div>
       <div class="buttons">
-        <button type="submit" class="btn btn-red">이전 승인자 불러오기</button>
-        <button type="submit" class="btn primary">신청</button>
-        <button type="reset" class="btn tertiary">취소</button>
+        <button type="button" class="btn btn-red" @click="fnRecord">이전 승인자 불러오기</button>
+        <button type="button" class="btn primary" @click="fnSave">신청</button>
+        <button type="reset" class="btn tertiary" @click="fnMoveTo('list')">취소</button>
       </div>
     </div>
   </div>
 </template>
-
 <script>
 import { PlusCircleFilled, CloseOutlined } from '@ant-design/icons-vue';
 import HrPopup from '../../../component/Popup/HrPopup.vue';
+import SanctnList from '../../../component/Sanctn/SanctnList.vue';
+import EditorComponent from '../../../component/editor/EditorComponent.vue';
+// API
+import { findVcatnProc, saveVcatnProc, findLastVcatnProc, updateVcatnProc } from '../../../../resources/api/vcatn';
+
 export default {
+  components: {
+    PlusCircleFilled,
+    CloseOutlined,
+    HrPopup,
+    SanctnList,
+    EditorComponent,
+  },
+
   data() {
-    const today = new Date().toISOString().split('T')[0];
     return {
-      showPopup: false,
-      approvals: [],
       require: "/client/resources/img/require.png",
-      startDate: today,
-      startTime: "09:00", // 기본 시작 시간 09:00
-      endDate: today,
-      endTime: "18:00", // 기본 종료 시간 18:00
-      category: "",
-      dayCount: 1,
-      reason: "", // 사유
+      pageId: null,
+      isOpenModal: false,
+      editData: {
+        vcatnId: null,
+        userId: null,
+        vcatnKnd: '',
+        vcatnSubKnd: '', // 세부 유형 추가
+        deptId: null,
+        clsf: null,
+        bgnde: null,
+        beginHour: null,
+        beginMnt: null,
+        endde: null,
+        endHour: null,
+        endMnt: null,
+        detailCn: null,
+        confmAt: null,
+        sanctnList: [],
+      },
+      dayCnt: 0,
+      totalDays: 0,
+      cmmnCodes: {},
+      workConfig: {
+        startHour: 9,    // 근무 시작 시간
+        endHour: 18,     // 근무 종료 시간
+        lunchStart: 12,  // 점심 시작 시간
+        lunchEnd: 13,    // 점심 종료 시간
+      },
+      subTypes: [],
     };
   },
-  components: {
-    PlusCircleFilled, CloseOutlined, HrPopup
-  },
+
   computed: {
-    // Pinia Store의 상태를 가져옵니다.
-    loginUser() {
-      const authStore = useAuthStore();
-      return authStore.getLoginUser;
-    },
+    // 세부 유형 선택박스를 보여줄지 여부
+    showSubTypeSelect() {
+      return this.dayCnt === 0.5 && this.subTypes.length > 0;
+    }
   },
-  methods: {
-    addApproval(selectedUser) {
-      this.approvals.push({
-        category: '결재',
-        name: selectedUser.name, // or other fields if needed
-      });
-      this.showPopup = false; // 팝업 닫기
-    },
-    // 승인자 삭제
-    removeApproval(index) {
-      this.approvals.splice(index, 1);
-    },
-    validateForm() {
-      // 필수 입력 필드 체크
-      if (
-        this.category &&
-        this.startDate &&
-        this.startTime &&
-        this.endDate &&
-        this.endTime &&
-        this.dayCount > 0 &&
-        this.reason.trim() !== ""
-      ) {
-        this.isFormValid = true;
-      } else {
-        this.isFormValid = false;
+
+  async created() {
+    this.pageId = this.$route.query.id;
+    this.cmmnCodes = await this.$defaultCodes();
+    for (const vcatnType of this.cmmnCodes.vcatnKndCodeList) {
+      if (parseFloat(vcatnType.codeValue) === 0.5) {
+        this.subTypes = await this.$findChildCodes(vcatnType.code);
+        break;
       }
-    },
-    calculateDayCount() {
-      const start = new Date(`${this.startDate}T${this.startTime}:00`);
-      const end = new Date(`${this.endDate}T${this.endTime}:00`);
-
-      let totalHours = (end - start) / (1000 * 60 * 60);  // 밀리초를 시간 단위로 변환
-
-      if (this.startDate !== this.endDate) {
-        // 시작일과 종료일이 다른경우
-        const startDateObj = new Date(this.startDate);
-        const endDateObj = new Date(this.endDate);
-        const daysDifference = (endDateObj - startDateObj) / (1000 * 60 * 60 * 24); // 두 날짜 사이의 차이를 일수로 계산
-        if (this.startTime !== "09:00" || this.endTime !== "18:00") {
-          this.dayCount = daysDifference + 0.5; // 시간 조건이 기준에서 벗어날 경우
-        } else {
-          this.dayCount = Math.ceil(daysDifference + 1); // 시간 조건이 기준에 맞을 경우
-        }
-      } else {
-        // 시작일과 종료일이 같은 경우
-        if (this.startTime !== "09:00" || this.endTime !== "18:00") {
-          this.dayCount = 0.5; // 시작 시간 또는 종료 시간이 기준과 다를 경우 0.5
-        } else {
-          this.dayCount = 1; // 기준 시간(09:00~18:00)이 맞으면 1일로 간주
-        }
-      }
-
-      this.validateForm(); // dayCount 변경 후 폼 재검증
-    },
-    handleSubmit() {
-      this.validateForm(); // 제출 시 유효성 확인
-      if (this.isFormValid) {
-        localStorage.setItem('HyugaFormData', JSON.stringify(this.$data));
-        alert("승인 요청이 완료되었습니다.");
-        // 추가 처리 로직 (API 요청 등)
-      } else {
-        alert("모든 필드를 올바르게 작성해주세요.");
-      }
-    },
-
+    }
   },
+
   mounted() {
+    if (!this.$isEmpty(this.pageId)) {
+      this.findData();
+    }
   },
+
   watch: {
-    startDate: 'calculateDayCount',
-    startTime: 'calculateDayCount',
-    endDate: 'calculateDayCount',
-    endTime: 'calculateDayCount',
-    reason: "validateForm",
-    category: 'category',
+    'editData.bgnde'(newVal, oldVal) {
+      if (newVal !== oldVal) {
+        this.changeBgnde();
+      }
+    },
+    'editData.endde'(newVal, oldVal) {
+      if (newVal !== oldVal) {
+        this.validateAndCalculateDays(); // calculateTotalDays 대신 validateAndCalculateDays 호출
+      }
+    },
+  },
+
+  methods: {
+    async findData() {
+      try {
+        const response = await findVcatnProc(this.pageId);
+        const result = response.data.data;
+
+        this.editData = result.vo;
+        this.editData.bgnde = this.editData.bgnde.split(' ')[0];
+        this.editData.endde = this.editData.endde.split(' ')[0];
+
+        let sanctns = [];
+        for (let sanctn of this.editData.sanctnList) {
+          console.log("sanctn: ", sanctn);
+          let data = {
+            confmerId: sanctn.confmerId,
+            clsf: sanctn.clsf,
+            sanctnOrdr: sanctn.sanctnOrdr,
+            sanctnSe: sanctn.sanctnSe,
+            clsfNm: sanctn.confmerInfo?.clsf,
+            userNm: sanctn.confmerInfo?.userNm
+          }
+          sanctns.push(data);
+        }
+        this.editData.sanctnList = sanctns;
+
+        // cmmnCodes가 초기화되었는지 확인 후 호출
+        if (this.cmmnCodes && this.cmmnCodes.vcatnKndCodeList) {
+          await this.fnOnchangeVcatnKnd();
+        } else {
+          console.warn('cmmnCodes가 아직 초기화되지 않았습니다.');
+        }
+      } catch (error) {
+        console.error('데이터 조회 실패:', error);
+        const message = error.response?.data?.message || "데이터를 불러오는데 실패했습니다.";
+        alert(message);
+        this.fnMoveTo('list');
+      }
+    },
+
+    // 유형 변경
+    async fnOnchangeVcatnKnd() {
+      // 안전성 검사 추가
+      if (!this.cmmnCodes || !this.cmmnCodes.vcatnKndCodeList || !Array.isArray(this.cmmnCodes.vcatnKndCodeList)) {
+        console.warn('cmmnCodes.vcatnKndCodeList가 초기화되지 않았거나 배열이 아닙니다.');
+        return;
+      }
+
+      const selectedVcatn = this.cmmnCodes.vcatnKndCodeList.find(item => item.code === this.editData.vcatnKnd);
+      if (!selectedVcatn) {
+        console.warn('선택된 휴가 유형을 찾을 수 없습니다.');
+        return;
+      }
+
+      this.dayCnt = parseFloat(selectedVcatn.codeValue);
+
+      if (this.dayCnt === 0.5) {
+        this.changeBgnde(); // 반차일 경우 종료일을 시작일과 동일하게 설정
+      } else {
+        // 반차가 아닌 경우
+        this.editData.vcatnSubKnd = ''; // 세부 유형 선택 초기화
+
+        // 전체 근무시간으로 설정
+        this.editData.beginHour = this.workConfig.startHour.toString().padStart(2, '0');
+        this.editData.beginMnt = '00';
+        this.editData.endHour = this.workConfig.endHour.toString().padStart(2, '0');
+        this.editData.endMnt = '00';
+        this.calculateTotalDays();
+      }
+    },
+
+    // 세부 유형 변경
+    fnOnchangeVcatnSubKnd() {
+      if (this.dayCnt === 0.5) {
+        const selectedSubType = this.subTypes.find(item => item.code === this.editData.vcatnSubKnd);
+        if (selectedSubType) {
+          this.setHalfDayTime(selectedSubType);
+        }
+      }
+      this.calculateTotalDays();
+    },
+
+    // 반차 시간 설정 메서드
+    setHalfDayTime(selectedSubType) {
+      const codeValue = selectedSubType.codeValue;
+
+      // 전체 실제 근무시간 계산 (점심시간 제외)
+      const totalHours = this.workConfig.endHour - this.workConfig.startHour; // 전체 시간
+      const lunchHours = this.workConfig.lunchEnd - this.workConfig.lunchStart; // 점심시간
+      const actualWorkHours = totalHours - lunchHours; // 실제 근무시간
+      const halfWorkHours = actualWorkHours / 2; // 반차 시간
+
+      this.editData.beginMnt = '00';
+      this.editData.endMnt = '00';
+
+      if (codeValue === 'AM') {
+        this.editData.beginHour = this.workConfig.startHour.toString().padStart(2, '0');
+        this.editData.endHour = this.calculateTimeWithLunch(this.workConfig.startHour, halfWorkHours, 1).toString().padStart(2, '0');
+      } else if (codeValue === 'PM') {
+        this.editData.beginHour = this.calculateTimeWithLunch(this.workConfig.endHour, halfWorkHours, -1).toString().padStart(2, '0');
+        this.editData.endHour = this.workConfig.endHour.toString().padStart(2, '0');
+      }
+    },
+
+    // 점심시간을 고려하여 시간 계산 (방향: 1=앞으로, -1=뒤로)
+    calculateTimeWithLunch(startTime, workHours, direction = 1) {
+      let currentHour = startTime;
+      let remainingHours = workHours;
+
+      while (remainingHours > 0) {
+        if (direction === 1) {
+          // 앞으로 계산 (종료시간 구하기)
+          if (currentHour >= this.workConfig.lunchStart && currentHour < this.workConfig.lunchEnd) {
+            currentHour++;
+          } else {
+            currentHour++;
+            remainingHours--;
+          }
+        } else {
+          // 뒤로 계산 (시작시간 구하기)
+          currentHour--;
+          if (!(currentHour >= this.workConfig.lunchStart && currentHour < this.workConfig.lunchEnd)) {
+            remainingHours--;
+          }
+        }
+      }
+
+      return currentHour;
+    },
+
+    // 시작일과 종료일 통일
+    changeBgnde() {
+      if (!this.editData.bgnde) {
+        return;
+      }
+
+      // 반차인 경우 종료일을 시작일과 동일하게 설정
+      if (this.dayCnt === 0.5) {
+        this.editData.endde = this.editData.bgnde;
+      }
+
+      this.validateAndCalculateDays(); // 사용 휴가일 계산
+    },
+
+    // 사용 휴가일 계산
+    validateAndCalculateDays() {
+      if (!this.editData.bgnde || !this.editData.endde) {
+        this.totalDays = 0;
+        return;
+      }
+
+      const startDate = new Date(this.editData.bgnde);
+      const endDate = new Date(this.editData.endde);
+
+      // 날짜 유효성 검사
+      if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
+        console.warn('유효하지 않은 날짜입니다.');
+        this.totalDays = 0;
+        return;
+      }
+
+      // 종료일이 시작일보다 이전인지 확인 (반차가 아닌 경우)
+      if (this.dayCnt !== 0.5 && endDate < startDate) {
+        alert('종료일은 시작일보다 이전일 수 없습니다.');
+        this.editData.endde = this.editData.bgnde;
+        return;
+      }
+
+      this.calculateTotalDays();
+    },
+
+    calculateTotalDays() {
+      if (!this.editData.bgnde || !this.editData.endde) {
+        this.totalDays = 0;
+        return;
+      }
+
+      const startDate = new Date(this.editData.bgnde);
+      const endDate = new Date(this.editData.endde);
+
+      if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
+        this.totalDays = 0;
+        return;
+      }
+
+      const timeDiff = endDate.getTime() - startDate.getTime();
+      const dayDiff = Math.floor(timeDiff / (1000 * 60 * 60 * 24)) + 1;
+
+      // 반차의 경우 0.5일, 그 외의 경우 실제 일수 계산
+      if (this.dayCnt === 0.5) {
+        this.totalDays = 0.5;
+      } else {
+        this.totalDays = Math.max(0, dayDiff);
+      }
+    },
+
+    preventKeyboard(event) {
+      if (event.key !== 'Tab') {
+        event.preventDefault();
+      }
+    },
+
+    fnAddSanctn(data) {
+      this.editData.sanctnList.push(data);
+      this.isOpenModal = false;
+    },
+
+    fnDelSanctn(idx) {
+      this.editData.sanctnList.splice(idx, 1);
+      this.editData.sanctnList.forEach((item, index) => {
+        item.sanctnOrdr = index + 1;
+      });
+    },
+
+    async fnRecord() {
+      try {
+        const response = await findLastVcatnProc();
+        const result = response.data.data;
+
+        if (this.$isEmpty(result.lists)) {
+          alert("휴가 기록이 존재하지 않아, 이전 승인자를 불러올 수 없습니다.");
+          return;
+        }
+
+        this.editData.sanctnList = result.lists;
+      } catch (error) {
+        console.error('이전 승인자 조회 실패:', error);
+        const message = error.response?.data?.message || "이전 승인자를 불러오는데 실패했습니다.";
+        alert(message);
+      }
+    },
+
+    validateForm() {
+      if (this.$isEmpty(this.editData.vcatnKnd)) {
+        alert("유형을 선택해 주세요.");
+        return false;
+      }
+
+      if (this.$isEmpty(this.editData.bgnde)) {
+        alert("시작일을 선택해 주세요.");
+        return false;
+      }
+
+      if (this.$isEmpty(this.editData.endde)) {
+        alert("종료일을 선택해 주세요.");
+        return false;
+      }
+
+      if (this.$isEmpty(this.editData.sanctnList)) {
+        alert("승인자를 선택해 주세요.");
+        return false;
+      }
+
+      if (this.dayCnt === 0.5) {
+        if (this.$isEmpty(this.editData.vcatnSubKnd)) {
+          alert("세부 유형을 선택해 주세요.");
+          return false;
+        }
+      }
+
+      return true;
+    },
+
+    async fnSave() {
+      try {
+        if (!this.validateForm()) {
+          return;
+        }
+
+        if (!this.$isEmpty(this.pageId)) {
+          this.editData.confm_at = 'W';
+        }
+
+        const response = this.$isEmpty(this.pageId) ? await saveVcatnProc(this.editData) : await updateVcatnProc(this.editData);
+        const message = this.$isEmpty(this.pageId) ? "등록되었습니다." : "수정되었습니다.";
+        alert(message);
+
+        this.fnMoveTo('view', response.data.data.pageId);
+      } catch (error) {
+        console.error('저장 실패:', error);
+        const message = error.response?.data?.message || "저장에 실패했습니다.";
+        alert(message);
+      }
+    },
+
+    fnMoveTo(type, id) {
+      const routes = {
+        'list': { name: 'hyugaStatue' },
+        'view': { name: 'HyugaDetail', query: { id } },
+        'edit': { name: 'hyugaInsert', query: this.$isEmpty(id) ? {} : { id } },
+      };
+
+      if (routes[type]) {
+        if (!this.$isEmpty(this.pageId) && type === 'list') {
+          this.$router.push({ name: 'HyugaDetail', query: { id: this.pageId } });
+          return;
+        }
+        this.$router.push(routes[type]);
+      } else {
+        alert("올바르지 않은 경로를 요청하여 목록으로 이동합니다.");
+        this.$router.push(routes['list']);
+      }
+    },
   },
 };
-</script>
+</script>
(파일 끝에 줄바꿈 문자 없음)
client/views/pages/Manager/attendance/hyugaStatue.vue
--- client/views/pages/Manager/attendance/hyugaStatue.vue
+++ client/views/pages/Manager/attendance/hyugaStatue.vue
@@ -1,221 +1,347 @@
 <template>
-<div class="col-lg-12">
-  <div class="card">
-    <div class="card-body">
-      <h2 class="card-title">휴가 현황</h2>
-     <!-- 폼그룹 -->
-<div class="form-group">
-    <div class="form-conts">
-        <div class="form-conts datepicker-conts">
-            <div class="datepicker-input">
-              <input
-    type="date"
-    class="form-control datepicker cal"
-    :value="startDate"
-    style="max-width: 200px;"
-  />
-  <mark>~</mark>
-  <input
-    type="date"
-    class="form-control datepicker cal"
-    :value="endDate"
-    style="max-width: 200px;"
-  />
+  <div class="col-lg-12">
+    <div class="card">
+      <div class="card-body">
+        <h2 class="card-title">휴가 현황</h2>
+        <div class="form-group">
+          <div class="form-conts">
+            <div class="form-conts datepicker-conts">
+              <div class="datepicker-input">
+                <input type="date" class="form-control datepicker cal" v-model="request.bgnde" style="max-width: 200px;" @change="changeDate" />
+                <mark>~</mark>
+                <input type="date" class="form-control datepicker cal" v-model="request.endde" style="max-width: 200px;" @change="changeDate" />
+              </div>
             </div>
+          </div>
         </div>
-    </div>
-</div>
-<!-- //폼그룹 -->
-      <div class="boxs">
-            <div class="color-boxs">
-                <div class="box ">
-                    <h3>지각</h3>
-                    3
-                </div>
-                <div class="box blue">
-                    <h3>조기퇴근</h3>
-                    3
-                </div>
-                <div class="box red">
-                    <h3>결근</h3>
-                    3
-                </div>
-                <div class="box green">
-                    <h3>결근</h3>
-                    3
-                </div>
-                <div class="box purple">
-                    <h3>결근</h3>
-                    3
-                </div>
-                <div class="box orange">
-                    <h3>결근</h3>
-                    3
-                </div>
+        <div class="boxs">
+          <div class="color-boxs">
+            <div v-for="(item, idx) of useSummary" :key="idx" :class="getBoxClass(idx)" @click="fnFindUseList(item)">
+              <h3>{{ item.name }}</h3> {{ item.value }}
             </div>
+            <div v-for="(item, idx) of notUseSummary" :key="idx" :class="getBoxClass(useSummary.length + idx)" @click="fnFindNotUseList(item.code)">
+              <h3>{{ item.codeNm }}</h3> {{ item.value }}
+            </div>
+          </div>
         </div>
-      <!-- Table  -->
-      <div class="tbl-wrap">
+        <div class="tbl-wrap">
           <table id="myTable" class="tbl data">
-            <!-- 동적으로 <th> 생성 -->
+            <colgroup>
+              <col style="width: 15%;">
+              <col style="width: 40%;">
+              <col style="width: 30%;">
+              <col style="width: 15%;">
+            </colgroup>
             <thead>
               <tr>
-                <th>구분 </th>
+                <th>구분</th>
                 <th>기간</th>
-                <th>승인자</th>
                 <th>신청일</th>
                 <th>상태</th>
               </tr>
             </thead>
-            <!-- 동적으로 <td> 생성 -->
             <tbody>
-              <tr v-for="(item, index) in listData" :key="index"  @click="goToDetailPage(item)">
-                <td>{{ item.type }}</td>
-                <td>{{ item.period }}</td>
-                <td>{{ item.approval }}</td>
-                <td>{{ item.requestDate  }}</td>
-                <td :class="getStatusClass(item.status)">{{ item.status }}</td>
+              <template v-if="formattedListData.length > 0">
+                <tr v-for="(item, idx) in formattedListData" :key="idx" @click="fnMoveTo('view', item.vcatnId)">
+                  <td :class="getTypeColorClass(item.formattedVcatnKnd)">{{ item.formattedVcatnKnd }}</td>
+                  <td>{{ item.formattedBgnde }} {{ item.formattedBeginHour }}:{{ item.formattedBeginMnt }} ~ {{ item.formattedEndde }} {{ item.formattedEndHour }}:{{ item.formattedEndMnt }}</td>
+                  <td>{{ item.rgsde }}</td>
+                  <td :class="getStatusClass(item.formattedStatus)">{{ item.formattedStatus }}</td>
+                </tr>
+              </template>
+              <tr v-else>
+                <td colspan="4">해당 기간 내 등록된 휴가가 없습니다.</td>
               </tr>
             </tbody>
           </table>
-
         </div>
-      <div class="pagination">
-        <ul>
-      <!-- 왼쪽 화살표 (이전 페이지) -->
-      <li 
-        class="arrow" 
-        :class="{ disabled: currentPage === 1 }" 
-        @click="changePage(currentPage - 1)"
-      >
-      &lt;
-      </li>
-
-      <!-- 페이지 번호 -->
-      <li 
-        v-for="page in totalPages" 
-        :key="page" 
-        :class="{ active: currentPage === page }" 
-        @click="changePage(page)"
-      >
-        {{ page }}
-      </li>
-
-      <!-- 오른쪽 화살표 (다음 페이지) -->
-      <li 
-        class="arrow" 
-        :class="{ disabled: currentPage === totalPages }" 
-        @click="changePage(currentPage + 1)"
-      >
-      &gt;
-      </li>
-    </ul>
-  </div>
-      
+        <Pagenation :search="request" @onChange="fnChangeCurrentPage" />
+      </div>
     </div>
   </div>
-</div>
 </template>
-
 <script>
-import { ref } from 'vue';
 import { SearchOutlined } from '@ant-design/icons-vue';
+import Pagenation from '../../../component/Pagenation.vue';
+// API
+import { findVcatnsSummary, findMyVcatnsProc } from '../../../../resources/api/vcatn';
+
 export default {
+  components: {
+    SearchOutlined,
+    Pagenation
+  },
+
   data() {
-    const today = new Date();
-    const formattedToday = today.toISOString().split('T')[0]; // 'YYYY-MM-DD'
     return {
-      startDate: '2025-01-01',
-      endDate: formattedToday,
-      showOptions: false,
-      currentPage: 1,
-      totalPages: 3,
       photoicon: "/client/resources/img/photo_icon.png",
-      // 데이터 초기화
-      years: [2023, 2024, 2025], // 연도 목록
-      months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // 월 목록
-      selectedYear: '',
-      selectedMonth: '',
-      listData: [
-        {
-          type: '연차',
-          period: '2025-05-10 ~ 2025-15-03',
-          approval: '홍길동',
-          requestDate: '2025-04-25',
-          status: '대기'
-        }, {
-          type: '반차',
-          period: '2025-05-01 ~ 2025-05-03',
-          approval: '홍길동',
-          requestDate: '2025-04-25',
-          status: '승인'
-        }],
-      filteredData: [],
+
+      color: ['blue', 'red', 'green', 'purple', 'orange'],
+      colorMap: [],
+
+      useSummary: [], // 연차 소모성 휴가
+      notUseSummary: [], // 연차 비 소모성 휴가
+
+      // 휴가 현황 목록 조회용
+      listData: [],
+      request: {
+        vcatnKnds: null, // 휴가 종류
+        bgnde: null, // 시작일
+        endde: null, // 종료일
+      },
+
+      // 공통코드(구분, 상태) 목록 조회용
+      vcatnKnds: [],
+      confmTypes: [],
+      serachRequest: {
+        searchType: "upperCd",
+        searchText: null,
+      },
     };
   },
-  components:{
-    SearchOutlined
-  },
+
   computed: {
+    formattedListData() {
+      if (!this.listData || !Array.isArray(this.listData)) {
+        return [];
+      }
+
+      return this.listData.map(item => {
+        const vcatnKnd = this.vcatnKnds.find(v => v.code === item.vcatnKnd);
+        const confmAt = this.confmTypes.find(v => v.codeValue === item.confmAt);
+        return {
+          ...item,
+          formattedVcatnKnd: vcatnKnd ? vcatnKnd.codeNm : item.vcatnKnd,
+          formattedBgnde: item.bgnde ? item.bgnde.split(' ')[0] : '',
+          formattedEndde: item.endde ? item.endde.split(' ')[0] : '',
+          formattedBeginHour: item.beginHour ? item.beginHour.toString().padStart(2, '0') : '00',
+          formattedEndHour: item.endHour ? item.endHour.toString().padStart(2, '0') : '00',
+          formattedBeginMnt: item.beginMnt ? item.beginMnt.toString().padStart(2, '0') : '00',
+          formattedEndMnt: item.endMnt ? item.endMnt.toString().padStart(2, '0') : '00',
+          formattedStatus: confmAt ? confmAt.codeNm : item.confmAt,
+        };
+      });
+    }
   },
+
+  created() {
+    this.vcatnCodes(); // 휴가 종류 코드 조회
+    this.confmTypeCodes(); // 상태 코드 조회
+  },
+
+  mounted() {
+    this.findSummary(); // 현황 조회
+    this.findList(); // 목록 조회
+  },
+
   methods: {
+    // 휴가 종류 코드 조회
+    async confmTypeCodes() {
+      this.vcatnKnds = []; // 초기화
+      this.serachRequest.searchText = "sanctn_mby_vcatn";
 
-    goToAttendancePage(item) {
-      this.$router.push({ name: 'AttendanceDetail', query: { id: item.id } });
-    },
-    changePage(page) {
-      if (page < 1 || page > this.totalPages) return;
-      this.currentPage = page;
-      this.$emit('page-changed', page); // 필요 시 부모에 알림
-    },
-    async onClickSubmit() {
-      // `useMutation` 훅을 사용하여 mutation 함수 가져오기
-      const { mutate, onDone, onError } = useMutation(mygql);
+      const codes = await this.$findCodeList(this.serachRequest);
+      if (codes && Array.isArray(codes)) {
+        for (const code of codes) {
+          this.serachRequest.searchText = code.code;
 
-      try {
-        const result = await mutate();
-        console.log(result);
-      } catch (error) {
-        console.error('Mutation error:', error);
+          const result = await this.$findCodeList(this.serachRequest);
+          if (result && Array.isArray(result)) {
+            this.vcatnKnds.push(...result);
+          }
+        }
       }
     },
-    goToDetailPage(item) {
-  this.$router.push({
-    name: 'HyugaDetail',
-    query: { id: item.id }
-  });
-},
 
-  // 상태에 따른 클래스 반환 메소드
-  getStatusClass(status) {
-    return status === 'active' ? 'status-active' : 'status-inactive';
-  },
+    // 상태 코드 조회
+    async vcatnCodes() {
+      this.confmTypes = []; // 초기화
+      this.serachRequest.searchText = "confm_code";
+
+      const codes = await this.$findCodeList(this.serachRequest);
+      if (codes && Array.isArray(codes)) {
+        this.confmTypes = codes;
+      }
+    },
+
+    // 현황 조회
+    async findSummary() {
+      const vm = this;
+      try {
+        const response = await findVcatnsSummary(vm.request);
+        const result = response.data.data;
+
+        this.request.bgnde = new Date(result.data.bgnde).toISOString().split('T')[0];
+        this.request.endde = new Date(result.data.endde).toISOString().split('T')[0];
+        if (result.data.yryc < 1) {
+          this.request.endde = new Date().toISOString().split('T')[0];
+        }
+
+        let yrycCyfdCo = result.data.yrycCyfdCo;
+        let totalCo = result.data.yrycCo + yrycCyfdCo;
+        let useCo = result.useSummary.useCo;
+        let notUseCo = totalCo - useCo;
+
+        this.useSummary = []; // 초기화
+        this.useSummary.push({ key: 'totalCo', name: '전체', value: totalCo + '(' + yrycCyfdCo + ')' });
+        this.useSummary.push({ key: 'useCo', name: '사용', value: useCo, codeList: result.useSummary.codeList });
+        this.useSummary.push({ key: 'notUseCo', name: '미사용', value: notUseCo });
+
+        this.notUseSummary = []; // 초기화
+        this.notUseSummary = result.notUseSummary;
+
+        this.colorMap = []; // 초기화
+        const keys = Object.keys(this.notUseSummary);
+        for (let i = 0; i < Object.keys(this.notUseSummary).length; i++) {
+          this.colorMap.push({
+            code: keys[i].code,
+            class: this.color[this.useSummary.length + i],
+          })
+        }
+      } catch (error) {
+        if (error.response) {
+          alert(error.response.data.message);
+        } else {
+          alert("에러가 발생했습니다.");
+        }
+        console.error(error.message);
+      };
+    },
+
+    // 현황에 클래스 부여
+    getBoxClass(idx) {
+      if (idx === 0) return 'box';
+      return `box ${this.color[(idx - 1) % this.color.length]}`;
+    },
+
+    // 날짜 선택
+    changeDate() {
+      if (!this.request.bgnde || !this.request.endde) {
+        return;
+      }
+
+      const startDate = new Date(this.request.bgnde);
+      const endDate = new Date(this.request.endde);
+      if (endDate < startDate) {
+        alert('종료일은 시작일보다 이전일 수 없습니다.');
+        this.request.endde = this.request.bgnde;
+        return;
+      }
+
+      this.findList(); // 목록 조회
+    },
+
+    // 목록 조회
+    async findList() {
+      const vm = this;
+      try {
+        delete vm.request.vcatnKndList;
+
+        const response = await findMyVcatnsProc(vm.request);
+        const result = response.data.data;
+
+        vm.listData = result.lists;
+        vm.request = result.search;
+      } catch (error) {
+        if (error.response) {
+          alert(error.response.data.message);
+        } else {
+          alert("에러가 발생했습니다.");
+        }
+        console.error(error.message);
+      };
+    },
+
+    // 목록 필터 조회
+    fnFindUseList(item) {
+      if (item.key === 'totalCo') {
+        this.request.vcatnKnds = null;
+      } else if (item.key === 'useCo') {
+        this.request.vcatnKnds = item.codeList.map(code => code.code).join(", ");
+      } else {
+        return;
+      }
+
+      this.findList(); // 목록 조회
+    },
+    fnFindNotUseList(vcatnKnd) {
+      this.request.vcatnKnds = vcatnKnd;
+
+      this.findList(); // 목록 조회
+    },
+
+    // 구분에 클래스 부여
+    getTypeColorClass(code) {
+      for (let color of this.colorMap) {
+        if (color.code === code) {
+          return 'vcatnKnd ' + color.class;
+        }
+      }
+      return 'vcatnKnd blue';
+    },
+
+    // 상태에 따른 클래스 반환 메소드
     getStatusClass(status) {
       if (status === '승인') return 'status-approved';
       if (status === '대기') return 'status-pending';
       return '';
     },
-    isPastPeriod(period) {
-    // 예: '2025-05-01 ~ 2025-05-03' → 종료일 추출
-    const endDateStr = period.split('~')[1]?.trim();
-    if (!endDateStr) return false;
 
-    const endDate = new Date(endDateStr);
-    const today = new Date();
+    // 페이지 이동
+    fnChangeCurrentPage(currentPage) {
+      this.request.currentPage = Number(currentPage);
+      this.$nextTick(() => {
+        this.findList();
+      });
+    },
 
-    // 현재 날짜보다 과거면 true
-    return endDate < today;
-  }
-  },
-  created() {
-  },
-  mounted() {
+    // 페이지 이동
+    fnMoveTo(type, id) {
+      const routes = {
+        'list': { name: 'hyugaStatue' },
+        'view': { name: 'HyugaDetail', query: { id } },
+        'edit': { name: 'hyugaInsert', query: this.$isEmpty(id) ? {} : { id } },
+      };
 
-
+      if (routes[type]) {
+        if (!this.$isEmpty(this.pageId) && type === 'list') {
+          this.$router.push({ name: 'HyugaDetail', query: { id: this.pageId } });
+        }
+        this.$router.push(routes[type]);
+      } else {
+        alert("올바르지 않은 경로를 요청하여 목록으로 이동합니다.");
+        this.$router.push(routes['list']);
+      }
+    },
   },
 };
 </script>
-
 <style scoped>
-tr{cursor: pointer;}
-</style>
+tr {
+  cursor: pointer;
+}
+
+.box {
+  cursor: pointer;
+}
+
+.vcatnKnd.red {
+  color: #E92727 !important;
+}
+
+.vcatnKnd.green {
+  color: #3C97AB !important;
+}
+
+.vcatnKnd.blue {
+  color: #1D75E1 !important;
+}
+
+.vcatnKnd.purple {
+  color: #A36CD4 !important;
+}
+
+.vcatnKnd.orange {
+  color: #F7941C !important;
+}
+</style>
(파일 끝에 줄바꿈 문자 없음)
client/views/pages/Manager/hr/hr.vue
--- client/views/pages/Manager/hr/hr.vue
+++ client/views/pages/Manager/hr/hr.vue
@@ -32,8 +32,8 @@
                 </div>
               </router-link>
             </li>
-           
-            
+
+
           </ul>
       </details>
       <details class="menu-box">
@@ -45,8 +45,8 @@
                   <img :src="menuicon" alt="">
                 </div>
               </router-link></li>
-           
-            
+
+
           </ul>
       </details>
     </div>
package-lock.json
--- package-lock.json
+++ package-lock.json
@@ -9,11 +9,13 @@
         "@ant-design/icons-vue": "^7.0.1",
         "@babel/cli": "7.19.3",
         "@babel/core": "7.19.3",
+        "@ckeditor/ckeditor5-vue": "^7.3.0",
         "@fullcalendar/core": "^6.1.15",
         "@fullcalendar/daygrid": "^6.1.15",
         "@fullcalendar/vue3": "^6.1.15",
         "axios": "^1.10.0",
         "babel-loader": "8.2.5",
+        "ckeditor5": "^45.2.1",
         "css-loader": "6.7.1",
         "express": "4.18.1",
         "express-http-proxy": "^2.1.1",
@@ -380,6 +382,828 @@
         "node": ">=6.9.0"
       }
     },
+    "node_modules/@ckeditor/ckeditor5-adapter-ckfinder": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-adapter-ckfinder/-/ckeditor5-adapter-ckfinder-45.2.1.tgz",
+      "integrity": "sha512-lvXu5hc1wP0U2tTGm9wi8QoWrxnoPfR04A5br8u5cYzIbmyYDFcHgBWhWXjrDrjxVGDMSLgmrMtsw5gLhzE2yw==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-upload": "45.2.1",
+        "ckeditor5": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-alignment": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-alignment/-/ckeditor5-alignment-45.2.1.tgz",
+      "integrity": "sha512-aYCxhuvur2vtgzhl8yw4OWqhjsNvqUBW5Lyk4SVcs+fVyaOxIYhjNw+9oVkAjryPT/wLVhXBHnQEBoG6hUuE8g==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-icons": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "ckeditor5": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-autoformat": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-autoformat/-/ckeditor5-autoformat-45.2.1.tgz",
+      "integrity": "sha512-0QsRJ+Oh3+3oAwG+KeuyPBnFArF4mzFk8RX277iqQInbIit63lds0pgejJGvXMrDcpUxo6RzmXKZPJEeps818Q==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-engine": "45.2.1",
+        "@ckeditor/ckeditor5-heading": "45.2.1",
+        "@ckeditor/ckeditor5-typing": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "ckeditor5": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-autosave": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-autosave/-/ckeditor5-autosave-45.2.1.tgz",
+      "integrity": "sha512-XnK5Ktec5nQ12ianvf++zrxNpS9sjA2C/e9TTBcY3h99oMcdO7cbm0Yv9zzQNODQifdrHq/KXwAoWSYYNTjtiw==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "ckeditor5": "45.2.1",
+        "es-toolkit": "1.32.0"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-basic-styles": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-basic-styles/-/ckeditor5-basic-styles-45.2.1.tgz",
+      "integrity": "sha512-PXnnddU8boF2PFziaodXrPYtLgEmWSjH9YaZMSFrcsf11RmqLJ3YjaJHfL94TFFQuK+Ey3pAIiT7d7EOjARBVw==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-icons": "45.2.1",
+        "@ckeditor/ckeditor5-typing": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "ckeditor5": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-block-quote": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-block-quote/-/ckeditor5-block-quote-45.2.1.tgz",
+      "integrity": "sha512-H7x7EYwfK6M2v4n6VIp1PJ5YyZBnR6/vyqEiH7CQB5cN6IvgjxArXimD6BnIVYXgiwVGk89P4Lv0UZOe4zICYQ==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-enter": "45.2.1",
+        "@ckeditor/ckeditor5-icons": "45.2.1",
+        "@ckeditor/ckeditor5-typing": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "ckeditor5": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-bookmark": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-bookmark/-/ckeditor5-bookmark-45.2.1.tgz",
+      "integrity": "sha512-qJxadIb5d0dJWSA3pLC9Udo22tViqgg60LqvkjA7HPoP/NNx0YPaS5fmnb7nW03KaZo/58C8nc9DdvuO7LlWWw==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-icons": "45.2.1",
+        "@ckeditor/ckeditor5-link": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "@ckeditor/ckeditor5-widget": "45.2.1",
+        "ckeditor5": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-ckbox": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-ckbox/-/ckeditor5-ckbox-45.2.1.tgz",
+      "integrity": "sha512-v+2I5scbc6y4lnz9cbH/6FQRxpqJ1vymlSACFe6R9mH8aqIROHArIhofG6Bwd9LK6ksAMITO+He20fFWe0HJRA==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-cloud-services": "45.2.1",
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-engine": "45.2.1",
+        "@ckeditor/ckeditor5-icons": "45.2.1",
+        "@ckeditor/ckeditor5-image": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-upload": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "blurhash": "2.0.5",
+        "ckeditor5": "45.2.1",
+        "es-toolkit": "1.32.0"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-ckfinder": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-ckfinder/-/ckeditor5-ckfinder-45.2.1.tgz",
+      "integrity": "sha512-LEgK9IM+ZX+WcE60kqyr1aMQQfH6HgUUIomly2b7InmIfwUBzIc37ef21WlFd/w+YSjwBjwRLG0W+oYVmx3SZg==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-icons": "45.2.1",
+        "@ckeditor/ckeditor5-image": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "ckeditor5": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-clipboard": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-clipboard/-/ckeditor5-clipboard-45.2.1.tgz",
+      "integrity": "sha512-VEzCNHEzJO4B5Co4Q3YO06g7GThkSmgOWAFgQwa/BDKqT40/u36ccB7uYKsWzBMBRu3KsOWFk6sPSueQiMfmew==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-engine": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "@ckeditor/ckeditor5-widget": "45.2.1",
+        "es-toolkit": "1.32.0"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-cloud-services": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-cloud-services/-/ckeditor5-cloud-services-45.2.1.tgz",
+      "integrity": "sha512-LQDjKxScFAkgXiFB9fypqf3pj2ou63SgsTmt3aMuH/e5NGxGCbG9EodHWgFCg5cvGEXV+jC7JCrOquflti/yBg==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "ckeditor5": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-code-block": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-code-block/-/ckeditor5-code-block-45.2.1.tgz",
+      "integrity": "sha512-/XmKP8lol6WHDK/wbxFJu3npH8WnbZC9Gt7kND8bnpfQTSVihZmtjOnSfmyo6jKTsXWB1eDUI4+JdKCdrgfd2A==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-clipboard": "45.2.1",
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-engine": "45.2.1",
+        "@ckeditor/ckeditor5-enter": "45.2.1",
+        "@ckeditor/ckeditor5-icons": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "ckeditor5": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-core": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-core/-/ckeditor5-core-45.2.1.tgz",
+      "integrity": "sha512-h32tw/50ampWiouysBEQLXbPSAV1QV4iUEFNQzJVSy9q9fnGy6hsLZNlTqA+bORHwhn8SL96+OcAcGUNOiWfEg==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-engine": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "@ckeditor/ckeditor5-watchdog": "45.2.1",
+        "es-toolkit": "1.32.0"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-easy-image": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-easy-image/-/ckeditor5-easy-image-45.2.1.tgz",
+      "integrity": "sha512-jPydqTv1pRWo+uYQQn+er/WH3z6R77Jn3w2hQGKy77Ku9j0gEp/YwODSTW/IeZDDHE7Bmrw1S5ULHoveX7k4lg==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-cloud-services": "45.2.1",
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-upload": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "ckeditor5": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-editor-balloon": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-editor-balloon/-/ckeditor5-editor-balloon-45.2.1.tgz",
+      "integrity": "sha512-K/nNGeaBuD5MvaxamoZzv97LPzyb7n9t8FiScmXIpgkmy/mRt1jkbhmMtGuHdTuUL2v4z3hCthb1EDsxJM8HwQ==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-engine": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "ckeditor5": "45.2.1",
+        "es-toolkit": "1.32.0"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-editor-classic": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-editor-classic/-/ckeditor5-editor-classic-45.2.1.tgz",
+      "integrity": "sha512-cIyNNu4jano8WHl94ljNxXVpxGQadx4esDbV1rmj3e5gCh0XCXPOcyHYW9QPCT1Jf2M7YX32t5FFT/ixV4269A==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-engine": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "ckeditor5": "45.2.1",
+        "es-toolkit": "1.32.0"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-editor-decoupled": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-editor-decoupled/-/ckeditor5-editor-decoupled-45.2.1.tgz",
+      "integrity": "sha512-LNXe8CTIYL7PxwMHe2i1Ta7BlqmEO9ke/xrQxW+FHJ1obEsjZS56uz7YgBWISGXjWh/0bBRpz+7jL0FhflK9Dw==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-engine": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "ckeditor5": "45.2.1",
+        "es-toolkit": "1.32.0"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-editor-inline": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-editor-inline/-/ckeditor5-editor-inline-45.2.1.tgz",
+      "integrity": "sha512-/z/7U43QdVn0PUgIq+a/HRbgAut2eeLCzWVL4henbfSRxYJ9DBHH3ckIhX//QTfyTodeJ/5igrSmZH7oGVPwww==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-engine": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "ckeditor5": "45.2.1",
+        "es-toolkit": "1.32.0"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-editor-multi-root": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-editor-multi-root/-/ckeditor5-editor-multi-root-45.2.1.tgz",
+      "integrity": "sha512-o3AIx3fKCbQPKrbcgflgUVLEfP+zL2/oX22agIA0iugghpLF2Q7y0hlVRuYjWa0fOY1mxt7EKC6BWwgrSF3X3Q==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-engine": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "ckeditor5": "45.2.1",
+        "es-toolkit": "1.32.0"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-emoji": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-emoji/-/ckeditor5-emoji-45.2.1.tgz",
+      "integrity": "sha512-g5jtferdBRC7VI34AiLoVQZ4MYYr3PgEM0yZ1rNJSt0UfxQgZcHTu0SrZV/qxxweDcyQikHeBMrkQy0i77zabQ==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-icons": "45.2.1",
+        "@ckeditor/ckeditor5-mention": "45.2.1",
+        "@ckeditor/ckeditor5-typing": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "ckeditor5": "45.2.1",
+        "es-toolkit": "1.32.0",
+        "fuzzysort": "3.1.0"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-engine": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-engine/-/ckeditor5-engine-45.2.1.tgz",
+      "integrity": "sha512-kX6myPFscybc7ujM/iFTCKi5353+uRg0h1UKXVvh0NuqcnzJkMmpke3iFyzUckkfmtw/GvLvtRTli3190QgBaw==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "es-toolkit": "1.32.0"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-enter": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-enter/-/ckeditor5-enter-45.2.1.tgz",
+      "integrity": "sha512-y5wsQmB6+rERieqkPi/DvrrY2v8t8bUVZtVtx7vdiujF3Iab7s6GvPcLCZJDhDPRNSVxJb2wR0jREIqGv+CAgw==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-engine": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-essentials": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-essentials/-/ckeditor5-essentials-45.2.1.tgz",
+      "integrity": "sha512-kMHqD/m5Y55CqgjAa4Ui5p+yg28c1Itm6V97Sn30z3lMXauSRU9Bg9JnSlV+b1zqZuiUGBedEtc8ou5rm1px+A==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-clipboard": "45.2.1",
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-enter": "45.2.1",
+        "@ckeditor/ckeditor5-select-all": "45.2.1",
+        "@ckeditor/ckeditor5-typing": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-undo": "45.2.1",
+        "ckeditor5": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-find-and-replace": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-find-and-replace/-/ckeditor5-find-and-replace-45.2.1.tgz",
+      "integrity": "sha512-mfdWr5Sy+HHLKZXXq3qkuMYgIUR+est/snEQyUeySUYMMh0LxvlO5nUG6X5RQXmWy9eTmpNDvObLxm48/tcsvg==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-icons": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "ckeditor5": "45.2.1",
+        "es-toolkit": "1.32.0"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-font": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-font/-/ckeditor5-font-45.2.1.tgz",
+      "integrity": "sha512-ZrvK0XcR/SXYbGyAczJRd4Y/U2fJcqEKSpxNHZLxTpVDArS9E1PQGQ/F27w4Z2wfmAKKjqf2SPlWb2JBZ/RY/w==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-engine": "45.2.1",
+        "@ckeditor/ckeditor5-icons": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "ckeditor5": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-fullscreen": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-fullscreen/-/ckeditor5-fullscreen-45.2.1.tgz",
+      "integrity": "sha512-quSvp00VLA03aXTStgICRIglKN19Dn2elyJ+w20i1ZitvWZNf9bNayDeeCZGR2+qU/2kAEAsn5Q2bdHlC2tUnA==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-editor-classic": "45.2.1",
+        "@ckeditor/ckeditor5-editor-decoupled": "45.2.1",
+        "@ckeditor/ckeditor5-icons": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "ckeditor5": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-heading": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-heading/-/ckeditor5-heading-45.2.1.tgz",
+      "integrity": "sha512-J9ICygU3x0c0gY7FJJ0Ba3EFVlspbQinungipnIyMF2/4SXTOepKxHYG3x7j9xcjpUGosokeSvKfdQ0ACKtoKg==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-engine": "45.2.1",
+        "@ckeditor/ckeditor5-icons": "45.2.1",
+        "@ckeditor/ckeditor5-paragraph": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "ckeditor5": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-highlight": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-highlight/-/ckeditor5-highlight-45.2.1.tgz",
+      "integrity": "sha512-Z8mIWO0TpvAyfQslJn3FertL8Brl2L7xIgad/s8JMfUWPcB7Tg2qm5GUqe1yfBmROz2g86JX+v6iMVvb5MkBHg==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-icons": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "ckeditor5": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-horizontal-line": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-horizontal-line/-/ckeditor5-horizontal-line-45.2.1.tgz",
+      "integrity": "sha512-YMI7XQuMUTbhG33jrAGlmkkIxVnusF/e057oUAJ+Er6ug1RRAd3Y1I2MLmmk4/4mGmjXZPOXH8ueWHHFCMOvog==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-icons": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "@ckeditor/ckeditor5-widget": "45.2.1",
+        "ckeditor5": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-html-embed": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-html-embed/-/ckeditor5-html-embed-45.2.1.tgz",
+      "integrity": "sha512-UMr3GPcoavAHHFv4DWk/9dvgHmHEft1VzUYEAYK9S2lC8ADkSzo6rrWOwYapFZps1tJEwQjLQF+XZ9fw41t0lw==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-icons": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "@ckeditor/ckeditor5-widget": "45.2.1",
+        "ckeditor5": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-html-support": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-html-support/-/ckeditor5-html-support-45.2.1.tgz",
+      "integrity": "sha512-ZxT3vxO4++iN7BU1fApTIDGkjAkWemUyNkUxWt/TRg3tgOUbnlyhyS9HHOh/UITfNuLgx9IKm04uh611okIIAA==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-engine": "45.2.1",
+        "@ckeditor/ckeditor5-enter": "45.2.1",
+        "@ckeditor/ckeditor5-heading": "45.2.1",
+        "@ckeditor/ckeditor5-image": "45.2.1",
+        "@ckeditor/ckeditor5-list": "45.2.1",
+        "@ckeditor/ckeditor5-table": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "@ckeditor/ckeditor5-widget": "45.2.1",
+        "ckeditor5": "45.2.1",
+        "es-toolkit": "1.32.0"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-icons": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-icons/-/ckeditor5-icons-45.2.1.tgz",
+      "integrity": "sha512-vDCbPu8zKMFgiUQTFuie0h9PNHifXuAeiNHvk/W/6OutTNJwuBp7oLBpYvvkkJ5/iTFPL7En5nOc/czScNmqQw=="
+    },
+    "node_modules/@ckeditor/ckeditor5-image": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-image/-/ckeditor5-image-45.2.1.tgz",
+      "integrity": "sha512-dBd9jKh0Pn6pXJEOc3HX0aONJjTOVTh758K43Ncm/AN73G/mcoaN92mkoMSm1yxzea5W21hFBbr8ttseOyaIYw==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-clipboard": "45.2.1",
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-engine": "45.2.1",
+        "@ckeditor/ckeditor5-icons": "45.2.1",
+        "@ckeditor/ckeditor5-typing": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-undo": "45.2.1",
+        "@ckeditor/ckeditor5-upload": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "@ckeditor/ckeditor5-widget": "45.2.1",
+        "ckeditor5": "45.2.1",
+        "es-toolkit": "1.32.0"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-indent": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-indent/-/ckeditor5-indent-45.2.1.tgz",
+      "integrity": "sha512-aQlXGtsydQ2l3wffKFE/Hs9RAmlJPLn6vMzi44NLnKxQ33bPPhqAr3JlCEMlyI5ihVlUE45KtEWt+F7wNljFrg==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-engine": "45.2.1",
+        "@ckeditor/ckeditor5-heading": "45.2.1",
+        "@ckeditor/ckeditor5-icons": "45.2.1",
+        "@ckeditor/ckeditor5-list": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "ckeditor5": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-integrations-common": {
+      "version": "2.2.3",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-integrations-common/-/ckeditor5-integrations-common-2.2.3.tgz",
+      "integrity": "sha512-92kQWQj1wiABF7bY1+J79Ze+WHr7pwVBufn1eeJLWcTXbPQq4sAolfKv8Y8Ka9g69mdyE9+GPWmGFYDeQJVPDg==",
+      "engines": {
+        "node": ">=18.0.0"
+      },
+      "peerDependencies": {
+        "ckeditor5": ">=42.0.0 || ^0.0.0-nightly"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-language": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-language/-/ckeditor5-language-45.2.1.tgz",
+      "integrity": "sha512-pCN1tr+g4Cj7wqRLiaLcbDRJze+pxp8bfHr0T1z1wZRIs5c2lyZtSX00EqyxkXWFYWfu0wPujqKxQzetPbr98w==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "ckeditor5": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-link": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-link/-/ckeditor5-link-45.2.1.tgz",
+      "integrity": "sha512-TjqPAfbjQEpmJK7YQ3ounjnEVSbseGwyws9AeyhVKHINbppE1LSA9Q8pDELEUalEOBvaEZgB5x0OtuCQE7exdg==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-clipboard": "45.2.1",
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-engine": "45.2.1",
+        "@ckeditor/ckeditor5-icons": "45.2.1",
+        "@ckeditor/ckeditor5-image": "45.2.1",
+        "@ckeditor/ckeditor5-typing": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "@ckeditor/ckeditor5-widget": "45.2.1",
+        "ckeditor5": "45.2.1",
+        "es-toolkit": "1.32.0"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-list": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-list/-/ckeditor5-list-45.2.1.tgz",
+      "integrity": "sha512-liUKNg4XVoj4dx2JtxS6F+zSlnAYLUCv8y9odEAOQ/ar9m2cCvtxfBAbO6GQnJu1aBOxM5/P1J3HuQmFCr48EQ==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-clipboard": "45.2.1",
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-engine": "45.2.1",
+        "@ckeditor/ckeditor5-enter": "45.2.1",
+        "@ckeditor/ckeditor5-icons": "45.2.1",
+        "@ckeditor/ckeditor5-typing": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "ckeditor5": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-markdown-gfm": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-markdown-gfm/-/ckeditor5-markdown-gfm-45.2.1.tgz",
+      "integrity": "sha512-SGLY//kbcZy/MzZDSy/af/80gmh5vkMoQhAqR4FBTgWTosKbZblhgXaLM10Ld0bcjQMJL2BLeTNKHDcy7S2MFQ==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-clipboard": "45.2.1",
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-engine": "45.2.1",
+        "@types/marked": "4.3.2",
+        "@types/turndown": "5.0.5",
+        "ckeditor5": "45.2.1",
+        "marked": "4.0.12",
+        "turndown": "7.2.0",
+        "turndown-plugin-gfm": "1.0.2"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-media-embed": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-media-embed/-/ckeditor5-media-embed-45.2.1.tgz",
+      "integrity": "sha512-0NED9h+BIAns/DNaUotCD5Uhm6MNzGI4QsJ4obCrtl0lolBerXhzcRtlRVaF3R8omlMvS6r80fWsWSCttcpGjw==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-clipboard": "45.2.1",
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-engine": "45.2.1",
+        "@ckeditor/ckeditor5-icons": "45.2.1",
+        "@ckeditor/ckeditor5-typing": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-undo": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "@ckeditor/ckeditor5-widget": "45.2.1",
+        "ckeditor5": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-mention": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-mention/-/ckeditor5-mention-45.2.1.tgz",
+      "integrity": "sha512-q77TXcjzQ1dZkvxPKu3TC3OAJiwLXeCNYglGvt5iqxQ1PmY86TI0EyO/qWApKiydacowev7ppCbDkHvA0oCMRA==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-typing": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "ckeditor5": "45.2.1",
+        "es-toolkit": "1.32.0"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-minimap": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-minimap/-/ckeditor5-minimap-45.2.1.tgz",
+      "integrity": "sha512-bKYcvZ7wIolttXfXR9BzyVEI+6FmflO0EhvOw+Ic9VClDy81Y4EYyCMB0XsXK0JO0KTnTKUOmLrPTfOnU/c68w==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-engine": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "ckeditor5": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-page-break": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-page-break/-/ckeditor5-page-break-45.2.1.tgz",
+      "integrity": "sha512-KiHGordLRIzebAeyyY43WHR1bpjXab9cD+S8BWeivGBYytFRN4S+MT288HiweYk53Pla2pumrgoBZ/qKLkn98Q==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-icons": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "@ckeditor/ckeditor5-widget": "45.2.1",
+        "ckeditor5": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-paragraph": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-paragraph/-/ckeditor5-paragraph-45.2.1.tgz",
+      "integrity": "sha512-bDZUEHXruWG7P7PDIwNKKuUjgIvLbbY+m0GEXOI4+eB7cnJBOLbs62t8m+tLFJ/PRWUsJ/kA1RJQgouiy60+0Q==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-engine": "45.2.1",
+        "@ckeditor/ckeditor5-icons": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-paste-from-office": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-paste-from-office/-/ckeditor5-paste-from-office-45.2.1.tgz",
+      "integrity": "sha512-G5duZPSLmdGxghjBeHU2i1WJoKn8YdKI2lZu8GxqRQ1/FTaoWqnCpEAnxztLLTtwyI9HuFh6IuxrdnyPQOWtxw==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-clipboard": "45.2.1",
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-engine": "45.2.1",
+        "ckeditor5": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-remove-format": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-remove-format/-/ckeditor5-remove-format-45.2.1.tgz",
+      "integrity": "sha512-JKZdCqdrCOjP84ynUhE8F10q0LTiOTadhCsMYtfFUaMXlv+Ey6BMwh28I9WstGmhBiDmF4evivI8Jovncc0pyg==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-icons": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "ckeditor5": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-restricted-editing": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-restricted-editing/-/ckeditor5-restricted-editing-45.2.1.tgz",
+      "integrity": "sha512-rapILyX18HIw8xCuXozfQorz6Y/0Jd48/zr9ChXDbDkbDr4qaWdqAZ2bMuz8qP8k53dmkvxl4DfVfqM200txXQ==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-engine": "45.2.1",
+        "@ckeditor/ckeditor5-icons": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "ckeditor5": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-select-all": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-select-all/-/ckeditor5-select-all-45.2.1.tgz",
+      "integrity": "sha512-WRXtKECu+yrZGfNeriRQT+q41NDmW3K72cQL7Sben9CsJ4ixAFdc4APaDC5GvIvE00hjjLhZAVBtV8fiBTys/g==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-engine": "45.2.1",
+        "@ckeditor/ckeditor5-icons": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-show-blocks": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-show-blocks/-/ckeditor5-show-blocks-45.2.1.tgz",
+      "integrity": "sha512-VX6v9mkDkh5xaK/KANxjDRiRMowcUODHBg1z+MJZyVqeFxfDabHSQuS2OLYzsSJfeTlCzs/11IRHCGQq4ifyIQ==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-icons": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "ckeditor5": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-source-editing": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-source-editing/-/ckeditor5-source-editing-45.2.1.tgz",
+      "integrity": "sha512-HFaUovnh6v/9oIUWpxmxNxQIHArONuqzKMyipLHS7XWzb9nnMda+P5wnoYEiysoiNSDyBhM+yLCsgqs28XsmJg==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-icons": "45.2.1",
+        "@ckeditor/ckeditor5-theme-lark": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "ckeditor5": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-special-characters": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-special-characters/-/ckeditor5-special-characters-45.2.1.tgz",
+      "integrity": "sha512-VECZXNiE1dA/TTJJUdz9kbWXDTXTIWsXyuBrvwQX/SgsPjIKYlpGW+oTu6iZdC4I/q0dsFvJvDJ6ngx89cxpLg==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-icons": "45.2.1",
+        "@ckeditor/ckeditor5-typing": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "ckeditor5": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-style": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-style/-/ckeditor5-style-45.2.1.tgz",
+      "integrity": "sha512-Rdf3FeNRFBngYBUrdfjLBYQkvcFHc4g7D48+nkBiYmPeYDIcviRxq0Ss50UHHgwl/aB6w6UvVdI2j+2HhYE+xQ==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-html-support": "45.2.1",
+        "@ckeditor/ckeditor5-list": "45.2.1",
+        "@ckeditor/ckeditor5-table": "45.2.1",
+        "@ckeditor/ckeditor5-typing": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "ckeditor5": "45.2.1",
+        "es-toolkit": "1.32.0"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-table": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-table/-/ckeditor5-table-45.2.1.tgz",
+      "integrity": "sha512-Twu2vztW0s5BVwn+AYcvH8Akx0SS1tSZ1P7MbVvYnm3+o0hOsHjO+bFi9anN+U53l5ciukNFtwLbvQw8YcKXvg==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-clipboard": "45.2.1",
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-engine": "45.2.1",
+        "@ckeditor/ckeditor5-icons": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "@ckeditor/ckeditor5-widget": "45.2.1",
+        "ckeditor5": "45.2.1",
+        "es-toolkit": "1.32.0"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-theme-lark": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-theme-lark/-/ckeditor5-theme-lark-45.2.1.tgz",
+      "integrity": "sha512-+JYkfdbCW7bYK+dYmxDSL49s4tZTzabrymvSt+fgFATDmbosP9zRRENPSYpfq0J7v0wJSOJrkK6aPiuEJr8VrQ==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-ui": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-typing": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-typing/-/ckeditor5-typing-45.2.1.tgz",
+      "integrity": "sha512-zsIDw2UZjEgH1OBFTE/e+ACFWgpQgwLGmfOA1R99SpSyuBzGgEeTQm/rLniZbnAQ3TJMHDm89dz0MneMhn9cgg==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-engine": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "es-toolkit": "1.32.0"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-ui": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-ui/-/ckeditor5-ui-45.2.1.tgz",
+      "integrity": "sha512-ZBl6xJliYhdQr8/9gCi9Pytz88YDD3VV+zyDq+5pBl1mI8feBjxp5XfPZxxAfV0dowqppqP/17UnPLtc+7YA0g==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-editor-multi-root": "45.2.1",
+        "@ckeditor/ckeditor5-engine": "45.2.1",
+        "@ckeditor/ckeditor5-icons": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "@types/color-convert": "2.0.4",
+        "color-convert": "2.0.1",
+        "color-parse": "1.4.2",
+        "es-toolkit": "1.32.0",
+        "vanilla-colorful": "0.7.2"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-undo": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-undo/-/ckeditor5-undo-45.2.1.tgz",
+      "integrity": "sha512-c3xJqk6iZLgHrCpJHektg/I1+ApcaTDEDfIS0pps8oxL1Ys6CWNE0QrlF5AuRoUopv//ik1iZg/qoA98JsQ1Ng==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-engine": "45.2.1",
+        "@ckeditor/ckeditor5-icons": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-upload": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-upload/-/ckeditor5-upload-45.2.1.tgz",
+      "integrity": "sha512-lg54V7wXouKy4q9Umt47ubUidelMsujgwxJCJg3egcNLKPiskYHnWlhYfA8hyVV21XCpfbm73c8Y1UWvkTglXA==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-utils": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-utils/-/ckeditor5-utils-45.2.1.tgz",
+      "integrity": "sha512-qjSsnUaw/VwklJZsRTVT6zDSBjMnmYIIGu3mHSk7Ybtao8W3fz1yzqdX3X6i937XGbw37+2MUfhJhrpubMnA8A==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "es-toolkit": "1.32.0"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-vue": {
+      "version": "7.3.0",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-vue/-/ckeditor5-vue-7.3.0.tgz",
+      "integrity": "sha512-OM8VW2bf5cXWKKaSr2eS1BhjzPmvkC2Jp/rWFdjU8wi4hhcKVJ5QqMepDguDcC+PHThaLec45WIrQTeLCb2AaA==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-integrations-common": "^2.2.2",
+        "lodash-es": "^4.17.21"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      },
+      "peerDependencies": {
+        "ckeditor5": ">=42.0.0 || ^0.0.0-nightly",
+        "vue": "^3.4.0"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-watchdog": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-watchdog/-/ckeditor5-watchdog-45.2.1.tgz",
+      "integrity": "sha512-FthIfLtl4qCv7iNEul9ZvUulFZVYNeMwS+Ww22HstZlJP3Ahh/JF4CGTkzfbP/oGFUsZCEochQ7H2/f4MN7pbg==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-editor-multi-root": "45.2.1",
+        "@ckeditor/ckeditor5-engine": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "es-toolkit": "1.32.0"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-widget": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-widget/-/ckeditor5-widget-45.2.1.tgz",
+      "integrity": "sha512-4HOfHewBE0cF+7zKnQBWRJHLdq4K48YDW/uC5J0vDhLrtetBwWPE9kbnOUUoAGNYqGkiYRd7btt1M6pPhXrz9Q==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-engine": "45.2.1",
+        "@ckeditor/ckeditor5-enter": "45.2.1",
+        "@ckeditor/ckeditor5-icons": "45.2.1",
+        "@ckeditor/ckeditor5-typing": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "es-toolkit": "1.32.0"
+      }
+    },
+    "node_modules/@ckeditor/ckeditor5-word-count": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-word-count/-/ckeditor5-word-count-45.2.1.tgz",
+      "integrity": "sha512-H4JmccFnFM+IaiT0zUTnx/7ebpAvelaJMEHWmntRlr1IFLf8AtwgOGBSeurglDVebyGXyqarbUxV4HpDxtzBxA==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "ckeditor5": "45.2.1",
+        "es-toolkit": "1.32.0"
+      }
+    },
     "node_modules/@ctrl/tinycolor": {
       "version": "3.6.1",
       "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
@@ -533,6 +1357,11 @@
         "@jridgewell/sourcemap-codec": "^1.4.14"
       }
     },
+    "node_modules/@mixmark-io/domino": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/@mixmark-io/domino/-/domino-2.2.0.tgz",
+      "integrity": "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="
+    },
     "node_modules/@nicolo-ribaudo/chokidar-2": {
       "version": "2.1.8-no-fsevents.3",
       "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz",
@@ -592,6 +1421,19 @@
         "node": ">= 10"
       }
     },
+    "node_modules/@types/color-convert": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/@types/color-convert/-/color-convert-2.0.4.tgz",
+      "integrity": "sha512-Ub1MmDdyZ7mX//g25uBAoH/mWGd9swVbt8BseymnaE18SU4po/PjmCrHxqIIRjBo3hV/vh1KGr0eMxUhp+t+dQ==",
+      "dependencies": {
+        "@types/color-name": "^1.1.0"
+      }
+    },
+    "node_modules/@types/color-name": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.5.tgz",
+      "integrity": "sha512-j2K5UJqGTxeesj6oQuGpMgifpT5k9HprgQd8D1Y0lOFqKHl3PJu5GMeS4Y5EgjS55AE6OQxf8mPED9uaGbf4Cg=="
+    },
     "node_modules/@types/eslint": {
       "version": "9.6.1",
       "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
@@ -624,6 +1466,11 @@
       "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
       "license": "MIT"
     },
+    "node_modules/@types/marked": {
+      "version": "4.3.2",
+      "resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.3.2.tgz",
+      "integrity": "sha512-a79Yc3TOk6dGdituy8hmTTJXjOkZ7zsFYV10L337ttq/rec8lRMDBpV7fL3uLx6TgbFCa5DU/h8FmIBQPSbU0w=="
+    },
     "node_modules/@types/minimist": {
       "version": "1.2.5",
       "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz",
@@ -646,6 +1493,11 @@
       "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==",
       "dev": true,
       "license": "MIT"
+    },
+    "node_modules/@types/turndown": {
+      "version": "5.0.5",
+      "resolved": "https://registry.npmjs.org/@types/turndown/-/turndown-5.0.5.tgz",
+      "integrity": "sha512-TL2IgGgc7B5j78rIccBtlYAnkuv8nUQqhQc+DSYV5j9Be9XOcm/SKOVRuA47xAVI3680Tk9B1d8flK2GWT2+4w=="
     },
     "node_modules/@vue/compiler-core": {
       "version": "3.5.13",
@@ -1294,6 +2146,11 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/blurhash": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/blurhash/-/blurhash-2.0.5.tgz",
+      "integrity": "sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w=="
+    },
     "node_modules/body-parser": {
       "version": "1.20.0",
       "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz",
@@ -1633,6 +2490,74 @@
         "node": ">=6.0"
       }
     },
+    "node_modules/ckeditor5": {
+      "version": "45.2.1",
+      "resolved": "https://registry.npmjs.org/ckeditor5/-/ckeditor5-45.2.1.tgz",
+      "integrity": "sha512-fPFysHo71yQ2TUYsD+VpnjH9kjboHSKGCIPf4AhJQ+eQwopzsnFkNgdvYcGPfAP11bHTRLG5Ol0wWISFa1mwCQ==",
+      "dependencies": {
+        "@ckeditor/ckeditor5-adapter-ckfinder": "45.2.1",
+        "@ckeditor/ckeditor5-alignment": "45.2.1",
+        "@ckeditor/ckeditor5-autoformat": "45.2.1",
+        "@ckeditor/ckeditor5-autosave": "45.2.1",
+        "@ckeditor/ckeditor5-basic-styles": "45.2.1",
+        "@ckeditor/ckeditor5-block-quote": "45.2.1",
+        "@ckeditor/ckeditor5-bookmark": "45.2.1",
+        "@ckeditor/ckeditor5-ckbox": "45.2.1",
+        "@ckeditor/ckeditor5-ckfinder": "45.2.1",
+        "@ckeditor/ckeditor5-clipboard": "45.2.1",
+        "@ckeditor/ckeditor5-cloud-services": "45.2.1",
+        "@ckeditor/ckeditor5-code-block": "45.2.1",
+        "@ckeditor/ckeditor5-core": "45.2.1",
+        "@ckeditor/ckeditor5-easy-image": "45.2.1",
+        "@ckeditor/ckeditor5-editor-balloon": "45.2.1",
+        "@ckeditor/ckeditor5-editor-classic": "45.2.1",
+        "@ckeditor/ckeditor5-editor-decoupled": "45.2.1",
+        "@ckeditor/ckeditor5-editor-inline": "45.2.1",
+        "@ckeditor/ckeditor5-editor-multi-root": "45.2.1",
+        "@ckeditor/ckeditor5-emoji": "45.2.1",
+        "@ckeditor/ckeditor5-engine": "45.2.1",
+        "@ckeditor/ckeditor5-enter": "45.2.1",
+        "@ckeditor/ckeditor5-essentials": "45.2.1",
+        "@ckeditor/ckeditor5-find-and-replace": "45.2.1",
+        "@ckeditor/ckeditor5-font": "45.2.1",
+        "@ckeditor/ckeditor5-fullscreen": "45.2.1",
+        "@ckeditor/ckeditor5-heading": "45.2.1",
+        "@ckeditor/ckeditor5-highlight": "45.2.1",
+        "@ckeditor/ckeditor5-horizontal-line": "45.2.1",
+        "@ckeditor/ckeditor5-html-embed": "45.2.1",
+        "@ckeditor/ckeditor5-html-support": "45.2.1",
+        "@ckeditor/ckeditor5-icons": "45.2.1",
+        "@ckeditor/ckeditor5-image": "45.2.1",
+        "@ckeditor/ckeditor5-indent": "45.2.1",
+        "@ckeditor/ckeditor5-language": "45.2.1",
+        "@ckeditor/ckeditor5-link": "45.2.1",
+        "@ckeditor/ckeditor5-list": "45.2.1",
+        "@ckeditor/ckeditor5-markdown-gfm": "45.2.1",
+        "@ckeditor/ckeditor5-media-embed": "45.2.1",
+        "@ckeditor/ckeditor5-mention": "45.2.1",
+        "@ckeditor/ckeditor5-minimap": "45.2.1",
+        "@ckeditor/ckeditor5-page-break": "45.2.1",
+        "@ckeditor/ckeditor5-paragraph": "45.2.1",
+        "@ckeditor/ckeditor5-paste-from-office": "45.2.1",
+        "@ckeditor/ckeditor5-remove-format": "45.2.1",
+        "@ckeditor/ckeditor5-restricted-editing": "45.2.1",
+        "@ckeditor/ckeditor5-select-all": "45.2.1",
+        "@ckeditor/ckeditor5-show-blocks": "45.2.1",
+        "@ckeditor/ckeditor5-source-editing": "45.2.1",
+        "@ckeditor/ckeditor5-special-characters": "45.2.1",
+        "@ckeditor/ckeditor5-style": "45.2.1",
+        "@ckeditor/ckeditor5-table": "45.2.1",
+        "@ckeditor/ckeditor5-theme-lark": "45.2.1",
+        "@ckeditor/ckeditor5-typing": "45.2.1",
+        "@ckeditor/ckeditor5-ui": "45.2.1",
+        "@ckeditor/ckeditor5-undo": "45.2.1",
+        "@ckeditor/ckeditor5-upload": "45.2.1",
+        "@ckeditor/ckeditor5-utils": "45.2.1",
+        "@ckeditor/ckeditor5-watchdog": "45.2.1",
+        "@ckeditor/ckeditor5-widget": "45.2.1",
+        "@ckeditor/ckeditor5-word-count": "45.2.1"
+      }
+    },
     "node_modules/classnames": {
       "version": "2.5.1",
       "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
@@ -1695,6 +2620,14 @@
       "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
       "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
       "license": "MIT"
+    },
+    "node_modules/color-parse": {
+      "version": "1.4.2",
+      "resolved": "https://registry.npmjs.org/color-parse/-/color-parse-1.4.2.tgz",
+      "integrity": "sha512-RI7s49/8yqDj3fECFZjUI1Yi0z/Gq1py43oNJivAIIDSyJiOZLfYCRQEgn8HEVAj++PcRe8AnL2XF0fRJ3BTnA==",
+      "dependencies": {
+        "color-name": "^1.0.0"
+      }
     },
     "node_modules/color-support": {
       "version": "1.1.3",
@@ -2167,6 +3100,11 @@
       "engines": {
         "node": ">= 0.4"
       }
+    },
+    "node_modules/es-toolkit": {
+      "version": "1.32.0",
+      "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.32.0.tgz",
+      "integrity": "sha512-ZfSfHP1l6ubgW/B/FRtqb9bYdMvI6jizbOSfbwwJNcOQ1QE6TFsC3jpQkZ900uUPSR3t3SU5Ds7UWKnYz+uP8Q=="
     },
     "node_modules/es6-promise": {
       "version": "4.2.8",
@@ -2644,6 +3582,11 @@
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
       }
+    },
+    "node_modules/fuzzysort": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-3.1.0.tgz",
+      "integrity": "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ=="
     },
     "node_modules/gauge": {
       "version": "4.0.4",
@@ -3418,6 +4361,11 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/lodash-es": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
+      "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
+    },
     "node_modules/lru-cache": {
       "version": "5.1.1",
       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -3507,6 +4455,17 @@
       },
       "funding": {
         "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/marked": {
+      "version": "4.0.12",
+      "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.12.tgz",
+      "integrity": "sha512-hgibXWrEDNBWgGiK18j/4lkS6ihTe9sxtV4Q1OQppb/0zzyPSzoFANBa5MfsG/zgsWklmNnhm0XACZOH/0HBiQ==",
+      "bin": {
+        "marked": "bin/marked.js"
+      },
+      "engines": {
+        "node": ">= 12"
       }
     },
     "node_modules/math-intrinsics": {
@@ -5890,6 +6849,19 @@
       "dev": true,
       "license": "Apache-2.0"
     },
+    "node_modules/turndown": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/turndown/-/turndown-7.2.0.tgz",
+      "integrity": "sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A==",
+      "dependencies": {
+        "@mixmark-io/domino": "^2.2.0"
+      }
+    },
+    "node_modules/turndown-plugin-gfm": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/turndown-plugin-gfm/-/turndown-plugin-gfm-1.0.2.tgz",
+      "integrity": "sha512-vwz9tfvF7XN/jE0dGoBei3FXWuvll78ohzCZQuOb+ZjWrs3a0XhQVomJEb2Qh4VHTPNRO4GPZh0V7VRbiWwkRg=="
+    },
     "node_modules/type-fest": {
       "version": "0.18.1",
       "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz",
@@ -6067,6 +7039,11 @@
         "spdx-expression-parse": "^3.0.0"
       }
     },
+    "node_modules/vanilla-colorful": {
+      "version": "0.7.2",
+      "resolved": "https://registry.npmjs.org/vanilla-colorful/-/vanilla-colorful-0.7.2.tgz",
+      "integrity": "sha512-z2YZusTFC6KnLERx1cgoIRX2CjPRP0W75N+3CC6gbvdX5Ch47rZkEMGO2Xnf+IEmi3RiFLxS18gayMA27iU7Kg=="
+    },
     "node_modules/vary": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
package.json
--- package.json
+++ package.json
@@ -4,11 +4,13 @@
     "@ant-design/icons-vue": "^7.0.1",
     "@babel/cli": "7.19.3",
     "@babel/core": "7.19.3",
+    "@ckeditor/ckeditor5-vue": "^7.3.0",
     "@fullcalendar/core": "^6.1.15",
     "@fullcalendar/daygrid": "^6.1.15",
     "@fullcalendar/vue3": "^6.1.15",
     "axios": "^1.10.0",
     "babel-loader": "8.2.5",
+    "ckeditor5": "^45.2.1",
     "css-loader": "6.7.1",
     "express": "4.18.1",
     "express-http-proxy": "^2.1.1",
 
vetur.config.js (added)
+++ vetur.config.js
@@ -0,0 +1,5 @@
+module.exports = {
+  settings: {
+    "vetur.ignoreProjectWarning": true,
+  },
+};
Add a comment
List