jichoi / calendar star
박정하 박정하 07-28
250728 박정하 결재 요청, 승인 대기 목록 추가
@1f61d56ed3724c9dc49ca957032db572fb0c2d37
client/resources/api/sanctns.js
--- client/resources/api/sanctns.js
+++ client/resources/api/sanctns.js
@@ -1,11 +1,11 @@
 import apiClient from "./index";
 
 // 결재 요청 목록 조회
-export const findMyRequestsProc = data => {
-  return apiClient.get('/sanctn/findMyRequests.json', { params: data });
+export const findMyApprovalRequestsProc = data => {
+  return apiClient.get('/sanctn/findMyApprovalRequests.json', { params: data });
 }
 
-// 조회 - 목록
-export const findSanctnsProc = data => {
-  return apiClient.get('/sanctn/findSanctns.json', { params: data });
+// 승인 대기 목록 조회
+export const findPendingApprovalsProc = data => {
+  return apiClient.get('/sanctn/findPendingApprovals.json', { params: data });
 }
(파일 끝에 줄바꿈 문자 없음)
client/resources/js/cmmnPlugin.js
--- client/resources/js/cmmnPlugin.js
+++ client/resources/js/cmmnPlugin.js
@@ -1,5 +1,5 @@
 import store from '../../views/pages/AppStore'
-import { findCodeListProc } from '../api/code'
+import { findCodesProc } from '../api/code'
 
 export default {
   install(Vue) {
@@ -37,23 +37,6 @@
       }
     }
 
-    // 공통코드 조회
-    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 = (d, h, m) => {
       if (Vue.config.globalProperties.$isEmpty(d) || Vue.config.globalProperties.$isEmpty(h) || Vue.config.globalProperties.$isEmpty(m)) {
@@ -74,22 +57,66 @@
       return startDate + " ~ " + endDate;
     }
 
-    // 공통코드 조회 (부모 코드로 자식 코드 조회)
+    // 공통코드 조회
+    Vue.config.globalProperties.$findCodeList = async function (serachRequest) {
+      try {
+        const response = await findCodesProc(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.$findChildCodes = async (upperCode) => {
       try {
         const searchRequest = {
-          searchType: 'upperCd',
+          searchType: 'UPPER_CODE',
           searchText: upperCode,
         };
 
-        const codes = await Vue.config.globalProperties.$findCodeList(searchRequest);
-        return Array.isArray(codes) ? codes : [];
+        const response = await findCodesProc(searchRequest);
+        const result = response.data.data;
+
+        return result.codes;
       } catch (error) {
         console.error(`코드 조회 실패 (${upperCode}):`, error);
         return [];
       }
     };
 
+    // 휴가 및 출장 구분 코드 조회
+    Vue.config.globalProperties.$sanctnIemCodes = async () => {
+      const lists = [];
+
+      // 휴가 구분 (연차, 반차, 공가 등)
+      const vcatnTopCodes = await Vue.config.globalProperties.$findChildCodes('sanctn_mby_vcatn');
+      for (let vcatnTopCode of vcatnTopCodes) {
+        const vcatnMidCodes = await Vue.config.globalProperties.$findChildCodes(vcatnTopCode.code);
+        for (let vcatnMidCode of vcatnMidCodes) {
+          if (parseFloat(vcatnMidCode.codeValue) === 0.5) {
+            const codes = await Vue.config.globalProperties.$findChildCodes(vcatnMidCode.code);
+            lists.push(...codes);
+          } else {
+            lists.push(vcatnMidCode);
+          }
+        }
+      }
+
+      // 출장 구분 (품의서, 복명서)
+      const bsrpCodes = await Vue.config.globalProperties.$findChildCodes('bsrp_code');
+      lists.push(...bsrpCodes);
+
+      return lists;
+    };
+
     // 기본 코드 목록 초기화
     Vue.config.globalProperties.$defaultCodes = async () => {
       const codeGroups = {
 
client/views/pages/Manager/approval/approvalList.vue (deleted)
--- client/views/pages/Manager/approval/approvalList.vue
@@ -1,226 +0,0 @@
-<template>
-  <div class="col-lg-12">
-    <div class="card">
-      <div class="card-body">
-        <h2 class="card-title">승인 대기 목록</h2>
-        <div class="sch-form-wrap title-wrap">
-          <h3><img :src="h3icon" alt="">승인 대기</h3>
-          <div class="input-group">
-            <select class="form-select" v-model="waitRequest.year" @change="fnChangeCurrentPage(1, 'wait')">
-              <option value="">연도 전체</option>
-              <option v-for="year in years" :key="year" :value="year">{{ year }}년</option>
-            </select>
-            <select class="form-select" v-model="waitRequest.month" @change="fnChangeCurrentPage(1, 'wait')">
-              <option value="">월 전체</option>
-              <option v-for="month in months" :key="month" :value="month">{{ month }}월</option>
-            </select>
-            <div class="sch-input">
-              <input type="text" class="form-control" placeholder="신청자명">
-              <button class="ico-sch">
-                <SearchOutlined />
-              </button>
-            </div>
-          </div>
-        </div>
-        <div class="tbl-wrap">
-          <table id="myTable" class="tbl data">
-            <colgroup>
-              <col style="width: 13.33%;">
-              <col style="width: 13.33%;">
-              <col style="width: 13.33%;">
-              <col style="width: 30%;">
-              <col style="width: 30%;">
-            </colgroup>
-            <thead>
-              <tr>
-                <th>구분</th>
-                <th>결재구분</th>
-                <th>신청자</th>
-                <th>기간</th>
-                <th>신청일</th>
-              </tr>
-            </thead>
-            <tbody>
-              <tr v-for="(item, idx) in waitList" :key="idx">
-                <td>{{ item.sanctnIemNm }}</td>
-                <td>{{ item.sanctnSeNm }}</td>
-                <td>{{ item.applcntNm }}</td>
-                <td>{{ $formattedDates(item) }}</td>
-                <td>{{ item.rgsde }}</td>
-              </tr>
-            </tbody>
-          </table>
-        </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 class="form-select" v-model="confmRequest.year" @change="fnChangeCurrentPage(1, 'wait')">
-              <option value="">연도 전체</option>
-              <option v-for="year in years" :key="year" :value="year">{{ year }}년</option>
-            </select>
-            <select class="form-select" v-model="confmRequest.month" @change="fnChangeCurrentPage(1, 'wait')">
-              <option value="">월 전체</option>
-              <option v-for="month in months" :key="month" :value="month">{{ month }}월</option>
-            </select>
-            <select name="" id="" class="form-select">
-              <option value="all">상태</option>
-              <option value="">승인</option>
-              <option value="">반려</option>
-            </select>
-            <div class="sch-input">
-              <input type="text" class="form-control" placeholder="신청자명">
-              <button class="ico-sch">
-                <SearchOutlined />
-              </button>
-            </div>
-          </div>
-        </div>
-        <div class="tbl-wrap">
-          <table id="myTable" class="tbl data">
-            <colgroup>
-              <col style="width: 13.33%;">
-              <col style="width: 13.33%;">
-              <col style="width: 13.33%;">
-              <col style="width: 30%;">
-              <col style="width: 16.66%;">
-              <col style="width: 13.33%;">
-            </colgroup>
-            <thead>
-              <tr>
-                <th>구분</th>
-                <th>결재구분</th>
-                <th>신청자</th>
-                <th>기간</th>
-                <th>신청일</th>
-                <th>상태</th>
-              </tr>
-            </thead>
-            <tbody>
-              <tr v-for="(item, index) in confmList" :key="index" :class="{ 'expired': true }">
-                <td>{{ item.sanctnIemNm }}</td>
-                <td>{{ item.sanctnSeNm }}</td>
-                <td>{{ item.applcntNm }}</td>
-                <td>{{ $formattedDates(item) }}</td>
-                <td>{{ item.rgsde }}</td>
-                <td>{{ item.confmAtNm }}</td>
-              </tr>
-            </tbody>
-          </table>
-        </div>
-        <Pagenation :search="confmRequest" @onChange="(currentPage) => fnChangeCurrentPage(currentPage, 'wait')" />
-      </div>
-    </div>
-  </div>
-</template>
-<script>
-import { SearchOutlined } from '@ant-design/icons-vue';
-import Pagenation from '../../../component/Pagenation.vue';
-// API
-import { findSanctnsProc } from '../../../../resources/api/sanctns';
-
-export default {
-  components: {
-    SearchOutlined,
-    Pagenation,
-  },
-
-  data() {
-    return {
-      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], // 월 목록
-
-      // 목록 조회용
-      waitList: [],
-      waitRequest: {
-        year: '',
-        month: '',
-        sanctnDe: null, // 결재일
-        sanctnIem: null, // 결재항목 : 출장 구분 or 휴가 종류 (휴가/출장 구분 코드)
-        sanctnMbyId: null, // 결재 주체 아이디 : 휴가/출장 아이디
-        confmAts: "W", // 승인여부
-        confmerId: this.$store.state.userInfo.userId,
-      },
-      confmList: [],
-      confmRequest: {
-        year: '',
-        month: '',
-        sanctnDe: null, // 결재일
-        sanctnIem: null, // 결재항목 : 출장 구분 or 휴가 종류 (휴가/출장 구분 코드)
-        sanctnMbyId: null, // 결재 주체 아이디 : 휴가/출장 아이디
-        confmAts: "A,R", // 승인여부
-        confmerId: this.$store.state.userInfo.userId,
-      },
-
-      // 공통코드 목록
-      sanctnCodes: [], // 결재 종류 코드 목록
-      sanctnMbyCodes: [], // 결재 항목 코드 목록
-      sanctnConfmCodes: [], // 결재 상태 코드 목록
-    };
-  },
-
-  computed: {},
-
-  created() { },
-
-  mounted() {
-    this.findList('wait'); // 목록 조회
-    this.findList('confm'); // 목록 조회
-  },
-
-  methods: {
-    // 목록 조회
-    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.waitList = result.lists;
-          vm.waitRequest = result.search;
-        } else if (type == 'confm') {
-          vm.confmList = 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);
-      });
-    },
-  },
-};
-</script>
-<style scoped>
-tr {
-  cursor: pointer;
-}
-</style>(파일 끝에 줄바꿈 문자 없음)
 
client/views/pages/Manager/approval/approvalRequest.vue (deleted)
--- client/views/pages/Manager/approval/approvalRequest.vue
@@ -1,160 +0,0 @@
-<template>
-  <div class="col-lg-12">
-    <div class="card">
-      <div class="card-body">
-        <h2 class="card-title">결재 요청</h2>
-        <div class="sch-form-wrap">
-          <div class="input-group">
-            <select class="form-select" v-model="request.year" @change="fnChangeCurrentPage(1)">
-              <option value="">연도 전체</option>
-              <option v-for="year in years" :key="year" :value="year">{{ year }}년</option>
-            </select>
-            <select class="form-select" v-model="request.month" @change="fnChangeCurrentPage(1)">
-              <option value="">월 전체</option>
-              <option v-for="month in months" :key="month" :value="month">{{ month }}월</option>
-            </select>
-            <select name="" id="" class="form-select">
-              <option value="" selected>전체</option>
-              <option value="">출장-복명</option>
-              <option value="">출장-품의</option>
-              <option value="">연차</option>
-              <option value="">반차-오전</option>
-              <option value="">반차-오후</option>
-              <option value="">대체휴가</option>
-              <option value="">공가</option>
-              <option value="">병가</option>
-            </select>
-          </div>
-        </div>
-        <div class="tbl-wrap">
-          <table id="myTable" class="tbl data">
-            <colgroup>
-              <col style="width: 13.33%;">
-              <col style="width: 13.33%;">
-              <col style="width: 13.33%;">
-              <col style="width: 30%;">
-              <col style="width: 16.66%;">
-              <col style="width: 13.33%;">
-            </colgroup>
-            <thead>
-              <tr>
-                <th>구분</th>
-                <th>결재구분</th>
-                <th>신청자</th>
-                <th>기간</th>
-                <th>신청일</th>
-                <th>상태</th>
-              </tr>
-            </thead>
-            <tbody>
-              <tr v-for="(item, idx) of lists" :key="idx">
-                <td>{{ item.sanctnIemNm }}</td>
-                <td>{{ item.sanctnSeNm }}</td>
-                <td>{{ item.applcntNm }}</td>
-                <td>{{ $formattedDates(item) }}</td>
-                <td>{{ item.rgsde }}</td>
-                <td>{{ item.confmAtNm }}</td>
-              </tr>
-            </tbody>
-          </table>
-        </div>
-        <Pagenation :search="request" @onChange="fnChangeCurrentPage" />
-        <div class="buttons">
-          <button type="button" class="btn sm sm primary" @click="showOptions = true"> 등록 </button>
-        </div>
-      </div>
-    </div>
-  </div>
-  <div v-if="showOptions" class="popup-overlay">
-    <div class="popup-content">
-      <div class="card">
-        <div class="card-body">
-          <h2 class="card-title">신청종류선택</h2>
-          <div class="buttons">
-            <button class="btn sm hyuga" @click="goToPage('휴가')">휴가신청</button>
-            <button class="btn sm chuljang" @click="goToPage('출장')">출장신청</button>
-          </div>
-        </div>
-      </div>
-      <button class="close-btn" @click="showOptions = false">
-        <CloseCircleFilled />
-      </button>
-    </div>
-  </div>
-</template>
-<script>
-import { SearchOutlined, CloseCircleFilled } from '@ant-design/icons-vue';
-import Pagenation from '../../../component/Pagenation.vue';
-// API
-import { findSanctnsProc } from '../../../../resources/api/sanctns';
-
-export default {
-  components: {
-    SearchOutlined, CloseCircleFilled,
-    Pagenation,
-  },
-
-  data() {
-    return {
-      photoicon: "/client/resources/img/photo_icon.png",
-      showOptions: false,
-
-      years: [],
-      months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
-
-      lists: [],
-      request: {
-        year: '',
-        month: '',
-        register: this.$store.state.userInfo.userId,
-      },
-    };
-  },
-
-  created() {
-    this.generateYears();
-  },
-
-  mounted() {
-    this.findList(); // 목록 조회
-  },
-
-  methods: {
-    generateYears() {
-      const startYear = 2020;
-      const currentYear = new Date().getFullYear();
-
-      for (let year = currentYear; year >= startYear; year--) {
-        this.years.push(year);
-      }
-    },
-
-    // 목록 조회
-    async findList() {
-      const vm = this;
-      try {
-        const response = await findSanctnsProc(vm.request);
-        const result = response.data.data;
-
-        this.lists = result.lists;
-        this.request = result.search;
-      } catch (error) {
-        if (error.response) {
-          alert(error.response.data.message);
-        } else {
-          alert("에러가 발생했습니다.");
-        }
-        console.error(error.message);
-      };
-    },
-
-    // 페이지 이동
-    fnChangeCurrentPage(currentPage) {
-      this.request.currentPage = Number(currentPage);
-      this.$nextTick(() => {
-        this.findList();
-      });
-    },
-  },
-};
-</script>(파일 끝에 줄바꿈 문자 없음)
client/views/pages/Manager/sanctn/ChuljangBokmyeong.vue (Renamed from client/views/pages/Manager/approval/ChuljangBokmyeong.vue)
--- client/views/pages/Manager/approval/ChuljangBokmyeong.vue
+++ client/views/pages/Manager/sanctn/ChuljangBokmyeong.vue
No changes
client/views/pages/Manager/sanctn/ChuljangPumui.vue (Renamed from client/views/pages/Manager/approval/ChuljangPumui.vue)
--- client/views/pages/Manager/approval/ChuljangPumui.vue
+++ client/views/pages/Manager/sanctn/ChuljangPumui.vue
No changes
client/views/pages/Manager/sanctn/Hyuga.vue (Renamed from client/views/pages/Manager/approval/Hyuga.vue)
--- client/views/pages/Manager/approval/Hyuga.vue
+++ client/views/pages/Manager/sanctn/Hyuga.vue
No changes
 
client/views/pages/Manager/sanctn/MyApprovalRequestList.vue (added)
+++ client/views/pages/Manager/sanctn/MyApprovalRequestList.vue
@@ -0,0 +1,184 @@
+<template>
+  <div class="col-lg-12">
+    <div class="card">
+      <div class="card-body">
+        <h2 class="card-title">결재 요청</h2>
+        <div class="sch-form-wrap">
+          <div class="input-group">
+            <select name="yearPicker" id="yearPicker" class="form-select" v-model="searchParams.year" @change="handlePageChange(1)">
+              <option value="">연도 전체</option>
+              <option v-for="year in yearOptions" :key="year" :value="year">{{ year }}년</option>
+            </select>
+            <select name="monthPicker" id="monthPicker" class="form-select" v-model="searchParams.month" @change="handlePageChange(1)">
+              <option value="">월 전체</option>
+              <option v-for="month in monthOptions" :key="month" :value="month">{{ month }}월</option>
+            </select>
+            <select name="sanctnIemPicker" id="sanctnIemPicker" class="form-select" v-model="searchParams.sanctnIem" @change="handlePageChange(1)">
+              <option value="">전체</option>
+              <option v-for="(item, idx) of approvalTypeOptions" :key="idx" :value="item.code">{{ item.codeNm }}</option>
+            </select>
+          </div>
+        </div>
+        <div class="tbl-wrap">
+          <table class="tbl data">
+            <colgroup>
+              <col style="width: 13.33%;">
+              <col style="width: 13.33%;">
+              <col style="width: 13.33%;">
+              <col style="width: 30%;">
+              <col style="width: 16.66%;">
+              <col style="width: 13.33%;">
+            </colgroup>
+            <thead>
+              <tr>
+                <th>구분</th>
+                <th>결재구분</th>
+                <th>신청자</th>
+                <th>기간</th>
+                <th>신청일</th>
+                <th>상태</th>
+              </tr>
+            </thead>
+            <tbody>
+              <template v-if="approvalRequestList.length > 0">
+                <tr v-for="(item, idx) of approvalRequestList" :key="idx" @click="handleDetailNavigation(item.sanctnMbyId)">
+                  <td>{{ item.sanctnIemNm }}</td>
+                  <td>{{ item.sanctnSeNm }}</td>
+                  <td>{{ item.registerNm }}</td>
+                  <td>{{ $formattedDates(item) }}</td>
+                  <td>{{ item.rgsde }}</td>
+                  <td>{{ item.confmAtNm }}</td>
+                </tr>
+              </template>
+              <tr v-else>
+                <td colspan="6">게시물이 존재하지 않습니다.</td>
+              </tr>
+            </tbody>
+          </table>
+        </div>
+        <Pagination :search="searchParams" @onChange="handlePageChange" />
+        <div class="buttons">
+          <button type="button" class="btn sm sm primary" @click="isOptionsModalVisible = true">등록</button>
+        </div>
+      </div>
+    </div>
+  </div>
+  <div v-if="isOptionsModalVisible" class="popup-overlay">
+    <div class="popup-content">
+      <div class="card">
+        <div class="card-body">
+          <h2 class="card-title">신청종류선택</h2>
+          <div class="buttons">
+            <button class="btn sm hyuga" @click="handleRegistrationNavigation('휴가')">휴가신청</button>
+            <button class="btn sm chuljang" @click="handleRegistrationNavigation('출장')">출장신청</button>
+          </div>
+        </div>
+      </div>
+      <button class="close-btn" @click="isOptionsModalVisible = false">
+        <CloseCircleFilled />
+      </button>
+    </div>
+  </div>
+</template>
+<script>
+import { SearchOutlined, CloseCircleFilled } from '@ant-design/icons-vue';
+import Pagination from '../../../component/Pagination.vue';
+// API
+import { findMyApprovalRequestsProc } from '../../../../resources/api/sanctns';
+
+export default {
+  components: {
+    SearchOutlined, CloseCircleFilled,
+    Pagination,
+  },
+
+  data() {
+    return {
+      photoicon: "/client/resources/img/photo_icon.png",
+      isOptionsModalVisible: false,
+
+      yearOptions: [],
+      monthOptions: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
+
+      approvalTypeOptions: [],
+
+      approvalRequestList: [],
+      searchParams: {
+        year: '',
+        month: '',
+        sanctnIem: '',
+        currentUserId: this.$store.state.userInfo.userId, // 변경불가 (고정값)
+      },
+    };
+  },
+
+  async created() {
+    this.generateYearOptions();
+    this.approvalTypeOptions = await this.$sanctnIemCodes(); // 휴가 및 출장 구분 코드 조회
+  },
+
+  mounted() {
+    this.fetchApprovalRequestList(); // 목록 조회
+  },
+
+  methods: {
+    generateYearOptions() {
+      const startYear = 2020;
+      const currentYear = new Date().getFullYear();
+
+      for (let year = currentYear; year >= startYear; year--) {
+        this.yearOptions.push(year);
+      }
+    },
+
+    // 목록 조회
+    async fetchApprovalRequestList() {
+      const vm = this;
+      try {
+        const response = await findMyApprovalRequestsProc(vm.searchParams);
+        const result = response.data.data;
+
+        this.approvalRequestList = result.lists;
+        this.searchParams = result.search;
+      } catch (error) {
+        this.handleError(error);
+      };
+    },
+
+    // 페이지 변경
+    handlePageChange(currentPage) {
+      this.searchParams.currentPage = Number(currentPage);
+      this.$nextTick(() => {
+        this.fetchApprovalRequestList();
+      });
+    },
+
+    // 상세 페이지 이동
+    handleDetailNavigation(id) {
+      const approvalType = id.split('_')[0];
+
+      if (approvalType === "VCATN") {
+        this.$router.push({ name: 'HyugaDetail', query: { id } });
+      } if (approvalType === "BSRP") {
+        this.$router.push({ name: 'ChuljangDetailAll', query: { id } });
+      }
+    },
+
+    // 등록 페이지 이동
+    handleRegistrationNavigation(type) {
+      if (type === "휴가") {
+        this.$router.push({ name: 'hyugaInsert' });
+      } else if (type === "출장") {
+        this.$router.push({ name: 'ChuljangInsert' });
+      }
+    },
+
+    // 에러 처리
+    handleError(error) {
+      const message = error.response?.data?.message || "에러가 발생했습니다.";
+      alert(message);
+      console.error(error.message);
+    },
+  },
+};
+</script>(파일 끝에 줄바꿈 문자 없음)
 
client/views/pages/Manager/sanctn/PendingApprovalList.vue (added)
+++ client/views/pages/Manager/sanctn/PendingApprovalList.vue
@@ -0,0 +1,249 @@
+<template>
+  <div class="col-lg-12">
+    <div class="card">
+      <div class="card-body">
+        <h2 class="card-title">승인 대기 목록</h2>
+        <div class="sch-form-wrap title-wrap">
+          <h3><img :src="sectionIcon" alt="">승인 대기</h3>
+          <div class="input-group">
+            <select class="form-select" v-model="pendingSearchParams.year" @change="handlePageChange(1, 'pending')">
+              <option value="">연도 전체</option>
+              <option v-for="year in yearOptions" :key="year" :value="year">{{ year }}년</option>
+            </select>
+            <select class="form-select" v-model="pendingSearchParams.month" @change="handlePageChange(1, 'pending')">
+              <option value="">월 전체</option>
+              <option v-for="month in monthOptions" :key="month" :value="month">{{ month }}월</option>
+            </select>
+            <div class="sch-input">
+              <input type="text" class="form-control" v-model="pendingSearchParams.searchText" placeholder="검색어를 입력하세요." @keyup.enter="handleSearch('pending')">
+              <button button="button" class="ico-sch" @click="handleSearch('pending')">
+                <SearchOutlined />
+              </button>
+            </div>
+          </div>
+        </div>
+        <div class="tbl-wrap">
+          <table id="myTable" class="tbl data">
+            <colgroup>
+              <col style="width: 13.33%;">
+              <col style="width: 13.33%;">
+              <col style="width: 13.33%;">
+              <col style="width: 30%;">
+              <col style="width: 30%;">
+            </colgroup>
+            <thead>
+              <tr>
+                <th>구분</th>
+                <th>결재구분</th>
+                <th>신청자</th>
+                <th>기간</th>
+                <th>신청일</th>
+              </tr>
+            </thead>
+            <tbody>
+              <tr v-for="(item, idx) in pendingApprovalList" :key="idx" @click="handleDetailNavigation(item.sanctnMbyId)">
+                <td>{{ item.sanctnIemNm }}</td>
+                <td>{{ item.sanctnSeNm }}</td>
+                <td>{{ item.registerNm }}</td>
+                <td>{{ $formattedDates(item) }}</td>
+                <td>{{ item.rgsde }}</td>
+              </tr>
+            </tbody>
+          </table>
+        </div>
+        <Pagenation :search="pendingSearchParams" @onChange="(currentPage) => handlePageChange(currentPage, 'pending')" />
+        <div class="sch-form-wrap title-wrap">
+          <h3><img :src="sectionIcon" alt="">승인 이력</h3>
+          <div class="input-group">
+            <select class="form-select" v-model="historySearchParams.year" @change="handlePageChange(1, 'history')">
+              <option value="">연도 전체</option>
+              <option v-for="year in yearOptions" :key="year" :value="year">{{ year }}년</option>
+            </select>
+            <select class="form-select" v-model="historySearchParams.month" @change="handlePageChange(1, 'history')">
+              <option value="">월 전체</option>
+              <option v-for="month in monthOptions" :key="month" :value="month">{{ month }}월</option>
+            </select>
+            <select name="" id="" class="form-select">
+              <option value="all">상태</option>
+              <option value="">승인</option>
+              <option value="">반려</option>
+            </select>
+            <div class="sch-input">
+              <input type="text" class="form-control" v-model="historySearchParams.searchText" placeholder="검색어를 입력하세요." @keyup.enter="handleSearch('history')">
+              <button button="button" class="ico-sch" @click="handleSearch('history')">
+                <SearchOutlined />
+              </button>
+            </div>
+          </div>
+        </div>
+        <div class="tbl-wrap">
+          <table id="myTable" class="tbl data">
+            <colgroup>
+              <col style="width: 13.33%;">
+              <col style="width: 13.33%;">
+              <col style="width: 13.33%;">
+              <col style="width: 30%;">
+              <col style="width: 16.66%;">
+              <col style="width: 13.33%;">
+            </colgroup>
+            <thead>
+              <tr>
+                <th>구분</th>
+                <th>결재구분</th>
+                <th>신청자</th>
+                <th>기간</th>
+                <th>신청일</th>
+                <th>상태</th>
+              </tr>
+            </thead>
+            <tbody>
+              <tr v-for="(item, index) in approvalHistoryList" :key="index" class="expired" @click="handleDetailNavigation(item.sanctnMbyId)">
+                <td>{{ item.sanctnIemNm }}</td>
+                <td>{{ item.sanctnSeNm }}</td>
+                <td>{{ item.registerNm }}</td>
+                <td>{{ $formattedDates(item) }}</td>
+                <td>{{ item.rgsde }}</td>
+                <td>{{ item.confmAtNm }}</td>
+              </tr>
+            </tbody>
+          </table>
+        </div>
+        <Pagenation :search="historySearchParams" @onChange="(currentPage) => handlePageChange(currentPage, 'history')" />
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+import { SearchOutlined } from '@ant-design/icons-vue';
+import Pagenation from '../../../component/Pagination.vue';
+// API
+import { findPendingApprovalsProc } from '../../../../resources/api/sanctns';
+
+export default {
+  components: {
+    SearchOutlined,
+    Pagenation,
+  },
+
+  data() {
+    return {
+      sectionIcon: "/client/resources/img/h3icon.png",
+
+      yearOptions: [2023, 2024, 2025], // 연도 목록
+      monthOptions: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // 월 목록
+
+      // 승인 대기 목록
+      pendingApprovalList: [],
+      pendingSearchParams: {
+        year: '',
+        month: '',
+        isConfmAt: true, // 변경불가 (고정값)
+        searchText: '',
+        currentUserId: this.$store.state.userInfo.userId, // 변경불가 (고정값)
+      },
+      // 승인 이력 목록
+      approvalHistoryList: [],
+      historySearchParams: {
+        year: '',
+        month: '',
+        isConfmAt: false, // 변경불가 (고정값)
+        searchText: '',
+        currentUserId: this.$store.state.userInfo.userId, // 변경불가 (고정값)
+      },
+    };
+  },
+
+  created() {
+    this.generateYearOptions();
+  },
+
+  mounted() {
+    this.fetchApprovalList('pending'); // 승인 대기 목록
+    this.fetchApprovalList('history'); // 승인 이력 목록
+  },
+
+  methods: {
+    generateYearOptions() {
+      const startYear = 2020;
+      const currentYear = new Date().getFullYear();
+
+      for (let year = currentYear; year >= startYear; year--) {
+        this.yearOptions.push(year);
+      }
+    },
+
+    // 목록 조회
+    async fetchApprovalList(listType) {
+      const vm = this;
+
+      try {
+        let searchParams = {};
+        if (listType === 'pending') {
+          searchParams = vm.pendingSearchParams;
+        } else if (listType === 'history') {
+          searchParams = vm.historySearchParams;
+        }
+
+        const response = await findPendingApprovalsProc(searchParams);
+        const result = response.data.data;
+
+        if (listType === 'pending') {
+          vm.pendingApprovalList = result.lists;
+          vm.pendingSearchParams = result.search;
+        } else if (listType === 'history') {
+          vm.approvalHistoryList = result.lists;
+          vm.historySearchParams = result.search;
+        }
+      } catch (error) {
+        this.handleError(error);
+      };
+    },
+
+    // 검색 실행
+    handleSearch(listType) {
+      if (listType === 'pending') {
+        this.pendingSearchParams.currentPage = 1;
+      } else if (listType === 'history') {
+        this.historySearchParams.currentPage = 1;
+      }
+      this.fetchApprovalList(listType);
+    },
+
+    // 페이지 변경
+    handlePageChange(currentPage, listType) {
+      if (listType === 'pending') {
+        this.pendingSearchParams.currentPage = Number(currentPage);
+      } else if (listType === 'history') {
+        this.historySearchParams.currentPage = Number(currentPage);
+      }
+
+      this.$nextTick(() => {
+        this.fetchApprovalList(listType);
+      });
+    },
+
+    // 상세 페이지 이동
+    handleDetailNavigation(id) {
+      const approvalType = id.split('_')[0];
+
+      if (approvalType === "VCATN") {
+        this.$router.push({ name: 'HyugaDetail', query: { id, type: 'sanctns' } });
+      } if (approvalType === "BSRP") {
+        this.$router.push({ name: 'ChuljangDetailAll', query: { id, type: 'sanctns' } });
+      }
+    },
+
+    // 에러 처리
+    handleError(error) {
+      const message = error.response?.data?.message || "에러가 발생했습니다.";
+      alert(message);
+      console.error(error.message);
+    },
+  },
+};
+</script>
+<style scoped>
+tr {
+  cursor: pointer;
+}
+</style>(파일 끝에 줄바꿈 문자 없음)
Add a comment
List