
+++ client/resources/api/bsrp.js
... | ... | @@ -0,0 +1,26 @@ |
1 | +import apiClient from "./index"; | |
2 | + | |
3 | +// 등록 | |
4 | +export const saveBsrpProc = data => { | |
5 | + return apiClient.post('/bsrp/saveBsrp.json', data); | |
6 | +} | |
7 | + | |
8 | +// 목록 조회 | |
9 | +export const bsrpsProc = data => { | |
10 | + return apiClient.get('/bsrp/bsrps.json', { params: data }); | |
11 | +} | |
12 | + | |
13 | +// 상세 조회 | |
14 | +export const bsrpProc = data => { | |
15 | + return apiClient.get(`/bsrp/${data}/bsrp.json`); | |
16 | +} | |
17 | + | |
18 | +// 수정 | |
19 | +export const updateBsrpProc = data => { | |
20 | + return apiClient.put('/bsrp/updateBsrp.json', data); | |
21 | +} | |
22 | + | |
23 | +// 삭제 | |
24 | +export const deleteBsrpProc = data => { | |
25 | + return apiClient.delete(`/bsrp/${data}/deleteBsrp.json`); | |
26 | +}(파일 끝에 줄바꿈 문자 없음) |
--- client/resources/js/cmmnPlugin.js
+++ client/resources/js/cmmnPlugin.js
... | ... | @@ -86,7 +86,7 @@ |
86 | 86 |
sanctnCodeList: [], // 결재 구분 (결재/대결/전결) |
87 | 87 |
confmCodeList: [], // 상태 코드 (대기/결재대기/승인/반려) |
88 | 88 |
clsfCodeList: [], // 직급 코드 (사원/주임/대리) |
89 |
- rspofcCodeList: [], // 직책 코드 (사원/주임/대리) |
|
89 |
+ rspofcCodeList: [], // 직책 코드 (팀장) |
|
90 | 90 |
}; |
91 | 91 |
|
92 | 92 |
// 휴가 종류 - depth 2 조회 |
--- client/views/component/Popup/CorpCarPopup.vue
+++ client/views/component/Popup/CorpCarPopup.vue
... | ... | @@ -1,105 +1,160 @@ |
1 | 1 |
<template> |
2 |
- <div class="popup-overlay" @click.self="$emit('close')"> |
|
3 |
- <div class="popup-content"> |
|
4 |
- <div class="card"> |
|
5 |
- <div class="card-body"> |
|
6 |
- <h2 class="card-title">법인차량 목록</h2> |
|
7 |
- <div class="sch-form-wrap"> |
|
8 |
- <div class="input-group"> |
|
9 |
- <div class="sch-input"> |
|
10 |
- <input type="text" class="form-control"> |
|
11 |
- <button class="ico-sch"><SearchOutlined /></button> |
|
12 |
- </div> |
|
13 |
- </div> |
|
14 |
- </div> |
|
15 |
- |
|
16 |
- <!-- Table --> |
|
17 |
- <div class="tbl-wrap"> |
|
18 |
- <table id="myTable" class="tbl data"> |
|
19 |
- <!-- 동적으로 <th> 생성 --> |
|
20 |
- <thead> |
|
21 |
- <tr> |
|
22 |
- <th>차량종류 </th> |
|
23 |
- <th>차량번호</th> |
|
24 |
- <th>선택</th> |
|
25 |
- </tr> |
|
26 |
- </thead> |
|
27 |
- <!-- 동적으로 <td> 생성 --> |
|
28 |
- <tbody> |
|
29 |
- <tr v-for="(item, index) in popuplistData" :key="index"> |
|
30 |
- <td>{{ item.category }}</td> |
|
31 |
- <td>{{ item.name }}</td> |
|
32 |
- <td> |
|
33 |
- <button |
|
34 |
- type="button" |
|
35 |
- class="btn sm secondary" |
|
36 |
- @click="selectCar(item)" |
|
37 |
- > |
|
38 |
- 선택 |
|
39 |
- </button> |
|
40 |
- </td> |
|
41 |
- </tr> |
|
42 |
- </tbody> |
|
43 |
- </table> |
|
44 |
- |
|
45 |
- </div> |
|
46 |
- <div class="pagination"> |
|
47 |
- <ul> |
|
48 |
- <!-- 왼쪽 화살표 (이전 페이지) --> |
|
49 |
- <li class="arrow" :class="{ disabled: currentPage === 1 }" |
|
50 |
- @click="changePage(currentPage - 1)"> |
|
51 |
- < |
|
52 |
- </li> |
|
53 |
- |
|
54 |
- <!-- 페이지 번호 --> |
|
55 |
- <li v-for="page in totalPages" :key="page" :class="{ active: currentPage === page }" |
|
56 |
- @click="changePage(page)"> |
|
57 |
- {{ page }} |
|
58 |
- </li> |
|
59 |
- |
|
60 |
- <!-- 오른쪽 화살표 (다음 페이지) --> |
|
61 |
- <li class="arrow" :class="{ disabled: currentPage === totalPages }" |
|
62 |
- @click="changePage(currentPage + 1)"> |
|
63 |
- > |
|
64 |
- </li> |
|
65 |
- </ul> |
|
66 |
- </div> |
|
67 |
- <!-- End Table --> |
|
68 |
- </div> |
|
69 |
- </div> |
|
70 |
- <button @click="$emit('close')" class="close-btn"> |
|
71 |
- <CloseCircleFilled /> |
|
72 |
- </button> |
|
73 |
- </div> |
|
2 |
+ <div class="popup-overlay" @click.self="$emit('close')"> |
|
3 |
+ <div class="popup-content"> |
|
4 |
+ <div class="card"> |
|
5 |
+ <div class="card-body"> |
|
6 |
+ <h2 class="card-title">법인차량 목록</h2> |
|
7 |
+ <div class="sch-form-wrap"> |
|
8 |
+ <div class="input-group"> |
|
9 |
+ <div class="sch-input"> |
|
10 |
+ <input type="text" class="form-control" v-model="request.searchText" @keyup.enter="fnChangeCurrentPage(1)"> |
|
11 |
+ <button class="ico-sch" @click="fnChangeCurrentPage(1)"> |
|
12 |
+ <SearchOutlined /> |
|
13 |
+ </button> |
|
74 | 14 |
</div> |
15 |
+ </div> |
|
16 |
+ </div> |
|
17 |
+ <div class="tbl-wrap"> |
|
18 |
+ <table id="myTable" class="tbl data"> |
|
19 |
+ <thead> |
|
20 |
+ <tr> |
|
21 |
+ <th>차량종류</th> |
|
22 |
+ <th>차량번호</th> |
|
23 |
+ <th>선택</th> |
|
24 |
+ </tr> |
|
25 |
+ </thead> |
|
26 |
+ <tbody> |
|
27 |
+ <tr v-for="(item, idx) in items" :key="idx"> |
|
28 |
+ <td>{{ item.vhcty }}</td> |
|
29 |
+ <td>{{ item.vhcleNo }}</td> |
|
30 |
+ <td> |
|
31 |
+ <button type="button" class="btn sm secondary" @click="selectCar(item)" :disabled="isVhcleSelected(item.vhcleId)">선택</button> |
|
32 |
+ </td> |
|
33 |
+ </tr> |
|
34 |
+ </tbody> |
|
35 |
+ </table> |
|
36 |
+ </div> |
|
37 |
+ <Pagenation :search="request" @onChange="fnChangeCurrentPage" /> |
|
38 |
+ </div> |
|
39 |
+ </div> |
|
40 |
+ <button @click="$emit('close')" class="close-btn"> |
|
41 |
+ <CloseCircleFilled /> |
|
42 |
+ </button> |
|
43 |
+ </div> |
|
44 |
+ </div> |
|
75 | 45 |
</template> |
76 | 46 |
<script> |
77 | 47 |
import { SearchOutlined, CloseCircleFilled } from '@ant-design/icons-vue'; |
48 |
+import Pagenation from '../Pagenation.vue'; |
|
49 |
+// API |
|
50 |
+import { findAllAsSetVhcle } from '../../../resources/api/asset' |
|
78 | 51 |
|
79 | 52 |
export default { |
80 |
- data() { |
|
81 |
- return { |
|
82 |
- popuplistData: [ |
|
83 |
- { |
|
84 |
- category: 'ooo', |
|
85 |
- name: 'ooo허 oooo', |
|
86 |
- }, |
|
87 |
- ], |
|
88 |
- } |
|
53 |
+ components: { |
|
54 |
+ SearchOutlined, CloseCircleFilled, |
|
55 |
+ Pagenation |
|
56 |
+ }, |
|
57 |
+ |
|
58 |
+ props: { |
|
59 |
+ editData: { |
|
60 |
+ type: Object, |
|
61 |
+ default: () => ({}), |
|
89 | 62 |
}, |
90 |
- components: { |
|
91 |
- SearchOutlined, CloseCircleFilled |
|
63 |
+ lists: { |
|
64 |
+ type: Array, |
|
65 |
+ default: () => [], |
|
66 |
+ } |
|
92 | 67 |
}, |
68 |
+ |
|
69 |
+ data() { |
|
70 |
+ return { |
|
71 |
+ items: [], |
|
72 |
+ |
|
73 |
+ request: { |
|
74 |
+ searchType: "vm", // 검색조건 ( all: 전체, vm: 차량명, vn: 차량 번호 ) |
|
75 |
+ searchText: null, // 검색어 |
|
76 |
+ useAt: null, // 사용여부 ( N: 미사용, Y: 사용 ) |
|
77 |
+ selectedVhcleIds: null, // 선택된 차량 Id |
|
78 |
+ bgnde: null, // 시작 일 |
|
79 |
+ beginHour: null, // 시작 시 |
|
80 |
+ beginMnt: null, // 시작 분 |
|
81 |
+ endde: null, // 종료 일 |
|
82 |
+ endHour: null, // 종료 시 |
|
83 |
+ endMnt: null, // 종료 분 |
|
84 |
+ }, |
|
85 |
+ } |
|
86 |
+ }, |
|
87 |
+ |
|
88 |
+ computed: { |
|
89 |
+ selectedVhcleIds() { |
|
90 |
+ return new Set(this.lists.map(item => item.vhcleId)); |
|
91 |
+ } |
|
92 |
+ }, |
|
93 |
+ |
|
94 |
+ watch: { |
|
95 |
+ editData: { |
|
96 |
+ handler(newVal) { |
|
97 |
+ if (newVal) { |
|
98 |
+ this.request.bgnde = newVal.bgnde; |
|
99 |
+ this.request.beginHour = newVal.beginHour; |
|
100 |
+ this.request.beginMnt = newVal.beginMnt; |
|
101 |
+ this.request.endde = newVal.endde; |
|
102 |
+ this.request.endHour = newVal.endHour; |
|
103 |
+ this.request.endMnt = newVal.endMnt; |
|
104 |
+ |
|
105 |
+ this.findDatas(); |
|
106 |
+ } |
|
107 |
+ }, |
|
108 |
+ deep: true, |
|
109 |
+ immediate: true |
|
110 |
+ } |
|
111 |
+ }, |
|
112 |
+ |
|
113 |
+ mounted() { |
|
114 |
+ this.findDatas(); |
|
115 |
+ }, |
|
116 |
+ |
|
93 | 117 |
methods: { |
118 |
+ // 목록 조회 |
|
119 |
+ async findDatas() { |
|
120 |
+ try { |
|
121 |
+ const response = await findAllAsSetVhcle(this.request); |
|
122 |
+ const result = response.data.data; |
|
123 |
+ |
|
124 |
+ this.items = result.vhcle; |
|
125 |
+ this.request = result.search; |
|
126 |
+ } catch (error) { |
|
127 |
+ if (error.response) { |
|
128 |
+ alert(error.response.data.message); |
|
129 |
+ } else { |
|
130 |
+ alert("에러가 발생했습니다."); |
|
131 |
+ } |
|
132 |
+ console.error(error.message); |
|
133 |
+ } |
|
134 |
+ }, |
|
135 |
+ |
|
136 |
+ // 차량 검증 |
|
137 |
+ isVhcleSelected(vhcleId) { |
|
138 |
+ return this.selectedVhcleIds.has(vhcleId); |
|
139 |
+ }, |
|
140 |
+ |
|
141 |
+ // 차량 선택 |
|
94 | 142 |
selectCar(item) { |
95 |
- this.$emit('select', item); // 부모에게 데이터 전달 |
|
96 |
- }, |
|
97 |
- |
|
143 |
+ this.$emit('onSelected', item); |
|
144 |
+ }, |
|
145 |
+ |
|
146 |
+ // 페이지 이동 |
|
147 |
+ fnChangeCurrentPage(currentPage) { |
|
148 |
+ this.request.currentPage = Number(currentPage); |
|
149 |
+ this.$nextTick(() => { |
|
150 |
+ this.findDatas(); |
|
151 |
+ }); |
|
152 |
+ }, |
|
98 | 153 |
} |
99 | 154 |
} |
100 | 155 |
</script> |
101 | 156 |
<style scoped> |
102 | 157 |
.popup-content { |
103 |
- width: 50%; |
|
158 |
+ width: 50%; |
|
104 | 159 |
} |
105 | 160 |
</style>(파일 끝에 줄바꿈 문자 없음) |
--- client/views/component/Popup/CorpCardPopup.vue
+++ client/views/component/Popup/CorpCardPopup.vue
... | ... | @@ -1,102 +1,163 @@ |
1 | 1 |
<template> |
2 |
- <div class="popup-overlay" @click.self="$emit('close')"> |
|
3 |
- <div class="popup-content"> |
|
4 |
- <div class="card"> |
|
5 |
- <div class="card-body"> |
|
6 |
- <h2 class="card-title">법인차량 목록</h2> |
|
7 |
- <div class="sch-form-wrap"> |
|
8 |
- <div class="input-group"> |
|
9 |
- <div class="sch-input"> |
|
10 |
- <input type="text" class="form-control"> |
|
11 |
- <button class="ico-sch"><SearchOutlined /></button> |
|
12 |
- </div> |
|
13 |
- </div> |
|
14 |
- </div> |
|
15 |
- |
|
16 |
- <!-- Table --> |
|
17 |
- <div class="tbl-wrap"> |
|
18 |
- <table id="myTable" class="tbl data"> |
|
19 |
- <!-- 동적으로 <th> 생성 --> |
|
20 |
- <thead> |
|
21 |
- <tr> |
|
22 |
- <th>카드명 </th> |
|
23 |
- <th>선택</th> |
|
24 |
- </tr> |
|
25 |
- </thead> |
|
26 |
- <!-- 동적으로 <td> 생성 --> |
|
27 |
- <tbody> |
|
28 |
- <tr v-for="(item, index) in popuplistData" :key="index"> |
|
29 |
- <td>{{ item.name }}</td> |
|
30 |
- <td> |
|
31 |
- <button |
|
32 |
- type="button" |
|
33 |
- class="btn sm secondary" |
|
34 |
- @click="selectCard(item)" |
|
35 |
- > |
|
36 |
- 선택 |
|
37 |
- </button> |
|
38 |
- </td> |
|
39 |
- </tr> |
|
40 |
- </tbody> |
|
41 |
- </table> |
|
42 |
- |
|
43 |
- </div> |
|
44 |
- <div class="pagination"> |
|
45 |
- <ul> |
|
46 |
- <!-- 왼쪽 화살표 (이전 페이지) --> |
|
47 |
- <li class="arrow" :class="{ disabled: currentPage === 1 }" |
|
48 |
- @click="changePage(currentPage - 1)"> |
|
49 |
- < |
|
50 |
- </li> |
|
51 |
- |
|
52 |
- <!-- 페이지 번호 --> |
|
53 |
- <li v-for="page in totalPages" :key="page" :class="{ active: currentPage === page }" |
|
54 |
- @click="changePage(page)"> |
|
55 |
- {{ page }} |
|
56 |
- </li> |
|
57 |
- |
|
58 |
- <!-- 오른쪽 화살표 (다음 페이지) --> |
|
59 |
- <li class="arrow" :class="{ disabled: currentPage === totalPages }" |
|
60 |
- @click="changePage(currentPage + 1)"> |
|
61 |
- > |
|
62 |
- </li> |
|
63 |
- </ul> |
|
64 |
- </div> |
|
65 |
- <!-- End Table --> |
|
66 |
- </div> |
|
67 |
- </div> |
|
68 |
- <button @click="$emit('close')" class="close-btn"> |
|
69 |
- <CloseCircleFilled /> |
|
70 |
- </button> |
|
71 |
- </div> |
|
2 |
+ <div class="popup-overlay" @click.self="$emit('close')"> |
|
3 |
+ <div class="popup-content"> |
|
4 |
+ <div class="card"> |
|
5 |
+ <div class="card-body"> |
|
6 |
+ <h2 class="card-title">법인카드 목록</h2> |
|
7 |
+ <div class="sch-form-wrap"> |
|
8 |
+ <div class="input-group"> |
|
9 |
+ <div class="sch-input"> |
|
10 |
+ <input type="text" class="form-control" v-model="request.searchText" @keyup.enter="fnChangeCurrentPage(1)"> |
|
11 |
+ <button class="ico-sch" @click="fnChangeCurrentPage(1)"> |
|
12 |
+ <SearchOutlined /> |
|
13 |
+ </button> |
|
72 | 14 |
</div> |
15 |
+ </div> |
|
16 |
+ </div> |
|
17 |
+ <div class="tbl-wrap"> |
|
18 |
+ <table id="myTable" class="tbl data"> |
|
19 |
+ <thead> |
|
20 |
+ <tr> |
|
21 |
+ <th>카드명</th> |
|
22 |
+ <th>선택</th> |
|
23 |
+ </tr> |
|
24 |
+ </thead> |
|
25 |
+ <tbody> |
|
26 |
+ <tr v-for="(item, idx) in items" :key="idx"> |
|
27 |
+ <td>{{ item.cardNm }}</td> |
|
28 |
+ <td> |
|
29 |
+ <button type="button" class="btn sm secondary" @click="selectCard(item)" :disabled="isSelected(item.cardId)">선택</button> |
|
30 |
+ </td> |
|
31 |
+ </tr> |
|
32 |
+ </tbody> |
|
33 |
+ </table> |
|
34 |
+ </div> |
|
35 |
+ <Pagenation :search="request" @onChange="fnChangeCurrentPage" /> |
|
36 |
+ </div> |
|
37 |
+ </div> |
|
38 |
+ <button @click="$emit('close')" class="close-btn"> |
|
39 |
+ <CloseCircleFilled /> |
|
40 |
+ </button> |
|
41 |
+ </div> |
|
42 |
+ </div> |
|
73 | 43 |
</template> |
74 | 44 |
<script> |
75 | 45 |
import { SearchOutlined, CloseCircleFilled } from '@ant-design/icons-vue'; |
46 |
+import Pagenation from '../Pagenation.vue'; |
|
47 |
+// API |
|
48 |
+import { findAllAsSetCard } from '../../../resources/api/asset' |
|
76 | 49 |
|
77 | 50 |
export default { |
78 |
- data() { |
|
79 |
- return { |
|
80 |
- popuplistData: [ |
|
81 |
- { |
|
82 |
- name: '법인카드1', |
|
83 |
- }, |
|
84 |
- ], |
|
85 |
- } |
|
51 |
+ components: { |
|
52 |
+ SearchOutlined, CloseCircleFilled, |
|
53 |
+ Pagenation, |
|
54 |
+ }, |
|
55 |
+ |
|
56 |
+ props: { |
|
57 |
+ editData: { |
|
58 |
+ type: Object, |
|
59 |
+ default: () => ({}), |
|
86 | 60 |
}, |
87 |
- components: { |
|
88 |
- SearchOutlined, CloseCircleFilled |
|
61 |
+ lists: { |
|
62 |
+ type: Array, |
|
63 |
+ default: () => [], |
|
64 |
+ } |
|
89 | 65 |
}, |
66 |
+ |
|
67 |
+ data() { |
|
68 |
+ return { |
|
69 |
+ items: [], |
|
70 |
+ |
|
71 |
+ request: { |
|
72 |
+ searchType: "vm", // 검색조건 ( all: 전체, vm: 차량명, vn: 차량 번호 ) |
|
73 |
+ searchText: null, // 검색어 |
|
74 |
+ useAt: null, // 사용여부 ( N: 미사용, Y: 사용 ) |
|
75 |
+ selectedCardIds: null, // 선택된 카드 Id |
|
76 |
+ bgnde: null, // 시작 일 |
|
77 |
+ beginHour: null, // 시작 시 |
|
78 |
+ beginMnt: null, // 시작 분 |
|
79 |
+ endde: null, // 종료 일 |
|
80 |
+ endHour: null, // 종료 시 |
|
81 |
+ endMnt: null, // 종료 분 |
|
82 |
+ }, |
|
83 |
+ } |
|
84 |
+ }, |
|
85 |
+ |
|
86 |
+ computed: { |
|
87 |
+ selectedIds() { |
|
88 |
+ return new Set(this.lists.map(item => item.cardId)); |
|
89 |
+ } |
|
90 |
+ }, |
|
91 |
+ |
|
92 |
+ watch: { |
|
93 |
+ editData: { |
|
94 |
+ handler(newVal) { |
|
95 |
+ if (newVal) { |
|
96 |
+ this.request.bgnde = newVal.bgnde; |
|
97 |
+ this.request.beginHour = newVal.beginHour; |
|
98 |
+ this.request.beginMnt = newVal.beginMnt; |
|
99 |
+ this.request.endde = newVal.endde; |
|
100 |
+ this.request.endHour = newVal.endHour; |
|
101 |
+ this.request.endMnt = newVal.endMnt; |
|
102 |
+ |
|
103 |
+ this.findDatas(); |
|
104 |
+ } |
|
105 |
+ }, |
|
106 |
+ deep: true, |
|
107 |
+ immediate: true |
|
108 |
+ } |
|
109 |
+ }, |
|
110 |
+ |
|
111 |
+ mounted() { |
|
112 |
+ this.findDatas(); |
|
113 |
+ }, |
|
114 |
+ |
|
90 | 115 |
methods: { |
116 |
+ // 목록 조회 |
|
117 |
+ async findDatas() { |
|
118 |
+ try { |
|
119 |
+ const response = await findAllAsSetCard(this.request); |
|
120 |
+ const result = response.data.data; |
|
121 |
+ |
|
122 |
+ this.items = result.card; |
|
123 |
+ this.request = result.search; |
|
124 |
+ } catch (error) { |
|
125 |
+ if (error.response) { |
|
126 |
+ alert(error.response.data.message); |
|
127 |
+ } else { |
|
128 |
+ alert("에러가 발생했습니다."); |
|
129 |
+ } |
|
130 |
+ console.error(error.message); |
|
131 |
+ } |
|
132 |
+ }, |
|
133 |
+ |
|
134 |
+ // 카드 검증 |
|
135 |
+ isSelected(id) { |
|
136 |
+ return this.selectedIds.has(id); |
|
137 |
+ }, |
|
138 |
+ |
|
139 |
+ // 카드 선택 |
|
91 | 140 |
selectCard(item) { |
92 |
- this.$emit('select', item); // 부모에게 데이터 전달 |
|
93 |
- }, |
|
94 |
- |
|
141 |
+ this.$emit('onSelected', item); |
|
142 |
+ }, |
|
143 |
+ |
|
144 |
+ // 페이지 이동 |
|
145 |
+ fnChangeCurrentPage(currentPage) { |
|
146 |
+ this.request.currentPage = Number(currentPage); |
|
147 |
+ this.$nextTick(() => { |
|
148 |
+ this.findDatas(); |
|
149 |
+ }); |
|
150 |
+ }, |
|
95 | 151 |
} |
96 | 152 |
} |
97 | 153 |
</script> |
98 | 154 |
<style scoped> |
99 | 155 |
.popup-content { |
100 |
- width: 50%; |
|
156 |
+ width: 50%; |
|
157 |
+} |
|
158 |
+ |
|
159 |
+.btn:disabled { |
|
160 |
+ opacity: 0.5; |
|
161 |
+ cursor: not-allowed; |
|
101 | 162 |
} |
102 | 163 |
</style>(파일 끝에 줄바꿈 문자 없음) |
--- client/views/component/Popup/HrPopup.vue
+++ client/views/component/Popup/HrPopup.vue
... | ... | @@ -27,9 +27,9 @@ |
27 | 27 |
</thead> |
28 | 28 |
<tbody> |
29 | 29 |
<tr v-for="(item, idx) in users" :key="idx"> |
30 |
- <td>{{ getCodeName(item.clsf, 'clsfCodeList') }}</td> |
|
31 |
- <td>{{ getCodeName(item.rspofc, 'rspofcCodeList') }}</td> |
|
32 |
- <td>{{ item.userDeptInfo.deptVO.deptNm }}</td> |
|
30 |
+ <td>{{ item.clsfNm }}</td> |
|
31 |
+ <td>{{ item.rspofcNm }}</td> |
|
32 |
+ <td>{{ item.deptNm }}</td> |
|
33 | 33 |
<td>{{ item.userNm }}</td> |
34 | 34 |
<td> |
35 | 35 |
<button type="button" class="btn sm secondary" @click="selectPerson(item)" :disabled="isUserSelected(item.userId)">선택</button> |
... | ... | @@ -60,7 +60,7 @@ |
60 | 60 |
}, |
61 | 61 |
|
62 | 62 |
props: { |
63 |
- sanctns: { |
|
63 |
+ lists: { |
|
64 | 64 |
type: Array, |
65 | 65 |
default: () => [], |
66 | 66 |
} |
... | ... | @@ -76,23 +76,22 @@ |
76 | 76 |
searchSttus: 1, // 회원상태 승인 고정 |
77 | 77 |
searchUseAt: 'Y', // 사용여부 사용 고정 |
78 | 78 |
}, |
79 |
- |
|
80 |
- cmmnCodes: {}, |
|
81 | 79 |
} |
82 | 80 |
}, |
83 | 81 |
|
84 | 82 |
computed: { |
85 | 83 |
selectedUserIds() { |
86 |
- return new Set(this.sanctns.map(sanctn => sanctn.confmerId)); |
|
84 |
+ return new Set(this.lists.map(sanctn => sanctn.confmerId)); |
|
87 | 85 |
} |
88 | 86 |
}, |
89 | 87 |
|
90 |
- async created() { |
|
91 |
- this.cmmnCodes = await this.$defaultCodes(); |
|
92 |
- }, |
|
88 |
+ watch: {}, |
|
89 |
+ |
|
90 |
+ created() { }, |
|
93 | 91 |
|
94 | 92 |
mounted() { |
95 | 93 |
this.findDatas(); // 목록 조회 |
94 |
+ |
|
96 | 95 |
}, |
97 | 96 |
|
98 | 97 |
methods: { |
... | ... | @@ -114,13 +113,6 @@ |
114 | 113 |
} |
115 | 114 |
}, |
116 | 115 |
|
117 |
- // 공통코드명 조회 |
|
118 |
- getCodeName(code, codeListName) { |
|
119 |
- if (!code || !this.cmmnCodes[codeListName]) return ''; |
|
120 |
- const codeItem = this.cmmnCodes[codeListName].find(item => item.code === code); |
|
121 |
- return codeItem ? codeItem.codeNm : null; |
|
122 |
- }, |
|
123 |
- |
|
124 | 116 |
// 사용자 검증 |
125 | 117 |
isUserSelected(userId) { |
126 | 118 |
return this.selectedUserIds.has(userId); |
... | ... | @@ -128,16 +120,7 @@ |
128 | 120 |
|
129 | 121 |
// 승인자 선택 |
130 | 122 |
selectPerson(item) { |
131 |
- const data = { |
|
132 |
- confmerId: item.userId, |
|
133 |
- clsf: item.clsf, |
|
134 |
- sanctnOrdr: this.sanctns.length + 1, |
|
135 |
- sanctnSe: this.cmmnCodes.sanctnCodeList[0].code, |
|
136 |
- |
|
137 |
- clsfNm: this.getCodeName(item.clsf, 'clsfCodeList'), |
|
138 |
- userNm: item.userNm, |
|
139 |
- }; |
|
140 |
- this.$emit('onSelected', data); |
|
123 |
+ this.$emit('onSelected', item); |
|
141 | 124 |
}, |
142 | 125 |
|
143 | 126 |
// 페이지 이동 |
--- client/views/component/Sanctn/SanctnList.vue
+++ client/views/component/Sanctn/SanctnFormList.vue
... | ... | @@ -1,6 +1,6 @@ |
1 | 1 |
<template> |
2 | 2 |
<div> |
3 |
- <div v-for="(sanctns, idx) of sanctns" :key="sanctns.sanctnId || idx" class="draggable-item-wrapper"> |
|
3 |
+ <div v-for="(lists, idx) of lists" :key="lists.sanctnId || idx" class="draggable-item-wrapper"> |
|
4 | 4 |
<div class="drop-zone" @dragover.prevent="handleDragEnter($event, idx)" @dragenter.prevent="handleDragEnter($event, idx)" @dragleave="handleDragLeave($event, idx)" @drop.prevent="handleDrop($event, idx)" :class="{ |
5 | 5 |
'drop-active': dropTarget === idx, |
6 | 6 |
'drop-visible': draggedIndex !== null && shouldShowDropZone(idx), |
... | ... | @@ -9,19 +9,19 @@ |
9 | 9 |
<div class="drop-indicator">여기에 놓기</div> |
10 | 10 |
</div> |
11 | 11 |
<div class="d-flex addapproval draggable-item" draggable="true" @dragstart="handleDragStart(idx, $event)" @dragend="handleDragEnd" :class="{ 'being-dragged': draggedIndex === idx }"> |
12 |
- <select class="form-select" v-model="sanctns.sanctnSe" style="width: 110px;" @mousedown.stop> |
|
12 |
+ <select class="form-select" v-model="lists.sanctnSe" style="width: 110px;" @mousedown.stop> |
|
13 | 13 |
<option v-for="(item, idx) of cmmnCodes.sanctnCodeList" :key="idx" :value="item.code"> {{ item.codeNm }} </option> |
14 | 14 |
</select> |
15 | 15 |
<div class="d-flex align-items-center border-x"> |
16 |
- <p>{{ sanctns.userNm }} {{ formatClsf(sanctns.clsfNm) }} ({{ sanctns.sanctnOrdr }})</p> |
|
16 |
+ <p>{{ lists.confmerNm }} {{ lists.clsfNm }} ({{ lists.sanctnOrdr }})</p> |
|
17 | 17 |
<button type="button" @click="$emit('delSanctn', idx)" @mousedown.stop> |
18 | 18 |
<CloseOutlined /> |
19 | 19 |
</button> |
20 | 20 |
</div> |
21 | 21 |
</div> |
22 | 22 |
</div> |
23 |
- <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="{ |
|
24 |
- 'drop-active': dropTarget === sanctns.length, |
|
23 |
+ <div class="drop-zone" @dragover.prevent="handleDragEnter($event, lists.length)" @dragenter.prevent="handleDragEnter($event, lists.length)" @dragleave="handleDragLeave($event, lists.length)" @drop.prevent="handleDrop($event, lists.length)" :class="{ |
|
24 |
+ 'drop-active': dropTarget === lists.length, |
|
25 | 25 |
'drop-visible': draggedIndex !== null && shouldShowLastDropZone(), |
26 | 26 |
'drop-hidden': draggedIndex !== null && !shouldShowLastDropZone() |
27 | 27 |
}"> |
... | ... | @@ -38,7 +38,7 @@ |
38 | 38 |
components: { CloseOutlined }, |
39 | 39 |
|
40 | 40 |
props: { |
41 |
- sanctns: { |
|
41 |
+ lists: { |
|
42 | 42 |
type: Array, |
43 | 43 |
default: () => [], |
44 | 44 |
} |
... | ... | @@ -57,11 +57,6 @@ |
57 | 57 |
}, |
58 | 58 |
|
59 | 59 |
methods: { |
60 |
- formatClsf(code) { |
|
61 |
- const clsfCode = this.cmmnCodes?.clsfCodeList?.find(item => item.code === code); |
|
62 |
- return clsfCode?.codeNm || code; |
|
63 |
- }, |
|
64 |
- |
|
65 | 60 |
shouldShowDropZone(index) { |
66 | 61 |
if (this.draggedIndex === null) return true; |
67 | 62 |
if (index === this.draggedIndex || index === this.draggedIndex + 1) return false; |
... | ... | @@ -70,7 +65,7 @@ |
70 | 65 |
|
71 | 66 |
shouldShowLastDropZone() { |
72 | 67 |
if (this.draggedIndex === null) return true; |
73 |
- return this.draggedIndex !== this.sanctns.length - 1; |
|
68 |
+ return this.draggedIndex !== this.lists.length - 1; |
|
74 | 69 |
}, |
75 | 70 |
|
76 | 71 |
handleDragStart(index, event) { |
... | ... | @@ -105,7 +100,7 @@ |
105 | 100 |
finalDropIndex = dropIndex - 1; |
106 | 101 |
} |
107 | 102 |
|
108 |
- const newSanctns = [...this.sanctns]; |
|
103 |
+ const newSanctns = [...this.lists]; |
|
109 | 104 |
const draggedItem = newSanctns.splice(this.draggedIndex, 1)[0]; |
110 | 105 |
newSanctns.splice(finalDropIndex, 0, draggedItem); |
111 | 106 |
|
... | ... | @@ -113,7 +108,7 @@ |
113 | 108 |
item.sanctnOrdr = index + 1; |
114 | 109 |
}); |
115 | 110 |
|
116 |
- this.$emit('update:sanctns', newSanctns); |
|
111 |
+ this.$emit('update:lists', newSanctns); |
|
117 | 112 |
} |
118 | 113 |
|
119 | 114 |
this.dropTarget = null; |
+++ client/views/component/Sanctn/SanctnViewList.vue
... | ... | @@ -0,0 +1,36 @@ |
1 | +<template> | |
2 | + <div class="approval-box tbl-wrap tbl2"> | |
3 | + <table class="tbl data"> | |
4 | + <tbody> | |
5 | + <tr class="thead"> | |
6 | + <td rowspan="2" class="th">승인자</td> | |
7 | + <td v-for="(item, idx) of sanctns" :key="idx">{{ item.clsfNm }}</td> | |
8 | + </tr> | |
9 | + <tr> | |
10 | + <td v-for="(item, idx) of sanctns" :key="idx"> | |
11 | + <template v-if="item.confmAt === 'A'"> | |
12 | + <p v-if="item.sanctnSe === 'sanctn_agncy'">(대결)</p> | |
13 | + <p class="name">{{ item.confmerNm }}</p> | |
14 | + <small class="date">{{ new Date(item.sanctnDe).toISOString().split('T')[0] }}</small> | |
15 | + </template> | |
16 | + <template v-else-if="item.confmAt === 'R'"> | |
17 | + <p class="name">반려</p> | |
18 | + </template> | |
19 | + </td> | |
20 | + </tr> | |
21 | + </tbody> | |
22 | + </table> | |
23 | + </div> | |
24 | +</template> | |
25 | +<script> | |
26 | +export default { | |
27 | + name: 'SanctnViewList', | |
28 | + | |
29 | + props: { | |
30 | + sanctns: { | |
31 | + type: Array, | |
32 | + default: () => [], | |
33 | + } | |
34 | + }, | |
35 | +} | |
36 | +</script>(파일 끝에 줄바꿈 문자 없음) |
--- client/views/pages/AppRouter.js
+++ client/views/pages/AppRouter.js
... | ... | @@ -5,11 +5,6 @@ |
5 | 5 |
import Join from '../pages/User/Join.vue'; |
6 | 6 |
import MyPage from '../pages/User/MyPage.vue'; |
7 | 7 |
|
8 |
-// import Main2 from '../pages/main/Main2.vue'; |
|
9 |
- |
|
10 |
-// import HomeView from "../pages/HomeView.vue"; |
|
11 |
-// import AboutView from "../pages/AboutView.vue"; |
|
12 |
- |
|
13 | 8 |
// 직원 |
14 | 9 |
import ChuljangList from '../pages/Employee/ChuljangList.vue'; |
15 | 10 |
import HyugaList from '../pages/Employee/HyugaList.vue'; |
... | ... | @@ -23,19 +18,18 @@ |
23 | 18 |
import Hyuga from '../pages/Manager/approval/Hyuga.vue'; |
24 | 19 |
|
25 | 20 |
//근태관리 |
26 |
-import ChuljangPumuiDetail from '../pages/Manager/attendance/ChuljangPumuiDetail.vue'; |
|
27 | 21 |
import ChuljangBokmyeongDetail from '../pages/Manager/attendance/ChuljangBokmyeongDetail.vue'; |
28 |
-import ChuljangDetailAll from '../pages/Manager/attendance/ChuljangDetailAll.vue'; |
|
29 |
-import attendance from '../pages/Manager/attendance/attendance.vue'; |
|
22 |
+import ChuljangDetailAll from './Manager/attendance/ChuljangDetailAll.vue'; |
|
23 |
+import ChuljangStatue from '../pages/Manager/attendance/ChuljangStatue.vue'; |
|
24 |
+import ChuljangInsert from '../pages/Manager/attendance/ChuljangInsert.vue'; |
|
30 | 25 |
import myAttendance from '../pages/Manager/attendance/myAttendance.vue'; |
31 | 26 |
import buseoAttendance from '../pages/Manager/attendance/buseoAttendance.vue'; |
27 |
+import attendance from '../pages/Manager/attendance/attendance.vue'; |
|
32 | 28 |
import AttendanceDetail from '../pages/Manager/attendance/AttendanceDetail.vue'; |
33 | 29 |
import hyugaStatue from '../pages/Manager/attendance/hyugaStatue.vue'; |
34 | 30 |
import HyugaInsert from '../pages/Manager/attendance/HyugaInsert.vue'; |
35 | 31 |
import HyugaDetail from '../pages/Manager/attendance/HyugaDetail.vue'; |
36 | 32 |
import BokmyeongInsert from '../pages/Manager/attendance/BokmyeongInsert.vue'; |
37 |
-import ChuljangStatue from '../pages/Manager/attendance/ChuljangStatue.vue'; |
|
38 |
-import ChuljangInsert from '../pages/Manager/attendance/ChuljangInsert.vue'; |
|
39 | 33 |
//업무관리 |
40 | 34 |
import task from '../pages/Manager/task/task.vue'; |
41 | 35 |
import projectStatue from '../pages/Manager/task/projectStatue.vue'; |
... | ... | @@ -75,133 +69,129 @@ |
75 | 69 |
import commonCodeDetail from '../pages/Manager/system/commonCodeDetail.vue'; |
76 | 70 |
|
77 | 71 |
const routes = [ |
78 |
- /* 메인화면 */ |
|
79 |
- { path: '/', name: '/', component: Main}, |
|
80 |
- { path: '/login.page', name: 'Login', component: Login}, |
|
81 |
- { path: '/join.page', name: 'Join', component: Join}, |
|
82 |
- { path: '/MyPage.page', name: 'MyPage', component: MyPage}, |
|
83 |
- |
|
84 |
- { path: '/ChuljangList.page', name: 'ChuljangList', component: ChuljangList }, |
|
85 |
- { path: '/HyugaList.page', name: 'HyugaList', component: HyugaList }, |
|
86 |
- |
|
87 |
- { path: '/HyugaOk.page', name: 'HyugaOk', component: HyugaOk }, |
|
88 |
- |
|
89 |
- { path: '/approval-management.page', name: 'approval', component: approval, redirect: '/approval-management.page/approvalRequest.page', |
|
72 |
+ /* 메인화면 */ |
|
73 |
+ { path: '/', name: '/', component: Main }, |
|
74 |
+ { path: '/login.page', name: 'Login', component: Login }, |
|
75 |
+ { path: '/join.page', name: 'Join', component: Join }, |
|
76 |
+ { path: '/MyPage.page', name: 'MyPage', component: MyPage }, |
|
77 |
+ { path: '/ChuljangList.page', name: 'ChuljangList', component: ChuljangList }, |
|
78 |
+ { path: '/HyugaList.page', name: 'HyugaList', component: HyugaList }, |
|
79 |
+ { path: '/HyugaOk.page', name: 'HyugaOk', component: HyugaOk }, |
|
80 |
+ { |
|
81 |
+ path: '/approval-management.page', name: 'approval', component: approval, redirect: '/approval-management.page/approvalRequest.page', |
|
82 |
+ children: [ |
|
83 |
+ { |
|
84 |
+ path: 'approvalRequest.page', // => /approval-management.page/list |
|
85 |
+ name: 'approvalRequest', |
|
86 |
+ component: approvalRequest, |
|
90 | 87 |
children: [ |
91 |
- { |
|
92 |
- path: 'approvalRequest.page', // => /approval-management.page/list |
|
93 |
- name: 'approvalRequest', |
|
94 |
- component: approvalRequest, |
|
95 |
- children: [ |
|
96 |
- |
|
97 |
- ] |
|
98 |
- }, |
|
99 |
- { |
|
100 |
- path: 'approvalList.page', // => /approval-management.page/detail/123 |
|
101 |
- name: 'approvalList', |
|
102 |
- component: approvalList, |
|
103 |
- children: [ |
|
104 |
- ] |
|
105 |
- }, |
|
106 |
- { path: 'ChuljangPumui.page', name: 'ChuljangPumui', component: ChuljangPumui }, |
|
107 |
- { path: 'ChuljangBokmyeong.page', name: 'ChuljangBokmyeong', component: ChuljangBokmyeong }, |
|
108 |
- { path: 'Hyuga.page', name: 'Hyuga', component: Hyuga }, |
|
109 |
- |
|
110 |
- |
|
111 |
- |
|
112 |
- ] |
|
113 |
- }, //결재관리 |
|
114 |
- { path: '/attendance-management.page', name: 'attendance', component: attendance, redirect: '/attendance-management.page/myAttendance.page', |
|
115 |
- children: [ |
|
116 |
- { path: 'myAttendance.page', name: 'myAttendance', component: myAttendance }, |
|
117 |
- { path: 'buseoAttendance.page', name: 'buseoAttendance', component: buseoAttendance }, |
|
118 |
- { path: 'AttendanceDetail.page', name: 'AttendanceDetail', component: AttendanceDetail }, |
|
119 |
- { path: 'hyugaStatue.page', name: 'hyugaStatue', component: hyugaStatue }, |
|
120 |
- { path: 'HyugaDetail.page', name: 'HyugaDetail', component: HyugaDetail }, |
|
121 |
- { path: 'hyugaInsert.page', name: 'hyugaInsert', component: HyugaInsert }, |
|
122 |
- { path: 'ChuljangStatue.page', name: 'ChuljangStatue', component: ChuljangStatue }, |
|
123 |
- { path: 'BokmyeongInsert.page', name: 'BokmyeongInsert', component: BokmyeongInsert }, |
|
124 |
- { path: 'ChuljangInsert.page', name: 'ChuljangInsert', component: ChuljangInsert }, |
|
125 |
- { path: 'ChuljangPumuiDetail.page', name: 'ChuljangPumuiDetail', component: ChuljangPumuiDetail }, |
|
126 |
- { path: 'ChuljangBokmyeongDetail.page', name: 'ChuljangBokmyeongDetail', component: ChuljangBokmyeongDetail }, |
|
127 |
- { path: 'ChuljangDetailAll.page', name: 'ChuljangDetailAll', component: ChuljangDetailAll }, |
|
128 | 88 |
|
129 | 89 |
] |
130 |
- }, //근태관리 |
|
131 |
- { path: '/task-management.page', name: 'task', component: task, redirect: '/task-management.page/projectStatue.page', |
|
90 |
+ }, |
|
91 |
+ { |
|
92 |
+ path: 'approvalList.page', // => /approval-management.page/detail/123 |
|
93 |
+ name: 'approvalList', |
|
94 |
+ component: approvalList, |
|
132 | 95 |
children: [ |
133 |
- { path: 'projectStatue.page', name: 'projectStatue', component: projectStatue }, |
|
134 |
- { path: 'projectDetail.page', name: 'projectDetail', component: projectDetail }, |
|
135 |
- { path: 'meetingInsert.page', name: 'meetingInsert', component: meetingInsert }, |
|
136 |
- { path: 'meetingDetail.page', name: 'meetingDetail', component: meetingDetail }, |
|
137 |
- { path: 'projectInsert.page', name: 'projectInsert', component: projectInsert }, |
|
138 |
- { path: 'projectTuib.page', name: 'projectTuib', component: projectTuib }, |
|
139 |
- { path: 'projectTuibDetail.page', name: 'projectTuibDetail', component: projectTuibDetail }, |
|
140 | 96 |
] |
141 |
- }, //업무관리 |
|
142 |
- { path: '/financial-management.page', name: 'financial', component: financial, redirect: '/financial-management.page/salaryList.page', |
|
143 |
- children: [ |
|
144 |
- { path: 'salaryList.page', name: 'salaryList', component: salaryList }, |
|
145 |
- { path: 'employeeSalaryList.page', name: 'employeeSalaryList', component: employeeSalaryList }, |
|
146 |
- { path: 'employeeSalaryDetail.page', name: 'employeeSalaryDetail', component: employeeSalaryDetail }, |
|
147 |
- { path: 'employeeSalaryInsert.page', name: 'employeeSalaryInsert', component: employeeSalaryInsert }, |
|
148 |
- { path: 'ChuljangCostList.page', name: 'ChuljangCostList', component: ChuljangCostList }, |
|
149 |
- { path: 'MeetingCostList.page', name: 'MeetingCostList', component: MeetingCostList }, |
|
150 |
- ] |
|
151 |
- }, //재무관리 |
|
152 |
- { path: '/asset-management.page', name: 'asset', component: asset, redirect: '/asset-management.page/VhcleList.page', |
|
153 |
- children: [ |
|
154 |
- { path: 'VhcleList.page', name: 'VhcleList', component: VhcleList }, |
|
155 |
- { path: 'VhcleInfoManagement.page', name: 'VhcleInfoManagement', component: VhcleInfoManagement }, |
|
156 |
- { path: 'CardList.page', name: 'CardList', component: CardList }, |
|
157 |
- { path: 'CardInfoManagement.page', name: 'CardInfoManagement', component: CardInfoManagement }, |
|
158 |
- |
|
159 |
- ] |
|
160 |
- }, //자산관리 |
|
161 |
- { path: '/hr-management.page', name: 'hr', component: hr, redirect: '/hr-management.page/hrSearch.page', |
|
162 |
- children: [ |
|
163 |
- { path: 'hrSearch.page', name: 'hrSearch', component: hrSearch }, |
|
164 |
- { path: 'hrManagement.page', name: 'hrManagement', component: hrManagement }, |
|
165 |
- { path: 'hrDetail.page', name: 'hrDetail', component: hrDetail }, |
|
166 |
- { path: 'hrInsert.page', name: 'hrInsert', component: hrInsert }, |
|
167 |
- { path: 'buseoManagement.page', name: 'buseoManagement', component: buseoManagement }, |
|
168 |
- ] |
|
169 |
- }, //인사관리 |
|
170 |
- { path: '/system-management.page', name: 'system', component: system, redirect: { name: 'userManagement' }, |
|
171 |
- children:[ |
|
172 |
- { path: 'userManagement.page', name: 'userManagement', component: userManagement }, |
|
173 |
- { path: 'accessControlManagement.page', name: 'accessControlManagement', component: accessControlManagement }, |
|
174 |
- { path: 'commonCodeManagement.page', name: 'commonCodeManagement', component: commonCodeManagement }, |
|
175 |
- { path: 'commonCodeInsert.page', name: 'commonCodeInsert', component: commonCodeInsert }, |
|
176 |
- { path: 'commonCodeDetail.page', name: 'commonCodeDetail', component: commonCodeDetail }, |
|
177 |
- |
|
178 |
- ] |
|
179 |
- }, //시스템관리 |
|
97 |
+ }, |
|
98 |
+ { path: 'ChuljangPumui.page', name: 'ChuljangPumui', component: ChuljangPumui }, |
|
99 |
+ { path: 'ChuljangBokmyeong.page', name: 'ChuljangBokmyeong', component: ChuljangBokmyeong }, |
|
100 |
+ { path: 'Hyuga.page', name: 'Hyuga', component: Hyuga }, |
|
101 |
+ ] |
|
102 |
+ }, //결재관리 |
|
103 |
+ { |
|
104 |
+ path: '/attendance-management.page', name: 'attendance', component: attendance, redirect: '/attendance-management.page/myAttendance.page', |
|
105 |
+ children: [ |
|
106 |
+ { path: 'myAttendance.page', name: 'myAttendance', component: myAttendance }, |
|
107 |
+ { path: 'buseoAttendance.page', name: 'buseoAttendance', component: buseoAttendance }, |
|
108 |
+ { path: 'AttendanceDetail.page', name: 'AttendanceDetail', component: AttendanceDetail }, |
|
109 |
+ { path: 'hyugaStatue.page', name: 'hyugaStatue', component: hyugaStatue }, |
|
110 |
+ { path: 'HyugaDetail.page', name: 'HyugaDetail', component: HyugaDetail }, |
|
111 |
+ { path: 'hyugaInsert.page', name: 'hyugaInsert', component: HyugaInsert }, |
|
112 |
+ { path: 'ChuljangStatue.page', name: 'ChuljangStatue', component: ChuljangStatue }, |
|
113 |
+ { path: 'BokmyeongInsert.page', name: 'BokmyeongInsert', component: BokmyeongInsert }, |
|
114 |
+ { path: 'ChuljangInsert.page', name: 'ChuljangInsert', component: ChuljangInsert }, |
|
115 |
+ { path: 'ChuljangBokmyeongDetail.page', name: 'ChuljangBokmyeongDetail', component: ChuljangBokmyeongDetail }, |
|
116 |
+ { path: 'ChuljangDetailAll.page', name: 'ChuljangDetailAll', component: ChuljangDetailAll }, |
|
117 |
+ ] |
|
118 |
+ }, //근태관리 |
|
119 |
+ { |
|
120 |
+ path: '/task-management.page', name: 'task', component: task, redirect: '/task-management.page/projectStatue.page', |
|
121 |
+ children: [ |
|
122 |
+ { path: 'projectStatue.page', name: 'projectStatue', component: projectStatue }, |
|
123 |
+ { path: 'projectDetail.page', name: 'projectDetail', component: projectDetail }, |
|
124 |
+ { path: 'meetingInsert.page', name: 'meetingInsert', component: meetingInsert }, |
|
125 |
+ { path: 'meetingDetail.page', name: 'meetingDetail', component: meetingDetail }, |
|
126 |
+ { path: 'projectInsert.page', name: 'projectInsert', component: projectInsert }, |
|
127 |
+ { path: 'projectTuib.page', name: 'projectTuib', component: projectTuib }, |
|
128 |
+ { path: 'projectTuibDetail.page', name: 'projectTuibDetail', component: projectTuibDetail }, |
|
129 |
+ ] |
|
130 |
+ }, //업무관리 |
|
131 |
+ { |
|
132 |
+ path: '/financial-management.page', name: 'financial', component: financial, redirect: '/financial-management.page/salaryList.page', |
|
133 |
+ children: [ |
|
134 |
+ { path: 'salaryList.page', name: 'salaryList', component: salaryList }, |
|
135 |
+ { path: 'employeeSalaryList.page', name: 'employeeSalaryList', component: employeeSalaryList }, |
|
136 |
+ { path: 'employeeSalaryDetail.page', name: 'employeeSalaryDetail', component: employeeSalaryDetail }, |
|
137 |
+ { path: 'employeeSalaryInsert.page', name: 'employeeSalaryInsert', component: employeeSalaryInsert }, |
|
138 |
+ { path: 'ChuljangCostList.page', name: 'ChuljangCostList', component: ChuljangCostList }, |
|
139 |
+ { path: 'MeetingCostList.page', name: 'MeetingCostList', component: MeetingCostList }, |
|
140 |
+ ] |
|
141 |
+ }, //재무관리 |
|
142 |
+ { |
|
143 |
+ path: '/asset-management.page', name: 'asset', component: asset, redirect: '/asset-management.page/VhcleList.page', |
|
144 |
+ children: [ |
|
145 |
+ { path: 'VhcleList.page', name: 'VhcleList', component: VhcleList }, |
|
146 |
+ { path: 'VhcleInfoManagement.page', name: 'VhcleInfoManagement', component: VhcleInfoManagement }, |
|
147 |
+ { path: 'CardList.page', name: 'CardList', component: CardList }, |
|
148 |
+ { path: 'CardInfoManagement.page', name: 'CardInfoManagement', component: CardInfoManagement }, |
|
149 |
+ |
|
150 |
+ ] |
|
151 |
+ }, //자산관리 |
|
152 |
+ { |
|
153 |
+ path: '/hr-management.page', name: 'hr', component: hr, redirect: '/hr-management.page/hrSearch.page', |
|
154 |
+ children: [ |
|
155 |
+ { path: 'hrSearch.page', name: 'hrSearch', component: hrSearch }, |
|
156 |
+ { path: 'hrManagement.page', name: 'hrManagement', component: hrManagement }, |
|
157 |
+ { path: 'hrDetail.page', name: 'hrDetail', component: hrDetail }, |
|
158 |
+ { path: 'hrInsert.page', name: 'hrInsert', component: hrInsert }, |
|
159 |
+ { path: 'buseoManagement.page', name: 'buseoManagement', component: buseoManagement }, |
|
160 |
+ ] |
|
161 |
+ }, //인사관리 |
|
162 |
+ { |
|
163 |
+ path: '/system-management.page', name: 'system', component: system, redirect: { name: 'userManagement' }, |
|
164 |
+ children: [ |
|
165 |
+ { path: 'userManagement.page', name: 'userManagement', component: userManagement }, |
|
166 |
+ { path: 'accessControlManagement.page', name: 'accessControlManagement', component: accessControlManagement }, |
|
167 |
+ { path: 'commonCodeManagement.page', name: 'commonCodeManagement', component: commonCodeManagement }, |
|
168 |
+ { path: 'commonCodeInsert.page', name: 'commonCodeInsert', component: commonCodeInsert }, |
|
169 |
+ { path: 'commonCodeDetail.page', name: 'commonCodeDetail', component: commonCodeDetail }, |
|
170 |
+ ] |
|
171 |
+ }, //시스템관리 |
|
180 | 172 |
]; |
181 | 173 |
|
182 |
- |
|
183 | 174 |
const AppRouter = createRouter({ |
184 |
- history: createWebHistory(), |
|
185 |
- routes, |
|
175 |
+ history: createWebHistory(), |
|
176 |
+ routes, |
|
186 | 177 |
}); |
187 | 178 |
|
188 | 179 |
// Add navigation guard |
189 | 180 |
AppRouter.beforeEach((to, from, next) => { |
190 |
- // Check login status |
|
191 |
- const isLoggedIn = localStorage.getItem('isLoggedIn') === 'true'; |
|
192 |
- |
|
193 |
- // If not logged in and trying to access any page other than login, redirect to login page |
|
194 |
- if (!isLoggedIn && to.path !== '/login.page') { |
|
195 |
- next('/login.page'); |
|
196 |
- } |
|
197 |
- // If logged in and trying to access login page, redirect to main page |
|
198 |
- else if (isLoggedIn && to.path === '/login.page') { |
|
199 |
- next('/'); |
|
200 |
- } |
|
201 |
- else { |
|
202 |
- next(); // Proceed to requested route |
|
203 |
- } |
|
204 |
- }); |
|
205 |
- |
|
181 |
+ // Check login status |
|
182 |
+ const isLoggedIn = localStorage.getItem('isLoggedIn') === 'true'; |
|
183 |
+ |
|
184 |
+ // If not logged in and trying to access any page other than login, redirect to login page |
|
185 |
+ if (!isLoggedIn && to.path !== '/login.page') { |
|
186 |
+ next('/login.page'); |
|
187 |
+ } |
|
188 |
+ // If logged in and trying to access login page, redirect to main page |
|
189 |
+ else if (isLoggedIn && to.path === '/login.page') { |
|
190 |
+ next('/'); |
|
191 |
+ } |
|
192 |
+ else { |
|
193 |
+ next(); // Proceed to requested route |
|
194 |
+ } |
|
195 |
+}); |
|
206 | 196 |
|
207 | 197 |
export default AppRouter;(파일 끝에 줄바꿈 문자 없음) |
--- client/views/pages/Manager/attendance/ChuljangDetailAll.vue
+++ client/views/pages/Manager/attendance/ChuljangDetailAll.vue
... | ... | @@ -1,225 +1,262 @@ |
1 | 1 |
<template> |
2 |
-<div class="card "> |
|
3 |
- <div class="card-body"> |
|
4 |
- <h2 class="card-title">출장 현황</h2> |
|
5 |
- |
|
2 |
+ <div class="card"> |
|
3 |
+ <div class="card-body"> |
|
4 |
+ <h2 class="card-title">출장 현황</h2> |
|
6 | 5 |
<div class="form-card" style="margin-bottom: 20px;"> |
7 | 6 |
<h1>출장품의서</h1> |
8 |
- <div class="approval-box tbl-wrap tbl2"> |
|
9 |
- <table class="tbl data"> |
|
10 |
- <tbody> |
|
11 |
- <tr class="thead"> |
|
12 |
- <td rowspan="2" class="th">승인자</td> |
|
13 |
- <td>과장</td> |
|
14 |
- <td>소장</td> |
|
15 |
- </tr> |
|
16 |
- <tr> |
|
17 |
- <td><p class="name">홍길동</p><p class="date">2025/05/09</p></td> |
|
18 |
- <td><p class="name">홍길동</p><p class="date">2025/05/09</p></td> |
|
19 |
- </tr> |
|
20 |
- </tbody> |
|
21 |
- |
|
22 |
- </table> |
|
23 |
- </div> |
|
24 |
- <form class="row g-3 needs-validation detail" :class="{ 'was-validated': formSubmitted }" @submit.prevent="handleRegister" novalidate > |
|
25 |
- <div class="col-12 "> |
|
26 |
- <div class="col-12 border-x"> |
|
27 |
- <label for="youremail" class="form-label ">출장구분<p class="require"><img :src="require" alt=""></p></label> |
|
28 |
- <input v-model="email" type="text" name="username" class="form-control" readonly > |
|
29 |
- </div> |
|
30 |
- |
|
31 |
- <div class="col-12 border-x"> |
|
32 |
- <label for="yourPassword" class="form-label">이름</label> |
|
33 |
- <input v-model="password" type="password" name="password" class="form-control" readonly placeholder="주식회사 테이큰 소프트"> |
|
34 |
- </div> |
|
35 |
- </div> |
|
36 |
- <div class="col-12"> |
|
37 |
- <div class="col-12 border-x"> |
|
38 |
- <label for="youremail" class="form-label">부서</label> |
|
39 |
- <input v-model="email" type="text" name="username" class="form-control" readonly placeholder="과장"> |
|
40 |
- </div> |
|
41 |
- |
|
42 |
- <div class="col-12 border-x"> |
|
43 |
- <label for="yourPassword" class="form-label">직급</label> |
|
44 |
- <input v-model="password" type="password" name="password" class="form-control" readonly placeholder="팀장"> |
|
45 |
- </div> |
|
46 |
- </div> |
|
47 |
- <div class="col-12"> |
|
48 |
- <label for="yourName" class="form-label">출장지</label> |
|
49 |
- <input v-model="name" type="text" name="name" class="form-control" readonly> |
|
50 |
- </div> |
|
51 |
- <div class="col-12"> |
|
52 |
- <label for="yourName" class="form-label">출장목적</label> |
|
53 |
- <input v-model="name" type="text" name="name" class="form-control " readonly> |
|
54 |
- </div> |
|
55 |
- <div class="col-12"> |
|
56 |
- <label for="yourName" class="form-label">동행자</label> |
|
57 |
- <input v-model="name" type="text" name="name" class="form-control " readonly> |
|
58 |
- </div> |
|
59 |
- <div class="col-12 chuljang"> |
|
60 |
- <label for="yourName" class="form-label">품의내용</label> |
|
61 |
- <input v-model="name" type="text" name="name" class="form-control textarea " readonly> |
|
62 |
- </div> |
|
63 |
- <div class="col-12"> |
|
64 |
- <label for="yourName" class="form-label">법인카드</label> |
|
65 |
- <input v-model="name" type="text" name="name" class="form-control " readonly> |
|
66 |
- </div> |
|
67 |
- <div class="col-12"> |
|
68 |
- <label for="yourName" class="form-label">법인차량</label> |
|
69 |
- <input v-model="name" type="text" name="name" class="form-control " readonly> |
|
7 |
+ <SanctnViewList v-if="bsrpCnsul.sanctnList.length > 0" :sanctns="bsrpCnsul.sanctnList" /> |
|
8 |
+ <form class="row g-3 needs-validation detail" :class="{ 'was-validated': formSubmitted }" @submit.prevent="handleRegister" novalidate> |
|
9 |
+ <div class="col-12"> |
|
10 |
+ <div class="col-12 border-x"> |
|
11 |
+ <label class="form-label">출장구분</label> |
|
12 |
+ <p>{{ bsrpInfo.bsrpSeNm }}</p> |
|
70 | 13 |
</div> |
71 | 14 |
<div class="col-12 border-x"> |
72 |
- <label for="yourName" class="form-label">품의 신청일</label> |
|
73 |
- <input v-model="name" type="text" name="name" class="form-control " i readonly> |
|
15 |
+ <label class="form-label">이름</label> |
|
16 |
+ <p>{{ user.userNm }}</p> |
|
74 | 17 |
</div> |
75 |
- </form> |
|
18 |
+ </div> |
|
19 |
+ <div class="col-12"> |
|
20 |
+ <div class="col-12 border-x"> |
|
21 |
+ <label class="form-label">부서</label> |
|
22 |
+ <p>{{ user.deptNm }}</p> |
|
23 |
+ </div> |
|
24 |
+ <div class="col-12 border-x"> |
|
25 |
+ <label class="form-label">직급</label> |
|
26 |
+ <p>{{ user.clsfNm }}</p> |
|
27 |
+ </div> |
|
28 |
+ </div> |
|
29 |
+ <div class="col-12"> |
|
30 |
+ <label class="form-label">출장지</label> |
|
31 |
+ <p>{{ bsrpInfo.bsrpPlace }}</p> |
|
32 |
+ </div> |
|
33 |
+ <div class="col-12"> |
|
34 |
+ <label class="form-label">출장목적</label> |
|
35 |
+ <p>{{ bsrpInfo.bsrpPurps }}</p> |
|
36 |
+ </div> |
|
37 |
+ <div class="col-12"> |
|
38 |
+ <label class="form-label">동행자</label> |
|
39 |
+ <div v-if="bsrpInfo.bsrpNmprList.length > 0"> |
|
40 |
+ <span v-for="(item, idx) of bsrpInfo.bsrpNmprList" :key="idx">{{ item.triperNm }}</span> |
|
41 |
+ </div> |
|
42 |
+ </div> |
|
43 |
+ <div class="col-12"> |
|
44 |
+ <label class="form-label">품의내용</label> |
|
45 |
+ <ViewerComponent :content="bsrpCnsul.cn" /> |
|
46 |
+ </div> |
|
47 |
+ <div class="col-12"> |
|
48 |
+ <label class="form-label">법인카드</label> |
|
49 |
+ <div> |
|
50 |
+ <p v-for="(item, idx) of cprCardList" :key="idx"> |
|
51 |
+ <span v-if="idx !== 0">, </span> |
|
52 |
+ <span>{{ item.cardNm }}</span> |
|
53 |
+ </p> |
|
54 |
+ </div> |
|
55 |
+ </div> |
|
56 |
+ <div class="col-12"> |
|
57 |
+ <label class="form-label">법인차량</label> |
|
58 |
+ <div> |
|
59 |
+ <p v-for="(item, idx) of cprVhcleList" :key="idx"> |
|
60 |
+ <span v-if="idx !== 0">, </span> |
|
61 |
+ <span>{{ item.vhcleNm }}</span> |
|
62 |
+ </p> |
|
63 |
+ </div> |
|
64 |
+ </div> |
|
65 |
+ <div class="col-12 border-x"> |
|
66 |
+ <label class="form-label">품의 신청일</label> |
|
67 |
+ <p>{{ bsrpCnsul.rgsde }}</p> |
|
68 |
+ </div> |
|
69 |
+ </form> |
|
76 | 70 |
</div> |
77 |
- <div class="form-card"> |
|
71 |
+ <div v-if="!$isEmpty(bsrpRport.bsrpId)" class="form-card"> |
|
78 | 72 |
<h1>출장복명서</h1> |
79 |
- <div class="approval-box tbl-wrap tbl2"> |
|
80 |
- <table class="tbl data"> |
|
81 |
- <tbody> |
|
82 |
- <tr class="thead"> |
|
83 |
- <td rowspan="2" class="th">승인자</td> |
|
84 |
- <td>과장</td> |
|
85 |
- <td>소장</td> |
|
86 |
- </tr> |
|
87 |
- <tr> |
|
88 |
- <td><p class="name">홍길동</p><p class="date">2025/05/09</p></td> |
|
89 |
- <td><p class="name">홍길동</p><p class="date">2025/05/09</p></td> |
|
90 |
- </tr> |
|
91 |
- </tbody> |
|
92 |
- |
|
93 |
- </table> |
|
94 |
- </div> |
|
95 |
- <form class="row g-3 needs-validation detail" :class="{ 'was-validated': formSubmitted }" @submit.prevent="handleRegister" novalidate> |
|
96 |
- |
|
97 |
- <div class="col-12 chuljang"> |
|
98 |
- <label for="yourName" class="form-label">복명내용</label> |
|
99 |
- <input v-model="name" type="text" name="name" class="form-control textarea " id="yourName" readonly> |
|
100 |
- </div> |
|
101 |
- <div class="col-12"> |
|
102 |
- <label for="yourName" class="form-label">여비계산</label> |
|
103 |
- <input v-model="name" type="text" name="name" class="form-control " id="yourName" readonly> |
|
104 |
- </div> |
|
105 |
- <div class="col-12 "> |
|
106 |
- <label for="yourName" class="form-label">복명 신청일</label> |
|
107 |
- <input v-model="name" type="text" name="name" class="form-control" id="yourName" readonly placeholder="2025-01-01"> |
|
108 |
- </div> |
|
109 |
- <div class="col-12 border-x return"> |
|
110 |
- <label for="yourName" class="form-label">반려사유</label> |
|
111 |
- <input v-model="name" type="text" name="name" class="form-control" readonly placeholder="2025-01-01"> |
|
112 |
- </div> |
|
113 |
- |
|
114 |
- |
|
115 |
- </form> |
|
73 |
+ <SanctnViewList v-if="bsrpRport.sanctnList.length > 0" :sanctns="bsrpRport.sanctnList" /> |
|
74 |
+ <form class="row g-3 needs-validation detail" :class="{ 'was-validated': formSubmitted }" @submit.prevent="handleRegister" novalidate> |
|
75 |
+ <div class="col-12"> |
|
76 |
+ <label class="form-label">복명내용</label> |
|
77 |
+ <ViewerComponent :content="bsrpRport.cn" /> |
|
78 |
+ </div> |
|
79 |
+ <div class="col-12"> |
|
80 |
+ <label class="form-label">여비계산</label> |
|
81 |
+ <p>{{ bsrpInfo.applcntId }}</p> |
|
82 |
+ </div> |
|
83 |
+ <div class="col-12"> |
|
84 |
+ <label class="form-label">복명 신청일</label> |
|
85 |
+ <p>{{ bsrpRport.rgsde }}</p> |
|
86 |
+ </div> |
|
87 |
+ <div class="col-12 border-x return"> |
|
88 |
+ <label class="form-label">반려사유</label> |
|
89 |
+ <p>{{ bsrpInfo.applcntId }}</p> |
|
90 |
+ </div> |
|
91 |
+ </form> |
|
116 | 92 |
</div> |
117 |
- <div class="buttons"> |
|
118 |
- <button class="btn btn-red " type="submit">신청취소</button> |
|
119 |
- <button class="btn secondary" type="submit">재신청</button> |
|
120 |
- <button class="btn secondary" type="submit"> 수정</button> |
|
121 |
- <button class="btn tertiary " type="submit">목록</button> |
|
122 |
- </div> |
|
123 |
- |
|
93 |
+ <div class="buttons"> |
|
94 |
+ <button v-if="sanctnStatus === 'waiting' && sanctnStatus !== 'approved'" type="button" class="btn btn-red" @click="deleteData">신청취소</button> |
|
95 |
+ <button v-if="sanctnStatus === 'waiting'" type="button" class="btn secondary" @click="fnMoveTo('edit', pageId)">수정</button> |
|
96 |
+ <button v-if="sanctnStatus === 'rejected'" type="button" class="btn secondary" @click="fnMoveTo('edit', pageId)">재신청</button> |
|
97 |
+ <button v-if="sanctnStatus === 'approved'" type="button" class="btn primary" @click="fnMoveTo('plus', pageId)">복명서 작성</button> |
|
98 |
+ <button type="button" class="btn tertiary" @click="fnMoveTo('list')">목록</button> |
|
124 | 99 |
</div> |
125 | 100 |
</div> |
101 |
+ </div> |
|
126 | 102 |
</template> |
127 |
- |
|
128 | 103 |
<script> |
104 |
+import SanctnViewList from '../../../component/Sanctn/SanctnViewList.vue'; |
|
105 |
+import ViewerComponent from '../../../component/editor/ViewerComponent.vue'; |
|
106 |
+// API |
|
107 |
+import { bsrpProc, deleteBsrpProc } from '../../../../resources/api/bsrp'; |
|
108 |
+ |
|
129 | 109 |
export default { |
110 |
+ components: { |
|
111 |
+ SanctnViewList, ViewerComponent, |
|
112 |
+ }, |
|
113 |
+ |
|
130 | 114 |
data() { |
131 |
- const today = new Date().toISOString().split('T')[0]; |
|
132 | 115 |
return { |
133 |
- isReturned: true, |
|
134 |
- startDate: today, |
|
135 |
- startTime: "09:00", // 기본 시작 시간 09:00 |
|
136 |
- endDate: today, |
|
137 |
- endTime: "18:00", // 기본 종료 시간 18:00 |
|
138 |
- category: "", |
|
139 |
- dayCount: 1, |
|
140 |
- reason: "", // 사유 |
|
141 |
- listData: [ |
|
142 |
- { |
|
143 |
- type: '연차', |
|
144 |
- approvalType: '결재', |
|
145 |
- applicant: '홍길동', |
|
146 |
- period: '2025-05-10 ~ 2025-15-03', |
|
147 |
- requestDate: '2025-04-25', |
|
148 |
- status: '대기' |
|
149 |
- }, { |
|
150 |
- type: '반차', |
|
151 |
- approvalType: '전결', |
|
152 |
- applicant: '홍길동', |
|
153 |
- period: '2025-05-01 ~ 2025-05-03', |
|
154 |
- requestDate: '2025-04-25', |
|
155 |
- status: '승인' |
|
156 |
- }], |
|
116 |
+ pageId: null, |
|
117 |
+ |
|
118 |
+ // 출장 정보 |
|
119 |
+ bsrpInfo: { |
|
120 |
+ bsrpId: null, // 출장 아이디 |
|
121 |
+ applcntId: null, // 신청자 아이디 |
|
122 |
+ bsrpSe: null, // 출장구분 (공통코드 : sanctn_mby_bsrp) |
|
123 |
+ bsrpPlace: null, // 출장지 |
|
124 |
+ bsrpPurps: null, // 출장목적 |
|
125 |
+ bgnde: null, // 시작일 |
|
126 |
+ beginHour: null, // 시작시 |
|
127 |
+ beginMnt: null, // 시작분 |
|
128 |
+ endde: null, // 종료일 |
|
129 |
+ endHour: null, // 종료시 |
|
130 |
+ endMnt: null, // 종료분 |
|
131 |
+ bsrpSeNm: null, // 출장구분 이름 |
|
132 |
+ bsrpNmprList: [], // 출장 인원 |
|
133 |
+ }, |
|
134 |
+ // 품의서 |
|
135 |
+ bsrpCnsul: { |
|
136 |
+ bsrpId: null, // 출장 아이디 |
|
137 |
+ cn: null, // 내용 |
|
138 |
+ confmAt: null, // 승인 여부 (W: 대기, A: 승인, R 반려) |
|
139 |
+ rgsde: null, // 등록일 |
|
140 |
+ register: null, // 등록자 |
|
141 |
+ updde: null, // 수정일 |
|
142 |
+ updusr: null, // 수정자 |
|
143 |
+ sanctnList: [], |
|
144 |
+ }, |
|
145 |
+ // 복명서 |
|
146 |
+ bsrpRport: { |
|
147 |
+ bsrpId: null, // 출장 아이디 |
|
148 |
+ cn: null, // 내용 |
|
149 |
+ confmAt: null, // 승인 여부 (W: 대기, A: 승인, R 반려) |
|
150 |
+ fileId: null, // 파일 아이디 |
|
151 |
+ rgsde: null, // 등록일 |
|
152 |
+ register: null, // 등록자 |
|
153 |
+ updde: null, // 수정일 |
|
154 |
+ updusr: null, // 수정자 |
|
155 |
+ sanctnList: [], |
|
156 |
+ }, |
|
157 |
+ // 신청자 정보 |
|
158 |
+ user: {}, |
|
157 | 159 |
}; |
158 | 160 |
}, |
159 |
- computed: { |
|
160 |
- // Pinia Store의 상태를 가져옵니다. |
|
161 |
- loginUser() { |
|
162 |
- const authStore = useAuthStore(); |
|
163 |
- return authStore.getLoginUser; |
|
164 |
- }, |
|
165 |
- }, |
|
166 |
- methods: { |
|
167 |
- // 폼 검증 메서드 |
|
168 |
- validateForm() { |
|
169 |
- // 필수 입력 필드 체크 |
|
170 |
- if ( |
|
171 |
- this.category && |
|
172 |
- this.startDate && |
|
173 |
- this.startTime && |
|
174 |
- this.endDate && |
|
175 |
- this.endTime && |
|
176 |
- this.dayCount > 0 && |
|
177 |
- this.reason.trim() !== "" |
|
178 |
- ) { |
|
179 |
- this.isFormValid = true; |
|
180 |
- } else { |
|
181 |
- this.isFormValid = false; |
|
182 |
- } |
|
183 |
- }, |
|
184 |
- calculateDayCount() { |
|
185 |
- const start = new Date(`${this.startDate}T${this.startTime}:00`); |
|
186 |
- const end = new Date(`${this.endDate}T${this.endTime}:00`); |
|
187 |
- |
|
188 |
- let totalHours = (end - start) / (1000 * 60 * 60); // 밀리초를 시간 단위로 변환 |
|
189 |
- |
|
190 |
- if (this.startDate !== this.endDate) { |
|
191 |
- // 시작일과 종료일이 다른경우 |
|
192 |
- const startDateObj = new Date(this.startDate); |
|
193 |
- const endDateObj = new Date(this.endDate); |
|
194 |
- const daysDifference = (endDateObj - startDateObj) / (1000 * 60 * 60 * 24); // 두 날짜 사이의 차이를 일수로 계산 |
|
195 |
- if (this.startTime !== "09:00" || this.endTime !== "18:00") { |
|
196 |
- this.dayCount = daysDifference + 0.5; // 시간 조건이 기준에서 벗어날 경우 |
|
197 |
- } else { |
|
198 |
- this.dayCount = Math.ceil(daysDifference + 1); // 시간 조건이 기준에 맞을 경우 |
|
199 |
- } |
|
200 |
- } else { |
|
201 |
- // 시작일과 종료일이 같은 경우 |
|
202 |
- if (this.startTime !== "09:00" || this.endTime !== "18:00") { |
|
203 |
- this.dayCount = 0.5; // 시작 시간 또는 종료 시간이 기준과 다를 경우 0.5 |
|
204 |
- } else { |
|
205 |
- this.dayCount = 1; // 기준 시간(09:00~18:00)이 맞으면 1일로 간주 |
|
206 |
- } |
|
207 |
- } |
|
208 | 161 |
|
209 |
- this.validateForm(); // dayCount 변경 후 폼 재검증 |
|
162 |
+ computed: { |
|
163 |
+ // 결재 상태 확인 |
|
164 |
+ sanctnStatus() { |
|
165 |
+ const sanctnList = this.bsrpCnsul.sanctnList; |
|
166 |
+ |
|
167 |
+ if (sanctnList.length === 0) return 'none'; // 결재 정보 없음 |
|
168 |
+ |
|
169 |
+ // 하나라도 반려가 있으면 '반려' |
|
170 |
+ if (sanctnList.some(item => item.sanctnSttus === 'R')) { |
|
171 |
+ return 'rejected'; |
|
172 |
+ } |
|
173 |
+ |
|
174 |
+ // 모든 결재가 승인이면 '승인' |
|
175 |
+ if (sanctnList.every(item => item.sanctnSttus === 'A')) { |
|
176 |
+ return 'approved'; |
|
177 |
+ } |
|
178 |
+ |
|
179 |
+ // 그 외에는 '대기' |
|
180 |
+ return 'waiting'; |
|
181 |
+ } |
|
210 | 182 |
}, |
211 |
- handleSubmit() { |
|
212 |
- this.validateForm(); // 제출 시 유효성 확인 |
|
213 |
- if (this.isFormValid) { |
|
214 |
- localStorage.setItem('HyugaFormData', JSON.stringify(this.$data)); |
|
215 |
- alert("승인 요청이 완료되었습니다."); |
|
216 |
- // 추가 처리 로직 (API 요청 등) |
|
217 |
- } else { |
|
218 |
- alert("모든 필드를 올바르게 작성해주세요."); |
|
183 |
+ |
|
184 |
+ async created() { |
|
185 |
+ this.pageId = this.$route.query.id; |
|
186 |
+ if (this.$isEmpty(this.pageId)) { |
|
187 |
+ alert("게시물이 존재하지 않습니다."); |
|
188 |
+ this.fnMoveTo('list'); |
|
189 |
+ } |
|
190 |
+ }, |
|
191 |
+ |
|
192 |
+ mounted() { |
|
193 |
+ this.findDatas(); // 상세 조회 |
|
194 |
+ }, |
|
195 |
+ |
|
196 |
+ methods: { |
|
197 |
+ // 상세 조회 |
|
198 |
+ async findDatas() { |
|
199 |
+ try { |
|
200 |
+ const response = await bsrpProc(this.pageId); |
|
201 |
+ const result = response.data.data; |
|
202 |
+ |
|
203 |
+ this.bsrpInfo = result.bsrpInfo; |
|
204 |
+ this.bsrpInfo.bgnde = this.bsrpInfo.bgnde.split(' ')[0]; |
|
205 |
+ this.bsrpInfo.endde = this.bsrpInfo.endde.split(' ')[0]; |
|
206 |
+ |
|
207 |
+ this.cprCardList = result.cprCardList; |
|
208 |
+ this.cprVhcleList = result.cprVhcleList; |
|
209 |
+ this.user = result.user; |
|
210 |
+ |
|
211 |
+ this.bsrpCnsul = result.bsrpCnsul; |
|
212 |
+ this.bsrpRport = result.bsrpRport; |
|
213 |
+ } catch (error) { |
|
214 |
+ const message = error.response.data.message; |
|
215 |
+ alert(message); |
|
219 | 216 |
} |
220 | 217 |
}, |
221 |
- |
|
222 |
- |
|
218 |
+ |
|
219 |
+ // 삭제 |
|
220 |
+ async deleteData() { |
|
221 |
+ const isCheck = confirm("삭제하시겠습니까?"); |
|
222 |
+ if (!isCheck) { |
|
223 |
+ return; |
|
224 |
+ } |
|
225 |
+ |
|
226 |
+ try { |
|
227 |
+ const response = await deleteBsrpProc(this.pageId); |
|
228 |
+ |
|
229 |
+ this.fnMoveTo('list'); |
|
230 |
+ } catch (error) { |
|
231 |
+ if (error.response) { |
|
232 |
+ alert(error.response.data.message); |
|
233 |
+ } else { |
|
234 |
+ alert("에러가 발생했습니다."); |
|
235 |
+ } |
|
236 |
+ console.error(error.message); |
|
237 |
+ this.fnMoveTo('list'); |
|
238 |
+ } |
|
239 |
+ }, |
|
240 |
+ |
|
241 |
+ // 페이지 이동 |
|
242 |
+ fnMoveTo(type, id) { |
|
243 |
+ const routes = { |
|
244 |
+ 'list': { name: 'ChuljangStatue' }, |
|
245 |
+ 'view': { name: 'ChuljangDetailAll', query: { id } }, |
|
246 |
+ 'edit': { name: 'ChuljangInsert', query: this.$isEmpty(id) ? {} : { id } }, |
|
247 |
+ 'plus': { name: 'ChuljangInsert', query: this.$isEmpty(id) ? {} : { id } }, |
|
248 |
+ }; |
|
249 |
+ |
|
250 |
+ if (routes[type]) { |
|
251 |
+ if (!this.$isEmpty(this.pageId) && type === 'list') { |
|
252 |
+ this.$router.push({ name: 'ChuljangDetailAll', query: { id: this.pageId } }); |
|
253 |
+ } |
|
254 |
+ this.$router.push(routes[type]); |
|
255 |
+ } else { |
|
256 |
+ alert("올바르지 않은 경로를 요청하여 목록으로 이동합니다."); |
|
257 |
+ this.$router.push(routes['list']); |
|
258 |
+ } |
|
259 |
+ }, |
|
223 | 260 |
}, |
224 | 261 |
}; |
225 | 262 |
</script> |
--- client/views/pages/Manager/attendance/ChuljangInsert.vue
+++ client/views/pages/Manager/attendance/ChuljangInsert.vue
... | ... | @@ -3,122 +3,94 @@ |
3 | 3 |
<div class="card-body"> |
4 | 4 |
<h2 class="card-title">출장 신청</h2> |
5 | 5 |
<p class="require"><img :src="require" alt=""> 필수입력</p> |
6 |
- <!-- Multi Columns Form --> |
|
7 | 6 |
<form class="row g-3 pt-3 needs-validation" @submit.prevent="handleSubmit"> |
8 |
- |
|
9 |
- |
|
10 | 7 |
<div class="col-12"> |
11 |
- <label for="where" class="form-label"><p>출장구분<p class="require"><img :src="require" alt=""></p></p></label> |
|
12 |
- <select class="form-select" style="width: 110px;"> |
|
13 |
- <option value="" selected>국내</option> |
|
14 |
- <option value="">국외</option> |
|
15 |
- </select> |
|
8 |
+ <label for="where" class="form-label"> |
|
9 |
+ <p>출장구분 <span class="require"><img :src="require" alt=""></span></p> |
|
10 |
+ </label> |
|
11 |
+ <select class="form-select" style="width: 110px;" v-model="bsrpInfo.bsrpSe"> |
|
12 |
+ <option v-for="(item, idx) of cmmnCodes.bsrpCodeList" :key="idx" :value="item.code">{{ item.codeNm }}</option> |
|
13 |
+ </select> |
|
16 | 14 |
</div> |
17 | 15 |
<div class="col-12"> |
18 |
- <label for="where" class="form-label"><p>출장지<p class="require"><img :src="require" alt=""></p></p></label> |
|
19 |
- <input type="text" class="form-control" id="where" v-model="where" /> |
|
16 |
+ <label for="bsrpInfo.bsrpPlace" class="form-label"> |
|
17 |
+ <p>출장지 <span class="require"><img :src="require" alt=""></span></p> |
|
18 |
+ </label> |
|
19 |
+ <input type="text" class="form-control" id="bsrpInfo.bsrpPlace" v-model="bsrpInfo.bsrpPlace" /> |
|
20 | 20 |
</div> |
21 | 21 |
<div class="col-12"> |
22 |
- <label for="where" class="form-label"><p>출장목적<p class="require"><img :src="require" alt=""></p></p></label> |
|
23 |
- <input type="text" class="form-control" id="where" v-model="where" /> |
|
22 |
+ <label for="bsrpInfo.bsrpPurps" class="form-label"> |
|
23 |
+ <p>출장목적 <span class="require"><img :src="require" alt=""></span></p> |
|
24 |
+ </label> |
|
25 |
+ <input type="text" class="form-control" id="bsrpInfo.bsrpPurps" v-model="bsrpInfo.bsrpPurps" /> |
|
24 | 26 |
</div> |
25 | 27 |
<div class="col-12"> |
26 |
- <label for="where" class="form-label"><p>출장기간<p class="require"><img :src="require" alt=""></p></p></label> |
|
28 |
+ <label for="where" class="form-label"> |
|
29 |
+ <p>출장기간 <span class="require"><img :src="require" alt=""></span></p> |
|
30 |
+ </label> |
|
27 | 31 |
<div class="d-flex gap-1"> |
28 |
- <input type="date" class="form-control" id="startDate" v-model="startDate" /> |
|
29 |
- <!-- 시간 선택을 위한 select 사용 --> |
|
30 |
- <select class="form-control" id="startTime" v-model="startTime"> |
|
31 |
- <option value="09:00">09:00</option> |
|
32 |
- <option value="10:00">10:00</option> |
|
33 |
- <option value="11:00">11:00</option> |
|
34 |
- <option value="12:00">12:00</option> |
|
35 |
- <option value="13:00">13:00</option> |
|
36 |
- <option value="14:00">14:00</option> |
|
37 |
- <option value="15:00">15:00</option> |
|
38 |
- <option value="16:00">16:00</option> |
|
39 |
- <option value="17:00">17:00</option> |
|
40 |
- <option value="18:00">18:00</option> |
|
41 |
- </select> |
|
42 |
- ~ |
|
32 |
+ <input type="date" class="form-control" id="bsrpInfo.bgnde" v-model="bsrpInfo.bgnde" /> |
|
33 |
+ <input type="text" class="form-control" id="bsrpInfo.beginHour" v-model="bsrpInfo.beginHour" style="width: 100px;" placeholder="시" /> |
|
34 |
+ <input type="text" class="form-control" id="bsrpInfo.beginMnt" v-model="bsrpInfo.beginMnt" style="width: 100px;" placeholder="분" /> |
|
43 | 35 |
</div> |
44 | 36 |
<div class="d-flex gap-1"> |
45 |
- <input type="date" class="form-control" id="endDate" v-model="endDate" /> |
|
46 |
- <!-- 종료 시간을 위한 select 사용 --> |
|
47 |
- <select class="form-control" id="endTime" v-model="endTime"> |
|
48 |
- <option value="09:00">09:00</option> |
|
49 |
- <option value="10:00">10:00</option> |
|
50 |
- <option value="11:00">11:00</option> |
|
51 |
- <option value="12:00">12:00</option> |
|
52 |
- <option value="13:00">13:00</option> |
|
53 |
- <option value="14:00">14:00</option> |
|
54 |
- <option value="15:00">15:00</option> |
|
55 |
- <option value="16:00">16:00</option> |
|
56 |
- <option value="17:00">17:00</option> |
|
57 |
- <option value="18:00">18:00</option> |
|
58 |
- </select> |
|
37 |
+ <input type="date" class="form-control" id="bsrpInfo.endde" v-model="bsrpInfo.endde" /> |
|
38 |
+ <input type="text" class="form-control" id="bsrpInfo.endHour" v-model="bsrpInfo.endHour" style="width: 100px;" placeholder="시" /> |
|
39 |
+ <input type="text" class="form-control" id="bsrpInfo.endMnt" v-model="bsrpInfo.endMnt" style="width: 100px;" placeholder="분" /> |
|
59 | 40 |
</div> |
60 |
- <div class="col-12 border-x"> |
|
61 |
- <label for="dayCount" class="form-label">일수</label> |
|
62 |
- <input type="text" class="form-control" id="dayCount" v-model="dayCount" readonly /> |
|
63 | 41 |
</div> |
64 |
- </div> |
|
65 |
- |
|
66 | 42 |
<div class="col-12"> |
67 |
- <label for="purpose" class="form-label">동행자 <button type="button" title="추가" @click="showPopup1 = true"> |
|
43 |
+ <label for="totalDays" class="form-label">일수</label> |
|
44 |
+ <input type="text" class="form-control" id="totalDays" v-model="totalDays" readonly /> |
|
45 |
+ </div> |
|
46 |
+ <div class="col-12"> |
|
47 |
+ <label for="purpose" class="form-label">동행자 <button type="button" title="추가" @click="isOpenNmprModal = true"> |
|
68 | 48 |
<PlusCircleFilled /> |
69 |
- </button></label> |
|
70 |
- <HrPopup v-if="showPopup1" @close="showPopup1 = false" @select="addMember"/> |
|
71 |
- <div v-for="(member, index) in members" :key="index" class="d-flex gap-2 addapproval mb-2"> |
|
72 |
- |
|
73 |
- <form class="d-flex align-items-center border-x"> |
|
74 |
- <input type="text" class="form-control" v-model="member.name" style="max-width: 150px;" /> |
|
75 |
- <button type="button" @click="removeMember(index)" class="delete-button"> |
|
49 |
+ </button> |
|
50 |
+ </label> |
|
51 |
+ <HrPopup v-if="isOpenNmprModal" :lists="bsrpInfo.bsrpNmprList" @onSelected="fnAddNmpr" @close="isOpenNmprModal = false" /> |
|
52 |
+ <div class="approval-container"> |
|
53 |
+ <div v-for="(item, idx) of bsrpInfo.bsrpNmprList" :key="idx" class="d-flex addapproval"> |
|
54 |
+ <div class="d-flex align-items-center border-x"> |
|
55 |
+ <p>{{ item.triperNm }} {{ item.clsfNm }}</p> |
|
56 |
+ <button type="button" @click="fnDelNmpr(idx)" @mousedown.stop> |
|
76 | 57 |
<CloseOutlined /> |
77 | 58 |
</button> |
78 |
- </form> |
|
59 |
+ </div> |
|
79 | 60 |
</div> |
61 |
+ </div> |
|
80 | 62 |
</div> |
81 |
- |
|
82 | 63 |
<div class="col-12"> |
83 | 64 |
<label for="member" class="form-label"> |
84 |
- <p>승인자<button type="button" title="추가" @click="showPopup = true"> |
|
65 |
+ <p>승인자 <span class="require"><img :src="require" alt=""></span></p> |
|
66 |
+ <button type="button" title="추가" @click="isOpenSanctnModal = true"> |
|
85 | 67 |
<PlusCircleFilled /> |
86 |
- </button><p class="require"><img :src="require" alt=""></p></p> |
|
87 |
- |
|
68 |
+ </button> |
|
88 | 69 |
</label> |
89 |
- <HrPopup v-if="showPopup" @close="showPopup = false" @select="addApproval"/> |
|
70 |
+ <HrPopup v-if="isOpenSanctnModal" :lists="bsrpCnsul.sanctnList" @onSelected="fnAddSanctn" @close="isOpenSanctnModal = false" /> |
|
90 | 71 |
<div class="approval-container"> |
91 |
- <div v-for="(approval, index) in approvals" :key="index" class="d-flex gap-2 addapproval mb-2"> |
|
92 |
- <select class="form-select" v-model="approval.category" style="width: 110px;"> |
|
93 |
- <option value="결재">결재</option> |
|
94 |
- <option value="전결">전결</option> |
|
95 |
- <option value="대결">대결</option> |
|
96 |
- </select> |
|
97 |
- |
|
98 |
- <form class="d-flex align-items-center border-x"> |
|
99 |
- <input type="text" class="form-control" v-model="approval.name" style="max-width: 150px;" /> |
|
100 |
- <button type="button" @click="removeApproval(index)" class="delete-button"> |
|
101 |
- <CloseOutlined /> |
|
102 |
- </button> |
|
103 |
- </form> |
|
104 |
- </div> |
|
72 |
+ <SanctnList v-model:lists="bsrpCnsul.sanctnList" @delSanctn="fnDelSanctn" /> |
|
105 | 73 |
</div> |
106 | 74 |
</div> |
107 |
- <div class="col-12 chuljang"> |
|
108 |
- <label for="prvonsh" class="form-label"><p>품의내용<p class="require"><img :src="require" alt=""></p></p></label> |
|
109 |
- <input type="text" class="form-control textarea" id="reason" v-model="reason" /> |
|
75 |
+ <div class="col-12"> |
|
76 |
+ <label for="prvonsh" class="form-label"> |
|
77 |
+ <p>품의내용 <span class="require"><img :src="require" alt=""></span></p> |
|
78 |
+ </label> |
|
79 |
+ <EditorComponent v-model:contents="bsrpCnsul.cn" /> |
|
110 | 80 |
</div> |
111 | 81 |
<div class="col-12"> |
112 |
- <label for="purpose" class="form-label">법인카드 <button type="button" title="추가" @click="showPopup3 = true"> |
|
82 |
+ <label for="purpose" class="form-label"> |
|
83 |
+ <span>법인카드</span> |
|
84 |
+ <button type="button" title="추가" @click="isOpenCardModal = true"> |
|
113 | 85 |
<PlusCircleFilled /> |
114 |
- </button></label> |
|
115 |
- <CorpCardPopup v-if="showPopup3" @close="showPopup3 = false" @select="addCard"/> |
|
116 |
- <div class="approval-container"> |
|
117 |
- <div v-for="(card, index) in cards" :key="index" class="d-flex gap-2 addapproval mb-2"> |
|
118 |
- |
|
86 |
+ </button> |
|
87 |
+ </label> |
|
88 |
+ <CorpCardPopup v-if="isOpenCardModal" :bsrpInfo="bsrpInfo" :lists="cprCardList" @close="isOpenCardModal = false" @onSelected="fnAddCard" /> |
|
89 |
+ <div class="approval-container"> |
|
90 |
+ <div v-for="(card, idx) in cprCardList" :key="idx" class="d-flex gap-2 addapproval mb-2"> |
|
119 | 91 |
<form class="d-flex align-items-center border-x"> |
120 |
- <input type="text" class="form-control" v-model="card.name" style="max-width: 150px;" /> |
|
121 |
- <button type="button" @click="removeCard(index)" class="delete-button"> |
|
92 |
+ <p>{{ card.cardNm }}</p> |
|
93 |
+ <button type="button" @click="fnDelCard(idx)" class="delete-button"> |
|
122 | 94 |
<CloseOutlined /> |
123 | 95 |
</button> |
124 | 96 |
</form> |
... | ... | @@ -126,215 +98,316 @@ |
126 | 98 |
</div> |
127 | 99 |
</div> |
128 | 100 |
<div class="col-12 border-x"> |
129 |
- <label for="purpose" class="form-label">법인차량 <button type="button" title="추가" @click="showPopup2 = true"> |
|
101 |
+ <label for="purpose" class="form-label"> |
|
102 |
+ <span>법인차량</span> |
|
103 |
+ <button type="button" title="추가" @click="isOpenVhcleModal = true"> |
|
130 | 104 |
<PlusCircleFilled /> |
131 |
- </button></label> |
|
132 |
- <CorpCarPopup v-if="showPopup2" @close="showPopup2 = false" @select="addCar"/> |
|
133 |
- <div class="approval-container"> |
|
134 |
- <div v-for="(car, index) in cars" :key="index" class="d-flex gap-2 addapproval mb-2"> |
|
135 |
- <input type="text" class="form-control" v-model="car.name" style="max-width: 150px;" /> |
|
136 |
- <select class="form-select" v-model="car.category" style="width: 120px;"> |
|
137 |
- <option value="운전자">운전자</option> |
|
105 |
+ </button> |
|
106 |
+ </label> |
|
107 |
+ <CorpCarPopup v-if="isOpenVhcleModal" :bsrpInfo="bsrpInfo" :lists="cprVhcleList" @close="isOpenVhcleModal = false" @onSelected="fnAddVhcle" /> |
|
108 |
+ <div class="approval-container"> |
|
109 |
+ <div v-for="(vhcle, idx) in cprVhcleList" :key="idx" class="d-flex gap-2 addapproval mb-2"> |
|
110 |
+ <p>{{ vhcle.vhcleNm }}</p> |
|
111 |
+ <select class="form-select" v-model="vhcle.drverId"> |
|
112 |
+ <option value="" disabled hidden>운전자 선택</option> |
|
113 |
+ <option :value="userInfo.userId">{{ userInfo.userNm }}</option> |
|
114 |
+ <option v-for="(item, idx) of bsrpInfo.bsrpNmprList" :key="idx" :value="item.userId">{{ item.userNm }}</option> |
|
138 | 115 |
</select> |
139 |
- <button type="button" @click="removeCar(index)" class="delete-button"> |
|
140 |
- <CloseOutlined /> |
|
141 |
- </button> |
|
116 |
+ <button type="button" @click="fnDelVhcle(idx)" class="delete-button"> |
|
117 |
+ <CloseOutlined /> |
|
118 |
+ </button> |
|
142 | 119 |
</div> |
143 | 120 |
</div> |
144 | 121 |
</div> |
145 |
- |
|
146 | 122 |
</form> |
147 | 123 |
<div class="buttons"> |
148 |
- <button type="submit" class="btn primary">신청</button> |
|
149 |
- <button type="reset" class="btn secondary">취소</button> |
|
124 |
+ <button type="button" class="btn primary" @click="fnSave">신청</button> |
|
125 |
+ <button v-if="$isEmpty(pageId)" type="button" class="btn secondary" @click="fnMoveTo('list')">목록</button> |
|
126 |
+ <button v-else type="button" class="btn secondary" @click="fnMoveTo('view', pageId)">취소</button> |
|
150 | 127 |
</div> |
151 |
- |
|
152 | 128 |
</div> |
153 | 129 |
</div> |
154 | 130 |
</template> |
155 |
- |
|
156 | 131 |
<script> |
157 | 132 |
import { PlusCircleFilled, CloseOutlined } from '@ant-design/icons-vue'; |
158 | 133 |
import HrPopup from '../../../component/Popup/HrPopup.vue'; |
159 | 134 |
import CorpCarPopup from '../../../component/Popup/CorpCarPopup.vue'; |
160 | 135 |
import CorpCardPopup from '../../../component/Popup/CorpCardPopup.vue'; |
136 |
+import SanctnList from '../../../component/Sanctn/SanctnFormList.vue'; |
|
137 |
+import EditorComponent from '../../../component/editor/EditorComponent.vue'; |
|
138 |
+// API |
|
139 |
+import { saveBsrpProc, bsrpProc, updateBsrpProc } from '../../../../resources/api/bsrp'; |
|
140 |
+ |
|
161 | 141 |
export default { |
142 |
+ components: { |
|
143 |
+ PlusCircleFilled, CloseOutlined, |
|
144 |
+ HrPopup, CorpCarPopup, CorpCardPopup, |
|
145 |
+ SanctnList, EditorComponent, |
|
146 |
+ }, |
|
147 |
+ |
|
162 | 148 |
data() { |
163 |
- const today = new Date().toISOString().split('T')[0]; |
|
164 | 149 |
return { |
165 |
- showPopup: false, |
|
166 |
- showPopup1: false, |
|
167 |
- showPopup2: false, |
|
168 |
- showPopup3: false, |
|
169 | 150 |
require: "/client/resources/img/require.png", |
170 |
- fileName: '', |
|
171 |
- startDate: today, |
|
172 |
- startTime: '09:00', |
|
173 |
- endDate: today, |
|
174 |
- endTime: '18:00', |
|
175 |
- where: '', |
|
176 |
- purpose: '', |
|
177 |
- approvals: [ |
|
178 |
- ], |
|
179 |
- members: [ |
|
180 |
- ], |
|
181 |
- cards: [ |
|
182 |
- ], |
|
183 |
- cars: [ |
|
184 |
- ], |
|
185 |
- receipts: [ |
|
186 |
- { |
|
187 |
- type: '개인결제', |
|
188 |
- category: '결재', |
|
189 |
- category1: '구분', |
|
190 |
- }, |
|
191 |
- ], |
|
151 |
+ |
|
152 |
+ pageId: null, |
|
153 |
+ userInfo: this.$store.state.userInfo, |
|
154 |
+ cmmnCodes: {}, |
|
155 |
+ |
|
156 |
+ isOpenNmprModal: false, |
|
157 |
+ isOpenSanctnModal: false, |
|
158 |
+ isOpenCardModal: false, |
|
159 |
+ isOpenVhcleModal: false, |
|
160 |
+ |
|
161 |
+ // 출장정보 |
|
162 |
+ bsrpInfo: { |
|
163 |
+ applcntId: null, // 신청자 아이디 |
|
164 |
+ bsrpSe: null, // 출장구분 |
|
165 |
+ bsrpPlace: null, // 출장지 |
|
166 |
+ bsrpPurps: null, // 출장목적 |
|
167 |
+ bgnde: null, // 시작일 |
|
168 |
+ beginHour: null, // 시작시 |
|
169 |
+ beginMnt: null, // 시작분 |
|
170 |
+ endde: null, // 종료일 |
|
171 |
+ endHour: null, // 종료시 |
|
172 |
+ endMnt: null, // 종료분 |
|
173 |
+ bsrpNmprList: [], // 출장인원 목록 |
|
174 |
+ }, |
|
175 |
+ // 출장품의 |
|
176 |
+ bsrpCnsul: { |
|
177 |
+ bsrpId: null, // 출장 아이디 |
|
178 |
+ cn: null, // 품의 내용 |
|
179 |
+ confmAt: null, // 승인여부 |
|
180 |
+ rgsde: null, // 등록일 |
|
181 |
+ register: null, // 등록자 |
|
182 |
+ updde: null, // 수정일 |
|
183 |
+ updusr: null, // 수정자 |
|
184 |
+ sanctnList: [], // 결재 목록 |
|
185 |
+ }, |
|
186 |
+ cprCardList: [], // 법인카드 목록 |
|
187 |
+ cprVhcleList: [], // 법인차량 목록 |
|
188 |
+ |
|
189 |
+ totalDays: 0, // 일수 |
|
192 | 190 |
}; |
193 | 191 |
}, |
194 |
- components: { |
|
195 |
- PlusCircleFilled, CloseOutlined, HrPopup, CorpCarPopup, CorpCardPopup |
|
196 |
- }, |
|
197 |
- computed: { |
|
198 |
- loginUser() { |
|
199 |
- const authStore = useAuthStore(); |
|
200 |
- return authStore.getLoginUser; |
|
192 |
+ |
|
193 |
+ computed: {}, |
|
194 |
+ |
|
195 |
+ watch: { |
|
196 |
+ cmmnCodes(newVal) { |
|
197 |
+ if (Object.keys(newVal).length > 0) { |
|
198 |
+ this.bsrpInfo.bsrpSe = this.cmmnCodes.bsrpCodeList[0].code; |
|
199 |
+ } |
|
201 | 200 |
}, |
201 |
+ 'bsrpInfo.bgnde'(newVal, oldVal) { |
|
202 |
+ if (newVal !== oldVal) { |
|
203 |
+ this.calcDayCnt(); // 일수 계산 |
|
204 |
+ } |
|
205 |
+ }, |
|
206 |
+ 'bsrpInfo.endde'(newVal, oldVal) { |
|
207 |
+ if (newVal !== oldVal) { |
|
208 |
+ this.calcDayCnt(); // 일수 계산 |
|
209 |
+ } |
|
210 |
+ }, |
|
211 |
+ }, |
|
212 |
+ |
|
213 |
+ async created() { |
|
214 |
+ this.pageId = this.$route.query.id; |
|
215 |
+ this.cmmnCodes = await this.$defaultCodes(); |
|
216 |
+ }, |
|
217 |
+ |
|
218 |
+ mounted() { |
|
219 |
+ if (!this.$isEmpty(this.pageId)) { |
|
220 |
+ this.findData(); |
|
221 |
+ } |
|
202 | 222 |
}, |
203 | 223 |
|
204 | 224 |
methods: { |
205 |
- handleFileUpload(event) { |
|
206 |
- const file = event.target.files[0]; |
|
207 |
- if (file) { |
|
208 |
- this.fileName = file.name; |
|
225 |
+ // 상세 조회 |
|
226 |
+ async findData() { |
|
227 |
+ try { |
|
228 |
+ const response = await bsrpProc(this.pageId); |
|
229 |
+ const result = response.data.data; |
|
230 |
+ |
|
231 |
+ this.bsrpInfo = result.bsrpInfo; |
|
232 |
+ this.bsrpInfo.bgnde = this.bsrpInfo.bgnde.split(' ')[0]; |
|
233 |
+ this.bsrpInfo.endde = this.bsrpInfo.endde.split(' ')[0]; |
|
234 |
+ |
|
235 |
+ this.bsrpCnsul = result.bsrpCnsul; |
|
236 |
+ this.cprCardList = result.cprCardList; |
|
237 |
+ this.cprVhcleList = result.cprVhcleList; |
|
238 |
+ } catch (error) { |
|
239 |
+ console.error('데이터 조회 실패:', error); |
|
240 |
+ alert(error.response.data.message); |
|
241 |
+ |
|
242 |
+ this.fnMoveTo('list'); |
|
209 | 243 |
} |
210 | 244 |
}, |
211 |
- addApproval(selectedUser) { |
|
212 |
- this.approvals.push({ |
|
213 |
- category: '결재', |
|
214 |
- name: selectedUser.name, // or other fields if needed |
|
215 |
- }); |
|
216 |
- this.showPopup = false; // 팝업 닫기 |
|
245 |
+ |
|
246 |
+ // 일수 계산 |
|
247 |
+ calcDayCnt() { |
|
248 |
+ if (!this.bsrpInfo.bgnde || !this.bsrpInfo.endde) { |
|
249 |
+ this.totalDays = 0; |
|
250 |
+ return; |
|
251 |
+ } |
|
252 |
+ |
|
253 |
+ const startDate = new Date(this.bsrpInfo.bgnde); |
|
254 |
+ const endDate = new Date(this.bsrpInfo.endde); |
|
255 |
+ |
|
256 |
+ if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) { |
|
257 |
+ this.totalDays = 0; |
|
258 |
+ return; |
|
259 |
+ } |
|
260 |
+ |
|
261 |
+ const timeDiff = endDate.getTime() - startDate.getTime(); |
|
262 |
+ const dayDiff = Math.floor(timeDiff / (1000 * 60 * 60 * 24)) + 1; |
|
263 |
+ |
|
264 |
+ this.totalDays = Math.max(0, dayDiff); |
|
217 | 265 |
}, |
218 |
- addMember(selectedUser) { |
|
219 |
- this.members.push({ |
|
220 |
- category: '결재', |
|
221 |
- name: selectedUser.name, // or other fields if needed |
|
222 |
- }); |
|
223 |
- this.showPopup1 = false; // 팝업 닫기 |
|
266 |
+ |
|
267 |
+ // 동행자 추가 |
|
268 |
+ fnAddNmpr(user) { |
|
269 |
+ const data = { |
|
270 |
+ triperId: user.userId, // 출장자 아이디 |
|
271 |
+ deptId: user.deptId, // 부서 아이디 |
|
272 |
+ clsf: user.clsf, // 직급 |
|
273 |
+ |
|
274 |
+ triperNm: user.userNm, // 출장자 이름 |
|
275 |
+ deptNm: user.deptNm, // 부서 이름 |
|
276 |
+ clsfNm: user.clsfNm, // 직급 이름 |
|
277 |
+ }; |
|
278 |
+ |
|
279 |
+ this.bsrpInfo.bsrpNmprList.push(data); |
|
280 |
+ this.isOpenNmprModal = false; |
|
224 | 281 |
}, |
225 |
- addCard(selectedCard) { |
|
226 |
- this.cards.push({ |
|
227 |
- category: '결재', |
|
228 |
- name: selectedCard.name, // or other fields if needed |
|
229 |
- }); |
|
230 |
- this.showPopup3 = false; // 팝업 닫기 |
|
282 |
+ |
|
283 |
+ // 동행자 삭제 |
|
284 |
+ fnDelNmpr(idx) { |
|
285 |
+ this.bsrpInfo.bsrpNmprList.splice(idx, 1); |
|
231 | 286 |
}, |
232 |
- addCar(selectedCar) { |
|
233 |
- this.cars.push({ |
|
234 |
- category: '결재', |
|
235 |
- name: selectedCar.name, // or other fields if needed |
|
236 |
- }); |
|
237 |
- this.showPopup2 = false; // 팝업 닫기 |
|
287 |
+ |
|
288 |
+ // 승인자 추가 |
|
289 |
+ fnAddSanctn(user) { |
|
290 |
+ const data = { |
|
291 |
+ sanctnId: null, // 결재 아이디 |
|
292 |
+ confmerId: user.userId, // 승인자 아이디 |
|
293 |
+ clsf: user.clsf, // 직급 |
|
294 |
+ sanctnOrdr: this.bsrpCnsul.sanctnList.length + 1, // 결재 순서 |
|
295 |
+ sanctnIem: 'cnsul', // 결재 항목 |
|
296 |
+ sanctnMbyId: null, // 결재 주체 아이디 |
|
297 |
+ sanctnSe: this.cmmnCodes.sanctnCodeList[0].code, // 결재구분 |
|
298 |
+ |
|
299 |
+ confmerNm: user.userNm, // 승인자 이름 |
|
300 |
+ clsfNm: user.clsfNm, // 직급 이름 |
|
301 |
+ }; |
|
302 |
+ |
|
303 |
+ this.bsrpCnsul.sanctnList.push(data); |
|
304 |
+ this.isOpenSanctnModal = false; |
|
238 | 305 |
}, |
239 |
- addReceipt() { |
|
240 |
- this.receipts.push({ |
|
241 |
- type: '개인결제', |
|
242 |
- category: '결재', |
|
243 |
- category1: '', |
|
244 |
- name: '', |
|
245 |
- file: null, |
|
246 |
- }); |
|
247 |
-}, |
|
306 |
+ |
|
248 | 307 |
// 승인자 삭제 |
249 |
- removeApproval(index) { |
|
250 |
- this.approvals.splice(index, 1); |
|
308 |
+ fnDelSanctn(idx) { |
|
309 |
+ this.bsrpInfo.sanctnList.splice(idx, 1); |
|
310 |
+ this.bsrpInfo.sanctnList.forEach((item, idx) => { item.sanctnOrdr = idx + 1; }); |
|
251 | 311 |
}, |
252 |
- removeMember(index) { |
|
253 |
- this.members.splice(index, 1); |
|
312 |
+ |
|
313 |
+ // 법인카드 추가 |
|
314 |
+ fnAddCard(item) { |
|
315 |
+ this.cprCardList.push(item); |
|
316 |
+ this.isOpenCardModal = false; |
|
254 | 317 |
}, |
255 |
- removeCard(index) { |
|
256 |
- this.cards.splice(index, 1); |
|
318 |
+ |
|
319 |
+ // 법인카드 삭제 |
|
320 |
+ fnDelCard(idx) { |
|
321 |
+ this.cprCardList.splice(idx, 1); |
|
257 | 322 |
}, |
258 |
- removeCar(index) { |
|
259 |
- this.cars.splice(index, 1); |
|
323 |
+ |
|
324 |
+ // 법인차량 추가 |
|
325 |
+ fnAddVhcle(item) { |
|
326 |
+ item.drverId = ''; |
|
327 |
+ this.cprVhcleList.push(item); |
|
328 |
+ this.isOpenVhcleModal = false; |
|
260 | 329 |
}, |
261 |
- removeReceipt(index) { |
|
262 |
- this.receipts.splice(index, 1); |
|
263 |
-}, |
|
330 |
+ |
|
331 |
+ // 법인차량 삭제 |
|
332 |
+ fnDelVhcle(idx) { |
|
333 |
+ this.cprVhcleList.splice(idx, 1); |
|
334 |
+ }, |
|
335 |
+ |
|
336 |
+ // 유효성 검사 |
|
264 | 337 |
validateForm() { |
265 |
- // 필수 입력 필드 체크 |
|
266 |
- if ( |
|
267 |
- this.startDate && |
|
268 |
- this.startTime && |
|
269 |
- this.endDate && |
|
270 |
- this.endTime && |
|
271 |
- this.where && |
|
272 |
- this.purpose.trim() !== "" |
|
273 |
- ) { |
|
274 |
- this.isFormValid = true; |
|
275 |
- } else { |
|
276 |
- this.isFormValid = false; |
|
338 |
+ if (this.$isEmpty(this.bsrpInfo.bsrpSe)) { |
|
339 |
+ return false; |
|
277 | 340 |
} |
341 |
+ if (this.$isEmpty(this.bsrpInfo.bsrpPlace)) { |
|
342 |
+ return false; |
|
343 |
+ } |
|
344 |
+ if (this.$isEmpty(this.bsrpInfo.bsrpPurps)) { |
|
345 |
+ return false; |
|
346 |
+ } |
|
347 |
+ if (this.$isEmpty(this.bsrpInfo.bgnde) || this.$isEmpty(this.bsrpInfo.beginHour) || this.$isEmpty(this.bsrpInfo.beginMnt)) { |
|
348 |
+ return false; |
|
349 |
+ } |
|
350 |
+ if (this.$isEmpty(this.bsrpInfo.endde) || this.$isEmpty(this.bsrpInfo.endHour) || this.$isEmpty(this.bsrpInfo.endMnt)) { |
|
351 |
+ return false; |
|
352 |
+ } |
|
353 |
+ if (this.$isEmpty(this.bsrpInfo.bsrpNmprList) || this.bsrpInfo.bsrpNmprList.length === 0) { |
|
354 |
+ return false; |
|
355 |
+ } |
|
356 |
+ if (this.$isEmpty(this.bsrpCnsul.cn)) { |
|
357 |
+ return false; |
|
358 |
+ } |
|
359 |
+ |
|
360 |
+ return true; |
|
278 | 361 |
}, |
279 |
- calculateDayCount() { |
|
280 |
- const start = new Date(`${this.startDate}T${this.startTime}:00`); |
|
281 |
- const end = new Date(`${this.endDate}T${this.endTime}:00`); |
|
282 | 362 |
|
283 |
- let totalHours = (end - start) / (1000 * 60 * 60); // 밀리초를 시간 단위로 변환 |
|
284 |
- |
|
285 |
- if (this.startDate !== this.endDate) { |
|
286 |
- // 시작일과 종료일이 다른경우 |
|
287 |
- const startDateObj = new Date(this.startDate); |
|
288 |
- const endDateObj = new Date(this.endDate); |
|
289 |
- const daysDifference = (endDateObj - startDateObj) / (1000 * 60 * 60 * 24); // 두 날짜 사이의 차이를 일수로 계산 |
|
290 |
- if (this.startTime !== "09:00" || this.endTime !== "18:00") { |
|
291 |
- this.dayCount = daysDifference + 0.5; // 시간 조건이 기준에서 벗어날 경우 |
|
292 |
- } else { |
|
293 |
- this.dayCount = Math.ceil(daysDifference + 1); // 시간 조건이 기준에 맞을 경우 |
|
363 |
+ // 신청 |
|
364 |
+ async fnSave() { |
|
365 |
+ try { |
|
366 |
+ if (!this.validateForm()) { |
|
367 |
+ return; |
|
294 | 368 |
} |
295 |
- } else { |
|
296 |
- // 시작일과 종료일이 같은 경우 |
|
297 |
- if (this.startTime !== "09:00" || this.endTime !== "18:00") { |
|
298 |
- this.dayCount = 0.5; // 시작 시간 또는 종료 시간이 기준과 다를 경우 0.5 |
|
299 |
- } else { |
|
300 |
- this.dayCount = 1; // 기준 시간(09:00~18:00)이 맞으면 1일로 간주 |
|
301 |
- } |
|
302 |
- } |
|
303 | 369 |
|
304 |
- this.validateForm(); // dayCount 변경 후 폼 재검증 |
|
370 |
+ let data = { |
|
371 |
+ bsrpCnsulInsertDTO: this.bsrpCnsul, |
|
372 |
+ cardDtlsList: this.cprCardList, |
|
373 |
+ vhcleDtlsList: this.cprVhcleList, |
|
374 |
+ } |
|
375 |
+ if (this.$isEmpty(this.pageId)) { |
|
376 |
+ data.bsrpInfoInsertDTO = this.bsrpInfo; |
|
377 |
+ } else { |
|
378 |
+ data.bsrpInfoUpdateDTO = this.bsrpInfo; |
|
379 |
+ } |
|
380 |
+ |
|
381 |
+ const response = this.$isEmpty(this.pageId) ? await saveBsrpProc(data) : await updateBsrpProc(data); |
|
382 |
+ const message = this.$isEmpty(this.pageId) ? "등록되었습니다." : "수정되었습니다."; |
|
383 |
+ alert(message); |
|
384 |
+ |
|
385 |
+ this.fnMoveTo('view', response.data.data.pageId); |
|
386 |
+ } catch (error) { |
|
387 |
+ console.error('저장 실패:', error); |
|
388 |
+ const message = error.response?.data?.message || "저장에 실패했습니다."; |
|
389 |
+ alert(message); |
|
390 |
+ } |
|
305 | 391 |
}, |
306 |
- handleSubmit() { |
|
307 |
- this.validateForm(); // 제출 시 유효성 확인 |
|
308 |
- if (this.isFormValid) { |
|
309 |
- localStorage.setItem('ChuljangFormData', JSON.stringify(this.$data)); |
|
310 |
- alert("승인 요청이 완료되었습니다."); |
|
311 |
- // 추가 처리 로직 (API 요청 등) |
|
392 |
+ |
|
393 |
+ fnMoveTo(type, id) { |
|
394 |
+ const routes = { |
|
395 |
+ 'list': { name: 'ChuljangStatue' }, |
|
396 |
+ 'view': { name: 'ChuljangDetailAll', query: { id } }, |
|
397 |
+ 'edit': { name: 'ChuljangInsert', query: this.$isEmpty(id) ? {} : { id } }, |
|
398 |
+ }; |
|
399 |
+ |
|
400 |
+ if (routes[type]) { |
|
401 |
+ if (!this.$isEmpty(this.pageId) && type === 'list') { |
|
402 |
+ this.$router.push({ name: 'ChuljangDetailAll', query: { id: this.pageId } }); |
|
403 |
+ return; |
|
404 |
+ } |
|
405 |
+ this.$router.push(routes[type]); |
|
312 | 406 |
} else { |
313 |
- alert("모든 필드를 올바르게 작성해주세요."); |
|
407 |
+ alert("올바르지 않은 경로를 요청하여 목록으로 이동합니다."); |
|
408 |
+ this.$router.push(routes['list']); |
|
314 | 409 |
} |
315 | 410 |
}, |
316 |
- loadFormData() { |
|
317 |
- const savedData = localStorage.getItem('ChuljangFormData'); |
|
318 |
- if (savedData) { |
|
319 |
- this.$data = JSON.parse(savedData); |
|
320 |
- } |
|
321 |
- }, |
|
322 |
- }, |
|
323 |
- mounted() { |
|
324 |
- // Load the saved form data when the page is loaded |
|
325 |
- this.loadFormData(); |
|
326 |
- }, |
|
327 |
- watch: { |
|
328 |
- startDate: 'calculateDayCount', |
|
329 |
- startTime: 'calculateDayCount', |
|
330 |
- endDate: 'calculateDayCount', |
|
331 |
- endTime: 'calculateDayCount', |
|
332 |
- where: 'validateForm', |
|
333 |
- purpose: "validateForm", |
|
334 | 411 |
}, |
335 | 412 |
}; |
336 |
-</script> |
|
337 |
- |
|
338 |
-<style scoped> |
|
339 |
-/* 필요한 스타일 추가 */ |
|
340 |
-</style> |
|
413 |
+</script>(파일 끝에 줄바꿈 문자 없음) |
--- client/views/pages/Manager/attendance/ChuljangPumuiDetail.vue
... | ... | @@ -1,186 +0,0 @@ |
1 | -<template> | |
2 | - <div class="card "> | |
3 | - <div class="card-body"> | |
4 | - <h2 class="card-title">출장 현황</h2> | |
5 | - | |
6 | - <div class="form-card"> | |
7 | - <h1>출장품의서</h1> | |
8 | - <div class="approval-box tbl-wrap tbl2"> | |
9 | - <table class="tbl data"> | |
10 | - <tbody> | |
11 | - <tr class="thead"> | |
12 | - <td rowspan="2" class="th">승인자</td> | |
13 | - <td v-for="(approver, index) in approvers" :key="index"> | |
14 | - <p class="position">{{ approver.position }}</p> | |
15 | - </td> | |
16 | - </tr> | |
17 | - <tr> | |
18 | - <td v-for="(approver, index) in approvers" :key="index"> | |
19 | - <p class="name">{{ approver.name }}</p> | |
20 | - <p class="date">{{ approver.date }}</p> | |
21 | - </td> | |
22 | - </tr> | |
23 | - | |
24 | - </tbody> | |
25 | - | |
26 | - </table> | |
27 | - </div> | |
28 | - <form class="row g-3 needs-validation detail" :class="{ 'was-validated': formSubmitted }" | |
29 | - @submit.prevent="handleRegister" novalidate> | |
30 | - <div class="col-12 "> | |
31 | - <div class="col-12 border-x"> | |
32 | - <label for="youremail" class="form-label ">출장구분<p class="require"><img :src="require" alt=""></p></label> | |
33 | - <input v-model="email" type="text" name="username" class="form-control" id="youremail" readonly> | |
34 | - </div> | |
35 | - | |
36 | - <div class="col-12 border-x"> | |
37 | - <label for="yourPassword" class="form-label">이름</label> | |
38 | - <input v-model="password" type="password" name="password" class="form-control" readonly | |
39 | - placeholder="주식회사 테이큰 소프트"> | |
40 | - </div> | |
41 | - </div> | |
42 | - <div class="col-12"> | |
43 | - <div class="col-12 border-x"> | |
44 | - <label for="youremail" class="form-label">부서</label> | |
45 | - <input v-model="email" type="text" name="username" class="form-control" readonly placeholder="과장"> | |
46 | - </div> | |
47 | - | |
48 | - <div class="col-12 border-x"> | |
49 | - <label for="yourPassword" class="form-label">직급</label> | |
50 | - <input v-model="password" type="password" name="password" class="form-control" readonly placeholder="팀장"> | |
51 | - </div> | |
52 | - </div> | |
53 | - <div class="col-12"> | |
54 | - <label for="yourName" class="form-label">출장지</label> | |
55 | - <input v-model="name" type="text" name="name" class="form-control" readonly> | |
56 | - </div> | |
57 | - <div class="col-12"> | |
58 | - <label for="yourName" class="form-label">출장목적</label> | |
59 | - <input v-model="name" type="text" name="name" class="form-control " readonly> | |
60 | - </div> | |
61 | - <div class="col-12"> | |
62 | - <label for="yourName" class="form-label">동행자</label> | |
63 | - <input v-model="name" type="text" name="name" class="form-control " readonly> | |
64 | - </div> | |
65 | - <div class="col-12 chuljang"> | |
66 | - <label for="yourName" class="form-label">내용</label> | |
67 | - <input v-model="name" type="text" name="name" class="form-control textarea " readonly> | |
68 | - </div> | |
69 | - <div class="col-12"> | |
70 | - <label for="yourName" class="form-label">법인카드</label> | |
71 | - <input v-model="name" type="text" name="name" class="form-control " readonly> | |
72 | - </div> | |
73 | - <div class="col-12"> | |
74 | - <label for="yourName" class="form-label">법인차량</label> | |
75 | - <input v-model="name" type="text" name="name" class="form-control " readonly> | |
76 | - </div> | |
77 | - <div class="col-12"> | |
78 | - <label for="yourName" class="form-label">품의 신청일</label> | |
79 | - <input v-model="name" type="text" name="name" class="form-control " readonly> | |
80 | - </div> | |
81 | - <div class="col-12 border-x return"> | |
82 | - <label for="yourName" class="form-label">반려사유</label> | |
83 | - <input v-model="name" type="text" name="name" class="form-control" readonly placeholder="2025-01-01"> | |
84 | - </div> | |
85 | - | |
86 | - | |
87 | - </form> | |
88 | - </div> | |
89 | - <div class="buttons"> | |
90 | - <button class="btn btn-red" type="submit">신청취소</button> | |
91 | - <button class="btn secondary" type="submit">재신청</button> | |
92 | - <button class="btn secondary" type="submit">수정</button> | |
93 | - <button v-if="hasAnyApprover" class="btn primary" type="submit" @click="goToBokmyeongInsert"> | |
94 | - 복명서 작성 | |
95 | - </button> | |
96 | - <button class="btn tertiary " type="submit">목록</button> | |
97 | - </div> | |
98 | - | |
99 | - </div> | |
100 | - </div> | |
101 | -</template> | |
102 | - | |
103 | -<script> | |
104 | -export default { | |
105 | - data() { | |
106 | - const today = new Date().toISOString().split('T')[0]; | |
107 | - return { | |
108 | - showPopup: false, | |
109 | - startDate: today, | |
110 | - startTime: "09:00", // 기본 시작 시간 09:00 | |
111 | - endDate: today, | |
112 | - endTime: "18:00", // 기본 종료 시간 18:00 | |
113 | - category: "", | |
114 | - dayCount: 1, | |
115 | - reason: "", // 사유 | |
116 | - approvers: [ | |
117 | - { position: '', name: '', date: '' }, | |
118 | - { position: '', name: '', date: '' }, | |
119 | - ], | |
120 | - listData: [ | |
121 | - { | |
122 | - type: '연차', | |
123 | - approvalType: '결재', | |
124 | - applicant: '홍길동', | |
125 | - period: '2025-05-10 ~ 2025-15-03', | |
126 | - requestDate: '2025-04-25', | |
127 | - status: '대기' | |
128 | - }, { | |
129 | - type: '반차', | |
130 | - approvalType: '전결', | |
131 | - applicant: '홍길동', | |
132 | - period: '2025-05-01 ~ 2025-05-03', | |
133 | - requestDate: '2025-04-25', | |
134 | - status: '승인' | |
135 | - }], | |
136 | - }; | |
137 | - }, | |
138 | - computed: { | |
139 | - }, | |
140 | - methods: { | |
141 | - goToBokmyeongInsert() { | |
142 | - this.$router.push({ name: 'BokmyeongInsert' }); | |
143 | - }, | |
144 | - hasAnyApprover() { | |
145 | - return this.approvers.some( | |
146 | - (approver) => | |
147 | - approver.name?.trim() !== '' && approver.date?.trim() !== '' | |
148 | - ); | |
149 | - }, | |
150 | - calculateDayCount() { | |
151 | - const start = new Date(`${this.startDate}T${this.startTime}:00`); | |
152 | - const end = new Date(`${this.endDate}T${this.endTime}:00`); | |
153 | - | |
154 | - let totalHours = (end - start) / (1000 * 60 * 60); // 밀리초를 시간 단위로 변환 | |
155 | - | |
156 | - if (this.startDate !== this.endDate) { | |
157 | - // 시작일과 종료일이 다른경우 | |
158 | - const startDateObj = new Date(this.startDate); | |
159 | - const endDateObj = new Date(this.endDate); | |
160 | - const daysDifference = (endDateObj - startDateObj) / (1000 * 60 * 60 * 24); // 두 날짜 사이의 차이를 일수로 계산 | |
161 | - if (this.startTime !== "09:00" || this.endTime !== "18:00") { | |
162 | - this.dayCount = daysDifference + 0.5; // 시간 조건이 기준에서 벗어날 경우 | |
163 | - } else { | |
164 | - this.dayCount = Math.ceil(daysDifference + 1); // 시간 조건이 기준에 맞을 경우 | |
165 | - } | |
166 | - } else { | |
167 | - // 시작일과 종료일이 같은 경우 | |
168 | - if (this.startTime !== "09:00" || this.endTime !== "18:00") { | |
169 | - this.dayCount = 0.5; // 시작 시간 또는 종료 시간이 기준과 다를 경우 0.5 | |
170 | - } else { | |
171 | - this.dayCount = 1; // 기준 시간(09:00~18:00)이 맞으면 1일로 간주 | |
172 | - } | |
173 | - } | |
174 | - | |
175 | - }, | |
176 | - | |
177 | - | |
178 | - | |
179 | - }, | |
180 | -}; | |
181 | -</script> | |
182 | -<style scoped> | |
183 | -td p { | |
184 | - width: 125px; | |
185 | -} | |
186 | -</style> |
--- client/views/pages/Manager/attendance/ChuljangStatue.vue
+++ client/views/pages/Manager/attendance/ChuljangStatue.vue
... | ... | @@ -3,42 +3,23 @@ |
3 | 3 |
<div class="card"> |
4 | 4 |
<div class="card-body"> |
5 | 5 |
<h2 class="card-title">출장 현황</h2> |
6 |
- <!-- 폼그룹 --> |
|
7 | 6 |
<div class="sch-form-wrap"> |
8 |
- <div class="input-group"> |
|
9 |
- <select name="" id="" class="form-select"> |
|
10 |
- <option :value="currentYear">{{ currentYear }}년</option> |
|
11 |
- <option value="all">전체</option> |
|
12 |
- <option |
|
13 |
- v-for="year in remainingYears" |
|
14 |
- :key="year" |
|
15 |
- :value="year" |
|
16 |
- v-if="year !== currentYear" |
|
17 |
- > |
|
18 |
- {{ year }}년 |
|
19 |
- </option> |
|
7 |
+ <div class="input-group"> |
|
8 |
+ <select class="form-select" v-model="request.year" @change="fnChangeCurrentPage(1)"> |
|
9 |
+ <option value="">연도 전체</option> |
|
10 |
+ <option v-for="year in years" :key="year" :value="year">{{ year }}년</option> |
|
20 | 11 |
</select> |
21 |
- <select name="" id="" class="form-select"> |
|
22 |
- <option :value="currentMonth">{{ currentMonth }}월</option> |
|
23 |
- <option value="all">전체</option> |
|
24 |
- <option |
|
25 |
- v-for="month in remainingMonths" |
|
26 |
- :key="month" |
|
27 |
- :value="month" |
|
28 |
- v-if="month !== currentMonth" |
|
29 |
- > |
|
30 |
- {{ month }}월 |
|
31 |
- </option> |
|
12 |
+ <select class="form-select" v-model="request.month" @change="fnChangeCurrentPage(1)"> |
|
13 |
+ <option value="">월 전체</option> |
|
14 |
+ <option v-for="month in months" :key="month" :value="month">{{ month }}월</option> |
|
32 | 15 |
</select> |
33 |
- </div> |
|
34 |
- </div> |
|
35 |
- <!-- Table --> |
|
16 |
+ </div> |
|
17 |
+ </div> |
|
36 | 18 |
<div class="tbl-wrap"> |
37 | 19 |
<table id="myTable" class="tbl data"> |
38 |
- <!-- 동적으로 <th> 생성 --> |
|
39 | 20 |
<thead> |
40 | 21 |
<tr> |
41 |
- <th>출장구분 </th> |
|
22 |
+ <th>출장구분</th> |
|
42 | 23 |
<th>출장지</th> |
43 | 24 |
<th>목적</th> |
44 | 25 |
<th>출장기간</th> |
... | ... | @@ -47,201 +28,113 @@ |
47 | 28 |
<th>복명서 상태</th> |
48 | 29 |
</tr> |
49 | 30 |
</thead> |
50 |
- <!-- 동적으로 <td> 생성 --> |
|
51 | 31 |
<tbody> |
52 |
- <tr v-for="(item, index) in listData" :key="index" :class="{ 'expired': isPast(item) }" @click="handleClick(item)"> |
|
53 |
- <td>{{ item.type }}</td> |
|
54 |
- <td>{{ item.where }}</td> |
|
55 |
- <td>{{ item.purpose }}</td> |
|
56 |
- <td >{{ item.period }}</td> |
|
57 |
-<td |
|
58 |
- :class="getStatusClass(item.pumuiStatue)" |
|
59 |
- |
|
60 |
-> |
|
61 |
- {{ item.pumuiStatue }} |
|
62 |
-</td> |
|
63 |
-<td |
|
64 |
- :class="getBokmyeongClass(item.bokmyeong)" |
|
65 |
-> |
|
66 |
- {{ item.bokmyeong }} |
|
67 |
-</td> |
|
68 |
- |
|
69 |
-<td :class="getStatusClass(item.status)"> |
|
70 |
- {{ item.status }} |
|
71 |
-</td> |
|
32 |
+ <tr v-for="(item, idx) in lists" :key="idx" :class="{ 'expired': item.hasRport }" @click="fnMoveTo('view', item.bsrpId)"> |
|
33 |
+ <td>{{ item.bsrpSeNm }}</td> |
|
34 |
+ <td>{{ item.bsrpPlace }}</td> |
|
35 |
+ <td>{{ item.bsrpPurps }}</td> |
|
36 |
+ <td>{{ item.bgnde }}</td> |
|
37 |
+ <td>{{ item.bsrpCnsulDTO.confmAtNm }}</td> |
|
38 |
+ <td>{{ item.hasRport ? '등록' : '미등록' }}</td> |
|
39 |
+ <td>{{ item.hasRport ? item.bsrpRportDTO.confmAt : '-' }}</td> |
|
72 | 40 |
</tr> |
73 | 41 |
</tbody> |
74 | 42 |
</table> |
75 |
- |
|
76 | 43 |
</div> |
77 |
- <div class="pagination"> |
|
78 |
- <ul> |
|
79 |
- <!-- 왼쪽 화살표 (이전 페이지) --> |
|
80 |
- <li class="arrow" :class="{ disabled: currentPage === 1 }" @click="changePage(currentPage - 1)"> |
|
81 |
- < |
|
82 |
- </li> |
|
83 |
- |
|
84 |
- <!-- 페이지 번호 --> |
|
85 |
- <li v-for="page in totalPages" :key="page" :class="{ active: currentPage === page }" |
|
86 |
- @click="changePage(page)"> |
|
87 |
- {{ page }} |
|
88 |
- </li> |
|
89 |
- |
|
90 |
- <!-- 오른쪽 화살표 (다음 페이지) --> |
|
91 |
- <li class="arrow" :class="{ disabled: currentPage === totalPages }" @click="changePage(currentPage + 1)"> |
|
92 |
- > |
|
93 |
- </li> |
|
94 |
- </ul> |
|
95 |
- </div> |
|
96 |
- |
|
44 |
+ <Pagenation :search="request" @onChange="fnChangeCurrentPage" /> |
|
97 | 45 |
</div> |
98 | 46 |
</div> |
99 | 47 |
</div> |
100 | 48 |
</template> |
101 |
- |
|
102 | 49 |
<script> |
103 |
-import { ref } from 'vue'; |
|
104 | 50 |
import { SearchOutlined } from '@ant-design/icons-vue'; |
105 |
-const currentYear = new Date().getFullYear(); |
|
106 |
-const currentMonth = new Date().getMonth() + 1; |
|
51 |
+// API |
|
52 |
+import { bsrpsProc } from '../../../../resources/api/bsrp'; |
|
53 |
+ |
|
107 | 54 |
export default { |
108 |
- data() { |
|
109 |
- return { |
|
110 |
- currentMonth, |
|
111 |
- selectedMonth: currentMonth, |
|
112 |
- remainingMonths: Array.from({ length: 12 }, (_, i) => i + 1), |
|
113 |
- currentYear, |
|
114 |
- selectedYear: currentYear, |
|
115 |
- remainingYears: Array.from({ length: 10 }, (_, i) => currentYear - i), |
|
116 |
- showOptions: false, |
|
117 |
- currentPage: 1, |
|
118 |
- totalPages: 3, |
|
119 |
- photoicon: "/client/resources/img/photo_icon.png", |
|
120 |
- // 데이터 초기화 |
|
121 |
- years: [2023, 2024, 2025], // 연도 목록 |
|
122 |
- months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // 월 목록 |
|
123 |
- selectedYear: '', |
|
124 |
- selectedMonth: '', |
|
125 |
- listData: [ |
|
126 |
- { |
|
127 |
- type: '연차', |
|
128 |
- where: '상주시청', |
|
129 |
- purpose: '유지보수', |
|
130 |
- period: '2025-05-10 ~ 2025-05-23', |
|
131 |
- pumuiStatue: '대기', |
|
132 |
- bokmyeong: '미등록', |
|
133 |
- status: '-' |
|
134 |
- }, |
|
135 |
- { |
|
136 |
- type: '연차', |
|
137 |
- where: '상주시청', |
|
138 |
- purpose: '유지보수', |
|
139 |
- period: '2025-05-10 ~ 2025-05-23', |
|
140 |
- pumuiStatue: '승인', |
|
141 |
- bokmyeong: '미등록', |
|
142 |
- status: '-' |
|
143 |
- }, |
|
144 |
- { |
|
145 |
- type: '연차', |
|
146 |
- where: '상주시청', |
|
147 |
- purpose: '유지보수', |
|
148 |
- period: '2025-05-10 ~ 2025-05-23', |
|
149 |
- pumuiStatue: '승인', |
|
150 |
- bokmyeong: '등록', |
|
151 |
- status: '대기' |
|
152 |
- }, { |
|
153 |
- type: '연차', |
|
154 |
- where: '상주시청', |
|
155 |
- purpose: '유지보수', |
|
156 |
- period: '2025-05-10 ~ 2025-05-10', |
|
157 |
- pumuiStatue: '승인', |
|
158 |
- bokmyeong: '등록', |
|
159 |
- status: '승인' |
|
160 |
- },], |
|
161 |
- filteredData: [], |
|
162 |
- }; |
|
163 |
- }, |
|
164 | 55 |
components: { |
165 | 56 |
SearchOutlined |
166 | 57 |
}, |
167 |
- computed: { |
|
168 |
- }, |
|
169 |
- methods: { |
|
170 |
- goToAttendancePage(item) { |
|
171 |
- this.$router.push({ name: 'AttendanceDetail', query: { id: item.id } }); |
|
172 |
- }, |
|
173 |
- changePage(page) { |
|
174 |
- if (page < 1 || page > this.totalPages) return; |
|
175 |
- this.currentPage = page; |
|
176 |
- this.$emit('page-changed', page); // 필요 시 부모에 알림 |
|
177 |
- }, |
|
178 |
- async onClickSubmit() { |
|
179 |
- // `useMutation` 훅을 사용하여 mutation 함수 가져오기 |
|
180 |
- const { mutate, onDone, onError } = useMutation(mygql); |
|
181 | 58 |
|
182 |
- try { |
|
183 |
- const result = await mutate(); |
|
184 |
- console.log(result); |
|
185 |
- } catch (error) { |
|
186 |
- console.error('Mutation error:', error); |
|
59 |
+ data() { |
|
60 |
+ return { |
|
61 |
+ photoicon: "/client/resources/img/photo_icon.png", |
|
62 |
+ |
|
63 |
+ years: [], |
|
64 |
+ months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], |
|
65 |
+ |
|
66 |
+ cmmnCodes: {}, |
|
67 |
+ request: { |
|
68 |
+ year: '', |
|
69 |
+ month: '', |
|
70 |
+ }, |
|
71 |
+ lists: [], |
|
72 |
+ }; |
|
73 |
+ }, |
|
74 |
+ |
|
75 |
+ watch: {}, |
|
76 |
+ |
|
77 |
+ created() { |
|
78 |
+ this.generateYears(); |
|
79 |
+ }, |
|
80 |
+ |
|
81 |
+ mounted() { |
|
82 |
+ this.findList(); // 목록 조회 |
|
83 |
+ }, |
|
84 |
+ |
|
85 |
+ methods: { |
|
86 |
+ generateYears() { |
|
87 |
+ const startYear = 2020; |
|
88 |
+ const currentYear = new Date().getFullYear(); |
|
89 |
+ |
|
90 |
+ for (let year = currentYear; year >= startYear; year--) { |
|
91 |
+ this.years.push(year); |
|
187 | 92 |
} |
188 | 93 |
}, |
189 | 94 |
|
190 |
- // 상태에 따른 클래스 반환 메소드 |
|
191 |
- getStatusClass(status) { |
|
192 |
- return status === 'active' ? 'status-active' : 'status-inactive'; |
|
95 |
+ // 목록 조회 |
|
96 |
+ async findList() { |
|
97 |
+ try { |
|
98 |
+ const response = await bsrpsProc(this.request); |
|
99 |
+ const result = response.data.data; |
|
100 |
+ |
|
101 |
+ this.lists = result.lists; |
|
102 |
+ this.request = result.search; |
|
103 |
+ } catch (error) { |
|
104 |
+ const message = error.response.data.message; |
|
105 |
+ alert(message); |
|
106 |
+ } |
|
193 | 107 |
}, |
194 |
- getStatusClass(status, pumuiStatue) { |
|
195 |
- // Check the 'status' and 'pumuiStatue' to return the correct class |
|
196 |
- if (pumuiStatue === '승인') return 'status-approved'; |
|
197 |
- if (pumuiStatue === '대기') return 'status-pending'; |
|
198 |
- // If no match, fallback to status-based class |
|
199 |
- if (status === '대기') return 'status-pending'; |
|
200 |
- if (status === '승인') return 'status-approved'; |
|
201 | 108 |
|
202 |
- // Default empty string |
|
203 |
- return ''; |
|
204 |
-}, |
|
205 |
-getBokmyeongClass(bokmyeong) { |
|
206 |
- if (bokmyeong === '등록') return 'status-approved'; |
|
207 |
- if (bokmyeong === '미등록') return 'status-pending'; |
|
208 |
- return ''; |
|
209 |
-}, |
|
210 |
-isPast(item) { |
|
211 |
- return ( |
|
212 |
- item.pumuiStatue === '승인' && |
|
213 |
- item.bokmyeong === '등록' && |
|
214 |
- item.status === '승인' |
|
215 |
- ); |
|
216 |
- }, |
|
217 |
- handleClick(item) { |
|
218 |
- const isCasePumui = ( |
|
219 |
- (item.pumuiStatue === '대기' && item.bokmyeong === '미등록') || |
|
220 |
- (item.pumuiStatue === '승인' && item.bokmyeong === '미등록') |
|
221 |
- ); |
|
109 |
+ // 페이지 이동 |
|
110 |
+ fnChangeCurrentPage(currentPage) { |
|
111 |
+ this.request.currentPage = Number(currentPage); |
|
112 |
+ this.$nextTick(() => { |
|
113 |
+ this.findList(); |
|
114 |
+ }); |
|
115 |
+ }, |
|
222 | 116 |
|
223 |
- if (item.bokmyeong === '등록') { |
|
224 |
- this.$router.push({ name: 'ChuljangDetailAll' }); |
|
225 |
- } else if (isCasePumui) { |
|
226 |
- this.$router.push({ name: 'ChuljangPumuiDetail' }); |
|
227 |
- } else { |
|
228 |
- console.log('이동 조건이 아닙니다.'); |
|
229 |
- } |
|
230 |
-}, |
|
231 |
- handleBokmyeongClick(item) { |
|
232 |
- this.$router.push({ name: 'ChuljangBokmyeongDetail' }); |
|
233 |
- }, |
|
234 |
- }, |
|
235 |
- created() { |
|
236 |
- }, |
|
237 |
- mounted() { |
|
117 |
+ fnMoveTo(type, id) { |
|
118 |
+ const routes = { |
|
119 |
+ 'list': { name: 'ChuljangStatue' }, |
|
120 |
+ 'view': { name: 'ChuljangDetailAll', query: { id } }, |
|
121 |
+ 'edit': { name: 'ChuljangInsert', query: this.$isEmpty(id) ? {} : { id } }, |
|
122 |
+ }; |
|
238 | 123 |
|
239 |
- |
|
124 |
+ if (routes[type]) { |
|
125 |
+ if (!this.$isEmpty(this.pageId) && type === 'list') { |
|
126 |
+ this.$router.push({ name: 'ChuljangDetailAll', query: { id: this.pageId } }); |
|
127 |
+ return; |
|
128 |
+ } |
|
129 |
+ this.$router.push(routes[type]); |
|
130 |
+ } else { |
|
131 |
+ alert("올바르지 않은 경로를 요청하여 목록으로 이동합니다."); |
|
132 |
+ this.$router.push(routes['list']); |
|
133 |
+ } |
|
134 |
+ }, |
|
240 | 135 |
}, |
241 |
- |
|
242 | 136 |
}; |
243 | 137 |
</script> |
244 |
- |
|
245 | 138 |
<style scoped> |
246 | 139 |
tr { |
247 | 140 |
cursor: pointer; |
--- client/views/pages/Manager/attendance/HyugaDetail.vue
+++ client/views/pages/Manager/attendance/HyugaDetail.vue
... | ... | @@ -76,7 +76,7 @@ |
76 | 76 |
<div class="buttons"> |
77 | 77 |
<button v-if="detailData.confmAt === 'W' || detailData.confmAt === 'R'" class="btn btn-red" type="button" @click="deleteData">신청취소</button> |
78 | 78 |
<button v-if="detailData.confmAt === 'W'" class="btn secondary" type="button" @click="fnMoveTo('edit', pageId)">수정</button> |
79 |
- <button v-if="detailData.confmAt === 'R'" class="btn secondary" type="button" @click="reSave">재신청</button> |
|
79 |
+ <button v-if="detailData.confmAt === 'R'" class="btn secondary" type="button" @click="fnMoveTo('edit', pageId)">재신청</button> |
|
80 | 80 |
<button class="btn tertiary" type="button" @click="fnMoveTo('list')">목록</button> |
81 | 81 |
</div> |
82 | 82 |
<ReturnPopup v-if="showPopup" @close="showPopup = false" /> |
... | ... | @@ -223,6 +223,11 @@ |
223 | 223 |
|
224 | 224 |
// 삭제 |
225 | 225 |
async deleteData() { |
226 |
+ const isCheck = confirm("삭제하시겠습니까?"); |
|
227 |
+ if (!isCheck) { |
|
228 |
+ return; |
|
229 |
+ } |
|
230 |
+ |
|
226 | 231 |
try { |
227 | 232 |
const response = await deleteVcatnProc(this.pageId); |
228 | 233 |
|
... | ... | @@ -236,11 +241,6 @@ |
236 | 241 |
console.error(error.message); |
237 | 242 |
this.fnMoveTo('list'); |
238 | 243 |
} |
239 |
- }, |
|
240 |
- |
|
241 |
- // 재신청 |
|
242 |
- reSave() { |
|
243 |
- this.$router.push({ name: 'hyugaInsert', query: { id: this.pageId } }); |
|
244 | 244 |
}, |
245 | 245 |
|
246 | 246 |
// 페이지 이동 |
--- client/views/pages/Manager/attendance/HyugaInsert.vue
+++ client/views/pages/Manager/attendance/HyugaInsert.vue
... | ... | @@ -25,8 +25,8 @@ |
25 | 25 |
</label> |
26 | 26 |
<div class="d-flex gap-1"> |
27 | 27 |
<input type="date" class="form-control" id="bgnde" v-model="editData.bgnde" @keydown="preventKeyboard" /> |
28 |
- <input type="text" class="form-control" placeholder="시" v-model="editData.beginHour" readonly /> |
|
29 |
- <input type="text" class="form-control" placeholder="분" v-model="editData.beginMnt" readonly /> |
|
28 |
+ <input type="text" class="form-control" placeholder="시" style="width: 100px;" v-model="editData.beginHour" readonly /> |
|
29 |
+ <input type="text" class="form-control" placeholder="분" style="width: 100px;" v-model="editData.beginMnt" readonly /> |
|
30 | 30 |
</div> |
31 | 31 |
</div> |
32 | 32 |
<div class="col-12"> |
... | ... | @@ -35,8 +35,8 @@ |
35 | 35 |
</label> |
36 | 36 |
<div class="d-flex gap-1"> |
37 | 37 |
<input type="date" class="form-control" id="endde" v-model="editData.endde" :readonly="dayCnt === 0.5" @keydown="preventKeyboard" /> |
38 |
- <input type="text" class="form-control" placeholder="시" v-model="editData.endHour" readonly /> |
|
39 |
- <input type="text" class="form-control" placeholder="분" v-model="editData.endMnt" readonly /> |
|
38 |
+ <input type="text" class="form-control" placeholder="시" style="width: 100px;" v-model="editData.endHour" readonly /> |
|
39 |
+ <input type="text" class="form-control" placeholder="분" style="width: 100px;" v-model="editData.endMnt" readonly /> |
|
40 | 40 |
</div> |
41 | 41 |
</div> |
42 | 42 |
<div class="col-12"> |
... | ... | @@ -50,7 +50,7 @@ |
50 | 50 |
<PlusCircleFilled /> |
51 | 51 |
</button> |
52 | 52 |
</label> |
53 |
- <HrPopup v-if="isOpenModal" :sanctns="editData.sanctnList" @onSelected="fnAddSanctn" @close="isOpenModal = false" /> |
|
53 |
+ <HrPopup v-if="isOpenModal && isCodesLoaded" :sanctns="editData.sanctnList" :cmmnCodes="cmmnCodes" @onSelected="fnAddSanctn" @close="isOpenModal = false" /> |
|
54 | 54 |
<div class="approval-container"> |
55 | 55 |
<SanctnList v-model:sanctns="editData.sanctnList" @delSanctn="fnDelSanctn" /> |
56 | 56 |
</div> |
... | ... | @@ -65,7 +65,7 @@ |
65 | 65 |
<div class="buttons"> |
66 | 66 |
<button type="button" class="btn btn-red" @click="fnRecord">이전 승인자 불러오기</button> |
67 | 67 |
<button type="button" class="btn primary" @click="fnSave">신청</button> |
68 |
- <button type="reset" class="btn tertiary" @click="fnMoveTo('list')">취소</button> |
|
68 |
+ <button type="button" class="btn tertiary" @click="fnMoveTo('list')">취소</button> |
|
69 | 69 |
</div> |
70 | 70 |
</div> |
71 | 71 |
</div> |
... | ... | @@ -73,7 +73,7 @@ |
73 | 73 |
<script> |
74 | 74 |
import { PlusCircleFilled, CloseOutlined } from '@ant-design/icons-vue'; |
75 | 75 |
import HrPopup from '../../../component/Popup/HrPopup.vue'; |
76 |
-import SanctnList from '../../../component/Sanctn/SanctnList.vue'; |
|
76 |
+import SanctnList from '../../../component/Sanctn/SanctnFormList.vue'; |
|
77 | 77 |
import EditorComponent from '../../../component/editor/EditorComponent.vue'; |
78 | 78 |
// API |
79 | 79 |
import { findVcatnProc, saveVcatnProc, findLastVcatnProc, updateVcatnProc } from '../../../../resources/api/vcatn'; |
... | ... | @@ -126,6 +126,11 @@ |
126 | 126 |
// 세부 유형 선택박스를 보여줄지 여부 |
127 | 127 |
showSubTypeSelect() { |
128 | 128 |
return this.dayCnt === 0.5 && this.subTypes.length > 0; |
129 |
+ }, |
|
130 |
+ |
|
131 |
+ // cmmnCodes가 로드되었는지 확인 |
|
132 |
+ isCodesLoaded() { |
|
133 |
+ return this.cmmnCodes && Object.keys(this.cmmnCodes).length > 0; |
|
129 | 134 |
} |
130 | 135 |
}, |
131 | 136 |
|
... | ... | @@ -154,7 +159,7 @@ |
154 | 159 |
}, |
155 | 160 |
'editData.endde'(newVal, oldVal) { |
156 | 161 |
if (newVal !== oldVal) { |
157 |
- this.validateAndCalculateDays(); // calculateTotalDays 대신 validateAndCalculateDays 호출 |
|
162 |
+ this.validateAndCalculateDays(); |
|
158 | 163 |
} |
159 | 164 |
}, |
160 | 165 |
}, |
... | ... | @@ -171,7 +176,6 @@ |
171 | 176 |
|
172 | 177 |
let sanctns = []; |
173 | 178 |
for (let sanctn of this.editData.sanctnList) { |
174 |
- console.log("sanctn: ", sanctn); |
|
175 | 179 |
let data = { |
176 | 180 |
confmerId: sanctn.confmerId, |
177 | 181 |
clsf: sanctn.clsf, |
... | ... | @@ -184,12 +188,7 @@ |
184 | 188 |
} |
185 | 189 |
this.editData.sanctnList = sanctns; |
186 | 190 |
|
187 |
- // cmmnCodes가 초기화되었는지 확인 후 호출 |
|
188 |
- if (this.cmmnCodes && this.cmmnCodes.vcatnKndCodeList) { |
|
189 |
- await this.fnOnchangeVcatnKnd(); |
|
190 |
- } else { |
|
191 |
- console.warn('cmmnCodes가 아직 초기화되지 않았습니다.'); |
|
192 |
- } |
|
191 |
+ await this.fnOnchangeVcatnKnd(); |
|
193 | 192 |
} catch (error) { |
194 | 193 |
console.error('데이터 조회 실패:', error); |
195 | 194 |
const message = error.response?.data?.message || "데이터를 불러오는데 실패했습니다."; |
... | ... | @@ -200,9 +199,8 @@ |
200 | 199 |
|
201 | 200 |
// 유형 변경 |
202 | 201 |
async fnOnchangeVcatnKnd() { |
203 |
- // 안전성 검사 추가 |
|
204 |
- if (!this.cmmnCodes || !this.cmmnCodes.vcatnKndCodeList || !Array.isArray(this.cmmnCodes.vcatnKndCodeList)) { |
|
205 |
- console.warn('cmmnCodes.vcatnKndCodeList가 초기화되지 않았거나 배열이 아닙니다.'); |
|
202 |
+ if (!this.isCodesLoaded || !Array.isArray(this.cmmnCodes.vcatnKndCodeList)) { |
|
203 |
+ console.warn('cmmnCodes가 초기화되지 않았습니다.'); |
|
206 | 204 |
return; |
207 | 205 |
} |
208 | 206 |
|
... | ... | @@ -217,7 +215,6 @@ |
217 | 215 |
if (this.dayCnt === 0.5) { |
218 | 216 |
this.changeBgnde(); // 반차일 경우 종료일을 시작일과 동일하게 설정 |
219 | 217 |
} else { |
220 |
- // 반차가 아닌 경우 |
|
221 | 218 |
this.editData.vcatnSubKnd = ''; // 세부 유형 선택 초기화 |
222 | 219 |
|
223 | 220 |
// 전체 근무시간으로 설정 |
... | ... | @@ -314,7 +311,6 @@ |
314 | 311 |
|
315 | 312 |
// 날짜 유효성 검사 |
316 | 313 |
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) { |
317 |
- console.warn('유효하지 않은 날짜입니다.'); |
|
318 | 314 |
this.totalDays = 0; |
319 | 315 |
return; |
320 | 316 |
} |
... | ... | @@ -360,7 +356,18 @@ |
360 | 356 |
} |
361 | 357 |
}, |
362 | 358 |
|
363 |
- fnAddSanctn(data) { |
|
359 |
+ // 승인자 |
|
360 |
+ fnAddSanctn(user) { |
|
361 |
+ const data = { |
|
362 |
+ confmerId: user.userId, |
|
363 |
+ clsf: user.clsf, |
|
364 |
+ sanctnOrdr: this.editData.nmprList.length + 1, |
|
365 |
+ sanctnSe: this.cmmnCodes.sanctnCodeList[0].code, |
|
366 |
+ |
|
367 |
+ clsfNm: this.formatClsf(user.clsf), |
|
368 |
+ userNm: user.userNm, |
|
369 |
+ }; |
|
370 |
+ |
|
364 | 371 |
this.editData.sanctnList.push(data); |
365 | 372 |
this.isOpenModal = false; |
366 | 373 |
}, |
... | ... | @@ -372,6 +379,12 @@ |
372 | 379 |
}); |
373 | 380 |
}, |
374 | 381 |
|
382 |
+ // 직급 매칭 |
|
383 |
+ formatClsf(code) { |
|
384 |
+ const clsfCode = this.cmmnCodes?.clsfCodeList?.find(item => item.code === code); |
|
385 |
+ return clsfCode?.codeNm || code; |
|
386 |
+ }, |
|
387 |
+ |
|
375 | 388 |
async fnRecord() { |
376 | 389 |
try { |
377 | 390 |
const response = await findLastVcatnProc(); |
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?